From 344aa22bd2ed2204f2763b6f4f01e3872e60458b Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 19 Aug 2016 00:12:01 +0100 Subject: [PATCH 001/264] qemu: Add the qemu package The qemu package is a self contained package used for launching, halting and managing qemu instances. Signed-off-by: Mark Ryan --- qmp.go | 600 ++++++++++++++++++++++++++++++++++++++++++++ qmp_test.go | 697 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1297 insertions(+) create mode 100644 qmp.go create mode 100644 qmp_test.go diff --git a/qmp.go b/qmp.go new file mode 100644 index 0000000000..11ca1ebbd1 --- /dev/null +++ b/qmp.go @@ -0,0 +1,600 @@ +/* +// 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 ( + "bufio" + "container/list" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "time" + + "golang.org/x/net/context" +) + +// Code to launch qemu +// move to package and document + +// QMPLog is a logging interface used by the qemu package to log various +// interesting pieces of information. Rather than introduce a dependency +// on a given logging package, qemu presents this interface that allows +// clients to provide their own logging type which they can use to +// seamlessly integrate qemu's logs into their own logs. A QMPLog +// implementation can be specified in the QMPConfig structure. +type QMPLog interface { + // V returns true if the given argument is less than or equal + // to the implementation's defined verbosity level. + V(int32) bool + + // Infof writes informational output to the log. A newline will be + // added to the output if one is not provided. + Infof(string, ...interface{}) + + // Warningf writes warning output to the log. A newline will be + // added to the output if one is not provided. + Warningf(string, ...interface{}) + + // Errorf writes error output to the log. A newline will be + // added to the output if one is not provided. + Errorf(string, ...interface{}) +} + +type qmpNullLogger struct{} + +func (l qmpNullLogger) V(level int32) bool { + return false +} + +func (l qmpNullLogger) Infof(format string, v ...interface{}) { +} + +func (l qmpNullLogger) Warningf(format string, v ...interface{}) { +} + +func (l qmpNullLogger) Errorf(format string, v ...interface{}) { +} + +// QMPConfig is a configuration structure that can be used to specify a +// logger and a channel to which logs and QMP events are to be sent. If +// neither of these fields are specified, or are set to nil, no logs will be +// written and no QMP events will be reported to the client. +type QMPConfig struct { + // eventCh can be specified by clients who wish to receive QMP + // events. + EventCh chan<- QMPEvent + + // logger is used by the qmpStart function and all the go routines + // it spawns to log information. + Logger QMPLog +} + +type qmpEventFilter struct { + eventName string + dataKey string + dataValue string +} + +// QMPEvent contains a single QMP event, sent on the QMPConfig.EventCh channel. +type QMPEvent struct { + // The name of the event, e.g., DEVICE_DELETED + Name string + + // The data associated with the event. The contents of this map are + // unprocessed by the qemu package. It is simply the result of + // unmarshalling the QMP json event. Here's an example map + // map[string]interface{}{ + // "driver": "virtio-blk-pci", + // "drive": "drive_3437843748734873483", + // } + Data map[string]interface{} + + // The event's timestamp converted to a time.Time object. + Timestamp time.Time +} + +type qmpResult struct { + err error + data map[string]interface{} +} + +type qmpCommand struct { + ctx context.Context + res chan qmpResult + name string + args map[string]interface{} + filter *qmpEventFilter + resultReceived bool +} + +// QMP is a structure that contains the internal state used by startQMPLoop and +// the go routines it spwans. All the contents of this structure are private. +type QMP struct { + cmdCh chan qmpCommand + conn io.ReadWriteCloser + cfg QMPConfig + connectedCh chan<- *QMPVersion + disconnectedCh chan struct{} +} + +// QMPVersion contains the version number and the capabailities of a QEMU +// instance, as reported in the QMP greeting message. +type QMPVersion struct { + Major int + Minor int + Micro int + Capabilities []string +} + +func (q *QMP) readLoop(fromVMCh chan<- []byte) { + scanner := bufio.NewScanner(q.conn) + for scanner.Scan() { + line := scanner.Bytes() + if q.cfg.Logger.V(1) { + q.cfg.Logger.Infof("%s", string(line)) + } + fromVMCh <- line + } + close(fromVMCh) +} + +func (q *QMP) processQMPEvent(cmdQueue *list.List, name interface{}, data interface{}, + timestamp interface{}) { + + strname, ok := name.(string) + if !ok { + return + } + + var eventData map[string]interface{} + if data != nil { + eventData, _ = data.(map[string]interface{}) + } + + cmdEl := cmdQueue.Front() + if cmdEl != nil { + cmd := cmdEl.Value.(*qmpCommand) + filter := cmd.filter + if filter != nil { + if filter.eventName == strname { + match := filter.dataKey == "" + if !match && eventData != nil { + match = eventData[filter.dataKey] == filter.dataValue + } + if match { + if cmd.resultReceived { + q.finaliseCommand(cmdEl, cmdQueue, true) + } else { + cmd.filter = nil + } + } + } + } + } + + if q.cfg.EventCh != nil { + ev := QMPEvent{ + Name: strname, + Data: eventData, + } + if timestamp != nil { + timestamp, ok := timestamp.(map[string]interface{}) + if ok { + seconds, _ := timestamp["seconds"].(float64) + microseconds, _ := timestamp["microseconds"].(float64) + ev.Timestamp = time.Unix(int64(seconds), int64(microseconds)) + } + } + + q.cfg.EventCh <- ev + } +} + +func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeeded bool) { + cmd := cmdEl.Value.(*qmpCommand) + cmdQueue.Remove(cmdEl) + select { + case <-cmd.ctx.Done(): + default: + if succeeded { + cmd.res <- qmpResult{} + } else { + cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed")} + } + } + if cmdQueue.Len() > 0 { + q.writeNextQMPCommand(cmdQueue) + } +} + +func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { + var vmData map[string]interface{} + err := json.Unmarshal(line, &vmData) + if err != nil { + q.cfg.Logger.Warningf("Unable to decode response [%s] from VM: %v", + string(line), err) + return + } + if evname, found := vmData["event"]; found { + q.processQMPEvent(cmdQueue, evname, vmData["data"], vmData["timestamp"]) + return + } + + _, succeeded := vmData["return"] + _, failed := vmData["error"] + + if !succeeded && !failed { + return + } + + cmdEl := cmdQueue.Front() + if cmdEl == nil { + q.cfg.Logger.Warningf("Unexpected command response received [%s] from VM", + string(line)) + return + } + cmd := cmdEl.Value.(*qmpCommand) + if failed || cmd.filter == nil { + q.finaliseCommand(cmdEl, cmdQueue, succeeded) + } else { + cmd.resultReceived = true + } +} + +func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { + cmdEl := cmdQueue.Front() + cmd := cmdEl.Value.(*qmpCommand) + cmdData := make(map[string]interface{}) + cmdData["execute"] = cmd.name + if cmd.args != nil { + cmdData["arguments"] = cmd.args + } + encodedCmd, err := json.Marshal(&cmdData) + if err != nil { + cmd.res <- qmpResult{ + err: fmt.Errorf("Unable to marhsall command %s: %v", + cmd.name, err), + } + cmdQueue.Remove(cmdEl) + } + q.cfg.Logger.Infof("%s", string(encodedCmd)) + encodedCmd = append(encodedCmd, '\n') + _, err = q.conn.Write(encodedCmd) + if err != nil { + cmd.res <- qmpResult{ + err: fmt.Errorf("Unable to write command to qmp socket %v", err), + } + cmdQueue.Remove(cmdEl) + } +} + +func failOutstandingCommands(cmdQueue *list.List) { + for e := cmdQueue.Front(); e != nil; e = e.Next() { + cmd := e.Value.(*qmpCommand) + select { + case cmd.res <- qmpResult{ + err: errors.New("exitting QMP loop, command cancelled"), + }: + case <-cmd.ctx.Done(): + } + } +} + +func (q *QMP) parseVersion(version []byte) *QMPVersion { + var qmp map[string]interface{} + err := json.Unmarshal(version, &qmp) + if err != nil { + q.cfg.Logger.Errorf("Invalid QMP greeting: %s", string(version)) + return nil + } + + versionMap := qmp + for _, k := range []string{"QMP", "version", "qemu"} { + versionMap, _ = versionMap[k].(map[string]interface{}) + if versionMap == nil { + q.cfg.Logger.Errorf("Invalid QMP greeting: %s", string(version)) + return nil + } + } + + micro, _ := versionMap["micro"].(float64) + minor, _ := versionMap["minor"].(float64) + major, _ := versionMap["major"].(float64) + capabilities, _ := qmp["QMP"].(map[string]interface{})["capabilities"].([]interface{}) + stringcaps := make([]string, 0, len(capabilities)) + for _, c := range capabilities { + if cap, ok := c.(string); ok { + stringcaps = append(stringcaps, cap) + } + } + return &QMPVersion{Major: int(major), + Minor: int(minor), + Micro: int(micro), + Capabilities: stringcaps, + } +} + +func (q *QMP) mainLoop() { + cmdQueue := list.New().Init() + fromVMCh := make(chan []byte) + go q.readLoop(fromVMCh) + + defer func() { + if q.cfg.EventCh != nil { + close(q.cfg.EventCh) + } + _ = q.conn.Close() + _ = <-fromVMCh + failOutstandingCommands(cmdQueue) + close(q.disconnectedCh) + }() + + version := []byte{} + +DONE: + for { + ok := false + select { + case cmd, ok := <-q.cmdCh: + if !ok { + return + } + _ = cmdQueue.PushBack(&cmd) + case version, ok = <-fromVMCh: + if !ok { + return + } + if cmdQueue.Len() >= 1 { + q.writeNextQMPCommand(cmdQueue) + } + break DONE + } + } + + q.connectedCh <- q.parseVersion(version) + + for { + select { + case cmd, ok := <-q.cmdCh: + if !ok { + return + } + _ = cmdQueue.PushBack(&cmd) + if cmdQueue.Len() >= 1 { + q.writeNextQMPCommand(cmdQueue) + } + case line, ok := <-fromVMCh: + if !ok { + return + } + q.processQMPInput(line, cmdQueue) + } + } +} + +func startQMPLoop(conn io.ReadWriteCloser, cfg QMPConfig, + connectedCh chan<- *QMPVersion, disconnectedCh chan struct{}) *QMP { + q := &QMP{ + cmdCh: make(chan qmpCommand), + conn: conn, + cfg: cfg, + connectedCh: connectedCh, + disconnectedCh: disconnectedCh, + } + go q.mainLoop() + return q +} + +func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]interface{}, + filter *qmpEventFilter) error { + var err error + resCh := make(chan qmpResult) + select { + case <-q.disconnectedCh: + err = errors.New("exitting QMP loop, command cancelled") + case q.cmdCh <- qmpCommand{ + ctx: ctx, + res: resCh, + name: name, + args: args, + filter: filter, + }: + } + + if err != nil { + return err + } + + select { + case res := <-resCh: + err = res.err + case <-ctx.Done(): + err = ctx.Err() + } + + return err +} + +// QMPStart connects to a unix domain socket maintained by a QMP instance. It +// waits to receive the QMP welcome message via the socket and spawns some go +// routines to manage the socket. The function returns a *QMP which can be +// used by callers to send commands to the QEMU instance or to close the +// socket and all the go routines that have been spawned to monitor it. A +// *QMPVersion is also returned. This structure contains the version and +// capabilities information returned by the QEMU instance in its welcome +// message. +// +// socket contains the path to the domain socket. cfg contains some options +// that can be specified by the caller, namely where the qemu package should +// send logs and QMP events. disconnectedCh is a channel that must be supplied +// by the caller. It is closed when an error occurs openning or writing to +// or reading from the unix domain socket. This implies that the QEMU instance +// that opened the socket has closed. +// +// If this function returns without error, callers should call QMP.Shutdown +// when they wish to stop monitoring the QMP instance. This is not strictly +// necessary if the QEMU instance exits and the disconnectedCh is closed, but +// doing so will not cause any problems. +// +// Commands can be sent to the QEMU instance via the QMP.Execute methods. +// These commands are executed serially, even if the QMP.Execute methods +// are called from different go routines. The QMP.Execute methods will +// block until they have received a success or failure message from QMP, +// i.e., {"return": {}} or {"error":{}}, and in some cases certain events +// are received. +func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh chan struct{}) (*QMP, *QMPVersion, error) { + if cfg.Logger == nil { + cfg.Logger = qmpNullLogger{} + } + dialer := net.Dialer{Cancel: ctx.Done()} + conn, err := dialer.Dial("unix", socket) + if err != nil { + cfg.Logger.Warningf("Unable to connect to unix socket (%s): %v", socket, err) + close(disconnectedCh) + return nil, nil, err + } + + connectedCh := make(chan *QMPVersion) + + var version *QMPVersion + q := startQMPLoop(conn, cfg, connectedCh, disconnectedCh) + select { + case <-ctx.Done(): + q.Shutdown() + <-disconnectedCh + return nil, nil, fmt.Errorf("Canceled by caller") + case <-disconnectedCh: + return nil, nil, fmt.Errorf("Lost connection to VM") + case version = <-connectedCh: + if version == nil { + return nil, nil, fmt.Errorf("Failed to find QMP version information") + } + } + + return q, version, nil +} + +// Shutdown closes the domain socket used to monitor a QEMU instance and +// terminates all the go routines spawned by QMPStart to manage that instance. +// QMP.Shutdown does not shut down the running instance. Calling QMP.Shutdown +// will result in the disconnectedCh channel being closed, indicating that we +// have lost connection to the QMP instance. In this case it does not indicate +// that the instance has quit. +// +// QMP.Shutdown should not be called concurrently with other QMP methods. It +// should not be called twice on the same QMP instance. +// +// Calling QMP.Shutdown after the disconnectedCh channel is closed is permitted but +// will not have any effect. +func (q *QMP) Shutdown() { + close(q.cmdCh) +} + +// ExecuteQMPCapabilities executes the qmp_capabilities command on the instance. +func (q *QMP) ExecuteQMPCapabilities(ctx context.Context) error { + return q.executeCommand(ctx, "qmp_capabilities", nil, nil) +} + +// ExecuteStop sends the stop command to the instance. +func (q *QMP) ExecuteStop(ctx context.Context) error { + return q.executeCommand(ctx, "stop", nil, nil) +} + +// ExecuteCont sends the cont command to the instance. +func (q *QMP) ExecuteCont(ctx context.Context) error { + return q.executeCommand(ctx, "cont", nil, nil) +} + +// ExecuteSystemPowerdown sends the system_powerdown command to the instance. +// This function will block until the SHUTDOWN event is received. +func (q *QMP) ExecuteSystemPowerdown(ctx context.Context) error { + filter := &qmpEventFilter{ + eventName: "SHUTDOWN", + } + return q.executeCommand(ctx, "system_powerdown", nil, filter) +} + +// ExecuteQuit sends the quit command to the instance, terminating +// the QMP instance immediately. +func (q *QMP) ExecuteQuit(ctx context.Context) error { + return q.executeCommand(ctx, "quit", nil, nil) +} + +// ExecuteBlockdevAdd sends a blockdev-add to the QEMU instance. device is the +// path of the device to add, e.g., /dev/rdb0, and blockdevID is an identifier +// used to name the device. As this identifier will be passed directly to QMP, +// it must obey QMP's naming rules, e,g., it must start with a letter. +func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) error { + args := map[string]interface{}{ + "options": map[string]interface{}{ + "driver": "raw", + "file": map[string]interface{}{ + "driver": "file", + "filename": device, + }, + "id": blockdevID, + }, + } + return q.executeCommand(ctx, "blockdev-add", args, nil) +} + +// ExecuteDeviceAdd adds the guest portion of a device to a QEMU instance +// using the device_add command. blockdevID should match the blockdevID passed +// to a previous call to ExecuteBlockdevAdd. devID is the id of the device to +// add. Both strings must be valid QMP identifiers. driver is the name of the +// driver,e.g., virtio-blk-pci, and bus is the name of the bus. bus is optional. +func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string) error { + args := map[string]interface{}{ + "id": devID, + "driver": driver, + "drive": blockdevID, + } + if bus != "" { + args["bus"] = bus + } + return q.executeCommand(ctx, "device_add", args, nil) +} + +// ExecuteXBlockdevDel deletes a block device by sending a x-blockdev-del command. +// blockdevID is the id of the block device to be deleted. Typically, this will +// match the id passed to ExecuteBlockdevAdd. It must be a valid QMP id. +func (q *QMP) ExecuteXBlockdevDel(ctx context.Context, blockdevID string) error { + args := map[string]interface{}{ + "id": blockdevID, + } + return q.executeCommand(ctx, "x-blockdev-del", args, nil) +} + +// ExecuteDeviceDel deletes guest portion of a QEMU device by sending a +// device_del command. devId is the identifier of the device to delete. +// Typically it would match the devID parameter passed to an earlier call +// to ExecuteDeviceAdd. It must be a valid QMP identidier. +// +// This method blocks until a DEVICE_DELETED event is received for devID. +func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { + args := map[string]interface{}{ + "id": devID, + } + filter := &qmpEventFilter{ + eventName: "DEVICE_DELETED", + dataKey: "device", + dataValue: devID, + } + return q.executeCommand(ctx, "device_del", args, filter) +} diff --git a/qmp_test.go b/qmp_test.go new file mode 100644 index 0000000000..9519a176ee --- /dev/null +++ b/qmp_test.go @@ -0,0 +1,697 @@ +/* +// 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 ( + "bytes" + "encoding/json" + "errors" + "fmt" + "log" + "sync" + "testing" + "time" + + "golang.org/x/net/context" + + "github.com/01org/ciao/testutil" +) + +const ( + microStr = "50" + minorStr = "6" + majorStr = "2" + micro = 50 + minor = 6 + major = 2 + cap1 = "one" + cap2 = "two" + qmpHello = `{ "QMP": { "version": { "qemu": { "micro": ` + microStr + `, "minor": ` + minorStr + `, "major": ` + majorStr + ` }, "package": ""}, "capabilities": ["` + cap1 + `","` + cap2 + `"]}}` + "\n" + qmpSuccess = `{ "return": {}}` + "\n" + qmpFailure = `{ "error": {}}` + "\n" +) + +type qmpTestLogger struct{} + +func (l qmpTestLogger) V(level int32) bool { + return true +} + +func (l qmpTestLogger) Infof(format string, v ...interface{}) { + log.Printf(format, v...) +} + +func (l qmpTestLogger) Warningf(format string, v ...interface{}) { + l.Infof(format, v) +} + +func (l qmpTestLogger) Errorf(format string, v ...interface{}) { + l.Infof(format, v) +} + +type qmpTestCommand struct { + name string + args map[string]interface{} +} + +type qmpTestEvent struct { + name string + data map[string]interface{} + timestamp map[string]interface{} + after time.Duration +} + +type qmpTestResult struct { + result string + data map[string]interface{} +} + +type qmpTestCommandBuffer struct { + newDataCh chan []byte + t *testing.T + buf *bytes.Buffer + cmds []qmpTestCommand + events []qmpTestEvent + results []qmpTestResult + currentCmd int + forceFail chan struct{} +} + +func newQMPTestCommandBuffer(t *testing.T) *qmpTestCommandBuffer { + b := &qmpTestCommandBuffer{ + newDataCh: make(chan []byte, 1), + t: t, + buf: bytes.NewBuffer([]byte{}), + forceFail: make(chan struct{}), + } + b.cmds = make([]qmpTestCommand, 0, 8) + b.events = make([]qmpTestEvent, 0, 8) + b.results = make([]qmpTestResult, 0, 8) + b.newDataCh <- []byte(qmpHello) + return b +} + +func (b *qmpTestCommandBuffer) startEventLoop(wg *sync.WaitGroup) { + wg.Add(1) + go func() { + for _, ev := range b.events { + time.Sleep(ev.after) + eventMap := map[string]interface{}{ + "event": ev.name, + } + + if ev.data != nil { + eventMap["data"] = ev.data + } + + if ev.timestamp != nil { + eventMap["timestamp"] = ev.timestamp + } + + encodedEvent, err := json.Marshal(&eventMap) + if err != nil { + b.t.Errorf("Unable to encode event: %v", err) + } + encodedEvent = append(encodedEvent, '\n') + b.newDataCh <- encodedEvent + } + wg.Done() + }() +} + +func (b *qmpTestCommandBuffer) AddCommmand(name string, args map[string]interface{}, + result string, data map[string]interface{}) { + b.cmds = append(b.cmds, qmpTestCommand{name, args}) + if data == nil { + data = make(map[string]interface{}) + } + b.results = append(b.results, qmpTestResult{result, data}) +} + +func (b *qmpTestCommandBuffer) AddEvent(name string, after time.Duration, + data map[string]interface{}, timestamp map[string]interface{}) { + b.events = append(b.events, qmpTestEvent{ + name: name, + data: data, + timestamp: timestamp, + after: after, + }) +} + +func (b *qmpTestCommandBuffer) Close() error { + close(b.newDataCh) + return nil +} + +func (b *qmpTestCommandBuffer) Read(p []byte) (n int, err error) { + if b.buf.Len() == 0 { + ok := false + var data []byte + select { + case <-b.forceFail: + return 0, errors.New("Connection shutdown") + case data, ok = <-b.newDataCh: + select { + case <-b.forceFail: + return 0, errors.New("Connection shutdown") + default: + } + } + if !ok { + return 0, nil + } + _, err := b.buf.Write(data) + if err != nil { + if err != nil { + b.t.Errorf("Unable to buffer result: %v", err) + } + } + } + return b.buf.Read(p) +} + +func (b *qmpTestCommandBuffer) Write(p []byte) (int, error) { + var cmdJSON map[string]interface{} + if b.currentCmd >= len(b.cmds) { + b.t.Fatalf("Unexpected command") + } + err := json.Unmarshal(p, &cmdJSON) + if err != nil { + b.t.Fatalf("Unexpected command") + } + cmdName := cmdJSON["execute"] + gotCmdName := cmdName.(string) + result := b.results[b.currentCmd].result + if gotCmdName != b.cmds[b.currentCmd].name { + b.t.Errorf("Unexpected command. Expected %s found %s", + b.cmds[b.currentCmd].name, gotCmdName) + result = "error" + } + resultMap := make(map[string]interface{}) + resultMap[result] = b.results[b.currentCmd].data + encodedRes, err := json.Marshal(&resultMap) + if err != nil { + b.t.Errorf("Unable to encode result: %v", err) + } + encodedRes = append(encodedRes, '\n') + b.newDataCh <- encodedRes + return len(p), nil +} + +func checkVersion(t *testing.T, connectedCh <-chan *QMPVersion) { + var version *QMPVersion + select { + case <-time.After(time.Second): + t.Fatal("Timed out waiting for qmp to connect") + case version = <-connectedCh: + } + + if version == nil { + t.Fatal("Invalid version information received") + } + if version.Micro != micro || version.Minor != minor || + version.Major != major { + t.Fatal("Invalid version number") + } + + if len(version.Capabilities) != 2 { + if version.Capabilities[0] != cap1 || version.Capabilities[1] != cap2 { + t.Fatal("Invalid capabilities") + } + } +} + +// Checks that a QMP Loop can be started and shutdown. +// +// We start a QMPLoop and shut it down. +// +// Loop should start up and shutdown correctly. The version information +// returned from startQMPLoop should be correct. +func TestQMPStartStopLoop(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + q.Shutdown() + <-disconnectedCh +} + +// Checks that the qmp_capabilities command is correctly sent. +// +// We start a QMPLoop, send the qmp_capabilities command and stop the +// loop. +// +// The qmp_capabilities should be correctly sent and the QMP loop +// should exit gracefully. +func TestQMPCapabilities(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("qmp_capabilities", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteQMPCapabilities(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the stop command is correctly sent. +// +// We start a QMPLoop, send the stop command and stop the +// loop. +// +// The stop command should be correctly sent and the QMP loop +// should exit gracefully. +func TestQMPStop(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("stop", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteStop(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the cont command is correctly sent. +// +// We start a QMPLoop, send the cont command and stop the +// loop. +// +// The cont command should be correctly sent and the QMP loop +// should exit gracefully. +func TestQMPCont(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("cont", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteCont(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the quit command is correctly sent. +// +// We start a QMPLoop, send the quit command and wait for the loop to exit. +// +// The quit command should be correctly sent and the QMP loop should exit +// gracefully without the test calling q.Shutdown(). +func TestQMPQuit(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("quit", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteQuit(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + close(buf.forceFail) + <-disconnectedCh +} + +// Checks that the blockdev-add command is correctly sent. +// +// We start a QMPLoop, send the blockdev-add command and stop the loop. +// +// The blockdev-add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPBlockdevAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("blockdev-add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteBlockdevAdd(context.Background(), "/dev/rbd0", + fmt.Sprintf("drive_%s", testutil.VolumeUUID)) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the device_add command is correctly sent. +// +// We start a QMPLoop, send the device_add command and stop the loop. +// +// The device_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + blockdevID := fmt.Sprintf("drive_%s", testutil.VolumeUUID) + devID := fmt.Sprintf("device_%s", testutil.VolumeUUID) + err := q.ExecuteDeviceAdd(context.Background(), blockdevID, devID, + "virtio-blk-pci", "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the x-blockdev-del command is correctly sent. +// +// We start a QMPLoop, send the x-blockdev-del command and stop the loop. +// +// The x-blockdev-del command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPXBlockdevDel(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("x-blockdev-del", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteXBlockdevDel(context.Background(), + fmt.Sprintf("drive_%s", testutil.VolumeUUID)) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the device_del command is correctly sent. +// +// We start a QMPLoop, send the device_del command and wait for it to complete. +// This command generates some events so we start a separate go routine to check +// that they are received. +// +// The device_del command should be correctly sent and the QMP loop should +// exit gracefully. We should also receive two events on the eventCh. +func TestQMPDeviceDel(t *testing.T) { + const ( + seconds = 1352167040730 + microsecondsEv1 = 123456 + microsecondsEv2 = 123556 + device = "device_" + testutil.VolumeUUID + path = "/dev/rbd0" + ) + + var wg sync.WaitGroup + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("device_del", nil, "return", nil) + buf.AddEvent("DEVICE_DELETED", time.Millisecond*200, + map[string]interface{}{ + "path": path, + }, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microsecondsEv1, + }) + buf.AddEvent("DEVICE_DELETED", time.Millisecond*200, + map[string]interface{}{ + "device": device, + "path": path, + }, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microsecondsEv2, + }) + eventCh := make(chan QMPEvent) + cfg := QMPConfig{EventCh: eventCh, Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + wg.Add(1) + go func() { + for i := 0; i < 2; i++ { + select { + case <-eventCh: + case <-time.After(time.Second): + t.Error("Timedout waiting for event") + } + } + wg.Done() + }() + checkVersion(t, connectedCh) + buf.startEventLoop(&wg) + err := q.ExecuteDeviceDel(context.Background(), + fmt.Sprintf("device_%s", testutil.VolumeUUID)) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh + wg.Wait() +} + +// Checks that contexts can be used to timeout a command. +// +// We start a QMPLoop and send the device_del command with a context that times +// out after 1 second. We don't however arrangefor any DEVICE_DELETED events +// to be sent so the device_del command should not complete normally. We then +// shutdown the QMP loop. +// +// The device_del command should timeout after 1 second and the QMP loop +// should exit gracefully. +func TestQMPDeviceDelTimeout(t *testing.T) { + const ( + seconds = 1352167040730 + microsecondsEv1 = 123456 + device = "device_" + testutil.VolumeUUID + path = "/dev/rbd0" + ) + + var wg sync.WaitGroup + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("device_del", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + err := q.ExecuteDeviceDel(ctx, + fmt.Sprintf("device_%s", testutil.VolumeUUID)) + cancel() + if err != context.DeadlineExceeded { + t.Fatalf("Timeout expected found %v", err) + } + q.Shutdown() + <-disconnectedCh + wg.Wait() +} + +// Checks that contexts can be used to cancel a command. +// +// We start a QMPLoop and send two qmp_capabilities commands, cancelling +// the first. The second is allowed to proceed normally. +// +// The first call to ExecuteQMPCapabilities should fail with +// context.Canceled. The second should succeed. +func TestQMPCancel(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("qmp_capabilities", nil, "return", nil) + buf.AddCommmand("qmp_capabilities", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err := q.ExecuteQMPCapabilities(ctx) + if err != context.Canceled { + t.Fatalf("Unexpected error %v", err) + } + err = q.ExecuteQMPCapabilities(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the system_powerdown command is correctly sent. +// +// We start a QMPLoop, send the system_powerdown command and stop the loop. +// +// The system_powerdown command should be correctly sent and should return +// as we've provisioned a SHUTDOWN event. The QMP loop should exit gracefully. +func TestQMPSystemPowerdown(t *testing.T) { + const ( + seconds = 1352167040730 + microsecondsEv1 = 123456 + ) + + var wg sync.WaitGroup + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("system_powerdown", nil, "return", nil) + buf.AddEvent("SHUTDOWN", time.Millisecond*100, + nil, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microsecondsEv1, + }) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + buf.startEventLoop(&wg) + err := q.ExecuteSystemPowerdown(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh + wg.Wait() +} + +// Checks that events can be received and parsed. +// +// Two events are provisioned and the QMPLoop is started with an valid eventCh. +// We wait for both events to be received and check that their contents are +// correct. We then shutdown the QMP loop. +// +// Both events are received and their contents are correct. The QMP loop should +// shut down gracefully. +func TestQMPEvents(t *testing.T) { + const ( + seconds = 1352167040730 + microsecondsEv1 = 123456 + microsecondsEv2 = 123556 + device = "device_" + testutil.VolumeUUID + path = "/dev/rbd0" + ) + var wg sync.WaitGroup + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddEvent("DEVICE_DELETED", time.Millisecond*100, + map[string]interface{}{ + "device": device, + "path": path, + }, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microsecondsEv1, + }) + buf.AddEvent("POWERDOWN", time.Millisecond*200, nil, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microsecondsEv2, + }) + eventCh := make(chan QMPEvent) + cfg := QMPConfig{EventCh: eventCh, Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + buf.startEventLoop(&wg) + + ev := <-eventCh + if ev.Name != "DEVICE_DELETED" { + t.Errorf("incorrect event name received. Expected %s, found %s", + "DEVICE_DELETED", ev.Name) + } + if ev.Timestamp != time.Unix(seconds, microsecondsEv1) { + t.Error("incorrect timestamp") + } + deviceName := ev.Data["device"].(string) + if deviceName != device { + t.Errorf("Unexpected device field. Expected %s, found %s", + "device_"+testutil.VolumeUUID, device) + } + pathName := ev.Data["path"].(string) + if pathName != path { + t.Errorf("Unexpected path field. Expected %s, found %s", + "/dev/rbd0", path) + } + + ev = <-eventCh + if ev.Name != "POWERDOWN" { + t.Errorf("incorrect event name received. Expected %s, found %s", + "POWERDOWN", ev.Name) + } + if ev.Timestamp != time.Unix(seconds, microsecondsEv2) { + t.Error("incorrect timestamp") + } + if ev.Data != nil { + t.Errorf("event data expected to be nil") + } + + q.Shutdown() + + select { + case _, ok := <-eventCh: + if ok { + t.Errorf("Expected eventCh to be closed") + } + case <-time.After(time.Second): + t.Error("Timed out waiting for eventCh to close") + } + + <-disconnectedCh + wg.Wait() +} + +// Checks that commands issued after the QMP loop exits fail (and don't hang) +// +// We start the QMP loop but force it to fail immediately simulating a QEMU +// instance exit. We then send two qmp_cabilities commands. +// +// Both commands should fail with an error. The QMP loop should exit. +func TestQMPLostLoop(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + close(buf.forceFail) + buf.AddCommmand("qmp_capabilities", nil, "return", nil) + err := q.ExecuteQMPCapabilities(context.Background()) + if err == nil { + t.Error("Expected executeQMPCapabilities to fail") + } + <-disconnectedCh + buf.AddCommmand("qmp_capabilities", nil, "return", nil) + err = q.ExecuteQMPCapabilities(context.Background()) + if err == nil { + t.Error("Expected executeQMPCapabilities to fail") + } +} From 306f54a90780b3b8f833fd7b9217890581d98b25 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 26 Aug 2016 15:31:45 +0100 Subject: [PATCH 002/264] ciao-launcher, qemu: Move launchQemu to qemu The launcher function launchQemu has been moved to the qemu package and is now called LaunchQemu. Signed-off-by: Mark Ryan --- qemu.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ qmp.go | 3 --- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 qemu.go diff --git a/qemu.go b/qemu.go new file mode 100644 index 0000000000..a98928cd39 --- /dev/null +++ b/qemu.go @@ -0,0 +1,62 @@ +/* +// 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 ( + "bytes" + "os" + "os/exec" + + "golang.org/x/net/context" +) + +// LaunchQemu can be used to launch a new qemu instance by invoking the +// qemu-system-x86_64 binary. +// +// 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 LaunchQemu(ctx context.Context, params []string, fds []*os.File, logger QMPLog) (string, error) { + errStr := "" + cmd := exec.Command("qemu-system-x86_64", params...) + if fds != nil { + 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 +} diff --git a/qmp.go b/qmp.go index 11ca1ebbd1..c95cdc35f1 100644 --- a/qmp.go +++ b/qmp.go @@ -29,9 +29,6 @@ import ( "golang.org/x/net/context" ) -// Code to launch qemu -// move to package and document - // QMPLog is a logging interface used by the qemu package to log various // interesting pieces of information. Rather than introduce a dependency // on a given logging package, qemu presents this interface that allows From fc6bf8cf80377d26f4ab4d09e11f8581d778a9d5 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 26 Aug 2016 16:27:50 +0100 Subject: [PATCH 003/264] qemu: Add package documentation This commit adds some package documentation to the qemu package, including an overview of the package and an example of its use. Signed-off-by: Mark Ryan --- examples_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ qemu.go | 8 +++++ 2 files changed, 92 insertions(+) create mode 100644 examples_test.go diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000000..1fc9dcf3a3 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,84 @@ +/* +// 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_test + +import ( + "time" + + "github.com/01org/ciao/qemu" + "golang.org/x/net/context" +) + +func Example() { + params := make([]string, 0, 32) + + // Rootfs + params = append(params, "-drive", "file=/tmp/image.qcow2,if=virtio,aio=threads,format=qcow2") + // Network + params = append(params, "-net", "nic,model=virtio", "-net", "user") + // kvm + params = append(params, "-enable-kvm", "-cpu", "host") + // qmp socket + params = append(params, "-daemonize", "-qmp", "unix:/tmp/qmp-socket,server,nowait") + // resources + params = append(params, "-m", "370", "-smp", "cpus=2") + + // LaunchQemu 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) + if err != nil { + panic(err) + } + + // This channel will be closed when the instance dies. + disconnectedCh := make(chan struct{}) + + // Set up our options. We don't want any logging or to receive any events. + cfg := qemu.QMPConfig{} + + // Start monitoring the qemu instance. This functon will block until we have + // connect to the QMP socket and received the welcome message. + q, _, err := qemu.QMPStart(context.Background(), "/tmp/qmp-socket", cfg, disconnectedCh) + if err != nil { + panic(err) + } + + // This has to be the first command executed in a QMP session. + err = q.ExecuteQMPCapabilities(context.Background()) + if err != nil { + panic(err) + } + + // Let's try to shutdown the VM. If it hasn't shutdown in 10 seconds we'll + // send a quit message. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + err = q.ExecuteSystemPowerdown(ctx) + cancel() + if err != nil { + err = q.ExecuteQuit(context.Background()) + if err != nil { + panic(err) + } + } + + q.Shutdown() + + // disconnectedCh is closed when the VM exits. This line blocks until this + // event occurs. + <-disconnectedCh +} diff --git a/qemu.go b/qemu.go index a98928cd39..4984ec63d6 100644 --- a/qemu.go +++ b/qemu.go @@ -14,6 +14,14 @@ // 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 ( From 7f50a41525c3a1e87842866608fdd34973e30265 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 26 Aug 2016 16:29:35 +0100 Subject: [PATCH 004/264] qemu: Fix a silly bug in LaunchQemu There's no point in setting cmd.ExtraFiles if the fds array is an empty slice. This won't do any harm but is essentially a no-op. Signed-off-by: Mark Ryan --- qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 4984ec63d6..88af0217b8 100644 --- a/qemu.go +++ b/qemu.go @@ -51,7 +51,7 @@ import ( func LaunchQemu(ctx context.Context, params []string, fds []*os.File, logger QMPLog) (string, error) { errStr := "" cmd := exec.Command("qemu-system-x86_64", params...) - if fds != nil { + if len(fds) > 0 { logger.Infof("Adding extra file %v", fds) cmd.ExtraFiles = fds } From 7d4199a4491045526a3f582ad62788d933e14d80 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 1 Sep 2016 18:46:37 +0100 Subject: [PATCH 005/264] qemu: Fix ineffassign error Fix ciao/qemu/qmp.go:349:3: ineffectual assignment to ok. Strictly speaking this is a bug in ineffassign but it's easier to change the ciao code. Signed-off-by: Mark Ryan --- qmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qmp.go b/qmp.go index c95cdc35f1..0634552fe7 100644 --- a/qmp.go +++ b/qmp.go @@ -346,7 +346,7 @@ func (q *QMP) mainLoop() { DONE: for { - ok := false + var ok bool select { case cmd, ok := <-q.cmdCh: if !ok { From f57201989b82e683e0d437747b7318c3e24fe0d3 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 9 Sep 2016 18:45:31 +0200 Subject: [PATCH 006/264] qemu: Use null QMP logger when the logger parameter is nil Or else LaunchQemu() ends up dereferencing a nil pointer and panic'ing. Signed-off-by: Samuel Ortiz --- qemu.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qemu.go b/qemu.go index 88af0217b8..6014d36a9e 100644 --- a/qemu.go +++ b/qemu.go @@ -49,6 +49,10 @@ import ( // 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) { + if logger == nil { + logger = qmpNullLogger{} + } + errStr := "" cmd := exec.Command("qemu-system-x86_64", params...) if len(fds) > 0 { From 5ccbaf2b5919172b56c731a9f4f440ece4ef2f06 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 12 Sep 2016 11:42:40 +0100 Subject: [PATCH 007/264] ciao-launcher, qemu: Upgrade to new context package. Ciao will use the new standard library context package from now on. This will allow us to use some of the new standard library functions such as DialContext. Partial fix for issue #541 Signed-off-by: Mark Ryan --- examples_test.go | 3 ++- qemu.go | 2 +- qmp.go | 2 +- qmp_test.go | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples_test.go b/examples_test.go index 1fc9dcf3a3..e7ed12abb6 100644 --- a/examples_test.go +++ b/examples_test.go @@ -19,8 +19,9 @@ package qemu_test import ( "time" + "context" + "github.com/01org/ciao/qemu" - "golang.org/x/net/context" ) func Example() { diff --git a/qemu.go b/qemu.go index 6014d36a9e..4fe3b2b932 100644 --- a/qemu.go +++ b/qemu.go @@ -29,7 +29,7 @@ import ( "os" "os/exec" - "golang.org/x/net/context" + "context" ) // LaunchQemu can be used to launch a new qemu instance by invoking the diff --git a/qmp.go b/qmp.go index 0634552fe7..bc2596823b 100644 --- a/qmp.go +++ b/qmp.go @@ -26,7 +26,7 @@ import ( "net" "time" - "golang.org/x/net/context" + "context" ) // QMPLog is a logging interface used by the qemu package to log various diff --git a/qmp_test.go b/qmp_test.go index 9519a176ee..4d60c0099b 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -26,7 +26,7 @@ import ( "testing" "time" - "golang.org/x/net/context" + "context" "github.com/01org/ciao/testutil" ) From 37a1f5003d4d6972c6f7932c2dc63b93da287b64 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 15:02:17 +0200 Subject: [PATCH 008/264] qemu: Add configuration structure to simplify LaunchQemu LaunchQemu() now takes a Config structure that contains some more descriptive fields than raw qemu parameter strings. LaunchQemu is now simpler to call and more extensible as supporting more qemu parameters would mean expanding Config instead of changing the API. Signed-off-by: Samuel Ortiz --- examples_test.go | 4 +-- qemu.go | 77 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/examples_test.go b/examples_test.go index e7ed12abb6..45607a0444 100644 --- a/examples_test.go +++ b/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.go b/qemu.go index 4fe3b2b932..a0067a5458 100644 --- a/qemu.go +++ b/qemu.go @@ -26,14 +26,78 @@ package qemu import ( "bytes" + "fmt" "os" "os/exec" "context" ) -// LaunchQemu can be used to launch a new qemu instance by invoking the -// qemu-system-x86_64 binary. +// 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 + + // MachineType is the machine type to be used by qemu. + MachineType string + + // MachineTypeAcceleration are the machine acceleration option to be used by qemu. + MachineTypeAcceleration string + + // CPUModel is the CPU model to be used by qemu. + CPUModel string + + // ExtraParams is a slice of options to pass to qemu. + ExtraParams []string + + // FDs is a list of open file descriptors to be passed to the spawned qemu process + FDs []*os.File +} + +func appendMachineParams(params []string, config Config) []string { + if config.MachineType != "" && config.MachineTypeAcceleration != "" { + params = append(params, "-machine") + params = append(params, fmt.Sprintf("%s,accel=%s", config.MachineType, config.MachineTypeAcceleration)) + } + + return params +} + +func appendCPUModel(params []string, config Config) []string { + if config.CPUModel != "" { + params = append(params, "-cpu") + params = append(params, config.CPUModel) + } + + 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 = appendMachineParams(params, config) + params = appendCPUModel(params, config) + params = append(params, config.ExtraParams...) + + 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 @@ -48,13 +112,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 From 171182709dc29aad693011e5b87285f59c32fc2f Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 17:11:30 +0200 Subject: [PATCH 009/264] qemu: Add qemu's name to the Config structure This allows us to set the qemu -name option. Signed-off-by: Samuel Ortiz --- qemu.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qemu.go b/qemu.go index a0067a5458..821bdfd6a7 100644 --- a/qemu.go +++ b/qemu.go @@ -42,6 +42,9 @@ type Config struct { // Ctx is not used at the moment. Ctx context.Context + // Name is the qemu guest name + Name string + // MachineType is the machine type to be used by qemu. MachineType string @@ -58,6 +61,15 @@ type Config struct { 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 appendMachineParams(params []string, config Config) []string { if config.MachineType != "" && config.MachineTypeAcceleration != "" { params = append(params, "-machine") @@ -88,6 +100,7 @@ func appendCPUModel(params []string, config Config) []string { func LaunchQemu(config Config, logger QMPLog) (string, error) { var params []string + params = appendName(params, config) params = appendMachineParams(params, config) params = appendCPUModel(params, config) params = append(params, config.ExtraParams...) From 5458de70ad1703cf03c7ae2d5080a7e96f1a2758 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 17:33:09 +0200 Subject: [PATCH 010/264] qemu: Add a QMP socket field to the Config structure QMP sockets are used to send qemu specific commands to the running qemu process. The QMPSocket structure allows us to define the socket type we want, along with its name. Signed-off-by: Samuel Ortiz --- qemu.go | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 821bdfd6a7..a550f94709 100644 --- a/qemu.go +++ b/qemu.go @@ -29,10 +29,26 @@ import ( "fmt" "os" "os/exec" + "strings" "context" ) +// 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 +} + // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { @@ -44,7 +60,7 @@ type Config struct { // Name is the qemu guest name Name string - + // MachineType is the machine type to be used by qemu. MachineType string @@ -54,6 +70,9 @@ type Config struct { // CPUModel is the CPU model to be used by qemu. CPUModel string + // QMPSocket is the QMP socket description + QMPSocket QMPSocket + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -88,6 +107,26 @@ func appendCPUModel(params []string, config Config) []string { 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 +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -103,6 +142,8 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendName(params, config) params = appendMachineParams(params, config) params = appendCPUModel(params, config) + params = appendQMPSocket(params, config) + params = append(params, config.ExtraParams...) return LaunchCustomQemu(config.Ctx, config.Path, params, config.FDs, logger) From 8744dfe85eb440fdbbe02775312e378b215ce859 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 17:46:14 +0200 Subject: [PATCH 011/264] qemu: Add a Device slice field to the Config structure We may need to support a large range of devices in the qemu created VM and the Device slice allows us to define which drivers are needed. Signed-off-by: Samuel Ortiz --- qemu.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index a550f94709..8ce5f959f1 100644 --- a/qemu.go +++ b/qemu.go @@ -34,6 +34,28 @@ import ( "context" ) +// Device describes a device to be created by qemu. +type Device struct { + // Type is the qemu device type + Type string + + // ID is the user defined device ID. + ID string + + // MemDev is the device memory identifier. + MemDev string + + // FSDev is the device filesystem identifier. + FSDev string + + // MountTag is the device filesystem mount point tag. + // It is only relevant when combined with FSDev. + MountTag string + + // CharDev is the device character device identifier. + CharDev string +} + // QMPSocket represents a qemu QMP socket configuration. type QMPSocket struct { // Type is the socket type (e.g. "unix"). @@ -70,9 +92,12 @@ type Config struct { // CPUModel is the CPU model to be used by qemu. CPUModel string - // QMPSocket is the QMP socket description + // QMPSocket is the QMP socket description. QMPSocket QMPSocket + // Devices is a list of devices for qemu to create. + Devices []Device + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -127,6 +152,41 @@ func appendQMPSocket(params []string, config Config) []string { return params } +func appendDevices(params []string, config Config) []string { + for _, d := range config.Devices { + if d.Type != "" { + var deviceParams []string + + deviceParams = append(deviceParams, fmt.Sprintf("%s", d.Type)) + + if d.ID != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", d.ID)) + } + + if d.MemDev != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", d.MemDev)) + } + + if d.CharDev != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",chardev=%s", d.CharDev)) + } + + if d.FSDev != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", d.FSDev)) + + if d.MountTag != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", d.MountTag)) + } + } + + params = append(params, "-device") + params = append(params, strings.Join(deviceParams, "")) + } + } + + return params +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -143,6 +203,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendMachineParams(params, config) params = appendCPUModel(params, config) params = appendQMPSocket(params, config) + params = appendDevices(params, config) params = append(params, config.ExtraParams...) From b973bc59fb82ab8d273de6493a65af043163491d Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:06:14 +0200 Subject: [PATCH 012/264] qemu: Add an Object slice field to the Config structure The Object slice tells qemu which specific object to create. Qemu objects can represent memory backend files, random number generators, TLS credentials, etc... Signed-off-by: Samuel Ortiz --- qemu.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/qemu.go b/qemu.go index 8ce5f959f1..0908e78194 100644 --- a/qemu.go +++ b/qemu.go @@ -56,6 +56,22 @@ type Device struct { CharDev string } +// Object is a qemu object representation. +type Object struct { + // Type is the qemu object type + Type string + + // 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 +} + // QMPSocket represents a qemu QMP socket configuration. type QMPSocket struct { // Type is the socket type (e.g. "unix"). @@ -98,6 +114,9 @@ type Config struct { // Devices is a list of devices for qemu to create. Devices []Device + // Objects is a list of objects for qemu to create. + Objects []Object + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -187,6 +206,33 @@ func appendDevices(params []string, config Config) []string { return params } +func appendObjects(params []string, config Config) []string { + for _, o := range config.Objects { + if o.Type != "" { + var objectParams []string + + objectParams = append(objectParams, o.Type) + + if o.ID != "" { + objectParams = append(objectParams, fmt.Sprintf(",id=%s", o.ID)) + } + + if o.MemPath != "" { + objectParams = append(objectParams, fmt.Sprintf(",mem-path=%s", o.MemPath)) + } + + if o.Size > 0 { + objectParams = append(objectParams, fmt.Sprintf(",size=%d", o.Size)) + } + + params = append(params, "-object") + params = append(params, strings.Join(objectParams, "")) + } + } + + return params +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -204,6 +250,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendCPUModel(params, config) params = appendQMPSocket(params, config) params = appendDevices(params, config) + params = appendObjects(params, config) params = append(params, config.ExtraParams...) From 518ba627b13764340297ac7c1c05e9085f629b07 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:13:09 +0200 Subject: [PATCH 013/264] qemu: Add a Kernel field to the Config structure The Kernel structure holds the guest kernel configuration: its path and its parameters. This is the kernel qemu will boot the VM from. Signed-off-by: Samuel Ortiz --- qemu.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/qemu.go b/qemu.go index 0908e78194..329cf51d60 100644 --- a/qemu.go +++ b/qemu.go @@ -87,6 +87,15 @@ type QMPSocket struct { NoWait bool } +// 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 +} + // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { @@ -117,6 +126,9 @@ type Config struct { // Objects is a list of objects for qemu to create. Objects []Object + // Kernel is the guest kernel configuration. + Kernel Kernel + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -233,6 +245,20 @@ func appendObjects(params []string, config Config) []string { 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 +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -251,6 +277,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendQMPSocket(params, config) params = appendDevices(params, config) params = appendObjects(params, config) + params = appendKernel(params, config) params = append(params, config.ExtraParams...) From 73e2d53c9a46e339198ec8d3ef3a0984f39030be Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:18:30 +0200 Subject: [PATCH 014/264] qemu: Add a Filesystem Devices slice field to the Config structure Each Filesystem device should have a corresponding "virtio-9p-pci" Device driver. They represent a filesystem to be exported through 9pfs. Signed-off-by: Samuel Ortiz --- qemu.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/qemu.go b/qemu.go index 329cf51d60..f7aeb7098f 100644 --- a/qemu.go +++ b/qemu.go @@ -72,6 +72,22 @@ type Object struct { Size uint64 } +// FSDevice represents a qemu filesystem configuration. +type FSDevice struct { + // Type is the filesystem device type (e.g. "local") + Type string + + // ID is the filesystem identifier. + // It should match an existing Device ID. + ID string + + // Path is the host root path for this filesystem. + Path string + + // SecurityModel is the security model for this filesystem device. + SecurityModel string +} + // QMPSocket represents a qemu QMP socket configuration. type QMPSocket struct { // Type is the socket type (e.g. "unix"). @@ -126,6 +142,9 @@ type Config struct { // Objects is a list of objects for qemu to create. Objects []Object + // FilesystemDevices is a list of filesystem devices. + FilesystemDevices []FSDevice + // Kernel is the guest kernel configuration. Kernel Kernel @@ -245,6 +264,33 @@ func appendObjects(params []string, config Config) []string { return params } +func appendFilesystemDevices(params []string, config Config) []string { + for _, f := range config.FilesystemDevices { + if f.Type != "" { + var fsParams []string + + fsParams = append(fsParams, fmt.Sprintf("%s", f.Type)) + + if f.ID != "" { + fsParams = append(fsParams, fmt.Sprintf(",id=%s", f.ID)) + } + + if f.Path != "" { + fsParams = append(fsParams, fmt.Sprintf(",path=%s", f.Path)) + } + + if f.SecurityModel != "" { + fsParams = append(fsParams, fmt.Sprintf(",security-model=%s", f.SecurityModel)) + } + + params = append(params, "-fsdev") + params = append(params, strings.Join(fsParams, "")) + } + } + + return params +} + func appendKernel(params []string, config Config) []string { if config.Kernel.Path != "" { params = append(params, "-kernel") @@ -276,6 +322,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendCPUModel(params, config) params = appendQMPSocket(params, config) params = appendDevices(params, config) + params = appendFilesystemDevices(params, config) params = appendObjects(params, config) params = appendKernel(params, config) From 6239e846b7f64555e328a160a6a8ec2dfbc90629 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:23:25 +0200 Subject: [PATCH 015/264] qemu: Add a Character Devices slice field to the Config structure Qemu character devices typically allow for sending traffic from the guest to the host by emulating a console, a tty, a serial device for example. Signed-off-by: Samuel Ortiz --- qemu.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qemu.go b/qemu.go index f7aeb7098f..4a6414cace 100644 --- a/qemu.go +++ b/qemu.go @@ -139,6 +139,9 @@ type Config struct { // Devices is a list of devices for qemu to create. Devices []Device + // CharDevices is a list of character devices for qemu to export. + CharDevices []string + // Objects is a list of objects for qemu to create. Objects []Object @@ -237,6 +240,15 @@ func appendDevices(params []string, config Config) []string { return params } +func appendCharDevices(params []string, config Config) []string { + for _, c := range config.CharDevices { + params = append(params, "-chardev") + params = append(params, c) + } + + return params +} + func appendObjects(params []string, config Config) []string { for _, o := range config.Objects { if o.Type != "" { @@ -322,6 +334,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendCPUModel(params, config) params = appendQMPSocket(params, config) params = appendDevices(params, config) + params = appendCharDevices(params, config) params = appendFilesystemDevices(params, config) params = appendObjects(params, config) params = appendKernel(params, config) From b198bc67e75b9e3ad24fe53d8acb21cc2f4d0f08 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:30:33 +0200 Subject: [PATCH 016/264] qemu: Add a UUID field to the Config structure The qemu UUID will be used to set the guest system UUID. Signed-off-by: Samuel Ortiz --- qemu.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qemu.go b/qemu.go index 4a6414cace..8888074501 100644 --- a/qemu.go +++ b/qemu.go @@ -148,6 +148,9 @@ type Config struct { // FilesystemDevices is a list of filesystem devices. FilesystemDevices []FSDevice + // UUID is the qemu process UUID. + UUID string + // Kernel is the guest kernel configuration. Kernel Kernel @@ -303,6 +306,15 @@ func appendFilesystemDevices(params []string, config Config) []string { return params } +func appendUUID(params []string, config Config) []string { + if config.UUID != "" { + params = append(params, "-uuid") + params = append(params, config.UUID) + } + + return params +} + func appendKernel(params []string, config Config) []string { if config.Kernel.Path != "" { params = append(params, "-kernel") @@ -330,6 +342,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { var params []string params = appendName(params, config) + params = appendUUID(params, config) params = appendMachineParams(params, config) params = appendCPUModel(params, config) params = appendQMPSocket(params, config) From 7cf386a81ca950cb4e67a1b0183798d411e6eb01 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:41:35 +0200 Subject: [PATCH 017/264] qemu: Add a Memory field to the Config structure The Memory field holds the guest memory configuration. It is used to define the current and maximum RAM is made available to the guest and how this amount of RAM is splitted into several slots. Signed-off-by: Samuel Ortiz --- qemu.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/qemu.go b/qemu.go index 8888074501..fbf287512b 100644 --- a/qemu.go +++ b/qemu.go @@ -103,6 +103,21 @@ type QMPSocket struct { NoWait bool } +// 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. @@ -154,6 +169,9 @@ type Config struct { // Kernel is the guest kernel configuration. Kernel Kernel + // Memory is the guest memory configuration. + Memory Memory + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -315,6 +333,27 @@ func appendUUID(params []string, config Config) []string { 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 appendKernel(params []string, config Config) []string { if config.Kernel.Path != "" { params = append(params, "-kernel") @@ -346,6 +385,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendMachineParams(params, config) params = appendCPUModel(params, config) params = appendQMPSocket(params, config) + params = appendMemory(params, config) params = appendDevices(params, config) params = appendCharDevices(params, config) params = appendFilesystemDevices(params, config) From c63ec0965a9a58a3ace31e3d0dc4c9775b1b41c2 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 18:50:01 +0200 Subject: [PATCH 018/264] qemu: Add a SMP field to the Config structure The SMP structure defines the amount of virtual CPUs, sockets, and threads per CPU that is made available to the guest. Signed-off-by: Samuel Ortiz --- qemu.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/qemu.go b/qemu.go index fbf287512b..fc902da5a2 100644 --- a/qemu.go +++ b/qemu.go @@ -103,6 +103,21 @@ type QMPSocket struct { 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. @@ -172,6 +187,9 @@ type Config struct { // Memory is the guest memory configuration. Memory Memory + // SMP is the quest multi processors configuration. + SMP SMP + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -354,6 +372,31 @@ func appendMemory(params []string, config Config) []string { 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 appendKernel(params []string, config Config) []string { if config.Kernel.Path != "" { params = append(params, "-kernel") @@ -386,6 +429,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendCPUModel(params, config) params = appendQMPSocket(params, config) params = appendMemory(params, config) + params = appendCPUs(params, config) params = appendDevices(params, config) params = appendCharDevices(params, config) params = appendFilesystemDevices(params, config) From 612a5a9e5d17d715c8da8e98219c27bc4229984e Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 19:07:32 +0200 Subject: [PATCH 019/264] qemu: Add a RTC field to the Config structure The RTC structure represents the guest Real Time Clock configuration. Signed-off-by: Samuel Ortiz --- qemu.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/qemu.go b/qemu.go index fc902da5a2..bef338735e 100644 --- a/qemu.go +++ b/qemu.go @@ -88,6 +88,18 @@ type FSDevice struct { SecurityModel string } +// 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"). @@ -178,6 +190,9 @@ type Config struct { // FilesystemDevices is a list of filesystem devices. FilesystemDevices []FSDevice + // RTC is the qemu Real Time Clock configuration + RTC RTC + // UUID is the qemu process UUID. UUID string @@ -397,6 +412,27 @@ func appendCPUs(params []string, config Config) []string { 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 appendKernel(params []string, config Config) []string { if config.Kernel.Path != "" { params = append(params, "-kernel") @@ -434,6 +470,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendCharDevices(params, config) params = appendFilesystemDevices(params, config) params = appendObjects(params, config) + params = appendRTC(params, config) params = appendKernel(params, config) params = append(params, config.ExtraParams...) From 4892d041e7889beccb7f3b128d86eadc66ee70c4 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 19:13:52 +0200 Subject: [PATCH 020/264] qemu: Add a Global parameter field to the Config structure The Global string represents the set of default Device driver properties we want qemu to use. This is mostly useful for automatically created devices. Signed-off-by: Samuel Ortiz --- qemu.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qemu.go b/qemu.go index bef338735e..0013e6a449 100644 --- a/qemu.go +++ b/qemu.go @@ -205,6 +205,9 @@ type Config struct { // SMP is the quest multi processors configuration. SMP SMP + // GlobalParam is the -global parameter + GlobalParam string + // ExtraParams is a slice of options to pass to qemu. ExtraParams []string @@ -433,6 +436,15 @@ func appendRTC(params []string, config Config) []string { return params } +func appendGlobalParam(params []string, config Config) []string { + if config.GlobalParam != "" { + params = append(params, "-global") + params = append(params, config.GlobalParam) + } + + return params +} + func appendKernel(params []string, config Config) []string { if config.Kernel.Path != "" { params = append(params, "-kernel") @@ -472,6 +484,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendObjects(params, config) params = appendRTC(params, config) params = appendKernel(params, config) + params = appendGlobalParam(params, config) params = append(params, config.ExtraParams...) From d94b5af8755b88a2081015a5bb6785bce9e3df00 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 19:15:38 +0200 Subject: [PATCH 021/264] qemu: Add a VGA parameter field to the Config structure The VGA string represents the type of VGA card qemu should emulate. Signed-off-by: Samuel Ortiz --- qemu.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qemu.go b/qemu.go index 0013e6a449..dbc120a0df 100644 --- a/qemu.go +++ b/qemu.go @@ -193,6 +193,9 @@ type Config struct { // RTC is the qemu Real Time Clock configuration RTC RTC + // VGA is the qemu VGA mode. + VGA string + // UUID is the qemu process UUID. UUID string @@ -445,6 +448,15 @@ func appendGlobalParam(params []string, config Config) []string { 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") @@ -485,6 +497,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendRTC(params, config) params = appendKernel(params, config) params = appendGlobalParam(params, config) + params = appendVGA(params, config) params = append(params, config.ExtraParams...) From 15bce61a904020f43f18d2f4b7f3cecbdf24f381 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 19:25:27 +0200 Subject: [PATCH 022/264] qemu: Group all machine configurations into one structure Here we group the machine type and acceleration together as they are defined through the same qemu parameter (-machine). Signed-off-by: Samuel Ortiz --- qemu.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/qemu.go b/qemu.go index dbc120a0df..ec721f683d 100644 --- a/qemu.go +++ b/qemu.go @@ -34,6 +34,15 @@ import ( "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 describes a device to be created by qemu. type Device struct { // Type is the qemu device type @@ -166,11 +175,8 @@ type Config struct { // Name is the qemu guest name Name string - // MachineType is the machine type to be used by qemu. - MachineType string - - // MachineTypeAcceleration are the machine acceleration option to be used by qemu. - MachineTypeAcceleration string + // Machine + Machine Machine // CPUModel is the CPU model to be used by qemu. CPUModel string @@ -227,10 +233,18 @@ func appendName(params []string, config Config) []string { return params } -func appendMachineParams(params []string, config Config) []string { - if config.MachineType != "" && config.MachineTypeAcceleration != "" { +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, fmt.Sprintf("%s,accel=%s", config.MachineType, config.MachineTypeAcceleration)) + params = append(params, strings.Join(machineParams, "")) } return params @@ -485,7 +499,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendName(params, config) params = appendUUID(params, config) - params = appendMachineParams(params, config) + params = appendMachine(params, config) params = appendCPUModel(params, config) params = appendQMPSocket(params, config) params = appendMemory(params, config) From fe1bdcd2f7e3eb7a3fbae34364f2be61da9a0ed9 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 19:27:42 +0200 Subject: [PATCH 023/264] qemu: Remove the extra parameters field from the Config structure The extraParams is confusing and can conflict with the rest of the Config structure definitions. We remove it and will add new fields to that structure as needed. Signed-off-by: Samuel Ortiz --- qemu.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/qemu.go b/qemu.go index ec721f683d..57790389fa 100644 --- a/qemu.go +++ b/qemu.go @@ -175,12 +175,15 @@ type Config struct { // Name is the qemu guest name Name string - // Machine - Machine Machine + // 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 @@ -202,9 +205,6 @@ type Config struct { // VGA is the qemu VGA mode. VGA string - // UUID is the qemu process UUID. - UUID string - // Kernel is the guest kernel configuration. Kernel Kernel @@ -217,9 +217,6 @@ type Config struct { // GlobalParam is the -global parameter GlobalParam string - // ExtraParams is a slice of options to pass to qemu. - ExtraParams []string - // FDs is a list of open file descriptors to be passed to the spawned qemu process FDs []*os.File } @@ -513,8 +510,6 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendGlobalParam(params, config) params = appendVGA(params, config) - params = append(params, config.ExtraParams...) - return LaunchCustomQemu(config.Ctx, config.Path, params, config.FDs, logger) } From ebfa382d2e2170d592fa8ea2f7634b31524079ed Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 12 Sep 2016 19:43:39 +0200 Subject: [PATCH 024/264] qemu: Add a Knobs field to the Config structure The Knobs structure groups all qemu isolated boolean settings. For now this is -no-user-config, -no-defaults and -nographic. Signed-off-by: Samuel Ortiz --- qemu.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 57790389fa..af09704c1f 100644 --- a/qemu.go +++ b/qemu.go @@ -163,6 +163,18 @@ type Kernel struct { 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 { @@ -214,9 +226,12 @@ type Config struct { // SMP is the quest multi processors configuration. SMP SMP - // GlobalParam is the -global parameter + // 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 } @@ -482,6 +497,22 @@ func appendKernel(params []string, config Config) []string { 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. @@ -509,6 +540,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendKernel(params, config) params = appendGlobalParam(params, config) params = appendVGA(params, config) + params = appendKnobs(params, config) return LaunchCustomQemu(config.Ctx, config.Path, params, config.FDs, logger) } From 54d32c24145f865b3b61df526b1818ac65267be0 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 12:20:44 +0200 Subject: [PATCH 025/264] qemu: Add parameters adding unit tests We only test the Machine parameters for now. Signed-off-by: Samuel Ortiz --- qemu_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 qemu_test.go diff --git a/qemu_test.go b/qemu_test.go new file mode 100644 index 0000000000..d2321a311d --- /dev/null +++ b/qemu_test.go @@ -0,0 +1,58 @@ +/* +// 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 ( + // "fmt" + "strings" + "testing" +) + +func testAppend(structure interface{}, expected string, t *testing.T) { + var params []string + + switch s := structure.(type) { + case Machine: + config := Config{ + Machine: s, + } + + params = appendMachine([]string{}, config) + } + + result := strings.Join(params, " ") + if result != expected { + t.Fatalf("Failed to append Machine [%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) +} From 38e041dc9d1ff81b593223ee4586f18350530a6d Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 12:31:24 +0200 Subject: [PATCH 026/264] qemu: Add Device unit tests We add a NVDIMM, a filesystem and an empty device. Signed-off-by: Samuel Ortiz --- qemu_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/qemu_test.go b/qemu_test.go index d2321a311d..e19f659306 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -32,6 +32,13 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendMachine([]string{}, config) + + case Device: + config := Config{ + Devices: []Device{s}, + } + + params = appendDevices([]string{}, config) } result := strings.Join(params, " ") @@ -56,3 +63,33 @@ func TestAppendEmptyMachine(t *testing.T) { testAppend(machine, "", t) } + +var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0" + +func TestAppendDeviceNVDIMM(t *testing.T) { + device := Device{ + Type: "nvdimm", + ID: "nv0", + MemDev: "mem0", + } + + testAppend(device, deviceNVDIMMString, t) +} + +var deviceFSString = "-device virtio-9p-pci,fsdev=workload9p,mount_tag=rootfs" + +func TestAppendDeviceFS(t *testing.T) { + device := Device{ + Type: "virtio-9p-pci", + FSDev: "workload9p", + MountTag: "rootfs", + } + + testAppend(device, deviceFSString, t) +} + +func TestAppendEmptyDevice(t *testing.T) { + device := Device{} + + testAppend(device, "", t) +} From 8aeb3d45aa670a57933cdf87763b7f73666c199e Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 12:38:18 +0200 Subject: [PATCH 027/264] qemu: Add an Object unit test We test that memory-backend-file and empty objects parameters are properly built. Signed-off-by: Samuel Ortiz --- qemu_test.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/qemu_test.go b/qemu_test.go index e19f659306..a86b193b7d 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -39,11 +39,18 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendDevices([]string{}, config) + + case Object: + config := Config{ + Objects: []Object{s}, + } + + params = appendObjects([]string{}, config) } result := strings.Join(params, " ") if result != expected { - t.Fatalf("Failed to append Machine [%s] != [%s]", result, expected) + t.Fatalf("Failed to append parameters [%s] != [%s]", result, expected) } } @@ -93,3 +100,22 @@ func TestAppendEmptyDevice(t *testing.T) { testAppend(device, "", t) } + +var objectMemoryString = "-object memory-backend-file,id=mem0,mem-path=/root,size=65536" + +func TestAppendObjectMemory(t *testing.T) { + object := Object{ + Type: "memory-backend-file", + ID: "mem0", + MemPath: "/root", + Size: 1 << 16, + } + + testAppend(object, objectMemoryString, t) +} + +func TestAppendEmptyObject(t *testing.T) { + device := Device{} + + testAppend(device, "", t) +} From 8e495f6eff5a5ab5f4afde9517251424b328d152 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 12:42:41 +0200 Subject: [PATCH 028/264] qemu: Add a Knobs unit test We test that all true and all false knobs parameters are properly built. Signed-off-by: Samuel Ortiz --- qemu_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/qemu_test.go b/qemu_test.go index a86b193b7d..3e38ca59c2 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -46,6 +46,13 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendObjects([]string{}, config) + + case Knobs: + config := Config{ + Knobs: s, + } + + params = appendKnobs([]string{}, config) } result := strings.Join(params, " ") @@ -119,3 +126,25 @@ func TestAppendEmptyObject(t *testing.T) { 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) +} From 2ea9b9a385a2d4295257537af11848faeee4b30f Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 12:50:49 +0200 Subject: [PATCH 029/264] qemu: Add a Kernel unit test We test that the kernel path and the kernel parameters are properly built. Signed-off-by: Samuel Ortiz --- qemu_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/qemu_test.go b/qemu_test.go index 3e38ca59c2..e43e39c239 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -53,6 +53,13 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendKnobs([]string{}, config) + + case Kernel: + config := Config{ + Kernel: s, + } + + params = appendKernel([]string{}, config) } result := strings.Join(params, " ") @@ -148,3 +155,14 @@ func TestAppendKnobsAllFalse(t *testing.T) { 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) +} From 7b2f7eb5d89ccb311484780bb74f0b2a7e536904 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 13:00:59 +0200 Subject: [PATCH 030/264] qemu: Add Memory and SMP unit tests We test that the memory and SMP configuration parameters are properly built. Signed-off-by: Samuel Ortiz --- qemu_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/qemu_test.go b/qemu_test.go index e43e39c239..7f01c4b612 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -60,6 +60,20 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendKernel([]string{}, config) + + case Memory: + config := Config{ + Memory: s, + } + + params = appendMemory([]string{}, config) + + case SMP: + config := Config{ + SMP: s, + } + + params = appendCPUs([]string{}, config) } result := strings.Join(params, " ") @@ -166,3 +180,28 @@ func TestAppendKernel(t *testing.T) { 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) +} From 5ba8ef79df1e09d27be8d829a1abf74cc33df656 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 14:29:42 +0200 Subject: [PATCH 031/264] qemu: Add QMP socket unit tests We test that the QMP socket parameter is properly built. Signed-off-by: Samuel Ortiz --- qemu_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/qemu_test.go b/qemu_test.go index 7f01c4b612..d973a090d0 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -74,6 +74,13 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendCPUs([]string{}, config) + + case QMPSocket: + config := Config{ + QMPSocket: s, + } + + params = appendQMPSocket([]string{}, config) } result := strings.Join(params, " ") @@ -205,3 +212,27 @@ func TestAppendCPUs(t *testing.T) { 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) +} From c0e2aacad28b92f9ce56c74999e44b677fbf68bb Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 14:35:02 +0200 Subject: [PATCH 032/264] qemu: Add one unit test for the Config strings Here we test that name, UUID and the CPU model are properly built. Signed-off-by: Samuel Ortiz --- qemu_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qemu_test.go b/qemu_test.go index d973a090d0..948d931dc1 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -236,3 +236,25 @@ func TestAppendQMPSocket(t *testing.T) { testAppend(qmp, qmpSocketString, t) } + +var qemuString = "-name cc-qemu -cpu host -uuid 123456789" + +func TestAppendStrings(t *testing.T) { + var params []string + + config := Config{ + Path: "qemu", + Name: "cc-qemu", + UUID: "123456789", + CPUModel: "host", + } + + params = appendName(params, config) + params = appendCPUModel(params, config) + params = appendUUID(params, config) + + result := strings.Join(params, " ") + if result != qemuString { + t.Fatalf("Failed to append parameters [%s] != [%s]", result, qemuString) + } +} From 137e7c72422f4e12baaf70d93a7e6ece1c7645f4 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 13 Sep 2016 19:52:06 +0200 Subject: [PATCH 033/264] qemu: Add a NetDevice slice to the Config structure The NetDevice structure represents a network device to be emulated by qemu. We also add the corresponding unit test. Signed-off-by: Samuel Ortiz --- qemu.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu_test.go | 21 +++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/qemu.go b/qemu.go index af09704c1f..e4824d40cd 100644 --- a/qemu.go +++ b/qemu.go @@ -175,6 +175,24 @@ type Knobs struct { NoGraphic bool } +// NetDevice represents a guest networking device +type NetDevice struct { + // Type is the netdev type (e.g. tap). + Type string + + // 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 +} + // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { @@ -202,6 +220,9 @@ type Config struct { // Devices is a list of devices for qemu to create. Devices []Device + // NetDevices is a list of networking devices for qemu to create. + NetDevices []NetDevice + // CharDevices is a list of character devices for qemu to export. CharDevices []string @@ -513,6 +534,37 @@ func appendKnobs(params []string, config Config) []string { return params } +func appendNetDevices(params []string, config Config) []string { + for _, d := range config.NetDevices { + if d.Type != "" { + var netdevParams []string + + netdevParams = append(netdevParams, fmt.Sprintf("%s", d.Type)) + + if d.ID != "" { + netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", d.ID)) + } + + if d.IfName != "" { + netdevParams = append(netdevParams, fmt.Sprintf(",ifname=%s", d.IfName)) + } + + if d.DownScript != "" { + netdevParams = append(netdevParams, fmt.Sprintf(",downscript=%s", d.DownScript)) + } + + if d.Script != "" { + netdevParams = append(netdevParams, fmt.Sprintf(",script=%s", d.Script)) + } + + params = append(params, "-netdev") + params = append(params, strings.Join(netdevParams, "")) + } + } + + return params +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -533,6 +585,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendMemory(params, config) params = appendCPUs(params, config) params = appendDevices(params, config) + params = appendNetDevices(params, config) params = appendCharDevices(params, config) params = appendFilesystemDevices(params, config) params = appendObjects(params, config) diff --git a/qemu_test.go b/qemu_test.go index 948d931dc1..4eef5bece8 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -81,6 +81,13 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendQMPSocket([]string{}, config) + + case NetDevice: + config := Config{ + NetDevices: []NetDevice{s}, + } + + params = appendNetDevices([]string{}, config) } result := strings.Join(params, " ") @@ -258,3 +265,17 @@ func TestAppendStrings(t *testing.T) { t.Fatalf("Failed to append parameters [%s] != [%s]", result, qemuString) } } + +var netdevString = "-netdev tap,id=ceth0,ifname=ceth0,downscript=no,script=no" + +func TestAppendNetDevices(t *testing.T) { + netdev := NetDevice{ + Type: "tap", + ID: "ceth0", + IfName: "ceth0", + Script: "no", + DownScript: "no", + } + + testAppend(netdev, netdevString, t) +} From 4780e2371f6025d7b00ba14d039b03fe6ad95cbb Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Thu, 15 Sep 2016 13:21:24 +0200 Subject: [PATCH 034/264] qemu: Add multi-queue and vhost definitions to NetDevice We can now specify if we want vhost to be enabled and wich fds we should use for multiqueue support. Signed-off-by: Samuel Ortiz --- qemu.go | 21 +++++++++++++++++++++ qemu_test.go | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/qemu.go b/qemu.go index e4824d40cd..3a035439b3 100644 --- a/qemu.go +++ b/qemu.go @@ -191,6 +191,13 @@ type NetDevice struct { // 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 } // Config is the qemu configuration structure. @@ -557,6 +564,20 @@ func appendNetDevices(params []string, config Config) []string { netdevParams = append(netdevParams, fmt.Sprintf(",script=%s", d.Script)) } + if len(d.FDs) > 0 { + var fdParams []string + + for _, fd := range d.FDs { + fdParams = append(fdParams, fmt.Sprintf("%d", fd)) + } + + netdevParams = append(netdevParams, fmt.Sprintf(",fds=%s", strings.Join(fdParams, ":"))) + } + + if d.VHost == true { + netdevParams = append(netdevParams, ",vhost=on") + } + params = append(params, "-netdev") params = append(params, strings.Join(netdevParams, "")) } diff --git a/qemu_test.go b/qemu_test.go index 4eef5bece8..6d634bb955 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -266,15 +266,16 @@ func TestAppendStrings(t *testing.T) { } } -var netdevString = "-netdev tap,id=ceth0,ifname=ceth0,downscript=no,script=no" +var netdevString = "-netdev tap,id=ceth0,downscript=no,script=no,fds=8:9:10,vhost=on" func TestAppendNetDevices(t *testing.T) { netdev := NetDevice{ Type: "tap", ID: "ceth0", - IfName: "ceth0", Script: "no", DownScript: "no", + FDs: []int{8, 9, 10}, + VHost: true, } testAppend(netdev, netdevString, t) From eda8607cc66db1fcf7e9232e909e2e100d54b7ee Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Thu, 15 Sep 2016 14:45:21 +0200 Subject: [PATCH 035/264] qemu: Add netdev options to the Device structure With the NetDev and MACAddress strings, we can now create networking device drivers. We also add a unit test for netdev Device creation. Signed-off-by: Samuel Ortiz --- qemu.go | 22 ++++++++++++++++++---- qemu_test.go | 16 ++++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/qemu.go b/qemu.go index 3a035439b3..850269369d 100644 --- a/qemu.go +++ b/qemu.go @@ -45,8 +45,8 @@ type Machine struct { // Device describes a device to be created by qemu. type Device struct { - // Type is the qemu device type - Type string + // Driver is the qemu device driver + Driver string // ID is the user defined device ID. ID string @@ -57,12 +57,18 @@ type Device struct { // FSDev is the device filesystem identifier. FSDev string + // NetDev is the networking device identifier. + NetDev string + // MountTag is the device filesystem mount point tag. // It is only relevant when combined with FSDev. MountTag string // CharDev is the device character device identifier. CharDev string + + // MACAddress is the networking device interface MAC address. + MACAddress string } // Object is a qemu object representation. @@ -321,10 +327,10 @@ func appendQMPSocket(params []string, config Config) []string { func appendDevices(params []string, config Config) []string { for _, d := range config.Devices { - if d.Type != "" { + if d.Driver != "" { var deviceParams []string - deviceParams = append(deviceParams, fmt.Sprintf("%s", d.Type)) + deviceParams = append(deviceParams, fmt.Sprintf("%s", d.Driver)) if d.ID != "" { deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", d.ID)) @@ -346,6 +352,14 @@ func appendDevices(params []string, config Config) []string { } } + if d.NetDev != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", d.NetDev)) + + if d.MACAddress != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", d.MACAddress)) + } + } + params = append(params, "-device") params = append(params, strings.Join(deviceParams, "")) } diff --git a/qemu_test.go b/qemu_test.go index 6d634bb955..432096acaf 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -117,7 +117,7 @@ var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0" func TestAppendDeviceNVDIMM(t *testing.T) { device := Device{ - Type: "nvdimm", + Driver: "nvdimm", ID: "nv0", MemDev: "mem0", } @@ -129,7 +129,7 @@ var deviceFSString = "-device virtio-9p-pci,fsdev=workload9p,mount_tag=rootfs" func TestAppendDeviceFS(t *testing.T) { device := Device{ - Type: "virtio-9p-pci", + Driver: "virtio-9p-pci", FSDev: "workload9p", MountTag: "rootfs", } @@ -137,6 +137,18 @@ func TestAppendDeviceFS(t *testing.T) { testAppend(device, deviceFSString, t) } +var deviceNetworkString = "-device virtio-net-pci,netdev=eth0,mac=01:02:de:ad:be:ef" + +func TestAppendDeviceNetwork(t *testing.T) { + device := Device{ + Driver: "virtio-net-pci", + NetDev: "eth0", + MACAddress: "01:02:de:ad:be:ef", + } + + testAppend(device, deviceNetworkString, t) +} + func TestAppendEmptyDevice(t *testing.T) { device := Device{} From e543c3383d0374762cebe9eab6077425a466419c Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 16 Sep 2016 17:58:55 +0200 Subject: [PATCH 036/264] 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 --- qemu.go | 591 +++++++++++++++++++++++++++++++++------------------ qemu_test.go | 155 +++++++------- 2 files changed, 462 insertions(+), 284 deletions(-) diff --git a/qemu.go b/qemu.go index 850269369d..6e2c06b291 100644 --- a/qemu.go +++ b/qemu.go @@ -43,38 +43,111 @@ type Machine struct { Acceleration string } -// Device describes a device to be created by qemu. -type Device struct { +// 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 string + Driver DeviceDriver // ID is the user defined device ID. ID string - // MemDev is the device memory identifier. - MemDev string - - // FSDev is the device filesystem identifier. - FSDev string - - // NetDev is the networking device identifier. - NetDev string - - // MountTag is the device filesystem mount point tag. - // It is only relevant when combined with FSDev. - MountTag string - - // CharDev is the device character device identifier. - CharDev string - - // MACAddress is the networking device interface MAC address. - MACAddress 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 string + // Type is the qemu object type. + Type ObjectType // ID is the user defined object ID. ID string @@ -87,20 +160,173 @@ type Object struct { 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 { - // Type is the filesystem device type (e.g. "local") - Type string + // Driver is the filesystem driver backend. + Driver FSDriver // ID is the filesystem identifier. - // It should match an existing Device ID. 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 string + 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. @@ -181,31 +407,6 @@ type Knobs struct { NoGraphic bool } -// NetDevice represents a guest networking device -type NetDevice struct { - // Type is the netdev type (e.g. tap). - Type string - - // 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 -} - // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { @@ -230,20 +431,8 @@ type Config struct { // QMPSocket is the QMP socket description. QMPSocket QMPSocket - // Devices is a list of devices for qemu to create. - Devices []Device - - // NetDevices is a list of networking devices for qemu to create. - NetDevices []NetDevice - - // CharDevices is a list of character devices for qemu to export. - CharDevices []string - - // Objects is a list of objects for qemu to create. - Objects []Object - - // FilesystemDevices is a list of filesystem devices. - FilesystemDevices []FSDevice + // Drivers is a list of device drivers for qemu to use. + Drivers []Driver // RTC is the qemu Real Time Clock configuration RTC RTC @@ -325,107 +514,156 @@ func appendQMPSocket(params []string, config Config) []string { return params } -func appendDevices(params []string, config Config) []string { - for _, d := range config.Devices { - if d.Driver != "" { - var deviceParams []string - - deviceParams = append(deviceParams, fmt.Sprintf("%s", d.Driver)) - - if d.ID != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", d.ID)) - } - - if d.MemDev != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", d.MemDev)) - } - - if d.CharDev != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",chardev=%s", d.CharDev)) - } - - if d.FSDev != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", d.FSDev)) - - if d.MountTag != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", d.MountTag)) - } - } - - if d.NetDev != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", d.NetDev)) - - if d.MACAddress != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", d.MACAddress)) - } - } - - params = append(params, "-device") - params = append(params, strings.Join(deviceParams, "")) - } +func appendObject(params []string, object Object) ([]string, error) { + if object.Valid() == false { + return nil, fmt.Errorf("Invalid Object") } - return params -} + var objectParams []string -func appendCharDevices(params []string, config Config) []string { - for _, c := range config.CharDevices { - params = append(params, "-chardev") - params = append(params, c) + 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)) } - return params + params = append(params, "-object") + params = append(params, strings.Join(objectParams, "")) + + return params, nil } -func appendObjects(params []string, config Config) []string { - for _, o := range config.Objects { - if o.Type != "" { - var objectParams []string - - objectParams = append(objectParams, o.Type) - - if o.ID != "" { - objectParams = append(objectParams, fmt.Sprintf(",id=%s", o.ID)) - } - - if o.MemPath != "" { - objectParams = append(objectParams, fmt.Sprintf(",mem-path=%s", o.MemPath)) - } - - if o.Size > 0 { - objectParams = append(objectParams, fmt.Sprintf(",size=%d", o.Size)) - } - - params = append(params, "-object") - params = append(params, strings.Join(objectParams, "")) - } +func appendFilesystemDevice(params []string, fsdev FSDevice) ([]string, error) { + if fsdev.Valid() == false { + return nil, fmt.Errorf("Invalid filesystem device") } - return params + 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 appendFilesystemDevices(params []string, config Config) []string { - for _, f := range config.FilesystemDevices { - if f.Type != "" { - var fsParams []string +func appendCharDevice(params []string, cdev CharDevice) ([]string, error) { + if cdev.Valid() == false { + return nil, fmt.Errorf("Invalid character device") + } - fsParams = append(fsParams, fmt.Sprintf("%s", f.Type)) + var cdevParams []string - if f.ID != "" { - fsParams = append(fsParams, fmt.Sprintf(",id=%s", f.ID)) - } + cdevParams = append(cdevParams, string(cdev.Backend)) + cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) + cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path)) - if f.Path != "" { - fsParams = append(fsParams, fmt.Sprintf(",path=%s", f.Path)) - } + params = append(params, "-chardev") + params = append(params, strings.Join(cdevParams, "")) - if f.SecurityModel != "" { - fsParams = append(fsParams, fmt.Sprintf(",security-model=%s", f.SecurityModel)) - } + return params, nil +} - params = append(params, "-fsdev") - params = append(params, strings.Join(fsParams, "")) +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 @@ -555,51 +793,6 @@ func appendKnobs(params []string, config Config) []string { return params } -func appendNetDevices(params []string, config Config) []string { - for _, d := range config.NetDevices { - if d.Type != "" { - var netdevParams []string - - netdevParams = append(netdevParams, fmt.Sprintf("%s", d.Type)) - - if d.ID != "" { - netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", d.ID)) - } - - if d.IfName != "" { - netdevParams = append(netdevParams, fmt.Sprintf(",ifname=%s", d.IfName)) - } - - if d.DownScript != "" { - netdevParams = append(netdevParams, fmt.Sprintf(",downscript=%s", d.DownScript)) - } - - if d.Script != "" { - netdevParams = append(netdevParams, fmt.Sprintf(",script=%s", d.Script)) - } - - if len(d.FDs) > 0 { - var fdParams []string - - for _, fd := range d.FDs { - fdParams = append(fdParams, fmt.Sprintf("%d", fd)) - } - - netdevParams = append(netdevParams, fmt.Sprintf(",fds=%s", strings.Join(fdParams, ":"))) - } - - if d.VHost == true { - netdevParams = append(netdevParams, ",vhost=on") - } - - params = append(params, "-netdev") - params = append(params, strings.Join(netdevParams, "")) - } - } - - return params -} - // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -619,16 +812,12 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendQMPSocket(params, config) params = appendMemory(params, config) params = appendCPUs(params, config) - params = appendDevices(params, config) - params = appendNetDevices(params, config) - params = appendCharDevices(params, config) - params = appendFilesystemDevices(params, config) - params = appendObjects(params, config) + params = appendDrivers(params, config) params = appendRTC(params, config) - params = appendKernel(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) } diff --git a/qemu_test.go b/qemu_test.go index 432096acaf..ec5a73af7f 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -17,9 +17,10 @@ package qemu import ( - // "fmt" "strings" "testing" + + "github.com/01org/ciao/testutil" ) func testAppend(structure interface{}, expected string, t *testing.T) { @@ -33,19 +34,12 @@ func testAppend(structure interface{}, expected string, t *testing.T) { params = appendMachine([]string{}, config) - case Device: + case Driver: config := Config{ - Devices: []Device{s}, + Drivers: []Driver{s}, } - params = appendDevices([]string{}, config) - - case Object: - config := Config{ - Objects: []Object{s}, - } - - params = appendObjects([]string{}, config) + params = appendDrivers([]string{}, config) case Knobs: config := Config{ @@ -81,13 +75,6 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendQMPSocket([]string{}, config) - - case NetDevice: - config := Config{ - NetDevices: []NetDevice{s}, - } - - params = appendNetDevices([]string{}, config) } result := strings.Join(params, " ") @@ -113,65 +100,82 @@ func TestAppendEmptyMachine(t *testing.T) { testAppend(machine, "", t) } -var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0" +var deviceNVDIMMString = "-object memory-backend-file,id=mem0,mem-path=/root,size=65536 -device nvdimm,id=nv0,memdev=mem0" func TestAppendDeviceNVDIMM(t *testing.T) { - device := Device{ - Driver: "nvdimm", - ID: "nv0", - MemDev: "mem0", - } - - testAppend(device, deviceNVDIMMString, t) -} - -var deviceFSString = "-device virtio-9p-pci,fsdev=workload9p,mount_tag=rootfs" - -func TestAppendDeviceFS(t *testing.T) { - device := Device{ - Driver: "virtio-9p-pci", - FSDev: "workload9p", - MountTag: "rootfs", - } - - testAppend(device, deviceFSString, t) -} - -var deviceNetworkString = "-device virtio-net-pci,netdev=eth0,mac=01:02:de:ad:be:ef" - -func TestAppendDeviceNetwork(t *testing.T) { - device := Device{ - Driver: "virtio-net-pci", - NetDev: "eth0", - MACAddress: "01:02:de:ad:be:ef", - } - - testAppend(device, deviceNetworkString, t) -} - -func TestAppendEmptyDevice(t *testing.T) { - device := Device{} - - testAppend(device, "", t) -} - -var objectMemoryString = "-object memory-backend-file,id=mem0,mem-path=/root,size=65536" - -func TestAppendObjectMemory(t *testing.T) { object := Object{ - Type: "memory-backend-file", + Type: MemoryBackendFile, ID: "mem0", MemPath: "/root", Size: 1 << 16, } - testAppend(object, objectMemoryString, t) + driver := Driver{ + Driver: NVDIMM, + ID: "nv0", + Device: object, + } + + testAppend(driver, deviceNVDIMMString, t) } -func TestAppendEmptyObject(t *testing.T) { - device := Device{} +var deviceFSString = "-fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security-model=none -device virtio-9p-pci,fsdev=workload9p,mount_tag=rootfs" - testAppend(device, "", t) +func TestAppendDeviceFS(t *testing.T) { + fsdev := FSDevice{ + Driver: Local, + ID: "workload9p", + Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", + MountTag: "rootfs", + SecurityModel: None, + } + + driver := Driver{ + Driver: Virtio9P, + Device: fsdev, + } + + testAppend(driver, deviceFSString, t) +} + +var deviceNetworkString = "-netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=8:9:10,vhost=on -device virtio-net,netdev=tap0,mac=01:02:de:ad:be:ef" + +func TestAppendDeviceNetwork(t *testing.T) { + netdev := NetDevice{ + Type: TAP, + ID: "tap0", + IFName: "ceth0", + Script: "no", + DownScript: "no", + FDs: []int{8, 9, 10}, + VHost: true, + MACAddress: "01:02:de:ad:be:ef", + } + + driver := Driver{ + Driver: VirtioNet, + Device: netdev, + } + + testAppend(driver, deviceNetworkString, t) +} + +var deviceSerialString = "-device virtio-serial-pci,id=serial0" + +func TestAppendDeviceSerial(t *testing.T) { + driver := Driver{ + Driver: VirtioSerial, + ID: "serial0", + Device: SerialDevice{}, + } + + testAppend(driver, deviceSerialString, t) +} + +func TestAppendEmptyDevice(t *testing.T) { + driver := Driver{} + + testAppend(driver, "", t) } var knobsString = "-no-user-config -nodefaults -nographic" @@ -256,7 +260,7 @@ func TestAppendQMPSocket(t *testing.T) { testAppend(qmp, qmpSocketString, t) } -var qemuString = "-name cc-qemu -cpu host -uuid 123456789" +var qemuString = "-name cc-qemu -cpu host -uuid " + testutil.AgentUUID func TestAppendStrings(t *testing.T) { var params []string @@ -264,7 +268,7 @@ func TestAppendStrings(t *testing.T) { config := Config{ Path: "qemu", Name: "cc-qemu", - UUID: "123456789", + UUID: testutil.AgentUUID, CPUModel: "host", } @@ -277,18 +281,3 @@ func TestAppendStrings(t *testing.T) { t.Fatalf("Failed to append parameters [%s] != [%s]", result, qemuString) } } - -var netdevString = "-netdev tap,id=ceth0,downscript=no,script=no,fds=8:9:10,vhost=on" - -func TestAppendNetDevices(t *testing.T) { - netdev := NetDevice{ - Type: "tap", - ID: "ceth0", - Script: "no", - DownScript: "no", - FDs: []int{8, 9, 10}, - VHost: true, - } - - testAppend(netdev, netdevString, t) -} From 2d736d717353c3724736ef52a79aafdf52d5f423 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 16 Sep 2016 18:41:48 +0200 Subject: [PATCH 037/264] qemu: Add RTC specific types Instead of open coding the RTC fields, we now have specific types for it. We also have a RTC unit test now. Signed-off-by: Samuel Ortiz --- qemu.go | 88 +++++++++++++++++++++++++++++++++++++++++----------- qemu_test.go | 19 ++++++++++++ 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/qemu.go b/qemu.go index 6e2c06b291..3c42bfd394 100644 --- a/qemu.go +++ b/qemu.go @@ -329,16 +329,66 @@ func (dev SerialDevice) Valid() bool { return true } +// 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 string + Base RTCBaseType // Clock is the is the RTC clock driver. - Clock string + Clock RTCClock // DriftFix is the drift fixing mechanism. - DriftFix string + 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 } // QMPSocket represents a qemu QMP socket configuration. @@ -725,23 +775,25 @@ func appendCPUs(params []string, config Config) []string { } 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, "")) + if config.RTC.Valid() == false { + return nil } + 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)) + } + + params = append(params, "-rtc") + params = append(params, strings.Join(RTCParams, "")) + return params } diff --git a/qemu_test.go b/qemu_test.go index ec5a73af7f..fc5d7447cf 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -75,6 +75,13 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } params = appendQMPSocket([]string{}, config) + + case RTC: + config := Config{ + RTC: s, + } + + params = appendRTC([]string{}, config) } result := strings.Join(params, " ") @@ -281,3 +288,15 @@ func TestAppendStrings(t *testing.T) { 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) +} From cc9cb33a5dde152acf649c701e962eb4bd720292 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 16 Sep 2016 18:56:19 +0200 Subject: [PATCH 038/264] qemu: Add QMPSocket specific type Instead of open coding the QMP socket type, we now have a specific type for it. Signed-off-by: Samuel Ortiz --- qemu.go | 49 ++++++++++++++++++++++++++++++++++--------------- qemu_test.go | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/qemu.go b/qemu.go index 3c42bfd394..5dea84894d 100644 --- a/qemu.go +++ b/qemu.go @@ -391,10 +391,18 @@ func (rtc RTC) Valid() bool { 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 string + Type QMPSocketType // Name is the socket name. Name string @@ -406,6 +414,15 @@ type QMPSocket struct { 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 + } + + return true +} + // SMP is the multi processors configuration structure. type SMP struct { // CPUs is the number of VCPUs made available to qemu. @@ -545,22 +562,24 @@ func appendCPUModel(params []string, config Config) []string { } 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, "")) + if config.QMPSocket.Valid() == false { + return nil } + 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 } diff --git a/qemu_test.go b/qemu_test.go index fc5d7447cf..34ef1ca380 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -259,7 +259,7 @@ func TestAppendQMPSocketServer(t *testing.T) { func TestAppendQMPSocket(t *testing.T) { qmp := QMPSocket{ - Type: "unix", + Type: Unix, Name: "cc-qmp", Server: false, } From 6d7dfa04bf4702a62f761dcaf518abd6bb75bd88 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Sat, 17 Sep 2016 00:25:50 +0200 Subject: [PATCH 039/264] qemu: Get rid of the Driver structure By adding QemuParams() to the Device interface, we can get rid of the driver structure and simplify further the appendDevices() routine. With that implementation we can generate the following qemu parameters: "-device virtio-9p-pci,fsdev=foo,mount_tag=rootfs -fsdev local,id=foo,path=/bar/foo,security-model=none" from these single structures: fsdev := FSDevice{ Driver: Virtio9P FSDriver: Local, ID: "foo", Path: "/bar/foo", MountTag: "rootfs", SecurityModel: None, } Signed-off-by: Samuel Ortiz --- qemu.go | 415 ++++++++++++++++++++++++--------------------------- qemu_test.go | 57 +++---- 2 files changed, 217 insertions(+), 255 deletions(-) diff --git a/qemu.go b/qemu.go index 5dea84894d..d835a06a25 100644 --- a/qemu.go +++ b/qemu.go @@ -46,9 +46,10 @@ type Machine struct { // Device is the qemu device interface. type Device interface { Valid() bool + QemuParams() []string } -// DeviceDriver is the Device driver string. +// DeviceDriver is the device driver string. type DeviceDriver string const ( @@ -68,74 +69,6 @@ const ( 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 @@ -146,12 +79,18 @@ const ( // 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 @@ -167,11 +106,42 @@ func (object Object) Valid() bool { 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() []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 @@ -205,8 +175,11 @@ const ( // FSDevice represents a qemu filesystem configuration. type FSDevice struct { - // Driver is the filesystem driver backend. - Driver FSDriver + // Driver is the qemu device driver + Driver DeviceDriver + + // FSDriver is the filesystem driver backend. + FSDriver FSDriver // ID is the filesystem identifier. ID string @@ -230,6 +203,30 @@ func (fsdev FSDevice) Valid() bool { return true } +// QemuParams returns the qemu parameters built out of this filesystem device. +func (fsdev FSDevice) QemuParams() []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 @@ -257,6 +254,12 @@ const ( 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 } @@ -270,6 +273,46 @@ func (cdev CharDevice) Valid() bool { 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() []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 @@ -283,6 +326,9 @@ 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 @@ -320,15 +366,83 @@ func (netdev NetDevice) Valid() bool { } } +// QemuParams returns the qemu parameters built out of this network device. +func (netdev NetDevice) QemuParams() []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 + + 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") + } + + 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() []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 +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string @@ -420,6 +534,10 @@ func (qmp QMPSocket) Valid() bool { return false } + if qmp.Type != Unix { + return false + } + return true } @@ -498,8 +616,8 @@ type Config struct { // QMPSocket is the QMP socket description. QMPSocket QMPSocket - // Drivers is a list of device drivers for qemu to use. - Drivers []Driver + // Devices is a list of devices for qemu to create and drive. + Devices []Device // RTC is the qemu Real Time Clock configuration RTC RTC @@ -583,156 +701,13 @@ func appendQMPSocket(params []string, config Config) []string { 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 { +func appendDevices(params []string, config Config) []string { + for _, d := range config.Devices { 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, "")) + params = append(params, d.QemuParams()...) } return params @@ -883,7 +858,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { params = appendQMPSocket(params, config) params = appendMemory(params, config) params = appendCPUs(params, config) - params = appendDrivers(params, config) + params = appendDevices(params, config) params = appendRTC(params, config) params = appendGlobalParam(params, config) params = appendVGA(params, config) diff --git a/qemu_test.go b/qemu_test.go index 34ef1ca380..b1353bf185 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -34,12 +34,12 @@ func testAppend(structure interface{}, expected string, t *testing.T) { params = appendMachine([]string{}, config) - case Driver: + case Device: config := Config{ - Drivers: []Driver{s}, + Devices: []Device{s}, } - params = appendDrivers([]string{}, config) + params = appendDevices([]string{}, config) case Knobs: config := Config{ @@ -107,48 +107,41 @@ func TestAppendEmptyMachine(t *testing.T) { testAppend(machine, "", t) } -var deviceNVDIMMString = "-object memory-backend-file,id=mem0,mem-path=/root,size=65536 -device nvdimm,id=nv0,memdev=mem0" +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{ - Type: MemoryBackendFile, - ID: "mem0", - MemPath: "/root", - Size: 1 << 16, + Driver: NVDIMM, + Type: MemoryBackendFile, + DeviceID: "nv0", + ID: "mem0", + MemPath: "/root", + Size: 1 << 16, } - driver := Driver{ - Driver: NVDIMM, - ID: "nv0", - Device: object, - } - - testAppend(driver, deviceNVDIMMString, t) + testAppend(object, deviceNVDIMMString, t) } -var deviceFSString = "-fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security-model=none -device virtio-9p-pci,fsdev=workload9p,mount_tag=rootfs" +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: Local, + Driver: Virtio9P, + FSDriver: Local, ID: "workload9p", Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", MountTag: "rootfs", SecurityModel: None, } - driver := Driver{ - Driver: Virtio9P, - Device: fsdev, - } - - testAppend(driver, deviceFSString, t) + testAppend(fsdev, deviceFSString, t) } -var deviceNetworkString = "-netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=8:9:10,vhost=on -device virtio-net,netdev=tap0,mac=01:02:de:ad:be:ef" +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=8:9:10,vhost=on" func TestAppendDeviceNetwork(t *testing.T) { netdev := NetDevice{ + Driver: VirtioNet, Type: TAP, ID: "tap0", IFName: "ceth0", @@ -159,30 +152,24 @@ func TestAppendDeviceNetwork(t *testing.T) { MACAddress: "01:02:de:ad:be:ef", } - driver := Driver{ - Driver: VirtioNet, - Device: netdev, - } - - testAppend(driver, deviceNetworkString, t) + testAppend(netdev, deviceNetworkString, t) } var deviceSerialString = "-device virtio-serial-pci,id=serial0" func TestAppendDeviceSerial(t *testing.T) { - driver := Driver{ + sdev := SerialDevice{ Driver: VirtioSerial, ID: "serial0", - Device: SerialDevice{}, } - testAppend(driver, deviceSerialString, t) + testAppend(sdev, deviceSerialString, t) } func TestAppendEmptyDevice(t *testing.T) { - driver := Driver{} + device := SerialDevice{} - testAppend(driver, "", t) + testAppend(device, "", t) } var knobsString = "-no-user-config -nodefaults -nographic" From 3908185ccd0382243899977a595d91dd8f22f150 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Sat, 17 Sep 2016 00:40:30 +0200 Subject: [PATCH 040/264] qemu: Add MACVTAP support The networking device structure now supports MACVTAP. Signed-off-by: Samuel Ortiz --- qemu.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qemu.go b/qemu.go index d835a06a25..42a2a9d7e8 100644 --- a/qemu.go +++ b/qemu.go @@ -319,6 +319,9 @@ 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 @@ -361,6 +364,8 @@ func (netdev NetDevice) Valid() bool { switch netdev.Type { case TAP: return true + case MACVTAP: + return true default: return false } From e193a77b8d5ebae5f207ff63a44f28cb8fed1d0b Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Sat, 17 Sep 2016 01:08:31 +0200 Subject: [PATCH 041/264] qemu: Add support for block devices For now we only support QCOW2 backed block devices. Signed-off-by: Samuel Ortiz --- qemu.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu_test.go | 17 +++++++++++ 2 files changed, 102 insertions(+) diff --git a/qemu.go b/qemu.go index 42a2a9d7e8..dc05031fda 100644 --- a/qemu.go +++ b/qemu.go @@ -65,6 +65,9 @@ const ( // 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" ) @@ -448,6 +451,88 @@ func (dev SerialDevice) QemuParams() []string { 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() []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 diff --git a/qemu_test.go b/qemu_test.go index b1353bf185..0da576530f 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -166,6 +166,23 @@ func TestAppendDeviceSerial(t *testing.T) { 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{} From 12f6ebe3891e631c3c2c8819bdcf3faffd31eaf8 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 19 Sep 2016 12:07:00 +0200 Subject: [PATCH 042/264] qemu: Embed the qemu parameters into the Config structure It is a private field now, and all append*() routines are now Config methods instead of private qemu functions. Since we will have to carry a kernelParams private field as well, this change will keep all built parameters internal and make things consistent. Signed-off-by: Samuel Ortiz --- qemu.go | 146 +++++++++++++++++++++------------------------------ qemu_test.go | 70 ++++++++---------------- 2 files changed, 82 insertions(+), 134 deletions(-) diff --git a/qemu.go b/qemu.go index dc05031fda..c1f89715d2 100644 --- a/qemu.go +++ b/qemu.go @@ -732,18 +732,18 @@ type Config struct { // FDs is a list of open file descriptors to be passed to the spawned qemu process FDs []*os.File + + qemuParams []string } -func appendName(params []string, config Config) []string { +func (config *Config) appendName() { if config.Name != "" { - params = append(params, "-name") - params = append(params, config.Name) + config.qemuParams = append(config.qemuParams, "-name") + config.qemuParams = append(config.qemuParams, config.Name) } - - return params } -func appendMachine(params []string, config Config) []string { +func (config *Config) appendMachine() { if config.Machine.Type != "" { var machineParams []string @@ -753,25 +753,21 @@ func appendMachine(params []string, config Config) []string { machineParams = append(machineParams, fmt.Sprintf(",accel=%s", config.Machine.Acceleration)) } - params = append(params, "-machine") - params = append(params, strings.Join(machineParams, "")) + config.qemuParams = append(config.qemuParams, "-machine") + config.qemuParams = append(config.qemuParams, strings.Join(machineParams, "")) } - - return params } -func appendCPUModel(params []string, config Config) []string { +func (config *Config) appendCPUModel() { if config.CPUModel != "" { - params = append(params, "-cpu") - params = append(params, config.CPUModel) + config.qemuParams = append(config.qemuParams, "-cpu") + config.qemuParams = append(config.qemuParams, config.CPUModel) } - - return params } -func appendQMPSocket(params []string, config Config) []string { +func (config *Config) appendQMPSocket() { if config.QMPSocket.Valid() == false { - return nil + return } var qmpParams []string @@ -785,34 +781,28 @@ func appendQMPSocket(params []string, config Config) []string { } } - params = append(params, "-qmp") - params = append(params, strings.Join(qmpParams, "")) - - return params + config.qemuParams = append(config.qemuParams, "-qmp") + config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, "")) } -func appendDevices(params []string, config Config) []string { +func (config *Config) appendDevices() { for _, d := range config.Devices { if d.Valid() == false { continue } - params = append(params, d.QemuParams()...) + config.qemuParams = append(config.qemuParams, d.QemuParams()...) } - - return params } -func appendUUID(params []string, config Config) []string { +func (config *Config) appendUUID() { if config.UUID != "" { - params = append(params, "-uuid") - params = append(params, config.UUID) + config.qemuParams = append(config.qemuParams, "-uuid") + config.qemuParams = append(config.qemuParams, config.UUID) } - - return params } -func appendMemory(params []string, config Config) []string { +func (config *Config) appendMemory() { if config.Memory.Size != "" { var memoryParams []string @@ -826,14 +816,12 @@ func appendMemory(params []string, config Config) []string { memoryParams = append(memoryParams, fmt.Sprintf(",maxmem=%s", config.Memory.MaxMem)) } - params = append(params, "-m") - params = append(params, strings.Join(memoryParams, "")) + config.qemuParams = append(config.qemuParams, "-m") + config.qemuParams = append(config.qemuParams, strings.Join(memoryParams, "")) } - - return params } -func appendCPUs(params []string, config Config) []string { +func (config *Config) appendCPUs() { if config.SMP.CPUs > 0 { var SMPParams []string @@ -851,16 +839,14 @@ func appendCPUs(params []string, config Config) []string { SMPParams = append(SMPParams, fmt.Sprintf(",sockets=%d", config.SMP.Sockets)) } - params = append(params, "-smp") - params = append(params, strings.Join(SMPParams, "")) + config.qemuParams = append(config.qemuParams, "-smp") + config.qemuParams = append(config.qemuParams, strings.Join(SMPParams, "")) } - - return params } -func appendRTC(params []string, config Config) []string { +func (config *Config) appendRTC() { if config.RTC.Valid() == false { - return nil + return } var RTCParams []string @@ -875,58 +861,48 @@ func appendRTC(params []string, config Config) []string { RTCParams = append(RTCParams, fmt.Sprintf(",clock=%s", config.RTC.Clock)) } - params = append(params, "-rtc") - params = append(params, strings.Join(RTCParams, "")) - - return params + config.qemuParams = append(config.qemuParams, "-rtc") + config.qemuParams = append(config.qemuParams, strings.Join(RTCParams, "")) } -func appendGlobalParam(params []string, config Config) []string { +func (config *Config) appendGlobalParam() { if config.GlobalParam != "" { - params = append(params, "-global") - params = append(params, config.GlobalParam) + config.qemuParams = append(config.qemuParams, "-global") + config.qemuParams = append(config.qemuParams, config.GlobalParam) } - - return params } -func appendVGA(params []string, config Config) []string { +func (config *Config) appendVGA() { if config.VGA != "" { - params = append(params, "-vga") - params = append(params, config.VGA) + config.qemuParams = append(config.qemuParams, "-vga") + config.qemuParams = append(config.qemuParams, config.VGA) } - - return params } -func appendKernel(params []string, config Config) []string { +func (config *Config) appendKernel() { if config.Kernel.Path != "" { - params = append(params, "-kernel") - params = append(params, config.Kernel.Path) + config.qemuParams = append(config.qemuParams, "-kernel") + config.qemuParams = append(config.qemuParams, config.Kernel.Path) if config.Kernel.Params != "" { - params = append(params, "-append") - params = append(params, config.Kernel.Params) + config.qemuParams = append(config.qemuParams, "-append") + config.qemuParams = append(config.qemuParams, config.Kernel.Params) } } - - return params } -func appendKnobs(params []string, config Config) []string { +func (config *Config) appendKnobs() { if config.Knobs.NoUserConfig == true { - params = append(params, "-no-user-config") + config.qemuParams = append(config.qemuParams, "-no-user-config") } if config.Knobs.NoDefaults == true { - params = append(params, "-nodefaults") + config.qemuParams = append(config.qemuParams, "-nodefaults") } if config.Knobs.NoGraphic == true { - params = append(params, "-nographic") + config.qemuParams = append(config.qemuParams, "-nographic") } - - return params } // LaunchQemu can be used to launch a new qemu instance. @@ -939,23 +915,21 @@ func appendKnobs(params []string, config Config) []string { // 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 + 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() - 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 = appendDevices(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) + return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, config.FDs, logger) } // LaunchCustomQemu can be used to launch a new qemu instance. @@ -966,7 +940,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { // 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. // diff --git a/qemu_test.go b/qemu_test.go index 0da576530f..a5875fe256 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -24,67 +24,43 @@ import ( ) func testAppend(structure interface{}, expected string, t *testing.T) { - var params []string + var config Config switch s := structure.(type) { case Machine: - config := Config{ - Machine: s, - } - - params = appendMachine([]string{}, config) + config.Machine = s + config.appendMachine() case Device: - config := Config{ - Devices: []Device{s}, - } - - params = appendDevices([]string{}, config) + config.Devices = []Device{s} + config.appendDevices() case Knobs: - config := Config{ - Knobs: s, - } - - params = appendKnobs([]string{}, config) + config.Knobs = s + config.appendKnobs() case Kernel: - config := Config{ - Kernel: s, - } - - params = appendKernel([]string{}, config) + config.Kernel = s + config.appendKernel() case Memory: - config := Config{ - Memory: s, - } - - params = appendMemory([]string{}, config) + config.Memory = s + config.appendMemory() case SMP: - config := Config{ - SMP: s, - } - - params = appendCPUs([]string{}, config) + config.SMP = s + config.appendCPUs() case QMPSocket: - config := Config{ - QMPSocket: s, - } - - params = appendQMPSocket([]string{}, config) + config.QMPSocket = s + config.appendQMPSocket() case RTC: - config := Config{ - RTC: s, - } - - params = appendRTC([]string{}, config) + config.RTC = s + config.appendRTC() } - result := strings.Join(params, " ") + result := strings.Join(config.qemuParams, " ") if result != expected { t.Fatalf("Failed to append parameters [%s] != [%s]", result, expected) } @@ -274,8 +250,6 @@ func TestAppendQMPSocket(t *testing.T) { var qemuString = "-name cc-qemu -cpu host -uuid " + testutil.AgentUUID func TestAppendStrings(t *testing.T) { - var params []string - config := Config{ Path: "qemu", Name: "cc-qemu", @@ -283,11 +257,11 @@ func TestAppendStrings(t *testing.T) { CPUModel: "host", } - params = appendName(params, config) - params = appendCPUModel(params, config) - params = appendUUID(params, config) + config.appendName() + config.appendCPUModel() + config.appendUUID() - result := strings.Join(params, " ") + result := strings.Join(config.qemuParams, " ") if result != qemuString { t.Fatalf("Failed to append parameters [%s] != [%s]", result, qemuString) } From db067857bd4bb9c0d590bdabb647dc78e23e6a71 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 19 Sep 2016 12:37:58 +0200 Subject: [PATCH 043/264] qemu: Make Config's FDs field private All file descriptors will come from specific devices configurations, so this patch: 1) Make the Config FDs file private 2) Provide an appendFDs() method for Config, that takes a slice of os.File pointers and a) Adds them to the Config private fd slice b) Return a slice of ints that represent the file descriptors for these device specific files, as seen by the qemu process. Signed-off-by: Samuel Ortiz --- qemu.go | 48 +++++++++++++++++++++++++++++++++++------------- qemu_test.go | 12 ++++++++++-- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/qemu.go b/qemu.go index c1f89715d2..869ae3be55 100644 --- a/qemu.go +++ b/qemu.go @@ -46,7 +46,7 @@ type Machine struct { // Device is the qemu device interface. type Device interface { Valid() bool - QemuParams() []string + QemuParams(config *Config) []string } // DeviceDriver is the device driver string. @@ -118,7 +118,7 @@ func (object Object) Valid() bool { } // QemuParams returns the qemu parameters built out of this Object device. -func (object Object) QemuParams() []string { +func (object Object) QemuParams(config *Config) []string { var objectParams []string var deviceParams []string var qemuParams []string @@ -207,7 +207,7 @@ func (fsdev FSDevice) Valid() bool { } // QemuParams returns the qemu parameters built out of this filesystem device. -func (fsdev FSDevice) QemuParams() []string { +func (fsdev FSDevice) QemuParams(config *Config) []string { var fsParams []string var deviceParams []string var qemuParams []string @@ -294,7 +294,7 @@ func appendCharDevice(params []string, cdev CharDevice) ([]string, error) { } // QemuParams returns the qemu parameters built out of this character device. -func (cdev CharDevice) QemuParams() []string { +func (cdev CharDevice) QemuParams(config *Config) []string { var cdevParams []string var deviceParams []string var qemuParams []string @@ -349,7 +349,7 @@ type NetDevice struct { // FDs represents the list of already existing file descriptors to be used. // This is mostly useful for mq support. - FDs []int + FDs []*os.File // VHost enables virtio device emulation from the host kernel instead of from qemu. VHost bool @@ -375,7 +375,7 @@ func (netdev NetDevice) Valid() bool { } // QemuParams returns the qemu parameters built out of this network device. -func (netdev NetDevice) QemuParams() []string { +func (netdev NetDevice) QemuParams(config *Config) []string { var netdevParams []string var deviceParams []string var qemuParams []string @@ -399,7 +399,9 @@ func (netdev NetDevice) QemuParams() []string { if len(netdev.FDs) > 0 { var fdParams []string - for _, fd := range netdev.FDs { + qemuFDs := config.appendFDs(netdev.FDs) + + for _, fd := range qemuFDs { fdParams = append(fdParams, fmt.Sprintf("%d", fd)) } @@ -438,7 +440,7 @@ func (dev SerialDevice) Valid() bool { } // QemuParams returns the qemu parameters built out of this serial device. -func (dev SerialDevice) QemuParams() []string { +func (dev SerialDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string @@ -503,7 +505,7 @@ func (blkdev BlockDevice) Valid() bool { } // QemuParams returns the qemu parameters built out of this block device. -func (blkdev BlockDevice) QemuParams() []string { +func (blkdev BlockDevice) QemuParams(config *Config) []string { var blkParams []string var deviceParams []string var qemuParams []string @@ -730,12 +732,32 @@ type Config struct { // 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 + // 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") @@ -791,7 +813,7 @@ func (config *Config) appendDevices() { continue } - config.qemuParams = append(config.qemuParams, d.QemuParams()...) + config.qemuParams = append(config.qemuParams, d.QemuParams(config)...) } } @@ -929,7 +951,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendKnobs() config.appendKernel() - return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, config.FDs, logger) + return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, config.fds, logger) } // LaunchCustomQemu can be used to launch a new qemu instance. diff --git a/qemu_test.go b/qemu_test.go index a5875fe256..647b202a92 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -17,6 +17,8 @@ package qemu import ( + "io/ioutil" + "os" "strings" "testing" @@ -113,9 +115,15 @@ func TestAppendDeviceFS(t *testing.T) { 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=8:9:10,vhost=on" +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, @@ -123,7 +131,7 @@ func TestAppendDeviceNetwork(t *testing.T) { IFName: "ceth0", Script: "no", DownScript: "no", - FDs: []int{8, 9, 10}, + FDs: []*os.File{foo, bar}, VHost: true, MACAddress: "01:02:de:ad:be:ef", } From eae8fae0e74ce8920dc68e542b820f6708ea3d16 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Wed, 21 Sep 2016 17:48:47 +0200 Subject: [PATCH 044/264] qemu: Fix security model typo The right qemu parameter is "security_model", not "security-model". Signed-off-by: Samuel Ortiz --- qemu.go | 2 +- qemu_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu.go b/qemu.go index 869ae3be55..15cb20c1a0 100644 --- a/qemu.go +++ b/qemu.go @@ -219,7 +219,7 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { 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)) + fsParams = append(fsParams, fmt.Sprintf(",security_model=%s", fsdev.SecurityModel)) qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) diff --git a/qemu_test.go b/qemu_test.go index 647b202a92..87e17ec9fe 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -100,7 +100,7 @@ func TestAppendDeviceNVDIMM(t *testing.T) { 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" +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{ From e555f565f46c0bb8f7841b01246860b36fb3589f Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 30 Sep 2016 15:42:59 +0200 Subject: [PATCH 045/264] qemu: Add support for socket based consoles When we get no virtual console to plug into, we may want qemu to create a socket where we can asynchronously connect to. Signed-off-by: Samuel Ortiz --- qemu.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 15cb20c1a0..974ebfb934 100644 --- a/qemu.go +++ b/qemu.go @@ -305,7 +305,11 @@ func (cdev CharDevice) QemuParams(config *Config) []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)) + if cdev.Backend == Socket { + cdevParams = append(cdevParams, fmt.Sprintf(",path=%s,server,nowait", cdev.Path)) + } else { + cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path)) + } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) From 997cb233996c3c08ff6fbad245689167e854a801 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 30 Sep 2016 15:46:57 +0200 Subject: [PATCH 046/264] qemu: Remove dead code appendCharDevice() got replaced by the CharDevice's QemuParams method but never got deleted. Signed-off-by: Samuel Ortiz --- qemu.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/qemu.go b/qemu.go index 974ebfb934..9cc7145f47 100644 --- a/qemu.go +++ b/qemu.go @@ -276,23 +276,6 @@ func (cdev CharDevice) Valid() bool { 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 From 992b861ec576336b9d5becc8af6914f80175f279 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 30 Sep 2016 15:54:46 +0200 Subject: [PATCH 047/264] qemu: Add the daemonize qemu option to the Knobs structure This way callers can choose if they want the qemu process to be a daemon or not. Signed-off-by: Samuel Ortiz --- qemu.go | 7 +++++++ qemu_test.go | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 9cc7145f47..b5760141e1 100644 --- a/qemu.go +++ b/qemu.go @@ -669,6 +669,9 @@ type Knobs struct { // NoGraphic completely disables graphic output. NoGraphic bool + + // Daemonize will turn the qemu process into a daemon + Daemonize bool } // Config is the qemu configuration structure. @@ -912,6 +915,10 @@ func (config *Config) appendKnobs() { if config.Knobs.NoGraphic == true { config.qemuParams = append(config.qemuParams, "-nographic") } + + if config.Knobs.Daemonize == true { + config.qemuParams = append(config.qemuParams, "-daemonize") + } } // LaunchQemu can be used to launch a new qemu instance. diff --git a/qemu_test.go b/qemu_test.go index 87e17ec9fe..18b016c62a 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -173,13 +173,14 @@ func TestAppendEmptyDevice(t *testing.T) { testAppend(device, "", t) } -var knobsString = "-no-user-config -nodefaults -nographic" +var knobsString = "-no-user-config -nodefaults -nographic -daemonize" func TestAppendKnobsAllTrue(t *testing.T) { knobs := Knobs{ NoUserConfig: true, NoDefaults: true, NoGraphic: true, + Daemonize: true, } testAppend(knobs, knobsString, t) From 6fe338d604357b10944220d0cac39f5712ba3f5e Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 11 Oct 2016 14:32:41 +0200 Subject: [PATCH 048/264] qemu: Support creating multiple QMP sockets The QMP socket implementation does not support multiple clients sending and receiving QMP commands. As a consequence we need to be able to create multiple QMP sockets from the qemu package, so that at least we can support a fixed number of QMP clients. Signed-off-by: Samuel Ortiz --- qemu.go | 38 +++++++++++++++++++------------------- qemu_test.go | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/qemu.go b/qemu.go index b5760141e1..739ce14efe 100644 --- a/qemu.go +++ b/qemu.go @@ -695,8 +695,8 @@ type Config struct { // Machine Machine Machine - // QMPSocket is the QMP socket description. - QMPSocket QMPSocket + // QMPSockets is a slice of QMP socket description. + QMPSockets []QMPSocket // Devices is a list of devices for qemu to create and drive. Devices []Device @@ -777,24 +777,24 @@ func (config *Config) appendCPUModel() { } } -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") +func (config *Config) appendQMPSockets() { + for _, q := range config.QMPSockets { + if q.Valid() == false { + continue } - } - config.qemuParams = append(config.qemuParams, "-qmp") - config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, "")) + qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type)) + qmpParams = append(qmpParams, fmt.Sprintf("%s", q.Name)) + if q.Server == true { + qmpParams = append(qmpParams, ",server") + if q.NoWait == true { + qmpParams = append(qmpParams, ",nowait") + } + } + + config.qemuParams = append(config.qemuParams, "-qmp") + config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, "")) + } } func (config *Config) appendDevices() { @@ -935,7 +935,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendUUID() config.appendMachine() config.appendCPUModel() - config.appendQMPSocket() + config.appendQMPSockets() config.appendMemory() config.appendCPUs() config.appendDevices() diff --git a/qemu_test.go b/qemu_test.go index 18b016c62a..32a511dd68 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -54,8 +54,12 @@ func testAppend(structure interface{}, expected string, t *testing.T) { config.appendCPUs() case QMPSocket: - config.QMPSocket = s - config.appendQMPSocket() + config.QMPSockets = []QMPSocket{s} + config.appendQMPSockets() + + case []QMPSocket: + config.QMPSockets = s + config.appendQMPSockets() case RTC: config.RTC = s @@ -232,10 +236,10 @@ func TestAppendCPUs(t *testing.T) { testAppend(smp, cpusString, t) } -var qmpSocketServerString = "-qmp unix:cc-qmp,server,nowait" -var qmpSocketString = "-qmp unix:cc-qmp" +var qmpSingleSocketServerString = "-qmp unix:cc-qmp,server,nowait" +var qmpSingleSocketString = "-qmp unix:cc-qmp" -func TestAppendQMPSocketServer(t *testing.T) { +func TestAppendSingleQMPSocketServer(t *testing.T) { qmp := QMPSocket{ Type: "unix", Name: "cc-qmp", @@ -243,17 +247,38 @@ func TestAppendQMPSocketServer(t *testing.T) { NoWait: true, } - testAppend(qmp, qmpSocketServerString, t) + testAppend(qmp, qmpSingleSocketServerString, t) } -func TestAppendQMPSocket(t *testing.T) { +func TestAppendSingleQMPSocket(t *testing.T) { qmp := QMPSocket{ Type: Unix, Name: "cc-qmp", Server: false, } - testAppend(qmp, qmpSocketString, t) + testAppend(qmp, qmpSingleSocketString, t) +} + +var qmpSocketServerString = "-qmp unix:cc-qmp-1,server,nowait -qmp unix:cc-qmp-2,server,nowait" + +func TestAppendQMPSocketServer(t *testing.T) { + qmp := []QMPSocket{ + { + Type: "unix", + Name: "cc-qmp-1", + Server: true, + NoWait: true, + }, + { + Type: "unix", + Name: "cc-qmp-2", + Server: true, + NoWait: true, + }, + } + + testAppend(qmp, qmpSocketServerString, t) } var qemuString = "-name cc-qemu -cpu host -uuid " + testutil.AgentUUID From 2aa5f5a3c01bb03124a3a4b7f4f823f0cc1ad000 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Thu, 13 Oct 2016 14:40:15 +0200 Subject: [PATCH 049/264] qemu: Add support for serial port addition We add a new device driver, and also a name to the CharDev structure this is needed for qemu to actually create the serial port on the guest. Signed-off-by: Samuel Ortiz --- qemu.go | 7 +++++++ qemu_test.go | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/qemu.go b/qemu.go index 739ce14efe..f5dd579374 100644 --- a/qemu.go +++ b/qemu.go @@ -70,6 +70,9 @@ const ( // Console is the console device driver. Console = "virtconsole" + + // VirtioSerialPort is the serial port device driver. + VirtioSerialPort = "virtserialport" ) // ObjectType is a string representing a qemu object type. @@ -265,6 +268,7 @@ type CharDevice struct { ID string Path string + Name string } // Valid returns true if the CharDevice structure is valid and complete. @@ -285,6 +289,9 @@ func (cdev CharDevice) QemuParams(config *Config) []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)) + if cdev.Name != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",name=%s", cdev.Name)) + } cdevParams = append(cdevParams, string(cdev.Backend)) cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) diff --git a/qemu_test.go b/qemu_test.go index 32a511dd68..193304084f 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -154,6 +154,21 @@ func TestAppendDeviceSerial(t *testing.T) { testAppend(sdev, deviceSerialString, t) } +var deviceSerialPortString = "-device virtserialport,chardev=char0,id=channel0,name=channel.0 -chardev socket,id=char0,path=/tmp/char.sock,server,nowait" + +func TestAppendDeviceSerialPort(t *testing.T) { + chardev := CharDevice{ + Driver: VirtioSerialPort, + Backend: Socket, + ID: "char0", + DeviceID: "channel0", + Path: "/tmp/char.sock", + Name: "channel.0", + } + + testAppend(chardev, deviceSerialPortString, 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) { From 30cf11632c4a5e876a4ef5a13dce84ab52c7740e Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Fri, 21 Oct 2016 08:53:23 -0700 Subject: [PATCH 050/264] Add missing bus parameter for a CharDevice When creating a CharDevice, we need to add a "bus" parameter so that it can match the serial pci device previously created. Signed-off-by: Sebastien Boeuf --- qemu.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qemu.go b/qemu.go index f5dd579374..ea344b2eba 100644 --- a/qemu.go +++ b/qemu.go @@ -263,6 +263,9 @@ type CharDevice struct { // Driver is the qemu device driver Driver DeviceDriver + // Bus is the serial bus associated to this device. + Bus string + // DeviceID is the user defined device ID. DeviceID string @@ -287,6 +290,9 @@ func (cdev CharDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", cdev.Driver)) + if cdev.Bus != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", cdev.Bus)) + } deviceParams = append(deviceParams, fmt.Sprintf(",chardev=%s", cdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", cdev.DeviceID)) if cdev.Name != "" { From a8a798b0c02ba46476490ab22e99d8f9cf84b288 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 6 Feb 2017 12:16:09 +0000 Subject: [PATCH 051/264] qemu, ciao-launcher: Move ConfigDrive ISO creation code to qemu Launcher's ConfigDrive ISO creation function, createCloudInitISO has been moved to the qemu package so that it can be re-used by ciao-down. Signed-off-by: Mark Ryan --- image.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 image.go diff --git a/image.go b/image.go new file mode 100644 index 0000000000..9b456872da --- /dev/null +++ b/image.go @@ -0,0 +1,72 @@ +/* +// Copyright (c) 2017 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 ( + "context" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" +) + +// CreateCloudInitISO creates a cloud-init ConfigDrive ISO image. This is +// useful for configuring newly booted VMs. Before it can create the ISO +// image it needs to create a file tree with the various files that will +// make up the image. This directory is created under scratchDir and is +// deleted when when the function returns, successfully or otherwise. ctx is +// a context that can be used to timeout or cancel the image creation. +// isoPath contains the desired path of the ISO image to be created. The +// userdata and metadata parameters are byte slices that contain the +// ConfigDrive userdata and metadata that will be stored with the ISO image. +func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, + userData, metaData []byte) error { + configDrivePath := path.Join(scratchDir, "clr-cloud-init") + dataDirPath := path.Join(configDrivePath, "openstack", "latest") + metaDataPath := path.Join(dataDirPath, "meta_data.json") + userDataPath := path.Join(dataDirPath, "user_data") + + defer func() { + _ = os.RemoveAll(configDrivePath) + }() + + err := os.MkdirAll(dataDirPath, 0755) + if err != nil { + return fmt.Errorf("Unable to create config drive directory %s : %v", + dataDirPath, err) + } + + err = ioutil.WriteFile(metaDataPath, metaData, 0644) + if err != nil { + return fmt.Errorf("Unable to create %s : %v", metaDataPath, err) + } + + err = ioutil.WriteFile(userDataPath, userData, 0644) + if err != nil { + return fmt.Errorf("Unable to create %s : %v", userDataPath, err) + } + + cmd := exec.CommandContext(ctx, "xorriso", "-as", "mkisofs", "-R", "-V", "config-2", + "-o", isoPath, configDrivePath) + err = cmd.Run() + if err != nil { + return fmt.Errorf("Unable to create cloudinit iso image %v", err) + } + + return nil +} From c6f334533ad05348ebfba942693c6e16329b32b8 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Tue, 14 Feb 2017 18:25:59 +0000 Subject: [PATCH 052/264] qemu: Fix command cancelling. There was a bug with the cancelling of commands that meant that when an attempt was made to cancel a command and then to issue a second command, the first, cancelled command was re-issued. This commit fixes the issue and adds a new test case to check that cancelling of commands does indeed work. There was also an issue with the test harness which meant that tests that issued more than one command were not actually testing the second and third commands. Signed-off-by: Mark Ryan --- qmp.go | 34 ++++++++++++++++- qmp_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/qmp.go b/qmp.go index bc2596823b..c7ed0ec489 100644 --- a/qmp.go +++ b/qmp.go @@ -254,6 +254,15 @@ func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { } } +func currentCommandDoneCh(cmdQueue *list.List) <-chan struct{} { + cmdEl := cmdQueue.Front() + if cmdEl == nil { + return nil + } + cmd := cmdEl.Value.(*qmpCommand) + return cmd.ctx.Done() +} + func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { cmdEl := cmdQueue.Front() cmd := cmdEl.Value.(*qmpCommand) @@ -293,6 +302,16 @@ func failOutstandingCommands(cmdQueue *list.List) { } } +func (q *QMP) cancelCurrentCommand(cmdQueue *list.List) { + cmdEl := cmdQueue.Front() + cmd := cmdEl.Value.(*qmpCommand) + if cmd.resultReceived { + q.finaliseCommand(cmdEl, cmdQueue, false) + } else { + cmd.filter = nil + } +} + func (q *QMP) parseVersion(version []byte) *QMPVersion { var qmp map[string]interface{} err := json.Unmarshal(version, &qmp) @@ -343,6 +362,7 @@ func (q *QMP) mainLoop() { }() version := []byte{} + var cmdDoneCh <-chan struct{} DONE: for { @@ -359,6 +379,7 @@ DONE: } if cmdQueue.Len() >= 1 { q.writeNextQMPCommand(cmdQueue) + cmdDoneCh = currentCommandDoneCh(cmdQueue) } break DONE } @@ -373,14 +394,25 @@ DONE: return } _ = cmdQueue.PushBack(&cmd) - if cmdQueue.Len() >= 1 { + + // We only want to execute the new cmd if there + // are no other commands pending. If there are + // commands pending our new command will get + // run when the pending commands complete. + + if cmdQueue.Len() == 1 { q.writeNextQMPCommand(cmdQueue) + cmdDoneCh = currentCommandDoneCh(cmdQueue) } case line, ok := <-fromVMCh: if !ok { return } q.processQMPInput(line, cmdQueue) + cmdDoneCh = currentCommandDoneCh(cmdQueue) + case <-cmdDoneCh: + q.cancelCurrentCommand(cmdQueue) + cmdDoneCh = currentCommandDoneCh(cmdQueue) } } } diff --git a/qmp_test.go b/qmp_test.go index 4d60c0099b..a052c9055e 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -186,7 +186,9 @@ func (b *qmpTestCommandBuffer) Read(p []byte) (n int, err error) { func (b *qmpTestCommandBuffer) Write(p []byte) (int, error) { var cmdJSON map[string]interface{} - if b.currentCmd >= len(b.cmds) { + currentCmd := b.currentCmd + b.currentCmd++ + if currentCmd >= len(b.cmds) { b.t.Fatalf("Unexpected command") } err := json.Unmarshal(p, &cmdJSON) @@ -195,14 +197,14 @@ func (b *qmpTestCommandBuffer) Write(p []byte) (int, error) { } cmdName := cmdJSON["execute"] gotCmdName := cmdName.(string) - result := b.results[b.currentCmd].result - if gotCmdName != b.cmds[b.currentCmd].name { + result := b.results[currentCmd].result + if gotCmdName != b.cmds[currentCmd].name { b.t.Errorf("Unexpected command. Expected %s found %s", - b.cmds[b.currentCmd].name, gotCmdName) + b.cmds[currentCmd].name, gotCmdName) result = "error" } resultMap := make(map[string]interface{}) - resultMap[result] = b.results[b.currentCmd].data + resultMap[result] = b.results[currentCmd].data encodedRes, err := json.Marshal(&resultMap) if err != nil { b.t.Errorf("Unable to encode result: %v", err) @@ -582,6 +584,101 @@ func TestQMPSystemPowerdown(t *testing.T) { wg.Wait() } +// Checks that event commands can be cancelled. +// +// We start a QMPLoop, send the system_powerdown command. This command +// will time out after 1 second as the SHUTDOWN event never arrives. +// We then send a quit command to terminate the session. +// +// The system_powerdown command should be correctly sent but should block +// waiting for the SHUTDOWN event and should be successfully cancelled. +// The quit command should be successfully received and the QMP loop should +// exit gracefully. +func TestQMPEventedCommandCancel(t *testing.T) { + var wg sync.WaitGroup + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommmand("system_powerdown", nil, "return", nil) + buf.AddCommmand("quit", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + buf.startEventLoop(&wg) + ctx, cancelFN := context.WithTimeout(context.Background(), time.Second) + err := q.ExecuteSystemPowerdown(ctx) + cancelFN() + if err == nil { + t.Fatalf("Expected SystemPowerdown to fail") + } + err = q.ExecuteQuit(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh + wg.Wait() +} + +// Checks that queued commands execute after an evented command is cancelled. +// +// This test is similar to the previous test with the exception that it +// tries to ensure that a second command is placed on the QMP structure's +// command queue before the evented command is cancelled. This allows us +// to test a slightly different use case. We start a QMPLoop, send the +// system_powerdown command. We do this by sending the command directly +// down the QMP.cmdCh rather than calling a higher level function as this +// allows us to ensure that we have another command queued before we +// timeout the first command. We then send a qmp_capabilities command and +// then we shutdown. +// +// The system_powerdown command should be correctly sent but should block +// waiting for the SHUTDOWN event and should be successfully cancelled. +// The query_capabilities command should be successfully received and the +// QMP loop should exit gracefully. +func TestQMPEventedCommandCancelConcurrent(t *testing.T) { + var wg sync.WaitGroup + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + + buf.AddCommmand("system_powerdown", nil, "error", nil) + buf.AddCommmand("qmp_capabilities", nil, "return", nil) + + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + buf.startEventLoop(&wg) + + resCh := make(chan qmpResult) + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) + q.cmdCh <- qmpCommand{ + ctx: ctx, + res: resCh, + name: "system_powerdown", + filter: &qmpEventFilter{ + eventName: "SHUTDOWN", + }, + } + + var cmdWg sync.WaitGroup + cmdWg.Add(1) + go func() { + err := q.ExecuteQMPCapabilities(context.Background()) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + cmdWg.Done() + }() + + <-resCh + cancelFn() + cmdWg.Wait() + q.Shutdown() + <-disconnectedCh + wg.Wait() +} + // Checks that events can be received and parsed. // // Two events are provisioned and the QMPLoop is started with an valid eventCh. From 1e7202a5a678a33bf7f73e691edc29d69adca53c Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Wed, 15 Feb 2017 18:41:18 +0000 Subject: [PATCH 053/264] qemu: Fix spelling error in qmp_test.go Command only has two ms. Signed-off-by: Mark Ryan --- qmp_test.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/qmp_test.go b/qmp_test.go index a052c9055e..54ec792cd5 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -133,7 +133,7 @@ func (b *qmpTestCommandBuffer) startEventLoop(wg *sync.WaitGroup) { }() } -func (b *qmpTestCommandBuffer) AddCommmand(name string, args map[string]interface{}, +func (b *qmpTestCommandBuffer) AddCommand(name string, args map[string]interface{}, result string, data map[string]interface{}) { b.cmds = append(b.cmds, qmpTestCommand{name, args}) if data == nil { @@ -265,7 +265,7 @@ func TestQMPCapabilities(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("qmp_capabilities", nil, "return", nil) + buf.AddCommand("qmp_capabilities", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -288,7 +288,7 @@ func TestQMPStop(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("stop", nil, "return", nil) + buf.AddCommand("stop", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -311,7 +311,7 @@ func TestQMPCont(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("cont", nil, "return", nil) + buf.AddCommand("cont", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -333,7 +333,7 @@ func TestQMPQuit(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("quit", nil, "return", nil) + buf.AddCommand("quit", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -355,7 +355,7 @@ func TestQMPBlockdevAdd(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("blockdev-add", nil, "return", nil) + buf.AddCommand("blockdev-add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -378,7 +378,7 @@ func TestQMPDeviceAdd(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("device_add", nil, "return", nil) + buf.AddCommand("device_add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -403,7 +403,7 @@ func TestQMPXBlockdevDel(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("x-blockdev-del", nil, "return", nil) + buf.AddCommand("x-blockdev-del", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -437,7 +437,7 @@ func TestQMPDeviceDel(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("device_del", nil, "return", nil) + buf.AddCommand("device_del", nil, "return", nil) buf.AddEvent("DEVICE_DELETED", time.Millisecond*200, map[string]interface{}{ "path": path, @@ -502,7 +502,7 @@ func TestQMPDeviceDelTimeout(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("device_del", nil, "return", nil) + buf.AddCommand("device_del", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -529,8 +529,8 @@ func TestQMPCancel(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("qmp_capabilities", nil, "return", nil) - buf.AddCommmand("qmp_capabilities", nil, "return", nil) + buf.AddCommand("qmp_capabilities", nil, "return", nil) + buf.AddCommand("qmp_capabilities", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -564,7 +564,7 @@ func TestQMPSystemPowerdown(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("system_powerdown", nil, "return", nil) + buf.AddCommand("system_powerdown", nil, "return", nil) buf.AddEvent("SHUTDOWN", time.Millisecond*100, nil, map[string]interface{}{ @@ -599,8 +599,8 @@ func TestQMPEventedCommandCancel(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("system_powerdown", nil, "return", nil) - buf.AddCommmand("quit", nil, "return", nil) + buf.AddCommand("system_powerdown", nil, "return", nil) + buf.AddCommand("quit", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) @@ -642,8 +642,8 @@ func TestQMPEventedCommandCancelConcurrent(t *testing.T) { disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommmand("system_powerdown", nil, "error", nil) - buf.AddCommmand("qmp_capabilities", nil, "return", nil) + buf.AddCommand("system_powerdown", nil, "error", nil) + buf.AddCommand("qmp_capabilities", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) @@ -780,13 +780,13 @@ func TestQMPLostLoop(t *testing.T) { q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) close(buf.forceFail) - buf.AddCommmand("qmp_capabilities", nil, "return", nil) + buf.AddCommand("qmp_capabilities", nil, "return", nil) err := q.ExecuteQMPCapabilities(context.Background()) if err == nil { t.Error("Expected executeQMPCapabilities to fail") } <-disconnectedCh - buf.AddCommmand("qmp_capabilities", nil, "return", nil) + buf.AddCommand("qmp_capabilities", nil, "return", nil) err = q.ExecuteQMPCapabilities(context.Background()) if err == nil { t.Error("Expected executeQMPCapabilities to fail") From a84228ae99acee94cb5ce5319e4a3f94195e20d2 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 16 Feb 2017 09:45:14 +0000 Subject: [PATCH 054/264] qemu: Document how cancelling works. The code that handles the serialization and cancelling of QMP commands is a little complex and it took me some time to remember how it actually works and why it works in this particular way. For this reason I've added some comments which will hopefully make the next bug fix in this area a little less painful. Signed-off-by: Mark Ryan --- qmp.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/qmp.go b/qmp.go index c7ed0ec489..fc09543f81 100644 --- a/qmp.go +++ b/qmp.go @@ -346,6 +346,39 @@ func (q *QMP) parseVersion(version []byte) *QMPVersion { } } +// The qemu package allows multiple QMP commands to be submitted concurrently +// from different Go routines. Unfortunately, QMP doesn't really support parallel +// commands as there is no way reliable way to associate a command response +// with a request. For this reason we need to submit our commands to +// QMP serially. The qemu package performs this serialisation using a +// queue (cmdQueue owned by mainLoop). We use a queue rather than a simple +// mutex so we can support cancelling of commands (see below) and ordered +// execution of commands, i.e., if command B is issued before command C, +// it should be executed before command C even if both commands are initially +// blocked waiting for command A to finish. This would be hard to achieve with +// a simple mutex. +// +// Cancelling is a little tricky. Commands such as ExecuteQMPCapabilities +// can be cancelled by cancelling or timing out their contexts. When a +// command is cancelled the calling function, e.g., ExecuteQMPCapabilities, +// will return but we may not be able to remove the command's entry from +// the command queue or issue the next command. There are two scenarios +// here. +// +// 1. The command has been processed by QMP, i.e., we have received a +// return or an error, but is still blocking as it is waiting for +// an event. For example, the ExecuteDeviceDel blocks until a DEVICE_DELETED +// event is received. When such a command is cancelled we can remove it +// from the queue and start issuing the next command. When the DEVICE_DELETED +// event eventually arrives it will just be ignored. +// +// 2. The command has not been processed by QMP. In this case the command +// needs to remain on the cmdQueue until the response to this command is +// received from QMP. During this time no new commands can be issued. When the +// response is received, it is discarded (as no one is interested in the result +// any more), the entry is removed from the cmdQueue and we can proceed to +// execute the next command. + func (q *QMP) mainLoop() { cmdQueue := list.New().Init() fromVMCh := make(chan []byte) From d48b5b5f48e0e661991fd1369279dc30a9c10fab Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 9 Mar 2017 14:54:11 -0800 Subject: [PATCH 055/264] qemu: Add PCI option to the NetDevice The existing NetDevice relies on virtio-net driver, but there is a useful PCI variant which was not available: virtio-net-pci. This patch adds this new driver and adds two parameters specific to this: "bus" and "addr". Signed-off-by: Sebastien Boeuf --- qemu.go | 23 +++++++++++++++++++++++ qemu_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/qemu.go b/qemu.go index ea344b2eba..827c054608 100644 --- a/qemu.go +++ b/qemu.go @@ -29,6 +29,7 @@ import ( "fmt" "os" "os/exec" + "strconv" "strings" "context" @@ -62,6 +63,9 @@ const ( // VirtioNet is the virt-io networking device driver. VirtioNet = "virtio-net" + // VirtioNetPCI is the virt-io pci networking device driver. + VirtioNetPCI = "virtio-net-pci" + // VirtioSerial is the serial device driver. VirtioSerial = "virtio-serial-pci" @@ -341,6 +345,12 @@ type NetDevice struct { // IfName is the interface name, IFName string + // Bus is the bus path name of a PCI device. + Bus string + + // Addr is the address offset of a PCI device. + Addr string + // DownScript is the tap interface deconfiguration script. DownScript string @@ -384,6 +394,19 @@ func (netdev NetDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) + if netdev.Driver == VirtioNetPCI { + if netdev.Bus != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", netdev.Bus)) + } + + if netdev.Addr != "" { + addr, err := strconv.Atoi(netdev.Addr) + if err == nil && addr >= 0 { + deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) + } + } + } + netdevParams = append(netdevParams, string(netdev.Type)) netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID)) netdevParams = append(netdevParams, fmt.Sprintf(",ifname=%s", netdev.IFName)) diff --git a/qemu_test.go b/qemu_test.go index 193304084f..7f3ba0c015 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -143,6 +143,32 @@ func TestAppendDeviceNetwork(t *testing.T) { testAppend(netdev, deviceNetworkString, t) } +var deviceNetworkPCIString = "-device virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" + +func TestAppendDeviceNetworkPCI(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: VirtioNetPCI, + Type: TAP, + ID: "tap0", + IFName: "ceth0", + Bus: "/pci-bus/pcie.0", + Addr: "255", + Script: "no", + DownScript: "no", + FDs: []*os.File{foo, bar}, + VHost: true, + MACAddress: "01:02:de:ad:be:ef", + } + + testAppend(netdev, deviceNetworkPCIString, t) +} + var deviceSerialString = "-device virtio-serial-pci,id=serial0" func TestAppendDeviceSerial(t *testing.T) { From 58a835e6a63c35d0ab198099ba59343ddb7e5da5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 12 Jul 2017 16:36:10 +0100 Subject: [PATCH 056/264] misc: Remove unused variables identified by varcheck And add varcheck to the list of linters used on travis runs (with an increased deadline.) ciao-launcher/qemu_test.go:31:5:warning: unused variable or constant imageInfoTestGood (varcheck) ciao-launcher/qemu_test.go:44:5:warning: unused variable or constant imageInfoTestMissingBytes (varcheck) ciao-launcher/qemu_test.go:57:5:warning: unused variable or constant imageInfoTestMissingLine (varcheck) ciao-launcher/qemu_test.go:69:5:warning: unused variable or constant imageInfoTooBig (varcheck) ciao-launcher/qemu_test.go:82:5:warning: unused variable or constant imageInfoBadBytes (varcheck) configuration/configuration_test.go:35:7:warning: unused variable or constant glanceURL (varcheck) ciao-controller/controller_test.go:1918:5:warning: unused variable or constant testClients (varcheck) qemu/qmp_test.go:44:2:warning: unused variable or constant qmpSuccess (varcheck) qemu/qmp_test.go:45:2:warning: unused variable or constant qmpFailure (varcheck) Signed-off-by: Rob Bradford --- qmp_test.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/qmp_test.go b/qmp_test.go index 54ec792cd5..51058ec26b 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -32,17 +32,15 @@ import ( ) const ( - microStr = "50" - minorStr = "6" - majorStr = "2" - micro = 50 - minor = 6 - major = 2 - cap1 = "one" - cap2 = "two" - qmpHello = `{ "QMP": { "version": { "qemu": { "micro": ` + microStr + `, "minor": ` + minorStr + `, "major": ` + majorStr + ` }, "package": ""}, "capabilities": ["` + cap1 + `","` + cap2 + `"]}}` + "\n" - qmpSuccess = `{ "return": {}}` + "\n" - qmpFailure = `{ "error": {}}` + "\n" + microStr = "50" + minorStr = "6" + majorStr = "2" + micro = 50 + minor = 6 + major = 2 + cap1 = "one" + cap2 = "two" + qmpHello = `{ "QMP": { "version": { "qemu": { "micro": ` + microStr + `, "minor": ` + minorStr + `, "major": ` + majorStr + ` }, "package": ""}, "capabilities": ["` + cap1 + `","` + cap2 + `"]}}` + "\n" ) type qmpTestLogger struct{} From a1600dc15b3a0f1f14947e5525822eb8fcaa78f1 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 12 Jul 2017 16:48:24 +0100 Subject: [PATCH 057/264] misc: Remove unused fields identified by structcheck Add structcheck to the list of linters used on travis runs. ciao-cli/event.go:109:2:warning: unused struct field github.com/01org/ciao/ciao-cli.eventDeleteCommand.all (structcheck) ciao-cli/event.go:110:2:warning: unused struct field github.com/01org/ciao/ciao-cli.eventDeleteCommand.tenant (structcheck) ciao-cli/external_ips.go:636:2:warning: unused struct field github.com/01org/ciao/ciao-cli.poolAddCommand.ips (structcheck) ciao-cli/node.go:43:2:warning: unused struct field github.com/01org/ciao/ciao-cli.nodeListCommand.nodeID (structcheck) ciao-controller/client_wrapper_test.go:29:2:warning: unused struct field github.com/01org/ciao/ciao-controller.ssntpClientWrapper.ctl (structcheck) qemu/qmp.go:111:2:warning: unused struct field github.com/01org/ciao/qemu.qmpResult.data (structcheck) ssntp/ssntp_test.go:193:2:warning: unused struct field github.com/01org/ciao/ssntp_test.ssntpClient.evtTracedChannel (structcheck) ssntp/ssntp_test.go:192:2:warning: unused struct field github.com/01org/ciao/ssntp_test.ssntpClient.staTracedChannel (structcheck) ssntp/ssntp_test.go:194:2:warning: unused struct field github.com/01org/ciao/ssntp_test.ssntpClient.errTracedChannel (structcheck) ssntp/server.go:75:2:warning: unused struct field github.com/01org/ciao/ssntp.Server.roleVerify (structcheck) networking/ciao-cnci-agent/client.go:97:2:warning: unused struct field github.com/01org/ciao/networking/ciao-cnci-agent.agentClient.netCh (structcheck) testutil/agent.go:37:2:warning: unused struct field github.com/01org/ciao/testutil.SsntpTestClient.ticker (structcheck) Signed-off-by: Rob Bradford --- qmp.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qmp.go b/qmp.go index fc09543f81..b92a71432c 100644 --- a/qmp.go +++ b/qmp.go @@ -107,8 +107,7 @@ type QMPEvent struct { } type qmpResult struct { - err error - data map[string]interface{} + err error } type qmpCommand struct { From d4f77103beb2fffb5898cc36b3a79f011c5040f6 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 12 Jul 2017 17:10:11 +0100 Subject: [PATCH 058/264] misc: Remove some of the code flagged by unused linter Unfortunately the ununused linter is overzealous with some of the fields that it things are unused as gophercloud relies on their values. So go ahead with the most straightforward removals but do not enable unused on travis builds. ciao-image/datastore/datastore_test.go:28:5:warning: var metaDsTables is unused (U1000) (unused) ciao-controller/api/api_test.go:39:6:warning: func myHostname is unused (U1000) (unused) ciao-cli/identity.go:58:3:warning: field Description is unused (U1000) (unused) ciao-cli/identity.go:59:3:warning: field DomainID is unused (U1000) (unused) ciao-cli/identity.go:60:3:warning: field Enabled is unused (U1000) (unused) ciao-cli/identity.go:62:3:warning: field ParentID is unused (U1000) (unused) ciao-cli/identity.go:63:3:warning: field Links is unused (U1000) (unused) ciao-cli/identity.go:70:3:warning: field Self is unused (U1000) (unused) ciao-cli/identity.go:71:3:warning: field Previous is unused (U1000) (unused) ciao-cli/identity.go:72:3:warning: field Next is unused (U1000) (unused) ciao-cli/identity.go:207:3:warning: field Next is unused (U1000) (unused) ciao-cli/identity.go:208:3:warning: field Previous is unused (U1000) (unused) ciao-cli/identity.go:209:3:warning: field Self is unused (U1000) (unused) ciao-cli/identity.go:213:3:warning: field Description is unused (U1000) (unused) ciao-cli/identity.go:214:3:warning: field DomainID is unused (U1000) (unused) ciao-cli/identity.go:215:3:warning: field Enabled is unused (U1000) (unused) ciao-cli/identity.go:217:3:warning: field Links is unused (U1000) (unused) ciao-cli/identity.go:221:3:warning: field ParentID is unused (U1000) (unused) ciao-cli/main.go:105:6:warning: type action is unused (U1000) (unused) ciao-cli/volume.go:37:6:warning: type customVolumeExt is unused (U1000) (unused) ciao-cli/volume.go:39:2:warning: field customVolumeExt is unused (U1000) (unused) networking/ciao-cnci-agent/network.go:98:8:warning: const maxKey is unused (U1000) (unused) networking/libsnnet/tests/parallel/parallel_test.go:371:6:warning: func dockerNetList is unused (U1000) (unused) networking/libsnnet/tests/parallel/parallel_test.go:379:6:warning: func dockerNetInfo is unused (U1000) (unused) openstack/compute/api.go:308:2:warning: const limit is unused (U1000) (unused) openstack/compute/api.go:309:2:warning: const marker is unused (U1000) (unused) openstack/compute/api.go:312:6:warning: type pager is unused (U1000) (unused) openstack/compute/api.go:313:2:warning: func pager.filter is unused (U1000) (unused) openstack/compute/api.go:314:2:warning: func pager.nextPage is unused (U1000) (unused) openstack/compute/api_test.go:34:6:warning: func myHostname is unused (U1000) (unused) ciao-controller/api.go:72:2:warning: const statusFilter is unused (U1000) (unused) ciao-controller/api.go:75:6:warning: type pager is unused (U1000) (unused) ciao-controller/api.go:76:2:warning: func pager.filter is unused (U1000) (unused) ciao-controller/api.go:77:2:warning: func pager.nextPage is unused (U1000) (unused) ciao-controller/api.go:136:25:warning: func (*nodePager).filter is unused (U1000) (unused) ciao-controller/api.go:198:31:warning: func (*nodeServerPager).filter is unused (U1000) (unused) ciao-controller/controller_test.go:107:6:warning: func addTestTenantNoCNCI is unused (U1000) (unused) ciao-controller/controller_test.go:1104:6:warning: func startTestWorkload is unused (U1000) (unused) ciao-controller/controller_test.go:1123:6:warning: func testStartWorkloadLaunchCNCI is unused (U1000) (unused) ciao-controller/openstack_compute.go:552:5:warning: field Links is unused (U1000) (unused) qemu/qmp_test.go:493:3:warning: const seconds is unused (U1000) (unused) qemu/qmp_test.go:494:3:warning: const microsecondsEv1 is unused (U1000) (unused) qemu/qmp_test.go:495:3:warning: const device is unused (U1000) (unused) qemu/qmp_test.go:496:3:warning: const path is unused (U1000) (unused) templateutils/example_test.go:53:3:warning: field hidden is unused (U1000) (unused) Signed-off-by: Rob Bradford --- qmp_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/qmp_test.go b/qmp_test.go index 51058ec26b..ce94c8ed5c 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -489,13 +489,6 @@ func TestQMPDeviceDel(t *testing.T) { // The device_del command should timeout after 1 second and the QMP loop // should exit gracefully. func TestQMPDeviceDelTimeout(t *testing.T) { - const ( - seconds = 1352167040730 - microsecondsEv1 = 123456 - device = "device_" + testutil.VolumeUUID - path = "/dev/rbd0" - ) - var wg sync.WaitGroup connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) From 25a2dc8f6e5a88b058a37c0d72daa5d3301c4113 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Fri, 4 Aug 2017 16:36:17 -0700 Subject: [PATCH 059/264] qemu: Update blockdev-add qmp command to support newer qemu versions With qemu 2.9, the qmp block-dev command was updated from: { "execute": "blockdev-add", "arguments": { "options": { ... } } } to: { "execute": "blockdev-add", "arguments": { ... } } Also, instead of id, blockdev-add now requires a node-name for the root node(https://wiki.qemu.org/index.php/ChangeLog/2.9) Store the version information with QMPStart and use that to issue qmp command for adding block devices in the correct format. Signed-off-by: Archana Shinde --- qmp.go | 34 ++++++++++++++++++++++------------ qmp_test.go | 6 ++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/qmp.go b/qmp.go index b92a71432c..573d223371 100644 --- a/qmp.go +++ b/qmp.go @@ -127,6 +127,7 @@ type QMP struct { cfg QMPConfig connectedCh chan<- *QMPVersion disconnectedCh chan struct{} + version *QMPVersion } // QMPVersion contains the version number and the capabailities of a QEMU @@ -533,7 +534,6 @@ func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh connectedCh := make(chan *QMPVersion) - var version *QMPVersion q := startQMPLoop(conn, cfg, connectedCh, disconnectedCh) select { case <-ctx.Done(): @@ -542,13 +542,13 @@ func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh return nil, nil, fmt.Errorf("Canceled by caller") case <-disconnectedCh: return nil, nil, fmt.Errorf("Lost connection to VM") - case version = <-connectedCh: - if version == nil { + case q.version = <-connectedCh: + if q.version == nil { return nil, nil, fmt.Errorf("Failed to find QMP version information") } } - return q, version, nil + return q, q.version, nil } // Shutdown closes the domain socket used to monitor a QEMU instance and @@ -602,16 +602,26 @@ func (q *QMP) ExecuteQuit(ctx context.Context) error { // used to name the device. As this identifier will be passed directly to QMP, // it must obey QMP's naming rules, e,g., it must start with a letter. func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) error { - args := map[string]interface{}{ - "options": map[string]interface{}{ - "driver": "raw", - "file": map[string]interface{}{ - "driver": "file", - "filename": device, - }, - "id": blockdevID, + var args map[string]interface{} + + blockdevArgs := map[string]interface{}{ + "driver": "raw", + "file": map[string]interface{}{ + "driver": "file", + "filename": device, }, } + + if q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 9) { + blockdevArgs["node-name"] = blockdevID + args = blockdevArgs + } else { + blockdevArgs["id"] = blockdevID + args = map[string]interface{}{ + "options": blockdevArgs, + } + } + return q.executeCommand(ctx, "blockdev-add", args, nil) } diff --git a/qmp_test.go b/qmp_test.go index ce94c8ed5c..e449783cef 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -212,7 +212,7 @@ func (b *qmpTestCommandBuffer) Write(p []byte) (int, error) { return len(p), nil } -func checkVersion(t *testing.T, connectedCh <-chan *QMPVersion) { +func checkVersion(t *testing.T, connectedCh <-chan *QMPVersion) *QMPVersion { var version *QMPVersion select { case <-time.After(time.Second): @@ -233,6 +233,8 @@ func checkVersion(t *testing.T, connectedCh <-chan *QMPVersion) { t.Fatal("Invalid capabilities") } } + + return version } // Checks that a QMP Loop can be started and shutdown. @@ -356,7 +358,7 @@ func TestQMPBlockdevAdd(t *testing.T) { buf.AddCommand("blockdev-add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) - checkVersion(t, connectedCh) + q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevAdd(context.Background(), "/dev/rbd0", fmt.Sprintf("drive_%s", testutil.VolumeUUID)) if err != nil { From 8d617ff5b9f66c9b0fae48f40998ba9d44e45202 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 16 Aug 2017 10:07:00 -0700 Subject: [PATCH 060/264] qemu: Update virtio-net-pci command line In case of a network device, and specifically virtio-net-pci, we have to update to what is expected by qemu. In this case, the driver name should be prefixed with "driver=". Signed-off-by: Sebastien Boeuf --- qemu.go | 3 +++ qemu_test.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 827c054608..6a08f26c11 100644 --- a/qemu.go +++ b/qemu.go @@ -390,6 +390,9 @@ func (netdev NetDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string + if netdev.Driver == VirtioNetPCI { + deviceParams = append(deviceParams, "driver=") + } 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)) diff --git a/qemu_test.go b/qemu_test.go index 7f3ba0c015..fd144936d6 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -143,7 +143,7 @@ func TestAppendDeviceNetwork(t *testing.T) { testAppend(netdev, deviceNetworkString, t) } -var deviceNetworkPCIString = "-device virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" +var deviceNetworkPCIString = "-device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" func TestAppendDeviceNetworkPCI(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") From e74aeef1ad333d424921a81eb1363c37efee7048 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 16 Aug 2017 02:21:20 -0700 Subject: [PATCH 061/264] qemu: Add disable-modern option for virtio devices For some cases, we have to disable the fast MMIO support, by disabling virtio 1.0. The reason for this is that we want to be able to nest our qemu VM inside a VM run by an hypervisor with no support for fast MMIO. Signed-off-by: Sebastien Boeuf --- qemu.go | 30 +++++++++++++++++++++ qemu_test.go | 75 ++++++++++++++++++++++++++++------------------------ 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/qemu.go b/qemu.go index 6a08f26c11..710a573fde 100644 --- a/qemu.go +++ b/qemu.go @@ -202,6 +202,9 @@ type FSDevice struct { // SecurityModel is the security model for this filesystem device. SecurityModel SecurityModelType + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } // Valid returns true if the FSDevice structure is valid and complete. @@ -220,6 +223,9 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", fsdev.Driver)) + if fsdev.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) @@ -276,6 +282,9 @@ type CharDevice struct { ID string Path string Name string + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } // Valid returns true if the CharDevice structure is valid and complete. @@ -294,6 +303,9 @@ func (cdev CharDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", cdev.Driver)) + if cdev.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } if cdev.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", cdev.Bus)) } @@ -366,6 +378,9 @@ type NetDevice struct { // MACAddress is the networking device interface MAC address. MACAddress string + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } // Valid returns true if the NetDevice structure is valid and complete. @@ -394,6 +409,9 @@ func (netdev NetDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, "driver=") } deviceParams = append(deviceParams, fmt.Sprintf("%s", netdev.Driver)) + if netdev.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) @@ -454,6 +472,9 @@ type SerialDevice struct { // ID is the serial device identifier. ID string + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } // Valid returns true if the SerialDevice structure is valid and complete. @@ -471,6 +492,9 @@ func (dev SerialDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", dev.Driver)) + if dev.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) qemuParams = append(qemuParams, "-device") @@ -519,6 +543,9 @@ type BlockDevice struct { Format BlockDeviceFormat SCSI bool WCE bool + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } // Valid returns true if the BlockDevice structure is valid and complete. @@ -537,6 +564,9 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", blkdev.Driver)) + if blkdev.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) if blkdev.SCSI == false { deviceParams = append(deviceParams, ",scsi=off") diff --git a/qemu_test.go b/qemu_test.go index fd144936d6..ea3ddc68a6 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -104,7 +104,7 @@ func TestAppendDeviceNVDIMM(t *testing.T) { 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" +var deviceFSString = "-device virtio-9p-pci,disable-modern=true,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{ @@ -114,12 +114,13 @@ func TestAppendDeviceFS(t *testing.T) { Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", MountTag: "rootfs", SecurityModel: None, + DisableModern: true, } 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" +var deviceNetworkString = "-device virtio-net,disable-modern=true,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") @@ -129,21 +130,22 @@ func TestAppendDeviceNetwork(t *testing.T) { 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", + 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", + DisableModern: true, } testAppend(netdev, deviceNetworkString, t) } -var deviceNetworkPCIString = "-device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" +var deviceNetworkPCIString = "-device driver=virtio-net-pci,disable-modern=true,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" func TestAppendDeviceNetworkPCI(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") @@ -153,28 +155,30 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { defer os.Remove(bar.Name()) netdev := NetDevice{ - Driver: VirtioNetPCI, - Type: TAP, - ID: "tap0", - IFName: "ceth0", - Bus: "/pci-bus/pcie.0", - Addr: "255", - Script: "no", - DownScript: "no", - FDs: []*os.File{foo, bar}, - VHost: true, - MACAddress: "01:02:de:ad:be:ef", + Driver: VirtioNetPCI, + Type: TAP, + ID: "tap0", + IFName: "ceth0", + Bus: "/pci-bus/pcie.0", + Addr: "255", + Script: "no", + DownScript: "no", + FDs: []*os.File{foo, bar}, + VHost: true, + MACAddress: "01:02:de:ad:be:ef", + DisableModern: true, } testAppend(netdev, deviceNetworkPCIString, t) } -var deviceSerialString = "-device virtio-serial-pci,id=serial0" +var deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0" func TestAppendDeviceSerial(t *testing.T) { sdev := SerialDevice{ - Driver: VirtioSerial, - ID: "serial0", + Driver: VirtioSerial, + ID: "serial0", + DisableModern: true, } testAppend(sdev, deviceSerialString, t) @@ -195,18 +199,19 @@ func TestAppendDeviceSerialPort(t *testing.T) { testAppend(chardev, deviceSerialPortString, 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" +var deviceBlockString = "-device virtio-blk,disable-modern=true,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, + Driver: VirtioBlock, + ID: "hd0", + File: "/var/lib/ciao.img", + AIO: Threads, + Format: QCOW2, + Interface: NoInterface, + SCSI: false, + WCE: false, + DisableModern: true, } testAppend(blkdev, deviceBlockString, t) From 1fbe6c5d1d4aed85a003e8cdb2dd642fca9314a0 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Wed, 30 Aug 2017 10:56:07 -0700 Subject: [PATCH 062/264] qmp: Update block device deletion for newer versions of qemu blockdev-del command has been added in qemu 2.9 to replace x-blockdev-del command used earlier for deleting block devices. Update ExecuteXBlockdevDel() to use this updated qmp command. Rename ExecuteXBlockdevDel to ExecuteBlockdevDel as this no longer executes x-block-del command for qemu>=2.9. Signed-off-by: Archana Shinde --- qmp.go | 14 ++++++++++---- qmp_test.go | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/qmp.go b/qmp.go index 573d223371..5cc19af0e4 100644 --- a/qmp.go +++ b/qmp.go @@ -642,13 +642,19 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b return q.executeCommand(ctx, "device_add", args, nil) } -// ExecuteXBlockdevDel deletes a block device by sending a x-blockdev-del command. +// ExecuteBlockdevDel deletes a block device by sending a x-blockdev-del command +// for qemu versions < 2.9. It sends the updated blockdev-del command for qemu>=2.9. // blockdevID is the id of the block device to be deleted. Typically, this will // match the id passed to ExecuteBlockdevAdd. It must be a valid QMP id. -func (q *QMP) ExecuteXBlockdevDel(ctx context.Context, blockdevID string) error { - args := map[string]interface{}{ - "id": blockdevID, +func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error { + args := map[string]interface{}{} + + if q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 9) { + args["node-name"] = blockdevID + return q.executeCommand(ctx, "blockdev-del", args, nil) } + + args["id"] = blockdevID return q.executeCommand(ctx, "x-blockdev-del", args, nil) } diff --git a/qmp_test.go b/qmp_test.go index e449783cef..710c200992 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -406,8 +406,8 @@ func TestQMPXBlockdevDel(t *testing.T) { buf.AddCommand("x-blockdev-del", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) - checkVersion(t, connectedCh) - err := q.ExecuteXBlockdevDel(context.Background(), + q.version = checkVersion(t, connectedCh) + err := q.ExecuteBlockdevDel(context.Background(), fmt.Sprintf("drive_%s", testutil.VolumeUUID)) if err != nil { t.Fatalf("Unexpected error %v", err) From 4ecb9de5b33be61e0c77d0142a94171fde0db8ea Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Tue, 12 Sep 2017 15:45:16 -0700 Subject: [PATCH 063/264] qemu: Add support for memory pre-allocation Add support for pre-allocating all of the RAM. This increases the memory footprint of QEMU and should be used only when needed. Signed-off-by: Manohar Castelino --- qemu.go | 17 +++++++++++++++++ qemu_test.go | 2 ++ 2 files changed, 19 insertions(+) diff --git a/qemu.go b/qemu.go index 710a573fde..e6ace3f945 100644 --- a/qemu.go +++ b/qemu.go @@ -741,6 +741,9 @@ type Knobs struct { // Daemonize will turn the qemu process into a daemon Daemonize bool + + // MemPrealloc will allocate all the RAM upfront + MemPrealloc bool } // Config is the qemu configuration structure. @@ -988,6 +991,20 @@ func (config *Config) appendKnobs() { if config.Knobs.Daemonize == true { config.qemuParams = append(config.qemuParams, "-daemonize") } + + if config.Knobs.MemPrealloc == true { + if config.Memory.Size != "" { + dimmName := "dimm1" + objMemParam := "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + ",prealloc=on" + deviceMemParam := "pc-dimm,id=" + dimmName + ",memdev=" + dimmName + + config.qemuParams = append(config.qemuParams, "-object") + config.qemuParams = append(config.qemuParams, objMemParam) + + config.qemuParams = append(config.qemuParams, "-device") + config.qemuParams = append(config.qemuParams, deviceMemParam) + } + } } // LaunchQemu can be used to launch a new qemu instance. diff --git a/qemu_test.go b/qemu_test.go index ea3ddc68a6..ae9d31d9a4 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -231,6 +231,7 @@ func TestAppendKnobsAllTrue(t *testing.T) { NoDefaults: true, NoGraphic: true, Daemonize: true, + MemPrealloc: true, } testAppend(knobs, knobsString, t) @@ -241,6 +242,7 @@ func TestAppendKnobsAllFalse(t *testing.T) { NoUserConfig: false, NoDefaults: false, NoGraphic: false, + MemPrealloc: false, } testAppend(knobs, "", t) From ddee41d5538f0fa422f6b83ffb18058c30c6a8eb Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Wed, 13 Sep 2017 07:48:15 -0700 Subject: [PATCH 064/264] QEMU: Enable realtime options Enable realtime options in QEMU. Also add support to control memory locking. Turning realtime on with memory locking disabled allows memory to be swapped out, potentially increasing density of VMs. Signed-off-by: Manohar Castelino --- qemu.go | 16 ++++++++++++++++ qemu_test.go | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index e6ace3f945..21868ac232 100644 --- a/qemu.go +++ b/qemu.go @@ -744,6 +744,13 @@ type Knobs struct { // MemPrealloc will allocate all the RAM upfront MemPrealloc bool + + // Mlock will control locking of memory + // Only active when Realtime is set to true + Mlock bool + + // Realtime will enable realtime QEMU + Realtime bool } // Config is the qemu configuration structure. @@ -1005,6 +1012,15 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, deviceMemParam) } } + + if config.Knobs.Realtime == true { + config.qemuParams = append(config.qemuParams, "-realtime") + if config.Knobs.Mlock == true { + config.qemuParams = append(config.qemuParams, "mlock=on") + } else { + config.qemuParams = append(config.qemuParams, "mlock=off") + } + } } // LaunchQemu can be used to launch a new qemu instance. diff --git a/qemu_test.go b/qemu_test.go index ae9d31d9a4..317d2a0488 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -223,7 +223,7 @@ func TestAppendEmptyDevice(t *testing.T) { testAppend(device, "", t) } -var knobsString = "-no-user-config -nodefaults -nographic -daemonize" +var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on" func TestAppendKnobsAllTrue(t *testing.T) { knobs := Knobs{ @@ -232,6 +232,8 @@ func TestAppendKnobsAllTrue(t *testing.T) { NoGraphic: true, Daemonize: true, MemPrealloc: true, + Realtime: true, + Mlock: true, } testAppend(knobs, knobsString, t) @@ -243,6 +245,8 @@ func TestAppendKnobsAllFalse(t *testing.T) { NoDefaults: false, NoGraphic: false, MemPrealloc: false, + Realtime: false, + Mlock: false, } testAppend(knobs, "", t) From 0c206170c43deda44ba51156e74aeb0237c942b3 Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Fri, 15 Sep 2017 09:44:23 -0700 Subject: [PATCH 065/264] Knobs: Modify the behaviour of the Mlock knob. The Mlock knob is unfortunately tied to realtime. Allow Mlock knob to implicitly enable realtime to get the desired swapping behavior when swapping is desired. Note: Realtime as implemented today can only be used to enable swap, and as such does not really control realtime behaviour. The knob is redundant but retained here just to ensure that when more capabilities are added in future QEMU iterations we can take advantage of the same. Signed-off-by: Manohar Castelino --- qemu.go | 11 +++++++++++ qemu_test.go | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/qemu.go b/qemu.go index 21868ac232..3bd79284b7 100644 --- a/qemu.go +++ b/qemu.go @@ -1015,11 +1015,22 @@ func (config *Config) appendKnobs() { if config.Knobs.Realtime == true { config.qemuParams = append(config.qemuParams, "-realtime") + // This path is redundant as the default behaviour is locked memory + // Realtime today does not control any other feature even though + // other features may be added in the future + // https://lists.gnu.org/archive/html/qemu-devel/2012-12/msg03330.html if config.Knobs.Mlock == true { config.qemuParams = append(config.qemuParams, "mlock=on") } else { config.qemuParams = append(config.qemuParams, "mlock=off") } + } else { + // In order to turn mlock off we need the -realtime option as well + if config.Knobs.Mlock == false { + //Enable realtime anyway just to get the right swapping behaviour + config.qemuParams = append(config.qemuParams, "-realtime") + config.qemuParams = append(config.qemuParams, "mlock=off") + } } } diff --git a/qemu_test.go b/qemu_test.go index 317d2a0488..09ed2a92e2 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -223,9 +223,8 @@ func TestAppendEmptyDevice(t *testing.T) { testAppend(device, "", t) } -var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on" - func TestAppendKnobsAllTrue(t *testing.T) { + var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on" knobs := Knobs{ NoUserConfig: true, NoDefaults: true, @@ -240,6 +239,7 @@ func TestAppendKnobsAllTrue(t *testing.T) { } func TestAppendKnobsAllFalse(t *testing.T) { + var knobsString = "-realtime mlock=off" knobs := Knobs{ NoUserConfig: false, NoDefaults: false, @@ -249,7 +249,7 @@ func TestAppendKnobsAllFalse(t *testing.T) { Mlock: false, } - testAppend(knobs, "", t) + testAppend(knobs, knobsString, t) } var kernelString = "-kernel /opt/vmlinux.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" From a70ffd1980fb2e989c2deffc7e97feeaece17ba9 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 21 Sep 2017 17:30:19 +0100 Subject: [PATCH 066/264] Build: Fix the build after repo move. Ciao has recently moved from github.com/01org/ciao to github.com/ciao-project/ciao. This moves requires us to update our import paths to build successfully. Signed-off-by: Mark Ryan --- examples_test.go | 2 +- qemu_test.go | 2 +- qmp_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples_test.go b/examples_test.go index 45607a0444..bc06f5209b 100644 --- a/examples_test.go +++ b/examples_test.go @@ -21,7 +21,7 @@ import ( "context" - "github.com/01org/ciao/qemu" + "github.com/ciao-project/ciao/qemu" ) func Example() { diff --git a/qemu_test.go b/qemu_test.go index 09ed2a92e2..98bf151fda 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -22,7 +22,7 @@ import ( "strings" "testing" - "github.com/01org/ciao/testutil" + "github.com/ciao-project/ciao/testutil" ) func testAppend(structure interface{}, expected string, t *testing.T) { diff --git a/qmp_test.go b/qmp_test.go index 710c200992..5b63b17071 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -28,7 +28,7 @@ import ( "context" - "github.com/01org/ciao/testutil" + "github.com/ciao-project/ciao/testutil" ) const ( From 9bfa7927950b04cce84c14d1d982de702189ef97 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Fri, 22 Sep 2017 16:02:27 -0700 Subject: [PATCH 067/264] vfio: Add ability to pass VFIO devices to qemu VFIO is meant for exposing exposing direct device access to the virtual machine. Add ability to append VFIO devices to qemu command line. Signed-off-by: Archana Shinde --- qemu.go | 26 ++++++++++++++++++++++++++ qemu_test.go | 10 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/qemu.go b/qemu.go index 3bd79284b7..66bf21ebc3 100644 --- a/qemu.go +++ b/qemu.go @@ -591,6 +591,32 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { return qemuParams } +// VFIODevice represents a qemu vfio device meant for direct access by guest OS. +type VFIODevice struct { + // Bus-Device-Function of device + BDF string +} + +// Valid returns true if the VFIODevice structure is valid and complete. +func (vfioDev VFIODevice) Valid() bool { + if vfioDev.BDF == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this vfio device. +func (vfioDev VFIODevice) QemuParams(config *Config) []string { + var qemuParams []string + + deviceParam := fmt.Sprintf("vfio-pci,host=%s", vfioDev.BDF) + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, deviceParam) + + return qemuParams +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string diff --git a/qemu_test.go b/qemu_test.go index 98bf151fda..f3d28e3b51 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -217,6 +217,16 @@ func TestAppendDeviceBlock(t *testing.T) { testAppend(blkdev, deviceBlockString, t) } +var deviceVFIOString = "-device vfio-pci,host=02:10.0" + +func TestAppendDeviceVFIO(t *testing.T) { + vfioDevice := VFIODevice{ + BDF: "02:10.0", + } + + testAppend(vfioDevice, deviceVFIOString, t) +} + func TestAppendEmptyDevice(t *testing.T) { device := SerialDevice{} From 3da2ef9deaec061887e8bef5a8e302e8509c8a8c Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Mon, 25 Sep 2017 16:30:56 -0700 Subject: [PATCH 068/264] QEMU: Knobs: Huge Page Support: Add support for huge pages Add support to launch virtual machines where the RAM is allocated using huge pages. This is useful for running with a user mode networking stack, and for custom setups which require high performance and low latency. Signed-off-by: Manohar Castelino --- qemu.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 66bf21ebc3..364f291be9 100644 --- a/qemu.go +++ b/qemu.go @@ -768,6 +768,16 @@ type Knobs struct { // Daemonize will turn the qemu process into a daemon Daemonize bool + // Both HugePages and MemPrealloc require the Memory.Size of the VM + // to be set, as they need to reserve the memory upfront in order + // for the VM to boot without errors. + // + // HugePages always results in memory pre-allocation. + // However the setup is different from normal pre-allocation. + // Hence HugePages has precedence over MemPrealloc + // HugePages will pre-allocate all the RAM from huge pages + HugePages bool + // MemPrealloc will allocate all the RAM upfront MemPrealloc bool @@ -1025,7 +1035,19 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, "-daemonize") } - if config.Knobs.MemPrealloc == true { + if config.Knobs.HugePages == true { + if config.Memory.Size != "" { + dimmName := "dimm1" + objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=/dev/hugepages,share=on,prealloc=on" + numaMemParam := "node,memdev=" + dimmName + + config.qemuParams = append(config.qemuParams, "-object") + config.qemuParams = append(config.qemuParams, objMemParam) + + config.qemuParams = append(config.qemuParams, "-numa") + config.qemuParams = append(config.qemuParams, numaMemParam) + } + } else if config.Knobs.MemPrealloc == true { if config.Memory.Size != "" { dimmName := "dimm1" objMemParam := "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + ",prealloc=on" From 83126d3e05429f6f5c3ca436392e7e5071c30447 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 6 Oct 2017 14:28:12 -0500 Subject: [PATCH 069/264] bios: add support for custom bios Add Bios field into qemu Config struct, this allows to start VM with custom bios Partially fixes https://github.com/clearcontainers/runtime/issues/686 Signed-off-by: Julio Montes --- qemu.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qemu.go b/qemu.go index 364f291be9..861bea09f9 100644 --- a/qemu.go +++ b/qemu.go @@ -837,6 +837,9 @@ type Config struct { // Knobs is a set of qemu boolean settings. Knobs Knobs + // Bios is the -bios parameter + Bios string + // fds is a list of open file descriptors to be passed to the spawned qemu process fds []*os.File @@ -1082,6 +1085,13 @@ func (config *Config) appendKnobs() { } } +func (config *Config) appendBios() { + if config.Bios != "" { + config.qemuParams = append(config.qemuParams, "-bios") + config.qemuParams = append(config.qemuParams, config.Bios) + } +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -1105,6 +1115,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendVGA() config.appendKnobs() config.appendKernel() + config.appendBios() return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, config.fds, logger) } From cfa8a995dee073b7ae4c0c44a71b8cfb1a578bfb Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Wed, 4 Oct 2017 17:04:26 -0700 Subject: [PATCH 070/264] Networking: Add support for handling macvtap interfaces Add support for macvtap interfaces. This also brings in support for generic multiqueue support in virt containers. Signed-off-by: Manohar Castelino --- qemu.go | 178 ++++++++++++++++++++++++++++++++++++++++----------- qemu_test.go | 63 +++++++++++++++--- 2 files changed, 195 insertions(+), 46 deletions(-) diff --git a/qemu.go b/qemu.go index 861bea09f9..0697054495 100644 --- a/qemu.go +++ b/qemu.go @@ -332,17 +332,71 @@ func (cdev CharDevice) QemuParams(config *Config) []string { return qemuParams } -// NetDeviceType is a qemu networing device type. +// NetDeviceType is a qemu networking 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 is a macvtap networking device type. MACVTAP = "macvtap" + + // IPVTAP is a ipvtap virtual networking device type. + IPVTAP = "ipvtap" + + // VETHTAP is a veth-tap virtual networking device type. + VETHTAP = "vethtap" + + // VFIO is a direct assigned PCI device or PCI VF + VFIO = "VFIO" + + // VHOSTUSER is a vhost-user port (socket) + VHOSTUSER = "vhostuser" ) +// QemuNetdevParam converts to the QEMU -netdev parameter notation +func (n NetDeviceType) QemuNetdevParam() string { + switch n { + case TAP: + return "tap" + case MACVTAP: + return "tap" + case IPVTAP: + return "tap" + case VETHTAP: + return "tap" // -netdev type=tap -device virtio-net-pci + case VFIO: + return "" // -device vfio-pci (no netdev) + case VHOSTUSER: + return "vhost-user" // -netdev type=vhost-user (no device) + default: + return "" + + } +} + +// QemuDeviceParam converts to the QEMU -device parameter notation +func (n NetDeviceType) QemuDeviceParam() string { + switch n { + case TAP: + return "virtio-net-pci" + case MACVTAP: + return "virtio-net-pci" + case IPVTAP: + return "virtio-net-pci" + case VETHTAP: + return "virtio-net-pci" // -netdev type=tap -device virtio-net-pci + case VFIO: + return "vfio-pci" // -device vfio-pci (no netdev) + case VHOSTUSER: + return "" // -netdev type=vhost-user (no device) + default: + return "" + + } +} + // NetDevice represents a guest networking device type NetDevice struct { // Type is the netdev type (e.g. tap). @@ -399,68 +453,116 @@ func (netdev NetDevice) Valid() bool { } } -// QemuParams returns the qemu parameters built out of this network device. -func (netdev NetDevice) QemuParams(config *Config) []string { - var netdevParams []string +// QemuDeviceParams returns the -device parameters for this network device +func (netdev NetDevice) QemuDeviceParams(config *Config) []string { var deviceParams []string - var qemuParams []string - if netdev.Driver == VirtioNetPCI { - deviceParams = append(deviceParams, "driver=") - } - deviceParams = append(deviceParams, fmt.Sprintf("%s", netdev.Driver)) - if netdev.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if netdev.Type.QemuDeviceParam() == "" { + return nil } + + deviceParams = append(deviceParams, "driver=") + deviceParams = append(deviceParams, netdev.Type.QemuDeviceParam()) deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) - if netdev.Driver == VirtioNetPCI { - if netdev.Bus != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", netdev.Bus)) - } + if netdev.Bus != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", netdev.Bus)) + } - if netdev.Addr != "" { - addr, err := strconv.Atoi(netdev.Addr) - if err == nil && addr >= 0 { - deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) - } + if netdev.Addr != "" { + addr, err := strconv.Atoi(netdev.Addr) + if err == nil && addr >= 0 { + deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) } } - netdevParams = append(netdevParams, string(netdev.Type)) + if netdev.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } + + if len(netdev.FDs) > 0 { + // https://www.linux-kvm.org/page/Multiqueue + // -netdev tap,vhost=on,queues=N + // enable mq and specify msix vectors in qemu cmdline + // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) + // -device virtio-net-pci,mq=on,vectors=2N+2... + // enable mq in guest by 'ethtool -L eth0 combined $queue_num' + // Clearlinux automatically sets up the queues properly + // The agent implementation should do this to ensure that it is + // always set + vectors := len(netdev.FDs)*2 + 2 + + // Note: We are appending to the device params here + deviceParams = append(deviceParams, ",mq=on") + deviceParams = append(deviceParams, fmt.Sprintf(",vectors=%d", vectors)) + } + + return deviceParams +} + +// QemuNetdevParams returns the -netdev parameters for this network device +func (netdev NetDevice) QemuNetdevParams(config *Config) []string { + var netdevParams []string + + if netdev.Type.QemuNetdevParam() == "" { + return nil + } + + netdevParams = append(netdevParams, netdev.Type.QemuNetdevParam()) 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 netdev.VHost == true { + netdevParams = append(netdevParams, ",vhost=on") } 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, ":"))) + + } else { + 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)) + } + } + return netdevParams +} + +// 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 + + // Macvtap can only be connected via fds + if (netdev.Type == MACVTAP) && (len(netdev.FDs) == 0) { + return nil // implicit error } - if netdev.VHost == true { - netdevParams = append(netdevParams, ",vhost=on") + if netdev.Type.QemuNetdevParam() != "" { + netdevParams = netdev.QemuNetdevParams(config) + if netdevParams != nil { + qemuParams = append(qemuParams, "-netdev") + qemuParams = append(qemuParams, strings.Join(netdevParams, "")) + } } - qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) - - qemuParams = append(qemuParams, "-netdev") - qemuParams = append(qemuParams, strings.Join(netdevParams, "")) + if netdev.Type.QemuDeviceParam() != "" { + deviceParams = netdev.QemuDeviceParams(config) + if deviceParams != nil { + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + } + } return qemuParams } diff --git a/qemu_test.go b/qemu_test.go index f3d28e3b51..8490437791 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -120,14 +120,36 @@ func TestAppendDeviceFS(t *testing.T) { testAppend(fsdev, deviceFSString, t) } -var deviceNetworkString = "-device virtio-net,disable-modern=true,netdev=tap0,mac=01:02:de:ad:be:ef -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" +var deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true" func TestAppendDeviceNetwork(t *testing.T) { + netdev := NetDevice{ + Driver: VirtioNet, + Type: TAP, + ID: "tap0", + IFName: "ceth0", + Script: "no", + DownScript: "no", + VHost: true, + MACAddress: "01:02:de:ad:be:ef", + DisableModern: true, + } + + testAppend(netdev, deviceNetworkString, t) +} + +var deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6" + +func TestAppendDeviceNetworkMq(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()) + defer func() { + foo.Close() + bar.Close() + os.Remove(foo.Name()) + os.Remove(bar.Name()) + }() netdev := NetDevice{ Driver: VirtioNet, @@ -142,17 +164,42 @@ func TestAppendDeviceNetwork(t *testing.T) { DisableModern: true, } - testAppend(netdev, deviceNetworkString, t) + testAppend(netdev, deviceNetworkStringMq, t) } -var deviceNetworkPCIString = "-device driver=virtio-net-pci,disable-modern=true,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff -netdev tap,id=tap0,ifname=ceth0,downscript=no,script=no,fds=3:4,vhost=on" +var deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true" func TestAppendDeviceNetworkPCI(t *testing.T) { + + netdev := NetDevice{ + Driver: VirtioNetPCI, + Type: TAP, + ID: "tap0", + IFName: "ceth0", + Bus: "/pci-bus/pcie.0", + Addr: "255", + Script: "no", + DownScript: "no", + VHost: true, + MACAddress: "01:02:de:ad:be:ef", + DisableModern: true, + } + + testAppend(netdev, deviceNetworkPCIString, t) +} + +var deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6" + +func TestAppendDeviceNetworkPCIMq(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()) + defer func() { + foo.Close() + bar.Close() + os.Remove(foo.Name()) + os.Remove(bar.Name()) + }() netdev := NetDevice{ Driver: VirtioNetPCI, @@ -169,7 +216,7 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { DisableModern: true, } - testAppend(netdev, deviceNetworkPCIString, t) + testAppend(netdev, deviceNetworkPCIStringMq, t) } var deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0" From 83485dc9a4c3c15a2ddb2f989a1f5f70d2f00a5a Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 23 Oct 2017 08:36:40 -0500 Subject: [PATCH 071/264] qemu: Implement Bridge struct Bridge struct represent pci bridges(pci-bridge) or pcie bridges(pcie-pci-bridges), bridges can be used to hot plug devices in pc and q35 machines Signed-off-by: Julio Montes --- qemu.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/qemu.go b/qemu.go index 0697054495..0e70793fe4 100644 --- a/qemu.go +++ b/qemu.go @@ -719,6 +719,73 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { return qemuParams } +// BridgeType is the type of the bridge +type BridgeType uint + +const ( + // PCIBridge is a pci bridge + PCIBridge BridgeType = iota + + // PCIEBridge is a pcie bridge + PCIEBridge +) + +// BridgeDevice represents a qemu bridge device like pci-bridge, pxb, etc. +type BridgeDevice struct { + // Type of the bridge + Type BridgeType + + // Bus number where the bridge is plugged, typically pci.0 or pcie.0 + Bus string + + // ID is used to identify the bridge in qemu + ID string + + // Chassis number + Chassis int + + // SHPC is used to enable or disable the standard hot plug controller + SHPC bool +} + +// Valid returns true if the BridgeDevice structure is valid and complete. +func (bridgeDev BridgeDevice) Valid() bool { + if bridgeDev.Type != PCIBridge && bridgeDev.Type != PCIEBridge { + return false + } + + if bridgeDev.Bus == "" { + return false + } + + if bridgeDev.ID == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this bridge device. +func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { + var qemuParams []string + + shpc := "off" + if bridgeDev.SHPC { + shpc = "on" + } + + deviceName := "pci-bridge" + if bridgeDev.Type == PCIEBridge { + deviceName = "pcie-pci-bridge" + } + + deviceParam := fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", deviceName, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc) + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, deviceParam) + + return qemuParams +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string From 14316ce0b17a24091718536aae859b1ae3abaf77 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 24 Oct 2017 08:57:58 -0500 Subject: [PATCH 072/264] qemu/qmp: Implement function to hot plug PCI devices ExecutePCIDeviceAdd is a function that can be used to hot plug devices directly on pci(e).0 or pci(e) bridges. ExecutePCIDeviceAdd is PCI specific because unlike ExecuteDeviceAdd, it includes an extra parameter to specify the device address on its parent bus. Signed-off-by: Julio Montes --- qmp.go | 16 ++++++++++++++++ qmp_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/qmp.go b/qmp.go index 5cc19af0e4..ec29104441 100644 --- a/qmp.go +++ b/qmp.go @@ -675,3 +675,19 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { } return q.executeCommand(ctx, "device_del", args, filter) } + +// ExecutePCIDeviceAdd is the PCI version of ExecuteDeviceAdd. This function can be used +// to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the +// device address on its parent bus. bus is optional. +func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus string) error { + args := map[string]interface{}{ + "id": devID, + "driver": driver, + "drive": blockdevID, + "addr": addr, + } + if bus != "" { + args["bus"] = bus + } + return q.executeCommand(ctx, "device_add", args, nil) +} diff --git a/qmp_test.go b/qmp_test.go index 5b63b17071..e8072431c8 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -785,3 +785,28 @@ func TestQMPLostLoop(t *testing.T) { t.Error("Expected executeQMPCapabilities to fail") } } + +// Checks that PCI devices are correctly added using device_add. +// +// We start a QMPLoop, send the device_add command and stop the loop. +// +// The device_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPPCIDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + blockdevID := fmt.Sprintf("drive_%s", testutil.VolumeUUID) + devID := fmt.Sprintf("device_%s", testutil.VolumeUUID) + err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, + "virtio-blk-pci", "0x1", "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From 7e5614b8a711847c775cdc96b4992e6a45e66bfe Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Thu, 2 Nov 2017 12:58:32 -0700 Subject: [PATCH 073/264] Networking: Add vhost fd support Add vhost fd support. This is needed in the case of multi queue. Signed-off-by: Manohar Castelino --- qemu.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/qemu.go b/qemu.go index 0e70793fe4..0d2fecf3a6 100644 --- a/qemu.go +++ b/qemu.go @@ -425,7 +425,8 @@ type NetDevice struct { // FDs represents the list of already existing file descriptors to be used. // This is mostly useful for mq support. - FDs []*os.File + FDs []*os.File + VhostFDs []*os.File // VHost enables virtio device emulation from the host kernel instead of from qemu. VHost bool @@ -511,8 +512,17 @@ func (netdev NetDevice) QemuNetdevParams(config *Config) []string { netdevParams = append(netdevParams, netdev.Type.QemuNetdevParam()) netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID)) + if netdev.VHost == true { netdevParams = append(netdevParams, ",vhost=on") + if len(netdev.VhostFDs) > 0 { + var fdParams []string + qemuFDs := config.appendFDs(netdev.VhostFDs) + for _, fd := range qemuFDs { + fdParams = append(fdParams, fmt.Sprintf("%d", fd)) + } + netdevParams = append(netdevParams, fmt.Sprintf(",vhostfds=%s", strings.Join(fdParams, ":"))) + } } if len(netdev.FDs) > 0 { From b639da45edc4a01bae37a625c27cf863408a7eac Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Thu, 9 Nov 2017 17:53:26 -0800 Subject: [PATCH 074/264] qemu: Add function to hotplug vfio device Add ability to hotplug a pci device bound to vfio-pci driver. Signed-off-by: Archana Shinde --- qmp.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qmp.go b/qmp.go index ec29104441..2a001887f1 100644 --- a/qmp.go +++ b/qmp.go @@ -691,3 +691,16 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver } return q.executeCommand(ctx, "device_add", args, nil) } + +// ExecuteVFIODeviceAdd adds a VFIO device to a QEMU instance +// using the device_add command. devID is the id of the device to add. +// Must be valid QMP identifier. bdf is the PCI bus-device-function +// of the pci device. +func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf string) error { + args := map[string]interface{}{ + "id": devID, + "driver": "vfio-pci", + "host": bdf, + } + return q.executeCommand(ctx, "device_add", args, nil) +} From 11977072ea827f70b37c86090ff632cad78bac85 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 30 Oct 2017 09:55:07 +0000 Subject: [PATCH 075/264] qemu: Add a SysProcAttr parameter to LaunchCustomQemu This change adds an additional parameter to LaunchCustomQemu that allows users more control over the newly created process. They can for instance specify the user under which the new qemu process should run and which capabilities should be retained in the child qemu process. Signed-off-by: Mark Ryan --- examples_test.go | 2 +- qemu.go | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples_test.go b/examples_test.go index bc06f5209b..35373cc1f4 100644 --- a/examples_test.go +++ b/examples_test.go @@ -41,7 +41,7 @@ func Example() { // 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.LaunchCustomQemu(context.Background(), "", params, nil, nil) + _, err := qemu.LaunchCustomQemu(context.Background(), "", params, nil, nil, nil) if err != nil { panic(err) } diff --git a/qemu.go b/qemu.go index 0d2fecf3a6..a342a43278 100644 --- a/qemu.go +++ b/qemu.go @@ -31,6 +31,7 @@ import ( "os/exec" "strconv" "strings" + "syscall" "context" ) @@ -1296,7 +1297,8 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendKernel() config.appendBios() - return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, config.fds, logger) + return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, + config.fds, nil, logger) } // LaunchCustomQemu can be used to launch a new qemu instance. @@ -1307,16 +1309,19 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { // signature of this function will not need to change when launch cancellation // is implemented. // -// config.qemuParams is a slice of options to pass to qemu-system-x86_64 and fds is a +// 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. +// process. The attrs parameter can be used to control aspects of the +// newly created qemu process, such as the user and group under which it +// runs. It may be nil. // // 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) { +func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []*os.File, + attr *syscall.SysProcAttr, logger QMPLog) (string, error) { if logger == nil { logger = qmpNullLogger{} } @@ -1333,6 +1338,8 @@ func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []* cmd.ExtraFiles = fds } + cmd.SysProcAttr = attr + var stderr bytes.Buffer cmd.Stderr = &stderr logger.Infof("launching qemu with: %v", params) From bc030d13d1ef3501613c4d02ab7688de1cea76a5 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 30 Oct 2017 10:06:20 +0000 Subject: [PATCH 076/264] qemu: Add a SysProcAttr parameter to CreateCloudInitISO This change adds an additional parameter to CreateCloudInitISO that allows users more control over the newly created xorriso process. They can for instance specify the user under which the new qemu process should run and which capabilities should be retained in the child xorriso process. Signed-off-by: Mark Ryan --- image.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/image.go b/image.go index 9b456872da..4f064c681b 100644 --- a/image.go +++ b/image.go @@ -23,6 +23,7 @@ import ( "os" "os/exec" "path" + "syscall" ) // CreateCloudInitISO creates a cloud-init ConfigDrive ISO image. This is @@ -34,8 +35,10 @@ import ( // isoPath contains the desired path of the ISO image to be created. The // userdata and metadata parameters are byte slices that contain the // ConfigDrive userdata and metadata that will be stored with the ISO image. +// The attrs parameter can be used to control aspects of the newly created +// qemu process, such as the user and group under which it runs. It may be nil. func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, - userData, metaData []byte) error { + userData, metaData []byte, attr *syscall.SysProcAttr) error { configDrivePath := path.Join(scratchDir, "clr-cloud-init") dataDirPath := path.Join(configDrivePath, "openstack", "latest") metaDataPath := path.Join(dataDirPath, "meta_data.json") @@ -63,6 +66,7 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, cmd := exec.CommandContext(ctx, "xorriso", "-as", "mkisofs", "-R", "-V", "config-2", "-o", isoPath, configDrivePath) + cmd.SysProcAttr = attr err = cmd.Run() if err != nil { return fmt.Errorf("Unable to create cloudinit iso image %v", err) From e39da6ca473c8df2426da411f4ef147ef2cbaa7b Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 29 Nov 2017 10:15:33 -0600 Subject: [PATCH 077/264] qmp: Add support for hot plugging VFIO devices on PCI(E) bridges This patch adds a new function to hot plug VFIO devices on PCI(E) bridges, This change allows to hot plug N VFIO devices in Qemu PC and Q35 Signed-off-by: Julio Montes --- qmp.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/qmp.go b/qmp.go index 2a001887f1..72ab89b6bd 100644 --- a/qmp.go +++ b/qmp.go @@ -704,3 +704,21 @@ func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf string) error } return q.executeCommand(ctx, "device_add", args, nil) } + +// ExecutePCIVFIODeviceAdd adds a VFIO device to a QEMU instance using the device_add command. +// This function can be used to hot plug VFIO devices on PCI(E) bridges, unlike +// ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. +// bus is optional. devID is the id of the device to add.Must be valid QMP identifier. bdf is the +// PCI bus-device-function of the pci device. +func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus string) error { + args := map[string]interface{}{ + "id": devID, + "driver": "vfio-pci", + "host": bdf, + "addr": addr, + } + if bus != "" { + args["bus"] = bus + } + return q.executeCommand(ctx, "device_add", args, nil) +} From 24b14059b362b57940b94c1de4943ae73a1589b9 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Thu, 7 Dec 2017 10:59:20 -0600 Subject: [PATCH 078/264] qemu: Add functions to process QMP response Some QMP commands like ```query-hotpluggable-cpus``` returns a response that needs to be processed and returned to the client as a struct. This patch adds the function ```executeCommandWithResponse``` that returns the response of a QMP command. Signed-off-by: Julio Montes --- qmp.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/qmp.go b/qmp.go index 72ab89b6bd..f6774c4e10 100644 --- a/qmp.go +++ b/qmp.go @@ -107,7 +107,8 @@ type QMPEvent struct { } type qmpResult struct { - err error + response interface{} + err error } type qmpCommand struct { @@ -203,14 +204,14 @@ func (q *QMP) processQMPEvent(cmdQueue *list.List, name interface{}, data interf } } -func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeeded bool) { +func (q *QMP) finaliseCommandWithResponse(cmdEl *list.Element, cmdQueue *list.List, succeeded bool, response interface{}) { cmd := cmdEl.Value.(*qmpCommand) cmdQueue.Remove(cmdEl) select { case <-cmd.ctx.Done(): default: if succeeded { - cmd.res <- qmpResult{} + cmd.res <- qmpResult{response: response} } else { cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed")} } @@ -220,6 +221,10 @@ func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeede } } +func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeeded bool) { + q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, nil) +} + func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { var vmData map[string]interface{} err := json.Unmarshal(line, &vmData) @@ -233,7 +238,7 @@ func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { return } - _, succeeded := vmData["return"] + response, succeeded := vmData["return"] _, failed := vmData["error"] if !succeeded && !failed { @@ -248,7 +253,7 @@ func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { } cmd := cmdEl.Value.(*qmpCommand) if failed || cmd.filter == nil { - q.finaliseCommand(cmdEl, cmdQueue, succeeded) + q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, response) } else { cmd.resultReceived = true } @@ -463,9 +468,10 @@ func startQMPLoop(conn io.ReadWriteCloser, cfg QMPConfig, return q } -func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]interface{}, - filter *qmpEventFilter) error { +func (q *QMP) executeCommandWithResponse(ctx context.Context, name string, args map[string]interface{}, + filter *qmpEventFilter) (interface{}, error) { var err error + var response interface{} resCh := make(chan qmpResult) select { case <-q.disconnectedCh: @@ -480,16 +486,24 @@ func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]i } if err != nil { - return err + return response, err } select { case res := <-resCh: err = res.err + response = res.response case <-ctx.Done(): err = ctx.Err() } + return response, err +} + +func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]interface{}, + filter *qmpEventFilter) error { + + _, err := q.executeCommandWithResponse(ctx, name, args, filter) return err } From 8c428ed722debf4f24ae7d19d4570123e8ff541b Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 6 Dec 2017 12:50:59 -0600 Subject: [PATCH 079/264] qemu: Add function to hotplug CPUs ExecuteCPUDeviceAdd hot-adds a CPU to a running VM Signed-off-by: Julio Montes --- qmp.go | 15 +++++++++++++++ qmp_test.go | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/qmp.go b/qmp.go index f6774c4e10..0e08e4d261 100644 --- a/qmp.go +++ b/qmp.go @@ -736,3 +736,18 @@ func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus } return q.executeCommand(ctx, "device_add", args, nil) } + +// ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command. +// driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within +// node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the +// thread number within core the CPU belongs to. +func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, coreID, threadID string) error { + args := map[string]interface{}{ + "driver": driver, + "id": cpuID, + "socket-id": socketID, + "core-id": coreID, + "thread-id": threadID, + } + return q.executeCommand(ctx, "device_add", args, nil) +} diff --git a/qmp_test.go b/qmp_test.go index e8072431c8..7e83f4f939 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -810,3 +810,25 @@ func TestQMPPCIDeviceAdd(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks that CPU are correctly added using device_add +func TestQMPCPUDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + driver := "qemu64-x86_64-cpu" + cpuID := "cpu-0" + socketID := "0" + coreID := "1" + threadID := "0" + err := q.ExecuteCPUDeviceAdd(context.Background(), driver, cpuID, socketID, coreID, threadID) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From b8ddd2440011d270746baa6ff2026a30d9f842ef Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Thu, 7 Dec 2017 13:27:52 -0600 Subject: [PATCH 080/264] qemu: Add function to list hotpluggable CPUs ExecuteQueryHotpluggableCPUs returns the list of hotpluggable CPUs Signed-off-by: Julio Montes --- qmp.go | 38 ++++++++++++++++++++++++++++++++++++++ qmp_test.go | 42 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/qmp.go b/qmp.go index 0e08e4d261..3727b8478e 100644 --- a/qmp.go +++ b/qmp.go @@ -140,6 +140,22 @@ type QMPVersion struct { Capabilities []string } +// CPUProperties contains the properties to be used for hotplugging a CPU instance +type CPUProperties struct { + Node int `json:"node-id"` + Socket int `json:"socket-id"` + Core int `json:"core-id"` + Thread int `json:"thread-id"` +} + +// HotpluggableCPU represents a hotpluggable CPU +type HotpluggableCPU struct { + Type string `json:"type"` + VcpusCount int `json:"vcpus-count"` + Properties CPUProperties `json:"props"` + QOMPath string `json:"qom-path"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) for scanner.Scan() { @@ -751,3 +767,25 @@ func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, } return q.executeCommand(ctx, "device_add", args, nil) } + +// ExecuteQueryHotpluggableCPUs returns a slice with the list of hotpluggable CPUs +func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableCPU, error) { + response, err := q.executeCommandWithResponse(ctx, "query-hotpluggable-cpus", nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("Unable to extract CPU information: %v", err) + } + + var cpus []HotpluggableCPU + // convert json to []HotpluggableCPU + if err = json.Unmarshal(data, &cpus); err != nil { + return nil, fmt.Errorf("Unable to convert json to hotpluggable CPU: %v", err) + } + + return cpus, nil +} diff --git a/qmp_test.go b/qmp_test.go index 7e83f4f939..21b5e968de 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "log" + "reflect" "sync" "testing" "time" @@ -75,7 +76,7 @@ type qmpTestEvent struct { type qmpTestResult struct { result string - data map[string]interface{} + data interface{} } type qmpTestCommandBuffer struct { @@ -132,11 +133,8 @@ func (b *qmpTestCommandBuffer) startEventLoop(wg *sync.WaitGroup) { } func (b *qmpTestCommandBuffer) AddCommand(name string, args map[string]interface{}, - result string, data map[string]interface{}) { + result string, data interface{}) { b.cmds = append(b.cmds, qmpTestCommand{name, args}) - if data == nil { - data = make(map[string]interface{}) - } b.results = append(b.results, qmpTestResult{result, data}) } @@ -832,3 +830,37 @@ func TestQMPCPUDeviceAdd(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks that hotpluggable CPUs are listed correctly +func TestQMPExecuteQueryHotpluggableCPUs(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + hotCPU := HotpluggableCPU{ + Type: "host-x86", + VcpusCount: 5, + Properties: CPUProperties{ + Node: 1, + Socket: 3, + Core: 2, + Thread: 4, + }, + QOMPath: "/abc/123/rgb", + } + buf.AddCommand("query-hotpluggable-cpus", nil, "return", []interface{}{hotCPU}) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + hotCPUs, err := q.ExecuteQueryHotpluggableCPUs(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(hotCPUs) != 1 { + t.Fatalf("Expected hot CPUs length equals to 1\n") + } + if reflect.DeepEqual(hotCPUs[0], hotCPU) == false { + t.Fatalf("Expected %v equals to %v\n", hotCPUs[0], hotCPU) + } + q.Shutdown() + <-disconnectedCh +} From 48feb29fe5b1d81fdbec9558a592ac9910593ace Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Wed, 6 Dec 2017 11:13:32 -0800 Subject: [PATCH 081/264] qemu: introduce vhost-user handling Add ability to add a vhostuser device to the QEMU commandline. We expect two different types of devices to be connected through a vhostuser socket: SCSI and network. Signed-off-by: Eric Ernst --- qemu.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu_test.go | 23 +++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/qemu.go b/qemu.go index a342a43278..b65983cce9 100644 --- a/qemu.go +++ b/qemu.go @@ -704,6 +704,78 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { return qemuParams } +// VhostUserDeviceType is a qemu networking device type. +type VhostUserDeviceType string + +const ( + //VhostUserSCSI represents a SCSI vhostuser device type + VhostUserSCSI = "vhost-user-scsi-pci" + //VhostUserNet represents a net vhostuser device type + VhostUserNet = "virtio-net-pci" +) + +// VhostUserDevice represents a qemu vhost-user network device meant to be passed +// in to the guest +type VhostUserDevice struct { + SocketPath string //path to vhostuser socket on host + CharDevID string + TypeDevID string //id (SCSI) or netdev (net) device parameter + MacAddress string //only valid if device type is VhostUserNet + VhostUserType VhostUserDeviceType +} + +// Valid returns true if there is a valid socket path defined for VhostUserDevice +func (vhostuserDev VhostUserDevice) Valid() bool { + if vhostuserDev.SocketPath == "" || vhostuserDev.CharDevID == "" || + vhostuserDev.TypeDevID == "" || + (vhostuserDev.VhostUserType == VhostUserNet && vhostuserDev.MacAddress == "") { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this vhostuser device. +func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { + var qemuParams []string + var charParams []string + var netParams []string + var devParams []string + + charParams = append(charParams, "socket") + charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) + charParams = append(charParams, fmt.Sprintf("path=%s", vhostuserDev.SocketPath)) + + // if network based vhost device: + if vhostuserDev.VhostUserType == VhostUserNet { + netParams = append(netParams, "type=vhost-user") + netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) + netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + netParams = append(netParams, "vhostforce") + + devParams = append(devParams, VhostUserNet) + devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) + devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.MacAddress)) + } else { + devParams = append(devParams, VhostUserSCSI) + devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) + devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + } + + qemuParams = append(qemuParams, "-chardev") + qemuParams = append(qemuParams, strings.Join(charParams, ",")) + + // if network based vhost device: + if vhostuserDev.VhostUserType == VhostUserNet { + qemuParams = append(qemuParams, "-netdev") + qemuParams = append(qemuParams, strings.Join(netParams, ",")) + } + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(devParams, ",")) + + return qemuParams +} + // VFIODevice represents a qemu vfio device meant for direct access by guest OS. type VFIODevice struct { // Bus-Device-Function of device diff --git a/qemu_test.go b/qemu_test.go index 8490437791..6b0195e2bb 100644 --- a/qemu_test.go +++ b/qemu_test.go @@ -264,6 +264,29 @@ func TestAppendDeviceBlock(t *testing.T) { testAppend(blkdev, deviceBlockString, t) } +var deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55" +var deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1" + +func TestAppendDeviceVhostUser(t *testing.T) { + vhostuserSCSIDevice := VhostUserDevice{ + SocketPath: "/tmp/nonexistentsocket.socket", + CharDevID: "char1", + TypeDevID: "scsi1", + MacAddress: "", + VhostUserType: VhostUserSCSI, + } + testAppend(vhostuserSCSIDevice, deviceVhostUserSCSIString, t) + + vhostuserNetDevice := VhostUserDevice{ + SocketPath: "/tmp/nonexistentsocket.socket", + CharDevID: "char1", + TypeDevID: "net1", + MacAddress: "00:11:22:33:44:55", + VhostUserType: VhostUserNet, + } + testAppend(vhostuserNetDevice, deviceVhostUserNetString, t) +} + var deviceVFIOString = "-device vfio-pci,host=02:10.0" func TestAppendDeviceVFIO(t *testing.T) { From 27709fce439b2f5abeda97230d713562f05389c5 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 23 Nov 2017 11:15:11 +0000 Subject: [PATCH 082/264] Move files to the qemu folder This commit moves all of the source files to the qemu folder. Signed-off-by: Mark Ryan --- examples_test.go => qemu/examples_test.go | 0 image.go => qemu/image.go | 0 qemu.go => qemu/qemu.go | 0 qemu_test.go => qemu/qemu_test.go | 0 qmp.go => qemu/qmp.go | 0 qmp_test.go => qemu/qmp_test.go | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename examples_test.go => qemu/examples_test.go (100%) rename image.go => qemu/image.go (100%) rename qemu.go => qemu/qemu.go (100%) rename qemu_test.go => qemu/qemu_test.go (100%) rename qmp.go => qemu/qmp.go (100%) rename qmp_test.go => qemu/qmp_test.go (100%) diff --git a/examples_test.go b/qemu/examples_test.go similarity index 100% rename from examples_test.go rename to qemu/examples_test.go diff --git a/image.go b/qemu/image.go similarity index 100% rename from image.go rename to qemu/image.go diff --git a/qemu.go b/qemu/qemu.go similarity index 100% rename from qemu.go rename to qemu/qemu.go diff --git a/qemu_test.go b/qemu/qemu_test.go similarity index 100% rename from qemu_test.go rename to qemu/qemu_test.go diff --git a/qmp.go b/qemu/qmp.go similarity index 100% rename from qmp.go rename to qemu/qmp.go diff --git a/qmp_test.go b/qemu/qmp_test.go similarity index 100% rename from qmp_test.go rename to qemu/qmp_test.go From 57aafb56383e10b21fcb264c232bc3341e90eb3c Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 23 Nov 2017 13:18:47 +0000 Subject: [PATCH 083/264] Remove all references to and dependencies on ciao This commit removes all the references to the ciao project. It also removes some of the dependencies that the unit tests were pulling in. Signed-off-by: Mark Ryan --- qemu/examples_test.go | 2 +- qemu/qemu_test.go | 21 +++++++++++---------- qemu/qmp_test.go | 24 +++++++++++------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/qemu/examples_test.go b/qemu/examples_test.go index 35373cc1f4..c3930a1e18 100644 --- a/qemu/examples_test.go +++ b/qemu/examples_test.go @@ -21,7 +21,7 @@ import ( "context" - "github.com/ciao-project/ciao/qemu" + "github.com/intel/govmm/qemu" ) func Example() { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 6b0195e2bb..8a97fd1d54 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -21,10 +21,11 @@ import ( "os" "strings" "testing" - - "github.com/ciao-project/ciao/testutil" ) +const agentUUID = "4cb19522-1e18-439a-883a-f9b2a3a95f5e" +const volumeUUID = "67d86208-b46c-4465-9018-e14187d4010" + func testAppend(structure interface{}, expected string, t *testing.T) { var config Config @@ -141,8 +142,8 @@ func TestAppendDeviceNetwork(t *testing.T) { var deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6" func TestAppendDeviceNetworkMq(t *testing.T) { - foo, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") - bar, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") + foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") + bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") defer func() { foo.Close() @@ -191,8 +192,8 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { var deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6" func TestAppendDeviceNetworkPCIMq(t *testing.T) { - foo, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") - bar, _ := ioutil.TempFile(os.TempDir(), "qemu-ciao-test") + foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") + bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") defer func() { foo.Close() @@ -246,13 +247,13 @@ func TestAppendDeviceSerialPort(t *testing.T) { testAppend(chardev, deviceSerialPortString, t) } -var deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off -drive id=hd0,file=/var/lib/ciao.img,aio=threads,format=qcow2,if=none" +var deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" func TestAppendDeviceBlock(t *testing.T) { blkdev := BlockDevice{ Driver: VirtioBlock, ID: "hd0", - File: "/var/lib/ciao.img", + File: "/var/lib/vm.img", AIO: Threads, Format: QCOW2, Interface: NoInterface, @@ -413,13 +414,13 @@ func TestAppendQMPSocketServer(t *testing.T) { testAppend(qmp, qmpSocketServerString, t) } -var qemuString = "-name cc-qemu -cpu host -uuid " + testutil.AgentUUID +var qemuString = "-name cc-qemu -cpu host -uuid " + agentUUID func TestAppendStrings(t *testing.T) { config := Config{ Path: "qemu", Name: "cc-qemu", - UUID: testutil.AgentUUID, + UUID: agentUUID, CPUModel: "host", } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 21b5e968de..9b87fbcc5d 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -28,8 +28,6 @@ import ( "time" "context" - - "github.com/ciao-project/ciao/testutil" ) const ( @@ -358,7 +356,7 @@ func TestQMPBlockdevAdd(t *testing.T) { q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevAdd(context.Background(), "/dev/rbd0", - fmt.Sprintf("drive_%s", testutil.VolumeUUID)) + fmt.Sprintf("drive_%s", volumeUUID)) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -380,8 +378,8 @@ func TestQMPDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - blockdevID := fmt.Sprintf("drive_%s", testutil.VolumeUUID) - devID := fmt.Sprintf("device_%s", testutil.VolumeUUID) + blockdevID := fmt.Sprintf("drive_%s", volumeUUID) + devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteDeviceAdd(context.Background(), blockdevID, devID, "virtio-blk-pci", "") if err != nil { @@ -406,7 +404,7 @@ func TestQMPXBlockdevDel(t *testing.T) { q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevDel(context.Background(), - fmt.Sprintf("drive_%s", testutil.VolumeUUID)) + fmt.Sprintf("drive_%s", volumeUUID)) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -427,7 +425,7 @@ func TestQMPDeviceDel(t *testing.T) { seconds = 1352167040730 microsecondsEv1 = 123456 microsecondsEv2 = 123556 - device = "device_" + testutil.VolumeUUID + device = "device_" + volumeUUID path = "/dev/rbd0" ) @@ -470,7 +468,7 @@ func TestQMPDeviceDel(t *testing.T) { checkVersion(t, connectedCh) buf.startEventLoop(&wg) err := q.ExecuteDeviceDel(context.Background(), - fmt.Sprintf("device_%s", testutil.VolumeUUID)) + fmt.Sprintf("device_%s", volumeUUID)) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -499,7 +497,7 @@ func TestQMPDeviceDelTimeout(t *testing.T) { checkVersion(t, connectedCh) ctx, cancel := context.WithTimeout(context.Background(), time.Second) err := q.ExecuteDeviceDel(ctx, - fmt.Sprintf("device_%s", testutil.VolumeUUID)) + fmt.Sprintf("device_%s", volumeUUID)) cancel() if err != context.DeadlineExceeded { t.Fatalf("Timeout expected found %v", err) @@ -683,7 +681,7 @@ func TestQMPEvents(t *testing.T) { seconds = 1352167040730 microsecondsEv1 = 123456 microsecondsEv2 = 123556 - device = "device_" + testutil.VolumeUUID + device = "device_" + volumeUUID path = "/dev/rbd0" ) var wg sync.WaitGroup @@ -721,7 +719,7 @@ func TestQMPEvents(t *testing.T) { deviceName := ev.Data["device"].(string) if deviceName != device { t.Errorf("Unexpected device field. Expected %s, found %s", - "device_"+testutil.VolumeUUID, device) + "device_"+volumeUUID, device) } pathName := ev.Data["path"].(string) if pathName != path { @@ -798,8 +796,8 @@ func TestQMPPCIDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - blockdevID := fmt.Sprintf("drive_%s", testutil.VolumeUUID) - devID := fmt.Sprintf("device_%s", testutil.VolumeUUID) + blockdevID := fmt.Sprintf("drive_%s", volumeUUID) + devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, "virtio-blk-pci", "0x1", "") if err != nil { From a8aaf534b660cad4dd7ecb3c651bc77fccec6aff Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 23 Nov 2017 15:17:49 +0000 Subject: [PATCH 084/264] Add project documentation This commit adds three documents: - CONTRIBUTING.md ( a files describing how to contribute to the project )` - COPYING ( the Apache 2.0 license ) - README.md ( a brief description of the project) Signed-off-by: Mark Ryan --- CONTRIBUTING.md | 62 +++++++++++++++ COPYING | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 15 ++++ 3 files changed, 279 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 COPYING create mode 100644 README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..9e94d1d028 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing to Virtual Machine Manager for Go + +Virtual Machine Manager for Go is an open source project licensed under the [Apache v2 License] (https://opensource.org/licenses/Apache-2.0) + +## Coding Style + +Virtual Machine Manager for Go follows the standard formatting recommendations and language idioms set out +in [Effective Go](https://golang.org/doc/effective_go.html) and in the +[Go Code Review Comments wiki](https://github.com/golang/go/wiki/CodeReviewComments). + +## Certificate of Origin + +In order to get a clear contribution chain of trust we use the [signed-off-by language] (https://01.org/community/signed-process) +used by the Linux kernel project. + +## Patch format + +Beside the signed-off-by footer, we expect each patch to comply with the following format: + +``` +Change summary + +More detailed explanation of your changes: Why and how. +Wrap it to 72 characters. +See [here] (http://chris.beams.io/posts/git-commit/) +for some more good advices. + +Fixes #NUMBER (or URL to the issue) + +Signed-off-by: +``` + +For example: + +``` +Fix poorly named identifiers + +One identifier, fnname, in func.go was poorly named. It has been renamed +to fnName. Another identifier retval was not needed and has been removed +entirely. + +Fixes #1 + +Signed-off-by: Mark Ryan +``` + +## Pull requests + +We accept github pull requests. + +## Quality Controls + +We request you give quality assurance some consideration by: + +* Adding go unit tests for changes where it makes sense. +* Enabling [Travis CI](https://travis-ci.org/intel/govmm) on your github fork of Virtual Machine Manager for Go to get continuous integration feedback on your dev/test branches. + +## Issue tracking + +If you have a problem, please let us know. If it's a bug not already documented, by all means please [open an +issue in github](https://github.com/intel/govmm/issues/new) so we all get visibility +the problem and work toward resolution. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..b86409a9ed --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Virtual Machine Manager for Go + +Virtual Machine Manager for Go (govmm) is a suite of packages that +provide Go APIs for creating and managing virtual machines. There's +currently support for only one hypervisor, qemu/kvm, support for which +is provided by the github.com/intel/govmm/qemu package. + +The qemu package provides APIs for launching qemu instances and for +managing those instances via QMP, once launched. VM instances can +be stopped, have devices attached to them and monitored for events +via the qemu APIs. + +The qemu package has no external dependencies apart from the Go +standard library and so is nice and easy to vendor inside other +projects. From 9cb47fc07d1770e7034f692fa8aa7c12705ada90 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 23 Nov 2017 15:21:26 +0000 Subject: [PATCH 085/264] Add .gitignore file. Currently it just ignores emacs backup files. Signed-off-by: Mark Ryan --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b25c15b81f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ From db60e32f30275bb3ea92b9c6652e82dde30fcf4b Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Tue, 12 Dec 2017 11:19:02 +0000 Subject: [PATCH 086/264] Enable Travis builds This commit adds a .travis file which enables Travis builds for govmm. The script builds the source and runs the unit tests and gometalinter enabling - misspell - vet - ineffassign - gofmt - gocyclo 15 - golint - errcheck - deadcode Signed-off-by: Mark Ryan --- .travis.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..36f4b30f08 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: go + +go: + - 1.8 + - 1.9 + - tip + +go_import_path: github.com/intel/govmm + +matrix: + allow_failures: + - go: tip + +before_install: + - go get github.com/alecthomas/gometalinter + - gometalinter --install + +script: + - go env + - go test -v ./... + - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode ./... From d74e3b66338c190688b3cd7a898426fb55f42dbf Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Tue, 12 Dec 2017 11:51:17 +0000 Subject: [PATCH 087/264] Fix errcheck failures in the unit tests There were some unchecked errors in some of the unit files relating to the closure and removal of temporary files. As the closure and removal of these files is not really important to whether the next passes or fails we ignore the errors. Signed-off-by: Mark Ryan --- qemu/qemu_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 8a97fd1d54..51269eabbf 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -146,10 +146,10 @@ func TestAppendDeviceNetworkMq(t *testing.T) { bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") defer func() { - foo.Close() - bar.Close() - os.Remove(foo.Name()) - os.Remove(bar.Name()) + _ = foo.Close() + _ = bar.Close() + _ = os.Remove(foo.Name()) + _ = os.Remove(bar.Name()) }() netdev := NetDevice{ @@ -196,10 +196,10 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") defer func() { - foo.Close() - bar.Close() - os.Remove(foo.Name()) - os.Remove(bar.Name()) + _ = foo.Close() + _ = bar.Close() + _ = os.Remove(foo.Name()) + _ = os.Remove(bar.Name()) }() netdev := NetDevice{ From 3baa776515bccb2e8e298e6060b3517f91d3b28c Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Tue, 12 Dec 2017 12:06:13 +0000 Subject: [PATCH 088/264] Add badges to the README.md file This commit adds three badges to the README.md file - Goreportcard - Godoc - Travis Signed-off-by: Mark Ryan --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b86409a9ed..0e8e2d23ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Virtual Machine Manager for Go +[![Go Report Card](https://goreportcard.com/badge/github.com/intel/govmm)](https://goreportcard.com/report/github.com/intel/govmm) +[![Build Status](https://travis-ci.org/intel/govmm.svg?branch=master)](https://travis-ci.org/intel/govmm) +[![GoDoc](https://godoc.org/github.com/intel/govmm/qemu?status.svg)](https://godoc.org/github.com/intel/govmm/qemu) + Virtual Machine Manager for Go (govmm) is a suite of packages that provide Go APIs for creating and managing virtual machines. There's currently support for only one hypervisor, qemu/kvm, support for which From 8fe572367a11bbbcb268c649a5ffbb83554ccc12 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 12 Dec 2017 07:32:31 -0600 Subject: [PATCH 089/264] qemu: Add maxcpus attribute to -smp maxcpus is used to specify how many cpus a VM can have. This attribute must be specified to enable the hotplugging CPUs capability, otherwise the maximum number of CPU will be defined by the number of CPU in -smp. Signed-off-by: Julio Montes --- qemu/qemu.go | 21 +++++++++++++++++++-- qemu/qemu_test.go | 23 +++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index b65983cce9..8bf0c3bd06 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -980,6 +980,10 @@ type SMP struct { // Sockets is the number of sockets made available to qemu. Sockets uint32 + + // MaxCPUs is the maximum number of VCPUs that a VM can have. + // This value, if non-zero, MUST BE equal to or greater than CPUs + MaxCPUs uint32 } // Memory is the guest memory configuration structure. @@ -1203,7 +1207,7 @@ func (config *Config) appendMemory() { } } -func (config *Config) appendCPUs() { +func (config *Config) appendCPUs() error { if config.SMP.CPUs > 0 { var SMPParams []string @@ -1221,9 +1225,19 @@ func (config *Config) appendCPUs() { SMPParams = append(SMPParams, fmt.Sprintf(",sockets=%d", config.SMP.Sockets)) } + if config.SMP.MaxCPUs > 0 { + if config.SMP.MaxCPUs < config.SMP.CPUs { + return fmt.Errorf("MaxCPUs %d must be equal to or greater than CPUs %d", + config.SMP.MaxCPUs, config.SMP.CPUs) + } + SMPParams = append(SMPParams, fmt.Sprintf(",maxcpus=%d", config.SMP.MaxCPUs)) + } + config.qemuParams = append(config.qemuParams, "-smp") config.qemuParams = append(config.qemuParams, strings.Join(SMPParams, "")) } + + return nil } func (config *Config) appendRTC() { @@ -1360,7 +1374,6 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendCPUModel() config.appendQMPSockets() config.appendMemory() - config.appendCPUs() config.appendDevices() config.appendRTC() config.appendGlobalParam() @@ -1369,6 +1382,10 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendKernel() config.appendBios() + if err := config.appendCPUs(); err != nil { + return "", err + } + return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, config.fds, nil, logger) } diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 51269eabbf..bf7376da6a 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -52,7 +52,9 @@ func testAppend(structure interface{}, expected string, t *testing.T) { case SMP: config.SMP = s - config.appendCPUs() + if err := config.appendCPUs(); err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } case QMPSocket: config.QMPSockets = []QMPSocket{s} @@ -356,7 +358,7 @@ func TestAppendMemory(t *testing.T) { testAppend(memory, memoryString, t) } -var cpusString = "-smp 2,cores=1,threads=2,sockets=2" +var cpusString = "-smp 2,cores=1,threads=2,sockets=2,maxcpus=6" func TestAppendCPUs(t *testing.T) { smp := SMP{ @@ -364,11 +366,28 @@ func TestAppendCPUs(t *testing.T) { Sockets: 2, Cores: 1, Threads: 2, + MaxCPUs: 6, } testAppend(smp, cpusString, t) } +func TestFailToAppendCPUs(t *testing.T) { + config := Config{ + SMP: SMP{ + CPUs: 2, + Sockets: 2, + Cores: 1, + Threads: 2, + MaxCPUs: 1, + }, + } + + if err := config.appendCPUs(); err == nil { + t.Fatalf("Expected appendCPUs to fail") + } +} + var qmpSingleSocketServerString = "-qmp unix:cc-qmp,server,nowait" var qmpSingleSocketString = "-qmp unix:cc-qmp" From e9e27673fa7711c163e69a556d2b040339d15c12 Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Tue, 12 Dec 2017 08:43:13 -0800 Subject: [PATCH 090/264] vhost-user: updating comments for accuracy, rename device field Some comments were network specific for vhost-user devices, which is incorect. Fixed these. Renamed the HWAddress field to be Address, so that it could potentially be used more generically for non-network based vhost-user types. Signed-off-by: Eric Ernst --- qemu/qemu.go | 14 +++++++------- qemu/qemu_test.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index b65983cce9..de935bcc7d 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -704,7 +704,7 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { return qemuParams } -// VhostUserDeviceType is a qemu networking device type. +// VhostUserDeviceType is a qemu vhost-user device type. type VhostUserDeviceType string const ( @@ -714,21 +714,21 @@ const ( VhostUserNet = "virtio-net-pci" ) -// VhostUserDevice represents a qemu vhost-user network device meant to be passed +// VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { SocketPath string //path to vhostuser socket on host CharDevID string - TypeDevID string //id (SCSI) or netdev (net) device parameter - MacAddress string //only valid if device type is VhostUserNet + TypeDevID string //variable QEMU parameter based on value of VhostUserType + Address string //used for MAC address in net case VhostUserType VhostUserDeviceType } -// Valid returns true if there is a valid socket path defined for VhostUserDevice +// Valid returns true if there is a valid structure defined for VhostUserDevice func (vhostuserDev VhostUserDevice) Valid() bool { if vhostuserDev.SocketPath == "" || vhostuserDev.CharDevID == "" || vhostuserDev.TypeDevID == "" || - (vhostuserDev.VhostUserType == VhostUserNet && vhostuserDev.MacAddress == "") { + (vhostuserDev.VhostUserType == VhostUserNet && vhostuserDev.Address == "") { return false } @@ -755,7 +755,7 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { devParams = append(devParams, VhostUserNet) devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) - devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.MacAddress)) + devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) } else { devParams = append(devParams, VhostUserSCSI) devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 51269eabbf..4610734675 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -273,7 +273,7 @@ func TestAppendDeviceVhostUser(t *testing.T) { SocketPath: "/tmp/nonexistentsocket.socket", CharDevID: "char1", TypeDevID: "scsi1", - MacAddress: "", + Address: "", VhostUserType: VhostUserSCSI, } testAppend(vhostuserSCSIDevice, deviceVhostUserSCSIString, t) @@ -282,7 +282,7 @@ func TestAppendDeviceVhostUser(t *testing.T) { SocketPath: "/tmp/nonexistentsocket.socket", CharDevID: "char1", TypeDevID: "net1", - MacAddress: "00:11:22:33:44:55", + Address: "00:11:22:33:44:55", VhostUserType: VhostUserNet, } testAppend(vhostuserNetDevice, deviceVhostUserNetString, t) From f565536673142f746e6edb1ba4fdc8038ffc268c Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Tue, 12 Dec 2017 09:13:04 -0800 Subject: [PATCH 091/264] vhost-user: add blk device support Introduce basic vhost-user-blk-pci support. In adding this, cleaned up the QemuParams function to use a more appropriate switch statement. Similarly, cleanup up the Valid() logic. We still need to look into parameterization of the block parameter fields as well as introducing multiqueue support for the vhost-user devices. Signed-off-by: Eric Ernst --- qemu/qemu.go | 33 ++++++++++++++++++++++++++++----- qemu/qemu_test.go | 11 +++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index de935bcc7d..959f193871 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -712,6 +712,8 @@ const ( VhostUserSCSI = "vhost-user-scsi-pci" //VhostUserNet represents a net vhostuser device type VhostUserNet = "virtio-net-pci" + //VhostUserBlk represents a block vhostuser device type + VhostUserBlk = "vhost-user-blk-pci" ) // VhostUserDevice represents a qemu vhost-user device meant to be passed @@ -726,9 +728,22 @@ type VhostUserDevice struct { // Valid returns true if there is a valid structure defined for VhostUserDevice func (vhostuserDev VhostUserDevice) Valid() bool { - if vhostuserDev.SocketPath == "" || vhostuserDev.CharDevID == "" || - vhostuserDev.TypeDevID == "" || - (vhostuserDev.VhostUserType == VhostUserNet && vhostuserDev.Address == "") { + + if vhostuserDev.SocketPath == "" || vhostuserDev.CharDevID == "" { + return false + } + + switch vhostuserDev.VhostUserType { + case VhostUserNet: + if vhostuserDev.TypeDevID == "" || vhostuserDev.Address == "" { + return false + } + case VhostUserSCSI: + if vhostuserDev.TypeDevID == "" { + return false + } + case VhostUserBlk: + default: return false } @@ -746,8 +761,9 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) charParams = append(charParams, fmt.Sprintf("path=%s", vhostuserDev.SocketPath)) + switch vhostuserDev.VhostUserType { // if network based vhost device: - if vhostuserDev.VhostUserType == VhostUserNet { + case VhostUserNet: netParams = append(netParams, "type=vhost-user") netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) @@ -756,10 +772,17 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { devParams = append(devParams, VhostUserNet) devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) - } else { + case VhostUserSCSI: devParams = append(devParams, VhostUserSCSI) devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + case VhostUserBlk: + devParams = append(devParams, VhostUserBlk) + devParams = append(devParams, "logical_block_size=4096") + devParams = append(devParams, "size=512M") + devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + default: + return nil } qemuParams = append(qemuParams, "-chardev") diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 4610734675..35d7e84e59 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -267,8 +267,19 @@ func TestAppendDeviceBlock(t *testing.T) { var deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55" var deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1" +var deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2" func TestAppendDeviceVhostUser(t *testing.T) { + + vhostuserBlkDevice := VhostUserDevice{ + SocketPath: "/tmp/nonexistentsocket.socket", + CharDevID: "char2", + TypeDevID: "", + Address: "", + VhostUserType: VhostUserBlk, + } + testAppend(vhostuserBlkDevice, deviceVhostUserBlkString, t) + vhostuserSCSIDevice := VhostUserDevice{ SocketPath: "/tmp/nonexistentsocket.socket", CharDevID: "char1", From 5316779d3516ac4022a173acb0c91c1a37f5c815 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 19 Dec 2017 15:24:25 +0100 Subject: [PATCH 092/264] qemu: Add VSOCK support VSOCK sockets are added through a vhost PCI device. It takes a device ID and a context ID, the latter being the endpoint value to be reached from the host. Signed-off-by: Samuel Ortiz --- qemu/qemu.go | 39 +++++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 3b3c22c140..7c5e33b3a7 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -892,6 +892,45 @@ func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { return qemuParams } +// VSOCKDevice represents a AF_VSOCK socket. +type VSOCKDevice struct { + ID string + + ContextID uint32 +} + +const ( + // MinimalGuestCID is the smallest valid context ID for a guest. + MinimalGuestCID uint32 = 3 + + // VhostVSOCKPCI is the VSOCK vhost device type. + VhostVSOCKPCI = "vhost-vsock-pci" + + // VSOCKGuestCID is the VSOCK guest CID parameter. + VSOCKGuestCID = "guest-cid" +) + +// Valid returns true if the VSOCKDevice structure is valid and complete. +func (vsock VSOCKDevice) Valid() bool { + if vsock.ID == "" || vsock.ContextID < MinimalGuestCID { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of the VSOCK device. +func (vsock VSOCKDevice) QemuParams(config *Config) []string { + var qemuParams []string + + deviceParam := fmt.Sprintf("%s,id=%s,%s=%d", VhostVSOCKPCI, vsock.ID, VSOCKGuestCID, vsock.ContextID) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, deviceParam) + + return qemuParams +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 47bd0e452d..2f9de21a6b 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -311,6 +311,34 @@ func TestAppendDeviceVFIO(t *testing.T) { testAppend(vfioDevice, deviceVFIOString, t) } +var deviceVSOCKString = "-device vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=4" + +func TestAppendVSOCK(t *testing.T) { + vsockDevice := VSOCKDevice{ + ID: "vhost-vsock-pci0", + ContextID: 4, + } + + testAppend(vsockDevice, deviceVSOCKString, t) +} + +func TestVSOCKValid(t *testing.T) { + vsockDevice := VSOCKDevice{ + ID: "vhost-vsock-pci0", + ContextID: MinimalGuestCID - 1, + } + + if vsockDevice.Valid() { + t.Fatalf("VSOCK Context ID is not valid") + } + + vsockDevice.ID = "" + + if vsockDevice.Valid() { + t.Fatalf("VSOCK ID is not valid") + } +} + func TestAppendEmptyDevice(t *testing.T) { device := SerialDevice{} From 3a31da32af4d0bdd93e1d483ffd9ad97a9a9e8c1 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Fri, 15 Dec 2017 16:10:45 -0800 Subject: [PATCH 093/264] scsi: Add a scsi controller device SCSI controller allows scsi disks to be attached on the SCSI bus created by the controller. Signed-off-by: Archana Shinde --- qemu/qemu.go | 39 +++++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 14 ++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 7c5e33b3a7..c10598639a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -825,6 +825,45 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { return qemuParams } +// SCSIController represents a SCSI controller device. +type SCSIController struct { + ID string + + // Bus on which the SCSI controller is attached, this is optional + Bus string + + // Addr is the PCI address offset, this is optional + Addr string +} + +// Valid returns true if the SCSIController structure is valid and complete. +func (scsiCon SCSIController) Valid() bool { + if scsiCon.ID == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this SCSIController device. +func (scsiCon SCSIController) QemuParams(config *Config) []string { + var qemuParams []string + var devParams []string + + devParams = append(devParams, fmt.Sprintf("virtio-scsi-pci,id=%s", scsiCon.ID)) + if scsiCon.Bus != "" { + devParams = append(devParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) + } + if scsiCon.Addr != "" { + devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(devParams, ",")) + + return qemuParams +} + // BridgeType is the type of the bridge type BridgeType uint diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 2f9de21a6b..2bed9bae36 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -339,6 +339,20 @@ func TestVSOCKValid(t *testing.T) { } } +var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo" +var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0" + +func TestAppendDeviceSCSIController(t *testing.T) { + scsiCon := SCSIController{ + ID: "foo", + } + testAppend(scsiCon, deviceSCSIControllerStr, t) + + scsiCon.Bus = "pci.0" + scsiCon.Addr = "00:04.0" + testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) +} + func TestAppendEmptyDevice(t *testing.T) { device := SerialDevice{} From 6d198b8a13ddd90d5470abd07b3acebc5e69fbf9 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Tue, 2 Jan 2018 16:21:01 +0000 Subject: [PATCH 094/264] Compute coverage statistics for unit tests in Travis builds This commit enables unit test coverage computation in Travis CI builds. Going forward, builds that decrease the unit test coverage by more than 1.0% will fail. Signed-off-by: Mark Ryan --- .travis.yml | 3 ++- README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 36f4b30f08..03a39421fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,9 @@ matrix: before_install: - go get github.com/alecthomas/gometalinter - gometalinter --install + - go get github.com/mattn/goveralls script: - go env - - go test -v ./... + - $GOPATH/bin/goveralls -v -service=travis-ci - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode ./... diff --git a/README.md b/README.md index 0e8e2d23ab..2bb92f0c47 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/intel/govmm)](https://goreportcard.com/report/github.com/intel/govmm) [![Build Status](https://travis-ci.org/intel/govmm.svg?branch=master)](https://travis-ci.org/intel/govmm) [![GoDoc](https://godoc.org/github.com/intel/govmm/qemu?status.svg)](https://godoc.org/github.com/intel/govmm/qemu) +[![Coverage Status](https://coveralls.io/repos/github/intel/govmm/badge.svg?branch=master)](https://coveralls.io/github/intel/govmm?branch=master) Virtual Machine Manager for Go (govmm) is a suite of packages that provide Go APIs for creating and managing virtual machines. There's From 3273aafd530f7f63af83ca601a5e6cbea5b72827 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Thu, 21 Dec 2017 23:17:03 -0800 Subject: [PATCH 095/264] scsi: Add function to send device_add qmp command for a scsi device device_add qmp command for scsi devices accepts additional parameters like scsi-id and lun. Implement function to add scsi devices. Devices with drivers "scsi-hd", "scsi-cd" and "scsi-disk" are accepted. Signed-off-by: Archana Shinde --- qemu/qmp.go | 39 +++++++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 3727b8478e..8888ad4d4a 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -672,6 +672,45 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b return q.executeCommand(ctx, "device_add", args, nil) } +// ExecuteSCSIDeviceAdd adds the guest portion of a block device to a QEMU instance +// using a SCSI driver with the device_add command. blockdevID should match the +// blockdevID passed to a previous call to ExecuteBlockdevAdd. devID is the id of +// the device to add. Both strings must be valid QMP identifiers. driver is the name of the +// scsi driver,e.g., scsi-hd, and bus is the name of a SCSI controller bus. +// scsiID is the SCSI id, lun is logical unit number. scsiID and lun are optional, a negative value +// for scsiID and lun is ignored. +func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string, scsiID, lun int) error { + // TBD: Add drivers for scsi passthrough like scsi-generic and scsi-block + drivers := []string{"scsi-hd", "scsi-cd", "scsi-disk"} + + isSCSIDriver := false + for _, d := range drivers { + if driver == d { + isSCSIDriver = true + break + } + } + + if !isSCSIDriver { + return fmt.Errorf("Invalid SCSI driver provided %s", driver) + } + + args := map[string]interface{}{ + "id": devID, + "driver": driver, + "drive": blockdevID, + "bus": bus, + } + if scsiID >= 0 { + args["scsi-id"] = scsiID + } + if lun >= 0 { + args["lun"] = lun + } + + return q.executeCommand(ctx, "device_add", args, nil) +} + // ExecuteBlockdevDel deletes a block device by sending a x-blockdev-del command // for qemu versions < 2.9. It sends the updated blockdev-del command for qemu>=2.9. // blockdevID is the id of the block device to be deleted. Typically, this will diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 9b87fbcc5d..85850fcb5b 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -389,6 +389,31 @@ func TestQMPDeviceAdd(t *testing.T) { <-disconnectedCh } +// Checks that the device_add command for scsi is correctly sent. +// +// We start a QMPLoop, send the device_add command and stop the loop. +// +// The device_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPSCSIDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + blockdevID := fmt.Sprintf("drive_%s", volumeUUID) + devID := fmt.Sprintf("device_%s", volumeUUID) + err := q.ExecuteSCSIDeviceAdd(context.Background(), blockdevID, devID, + "scsi-hd", "scsi0.0", 1, 2) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the x-blockdev-del command is correctly sent. // // We start a QMPLoop, send the x-blockdev-del command and stop the loop. From 693d9548dcf106f4de19fd83968690903e5c1da4 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 12 Feb 2018 08:14:02 -0600 Subject: [PATCH 096/264] qemu: add options for the machine type certain machines types need to have options to enable or disable features For example the machine type virt in certain hosts must have the gic version (gic-version=3 or gic-version=host) to start without problems Signed-off-by: Julio Montes --- qemu/qemu.go | 8 ++++++++ qemu/qemu_test.go | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index c10598639a..7ef751b96a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -43,6 +43,10 @@ type Machine struct { // Acceleration are the machine acceleration options to be used by qemu. Acceleration string + + // Options are options for the machine type + // For example gic-version=host and usb=off + Options string } // Device is the qemu device interface. @@ -1240,6 +1244,10 @@ func (config *Config) appendMachine() { machineParams = append(machineParams, fmt.Sprintf(",accel=%s", config.Machine.Acceleration)) } + if config.Machine.Options != "" { + machineParams = append(machineParams, fmt.Sprintf(",%s", config.Machine.Options)) + } + config.qemuParams = append(config.qemuParams, "-machine") config.qemuParams = append(config.qemuParams, strings.Join(machineParams, "")) } diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 2bed9bae36..cc00a82fc1 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -75,14 +75,20 @@ func testAppend(structure interface{}, expected string, t *testing.T) { } } -var machineString = "-machine pc-lite,accel=kvm,kernel_irqchip,nvdimm" - func TestAppendMachine(t *testing.T) { + machineString := "-machine pc-lite,accel=kvm,kernel_irqchip,nvdimm" machine := Machine{ Type: "pc-lite", Acceleration: "kvm,kernel_irqchip,nvdimm", } + testAppend(machine, machineString, t) + machineString = "-machine pc-lite,accel=kvm,kernel_irqchip,nvdimm,gic-version=host,usb=off" + machine = Machine{ + Type: "pc-lite", + Acceleration: "kvm,kernel_irqchip,nvdimm", + Options: "gic-version=host,usb=off", + } testAppend(machine, machineString, t) } From 68f307180602e10548e3bcc0442a26ea35db9b75 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 6 Mar 2018 11:55:15 -0600 Subject: [PATCH 097/264] qemu: add DisableModern to SCSIController DisableModern prevents qemu from relying on fast MMIO. Signed-off-by: Julio Montes --- qemu/qemu.go | 6 ++++++ qemu/qemu_test.go | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 7ef751b96a..218507808d 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -838,6 +838,9 @@ type SCSIController struct { // Addr is the PCI address offset, this is optional Addr string + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } // Valid returns true if the SCSIController structure is valid and complete. @@ -861,6 +864,9 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { if scsiCon.Addr != "" { devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } + if scsiCon.DisableModern { + devParams = append(devParams, fmt.Sprintf("disable-modern=true")) + } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index cc00a82fc1..bcbdb2ec13 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -346,7 +346,7 @@ func TestVSOCKValid(t *testing.T) { } var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo" -var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0" +var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true" func TestAppendDeviceSCSIController(t *testing.T) { scsiCon := SCSIController{ @@ -356,6 +356,7 @@ func TestAppendDeviceSCSIController(t *testing.T) { scsiCon.Bus = "pci.0" scsiCon.Addr = "00:04.0" + scsiCon.DisableModern = true testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } From 0c0ec8f3c9fc181e8bce6ea38df2578f8f86770d Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 20 Mar 2018 16:30:28 +0800 Subject: [PATCH 098/264] qemu: add initrd support Append initrd image to qemu arguments if configured. Signed-off-by: Peng Tao --- qemu/qemu.go | 8 ++++++++ qemu/qemu_test.go | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 218507808d..89d053fe95 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1117,6 +1117,9 @@ type Kernel struct { // Path is the guest kernel path on the host filesystem. Path string + // InitrdPath is the guest initrd path on the host filesystem. + InitrdPath string + // Params is the kernel parameters string. Params string } @@ -1395,6 +1398,11 @@ func (config *Config) appendKernel() { config.qemuParams = append(config.qemuParams, "-kernel") config.qemuParams = append(config.qemuParams, config.Kernel.Path) + if config.Kernel.InitrdPath != "" { + config.qemuParams = append(config.qemuParams, "-initrd") + config.qemuParams = append(config.qemuParams, config.Kernel.InitrdPath) + } + if config.Kernel.Params != "" { config.qemuParams = append(config.qemuParams, "-append") config.qemuParams = append(config.qemuParams, config.Kernel.Params) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index bcbdb2ec13..b7e60e7271 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -395,12 +395,13 @@ func TestAppendKnobsAllFalse(t *testing.T) { testAppend(knobs, knobsString, t) } -var kernelString = "-kernel /opt/vmlinux.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" +var kernelString = "-kernel /opt/vmlinux.container -initrd /opt/initrd.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", + Path: "/opt/vmlinux.container", + InitrdPath: "/opt/initrd.container", + Params: "root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable", } testAppend(kernel, kernelString, t) From a54de1835bdf1516905e500143ff3f8522ab5e27 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Wed, 28 Mar 2018 16:12:31 -0700 Subject: [PATCH 099/264] iothread: Add ability to configure iothreads IOthreads also known as x-data-plane allow IO to be processed in a separate thread rather than the main event loop. This produces much better IO throughput and latency. Signed-off-by: Archana Shinde --- qemu/qemu.go | 17 +++++++++++++++++ qemu/qemu_test.go | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 89d053fe95..0b5adb2858 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1159,6 +1159,11 @@ type Knobs struct { Realtime bool } +// IOThread allows IO to be performed on a separate thread. +type IOThread struct { + ID string +} + // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { @@ -1213,6 +1218,8 @@ type Config struct { // fds is a list of open file descriptors to be passed to the spawned qemu process fds []*os.File + IOThreads []IOThread + qemuParams []string } @@ -1481,6 +1488,15 @@ func (config *Config) appendBios() { } } +func (config *Config) appendIOThreads() { + for _, t := range config.IOThreads { + if t.ID != "" { + config.qemuParams = append(config.qemuParams, "-object") + config.qemuParams = append(config.qemuParams, fmt.Sprintf("iothread,id=%s", t.ID)) + } + } +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -1504,6 +1520,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendKnobs() config.appendKernel() config.appendBios() + config.appendIOThreads() if err := config.appendCPUs(); err != nil { return "", err diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index b7e60e7271..1c63e42bdb 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -67,6 +67,10 @@ func testAppend(structure interface{}, expected string, t *testing.T) { case RTC: config.RTC = s config.appendRTC() + + case IOThread: + config.IOThreads = []IOThread{s} + config.appendIOThreads() } result := strings.Join(config.qemuParams, " ") @@ -525,3 +529,13 @@ func TestAppendRTC(t *testing.T) { testAppend(rtc, rtcString, t) } + +var ioThreadString = "-object iothread,id=iothread1" + +func TestAppendIOThread(t *testing.T) { + ioThread := IOThread{ + ID: "iothread1", + } + + testAppend(ioThread, ioThreadString, t) +} From 9130f375166febfa9ca1937d126d2c6aa155b2a1 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Wed, 28 Mar 2018 16:15:52 -0700 Subject: [PATCH 100/264] scsi: Allow scsi controller to associate with an IO thread. This enable data-plane for scsi. All drives attached to the scsi controller will have their IO processed in a single separate IO thread instead of qemu's main event loop. Signed-off-by: Archana Shinde --- qemu/qemu.go | 6 ++++++ qemu/qemu_test.go | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 0b5adb2858..4b0e41795a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -841,6 +841,9 @@ type SCSIController struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // IOThread is the IO thread on which IO will be handled + IOThread string } // Valid returns true if the SCSIController structure is valid and complete. @@ -867,6 +870,9 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { if scsiCon.DisableModern { devParams = append(devParams, fmt.Sprintf("disable-modern=true")) } + if scsiCon.IOThread != "" { + devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) + } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 1c63e42bdb..0506f4240f 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -350,7 +350,7 @@ func TestVSOCKValid(t *testing.T) { } var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo" -var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true" +var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1" func TestAppendDeviceSCSIController(t *testing.T) { scsiCon := SCSIController{ @@ -361,6 +361,7 @@ func TestAppendDeviceSCSIController(t *testing.T) { scsiCon.Bus = "pci.0" scsiCon.Addr = "00:04.0" scsiCon.DisableModern = true + scsiCon.IOThread = "iothread1" testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } From 30aeacb89e31cdead0f5be2a94ce307bb6e25976 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Tue, 3 Apr 2018 11:57:26 -0700 Subject: [PATCH 101/264] qemu: Add qemu parameter for PCI address for a bridge. We need to be able to specify the PCI slot for a bridge while adding it. Add test to verify bridge is correctly added. Signed-off-by: Archana Shinde --- qemu/qemu.go | 10 ++++++++++ qemu/qemu_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 4b0e41795a..f2f4f5b8b2 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -907,6 +907,9 @@ type BridgeDevice struct { // SHPC is used to enable or disable the standard hot plug controller SHPC bool + + // PCI Slot + Addr string } // Valid returns true if the BridgeDevice structure is valid and complete. @@ -941,6 +944,13 @@ func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { } deviceParam := fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", deviceName, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc) + if bridgeDev.Addr != "" { + addr, err := strconv.Atoi(bridgeDev.Addr) + if err == nil && addr >= 0 { + deviceParam += fmt.Sprintf(",addr=%x", addr) + } + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, deviceParam) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 0506f4240f..895fe0f15c 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -365,6 +365,22 @@ func TestAppendDeviceSCSIController(t *testing.T) { testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } +var deviceBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" + +func TestAppendBridgeDevice(t *testing.T) { + + bridge := BridgeDevice{ + Type: PCIBridge, + ID: "mybridge", + Bus: "/pci-bus/pcie.0", + Addr: "255", + Chassis: 5, + SHPC: true, + } + + testAppend(bridge, deviceBridgeString, t) +} + func TestAppendEmptyDevice(t *testing.T) { device := SerialDevice{} From 283d7df99e4be6fa0eeb30f80597a1e90b7e9f78 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 7 May 2018 17:34:33 +0800 Subject: [PATCH 102/264] qemu: add file backed memory device support It allows a caller to use a local file as the memory backend of the guest, and it also allows the file backed memory device to be set shared or not. Signed-off-by: Peng Tao --- qemu/qemu.go | 26 ++++++++++++++++++++++++++ qemu/qemu_test.go | 31 ++++++++++++++++++------------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f2f4f5b8b2..6022fe470a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1126,6 +1126,10 @@ type Memory struct { // MaxMem is the maximum amount of memory that can be made available // to the guest through e.g. hot pluggable memory. MaxMem string + + // Path is the file path of the memory device. It points to a local + // file path used by FileBackedMem. + Path string } // Kernel is the guest kernel configuration structure. @@ -1167,6 +1171,13 @@ type Knobs struct { // MemPrealloc will allocate all the RAM upfront MemPrealloc bool + // FileBackedMem requires Memory.Size and Memory.Path of the VM to + // be set. + FileBackedMem bool + + // FileBackedMemShared will set the FileBackedMem device as shared. + FileBackedMemShared bool + // Mlock will control locking of memory // Only active when Realtime is set to true Mlock bool @@ -1474,6 +1485,21 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, "-device") config.qemuParams = append(config.qemuParams, deviceMemParam) } + } else if config.Knobs.FileBackedMem == true { + if config.Memory.Size != "" && config.Memory.Path != "" { + dimmName := "dimm1" + objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=" + config.Memory.Path + if config.Knobs.FileBackedMemShared == true { + objMemParam += ",share=on" + } + numaMemParam := "node,memdev=" + dimmName + + config.qemuParams = append(config.qemuParams, "-object") + config.qemuParams = append(config.qemuParams, objMemParam) + + config.qemuParams = append(config.qemuParams, "-numa") + config.qemuParams = append(config.qemuParams, numaMemParam) + } } if config.Knobs.Realtime == true { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 895fe0f15c..09c6523771 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -390,13 +390,15 @@ func TestAppendEmptyDevice(t *testing.T) { func TestAppendKnobsAllTrue(t *testing.T) { var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on" knobs := Knobs{ - NoUserConfig: true, - NoDefaults: true, - NoGraphic: true, - Daemonize: true, - MemPrealloc: true, - Realtime: true, - Mlock: true, + NoUserConfig: true, + NoDefaults: true, + NoGraphic: true, + Daemonize: true, + MemPrealloc: true, + FileBackedMem: true, + FileBackedMemShared: true, + Realtime: true, + Mlock: true, } testAppend(knobs, knobsString, t) @@ -405,12 +407,14 @@ func TestAppendKnobsAllTrue(t *testing.T) { func TestAppendKnobsAllFalse(t *testing.T) { var knobsString = "-realtime mlock=off" knobs := Knobs{ - NoUserConfig: false, - NoDefaults: false, - NoGraphic: false, - MemPrealloc: false, - Realtime: false, - Mlock: false, + NoUserConfig: false, + NoDefaults: false, + NoGraphic: false, + MemPrealloc: false, + FileBackedMem: false, + FileBackedMemShared: false, + Realtime: false, + Mlock: false, } testAppend(knobs, knobsString, t) @@ -435,6 +439,7 @@ func TestAppendMemory(t *testing.T) { Size: "2G", Slots: 2, MaxMem: "3G", + Path: "", } testAppend(memory, memoryString, t) From 723bc5f3c610936e48ee24f7c2c2c087393d2845 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 7 May 2018 17:38:54 +0800 Subject: [PATCH 103/264] qemu: allow to create a stopped guest When Knobs.Stopped is set, the guest CPU will not be started at startup. Signed-off-by: Peng Tao --- qemu/qemu.go | 7 +++++++ qemu/qemu_test.go | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 6022fe470a..33b4489c3c 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1182,6 +1182,9 @@ type Knobs struct { // Only active when Realtime is set to true Mlock bool + // Stopped will not start guest CPU at startup + Stopped bool + // Realtime will enable realtime QEMU Realtime bool } @@ -1521,6 +1524,10 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, "mlock=off") } } + + if config.Knobs.Stopped == true { + config.qemuParams = append(config.qemuParams, "-S") + } } func (config *Config) appendBios() { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 09c6523771..8e805dc7e2 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -388,7 +388,7 @@ func TestAppendEmptyDevice(t *testing.T) { } func TestAppendKnobsAllTrue(t *testing.T) { - var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on" + var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on -S" knobs := Knobs{ NoUserConfig: true, NoDefaults: true, @@ -399,6 +399,7 @@ func TestAppendKnobsAllTrue(t *testing.T) { FileBackedMemShared: true, Realtime: true, Mlock: true, + Stopped: true, } testAppend(knobs, knobsString, t) @@ -415,6 +416,7 @@ func TestAppendKnobsAllFalse(t *testing.T) { FileBackedMemShared: false, Realtime: false, Mlock: false, + Stopped: false, } testAppend(knobs, knobsString, t) From 0ace4176b4a41e1ee3865f9335abf4094806bf22 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 7 May 2018 17:50:19 +0800 Subject: [PATCH 104/264] qemu: allow to set migration incoming It is useful when we want to specify migration incoming source. Supported source are fd and exec right now. Signed-off-by: Peng Tao --- qemu/qemu.go | 36 ++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 33b4489c3c..04a5fa6f77 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1194,6 +1194,24 @@ type IOThread struct { ID string } +const ( + // MigrationFD is the migration incoming type based on open file descriptor. + // Skip default 0 so that it must be set on purpose. + MigrationFD = 1 + // MigrationExec is the migration incoming type based on commands. + MigrationExec = 2 +) + +// Incoming controls migration source preparation +type Incoming struct { + // Possible values are MigrationFD, MigrationExec + MigrationType int + // Only valid if MigrationType == MigrationFD + FD *os.File + // Only valid if MigrationType == MigrationExec + Exec string +} + // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { @@ -1245,6 +1263,9 @@ type Config struct { // Bios is the -bios parameter Bios string + // Incoming controls migration source preparation + Incoming Incoming + // fds is a list of open file descriptors to be passed to the spawned qemu process fds []*os.File @@ -1546,6 +1567,20 @@ func (config *Config) appendIOThreads() { } } +func (config *Config) appendIncoming() { + var uri string + switch config.Incoming.MigrationType { + case MigrationExec: + uri = fmt.Sprintf("exec:%s", config.Incoming.Exec) + case MigrationFD: + chFDs := config.appendFDs([]*os.File{config.Incoming.FD}) + uri = fmt.Sprintf("fd:%d", chFDs[0]) + default: + return + } + config.qemuParams = append(config.qemuParams, "-S", "-incoming", uri) +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -1570,6 +1605,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendKernel() config.appendBios() config.appendIOThreads() + config.appendIncoming() if err := config.appendCPUs(); err != nil { return "", err diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 8e805dc7e2..43fe257e6e 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -71,6 +71,9 @@ func testAppend(structure interface{}, expected string, t *testing.T) { case IOThread: config.IOThreads = []IOThread{s} config.appendIOThreads() + case Incoming: + config.Incoming = s + config.appendIncoming() } result := strings.Join(config.qemuParams, " ") @@ -563,3 +566,25 @@ func TestAppendIOThread(t *testing.T) { testAppend(ioThread, ioThreadString, t) } + +var incomingStringFD = "-S -incoming fd:3" + +func TestAppendIncomingFD(t *testing.T) { + source := Incoming{ + MigrationType: MigrationFD, + FD: os.Stdout, + } + + testAppend(source, incomingStringFD, t) +} + +var incomingStringExec = "-S -incoming exec:test migration cmd" + +func TestAppendIncomingExec(t *testing.T) { + source := Incoming{ + MigrationType: MigrationExec, + Exec: "test migration cmd", + } + + testAppend(source, incomingStringExec, t) +} From a03d4968e11ed945d045b118eff3b48ad0bbc3c2 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 7 May 2018 18:08:25 +0800 Subject: [PATCH 105/264] qmp: add set migration capabilities It allows to set guest migration capabilities. Signed-off-by: Peng Tao --- qemu/qmp.go | 9 +++++++++ qemu/qmp_test.go | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 8888ad4d4a..8a2b4bc1ff 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -828,3 +828,12 @@ func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableC return cpus, nil } + +// ExecSetMigrationCaps sets migration capabilities +func (q *QMP) ExecSetMigrationCaps(ctx context.Context, caps []map[string]interface{}) error { + args := map[string]interface{}{ + "capabilities": caps, + } + + return q.executeCommand(ctx, "migrate-set-capabilities", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 85850fcb5b..62018675cd 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -887,3 +887,26 @@ func TestQMPExecuteQueryHotpluggableCPUs(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks that migrate capabilities can be set +func TestExecSetMigrationCaps(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("migrate-set-capabilities", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + caps := []map[string]interface{}{ + { + "capability": "bypass-shared-memory", + "state": true, + }, + } + err := q.ExecSetMigrationCaps(context.Background(), caps) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + q.Shutdown() + <-disconnectedCh +} From 8aeca1538875f939748c2f09ad288c3ac2b7b755 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 7 May 2018 18:19:17 +0800 Subject: [PATCH 106/264] qmp: add migrate set arguments It allows to set migration arguments so that callers can control how migration is done. Signed-off-by: Peng Tao --- qemu/qmp.go | 9 +++++++++ qemu/qmp_test.go | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 8a2b4bc1ff..9e37821814 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -837,3 +837,12 @@ func (q *QMP) ExecSetMigrationCaps(ctx context.Context, caps []map[string]interf return q.executeCommand(ctx, "migrate-set-capabilities", args, nil) } + +// ExecSetMigrateArguments sets the command line used for migration +func (q *QMP) ExecSetMigrateArguments(ctx context.Context, url string) error { + args := map[string]interface{}{ + "uri": url, + } + + return q.executeCommand(ctx, "migrate", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 62018675cd..9b471d6c63 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -910,3 +910,20 @@ func TestExecSetMigrationCaps(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks that migrate arguments can be set +func TestExecSetMigrateArguments(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("migrate", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecSetMigrateArguments(context.Background(), "exec:foobar") + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + q.Shutdown() + <-disconnectedCh +} From e66a9b481b3747ac67b5889a567c5fb67a6a0495 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 7 May 2018 19:28:12 +0800 Subject: [PATCH 107/264] qemu: add appendMemoryKnobs helper To fix travis failure about cyclomatic complexity in appendKnobs(). Signed-off-by: Peng Tao --- qemu/qemu.go | 38 ++++++++++--------- qemu/qemu_test.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 17 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 04a5fa6f77..41381d80ee 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1468,23 +1468,7 @@ func (config *Config) appendKernel() { } } -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") - } - - if config.Knobs.Daemonize == true { - config.qemuParams = append(config.qemuParams, "-daemonize") - } - +func (config *Config) appendMemoryKnobs() { if config.Knobs.HugePages == true { if config.Memory.Size != "" { dimmName := "dimm1" @@ -1525,6 +1509,26 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, numaMemParam) } } +} + +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") + } + + if config.Knobs.Daemonize == true { + config.qemuParams = append(config.qemuParams, "-daemonize") + } + + config.appendMemoryKnobs() if config.Knobs.Realtime == true { config.qemuParams = append(config.qemuParams, "-realtime") diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 43fe257e6e..14637910d0 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -28,7 +28,12 @@ const volumeUUID = "67d86208-b46c-4465-9018-e14187d4010" func testAppend(structure interface{}, expected string, t *testing.T) { var config Config + testConfigAppend(&config, structure, expected, t) + return +} + +func testConfigAppend(config *Config, structure interface{}, expected string, t *testing.T) { switch s := structure.(type) { case Machine: config.Machine = s @@ -425,6 +430,97 @@ func TestAppendKnobsAllFalse(t *testing.T) { testAppend(knobs, knobsString, t) } +func TestAppendMemoryHugePages(t *testing.T) { + conf := &Config{ + Memory: Memory{ + Size: "1G", + Slots: 8, + MaxMem: "3G", + Path: "foobar", + }, + } + memString := "-m 1G,slots=8,maxmem=3G" + testConfigAppend(conf, conf.Memory, memString, t) + + knobs := Knobs{ + HugePages: true, + MemPrealloc: true, + FileBackedMem: true, + FileBackedMemShared: true, + } + knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=/dev/hugepages,share=on,prealloc=on -numa node,memdev=dimm1" + mlockFalseString := "-realtime mlock=off" + + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) +} + +func TestAppendMemoryMemPrealloc(t *testing.T) { + conf := &Config{ + Memory: Memory{ + Size: "1G", + Slots: 8, + MaxMem: "3G", + Path: "foobar", + }, + } + memString := "-m 1G,slots=8,maxmem=3G" + testConfigAppend(conf, conf.Memory, memString, t) + + knobs := Knobs{ + MemPrealloc: true, + FileBackedMem: true, + FileBackedMemShared: true, + } + knobsString := "-object memory-backend-ram,id=dimm1,size=1G,prealloc=on -device pc-dimm,id=dimm1,memdev=dimm1" + mlockFalseString := "-realtime mlock=off" + + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) +} + +func TestAppendMemoryFileBackedMemShared(t *testing.T) { + conf := &Config{ + Memory: Memory{ + Size: "1G", + Slots: 8, + MaxMem: "3G", + Path: "foobar", + }, + } + memString := "-m 1G,slots=8,maxmem=3G" + testConfigAppend(conf, conf.Memory, memString, t) + + knobs := Knobs{ + FileBackedMem: true, + FileBackedMemShared: true, + } + knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on -numa node,memdev=dimm1" + mlockFalseString := "-realtime mlock=off" + + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) +} + +func TestAppendMemoryFileBackedMem(t *testing.T) { + conf := &Config{ + Memory: Memory{ + Size: "1G", + Slots: 8, + MaxMem: "3G", + Path: "foobar", + }, + } + memString := "-m 1G,slots=8,maxmem=3G" + testConfigAppend(conf, conf.Memory, memString, t) + + knobs := Knobs{ + FileBackedMem: true, + FileBackedMemShared: false, + } + knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar -numa node,memdev=dimm1" + mlockFalseString := "-realtime mlock=off" + + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) +} + var kernelString = "-kernel /opt/vmlinux.container -initrd /opt/initrd.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" func TestAppendKernel(t *testing.T) { From 54caf7810b3c0bf33a7e865660297a008380978e Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 8 May 2018 00:46:16 +0800 Subject: [PATCH 108/264] qmp: add hotplug memory It adds size of MiB memory to the guest. Signed-off-by: Peng Tao --- qemu/qmp.go | 35 +++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 18 ++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9e37821814..37334e99e2 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -846,3 +846,38 @@ func (q *QMP) ExecSetMigrateArguments(ctx context.Context, url string) error { return q.executeCommand(ctx, "migrate", args, nil) } + +// ExecHotplugMemory adds size of MiB memory to the guest +func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int) error { + args := map[string]interface{}{ + "qom-type": qomtype, + "id": id, + "props": map[string]interface{}{"size": uint64(size) << 20}, + } + if mempath != "" { + args["mem-path"] = mempath + } + err := q.executeCommand(ctx, "object-add", args, nil) + if err != nil { + return err + } + + defer func() { + if err != nil { + q.cfg.Logger.Errorf("Unable to hotplug memory device: %v", err) + err = q.executeCommand(ctx, "object-del", map[string]interface{}{"id": id}, nil) + if err != nil { + q.cfg.Logger.Warningf("Unable to clean up memory object: %v", err) + } + } + }() + + args = map[string]interface{}{ + "driver": "pc-dimm", + "id": "dimm" + id, + "memdev": id, + } + err = q.executeCommand(ctx, "device_add", args, nil) + + return err +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 9b471d6c63..f5b472d741 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -927,3 +927,21 @@ func TestExecSetMigrateArguments(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks hotplug memory +func TestExecHotplugMemory(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("object-add", nil, "return", nil) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecHotplugMemory(context.Background(), "memory-backend-ram", "mem0", "", 128) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + q.Shutdown() + <-disconnectedCh +} From ffc06e6bc40eed80e80646fcf16be6efa4807024 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 28 Jun 2018 15:11:13 +0100 Subject: [PATCH 109/264] qemu,qmp: Add staticcheck to travis and fix errors This commit enables staticcheck in the travis builds and fixes the existing errors detected by staticcheck. There was one type of error repeated in qemu.go in which the type of some constants was not explicitly specified. Signed-off-by: Mark Ryan --- .travis.yml | 2 +- qemu/qemu.go | 56 +++++++++++++++++++++++++++------------------------- qemu/qmp.go | 2 +- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03a39421fd..fb5bc44199 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,4 @@ before_install: script: - go env - $GOPATH/bin/goveralls -v -service=travis-ci - - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode ./... + - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck ./... diff --git a/qemu/qemu.go b/qemu/qemu.go index 41381d80ee..831608af33 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -63,25 +63,25 @@ const ( NVDIMM DeviceDriver = "nvdimm" // Virtio9P is the 9pfs device driver. - Virtio9P = "virtio-9p-pci" + Virtio9P DeviceDriver = "virtio-9p-pci" // VirtioNet is the virt-io networking device driver. - VirtioNet = "virtio-net" + VirtioNet DeviceDriver = "virtio-net" // VirtioNetPCI is the virt-io pci networking device driver. - VirtioNetPCI = "virtio-net-pci" + VirtioNetPCI DeviceDriver = "virtio-net-pci" // VirtioSerial is the serial device driver. - VirtioSerial = "virtio-serial-pci" + VirtioSerial DeviceDriver = "virtio-serial-pci" // VirtioBlock is the block device driver. - VirtioBlock = "virtio-blk" + VirtioBlock DeviceDriver = "virtio-blk" // Console is the console device driver. - Console = "virtconsole" + Console DeviceDriver = "virtconsole" // VirtioSerialPort is the serial port device driver. - VirtioSerialPort = "virtserialport" + VirtioSerialPort DeviceDriver = "virtserialport" ) // ObjectType is a string representing a qemu object type. @@ -168,10 +168,10 @@ const ( Local FSDriver = "local" // Handle is the handle qemu filesystem driver. - Handle = "handle" + Handle FSDriver = "handle" // Proxy is the proxy qemu filesystem driver. - Proxy = "proxy" + Proxy FSDriver = "proxy" ) const ( @@ -179,13 +179,13 @@ const ( None SecurityModelType = "none" // PassThrough uses the same credentials on both the host and guest. - PassThrough = "passthrough" + PassThrough SecurityModelType = "passthrough" // MappedXattr stores some files attributes as extended attributes. - MappedXattr = "mapped-xattr" + MappedXattr SecurityModelType = "mapped-xattr" // MappedFile stores some files attributes in the .virtfs directory. - MappedFile = "mapped-file" + MappedFile SecurityModelType = "mapped-file" ) // FSDevice represents a qemu filesystem configuration. @@ -256,19 +256,19 @@ const ( Pipe CharDeviceBackend = "pipe" // Socket creates a 2 way stream socket (TCP or Unix). - Socket = "socket" + Socket CharDeviceBackend = "socket" // CharConsole sends traffic from the guest to QEMU's standard output. - CharConsole = "console" + CharConsole CharDeviceBackend = "console" // Serial sends traffic from the guest to a serial device on the host. - Serial = "serial" + Serial CharDeviceBackend = "serial" // TTY is an alias for Serial. - TTY = "tty" + TTY CharDeviceBackend = "tty" // PTY creates a new pseudo-terminal on the host and connect to it. - PTY = "pty" + PTY CharDeviceBackend = "pty" ) // CharDevice represents a qemu character device. @@ -345,19 +345,19 @@ const ( TAP NetDeviceType = "tap" // MACVTAP is a macvtap networking device type. - MACVTAP = "macvtap" + MACVTAP NetDeviceType = "macvtap" // IPVTAP is a ipvtap virtual networking device type. - IPVTAP = "ipvtap" + IPVTAP NetDeviceType = "ipvtap" // VETHTAP is a veth-tap virtual networking device type. - VETHTAP = "vethtap" + VETHTAP NetDeviceType = "vethtap" // VFIO is a direct assigned PCI device or PCI VF - VFIO = "VFIO" + VFIO NetDeviceType = "VFIO" // VHOSTUSER is a vhost-user port (socket) - VHOSTUSER = "vhostuser" + VHOSTUSER NetDeviceType = "vhostuser" ) // QemuNetdevParam converts to the QEMU -netdev parameter notation @@ -634,7 +634,7 @@ const ( NoInterface BlockDeviceInterface = "none" // SCSI represents a SCSI block device interface. - SCSI = "scsi" + SCSI BlockDeviceInterface = "scsi" ) const ( @@ -642,7 +642,7 @@ const ( Threads BlockDeviceAIO = "threads" // Native is the pthread asynchronous I/O implementation. - Native = "native" + Native BlockDeviceAIO = "native" ) const ( @@ -967,7 +967,9 @@ type VSOCKDevice struct { const ( // MinimalGuestCID is the smallest valid context ID for a guest. MinimalGuestCID uint32 = 3 +) +const ( // VhostVSOCKPCI is the VSOCK vhost device type. VhostVSOCKPCI = "vhost-vsock-pci" @@ -1010,7 +1012,7 @@ const ( UTC RTCBaseType = "utc" // LocalTime is the local base time for qemu RTC. - LocalTime = "localtime" + LocalTime RTCBaseType = "localtime" ) const ( @@ -1018,7 +1020,7 @@ const ( Host RTCClock = "host" // VM is for using the guest clock as a reference - VM = "vm" + VM RTCClock = "vm" ) const ( @@ -1026,7 +1028,7 @@ const ( Slew RTCDriftFix = "slew" // NoDriftFix means we don't want/need to fix qemu's RTC drift. - NoDriftFix = "none" + NoDriftFix RTCDriftFix = "none" ) // RTC represents a qemu Real Time Clock configuration. diff --git a/qemu/qmp.go b/qemu/qmp.go index 37334e99e2..814a7dd50b 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -415,7 +415,7 @@ func (q *QMP) mainLoop() { close(q.disconnectedCh) }() - version := []byte{} + var version []byte var cmdDoneCh <-chan struct{} DONE: From 430e72c63b835038a7471e9d909f5b68faad173d Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 28 Jun 2018 15:56:27 +0100 Subject: [PATCH 110/264] qemu,qmp: Enable gas security checker This commit enables the gas security checker on govmm builds. The security checker has signalled 4 issues all of which I've checked and have determined to be non issues. These issues are disabled by this commit. Signed-off-by: Mark Ryan --- .travis.yml | 2 +- qemu/image.go | 2 ++ qemu/qemu.go | 1 + qemu/qmp.go | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fb5bc44199..f23679412b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,4 @@ before_install: script: - go env - $GOPATH/bin/goveralls -v -service=travis-ci - - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck ./... + - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... diff --git a/qemu/image.go b/qemu/image.go index 4f064c681b..352659adbb 100644 --- a/qemu/image.go +++ b/qemu/image.go @@ -45,9 +45,11 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, userDataPath := path.Join(dataDirPath, "user_data") defer func() { + /* #nosec */ _ = os.RemoveAll(configDrivePath) }() + /* #nosec */ err := os.MkdirAll(dataDirPath, 0755) if err != nil { return fmt.Errorf("Unable to create config drive directory %s : %v", diff --git a/qemu/qemu.go b/qemu/qemu.go index 831608af33..d146610949 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1652,6 +1652,7 @@ func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []* path = "qemu-system-x86_64" } + /* #nosec */ cmd := exec.Command(path, params...) if len(fds) > 0 { logger.Infof("Adding extra file %v", fds) diff --git a/qemu/qmp.go b/qemu/qmp.go index 814a7dd50b..a66ef29303 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -409,6 +409,7 @@ func (q *QMP) mainLoop() { if q.cfg.EventCh != nil { close(q.cfg.EventCh) } + /* #nosec */ _ = q.conn.Close() _ = <-fromVMCh failOutstandingCommands(cmdQueue) From 4ca232ecdf15216bb26484911b54f434dd6d09cc Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 28 Jun 2018 16:05:00 +0100 Subject: [PATCH 111/264] qmp_test: Fix Warning and Error level logs This commit fixes an issue with the log handlers defined by qmp_test. The issue was picked up by the latest version of go vet on go tip. qemu/qmp_test.go:56::error: missing ... in args forwarded to printf-like function (vet) qemu/qmp_test.go:60::error: missing ... in args forwarded to printf-like function (vet) Signed-off-by: Mark Ryan --- qemu/qmp_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index f5b472d741..2abb196c04 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -53,11 +53,11 @@ func (l qmpTestLogger) Infof(format string, v ...interface{}) { } func (l qmpTestLogger) Warningf(format string, v ...interface{}) { - l.Infof(format, v) + l.Infof(format, v...) } func (l qmpTestLogger) Errorf(format string, v ...interface{}) { - l.Infof(format, v) + l.Infof(format, v...) } type qmpTestCommand struct { From f700a97beef89be2d759a1616a7364e92bc8ec77 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 13 Jul 2018 12:05:19 -0500 Subject: [PATCH 112/264] qemu/qmp: implement function to hotplug vsock-pci Implement function to hotplug vsocks, vsocks are needed to communicate processes are running inside the VM with processes are running on the host. Signed-off-by: Julio Montes --- qemu/qemu.go | 3 +++ qemu/qmp.go | 10 ++++++++++ qemu/qmp_test.go | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 41381d80ee..e911364846 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -82,6 +82,9 @@ const ( // VirtioSerialPort is the serial port device driver. VirtioSerialPort = "virtserialport" + + // VHostVSockPCI is the vhost vsock pci driver. + VHostVSockPCI = "vhost-vsock-pci" ) // ObjectType is a string representing a qemu object type. diff --git a/qemu/qmp.go b/qemu/qmp.go index 37334e99e2..32a59ef04b 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -881,3 +881,13 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string return err } + +// ExecutePCIVSockAdd adds a vhost-vsock-pci bus +func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID string) error { + args := map[string]interface{}{ + "driver": VHostVSockPCI, + "id": id, + "guest-cid": guestCID, + } + return q.executeCommand(ctx, "device_add", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index f5b472d741..c87822ae8e 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -945,3 +945,20 @@ func TestExecHotplugMemory(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks vsock-pci hotplug +func TestExecutePCIVSockAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From 3830b4419f4ddee5de05dc89e6d934e345837e76 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 24 Jul 2018 13:37:28 -0500 Subject: [PATCH 113/264] qemu: add vhostfd and disable-modern to vhost-vsock-pci `vhostfd` is the vhost file descriptor that holds the socket context ID `disable-modern` prevents qemu from relying on fast MMIO Signed-off-by: Julio Montes --- qemu/qemu.go | 20 ++++++++++++++++++-- qemu/qemu_test.go | 14 +++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index e911364846..4f7a383ee1 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -965,6 +965,12 @@ type VSOCKDevice struct { ID string ContextID uint32 + + // VHostFD vhost file descriptor that holds the ContextID + VHostFD *os.File + + // DisableModern prevents qemu from relying on fast MMIO. + DisableModern bool } const ( @@ -989,12 +995,22 @@ func (vsock VSOCKDevice) Valid() bool { // QemuParams returns the qemu parameters built out of the VSOCK device. func (vsock VSOCKDevice) QemuParams(config *Config) []string { + var deviceParams []string var qemuParams []string - deviceParam := fmt.Sprintf("%s,id=%s,%s=%d", VhostVSOCKPCI, vsock.ID, VSOCKGuestCID, vsock.ContextID) + deviceParams = append(deviceParams, fmt.Sprintf("%s", VhostVSOCKPCI)) + if vsock.DisableModern { + deviceParams = append(deviceParams, ",disable-modern=true") + } + if vsock.VHostFD != nil { + qemuFDs := config.appendFDs([]*os.File{vsock.VHostFD}) + deviceParams = append(deviceParams, fmt.Sprintf(",vhostfd=%d", qemuFDs[0])) + } + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID)) + deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID)) qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, deviceParam) + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) return qemuParams } diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 14637910d0..be9657b0eb 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -329,12 +329,14 @@ func TestAppendDeviceVFIO(t *testing.T) { testAppend(vfioDevice, deviceVFIOString, t) } -var deviceVSOCKString = "-device vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=4" +var deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4" func TestAppendVSOCK(t *testing.T) { vsockDevice := VSOCKDevice{ - ID: "vhost-vsock-pci0", - ContextID: 4, + ID: "vhost-vsock-pci0", + ContextID: 4, + VHostFD: nil, + DisableModern: true, } testAppend(vsockDevice, deviceVSOCKString, t) @@ -342,8 +344,10 @@ func TestAppendVSOCK(t *testing.T) { func TestVSOCKValid(t *testing.T) { vsockDevice := VSOCKDevice{ - ID: "vhost-vsock-pci0", - ContextID: MinimalGuestCID - 1, + ID: "vhost-vsock-pci0", + ContextID: MinimalGuestCID - 1, + VHostFD: nil, + DisableModern: true, } if vsockDevice.Valid() { From 12dfa872936de1f8cd003238711a044b37be9b59 Mon Sep 17 00:00:00 2001 From: Ruidong Cao Date: Tue, 24 Jul 2018 17:06:22 +0800 Subject: [PATCH 114/264] qemu/qmp: implement function for hotplug network Implement function to hotplug and delete a network device to QEMU Signed-off-by: Ruidong Cao --- qemu/qmp.go | 45 ++++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 37334e99e2..c4e9f7356a 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -727,6 +727,51 @@ func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error { return q.executeCommand(ctx, "x-blockdev-del", args, nil) } +// ExecuteNetdevAdd adds a Net device to a QEMU instance +// using the netdev_add command. netdevID is the id of the device to add. +// Must be valid QMP identifier. +func (q *QMP) ExecuteNetdevAdd(ctx context.Context, netdevType, netdevID, ifname, downscript, script string, queues int) error { + args := map[string]interface{}{ + "type": netdevType, + "id": netdevID, + "ifname": ifname, + "downscript": downscript, + "script": script, + } + if queues > 1 { + args["queues"] = queues + } + + return q.executeCommand(ctx, "netdev_add", args, nil) +} + +// ExecuteNetdevDel deletes a Net device from a QEMU instance +// using the netdev_del command. netdevID is the id of the device to delete. +func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error { + args := map[string]interface{}{ + "id": netdevID, + } + return q.executeCommand(ctx, "netdev_del", args, nil) +} + +// ExecuteNetPCIDeviceAdd adds a Net PCI device to a QEMU instance +// using the device_add command. devID is the id of the device to add. +// Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. +func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus string) error { + args := map[string]interface{}{ + "id": devID, + "driver": VirtioNetPCI, + "netdev": netdevID, + "mac": macAddr, + "addr": addr, + } + + if bus != "" { + args["bus"] = bus + } + return q.executeCommand(ctx, "device_add", args, nil) +} + // ExecuteDeviceDel deletes guest portion of a QEMU device by sending a // device_del command. devId is the identifier of the device to delete. // Typically it would match the devID parameter passed to an earlier call diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index f5b472d741..72ef08ddfa 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -364,6 +364,66 @@ func TestQMPBlockdevAdd(t *testing.T) { <-disconnectedCh } +// Checks that the netdev_add command is correctly sent. +// +// We start a QMPLoop, send the netdev_add command and stop the loop. +// +// The netdev_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPNetdevAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("netdev_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + q.version = checkVersion(t, connectedCh) + err := q.ExecuteNetdevAdd(context.Background(), "tap", "br0", "tap0", "no", "no", 8) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that the netdev_del command is correctly sent. +// +// We start a QMPLoop, send the netdev_del command and stop the loop. +// +// The netdev_del command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPNetdevDel(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("netdev_del", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + q.version = checkVersion(t, connectedCh) + err := q.ExecuteNetdevDel(context.Background(), "br0") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + +func TestQMPNetPCIDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the device_add command is correctly sent. // // We start a QMPLoop, send the device_add command and stop the loop. From 84b212f1b85711e969eee035884de8787aebffd8 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 20 Jul 2018 15:58:31 -0500 Subject: [PATCH 115/264] qemu: add vhostfd and disable-modern to vsock hotplug `vhostfd` is used to specify the vhost-vsock device fd, and it holds the context ID previously opened. `disable-modern` is to disable the use of "modern" devices, by using virtio 0.9 instead of virtio 1.0. Particularly, this is useful when running the VM in a nested environment. Signed-off-by: Julio Montes --- qemu/qmp.go | 8 +++++++- qemu/qmp_test.go | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 32a59ef04b..61788e0fa2 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -883,11 +883,17 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string } // ExecutePCIVSockAdd adds a vhost-vsock-pci bus -func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID string) error { +func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd string, disableModern bool) error { args := map[string]interface{}{ "driver": VHostVSockPCI, "id": id, "guest-cid": guestCID, + "vhostfd": vhostfd, } + + if disableModern { + args["disable-modern"] = disableModern + } + return q.executeCommand(ctx, "device_add", args, nil) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index c87822ae8e..5b238648db 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -955,7 +955,7 @@ func TestExecutePCIVSockAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3") + err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3", "1", true) if err != nil { t.Fatalf("Unexpected error %v", err) } From 03f1a1c3a8a3b10a389c99aa1b37b6b772463420 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 30 Jul 2018 15:07:11 -0500 Subject: [PATCH 116/264] qemu/qmp: implement getfd `getfd` receives a file descriptor via SCM rights and assign it a name, this command is useful to send file descriptors from the host, and then hot plug devices that needs file descriptors like vhost-vsock-pci devices. Signed-off-by: Julio Montes --- qemu/qmp.go | 28 ++++++++++++++++++++++++---- qemu/qmp_test.go | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 61788e0fa2..3913e9cf79 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -24,6 +24,8 @@ import ( "fmt" "io" "net" + "os" + "syscall" "time" "context" @@ -118,6 +120,7 @@ type qmpCommand struct { args map[string]interface{} filter *qmpEventFilter resultReceived bool + oob []byte } // QMP is a structure that contains the internal state used by startQMPLoop and @@ -302,7 +305,12 @@ func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { } q.cfg.Logger.Infof("%s", string(encodedCmd)) encodedCmd = append(encodedCmd, '\n') - _, err = q.conn.Write(encodedCmd) + if unixConn, ok := q.conn.(*net.UnixConn); ok && len(cmd.oob) > 0 { + _, _, err = unixConn.WriteMsgUnix(encodedCmd, cmd.oob, nil) + } else { + _, err = q.conn.Write(encodedCmd) + } + if err != nil { cmd.res <- qmpResult{ err: fmt.Errorf("Unable to write command to qmp socket %v", err), @@ -485,7 +493,7 @@ func startQMPLoop(conn io.ReadWriteCloser, cfg QMPConfig, } func (q *QMP) executeCommandWithResponse(ctx context.Context, name string, args map[string]interface{}, - filter *qmpEventFilter) (interface{}, error) { + oob []byte, filter *qmpEventFilter) (interface{}, error) { var err error var response interface{} resCh := make(chan qmpResult) @@ -498,6 +506,7 @@ func (q *QMP) executeCommandWithResponse(ctx context.Context, name string, args name: name, args: args, filter: filter, + oob: oob, }: } @@ -519,7 +528,7 @@ func (q *QMP) executeCommandWithResponse(ctx context.Context, name string, args func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]interface{}, filter *qmpEventFilter) error { - _, err := q.executeCommandWithResponse(ctx, name, args, filter) + _, err := q.executeCommandWithResponse(ctx, name, args, nil, filter) return err } @@ -809,7 +818,7 @@ func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, // ExecuteQueryHotpluggableCPUs returns a slice with the list of hotpluggable CPUs func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableCPU, error) { - response, err := q.executeCommandWithResponse(ctx, "query-hotpluggable-cpus", nil, nil) + response, err := q.executeCommandWithResponse(ctx, "query-hotpluggable-cpus", nil, nil, nil) if err != nil { return nil, err } @@ -897,3 +906,14 @@ func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd stri return q.executeCommand(ctx, "device_add", args, nil) } + +// ExecuteGetFD sends a file descriptor via SCM rights and assigns it a name +func (q *QMP) ExecuteGetFD(ctx context.Context, fdname string, fd *os.File) error { + oob := syscall.UnixRights(int(fd.Fd())) + args := map[string]interface{}{ + "fdname": fdname, + } + + _, err := q.executeCommandWithResponse(ctx, "getfd", args, oob, nil) + return err +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 5b238648db..4153879240 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "log" + "os" "reflect" "sync" "testing" @@ -962,3 +963,20 @@ func TestExecutePCIVSockAdd(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks getfd +func TestExecuteGetFdD(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("getfd", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteGetFD(context.Background(), "foo", os.NewFile(0, "foo")) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From ca46f21f3ffdbd23e8f4468a523d3c1cafa8d142 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 3 Aug 2018 08:01:05 -0500 Subject: [PATCH 117/264] qemu/qmp: implement function to hotplug character devices implement function to hotplug character devices using as backend unix sockets, binding a character device with a serial port allows the communnication between processes running in the guest with processes running in the host. Signed-off-by: Julio Montes --- qemu/qmp.go | 23 +++++++++++++++++++++++ qemu/qmp_test.go | 17 +++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 1e57aaa9e0..fccc0f56ae 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -963,3 +963,26 @@ func (q *QMP) ExecuteGetFD(ctx context.Context, fdname string, fd *os.File) erro _, err := q.executeCommandWithResponse(ctx, "getfd", args, oob, nil) return err } + +// ExecuteCharDevUnixSocketAdd adds a character device using as backend a unix socket, +// id is an identifier for the device, path specifies the local path of the unix socket, +// wait is to block waiting for a client to connect, server specifies that the socket is a listening socket. +func (q *QMP) ExecuteCharDevUnixSocketAdd(ctx context.Context, id, path string, wait, server bool) error { + args := map[string]interface{}{ + "id": id, + "backend": map[string]interface{}{ + "type": "socket", + "data": map[string]interface{}{ + "wait": wait, + "server": server, + "addr": map[string]interface{}{ + "type": "unix", + "data": map[string]interface{}{ + "path": path, + }, + }, + }, + }, + } + return q.executeCommand(ctx, "chardev-add", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 0ffdf5bc32..2f60db1abd 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1040,3 +1040,20 @@ func TestExecuteGetFdD(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks chardev-add unix socket +func TestExecuteCharDevUnixSocketAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("chardev-add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteCharDevUnixSocketAdd(context.Background(), "foo", "foo.sock", false, true) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From 80ed88edb120b5f70e748e5864c68e234a365d4a Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 3 Aug 2018 08:10:44 -0500 Subject: [PATCH 118/264] qemu/qmp: implement function to hotplug serial ports Implement function to hotplug virtio serial ports, the serial ports are visible in the guest at the directory /dev/virtio-ports. Signed-off-by: Julio Montes --- qemu/qmp.go | 14 ++++++++++++++ qemu/qmp_test.go | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index fccc0f56ae..40e4781a69 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -986,3 +986,17 @@ func (q *QMP) ExecuteCharDevUnixSocketAdd(ctx context.Context, id, path string, } return q.executeCommand(ctx, "chardev-add", args, nil) } + +// ExecuteVirtSerialPortAdd adds a virtserialport. +// id is an identifier for the virtserialport, name is a name for the virtserialport and +// it will be visible in the VM, chardev is the character device id previously added. +func (q *QMP) ExecuteVirtSerialPortAdd(ctx context.Context, id, name, chardev string) error { + args := map[string]interface{}{ + "driver": VirtioSerialPort, + "id": id, + "name": name, + "chardev": chardev, + } + + return q.executeCommand(ctx, "device_add", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 2f60db1abd..c6c4ed4f00 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1057,3 +1057,20 @@ func TestExecuteCharDevUnixSocketAdd(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks virtio serial port hotplug +func TestExecuteVirtSerialPortAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteVirtSerialPortAdd(context.Background(), "foo", "foo.channel", "foo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From 10efa84132bacc8fd3bf22880a6a1740f5027e18 Mon Sep 17 00:00:00 2001 From: Ruidong Cao Date: Tue, 7 Aug 2018 16:33:22 +0800 Subject: [PATCH 119/264] qemu/qmp: add function for hotplug network by fds Implement function to hotplug a network device to QEMU by fds. Macvtap can only be hotplug by this way. Signed-off-by: Ruidong Cao --- qemu/qmp.go | 18 ++++++++++++++++++ qemu/qmp_test.go | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 40e4781a69..ea6cb6454e 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -29,6 +29,7 @@ import ( "time" "context" + "strings" ) // QMPLog is a logging interface used by the qemu package to log various @@ -755,6 +756,23 @@ func (q *QMP) ExecuteNetdevAdd(ctx context.Context, netdevType, netdevID, ifname return q.executeCommand(ctx, "netdev_add", args, nil) } +// ExecuteNetdevAddByFds adds a Net device to a QEMU instance +// using the netdev_add command by fds and vhostfds. netdevID is the id of the device to add. +// Must be valid QMP identifier. +func (q *QMP) ExecuteNetdevAddByFds(ctx context.Context, netdevType, netdevID string, fdNames, vhostFdNames []string) error { + fdNameStr := strings.Join(fdNames, ":") + vhostFdNameStr := strings.Join(vhostFdNames, ":") + args := map[string]interface{}{ + "type": netdevType, + "id": netdevID, + "fds": fdNameStr, + "vhost": "on", + "vhostfds": vhostFdNameStr, + } + + return q.executeCommand(ctx, "netdev_add", args, nil) +} + // ExecuteNetdevDel deletes a Net device from a QEMU instance // using the netdev_del command. netdevID is the id of the device to delete. func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error { diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index c6c4ed4f00..fe3d0b5b72 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -387,6 +387,28 @@ func TestQMPNetdevAdd(t *testing.T) { <-disconnectedCh } +// Checks that the netdev_add command with fds is correctly sent. +// +// We start a QMPLoop, send the netdev_add command with fds and stop the loop. +// +// The netdev_add command with fds should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPNetdevAddByFds(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("netdev_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + q.version = checkVersion(t, connectedCh) + err := q.ExecuteNetdevAddByFds(context.Background(), "tap", "br0", nil, nil) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the netdev_del command is correctly sent. // // We start a QMPLoop, send the netdev_del command and stop the loop. From 685199980d27f7aaddb30fdb44f01ee1417b9ec6 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 7 Aug 2018 13:33:13 -0500 Subject: [PATCH 120/264] qemu/qmp: add addr and bus to hotplug vsock devices For machines types based on PCIe like q35, device addr and bus must be specified. For machines types based on PCI like pc, device addr must be specified and bus is optional since devices can be hot plugged directly on the root bus. Signed-off-by: Julio Montes --- qemu/qmp.go | 7 ++++++- qemu/qmp_test.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 40e4781a69..22a59d91da 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -938,12 +938,17 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string } // ExecutePCIVSockAdd adds a vhost-vsock-pci bus -func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd string, disableModern bool) error { +func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus string, disableModern bool) error { args := map[string]interface{}{ "driver": VHostVSockPCI, "id": id, "guest-cid": guestCID, "vhostfd": vhostfd, + "addr": addr, + } + + if bus != "" { + args["bus"] = bus } if disableModern { diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index c6c4ed4f00..3abcf9649b 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1016,7 +1016,7 @@ func TestExecutePCIVSockAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3", "1", true) + err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3", "1", "1", "1", true) if err != nil { t.Fatalf("Unexpected error %v", err) } From 4461c459a3465e06e07e9e31d4466f5948b640d8 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Mon, 6 Aug 2018 17:29:39 -0700 Subject: [PATCH 121/264] disk: Add --share-rw option for hotplugging disks With qemu 2.10, a write lock was added for qcow images that prevents the same image to be passed more than once. This can be over-ridden using the --share-rw option which is desired for raw images. This solves an issue with running Kata with devicemapper using the privileged mode as in this case all devices on the host are passed to the container using the block device associated with the rootfs, causing it to be passed twice to qemu. Signed-off-by: Archana Shinde --- qemu/qmp.go | 23 ++++++++++++++++++----- qemu/qmp_test.go | 12 ++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 40e4781a69..efa672b61a 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -670,7 +670,8 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) // to a previous call to ExecuteBlockdevAdd. devID is the id of the device to // add. Both strings must be valid QMP identifiers. driver is the name of the // driver,e.g., virtio-blk-pci, and bus is the name of the bus. bus is optional. -func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string) error { +// shared denotes if the drive can be shared allowing it to be passed more than once. +func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string, shared bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -679,6 +680,9 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b if bus != "" { args["bus"] = bus } + if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { + args["share-rw"] = "on" + } return q.executeCommand(ctx, "device_add", args, nil) } @@ -688,8 +692,9 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b // the device to add. Both strings must be valid QMP identifiers. driver is the name of the // scsi driver,e.g., scsi-hd, and bus is the name of a SCSI controller bus. // scsiID is the SCSI id, lun is logical unit number. scsiID and lun are optional, a negative value -// for scsiID and lun is ignored. -func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string, scsiID, lun int) error { +// for scsiID and lun is ignored. shared denotes if the drive can be shared allowing it +// to be passed more than once. +func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string, scsiID, lun int, shared bool) error { // TBD: Add drivers for scsi passthrough like scsi-generic and scsi-block drivers := []string{"scsi-hd", "scsi-cd", "scsi-disk"} @@ -717,6 +722,9 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive if lun >= 0 { args["lun"] = lun } + if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { + args["share-rw"] = "on" + } return q.executeCommand(ctx, "device_add", args, nil) } @@ -802,8 +810,9 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { // ExecutePCIDeviceAdd is the PCI version of ExecuteDeviceAdd. This function can be used // to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the -// device address on its parent bus. bus is optional. -func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus string) error { +// device address on its parent bus. bus is optional. shared denotes if the drive can be shared +// allowing it to be passed more than once. +func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus string, shared bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -813,6 +822,10 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver if bus != "" { args["bus"] = bus } + if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { + args["share-rw"] = "on" + } + return q.executeCommand(ctx, "device_add", args, nil) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index c6c4ed4f00..4ebda65d6d 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -438,11 +438,11 @@ func TestQMPDeviceAdd(t *testing.T) { buf.AddCommand("device_add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) - checkVersion(t, connectedCh) + q.version = checkVersion(t, connectedCh) blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "") + "virtio-blk-pci", "", true) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -463,11 +463,11 @@ func TestQMPSCSIDeviceAdd(t *testing.T) { buf.AddCommand("device_add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) - checkVersion(t, connectedCh) + q.version = checkVersion(t, connectedCh) blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteSCSIDeviceAdd(context.Background(), blockdevID, devID, - "scsi-hd", "scsi0.0", 1, 2) + "scsi-hd", "scsi0.0", 1, 2, true) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -881,11 +881,11 @@ func TestQMPPCIDeviceAdd(t *testing.T) { buf.AddCommand("device_add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) - checkVersion(t, connectedCh) + q.version = checkVersion(t, connectedCh) blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "0x1", "") + "virtio-blk-pci", "0x1", "", true) if err != nil { t.Fatalf("Unexpected error %v", err) } From fcaf61dcb1ee8512a043fbc38b0bdc57c7d1bd38 Mon Sep 17 00:00:00 2001 From: Zhao Xinda Date: Fri, 10 Aug 2018 12:43:22 +0800 Subject: [PATCH 122/264] qemu/qmp: add vfio mediated device support In addition to normal VFIO device, this patch adds VFIO mediated device as a supplement to do hot plug on PCI(E) bridges. Signed-off-by: Zhao Xinda --- qemu/qmp.go | 18 ++++++++++++++++++ qemu/qmp_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index e5dbb9318c..002ea42cf4 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -837,6 +837,24 @@ func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus return q.executeCommand(ctx, "device_add", args, nil) } +// ExecutePCIVFIOMediatedDeviceAdd adds a VFIO mediated device to a QEMU instance using the device_add command. +// This function can be used to hot plug VFIO mediated devices on PCI(E) bridges, unlike +// ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. +// bus is optional. devID is the id of the device to add. Must be valid QMP identifier. sysfsdev is the VFIO +// mediated device. +func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus string) error { + args := map[string]interface{}{ + "id": devID, + "driver": "vfio-pci", + "sysfsdev": sysfsdev, + "addr": addr, + } + if bus != "" { + args["bus"] = bus + } + return q.executeCommand(ctx, "device_add", args, nil) +} + // ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command. // driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within // node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 3733918e1e..8e1bce3815 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -892,6 +892,30 @@ func TestQMPPCIDeviceAdd(t *testing.T) { <-disconnectedCh } +// Checks that PCI VFIO mediated devices are correctly added using device_add. +// +// We start a QMPLoop, send the device_add command and stop the loop. +// +// The device_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPPCIVFIOMediatedDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + sysfsDev := "/sys/bus/pci/devices/0000:00:02.0/a297db4a-f4c2-11e6-90f6-d3b88d6c9525" + devID := fmt.Sprintf("device_%s", volumeUUID) + err := q.ExecutePCIVFIOMediatedDeviceAdd(context.Background(), devID, sysfsDev, "0x1", "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that CPU are correctly added using device_add func TestQMPCPUDeviceAdd(t *testing.T) { connectedCh := make(chan *QMPVersion) From e46092e03ac049660d2eb5bf0c4e3cb7ed2ce03d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 10 Aug 2018 14:54:22 +0100 Subject: [PATCH 123/264] qemu: Do not try and generate invalid RTC parameters If no RTC is specified in the config then do not generate any RTC command line options. RTC command line options are optional for QEMU so make Valid() return false when presented with the empty version of the RTC struct containing empty strings. Signed-off-by: Rob Bradford --- qemu/qemu.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 31254231d2..1337b8af25 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1064,16 +1064,12 @@ type RTC struct { // 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.Clock != Host && rtc.Clock != VM { + return false } - if rtc.DriftFix != "" { - if rtc.DriftFix != Slew && rtc.DriftFix != NoDriftFix { - return false - } + if rtc.DriftFix != Slew && rtc.DriftFix != NoDriftFix { + return false } return true From 2706a07be5fc3be655e0ac1d3c38b52ce347140c Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Aug 2018 15:01:08 +0100 Subject: [PATCH 124/264] qemu: Use the supplied context.Context for launching This will kill the process when the context is cancelled. As using a nil context is not permitted it is necessary to substitute with a real context if it is not initialised in the Config struct. Signed-off-by: Rob Bradford --- qemu/qemu.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 1337b8af25..d2f27f51dc 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1235,7 +1235,7 @@ type Config struct { // Path is the qemu binary path. Path string - // Ctx is not used at the moment. + // Ctx is the context used when launching qemu. Ctx context.Context // Name is the qemu guest name @@ -1632,7 +1632,12 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { return "", err } - return LaunchCustomQemu(config.Ctx, config.Path, config.qemuParams, + ctx := config.Ctx + if ctx == nil { + ctx = context.Background() + } + + return LaunchCustomQemu(ctx, config.Path, config.qemuParams, config.fds, nil, logger) } @@ -1640,10 +1645,6 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { // // 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. The attrs parameter can be used to control aspects of the @@ -1668,7 +1669,7 @@ func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []* } /* #nosec */ - cmd := exec.Command(path, params...) + cmd := exec.CommandContext(ctx, path, params...) if len(fds) > 0 { logger.Infof("Adding extra file %v", fds) cmd.ExtraFiles = fds From 17cacc72387012762c1f9d56c85833f1f3581123 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 20 Aug 2018 14:45:03 +0100 Subject: [PATCH 125/264] Add negative test cases for qemu.go This commit adds some negative test cases for the append functions in qemu.go that build up the qemu command line. Signed-off-by: Mark Ryan --- qemu/qemu_test.go | 292 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index be9657b0eb..7fdaa9bffb 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -688,3 +688,295 @@ func TestAppendIncomingExec(t *testing.T) { testAppend(source, incomingStringExec, t) } + +func TestBadName(t *testing.T) { + c := &Config{} + c.appendName() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadMachine(t *testing.T) { + c := &Config{} + c.appendMachine() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadCPUModel(t *testing.T) { + c := &Config{} + c.appendCPUModel() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadQMPSockets(t *testing.T) { + c := &Config{} + c.appendQMPSockets() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + QMPSockets: []QMPSocket{{}}, + } + + c.appendQMPSockets() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + QMPSockets: []QMPSocket{{Name: "test"}}, + } + + c.appendQMPSockets() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + QMPSockets: []QMPSocket{ + { + Name: "test", + Type: QMPSocketType("ip"), + }, + }, + } + + c.appendQMPSockets() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadDevices(t *testing.T) { + c := &Config{} + c.appendDevices() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + Devices: []Device{ + FSDevice{}, + FSDevice{ + ID: "id0", + MountTag: "tag", + }, + CharDevice{}, + CharDevice{ + ID: "id1", + }, + NetDevice{}, + NetDevice{ + ID: "id1", + IFName: "if", + Type: IPVTAP, + }, + SerialDevice{}, + SerialDevice{ + ID: "id0", + }, + BlockDevice{}, + BlockDevice{ + Driver: "drv", + ID: "id1", + }, + VhostUserDevice{}, + VhostUserDevice{ + CharDevID: "devid", + }, + VhostUserDevice{ + CharDevID: "devid", + SocketPath: "/var/run/sock", + }, + VhostUserDevice{ + CharDevID: "devid", + SocketPath: "/var/run/sock", + VhostUserType: VhostUserNet, + }, + VhostUserDevice{ + CharDevID: "devid", + SocketPath: "/var/run/sock", + VhostUserType: VhostUserSCSI, + }, + }, + } + + c.appendDevices() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadRTC(t *testing.T) { + c := &Config{} + c.appendRTC() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + RTC: RTC{ + Clock: RTCClock("invalid"), + }, + } + c.appendRTC() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + RTC: RTC{ + Clock: Host, + DriftFix: RTCDriftFix("invalid"), + }, + } + c.appendRTC() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadGlobalParam(t *testing.T) { + c := &Config{} + c.appendGlobalParam() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadVGA(t *testing.T) { + c := &Config{} + c.appendVGA() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadKernel(t *testing.T) { + c := &Config{} + c.appendKernel() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadMemoryKnobs(t *testing.T) { + c := &Config{} + c.appendMemoryKnobs() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + Knobs: Knobs{ + HugePages: true, + }, + } + c.appendMemoryKnobs() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + Knobs: Knobs{ + HugePages: true, + }, + } + c.appendMemoryKnobs() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + Knobs: Knobs{ + MemPrealloc: true, + }, + } + c.appendMemoryKnobs() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + Knobs: Knobs{ + FileBackedMem: true, + }, + Memory: Memory{ + Size: "1024", + }, + } + c.appendMemoryKnobs() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadKnobs(t *testing.T) { + c := &Config{ + Knobs: Knobs{ + Mlock: true, + }, + } + c.appendKnobs() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadBios(t *testing.T) { + c := &Config{} + c.appendBios() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadIOThreads(t *testing.T) { + c := &Config{} + c.appendIOThreads() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + IOThreads: []IOThread{{ID: ""}}, + } + c.appendIOThreads() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadIncoming(t *testing.T) { + c := &Config{} + c.appendIncoming() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestBadCPUs(t *testing.T) { + c := &Config{} + if err := c.appendCPUs(); err != nil { + t.Fatalf("No error expected got %v", err) + } + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + SMP: SMP{ + MaxCPUs: 1, + CPUs: 2, + }, + } + if c.appendCPUs() == nil { + t.Errorf("Error expected") + } +} From ed34f616641e6b90bbbf256b26f1feec11d88d98 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 20 Aug 2018 15:31:09 +0100 Subject: [PATCH 126/264] Add some negative test cases for qmp.go This commit adds a couple of negative test cases for qmp.go, one which checks that failed commands return errors and the other checks that QMPStart exits gracefully when passed an invalid socket path. Signed-off-by: Mark Ryan --- qemu/qmp_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 7a59edc6fa..d5718c11a4 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -251,6 +251,22 @@ func TestQMPStartStopLoop(t *testing.T) { <-disconnectedCh } +// Checks that a call to QMPStart with an invalid path exits gracefully. +// +// We call QMPStart with an invalid path. +// +// An error should be returned and the disconnected channel should be closed. +func TestQMPStartBadPath(t *testing.T) { + cfg := QMPConfig{Logger: qmpTestLogger{}} + disconnectedCh := make(chan struct{}) + q, _, err := QMPStart(context.Background(), "", cfg, disconnectedCh) + if err == nil { + t.Errorf("Expected error") + q.Shutdown() + } + <-disconnectedCh +} + // Checks that the qmp_capabilities command is correctly sent. // // We start a QMPLoop, send the qmp_capabilities command and stop the @@ -274,6 +290,28 @@ func TestQMPCapabilities(t *testing.T) { <-disconnectedCh } +// Checks that an error returned by a QMP command is correctly handled. +// +// We start a QMPLoop, send the qmp_capabilities command and stop the +// loop. +// +// The qmp_capabilities command fails and yet we should exit gracefully. +func TestQMPBadCapabilities(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("qmp_capabilities", nil, "error", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteQMPCapabilities(context.Background()) + if err == nil { + t.Fatalf("Expected error") + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the stop command is correctly sent. // // We start a QMPLoop, send the stop command and stop the From 21504d31ff612b0939daf7063917b49f0bfc552d Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 21 Aug 2018 10:51:14 -0700 Subject: [PATCH 127/264] qemu/qmp: Add netdev_add with chardev support In order to be able to hotplug network devices such as vhost user net, we need to be able to define a previously declared chardev as a parameter of this new network device. Signed-off-by: Sebastien Boeuf --- qemu/qmp.go | 16 ++++++++++++++++ qemu/qmp_test.go | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 92000fe03f..264601d7cb 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -764,6 +764,22 @@ func (q *QMP) ExecuteNetdevAdd(ctx context.Context, netdevType, netdevID, ifname return q.executeCommand(ctx, "netdev_add", args, nil) } +// ExecuteNetdevChardevAdd adds a Net device to a QEMU instance +// using the netdev_add command. netdevID is the id of the device to add. +// Must be valid QMP identifier. +func (q *QMP) ExecuteNetdevChardevAdd(ctx context.Context, netdevType, netdevID, chardev string, queues int) error { + args := map[string]interface{}{ + "type": netdevType, + "id": netdevID, + "chardev": chardev, + } + if queues > 1 { + args["queues"] = queues + } + + return q.executeCommand(ctx, "netdev_add", args, nil) +} + // ExecuteNetdevAddByFds adds a Net device to a QEMU instance // using the netdev_add command by fds and vhostfds. netdevID is the id of the device to add. // Must be valid QMP identifier. diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index d5718c11a4..47ab3fd924 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -425,6 +425,28 @@ func TestQMPNetdevAdd(t *testing.T) { <-disconnectedCh } +// Checks that the netdev_add command is correctly sent. +// +// We start a QMPLoop, send the netdev_add command and stop the loop. +// +// The netdev_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPNetdevChardevAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("netdev_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + q.version = checkVersion(t, connectedCh) + err := q.ExecuteNetdevChardevAdd(context.Background(), "tap", "br0", "chr0", 8) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the netdev_add command with fds is correctly sent. // // We start a QMPLoop, send the netdev_add command with fds and stop the loop. From 8515ae4817f6aad7ffbe68d078884c1689bf3e1e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 22 Aug 2018 17:07:21 +0100 Subject: [PATCH 128/264] qmp: Remind users that you must first call ExecuteQMPCapabilities() Before calling any other command it is necessary to call ExecuteQMPCapabilities() otherwise QEMU will not process the subsequent QMP commands. Signed-off-by: Rob Bradford --- qemu/qmp.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 264601d7cb..c78d1ae6a0 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -561,6 +561,10 @@ func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]i // block until they have received a success or failure message from QMP, // i.e., {"return": {}} or {"error":{}}, and in some cases certain events // are received. +// +// QEMU currently requires that the "qmp_capabilties" command is sent before any +// other command. Therefore you must call qmp.ExecuteQMPCapabilities() before +// you execute any other command. func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh chan struct{}) (*QMP, *QMPVersion, error) { if cfg.Logger == nil { cfg.Logger = qmpNullLogger{} From 0286ff9e6eaabe9294d697c0cd45ce69d1f2928d Mon Sep 17 00:00:00 2001 From: Ruidong Cao Date: Tue, 21 Aug 2018 18:48:42 +0800 Subject: [PATCH 129/264] qemu/qmp: support hotplug a nic whose qdisc is mq If we hotplug a nic with args mq=on, its qdisc will be mq by default. This aligns with cold plug nics. Signed-off-by: Ruidong Cao --- qemu/qmp.go | 15 ++++++++++++++- qemu/qmp_test.go | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 92000fe03f..08c9de11ae 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -793,7 +793,8 @@ func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error { // ExecuteNetPCIDeviceAdd adds a Net PCI device to a QEMU instance // using the device_add command. devID is the id of the device to add. // Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. -func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus string) error { +// queues is the number of queues of a nic. +func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus string, queues int) error { args := map[string]interface{}{ "id": devID, "driver": VirtioNetPCI, @@ -805,6 +806,18 @@ func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAd if bus != "" { args["bus"] = bus } + + if queues > 0 { + // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) + // -device virtio-net-pci,mq=on,vectors=2N+2... + // enable mq in guest by 'ethtool -L eth0 combined $queue_num' + // Clearlinux automatically sets up the queues properly + // The agent implementation should do this to ensure that it is + // always set + args["mq"] = "on" + args["vectors"] = 2*queues + 2 + } + return q.executeCommand(ctx, "device_add", args, nil) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 7a59edc6fa..2d1da0a092 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -439,7 +439,7 @@ func TestQMPNetPCIDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "") + err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", 8) if err != nil { t.Fatalf("Unexpected error %v", err) } From ce070d11f7abbf8c7651274d13b0295f2fa38ae5 Mon Sep 17 00:00:00 2001 From: flyflypeng Date: Fri, 24 Aug 2018 22:56:27 +0800 Subject: [PATCH 130/264] govmm: modify govmm to be compatible with qemu 2.8 govmm has ExecuteBlockdevAdd() function and ExecuteBlockdevDel() function doesn't compatible with qemu 2.8,because blockdev-add and x-blockdev-del usages are different between qemu 2.7 and qemu 2.8 Follow the qemu 2.7 and qemu 2.8 qmp-commands.txt documents to modify ExecuteBlockdevAdd() function and ExecuteBlockdevDel() function to be compatible with qemu 2.8 Signed-off-by: flyflypeng --- qemu/qmp.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 3f468d8817..58b8924fda 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -657,7 +657,7 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) }, } - if q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 9) { + if q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 8) { blockdevArgs["node-name"] = blockdevID args = blockdevArgs } else { @@ -746,7 +746,12 @@ func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error { return q.executeCommand(ctx, "blockdev-del", args, nil) } - args["id"] = blockdevID + if q.version.Major == 2 && q.version.Minor == 8 { + args["node-name"] = blockdevID + } else { + args["id"] = blockdevID + } + return q.executeCommand(ctx, "x-blockdev-del", args, nil) } From b16291cfabb128994d3dd59e2f32f4a9418732e0 Mon Sep 17 00:00:00 2001 From: Clare Chen Date: Tue, 28 Aug 2018 22:18:51 -0400 Subject: [PATCH 131/264] qemu/qmp: support query-memory-devices qmp command. Implement query qemu memory devices function and testcase. Signed-off-by: Clare Chen --- qemu/qmp.go | 40 ++++++++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 58b8924fda..addf4b2fa6 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -160,6 +160,24 @@ type HotpluggableCPU struct { QOMPath string `json:"qom-path"` } +// MemoryDevicesData cotains the data describes a memory device +type MemoryDevicesData struct { + Slot int `json:"slot"` + Node int `json:"node"` + Addr uint64 `json:"addr"` + Memdev string `json:"memdev"` + ID string `json:"id"` + Hotpluggable bool `json:"hotpluggable"` + Hotplugged bool `json:"hotplugged"` + Size uint64 `json:"size"` +} + +// MemoryDevices represents memory devices of vm +type MemoryDevices struct { + Data MemoryDevicesData `json:"data"` + Type string `json:"type"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) for scanner.Scan() { @@ -989,6 +1007,28 @@ func (q *QMP) ExecSetMigrateArguments(ctx context.Context, url string) error { return q.executeCommand(ctx, "migrate", args, nil) } +// ExecQueryMemoryDevices returns a slice with the list of memory devices +func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, error) { + response, err := q.executeCommandWithResponse(ctx, "query-memory-devices", nil, nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + } + + var memoryDevices []MemoryDevices + // convert json to []MemoryDevices + if err = json.Unmarshal(data, &memoryDevices); err != nil { + return nil, fmt.Errorf("unable to convert json to memory devices: %v", err) + } + + return memoryDevices, nil +} + // ExecHotplugMemory adds size of MiB memory to the guest func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int) error { args := map[string]interface{}{ diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 7d0b40ec2f..07c6126fe5 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1055,6 +1055,42 @@ func TestQMPExecuteQueryHotpluggableCPUs(t *testing.T) { <-disconnectedCh } +// Checks that memory devices are listed correctly +func TestQMPExecuteQueryMemoryDevices(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + memoryDevices := MemoryDevices{ + Type: "dimm", + Data: MemoryDevicesData{ + Slot: 1, + Node: 0, + Addr: 1234, + Memdev: "dimm1", + ID: "mem1", + Hotpluggable: true, + Hotplugged: false, + Size: 1234, + }, + } + buf.AddCommand("query-memory-devices", nil, "return", []interface{}{memoryDevices}) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + memDevices, err := q.ExecQueryMemoryDevices(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(memDevices) != 1 { + t.Fatalf("Expected memory devices length equals to 1\n") + } + if reflect.DeepEqual(memDevices[0], memoryDevices) == false { + t.Fatalf("Expected %v equals to %v\n", memDevices[0], memoryDevices) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that migrate capabilities can be set func TestExecSetMigrationCaps(t *testing.T) { connectedCh := make(chan *QMPVersion) From 6c3d84ea8cdf61d1ae87266d12909330ad953cba Mon Sep 17 00:00:00 2001 From: Jose Carlos Venegas Munoz Date: Fri, 7 Sep 2018 12:07:36 -0500 Subject: [PATCH 132/264] qemu: Add virtio RNG device. Add support for virtio-rng divice. Signed-off-by: Jose Carlos Venegas Munoz --- qemu/qemu.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index d2f27f51dc..c883de53a3 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -85,6 +85,9 @@ const ( // VHostVSockPCI is the vhost vsock pci driver. VHostVSockPCI DeviceDriver = "vhost-vsock-pci" + + // VirtioRng is the paravirtualized RNG device driver. + VirtioRng DeviceDriver = "virtio-rng" ) // ObjectType is a string representing a qemu object type. @@ -1017,6 +1020,63 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { return qemuParams } +// RngDevice represents a random number generator device. +type RngDevice struct { + // ID is the device ID + ID string + // Filename is entropy source on the host + Filename string + // MaxBytes is the bytes allowed to guest to get from the host’s entropy per period + MaxBytes uint + // Period is duration of a read period in seconds + Period uint +} + +// Valid returns true if the RngDevice structure is valid and complete. +func (v RngDevice) Valid() bool { + if v.ID == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of the RngDevice. +func (v RngDevice) QemuParams(_ *Config) []string { + var qemuParams []string + + //-object rng-random,filename=/dev/hwrng,id=rng0 + var objectParams []string + //-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 + var deviceParams []string + + objectParams = append(objectParams, "rng-random") + objectParams = append(objectParams, "id="+v.ID) + + deviceParams = append(deviceParams, string(VirtioRng)) + deviceParams = append(deviceParams, "rng="+v.ID) + + if v.Filename != "" { + objectParams = append(objectParams, "filename="+v.Filename) + } + + if v.MaxBytes > 0 { + deviceParams = append(deviceParams, fmt.Sprintf("max-bytes=%d", v.MaxBytes)) + } + + if v.Period > 0 { + deviceParams = append(deviceParams, fmt.Sprintf("period=%d", v.Period)) + } + + qemuParams = append(qemuParams, "-object") + qemuParams = append(qemuParams, strings.Join(objectParams, ",")) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) + + return qemuParams +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 7fdaa9bffb..f8bdf34eb0 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -17,6 +17,7 @@ package qemu import ( + "fmt" "io/ioutil" "os" "strings" @@ -361,6 +362,48 @@ func TestVSOCKValid(t *testing.T) { } } +func TestAppendVirtioRng(t *testing.T) { + var objectString = "-object rng-random,id=rng0" + var deviceString = "-device " + string(VirtioRng) + ",rng=rng0" + rngDevice := RngDevice{ + ID: "rng0", + } + + testAppend(rngDevice, objectString+" "+deviceString, t) + + rngDevice.Filename = "/dev/urandom" + objectString += ",filename=" + rngDevice.Filename + + testAppend(rngDevice, objectString+" "+deviceString, t) + + rngDevice.MaxBytes = 20 + + deviceString += fmt.Sprintf(",max-bytes=%d", rngDevice.MaxBytes) + testAppend(rngDevice, objectString+" "+deviceString, t) + + rngDevice.Period = 500 + + deviceString += fmt.Sprintf(",period=%d", rngDevice.Period) + testAppend(rngDevice, objectString+" "+deviceString, t) + +} + +func TestVirtioRngValid(t *testing.T) { + rng := RngDevice{ + ID: "", + } + + if rng.Valid() { + t.Fatalf("rng should be not valid when ID is empty") + } + + rng.ID = "rng0" + if !rng.Valid() { + t.Fatalf("rng should be valid") + } + +} + var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo" var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1" From 1a1fee75e5a4230399e09bf25841549dd9f86846 Mon Sep 17 00:00:00 2001 From: Ruidong Cao Date: Mon, 10 Sep 2018 11:28:21 +0800 Subject: [PATCH 133/264] qemu/qmp: nic can works without vhost If host doesn't support vhost_net, we won't pass vhost="on" in QMP. Signed-off-by: Ruidong Cao --- qemu/qmp.go | 14 ++++++++------ qemu/qmp_test.go | 7 ++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index addf4b2fa6..e9fa65e52d 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -812,13 +812,15 @@ func (q *QMP) ExecuteNetdevChardevAdd(ctx context.Context, netdevType, netdevID, // Must be valid QMP identifier. func (q *QMP) ExecuteNetdevAddByFds(ctx context.Context, netdevType, netdevID string, fdNames, vhostFdNames []string) error { fdNameStr := strings.Join(fdNames, ":") - vhostFdNameStr := strings.Join(vhostFdNames, ":") args := map[string]interface{}{ - "type": netdevType, - "id": netdevID, - "fds": fdNameStr, - "vhost": "on", - "vhostfds": vhostFdNameStr, + "type": netdevType, + "id": netdevID, + "fds": fdNameStr, + } + if len(vhostFdNames) > 0 { + vhostFdNameStr := strings.Join(vhostFdNames, ":") + args["vhost"] = "on" + args["vhostfds"] = vhostFdNameStr } return q.executeCommand(ctx, "netdev_add", args, nil) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 07c6126fe5..a1831a863b 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -458,10 +458,15 @@ func TestQMPNetdevAddByFds(t *testing.T) { disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) buf.AddCommand("netdev_add", nil, "return", nil) + buf.AddCommand("netdev_add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) - err := q.ExecuteNetdevAddByFds(context.Background(), "tap", "br0", nil, nil) + err := q.ExecuteNetdevAddByFds(context.Background(), "tap", "br0", nil, []string{}) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + err = q.ExecuteNetdevAddByFds(context.Background(), "tap", "br1", nil, []string{"3"}) if err != nil { t.Fatalf("Unexpected error %v", err) } From de00d7a6817f579628648c65a1b1930bfc6bab85 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 13 Sep 2018 09:18:47 +0200 Subject: [PATCH 134/264] qemu/image: Reduce permissions of .iso creation dir The contents of .iso used to bootstrap VMs with cloudinit are initialised using a precreated, short-lived directory. The permissions on this directory were too lenient. This commit restricts access to this directory to the user and his/her group. Signed-off-by: Mark Ryan --- qemu/image.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qemu/image.go b/qemu/image.go index 352659adbb..b2f906f7cb 100644 --- a/qemu/image.go +++ b/qemu/image.go @@ -49,8 +49,7 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, _ = os.RemoveAll(configDrivePath) }() - /* #nosec */ - err := os.MkdirAll(dataDirPath, 0755) + err := os.MkdirAll(dataDirPath, 0750) if err != nil { return fmt.Errorf("Unable to create config drive directory %s : %v", dataDirPath, err) From de5d27888950aaf0090d1be96a26c5cce5987dde Mon Sep 17 00:00:00 2001 From: Zhao Xinda Date: Tue, 18 Sep 2018 15:54:53 +0800 Subject: [PATCH 135/264] qemu/qmp: add vfio mediated device support on root bus In addition to supporting hotplug for VFIO mediated device on PCI bridge, this patch adds hotplug functionality on root bus. When parameter bus and addr are set to be empty, the system will pick up an empty slot on root bus. Signed-off-by: Zhao Xinda --- qemu/qmp.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index e9fa65e52d..9f764f1236 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -937,20 +937,22 @@ func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus } // ExecutePCIVFIOMediatedDeviceAdd adds a VFIO mediated device to a QEMU instance using the device_add command. -// This function can be used to hot plug VFIO mediated devices on PCI(E) bridges, unlike +// This function can be used to hot plug VFIO mediated devices on PCI(E) bridges or root bus, unlike // ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. -// bus is optional. devID is the id of the device to add. Must be valid QMP identifier. sysfsdev is the VFIO -// mediated device. +// devID is the id of the device to add. Must be valid QMP identifier. sysfsdev is the VFIO mediated device. +// Both bus and addr are optional. If they are both set to be empty, the system will pick up an empty slot on root bus. func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus string) error { args := map[string]interface{}{ "id": devID, "driver": "vfio-pci", "sysfsdev": sysfsdev, - "addr": addr, } if bus != "" { args["bus"] = bus } + if addr != "" { + args["addr"] = addr + } return q.executeCommand(ctx, "device_add", args, nil) } From 1130aab85ec72354f3cd96247a4cfee5d4b551c8 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 19 Sep 2018 10:09:46 +0800 Subject: [PATCH 136/264] qmp: add "query-cpus" support Add "query-cpus" and "query-cpus-fast" to query CPU information from qemu Signed-off-by: Wei Zhang --- qemu/qmp.go | 72 +++++++++++++++++++++++++++++++++++++++++++++- qemu/qmp_test.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9f764f1236..ea3e97cb10 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -144,7 +144,7 @@ type QMPVersion struct { Capabilities []string } -// CPUProperties contains the properties to be used for hotplugging a CPU instance +// CPUProperties contains the properties of a CPU instance type CPUProperties struct { Node int `json:"node-id"` Socket int `json:"socket-id"` @@ -178,6 +178,28 @@ type MemoryDevices struct { Type string `json:"type"` } +// CPUInfo represents information about each virtual CPU +type CPUInfo struct { + CPU int `json:"CPU"` + Current bool `json:"current"` + Halted bool `json:"halted"` + QomPath string `json:"qom_path"` + Arch string `json:"arch"` + Pc int `json:"pc"` + ThreadID int `json:"thread_id"` + Props CPUProperties `json:"props"` +} + +// CPUInfoFast represents information about each virtual CPU +type CPUInfoFast struct { + CPUIndex int `json:"cpu-index"` + QomPath string `json:"qom-path"` + Arch string `json:"arch"` + ThreadID int `json:"thread-id"` + Target string `json:"target"` + Props CPUProperties `json:"props"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) for scanner.Scan() { @@ -1033,6 +1055,54 @@ func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, erro return memoryDevices, nil } +// ExecQueryCpus returns a slice with the list of `CpuInfo` +// Since qemu 2.12, we have `query-cpus-fast` as a better choice in production +// we can still choose `ExecQueryCpus` for compatibility though not recommended. +func (q *QMP) ExecQueryCpus(ctx context.Context) ([]CPUInfo, error) { + response, err := q.executeCommandWithResponse(ctx, "query-cpus", nil, nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + } + + var cpuInfo []CPUInfo + // convert json to []CPUInfo + if err = json.Unmarshal(data, &cpuInfo); err != nil { + return nil, fmt.Errorf("unable to convert json to CPUInfo: %v", err) + } + + return cpuInfo, nil +} + +// ExecQueryCpusFast returns a slice with the list of `CpuInfoFast` +// This is introduced since 2.12, it does not incur a performance penalty and +// should be used in production instead of query-cpus. +func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { + response, err := q.executeCommandWithResponse(ctx, "query-cpus-fast", nil, nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + } + + var cpuInfoFast []CPUInfoFast + // convert json to []CPUInfoFast + if err = json.Unmarshal(data, &cpuInfoFast); err != nil { + return nil, fmt.Errorf("unable to convert json to CPUInfoFast: %v", err) + } + + return cpuInfoFast, nil +} + // ExecHotplugMemory adds size of MiB memory to the guest func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int) error { args := map[string]interface{}{ diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index a1831a863b..8014cbe0c6 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1096,6 +1096,80 @@ func TestQMPExecuteQueryMemoryDevices(t *testing.T) { <-disconnectedCh } +// Checks that cpus are listed correctly +func TestQMPExecuteQueryCpus(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + cpuInfo := CPUInfo{ + CPU: 1, + Current: false, + Halted: false, + Arch: "x86_64", + QomPath: "/tmp/testQom", + Pc: 123456, + ThreadID: 123457, + Props: CPUProperties{ + Node: 0, + Socket: 1, + Core: 1, + Thread: 1966, + }, + } + buf.AddCommand("query-cpus", nil, "return", []interface{}{cpuInfo}) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + cpus, err := q.ExecQueryCpus(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(cpus) != 1 { + t.Fatalf("Expected memory devices length equals to 1\n") + } + if reflect.DeepEqual(cpus[0], cpuInfo) == false { + t.Fatalf("Expected %v equals to %v\n", cpus[0], cpuInfo) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that cpus are listed correctly +func TestQMPExecuteQueryCpusFast(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + cpuInfoFast := CPUInfoFast{ + CPUIndex: 1, + Arch: "x86", + Target: "x86_64", + QomPath: "/tmp/testQom", + ThreadID: 123457, + Props: CPUProperties{ + Node: 0, + Socket: 1, + Core: 1, + Thread: 1966, + }, + } + buf.AddCommand("query-cpus-fast", nil, "return", []interface{}{cpuInfoFast}) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + cpus, err := q.ExecQueryCpusFast(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(cpus) != 1 { + t.Fatalf("Expected memory devices length equals to 1\n") + } + if reflect.DeepEqual(cpus[0], cpuInfoFast) == false { + t.Fatalf("Expected %v equals to %v\n", cpus[0], cpuInfoFast) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that migrate capabilities can be set func TestExecSetMigrationCaps(t *testing.T) { connectedCh := make(chan *QMPVersion) From a429677a0b9bd44079744b1a4cbb109959389236 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Fri, 7 Sep 2018 22:02:47 +0800 Subject: [PATCH 137/264] govmm: fix memory prealloc The memory-backend-ram should also be set to a numa node instead of being inserted as a new device. Otherwise it becomes additional memory and requires explicit online to be available, instead of just being a backend of the memory specified by -m option. Signed-off-by: Peng Tao --- qemu/qemu.go | 6 +++--- qemu/qemu_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index d2f27f51dc..0b97025da5 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1502,13 +1502,13 @@ func (config *Config) appendMemoryKnobs() { if config.Memory.Size != "" { dimmName := "dimm1" objMemParam := "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + ",prealloc=on" - deviceMemParam := "pc-dimm,id=" + dimmName + ",memdev=" + dimmName + numaMemParam := "node,memdev=" + dimmName config.qemuParams = append(config.qemuParams, "-object") config.qemuParams = append(config.qemuParams, objMemParam) - config.qemuParams = append(config.qemuParams, "-device") - config.qemuParams = append(config.qemuParams, deviceMemParam) + config.qemuParams = append(config.qemuParams, "-numa") + config.qemuParams = append(config.qemuParams, numaMemParam) } } else if config.Knobs.FileBackedMem == true { if config.Memory.Size != "" && config.Memory.Path != "" { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 7fdaa9bffb..014c58d9c8 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -475,7 +475,7 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { FileBackedMem: true, FileBackedMemShared: true, } - knobsString := "-object memory-backend-ram,id=dimm1,size=1G,prealloc=on -device pc-dimm,id=dimm1,memdev=dimm1" + knobsString := "-object memory-backend-ram,id=dimm1,size=1G,prealloc=on -numa node,memdev=dimm1" mlockFalseString := "-realtime mlock=off" testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) From 56f645eac6a2db58e5d8bff43b01121405cf738d Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 18 Sep 2018 16:14:32 +0800 Subject: [PATCH 138/264] qmp: add ExecuteQueryMigration It sends query-migrate qmp command to check migration status. Signed-off-by: Peng Tao --- qemu/qmp.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index ea3e97cb10..58cd76709c 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -200,6 +200,46 @@ type CPUInfoFast struct { Props CPUProperties `json:"props"` } +// MigrationRAM represents migration ram status +type MigrationRAM struct { + Total int64 `json:"total"` + Remaining int64 `json:"remaining"` + Transferred int64 `json:"transferred"` + TotalTime int64 `json:"total-time"` + SetupTime int64 `json:"setup-time"` + ExpectedDowntime int64 `json:"expected-downtime"` + Duplicate int64 `json:"duplicate"` + Normal int64 `json:"normal"` + NormalBytes int64 `json:"normal-bytes"` + DirtySyncCount int64 `json:"dirty-sync-count"` +} + +// MigrationDisk represents migration disk status +type MigrationDisk struct { + Total int64 `json:"total"` + Remaining int64 `json:"remaining"` + Transferred int64 `json:"transferred"` +} + +// MigrationXbzrleCache represents migration XbzrleCache status +type MigrationXbzrleCache struct { + CacheSize int64 `json:"cache-size"` + Bytes int64 `json:"bytes"` + Pages int64 `json:"pages"` + CacheMiss int64 `json:"cache-miss"` + CacheMissRate int64 `json:"cache-miss-rate"` + Overflow int64 `json:"overflow"` +} + +// MigrationStatus represents migration status of a vm +type MigrationStatus struct { + Status string `json:"status"` + Capabilities []map[string]interface{} `json:"capabilities,omitempty"` + RAM MigrationRAM `json:"ram,omitempty"` + Disk MigrationDisk `json:"disk,omitempty"` + XbzrleCache MigrationXbzrleCache `json:"xbzrle-cache,omitempty"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) for scanner.Scan() { @@ -1206,3 +1246,23 @@ func (q *QMP) ExecuteVirtSerialPortAdd(ctx context.Context, id, name, chardev st return q.executeCommand(ctx, "device_add", args, nil) } + +// ExecuteQueryMigration queries migration progress. +func (q *QMP) ExecuteQueryMigration(ctx context.Context) (MigrationStatus, error) { + response, err := q.executeCommandWithResponse(ctx, "query-migrate", nil, nil, nil) + if err != nil { + return MigrationStatus{}, err + } + + data, err := json.Marshal(response) + if err != nil { + return MigrationStatus{}, fmt.Errorf("Unable to extract migrate status information: %v", err) + } + + var status MigrationStatus + if err = json.Unmarshal(data, &status); err != nil { + return MigrationStatus{}, fmt.Errorf("Unable to convert migrate status information: %v", err) + } + + return status, nil +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 8014cbe0c6..6a70d02c62 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1295,3 +1295,53 @@ func TestExecuteVirtSerialPortAdd(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks migration status +func TestExecuteQueryMigration(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + status := MigrationStatus{ + Status: "completed", + RAM: MigrationRAM{ + Total: 100, + Remaining: 101, + Transferred: 101, + TotalTime: 101, + SetupTime: 101, + ExpectedDowntime: 101, + Duplicate: 101, + Normal: 101, + NormalBytes: 101, + DirtySyncCount: 101, + }, + Disk: MigrationDisk{ + Total: 200, + Remaining: 200, + Transferred: 200, + }, + XbzrleCache: MigrationXbzrleCache{ + CacheSize: 300, + Bytes: 300, + Pages: 300, + CacheMiss: 300, + CacheMissRate: 300, + Overflow: 300, + }, + } + caps := map[string]interface{}{"foo": true} + status.Capabilities = append(status.Capabilities, caps) + buf.AddCommand("query-migrate", nil, "return", interface{}(status)) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + s, err := q.ExecuteQueryMigration(context.Background()) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if !reflect.DeepEqual(s, status) { + t.Fatalf("expected %v\n got %v", status, s) + } + q.Shutdown() + <-disconnectedCh +} From ef7250508cda2be77112f145b6c8b4e1aeb5b700 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 2 Oct 2018 13:43:24 -0700 Subject: [PATCH 139/264] qemu: Fix the support of PCIe bridge In case the type of bridge is PCIEBridge, which we expect as ending up using pcie-pci-bridge device from Qemu, the properties chassis_nr and shpc don't exist. This commit simply fixes this use case by removing those parameters from the command line. Signed-off-by: Sebastien Boeuf --- qemu/qemu.go | 19 ++++++++++--------- qemu/qemu_test.go | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 2d55ebed05..a44cc63f4a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -938,18 +938,19 @@ func (bridgeDev BridgeDevice) Valid() bool { // QemuParams returns the qemu parameters built out of this bridge device. func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { var qemuParams []string + var deviceParam string - shpc := "off" - if bridgeDev.SHPC { - shpc = "on" + switch bridgeDev.Type { + case PCIEBridge: + deviceParam = fmt.Sprintf("pcie-pci-bridge,bus=%s,id=%s", bridgeDev.Bus, bridgeDev.ID) + default: + shpc := "off" + if bridgeDev.SHPC { + shpc = "on" + } + deviceParam = fmt.Sprintf("pci-bridge,bus=%s,id=%s,chassis_nr=%d,shpc=%s", bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc) } - deviceName := "pci-bridge" - if bridgeDev.Type == PCIEBridge { - deviceName = "pcie-pci-bridge" - } - - deviceParam := fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", deviceName, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc) if bridgeDev.Addr != "" { addr, err := strconv.Atoi(bridgeDev.Addr) if err == nil && addr >= 0 { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 002d06fc74..ea568ca0c6 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -420,9 +420,9 @@ func TestAppendDeviceSCSIController(t *testing.T) { testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } -var deviceBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" +var devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" -func TestAppendBridgeDevice(t *testing.T) { +func TestAppendPCIBridgeDevice(t *testing.T) { bridge := BridgeDevice{ Type: PCIBridge, @@ -433,7 +433,21 @@ func TestAppendBridgeDevice(t *testing.T) { SHPC: true, } - testAppend(bridge, deviceBridgeString, t) + testAppend(bridge, devicePCIBridgeString, t) +} + +var devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" + +func TestAppendPCIEBridgeDevice(t *testing.T) { + + bridge := BridgeDevice{ + Type: PCIEBridge, + ID: "mybridge", + Bus: "/pci-bus/pcie.0", + Addr: "255", + } + + testAppend(bridge, devicePCIEBridgeString, t) } func TestAppendEmptyDevice(t *testing.T) { From 46970781fae7623434406bab5cb9ebf1c387f48d Mon Sep 17 00:00:00 2001 From: "James O. D. Hunt" Date: Wed, 3 Oct 2018 16:41:08 +0100 Subject: [PATCH 140/264] qemu: Show full path to qemu binary at launch time Rather than show the generic "qemu", log the full path to the particular qemu binary being used. Signed-off-by: James O. D. Hunt --- qemu/qemu.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index a44cc63f4a..5fbfb81a57 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1740,11 +1740,11 @@ func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []* var stderr bytes.Buffer cmd.Stderr = &stderr - logger.Infof("launching qemu with: %v", params) + logger.Infof("launching %s with: %v", path, params) err := cmd.Run() if err != nil { - logger.Errorf("Unable to launch qemu: %v", err) + logger.Errorf("Unable to launch %s: %v", path, err) errStr = stderr.String() logger.Errorf("%s", errStr) } From ec83abe69e130c17c3920d4acff7861269f7b50b Mon Sep 17 00:00:00 2001 From: Jose Carlos Venegas Munoz Date: Wed, 3 Oct 2018 16:19:33 -0500 Subject: [PATCH 141/264] qemu: Add virtio-balloon device suppport. Add support for virtio-balloon. - Add test - Support disable-modern - Support deflate-on-oom Signed-off-by: Jose Carlos Venegas Munoz --- qemu/qemu.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 39 ++++++++++++++++++++++++++++++++++++++ qemu/qmp.go | 9 +++++++++ qemu/qmp_test.go | 18 ++++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 5fbfb81a57..a518da52c4 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -88,6 +88,9 @@ const ( // VirtioRng is the paravirtualized RNG device driver. VirtioRng DeviceDriver = "virtio-rng" + + // VirtioBalloon is the memory balloon device driver. + VirtioBalloon DeviceDriver = "virtio-balloon" ) // ObjectType is a string representing a qemu object type. @@ -1078,6 +1081,51 @@ func (v RngDevice) QemuParams(_ *Config) []string { return qemuParams } +// BalloonDevice represents a memory balloon device. +type BalloonDevice struct { + DeflateOnOOM bool + DisableModern bool + ID string +} + +// QemuParams returns the qemu parameters built out of the BalloonDevice. +func (b BalloonDevice) QemuParams(_ *Config) []string { + var qemuParams []string + var deviceParams []string + + deviceParams = append(deviceParams, string(VirtioBalloon)) + + if b.ID != "" { + deviceParams = append(deviceParams, "id="+b.ID) + } + + if b.DeflateOnOOM { + deviceParams = append(deviceParams, "deflate-on-oom=on") + } else { + deviceParams = append(deviceParams, "deflate-on-oom=off") + } + + if b.DisableModern { + deviceParams = append(deviceParams, "disable-modern=on") + } else { + deviceParams = append(deviceParams, "disable-modern=off") + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) + + return qemuParams +} + +// Valid returns true if the balloonDevice structure is valid and complete. +func (b BalloonDevice) Valid() bool { + if b.ID == "" { + return false + } + + return true +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index ea568ca0c6..e1d9fb341b 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -404,6 +404,45 @@ func TestVirtioRngValid(t *testing.T) { } +func TestAppendVirtioBalloon(t *testing.T) { + balloonDevice := BalloonDevice{ + ID: "balloon", + } + + var deviceString = "-device " + string(VirtioBalloon) + deviceString += ",id=" + balloonDevice.ID + + var OnDeflateOnOMM = ",deflate-on-oom=on" + var OffDeflateOnOMM = ",deflate-on-oom=off" + + var OnDisableModern = ",disable-modern=on" + var OffDisableModern = ",disable-modern=off" + + testAppend(balloonDevice, deviceString+OffDeflateOnOMM+OffDisableModern, t) + + balloonDevice.DeflateOnOOM = true + testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OffDisableModern, t) + + balloonDevice.DisableModern = true + testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OnDisableModern, t) + +} + +func TestVirtioBalloonValid(t *testing.T) { + balloon := BalloonDevice{ + ID: "", + } + + if balloon.Valid() { + t.Fatalf("balloon should be not valid when ID is empty") + } + + balloon.ID = "balloon0" + if !balloon.Valid() { + t.Fatalf("balloon should be valid") + } +} + var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo" var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1" diff --git a/qemu/qmp.go b/qemu/qmp.go index 58cd76709c..aac906f440 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1178,6 +1178,15 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string return err } +// ExecuteBalloon sets the size of the balloon, hence updates the memory +// allocated for the VM. +func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { + args := map[string]interface{}{ + "value": bytes, + } + return q.executeCommand(ctx, "balloon", args, nil) +} + // ExecutePCIVSockAdd adds a vhost-vsock-pci bus func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus string, disableModern bool) error { args := map[string]interface{}{ diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 6a70d02c62..b7c201f02e 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1345,3 +1345,21 @@ func TestExecuteQueryMigration(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks balloon +func TestExecuteBalloon(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("balloon", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteBalloon(context.Background(), 1073741824) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} From e74de3c7f198ad8e0de285bbc45a42d8023fcbdb Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Wed, 10 Oct 2018 10:05:58 +0200 Subject: [PATCH 142/264] Update guidelines on security issue reporting This commit clarifies the process to be used when reporting security issues. Signed-off-by: Mark Ryan --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e94d1d028..b97d45671e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,3 +60,5 @@ We request you give quality assurance some consideration by: If you have a problem, please let us know. If it's a bug not already documented, by all means please [open an issue in github](https://github.com/intel/govmm/issues/new) so we all get visibility the problem and work toward resolution. + +Any security issues discovered with govmm should be reported by following the instructions on https://01.org/security. From 7fdfc6a4c979ed691f90e279d65746c8fa49cdb3 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 8 Oct 2018 16:04:17 -0700 Subject: [PATCH 143/264] qemu: Add support for romfile option Any device inheriting from virtio-pci can specify a ROM file. This option is provisioned by default with "efi-virtio.rom", but most of the time, firmwares such as OVMF or seabios will already support what is provided by this ROM file. In order to reduce the "forced" dependency on such ROM file, govmm should provide an empty path if the consumer of the library does not provide one. This patch reorganizes the list of devices, so that it gets easier to list which devices inherit from virtio-pci, and then adds the romfile option to every single device that support this option. Signed-off-by: Sebastien Boeuf --- qemu/qemu.go | 189 +++++++++++++++++++++++++++++++++++++--------- qemu/qemu_test.go | 69 ++++++++++------- qemu/qmp.go | 62 +++++++++------ qemu/qmp_test.go | 14 ++-- 4 files changed, 245 insertions(+), 89 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index a518da52c4..f26f093922 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -91,8 +91,52 @@ const ( // VirtioBalloon is the memory balloon device driver. VirtioBalloon DeviceDriver = "virtio-balloon" + + //VhostUserSCSI represents a SCSI vhostuser device type. + VhostUserSCSI DeviceDriver = "vhost-user-scsi-pci" + + //VhostUserNet represents a net vhostuser device type. + VhostUserNet DeviceDriver = "virtio-net-pci" + + //VhostUserBlk represents a block vhostuser device type. + VhostUserBlk DeviceDriver = "vhost-user-blk-pci" + + // VfioPCI represent a VFIO device type. + VfioPCI DeviceDriver = "vfio-pci" + + // VirtioScsiPCI represents a SCSI device type. + VirtioScsiPCI DeviceDriver = "virtio-scsi-pci" + + // PCIBridgeDriver represents a PCI bridge device type. + PCIBridgeDriver DeviceDriver = "pci-bridge" + + // PCIePCIBridgeDriver represents a PCIe to PCI bridge device type. + PCIePCIBridgeDriver DeviceDriver = "pcie-pci-bridge" ) +// isVirtioPCI is a map indicating if a DeviceDriver is considered as a +// virtio PCI device, which is helpful to determine if the option "romfile" +// applies or not to this specific device. +var isVirtioPCI = map[DeviceDriver]bool{ + NVDIMM: false, + Virtio9P: true, + VirtioNet: true, + VirtioNetPCI: true, + VirtioSerial: true, + VirtioBlock: true, + Console: false, + VirtioSerialPort: false, + VHostVSockPCI: true, + VirtioRng: true, + VirtioBalloon: true, + VhostUserSCSI: true, + VhostUserBlk: true, + VfioPCI: true, + VirtioScsiPCI: true, + PCIBridgeDriver: true, + PCIePCIBridgeDriver: true, +} + // ObjectType is a string representing a qemu object type. type ObjectType string @@ -219,6 +263,9 @@ type FSDevice struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the FSDevice structure is valid and complete. @@ -242,6 +289,9 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { } deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) + if isVirtioPCI[fsdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) + } fsParams = append(fsParams, string(fsdev.FSDriver)) fsParams = append(fsParams, fmt.Sprintf(",id=%s", fsdev.ID)) @@ -299,6 +349,9 @@ type CharDevice struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the CharDevice structure is valid and complete. @@ -328,6 +381,9 @@ func (cdev CharDevice) QemuParams(config *Config) []string { if cdev.Name != "" { deviceParams = append(deviceParams, fmt.Sprintf(",name=%s", cdev.Name)) } + if isVirtioPCI[cdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", cdev.ROMFile)) + } cdevParams = append(cdevParams, string(cdev.Backend)) cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) @@ -391,7 +447,7 @@ func (n NetDeviceType) QemuNetdevParam() string { } // QemuDeviceParam converts to the QEMU -device parameter notation -func (n NetDeviceType) QemuDeviceParam() string { +func (n NetDeviceType) QemuDeviceParam() DeviceDriver { switch n { case TAP: return "virtio-net-pci" @@ -450,6 +506,9 @@ type NetDevice struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the NetDevice structure is valid and complete. @@ -476,8 +535,7 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { return nil } - deviceParams = append(deviceParams, "driver=") - deviceParams = append(deviceParams, netdev.Type.QemuDeviceParam()) + deviceParams = append(deviceParams, fmt.Sprintf("driver=%s", netdev.Type.QemuDeviceParam())) deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) @@ -513,6 +571,10 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",vectors=%d", vectors)) } + if isVirtioPCI[netdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", netdev.ROMFile)) + } + return deviceParams } @@ -601,6 +663,9 @@ type SerialDevice struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the SerialDevice structure is valid and complete. @@ -622,6 +687,9 @@ func (dev SerialDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, ",disable-modern=true") } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) + if isVirtioPCI[dev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) + } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) @@ -672,6 +740,9 @@ type BlockDevice struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the BlockDevice structure is valid and complete. @@ -702,6 +773,10 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, ",config-wce=off") } + if isVirtioPCI[blkdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", blkdev.ROMFile)) + } + 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)) @@ -717,18 +792,6 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { return qemuParams } -// VhostUserDeviceType is a qemu vhost-user device type. -type VhostUserDeviceType string - -const ( - //VhostUserSCSI represents a SCSI vhostuser device type - VhostUserSCSI = "vhost-user-scsi-pci" - //VhostUserNet represents a net vhostuser device type - VhostUserNet = "virtio-net-pci" - //VhostUserBlk represents a block vhostuser device type - VhostUserBlk = "vhost-user-blk-pci" -) - // VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { @@ -736,7 +799,10 @@ type VhostUserDevice struct { CharDevID string TypeDevID string //variable QEMU parameter based on value of VhostUserType Address string //used for MAC address in net case - VhostUserType VhostUserDeviceType + VhostUserType DeviceDriver + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if there is a valid structure defined for VhostUserDevice @@ -769,6 +835,7 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { var charParams []string var netParams []string var devParams []string + var driver DeviceDriver charParams = append(charParams, "socket") charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) @@ -777,20 +844,23 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { switch vhostuserDev.VhostUserType { // if network based vhost device: case VhostUserNet: + driver = VhostUserNet netParams = append(netParams, "type=vhost-user") netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) netParams = append(netParams, "vhostforce") - devParams = append(devParams, VhostUserNet) + devParams = append(devParams, string(driver)) devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) case VhostUserSCSI: - devParams = append(devParams, VhostUserSCSI) + driver = VhostUserSCSI + devParams = append(devParams, string(driver)) devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) case VhostUserBlk: - devParams = append(devParams, VhostUserBlk) + driver = VhostUserBlk + devParams = append(devParams, string(driver)) devParams = append(devParams, "logical_block_size=4096") devParams = append(devParams, "size=512M") devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) @@ -798,6 +868,10 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { return nil } + if isVirtioPCI[driver] { + devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + } + qemuParams = append(qemuParams, "-chardev") qemuParams = append(qemuParams, strings.Join(charParams, ",")) @@ -816,6 +890,9 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { type VFIODevice struct { // Bus-Device-Function of device BDF string + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the VFIODevice structure is valid and complete. @@ -830,10 +907,17 @@ func (vfioDev VFIODevice) Valid() bool { // QemuParams returns the qemu parameters built out of this vfio device. func (vfioDev VFIODevice) QemuParams(config *Config) []string { var qemuParams []string + var deviceParams []string + + driver := VfioPCI + + deviceParams = append(deviceParams, fmt.Sprintf("%s,host=%s", driver, vfioDev.BDF)) + if isVirtioPCI[driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) + } - deviceParam := fmt.Sprintf("vfio-pci,host=%s", vfioDev.BDF) qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, deviceParam) + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) return qemuParams } @@ -853,6 +937,9 @@ type SCSIController struct { // IOThread is the IO thread on which IO will be handled IOThread string + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the SCSIController structure is valid and complete. @@ -869,7 +956,8 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { var qemuParams []string var devParams []string - devParams = append(devParams, fmt.Sprintf("virtio-scsi-pci,id=%s", scsiCon.ID)) + driver := VirtioScsiPCI + devParams = append(devParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) if scsiCon.Bus != "" { devParams = append(devParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) } @@ -882,6 +970,9 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { if scsiCon.IOThread != "" { devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) } + if isVirtioPCI[driver] { + devParams = append(devParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) + } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) @@ -919,6 +1010,9 @@ type BridgeDevice struct { // PCI Slot Addr string + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the BridgeDevice structure is valid and complete. @@ -941,28 +1035,35 @@ func (bridgeDev BridgeDevice) Valid() bool { // QemuParams returns the qemu parameters built out of this bridge device. func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { var qemuParams []string - var deviceParam string + var deviceParam []string + var driver DeviceDriver switch bridgeDev.Type { case PCIEBridge: - deviceParam = fmt.Sprintf("pcie-pci-bridge,bus=%s,id=%s", bridgeDev.Bus, bridgeDev.ID) + driver = PCIePCIBridgeDriver + deviceParam = append(deviceParam, fmt.Sprintf("%s,bus=%s,id=%s", driver, bridgeDev.Bus, bridgeDev.ID)) default: + driver = PCIBridgeDriver shpc := "off" if bridgeDev.SHPC { shpc = "on" } - deviceParam = fmt.Sprintf("pci-bridge,bus=%s,id=%s,chassis_nr=%d,shpc=%s", bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc) + deviceParam = append(deviceParam, fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", driver, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc)) } if bridgeDev.Addr != "" { addr, err := strconv.Atoi(bridgeDev.Addr) if err == nil && addr >= 0 { - deviceParam += fmt.Sprintf(",addr=%x", addr) + deviceParam = append(deviceParam, fmt.Sprintf(",addr=%x", addr)) } } + if isVirtioPCI[driver] { + deviceParam = append(deviceParam, fmt.Sprintf(",romfile=%s", bridgeDev.ROMFile)) + } + qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, deviceParam) + qemuParams = append(qemuParams, strings.Join(deviceParam, "")) return qemuParams } @@ -978,16 +1079,14 @@ type VSOCKDevice struct { // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } const ( // MinimalGuestCID is the smallest valid context ID for a guest. MinimalGuestCID uint32 = 3 -) - -const ( - // VhostVSOCKPCI is the VSOCK vhost device type. - VhostVSOCKPCI = "vhost-vsock-pci" // VSOCKGuestCID is the VSOCK guest CID parameter. VSOCKGuestCID = "guest-cid" @@ -1007,7 +1106,8 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, fmt.Sprintf("%s", VhostVSOCKPCI)) + driver := VHostVSockPCI + deviceParams = append(deviceParams, fmt.Sprintf("%s", driver)) if vsock.DisableModern { deviceParams = append(deviceParams, ",disable-modern=true") } @@ -1018,6 +1118,10 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID)) + if isVirtioPCI[driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vsock.ROMFile)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) @@ -1034,6 +1138,8 @@ type RngDevice struct { MaxBytes uint // Period is duration of a read period in seconds Period uint + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // Valid returns true if the RngDevice structure is valid and complete. @@ -1054,12 +1160,17 @@ func (v RngDevice) QemuParams(_ *Config) []string { //-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 var deviceParams []string + driver := VirtioRng objectParams = append(objectParams, "rng-random") objectParams = append(objectParams, "id="+v.ID) - deviceParams = append(deviceParams, string(VirtioRng)) + deviceParams = append(deviceParams, string(driver)) deviceParams = append(deviceParams, "rng="+v.ID) + if isVirtioPCI[driver] { + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", v.ROMFile)) + } + if v.Filename != "" { objectParams = append(objectParams, "filename="+v.Filename) } @@ -1086,6 +1197,9 @@ type BalloonDevice struct { DeflateOnOOM bool DisableModern bool ID string + + // ROMFile specifies the ROM file being used for this device. + ROMFile string } // QemuParams returns the qemu parameters built out of the BalloonDevice. @@ -1093,12 +1207,17 @@ func (b BalloonDevice) QemuParams(_ *Config) []string { var qemuParams []string var deviceParams []string - deviceParams = append(deviceParams, string(VirtioBalloon)) + driver := VirtioBalloon + deviceParams = append(deviceParams, string(driver)) if b.ID != "" { deviceParams = append(deviceParams, "id="+b.ID) } + if isVirtioPCI[driver] { + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) + } + if b.DeflateOnOOM { deviceParams = append(deviceParams, "deflate-on-oom=on") } else { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index e1d9fb341b..e1c70e56d1 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -126,7 +126,7 @@ func TestAppendDeviceNVDIMM(t *testing.T) { testAppend(object, deviceNVDIMMString, t) } -var deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" +var deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" func TestAppendDeviceFS(t *testing.T) { fsdev := FSDevice{ @@ -137,12 +137,13 @@ func TestAppendDeviceFS(t *testing.T) { MountTag: "rootfs", SecurityModel: None, DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(fsdev, deviceFSString, t) } -var deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true" +var deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,romfile=efi-virtio.rom" func TestAppendDeviceNetwork(t *testing.T) { netdev := NetDevice{ @@ -155,12 +156,13 @@ func TestAppendDeviceNetwork(t *testing.T) { VHost: true, MACAddress: "01:02:de:ad:be:ef", DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(netdev, deviceNetworkString, t) } -var deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6" +var deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" func TestAppendDeviceNetworkMq(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") @@ -184,12 +186,13 @@ func TestAppendDeviceNetworkMq(t *testing.T) { VHost: true, MACAddress: "01:02:de:ad:be:ef", DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(netdev, deviceNetworkStringMq, t) } -var deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true" +var deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,romfile=efi-virtio.rom" func TestAppendDeviceNetworkPCI(t *testing.T) { @@ -205,12 +208,13 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { VHost: true, MACAddress: "01:02:de:ad:be:ef", DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(netdev, deviceNetworkPCIString, t) } -var deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6" +var deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" func TestAppendDeviceNetworkPCIMq(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") @@ -236,18 +240,20 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { VHost: true, MACAddress: "01:02:de:ad:be:ef", DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(netdev, deviceNetworkPCIStringMq, t) } -var deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0" +var deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom" func TestAppendDeviceSerial(t *testing.T) { sdev := SerialDevice{ Driver: VirtioSerial, ID: "serial0", DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(sdev, deviceSerialString, t) @@ -268,7 +274,7 @@ func TestAppendDeviceSerialPort(t *testing.T) { testAppend(chardev, deviceSerialPortString, t) } -var deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" +var deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" func TestAppendDeviceBlock(t *testing.T) { blkdev := BlockDevice{ @@ -281,14 +287,15 @@ func TestAppendDeviceBlock(t *testing.T) { SCSI: false, WCE: false, DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(blkdev, deviceBlockString, t) } -var deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55" -var deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1" -var deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2" +var deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" +var deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" +var deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" func TestAppendDeviceVhostUser(t *testing.T) { @@ -298,6 +305,7 @@ func TestAppendDeviceVhostUser(t *testing.T) { TypeDevID: "", Address: "", VhostUserType: VhostUserBlk, + ROMFile: "efi-virtio.rom", } testAppend(vhostuserBlkDevice, deviceVhostUserBlkString, t) @@ -307,6 +315,7 @@ func TestAppendDeviceVhostUser(t *testing.T) { TypeDevID: "scsi1", Address: "", VhostUserType: VhostUserSCSI, + ROMFile: "efi-virtio.rom", } testAppend(vhostuserSCSIDevice, deviceVhostUserSCSIString, t) @@ -316,21 +325,23 @@ func TestAppendDeviceVhostUser(t *testing.T) { TypeDevID: "net1", Address: "00:11:22:33:44:55", VhostUserType: VhostUserNet, + ROMFile: "efi-virtio.rom", } testAppend(vhostuserNetDevice, deviceVhostUserNetString, t) } -var deviceVFIOString = "-device vfio-pci,host=02:10.0" +var deviceVFIOString = "-device vfio-pci,host=02:10.0,romfile=efi-virtio.rom" func TestAppendDeviceVFIO(t *testing.T) { vfioDevice := VFIODevice{ - BDF: "02:10.0", + BDF: "02:10.0", + ROMFile: "efi-virtio.rom", } testAppend(vfioDevice, deviceVFIOString, t) } -var deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4" +var deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" func TestAppendVSOCK(t *testing.T) { vsockDevice := VSOCKDevice{ @@ -338,6 +349,7 @@ func TestAppendVSOCK(t *testing.T) { ContextID: 4, VHostFD: nil, DisableModern: true, + ROMFile: "efi-virtio.rom", } testAppend(vsockDevice, deviceVSOCKString, t) @@ -364,9 +376,10 @@ func TestVSOCKValid(t *testing.T) { func TestAppendVirtioRng(t *testing.T) { var objectString = "-object rng-random,id=rng0" - var deviceString = "-device " + string(VirtioRng) + ",rng=rng0" + var deviceString = "-device " + string(VirtioRng) + ",rng=rng0,romfile=efi-virtio.rom" rngDevice := RngDevice{ - ID: "rng0", + ID: "rng0", + ROMFile: "efi-virtio.rom", } testAppend(rngDevice, objectString+" "+deviceString, t) @@ -406,11 +419,12 @@ func TestVirtioRngValid(t *testing.T) { func TestAppendVirtioBalloon(t *testing.T) { balloonDevice := BalloonDevice{ - ID: "balloon", + ID: "balloon", + ROMFile: "efi-virtio.rom", } var deviceString = "-device " + string(VirtioBalloon) - deviceString += ",id=" + balloonDevice.ID + deviceString += ",id=" + balloonDevice.ID + ",romfile=" + balloonDevice.ROMFile var OnDeflateOnOMM = ",deflate-on-oom=on" var OffDeflateOnOMM = ",deflate-on-oom=off" @@ -443,12 +457,13 @@ func TestVirtioBalloonValid(t *testing.T) { } } -var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo" -var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1" +var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,romfile=efi-virtio.rom" +var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" func TestAppendDeviceSCSIController(t *testing.T) { scsiCon := SCSIController{ - ID: "foo", + ID: "foo", + ROMFile: "efi-virtio.rom", } testAppend(scsiCon, deviceSCSIControllerStr, t) @@ -459,7 +474,7 @@ func TestAppendDeviceSCSIController(t *testing.T) { testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } -var devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" +var devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" func TestAppendPCIBridgeDevice(t *testing.T) { @@ -470,20 +485,22 @@ func TestAppendPCIBridgeDevice(t *testing.T) { Addr: "255", Chassis: 5, SHPC: true, + ROMFile: "efi-virtio.rom", } testAppend(bridge, devicePCIBridgeString, t) } -var devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" +var devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" func TestAppendPCIEBridgeDevice(t *testing.T) { bridge := BridgeDevice{ - Type: PCIEBridge, - ID: "mybridge", - Bus: "/pci-bus/pcie.0", - Addr: "255", + Type: PCIEBridge, + ID: "mybridge", + Bus: "/pci-bus/pcie.0", + Addr: "255", + ROMFile: "efi-virtio.rom", } testAppend(bridge, devicePCIEBridgeString, t) diff --git a/qemu/qmp.go b/qemu/qmp.go index aac906f440..134608ec7d 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -756,7 +756,7 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) // add. Both strings must be valid QMP identifiers. driver is the name of the // driver,e.g., virtio-blk-pci, and bus is the name of the bus. bus is optional. // shared denotes if the drive can be shared allowing it to be passed more than once. -func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string, shared bool) error { +func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -768,6 +768,10 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } + if isVirtioPCI[DeviceDriver(driver)] { + args["romfile"] = romfile + } + return q.executeCommand(ctx, "device_add", args, nil) } @@ -779,7 +783,7 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b // scsiID is the SCSI id, lun is logical unit number. scsiID and lun are optional, a negative value // for scsiID and lun is ignored. shared denotes if the drive can be shared allowing it // to be passed more than once. -func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus string, scsiID, lun int, shared bool) error { +func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, scsiID, lun int, shared bool) error { // TBD: Add drivers for scsi passthrough like scsi-generic and scsi-block drivers := []string{"scsi-hd", "scsi-cd", "scsi-disk"} @@ -810,6 +814,9 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } + if isVirtioPCI[DeviceDriver(driver)] { + args["romfile"] = romfile + } return q.executeCommand(ctx, "device_add", args, nil) } @@ -901,13 +908,14 @@ func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error { // using the device_add command. devID is the id of the device to add. // Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. // queues is the number of queues of a nic. -func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus string, queues int) error { +func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus, romfile string, queues int) error { args := map[string]interface{}{ - "id": devID, - "driver": VirtioNetPCI, - "netdev": netdevID, - "mac": macAddr, - "addr": addr, + "id": devID, + "driver": VirtioNetPCI, + "netdev": netdevID, + "mac": macAddr, + "addr": addr, + "romfile": romfile, } if bus != "" { @@ -950,7 +958,7 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { // to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the // device address on its parent bus. bus is optional. shared denotes if the drive can be shared // allowing it to be passed more than once. -func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus string, shared bool) error { +func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, shared bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -963,6 +971,9 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } + if isVirtioPCI[DeviceDriver(driver)] { + args["romfile"] = romfile + } return q.executeCommand(ctx, "device_add", args, nil) } @@ -971,11 +982,12 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver // using the device_add command. devID is the id of the device to add. // Must be valid QMP identifier. bdf is the PCI bus-device-function // of the pci device. -func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf string) error { +func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, romfile string) error { args := map[string]interface{}{ - "id": devID, - "driver": "vfio-pci", - "host": bdf, + "id": devID, + "driver": "vfio-pci", + "host": bdf, + "romfile": romfile, } return q.executeCommand(ctx, "device_add", args, nil) } @@ -985,12 +997,13 @@ func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf string) error // ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. // bus is optional. devID is the id of the device to add.Must be valid QMP identifier. bdf is the // PCI bus-device-function of the pci device. -func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus string) error { +func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus, romfile string) error { args := map[string]interface{}{ - "id": devID, - "driver": "vfio-pci", - "host": bdf, - "addr": addr, + "id": devID, + "driver": "vfio-pci", + "host": bdf, + "addr": addr, + "romfile": romfile, } if bus != "" { args["bus"] = bus @@ -1003,11 +1016,12 @@ func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus // ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. // devID is the id of the device to add. Must be valid QMP identifier. sysfsdev is the VFIO mediated device. // Both bus and addr are optional. If they are both set to be empty, the system will pick up an empty slot on root bus. -func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus string) error { +func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus, romfile string) error { args := map[string]interface{}{ "id": devID, "driver": "vfio-pci", "sysfsdev": sysfsdev, + "romfile": romfile, } if bus != "" { args["bus"] = bus @@ -1022,7 +1036,7 @@ func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsd // driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within // node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the // thread number within core the CPU belongs to. -func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, coreID, threadID string) error { +func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, coreID, threadID, romfile string) error { args := map[string]interface{}{ "driver": driver, "id": cpuID, @@ -1030,6 +1044,11 @@ func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, "core-id": coreID, "thread-id": threadID, } + + if isVirtioPCI[DeviceDriver(driver)] { + args["romfile"] = romfile + } + return q.executeCommand(ctx, "device_add", args, nil) } @@ -1188,13 +1207,14 @@ func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { } // ExecutePCIVSockAdd adds a vhost-vsock-pci bus -func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus string, disableModern bool) error { +func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus, romfile string, disableModern bool) error { args := map[string]interface{}{ "driver": VHostVSockPCI, "id": id, "guest-cid": guestCID, "vhostfd": vhostfd, "addr": addr, + "romfile": romfile, } if bus != "" { diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index b7c201f02e..a1a784dd6e 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -504,7 +504,7 @@ func TestQMPNetPCIDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", 8) + err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", "", 8) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -529,7 +529,7 @@ func TestQMPDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "", true) + "virtio-blk-pci", "", "", true) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -554,7 +554,7 @@ func TestQMPSCSIDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteSCSIDeviceAdd(context.Background(), blockdevID, devID, - "scsi-hd", "scsi0.0", 1, 2, true) + "scsi-hd", "scsi0.0", "", 1, 2, true) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -972,7 +972,7 @@ func TestQMPPCIDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "0x1", "", true) + "virtio-blk-pci", "0x1", "", "", true) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -996,7 +996,7 @@ func TestQMPPCIVFIOMediatedDeviceAdd(t *testing.T) { checkVersion(t, connectedCh) sysfsDev := "/sys/bus/pci/devices/0000:00:02.0/a297db4a-f4c2-11e6-90f6-d3b88d6c9525" devID := fmt.Sprintf("device_%s", volumeUUID) - err := q.ExecutePCIVFIOMediatedDeviceAdd(context.Background(), devID, sysfsDev, "0x1", "") + err := q.ExecutePCIVFIOMediatedDeviceAdd(context.Background(), devID, sysfsDev, "0x1", "", "") if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -1018,7 +1018,7 @@ func TestQMPCPUDeviceAdd(t *testing.T) { socketID := "0" coreID := "1" threadID := "0" - err := q.ExecuteCPUDeviceAdd(context.Background(), driver, cpuID, socketID, coreID, threadID) + err := q.ExecuteCPUDeviceAdd(context.Background(), driver, cpuID, socketID, coreID, threadID, "") if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -1237,7 +1237,7 @@ func TestExecutePCIVSockAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3", "1", "1", "1", true) + err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3", "1", "1", "1", "", true) if err != nil { t.Fatalf("Unexpected error %v", err) } From 9c819db5a3523bc38fb036bd7c91bb334eb872a8 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 16 Oct 2018 13:20:55 -0700 Subject: [PATCH 144/264] qemu: Fix virtio-net-pci QMP command This patch fixes the wrong behavior of specifying a netdev, MAC address or PCI address entry when those were empty. Instead, it does not provide those entries if the content is empty. Signed-off-by: Sebastien Boeuf --- qemu/qmp.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 134608ec7d..43daed369d 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -912,15 +912,21 @@ func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAd args := map[string]interface{}{ "id": devID, "driver": VirtioNetPCI, - "netdev": netdevID, - "mac": macAddr, - "addr": addr, "romfile": romfile, } if bus != "" { args["bus"] = bus } + if addr != "" { + args["addr"] = addr + } + if macAddr != "" { + args["mac"] = macAddr + } + if netdevID != "" { + args["netdev"] = netdevID + } if queues > 0 { // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) From 10c36a13da29ba68405db0d7a103b723b6fd957c Mon Sep 17 00:00:00 2001 From: l00397676 Date: Wed, 21 Nov 2018 19:32:43 +0800 Subject: [PATCH 145/264] qemu: add support for pidfile option Add input for -pidfile option of qemu, so that we can get pid of qemu main process, and apply resource limitations to it. Fixes #62 Signed-off-by: l00397676 --- qemu/qemu.go | 11 +++++++++++ qemu/qemu_test.go | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f26f093922..a7e637140e 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1516,6 +1516,9 @@ type Config struct { IOThreads []IOThread + // PidFile is the -pidfile parameter + PidFile string + qemuParams []string } @@ -1830,6 +1833,13 @@ func (config *Config) appendIncoming() { config.qemuParams = append(config.qemuParams, "-S", "-incoming", uri) } +func (config *Config) appendPidFile() { + if config.PidFile != "" { + config.qemuParams = append(config.qemuParams, "-pidfile") + config.qemuParams = append(config.qemuParams, config.PidFile) + } +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -1855,6 +1865,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendBios() config.appendIOThreads() config.appendIncoming() + config.appendPidFile() if err := config.appendCPUs(); err != nil { return "", err diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index e1c70e56d1..c74ef36b2b 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -738,7 +738,8 @@ func TestAppendQMPSocketServer(t *testing.T) { testAppend(qmp, qmpSocketServerString, t) } -var qemuString = "-name cc-qemu -cpu host -uuid " + agentUUID +var pidfile = "/run/vc/vm/iamsandboxid/pidfile" +var qemuString = "-name cc-qemu -cpu host -uuid " + agentUUID + " -pidfile " + pidfile func TestAppendStrings(t *testing.T) { config := Config{ @@ -746,11 +747,13 @@ func TestAppendStrings(t *testing.T) { Name: "cc-qemu", UUID: agentUUID, CPUModel: "host", + PidFile: pidfile, } config.appendName() config.appendCPUModel() config.appendUUID() + config.appendPidFile() result := strings.Join(config.qemuParams, " ") if result != qemuString { From a0b0c86e9c51e69d3ac5e56774548afda5405e1f Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Fri, 23 Nov 2018 09:33:21 +0800 Subject: [PATCH 146/264] qmp_test: Change QMP version from 2.6 to 2.9 Also change TestQMPXBlockdevDel to TestQMPBlockdevDel because QMP verion 2.9 and older use blockdev-del but not x-blockdev-del. Signed-off-by: Hui Zhu --- qemu/qmp_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index a1a784dd6e..592e70d48c 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -33,10 +33,10 @@ import ( const ( microStr = "50" - minorStr = "6" + minorStr = "9" majorStr = "2" micro = 50 - minor = 6 + minor = 9 major = 2 cap1 = "one" cap2 = "two" @@ -562,17 +562,17 @@ func TestQMPSCSIDeviceAdd(t *testing.T) { <-disconnectedCh } -// Checks that the x-blockdev-del command is correctly sent. +// Checks that the blockdev-del command is correctly sent. // -// We start a QMPLoop, send the x-blockdev-del command and stop the loop. +// We start a QMPLoop, send the blockdev-del command and stop the loop. // -// The x-blockdev-del command should be correctly sent and the QMP loop should +// The blockdev-del command should be correctly sent and the QMP loop should // exit gracefully. -func TestQMPXBlockdevDel(t *testing.T) { +func TestQMPBlockdevDel(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) - buf.AddCommand("x-blockdev-del", nil, "return", nil) + buf.AddCommand("blockdev-del", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) From 110d2fa0493db5992c3e0ab89d256576bf5ee86d Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Mon, 19 Nov 2018 12:33:05 +0800 Subject: [PATCH 147/264] qemu/qmp: add new function ExecuteBlockdevAddWithCache ExecuteBlockdevAddWithCache has two more parameters direct and noFlush than ExecuteBlockdevAdd. They are cache-related options for block devices that are described in https://github.com/qemu/qemu/blob/master/qapi/block-core.json. direct denotes whether use of O_DIRECT (bypass the host page cache) is enabled. noFlush denotes whether flush requests for the device are ignored. Signed-off-by: Hui Zhu --- qemu/qmp.go | 39 ++++++++++++++++++++++++++++++++++----- qemu/qmp_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 43daed369d..97d549ecee 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -722,11 +722,7 @@ func (q *QMP) ExecuteQuit(ctx context.Context) error { return q.executeCommand(ctx, "quit", nil, nil) } -// ExecuteBlockdevAdd sends a blockdev-add to the QEMU instance. device is the -// path of the device to add, e.g., /dev/rdb0, and blockdevID is an identifier -// used to name the device. As this identifier will be passed directly to QMP, -// it must obey QMP's naming rules, e,g., it must start with a letter. -func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) error { +func (q *QMP) blockdevAddBaseArgs(device, blockdevID string) (map[string]interface{}, map[string]interface{}) { var args map[string]interface{} blockdevArgs := map[string]interface{}{ @@ -747,6 +743,39 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) } } + return args, blockdevArgs +} + +// ExecuteBlockdevAdd sends a blockdev-add to the QEMU instance. device is the +// path of the device to add, e.g., /dev/rdb0, and blockdevID is an identifier +// used to name the device. As this identifier will be passed directly to QMP, +// it must obey QMP's naming rules, e,g., it must start with a letter. +func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) error { + args, _ := q.blockdevAddBaseArgs(device, blockdevID) + + return q.executeCommand(ctx, "blockdev-add", args, nil) +} + +// ExecuteBlockdevAddWithCache has two more parameters direct and noFlush +// than ExecuteBlockdevAdd. +// They are cache-related options for block devices that are described in +// https://github.com/qemu/qemu/blob/master/qapi/block-core.json. +// direct denotes whether use of O_DIRECT (bypass the host page cache) +// is enabled. noFlush denotes whether flush requests for the device are +// ignored. +func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevID string, direct, noFlush bool) error { + args, blockdevArgs := q.blockdevAddBaseArgs(device, blockdevID) + + if q.version.Major < 2 || (q.version.Major == 2 && q.version.Minor < 9) { + return fmt.Errorf("versions of qemu (%d.%d) older than 2.9 do not support set cache-related options for block devices", + q.version.Major, q.version.Minor) + } + + blockdevArgs["cache"] = map[string]interface{}{ + "direct": direct, + "no-flush": noFlush, + } + return q.executeCommand(ctx, "blockdev-add", args, nil) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 592e70d48c..7d158d1b85 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -403,6 +403,30 @@ func TestQMPBlockdevAdd(t *testing.T) { <-disconnectedCh } +// Checks that the blockdev-add with cache options command is correctly sent. +// +// We start a QMPLoop, send the blockdev-add with cache options +// command and stop the loop. +// +// The blockdev-add with cache options command should be correctly sent and +// the QMP loop should exit gracefully. +func TestQMPBlockdevAddWithCache(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("blockdev-add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + q.version = checkVersion(t, connectedCh) + err := q.ExecuteBlockdevAddWithCache(context.Background(), "/dev/rbd0", + fmt.Sprintf("drive_%s", volumeUUID), true, true) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the netdev_add command is correctly sent. // // We start a QMPLoop, send the netdev_add command and stop the loop. From e4892e33961be91b8bc132d5bf5677ae366038ba Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Tue, 20 Nov 2018 10:57:13 +0100 Subject: [PATCH 148/264] qemu/qmp: preparation for s390x support This PR prepares for the s390x support. It introduces: - a generalization of ccw and pci devices. The variables for the pci devices have been renamed by removing the Pci suffix. They have been moved to the qemu_arch_base.go - the mapping isVirtioPCI has been move to qemu_arch_base.go because in this way a different mapping can be added for other architecture (e.g s390x) - the functions QemuNetdevParam and QemuDeviceParam have been moved to qemu_arch_base.go. In this way, they could be reimplemented for other architecture for the case VHOSTUSER - a function disableModern has been introduced to check if the device is a pci device and then returns the right parameters. In the case of ccw devices, they don't have the disable-modern flag - a function mqParameter has been introduced to return the right parameters for the mq case. The virtio-net-ccw device doesn't have the vectors flag - in qemu_arch_base_test.go contains the test and strings that can be overwritten for other architectures (e.g s390). The devices names and the flags for the devices can be overwritten. - the string for the romfile has been replaced by a variable romfile that could be left empty if the devices doesn't support a romfile as for the ccw devices for s390. - clean-up: the disable-modern=on/off options have been changed to disable-modern=true/false. In the code there was a mixture of on/true off/false Fixes: #61 Co-authored-by: Yash D Jain Signed-off-by: Alice Frosi --- qemu/qemu.go | 176 ++++++++++++------------------------ qemu/qemu_arch_base.go | 103 +++++++++++++++++++++ qemu/qemu_arch_base_test.go | 99 ++++++++++++++++++++ qemu/qemu_test.go | 114 +++-------------------- qemu/qmp.go | 10 +- 5 files changed, 281 insertions(+), 221 deletions(-) create mode 100644 qemu/qemu_arch_base.go create mode 100644 qemu/qemu_arch_base_test.go diff --git a/qemu/qemu.go b/qemu/qemu.go index f26f093922..be23be84f2 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -62,18 +62,9 @@ const ( // NVDIMM is the Non Volatile DIMM device driver. NVDIMM DeviceDriver = "nvdimm" - // Virtio9P is the 9pfs device driver. - Virtio9P DeviceDriver = "virtio-9p-pci" - - // VirtioNet is the virt-io networking device driver. - VirtioNet DeviceDriver = "virtio-net" - // VirtioNetPCI is the virt-io pci networking device driver. VirtioNetPCI DeviceDriver = "virtio-net-pci" - // VirtioSerial is the serial device driver. - VirtioSerial DeviceDriver = "virtio-serial-pci" - // VirtioBlock is the block device driver. VirtioBlock DeviceDriver = "virtio-blk" @@ -83,9 +74,6 @@ const ( // VirtioSerialPort is the serial port device driver. VirtioSerialPort DeviceDriver = "virtserialport" - // VHostVSockPCI is the vhost vsock pci driver. - VHostVSockPCI DeviceDriver = "vhost-vsock-pci" - // VirtioRng is the paravirtualized RNG device driver. VirtioRng DeviceDriver = "virtio-rng" @@ -101,12 +89,6 @@ const ( //VhostUserBlk represents a block vhostuser device type. VhostUserBlk DeviceDriver = "vhost-user-blk-pci" - // VfioPCI represent a VFIO device type. - VfioPCI DeviceDriver = "vfio-pci" - - // VirtioScsiPCI represents a SCSI device type. - VirtioScsiPCI DeviceDriver = "virtio-scsi-pci" - // PCIBridgeDriver represents a PCI bridge device type. PCIBridgeDriver DeviceDriver = "pci-bridge" @@ -114,27 +96,19 @@ const ( PCIePCIBridgeDriver DeviceDriver = "pcie-pci-bridge" ) -// isVirtioPCI is a map indicating if a DeviceDriver is considered as a -// virtio PCI device, which is helpful to determine if the option "romfile" -// applies or not to this specific device. -var isVirtioPCI = map[DeviceDriver]bool{ - NVDIMM: false, - Virtio9P: true, - VirtioNet: true, - VirtioNetPCI: true, - VirtioSerial: true, - VirtioBlock: true, - Console: false, - VirtioSerialPort: false, - VHostVSockPCI: true, - VirtioRng: true, - VirtioBalloon: true, - VhostUserSCSI: true, - VhostUserBlk: true, - VfioPCI: true, - VirtioScsiPCI: true, - PCIBridgeDriver: true, - PCIePCIBridgeDriver: true, +// disableModern returns the parameters with the disable-modern option. +// In case the device driver is not a PCI device and it doesn't have the option +// an empty string is returned. +func (driver DeviceDriver) disableModern(disable bool) string { + if !isVirtioPCI[driver] { + return "" + } + + if disable { + return "disable-modern=true" + } + + return "disable-modern=false" } // ObjectType is a string representing a qemu object type. @@ -284,8 +258,8 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", fsdev.Driver)) - if fsdev.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if s := fsdev.Driver.disableModern(fsdev.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) @@ -370,8 +344,8 @@ func (cdev CharDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", cdev.Driver)) - if cdev.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if s := cdev.Driver.disableModern(cdev.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if cdev.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", cdev.Bus)) @@ -425,48 +399,6 @@ const ( VHOSTUSER NetDeviceType = "vhostuser" ) -// QemuNetdevParam converts to the QEMU -netdev parameter notation -func (n NetDeviceType) QemuNetdevParam() string { - switch n { - case TAP: - return "tap" - case MACVTAP: - return "tap" - case IPVTAP: - return "tap" - case VETHTAP: - return "tap" // -netdev type=tap -device virtio-net-pci - case VFIO: - return "" // -device vfio-pci (no netdev) - case VHOSTUSER: - return "vhost-user" // -netdev type=vhost-user (no device) - default: - return "" - - } -} - -// QemuDeviceParam converts to the QEMU -device parameter notation -func (n NetDeviceType) QemuDeviceParam() DeviceDriver { - switch n { - case TAP: - return "virtio-net-pci" - case MACVTAP: - return "virtio-net-pci" - case IPVTAP: - return "virtio-net-pci" - case VETHTAP: - return "virtio-net-pci" // -netdev type=tap -device virtio-net-pci - case VFIO: - return "vfio-pci" // -device vfio-pci (no netdev) - case VHOSTUSER: - return "" // -netdev type=vhost-user (no device) - default: - return "" - - } -} - // NetDevice represents a guest networking device type NetDevice struct { // Type is the netdev type (e.g. tap). @@ -527,6 +459,29 @@ func (netdev NetDevice) Valid() bool { } } +// mqParameter returns the parameters for multi-queue driver. If the driver is a PCI device then the +// vector flag is required. If the driver is a CCW type than the vector flag is not implemented and only +// multi-queue option mq needs to be activated. See comment in libvirt code at +// https://github.com/libvirt/libvirt/blob/6e7e965dcd3d885739129b1454ce19e819b54c25/src/qemu/qemu_command.c#L3633 +func (netdev NetDevice) mqParameter() string { + p := []string{",mq=on"} + + if isVirtioPCI[netdev.Driver] { + // https://www.linux-kvm.org/page/Multiqueue + // -netdev tap,vhost=on,queues=N + // enable mq and specify msix vectors in qemu cmdline + // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) + // -device virtio-net-pci,mq=on,vectors=2N+2... + // enable mq in guest by 'ethtool -L eth0 combined $queue_num' + // Clearlinux automatically sets up the queues properly + // The agent implementation should do this to ensure that it is + // always set + vectors := len(netdev.FDs)*2 + 2 + p = append(p, fmt.Sprintf(",vectors=%d", vectors)) + } + return strings.Join(p, "") +} + // QemuDeviceParams returns the -device parameters for this network device func (netdev NetDevice) QemuDeviceParams(config *Config) []string { var deviceParams []string @@ -549,26 +504,13 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) } } - - if netdev.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if s := netdev.Driver.disableModern(netdev.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if len(netdev.FDs) > 0 { - // https://www.linux-kvm.org/page/Multiqueue - // -netdev tap,vhost=on,queues=N - // enable mq and specify msix vectors in qemu cmdline - // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) - // -device virtio-net-pci,mq=on,vectors=2N+2... - // enable mq in guest by 'ethtool -L eth0 combined $queue_num' - // Clearlinux automatically sets up the queues properly - // The agent implementation should do this to ensure that it is - // always set - vectors := len(netdev.FDs)*2 + 2 - // Note: We are appending to the device params here - deviceParams = append(deviceParams, ",mq=on") - deviceParams = append(deviceParams, fmt.Sprintf(",vectors=%d", vectors)) + deviceParams = append(deviceParams, netdev.mqParameter()) } if isVirtioPCI[netdev.Driver] { @@ -683,8 +625,8 @@ func (dev SerialDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", dev.Driver)) - if dev.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if s := dev.Driver.disableModern(dev.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) if isVirtioPCI[dev.Driver] { @@ -761,8 +703,8 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { var qemuParams []string deviceParams = append(deviceParams, fmt.Sprintf("%s", blkdev.Driver)) - if blkdev.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if s := blkdev.Driver.disableModern(blkdev.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) if blkdev.SCSI == false { @@ -909,7 +851,7 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string - driver := VfioPCI + driver := Vfio deviceParams = append(deviceParams, fmt.Sprintf("%s,host=%s", driver, vfioDev.BDF)) if isVirtioPCI[driver] { @@ -956,7 +898,7 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { var qemuParams []string var devParams []string - driver := VirtioScsiPCI + driver := VirtioScsi devParams = append(devParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) if scsiCon.Bus != "" { devParams = append(devParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) @@ -964,8 +906,8 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { if scsiCon.Addr != "" { devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } - if scsiCon.DisableModern { - devParams = append(devParams, fmt.Sprintf("disable-modern=true")) + if s := driver.disableModern(scsiCon.DisableModern); s != "" { + devParams = append(devParams, fmt.Sprintf("%s", s)) } if scsiCon.IOThread != "" { devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) @@ -1087,7 +1029,9 @@ type VSOCKDevice struct { const ( // MinimalGuestCID is the smallest valid context ID for a guest. MinimalGuestCID uint32 = 3 +) +const ( // VSOCKGuestCID is the VSOCK guest CID parameter. VSOCKGuestCID = "guest-cid" ) @@ -1106,10 +1050,10 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - driver := VHostVSockPCI + driver := VHostVSock deviceParams = append(deviceParams, fmt.Sprintf("%s", driver)) - if vsock.DisableModern { - deviceParams = append(deviceParams, ",disable-modern=true") + if s := driver.disableModern(vsock.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if vsock.VHostFD != nil { qemuFDs := config.appendFDs([]*os.File{vsock.VHostFD}) @@ -1223,13 +1167,9 @@ func (b BalloonDevice) QemuParams(_ *Config) []string { } else { deviceParams = append(deviceParams, "deflate-on-oom=off") } - - if b.DisableModern { - deviceParams = append(deviceParams, "disable-modern=on") - } else { - deviceParams = append(deviceParams, "disable-modern=off") + if s := driver.disableModern(b.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf("%s", s)) } - qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go new file mode 100644 index 0000000000..0db1ecaedd --- /dev/null +++ b/qemu/qemu_arch_base.go @@ -0,0 +1,103 @@ +// +build !s390x + +/* +// 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 + +const ( + // Virtio9P is the 9pfs device driver. + Virtio9P DeviceDriver = "virtio-9p-pci" + + // VirtioSerial is the serial device driver. + VirtioSerial DeviceDriver = "virtio-serial-pci" + + // VirtioNet is the virt-io pci networking device driver. + VirtioNet DeviceDriver = VirtioNetPCI + + // Vfio is the vfio driver + Vfio DeviceDriver = "vfio-pci" + + // VirtioScsi is the virtio-scsi device + VirtioScsi DeviceDriver = "virtio-scsi-pci" + + // VHostVSock is a generic Vsock vhost device + VHostVSock DeviceDriver = "vhost-vsock-pci" +) + +// isVirtioPCI is a map indicating if a DeviceDriver is considered as a +// virtio PCI device, which is helpful to determine if the option "romfile" +// applies or not to this specific device. +var isVirtioPCI = map[DeviceDriver]bool{ + NVDIMM: false, + Virtio9P: true, + VirtioNetPCI: true, + VirtioSerial: true, + VirtioBlock: true, + Console: false, + VirtioSerialPort: false, + VHostVSock: true, + VirtioRng: true, + VirtioBalloon: true, + VhostUserSCSI: true, + VhostUserBlk: true, + Vfio: true, + VirtioScsi: true, + PCIBridgeDriver: true, + PCIePCIBridgeDriver: true, +} + +// QemuNetdevParam converts to the QEMU -netdev parameter notation +func (n NetDeviceType) QemuNetdevParam() string { + switch n { + case TAP: + return "tap" + case MACVTAP: + return "tap" + case IPVTAP: + return "tap" + case VETHTAP: + return "tap" // -netdev type=tap -device virtio-net-pci + case VFIO: + return "" // -device vfio-pci (no netdev) + case VHOSTUSER: + return "vhost-user" // -netdev type=vhost-user (no device) + default: + return "" + + } +} + +// QemuDeviceParam converts to the QEMU -device parameter notation +func (n NetDeviceType) QemuDeviceParam() DeviceDriver { + switch n { + case TAP: + return "virtio-net-pci" + case MACVTAP: + return "virtio-net-pci" + case IPVTAP: + return "virtio-net-pci" + case VETHTAP: + return "virtio-net-pci" // -netdev type=tap -device virtio-net-pci + case VFIO: + return "vfio-pci" // -device vfio-pci (no netdev) + case VHOSTUSER: + return "" // -netdev type=vhost-user (no device) + default: + return "" + + } +} diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go new file mode 100644 index 0000000000..a30b3bdbd8 --- /dev/null +++ b/qemu/qemu_arch_base_test.go @@ -0,0 +1,99 @@ +// +build !s390x + +/* +// 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 "testing" + +var ( + deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" + deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,romfile=efi-virtio.rom" + deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" + deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,romfile=efi-virtio.rom" + deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" + deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom" + deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" + deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" + deviceVFIOString = "-device vfio-pci,host=02:10.0,romfile=efi-virtio.rom" + deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,disable-modern=false,romfile=efi-virtio.rom" + deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" + deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" + deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" + deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" + devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" + romfile = "efi-virtio.rom" +) + +func TestAppendDeviceVhostUser(t *testing.T) { + + vhostuserBlkDevice := VhostUserDevice{ + SocketPath: "/tmp/nonexistentsocket.socket", + CharDevID: "char2", + TypeDevID: "", + Address: "", + VhostUserType: VhostUserBlk, + ROMFile: romfile, + } + testAppend(vhostuserBlkDevice, deviceVhostUserBlkString, t) + + vhostuserSCSIDevice := VhostUserDevice{ + SocketPath: "/tmp/nonexistentsocket.socket", + CharDevID: "char1", + TypeDevID: "scsi1", + Address: "", + VhostUserType: VhostUserSCSI, + ROMFile: romfile, + } + testAppend(vhostuserSCSIDevice, deviceVhostUserSCSIString, t) + + vhostuserNetDevice := VhostUserDevice{ + SocketPath: "/tmp/nonexistentsocket.socket", + CharDevID: "char1", + TypeDevID: "net1", + Address: "00:11:22:33:44:55", + VhostUserType: VhostUserNet, + ROMFile: romfile, + } + testAppend(vhostuserNetDevice, deviceVhostUserNetString, t) +} + +func TestAppendVirtioBalloon(t *testing.T) { + balloonDevice := BalloonDevice{ + ID: "balloon", + ROMFile: romfile, + } + + var deviceString = "-device " + string(VirtioBalloon) + deviceString += ",id=" + balloonDevice.ID + ",romfile=" + balloonDevice.ROMFile + + var OnDeflateOnOMM = ",deflate-on-oom=on" + var OffDeflateOnOMM = ",deflate-on-oom=off" + + var OnDisableModern = ",disable-modern=true" + var OffDisableModern = ",disable-modern=false" + + testAppend(balloonDevice, deviceString+OffDeflateOnOMM+OffDisableModern, t) + + balloonDevice.DeflateOnOOM = true + testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OffDisableModern, t) + + balloonDevice.DisableModern = true + testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OnDisableModern, t) + +} diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index e1c70e56d1..5dcdb8aebd 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -126,8 +126,6 @@ func TestAppendDeviceNVDIMM(t *testing.T) { testAppend(object, deviceNVDIMMString, t) } -var deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" - func TestAppendDeviceFS(t *testing.T) { fsdev := FSDevice{ Driver: Virtio9P, @@ -143,8 +141,6 @@ func TestAppendDeviceFS(t *testing.T) { testAppend(fsdev, deviceFSString, t) } -var deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,romfile=efi-virtio.rom" - func TestAppendDeviceNetwork(t *testing.T) { netdev := NetDevice{ Driver: VirtioNet, @@ -162,8 +158,6 @@ func TestAppendDeviceNetwork(t *testing.T) { testAppend(netdev, deviceNetworkString, t) } -var deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" - func TestAppendDeviceNetworkMq(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") @@ -192,12 +186,10 @@ func TestAppendDeviceNetworkMq(t *testing.T) { testAppend(netdev, deviceNetworkStringMq, t) } -var deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,romfile=efi-virtio.rom" - func TestAppendDeviceNetworkPCI(t *testing.T) { netdev := NetDevice{ - Driver: VirtioNetPCI, + Driver: VirtioNet, Type: TAP, ID: "tap0", IFName: "ceth0", @@ -208,14 +200,12 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { VHost: true, MACAddress: "01:02:de:ad:be:ef", DisableModern: true, - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(netdev, deviceNetworkPCIString, t) } -var deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" - func TestAppendDeviceNetworkPCIMq(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") @@ -228,7 +218,7 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { }() netdev := NetDevice{ - Driver: VirtioNetPCI, + Driver: VirtioNet, Type: TAP, ID: "tap0", IFName: "ceth0", @@ -240,20 +230,18 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { VHost: true, MACAddress: "01:02:de:ad:be:ef", DisableModern: true, - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(netdev, deviceNetworkPCIStringMq, t) } -var deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom" - func TestAppendDeviceSerial(t *testing.T) { sdev := SerialDevice{ Driver: VirtioSerial, ID: "serial0", DisableModern: true, - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(sdev, deviceSerialString, t) @@ -274,8 +262,6 @@ func TestAppendDeviceSerialPort(t *testing.T) { testAppend(chardev, deviceSerialPortString, t) } -var deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" - func TestAppendDeviceBlock(t *testing.T) { blkdev := BlockDevice{ Driver: VirtioBlock, @@ -287,69 +273,28 @@ func TestAppendDeviceBlock(t *testing.T) { SCSI: false, WCE: false, DisableModern: true, - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(blkdev, deviceBlockString, t) } -var deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" -var deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" -var deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" - -func TestAppendDeviceVhostUser(t *testing.T) { - - vhostuserBlkDevice := VhostUserDevice{ - SocketPath: "/tmp/nonexistentsocket.socket", - CharDevID: "char2", - TypeDevID: "", - Address: "", - VhostUserType: VhostUserBlk, - ROMFile: "efi-virtio.rom", - } - testAppend(vhostuserBlkDevice, deviceVhostUserBlkString, t) - - vhostuserSCSIDevice := VhostUserDevice{ - SocketPath: "/tmp/nonexistentsocket.socket", - CharDevID: "char1", - TypeDevID: "scsi1", - Address: "", - VhostUserType: VhostUserSCSI, - ROMFile: "efi-virtio.rom", - } - testAppend(vhostuserSCSIDevice, deviceVhostUserSCSIString, t) - - vhostuserNetDevice := VhostUserDevice{ - SocketPath: "/tmp/nonexistentsocket.socket", - CharDevID: "char1", - TypeDevID: "net1", - Address: "00:11:22:33:44:55", - VhostUserType: VhostUserNet, - ROMFile: "efi-virtio.rom", - } - testAppend(vhostuserNetDevice, deviceVhostUserNetString, t) -} - -var deviceVFIOString = "-device vfio-pci,host=02:10.0,romfile=efi-virtio.rom" - func TestAppendDeviceVFIO(t *testing.T) { vfioDevice := VFIODevice{ BDF: "02:10.0", - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(vfioDevice, deviceVFIOString, t) } -var deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" - func TestAppendVSOCK(t *testing.T) { vsockDevice := VSOCKDevice{ ID: "vhost-vsock-pci0", ContextID: 4, VHostFD: nil, DisableModern: true, - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(vsockDevice, deviceVSOCKString, t) @@ -376,10 +321,13 @@ func TestVSOCKValid(t *testing.T) { func TestAppendVirtioRng(t *testing.T) { var objectString = "-object rng-random,id=rng0" - var deviceString = "-device " + string(VirtioRng) + ",rng=rng0,romfile=efi-virtio.rom" + var deviceString = "-device " + string(VirtioRng) + ",rng=rng0" + if romfile != "" { + deviceString = deviceString + ",romfile=efi-virtio.rom" + } rngDevice := RngDevice{ ID: "rng0", - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(rngDevice, objectString+" "+deviceString, t) @@ -417,31 +365,6 @@ func TestVirtioRngValid(t *testing.T) { } -func TestAppendVirtioBalloon(t *testing.T) { - balloonDevice := BalloonDevice{ - ID: "balloon", - ROMFile: "efi-virtio.rom", - } - - var deviceString = "-device " + string(VirtioBalloon) - deviceString += ",id=" + balloonDevice.ID + ",romfile=" + balloonDevice.ROMFile - - var OnDeflateOnOMM = ",deflate-on-oom=on" - var OffDeflateOnOMM = ",deflate-on-oom=off" - - var OnDisableModern = ",disable-modern=on" - var OffDisableModern = ",disable-modern=off" - - testAppend(balloonDevice, deviceString+OffDeflateOnOMM+OffDisableModern, t) - - balloonDevice.DeflateOnOOM = true - testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OffDisableModern, t) - - balloonDevice.DisableModern = true - testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OnDisableModern, t) - -} - func TestVirtioBalloonValid(t *testing.T) { balloon := BalloonDevice{ ID: "", @@ -457,13 +380,10 @@ func TestVirtioBalloonValid(t *testing.T) { } } -var deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,romfile=efi-virtio.rom" -var deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" - func TestAppendDeviceSCSIController(t *testing.T) { scsiCon := SCSIController{ ID: "foo", - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(scsiCon, deviceSCSIControllerStr, t) @@ -474,8 +394,6 @@ func TestAppendDeviceSCSIController(t *testing.T) { testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } -var devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" - func TestAppendPCIBridgeDevice(t *testing.T) { bridge := BridgeDevice{ @@ -485,14 +403,12 @@ func TestAppendPCIBridgeDevice(t *testing.T) { Addr: "255", Chassis: 5, SHPC: true, - ROMFile: "efi-virtio.rom", + ROMFile: romfile, } testAppend(bridge, devicePCIBridgeString, t) } -var devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" - func TestAppendPCIEBridgeDevice(t *testing.T) { bridge := BridgeDevice{ diff --git a/qemu/qmp.go b/qemu/qmp.go index 43daed369d..7f89bdbda3 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -991,7 +991,7 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, romfile string) error { args := map[string]interface{}{ "id": devID, - "driver": "vfio-pci", + "driver": Vfio, "host": bdf, "romfile": romfile, } @@ -1006,11 +1006,12 @@ func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, romfile stri func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus, romfile string) error { args := map[string]interface{}{ "id": devID, - "driver": "vfio-pci", + "driver": Vfio, "host": bdf, "addr": addr, "romfile": romfile, } + if bus != "" { args["bus"] = bus } @@ -1025,10 +1026,11 @@ func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus, romfile string) error { args := map[string]interface{}{ "id": devID, - "driver": "vfio-pci", + "driver": Vfio, "sysfsdev": sysfsdev, "romfile": romfile, } + if bus != "" { args["bus"] = bus } @@ -1215,7 +1217,7 @@ func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { // ExecutePCIVSockAdd adds a vhost-vsock-pci bus func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus, romfile string, disableModern bool) error { args := map[string]interface{}{ - "driver": VHostVSockPCI, + "driver": VHostVSock, "id": id, "guest-cid": guestCID, "vhostfd": vhostfd, From 7da6a4c7c630d403aae927d040f47dbe25ed1194 Mon Sep 17 00:00:00 2001 From: NingBo Date: Tue, 27 Nov 2018 15:38:57 +0800 Subject: [PATCH 149/264] qmp: fix mem-path properties for hotplug memory. The QMP command 'object-add' only has three arguments: 'qom-type' 'id' and 'props', thus 'mem-path' has to be saved in 'props'. https://github.com/qemu/qemu/blob/stable-2.0/qapi-schema.json#L2958 https://github.com/qemu/qemu/blob/stable-2.12/qapi/misc.json#L1846 Signed-off-by: NingBo --- qemu/qmp.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 43daed369d..2356503ac6 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1170,13 +1170,14 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { // ExecHotplugMemory adds size of MiB memory to the guest func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int) error { + props := map[string]interface{}{"size": uint64(size) << 20} args := map[string]interface{}{ "qom-type": qomtype, "id": id, - "props": map[string]interface{}{"size": uint64(size) << 20}, + "props": props, } if mempath != "" { - args["mem-path"] = mempath + props["mem-path"] = mempath } err := q.executeCommand(ctx, "object-add", args, nil) if err != nil { From f30fd1354a01c45a538541db091934d6e687ebcb Mon Sep 17 00:00:00 2001 From: NingBo Date: Thu, 29 Nov 2018 16:20:03 +0800 Subject: [PATCH 150/264] qmp: Output error detail when execute QMP command failed Only get 'QMP command failed' error message now when execute QMP command by 'executeCommandWithResponse' failed. This patch will output more error detail. Signed-off-by: NingBo --- qemu/qmp.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 43daed369d..a5da684ef7 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -313,7 +313,7 @@ func (q *QMP) finaliseCommandWithResponse(cmdEl *list.Element, cmdQueue *list.Li if succeeded { cmd.res <- qmpResult{response: response} } else { - cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed")} + cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed: %v", response)} } } if cmdQueue.Len() > 0 { @@ -325,6 +325,23 @@ func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeede q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, nil) } +func (q *QMP) errorDesc(errorData interface{}) (string, error) { + // convert error to json + data, err := json.Marshal(errorData) + if err != nil { + return "", fmt.Errorf("Unable to extract error information: %v", err) + } + + // see: https://github.com/qemu/qemu/blob/stable-2.12/qapi/qmp-dispatch.c#L125 + var qmpErr map[string]string + // convert json to qmpError + if err = json.Unmarshal(data, &qmpErr); err != nil { + return "", fmt.Errorf("Unable to convert json to qmpError: %v", err) + } + + return qmpErr["desc"], nil +} + func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { var vmData map[string]interface{} err := json.Unmarshal(line, &vmData) @@ -339,7 +356,7 @@ func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { } response, succeeded := vmData["return"] - _, failed := vmData["error"] + errData, failed := vmData["error"] if !succeeded && !failed { return @@ -353,6 +370,14 @@ func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { } cmd := cmdEl.Value.(*qmpCommand) if failed || cmd.filter == nil { + if errData != nil { + desc, err := q.errorDesc(errData) + if err != nil { + q.cfg.Logger.Infof("Get error description failed: %v", err) + } else { + response = desc + } + } q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, response) } else { cmd.resultReceived = true From 3becff5f4e33b1ee4ea37df1846e280c33379daf Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Thu, 29 Nov 2018 13:07:54 +0100 Subject: [PATCH 151/264] qemu: change of ContextID from uint32 to uint64 The correct type used by qemu and in kernel is uint64 and this leads to an endianess problem with ioctl system call. See the issue https://github.com/kata-containers/runtime/issues/947 Fixes: #70 Signed-off-by: Alice Frosi --- qemu/qemu.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index e10fcb5f81..d1dcecdc86 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1014,7 +1014,7 @@ func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { type VSOCKDevice struct { ID string - ContextID uint32 + ContextID uint64 // VHostFD vhost file descriptor that holds the ContextID VHostFD *os.File @@ -1028,7 +1028,10 @@ type VSOCKDevice struct { const ( // MinimalGuestCID is the smallest valid context ID for a guest. - MinimalGuestCID uint32 = 3 + MinimalGuestCID uint64 = 3 + + // MaxGuestCID is the largest valid context ID for a guest. + MaxGuestCID uint64 = 1<<32 - 1 ) const ( @@ -1038,7 +1041,7 @@ const ( // Valid returns true if the VSOCKDevice structure is valid and complete. func (vsock VSOCKDevice) Valid() bool { - if vsock.ID == "" || vsock.ContextID < MinimalGuestCID { + if vsock.ID == "" || vsock.ContextID < MinimalGuestCID || vsock.ContextID > MaxGuestCID { return false } From b3b765cbe620fa09dc2471d870719f98d6cf984d Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Thu, 29 Nov 2018 12:29:46 +0000 Subject: [PATCH 152/264] qemu: test Valid for Vsock for Context ID Add test for the validation when the Context ID is larger than 32 bits Signed-off-by: Alice Frosi --- qemu/qemu_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 8dadc465fe..528ef4b9d3 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -312,6 +312,12 @@ func TestVSOCKValid(t *testing.T) { t.Fatalf("VSOCK Context ID is not valid") } + vsockDevice.ContextID = MaxGuestCID + 1 + + if vsockDevice.Valid() { + t.Fatalf("VSOCK Context ID is not valid") + } + vsockDevice.ID = "" if vsockDevice.Valid() { From 2b7db5473fcbf6df2597c0595d6cdc86dde44e5e Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 30 Nov 2018 09:18:31 +0100 Subject: [PATCH 153/264] Add the CONTRIBUTORS.md file This file is a partial list of contributors to the Virtual Machine Manager for Go project. To see the full list of contributors, see the revision history in source control. Contributors who wish to be recognized in this file should add themselves (or their employer, as appropriate). Signed-off-by: Mark Ryan --- CONTRIBUTORS.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000000..7f0cbf4621 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,18 @@ +This file is a partial list of contributors to the Virtual Machine +Manager for Go project. To see the full list of contributors, see the +revision history in source control. + +Contributors who wish to be recognized in this file should add +themselves (or their employer, as appropriate). + +- archana.m.shinde@intel.com +- eric.ernst@intel.com +- james.o.hunt@intel.com +- jose.carlos.venegas.munoz@intel.com +- julio.montes@intel.com +- manohar.r.castelino@intel.com +- mark.d.ryan@intel.com +- robert.bradford@intel.com +- sameo@linux.intel.com +- sebastien.boeuf@intel.com +- xinda.zhao@intel.com From e68e0056977cc10c1f0122920563651f08bc0899 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 30 Nov 2018 09:20:38 +0100 Subject: [PATCH 154/264] Update the CONTRIBUTING.md The CONTRIBUTING.md file is updated to provide a template for new source files and to invite contributors to add themselves to the CONTRIBUTORS.md file. Signed-off-by: Mark Ryan --- CONTRIBUTING.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b97d45671e..99630bccf0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,6 +44,38 @@ Fixes #1 Signed-off-by: Mark Ryan ``` +## New files + +Each Go source file in the Virtual Machine Manager for Go project must +contain the following header: + +``` +/* +// Copyright contributors to the Virtual Machine Manager for Go project +// +// 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. +*/ +``` + +## Contributors File + +This CONTRIBUTORS.md file is a partial list of contributors to the +Virtual Machine Manager for Go project. To see the full list of +contributors, see the revision history in source control. + +Contributors who wish to be recognized in this file should add +themselves (or their employer, as appropriate). + ## Pull requests We accept github pull requests. From ca477a18b6101947ad82bbc4b3578ff0113cf099 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 30 Nov 2018 09:25:18 +0100 Subject: [PATCH 155/264] Update source file headers This commit updates the headers in the Go source files to adhere to the new guidelines in the CONTRIBUTING.md file. Signed-off-by: Mark Ryan --- qemu/examples_test.go | 2 +- qemu/image.go | 2 +- qemu/qemu.go | 2 +- qemu/qemu_arch_base.go | 2 +- qemu/qemu_arch_base_test.go | 2 +- qemu/qemu_test.go | 2 +- qemu/qmp.go | 2 +- qemu/qmp_test.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qemu/examples_test.go b/qemu/examples_test.go index c3930a1e18..737d58913e 100644 --- a/qemu/examples_test.go +++ b/qemu/examples_test.go @@ -1,5 +1,5 @@ /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/image.go b/qemu/image.go index b2f906f7cb..de12333c45 100644 --- a/qemu/image.go +++ b/qemu/image.go @@ -1,5 +1,5 @@ /* -// Copyright (c) 2017 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/qemu.go b/qemu/qemu.go index d1dcecdc86..7acc3c2559 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1,5 +1,5 @@ /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index 0db1ecaedd..5e293d1d62 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -1,7 +1,7 @@ // +build !s390x /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index a30b3bdbd8..64a8415b49 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -1,7 +1,7 @@ // +build !s390x /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 528ef4b9d3..622f47ea70 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -1,5 +1,5 @@ /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/qmp.go b/qemu/qmp.go index 1fb33bdb28..8d3cd2fe17 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1,5 +1,5 @@ /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 7d158d1b85..a28e0e91c9 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1,5 +1,5 @@ /* -// Copyright (c) 2016 Intel Corporation +// Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From c80fc3b12f4e9aa3fddf67976125793dd303d055 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Wed, 21 Nov 2018 14:33:04 +0100 Subject: [PATCH 156/264] qemu: Add s390x support The PR adds the s390x support. It sets the CCW devices and sets to false all the devices in the mapping isVirtioPCI. It reimplements the functions QemuNetdevParam and QemuDeviceParam to print an error message if the vhost-user devices are used. It introduces a new function ExecuteNetCCWDeviceAdd for qmp for the CCW devices. Fixes: #37 Co-authored-by: Yash D Jain Signed-off-by: Alice Frosi --- qemu/qemu.go | 3 ++ qemu/qemu_s390x.go | 115 ++++++++++++++++++++++++++++++++++++++++ qemu/qemu_s390x_test.go | 57 ++++++++++++++++++++ qemu/qmp.go | 20 +++++++ qemu/qmp_test.go | 16 ++++++ 5 files changed, 211 insertions(+) create mode 100644 qemu/qemu_s390x.go create mode 100644 qemu/qemu_s390x_test.go diff --git a/qemu/qemu.go b/qemu/qemu.go index 7acc3c2559..8bd6afbef0 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -65,6 +65,9 @@ const ( // VirtioNetPCI is the virt-io pci networking device driver. VirtioNetPCI DeviceDriver = "virtio-net-pci" + // VirtioNetCCW is the virt-io ccw networking device driver. + VirtioNetCCW DeviceDriver = "virtio-net-ccw" + // VirtioBlock is the block device driver. VirtioBlock DeviceDriver = "virtio-blk" diff --git a/qemu/qemu_s390x.go b/qemu/qemu_s390x.go new file mode 100644 index 0000000000..d8395837fc --- /dev/null +++ b/qemu/qemu_s390x.go @@ -0,0 +1,115 @@ +// +build s390x + +/* +// Copyright contributors to the Virtual Machine Manager for Go project +// +// 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 "log" + +// IBM Z uses CCW devices intead of PCI devices. +// See https://wiki.qemu.org/Documentation/Platforms/S390X +const ( + // Virtio9P is the 9pfs device driver. + Virtio9P DeviceDriver = "virtio-9p-ccw" + + // VirtioSerial is the serial device driver. + VirtioSerial DeviceDriver = "virtio-serial-ccw" + + // VirtioNet is the virt-io ccw networking device driver. + VirtioNet DeviceDriver = VirtioNetCCW + + // Vfio is the vfio driver + Vfio DeviceDriver = "vfio-ccw" + + // VirtioScsi is the virtio-scsi device + VirtioScsi DeviceDriver = "virtio-scsi-ccw" + + // VHostVSock is a generic Vsock Device + VHostVSock DeviceDriver = "vhost-vsock-ccw" +) + +// isVirtioPCI is a fake map on s390x to always avoid the "romfile" +// option +var isVirtioPCI = map[DeviceDriver]bool{ + NVDIMM: false, + Virtio9P: false, + VirtioNetCCW: false, + VirtioSerial: false, + VirtioBlock: false, + Console: false, + VirtioSerialPort: false, + VHostVSock: false, + VirtioRng: false, + VirtioBalloon: false, + VhostUserSCSI: false, + VhostUserBlk: false, + Vfio: false, + VirtioScsi: false, + PCIBridgeDriver: false, + PCIePCIBridgeDriver: false, +} + +// QemuDeviceParam converts to the QEMU -device parameter notation +// This function has been reimplemented for the s390x architecture to deal +// with the VHOSTUSER case. Vhost user devices are not implemented on s390x +// architecture. For further details see issue +// https://github.com/kata-containers/runtime/issues/659 +func (n NetDeviceType) QemuDeviceParam() string { + switch n { + case TAP: + return string(VirtioNet) + case MACVTAP: + return string(VirtioNet) + case IPVTAP: + return string(VirtioNet) + case VETHTAP: + return string(VirtioNet) + case VFIO: + return string(Vfio) + case VHOSTUSER: + log.Fatal("vhost-user devices are not supported on IBM Z") + return "" + default: + return "" + } +} + +// QemuNetdevParam converts to the QEMU -netdev parameter notation +// This function has been reimplemented for the s390x architecture to deal +// with the VHOSTUSER case. Vhost user devices are not implemented on s390x +// architecture. For further details see issue +// https://github.com/kata-containers/runtime/issues/659 +func (n NetDeviceType) QemuNetdevParam() string { + switch n { + case TAP: + return "tap" + case MACVTAP: + return "tap" + case IPVTAP: + return "tap" + case VETHTAP: + return "tap" + case VFIO: + return "" + case VHOSTUSER: + log.Fatal("vhost-user devices are not supported on IBM Z") + return "" + default: + return "" + + } +} diff --git a/qemu/qemu_s390x_test.go b/qemu/qemu_s390x_test.go new file mode 100644 index 0000000000..b76af3ced5 --- /dev/null +++ b/qemu/qemu_s390x_test.go @@ -0,0 +1,57 @@ +// +build s390x + +/* +// Copyright contributors to the Virtual Machine Manager for Go project +// +// 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 "testing" + +// -pci devices don't play well with Z hence replace them with corresponding -ccw devices +// See https://wiki.qemu.org/Documentation/Platforms/S390X +var ( + deviceFSString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" + deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef" + deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,mq=on" + deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff" + deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,mq=on" + deviceSerialString = "-device virtio-serial-ccw,id=serial0" + deviceVSOCKString = "-device vhost-vsock-ccw,id=vhost-vsock-pci0,guest-cid=4" + deviceVFIOString = "-device vfio-ccw,host=02:10.0" + deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo" + deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1" + deviceBlockString = "-device virtio-blk,drive=hd0,scsi=off,config-wce=off -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" + devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" + romfile = "" +) + +func TestAppendVirtioBalloon(t *testing.T) { + balloonDevice := BalloonDevice{ + ID: "balloon", + } + + var deviceString = "-device " + string(VirtioBalloon) + deviceString += ",id=" + balloonDevice.ID + + var OnDeflateOnOMM = ",deflate-on-oom=on" + var OffDeflateOnOMM = ",deflate-on-oom=off" + + testAppend(balloonDevice, deviceString+OffDeflateOnOMM, t) + + balloonDevice.DeflateOnOOM = true + testAppend(balloonDevice, deviceString+OnDeflateOnOMM, t) +} diff --git a/qemu/qmp.go b/qemu/qmp.go index ef56415fbe..98371e579f 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -971,6 +971,26 @@ func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAd return q.executeCommand(ctx, "device_add", args, nil) } +// ExecuteNetCCWDeviceAdd adds a Net CCW device to a QEMU instance +// using the device_add command. devID is the id of the device to add. +// Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. +// queues is the number of queues of a nic. +func (q *QMP) ExecuteNetCCWDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus string, queues int) error { + args := map[string]interface{}{ + "id": devID, + "driver": VirtioNetCCW, + "netdev": netdevID, + "mac": macAddr, + "addr": addr, + } + + if queues > 0 { + args["mq"] = "on" + } + + return q.executeCommand(ctx, "device_add", args, nil) +} + // ExecuteDeviceDel deletes guest portion of a QEMU device by sending a // device_del command. devId is the identifier of the device to delete. // Typically it would match the devID parameter passed to an earlier call diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index a28e0e91c9..270c473136 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -536,6 +536,22 @@ func TestQMPNetPCIDeviceAdd(t *testing.T) { <-disconnectedCh } +func TestQMPNetCCWDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteNetCCWDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", 8) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the device_add command is correctly sent. // // We start a QMPLoop, send the device_add command and stop the loop. From ee75813ad11d70b39084fefd00018f2ede395372 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Fri, 30 Nov 2018 10:14:45 +0100 Subject: [PATCH 157/264] contributors: add my name Signed-off-by: Alice Frosi --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7f0cbf4621..905f08ff9f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,6 +5,7 @@ revision history in source control. Contributors who wish to be recognized in this file should add themselves (or their employer, as appropriate). +- afrosi@de.ibm.com - archana.m.shinde@intel.com - eric.ernst@intel.com - james.o.hunt@intel.com From 5ea6da1448375ff81b77c0590845b07418c6c547 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 30 Nov 2018 15:20:26 +0100 Subject: [PATCH 158/264] Verify govmm builds on s390x This commit adds a single command to the travis script that checks that the s390x build works. We can't run the unit tests but at least we can check that everything builds on this architecture. Signed-off-by: Mark Ryan --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f23679412b..1cfe2ef036 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ script: - go env - $GOPATH/bin/goveralls -v -service=travis-ci - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... + - GOARCH=s390x go install ./... From dab4cf1d70801c4c78ce47dbbd2d2c1300a399f4 Mon Sep 17 00:00:00 2001 From: NingBo Date: Mon, 3 Dec 2018 14:40:26 +0800 Subject: [PATCH 159/264] qmp: Add tests Test execute QMP command with error response. Signed-off-by: NingBo --- qemu/qmp_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index a1a784dd6e..0d9dd04628 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1363,3 +1363,86 @@ func TestExecuteBalloon(t *testing.T) { q.Shutdown() <-disconnectedCh } + +func TestErrorDesc(t *testing.T) { + errDesc := "Somthing err messages" + errData := map[string]string{ + "class": "GenericError", + "desc": errDesc, + } + + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + + desc, err := q.errorDesc(errData) + if err != nil { + t.Fatalf("Unexpected error '%v'", err) + } + if desc != errDesc { + t.Fatalf("expected '%v'\n got '%v'", errDesc, desc) + } + + q.Shutdown() + <-disconnectedCh +} + +func TestExecCommandFailed(t *testing.T) { + errDesc := "unable to map backing store for guest RAM: Cannot allocate memory" + errData := map[string]string{ + "class": "GenericError", + "desc": errDesc, + } + + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("object-add", nil, "error", errData) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + + _, err := q.executeCommandWithResponse(context.Background(), "object-add", nil, nil, nil) + if err == nil { + t.Fatalf("expected error but got nil") + } + + expectedString := "QMP command failed: " + errDesc + if err.Error() != expectedString { + t.Fatalf("expected '%v' but got '%v'", expectedString, err) + } + + q.Shutdown() + <-disconnectedCh +} + +func TestExecCommandFailedWithInnerError(t *testing.T) { + errData := map[string]string{ + "class": "GenericError", + "descFieldInvalid": "Invalid", + } + + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("object-add", nil, "error", errData) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + + _, err := q.executeCommandWithResponse(context.Background(), "object-add", nil, nil, nil) + if err == nil { + t.Fatalf("expected error but got nil") + } + + expectedString := "QMP command failed: " + if err.Error() != expectedString { + t.Fatalf("expected '%v' but got '%v'", expectedString, err) + } + + q.Shutdown() + <-disconnectedCh +} From b41939c6b4da69a53ea2239f22845ae40cb50a12 Mon Sep 17 00:00:00 2001 From: Ruidong Cao Date: Mon, 3 Dec 2018 20:48:35 +0800 Subject: [PATCH 160/264] Contributors: Add my name Signed-off-by: Ruidong Cao --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 905f08ff9f..acfe104bd4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,6 +7,7 @@ themselves (or their employer, as appropriate). - afrosi@de.ibm.com - archana.m.shinde@intel.com +- caoruidong@huawei.com - eric.ernst@intel.com - james.o.hunt@intel.com - jose.carlos.venegas.munoz@intel.com From b36b5a8f672d62766f70f373ce9f64015e4443fe Mon Sep 17 00:00:00 2001 From: Clare Chen Date: Mon, 3 Dec 2018 06:22:11 -0500 Subject: [PATCH 161/264] Contributors: Add Clare Chen to CONTRIBUTORS.md Signed-off-by: Clare Chen --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index acfe104bd4..c159b9a1f0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -8,6 +8,7 @@ themselves (or their employer, as appropriate). - afrosi@de.ibm.com - archana.m.shinde@intel.com - caoruidong@huawei.com +- clare.chenhui@huawei.com - eric.ernst@intel.com - james.o.hunt@intel.com - jose.carlos.venegas.munoz@intel.com From d6173077f12c029d8d4c70f500a3602af88b4337 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 3 Dec 2018 15:53:16 +0100 Subject: [PATCH 162/264] Run tests for the s390x build It turns out it is possible to run the unit tests for the s390x build on travis by renaming the s390x specific files, so that their inclusion in the build is determined only by tags and not by filename, and by introducing a new tag s390x_test that we can use to force their inclusion into a build by using this tag. The .travis file is then updated to include the line go test --tags s390x_test ./... This creates a build on travis that includes the s390x specific files and runs the unit tests. Signed-off-by: Mark Ryan --- .travis.yml | 1 + qemu/qemu_arch_base.go | 2 +- qemu/qemu_arch_base_test.go | 2 +- qemu/{qemu_s390x.go => qemus390x.go} | 2 +- qemu/{qemu_s390x_test.go => qemus390x_test.go} | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) rename qemu/{qemu_s390x.go => qemus390x.go} (99%) rename qemu/{qemu_s390x_test.go => qemus390x_test.go} (99%) diff --git a/.travis.yml b/.travis.yml index 1cfe2ef036..b8eaf3eb6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,3 +21,4 @@ script: - $GOPATH/bin/goveralls -v -service=travis-ci - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... - GOARCH=s390x go install ./... + - go test --tags s390x_test ./... diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index 5e293d1d62..521741fa6a 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -1,4 +1,4 @@ -// +build !s390x +// +build !s390x,!s390x_test /* // Copyright contributors to the Virtual Machine Manager for Go project diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 64a8415b49..93eac3ee56 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -1,4 +1,4 @@ -// +build !s390x +// +build !s390x,!s390x_test /* // Copyright contributors to the Virtual Machine Manager for Go project diff --git a/qemu/qemu_s390x.go b/qemu/qemus390x.go similarity index 99% rename from qemu/qemu_s390x.go rename to qemu/qemus390x.go index d8395837fc..7c45c3edd9 100644 --- a/qemu/qemu_s390x.go +++ b/qemu/qemus390x.go @@ -1,4 +1,4 @@ -// +build s390x +// +build s390x s390x_test /* // Copyright contributors to the Virtual Machine Manager for Go project diff --git a/qemu/qemu_s390x_test.go b/qemu/qemus390x_test.go similarity index 99% rename from qemu/qemu_s390x_test.go rename to qemu/qemus390x_test.go index b76af3ced5..1aac28434e 100644 --- a/qemu/qemu_s390x_test.go +++ b/qemu/qemus390x_test.go @@ -1,4 +1,4 @@ -// +build s390x +// +build s390x s390x_test /* // Copyright contributors to the Virtual Machine Manager for Go project From f9b31c0f80862a2293b8a3247010ca1324a63da8 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 4 Dec 2018 14:03:03 -0800 Subject: [PATCH 163/264] qemu: Allow disable-modern option from QMP For devices that actually support the option disable-modern, this current commit provides a proper flag to the caller. This will allow for better support when used in nested environment as virtio-pci devices should rely on virtio 0.9 instead of 1.0 due to a bug in KVM. Fixes #80 Signed-off-by: Sebastien Boeuf --- qemu/qmp.go | 38 ++++++++++++++++++++++++++++++++++---- qemu/qmp_test.go | 8 ++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 71ed56f28f..65557b7fa5 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -810,7 +810,10 @@ func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevI // add. Both strings must be valid QMP identifiers. driver is the name of the // driver,e.g., virtio-blk-pci, and bus is the name of the bus. bus is optional. // shared denotes if the drive can be shared allowing it to be passed more than once. -func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared bool) error { +// disableModern indicates if virtio version 1.0 should be replaced by the +// former version 0.9, as there is a KVM bug that occurs when using virtio +// 1.0 in nested environments. +func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -824,6 +827,10 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b } if isVirtioPCI[DeviceDriver(driver)] { args["romfile"] = romfile + + if disableModern { + args["disable-modern"] = disableModern + } } return q.executeCommand(ctx, "device_add", args, nil) @@ -837,7 +844,10 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b // scsiID is the SCSI id, lun is logical unit number. scsiID and lun are optional, a negative value // for scsiID and lun is ignored. shared denotes if the drive can be shared allowing it // to be passed more than once. -func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, scsiID, lun int, shared bool) error { +// disableModern indicates if virtio version 1.0 should be replaced by the +// former version 0.9, as there is a KVM bug that occurs when using virtio +// 1.0 in nested environments. +func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, scsiID, lun int, shared, disableModern bool) error { // TBD: Add drivers for scsi passthrough like scsi-generic and scsi-block drivers := []string{"scsi-hd", "scsi-cd", "scsi-disk"} @@ -870,6 +880,10 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive } if isVirtioPCI[DeviceDriver(driver)] { args["romfile"] = romfile + + if disableModern { + args["disable-modern"] = disableModern + } } return q.executeCommand(ctx, "device_add", args, nil) @@ -962,7 +976,10 @@ func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error { // using the device_add command. devID is the id of the device to add. // Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. // queues is the number of queues of a nic. -func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus, romfile string, queues int) error { +// disableModern indicates if virtio version 1.0 should be replaced by the +// former version 0.9, as there is a KVM bug that occurs when using virtio +// 1.0 in nested environments. +func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus, romfile string, queues int, disableModern bool) error { args := map[string]interface{}{ "id": devID, "driver": VirtioNetPCI, @@ -981,6 +998,9 @@ func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAd if netdevID != "" { args["netdev"] = netdevID } + if disableModern { + args["disable-modern"] = disableModern + } if queues > 0 { // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) @@ -1038,7 +1058,10 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { // to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the // device address on its parent bus. bus is optional. shared denotes if the drive can be shared // allowing it to be passed more than once. -func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, shared bool) error { +// disableModern indicates if virtio version 1.0 should be replaced by the +// former version 0.9, as there is a KVM bug that occurs when using virtio +// 1.0 in nested environments. +func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, shared, disableModern bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -1053,6 +1076,10 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver } if isVirtioPCI[DeviceDriver(driver)] { args["romfile"] = romfile + + if disableModern { + args["disable-modern"] = disableModern + } } return q.executeCommand(ctx, "device_add", args, nil) @@ -1290,6 +1317,9 @@ func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { } // ExecutePCIVSockAdd adds a vhost-vsock-pci bus +// disableModern indicates if virtio version 1.0 should be replaced by the +// former version 0.9, as there is a KVM bug that occurs when using virtio +// 1.0 in nested environments. func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus, romfile string, disableModern bool) error { args := map[string]interface{}{ "driver": VHostVSock, diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 924c4f5bd9..2220830996 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -528,7 +528,7 @@ func TestQMPNetPCIDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", "", 8) + err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", "", 8, false) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -569,7 +569,7 @@ func TestQMPDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "", "", true) + "virtio-blk-pci", "", "", true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -594,7 +594,7 @@ func TestQMPSCSIDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecuteSCSIDeviceAdd(context.Background(), blockdevID, devID, - "scsi-hd", "scsi0.0", "", 1, 2, true) + "scsi-hd", "scsi0.0", "", 1, 2, true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -1012,7 +1012,7 @@ func TestQMPPCIDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "0x1", "", "", true) + "virtio-blk-pci", "0x1", "", "", true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } From c891f5f84b8e8b5340c842074e9b294dc85a2949 Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Sun, 2 Dec 2018 19:20:08 +0800 Subject: [PATCH 164/264] qmp: Add nvdimm support ExecuteNVDIMMDeviceAdd can add a nvdimm disk to qemu. Not implement NVDIMM device delete function because qemu doesn't support it. Signed-off-by: Hui Zhu --- qemu/qmp.go | 36 ++++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 18 ++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 71ed56f28f..6dd7571813 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1280,6 +1280,42 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string return err } +// ExecuteNVDIMMDeviceAdd adds a block device to a QEMU instance using +// a NVDIMM driver with the device_add command. +// id is the id of the device to add. It must be a valid QMP identifier. +// mempath is the path of the device to add, e.g., /dev/rdb0. size is +// the data size of the device. +func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64) error { + args := map[string]interface{}{ + "qom-type": "memory-backend-file", + "id": "nvdimmbackmem" + id, + "props": map[string]interface{}{ + "mem-path": mempath, + "size": size, + "share": true, + }, + } + err := q.executeCommand(ctx, "object-add", args, nil) + if err != nil { + return err + } + + args = map[string]interface{}{ + "driver": "nvdimm", + "id": "nvdimm" + id, + "memdev": "nvdimmbackmem" + id, + } + if err = q.executeCommand(ctx, "device_add", args, nil); err != nil { + q.cfg.Logger.Errorf("Unable to hotplug NVDIMM device: %v", err) + err2 := q.executeCommand(ctx, "object-del", map[string]interface{}{"id": "nvdimmbackmem" + id}, nil) + if err2 != nil { + q.cfg.Logger.Warningf("Unable to clean up memory object: %v", err2) + } + } + + return err +} + // ExecuteBalloon sets the size of the balloon, hence updates the memory // allocated for the VM. func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 924c4f5bd9..24a03fae1f 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1486,3 +1486,21 @@ func TestExecCommandFailedWithInnerError(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks NVDIMM device add +func TestExecuteNVDIMMDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("object-add", nil, "return", nil) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + q.Shutdown() + <-disconnectedCh +} From 97fc3435cf16749acb7ffe979ba89a354715ed06 Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Mon, 3 Dec 2018 15:06:32 +0800 Subject: [PATCH 165/264] contributors: add my name Signed-off-by: Hui Zhu --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c159b9a1f0..7a822adbe0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -18,4 +18,5 @@ themselves (or their employer, as appropriate). - robert.bradford@intel.com - sameo@linux.intel.com - sebastien.boeuf@intel.com +- teawater@hyper.sh - xinda.zhao@intel.com From 4beea5133ed152abb43c2734dbe0815eb1f7f53e Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 15:32:07 +0100 Subject: [PATCH 166/264] Fix staticcheck (ST1005) errors staticcheck was complaining as some of the error messages returned by govmm began with a capital letter. This commit fixes the issue. Signed-off-by: Mark Ryan --- qemu/image.go | 8 ++++---- qemu/qmp.go | 30 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/qemu/image.go b/qemu/image.go index de12333c45..ddee0670e1 100644 --- a/qemu/image.go +++ b/qemu/image.go @@ -51,18 +51,18 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, err := os.MkdirAll(dataDirPath, 0750) if err != nil { - return fmt.Errorf("Unable to create config drive directory %s : %v", + return fmt.Errorf("unable to create config drive directory %s : %v", dataDirPath, err) } err = ioutil.WriteFile(metaDataPath, metaData, 0644) if err != nil { - return fmt.Errorf("Unable to create %s : %v", metaDataPath, err) + return fmt.Errorf("unable to create %s : %v", metaDataPath, err) } err = ioutil.WriteFile(userDataPath, userData, 0644) if err != nil { - return fmt.Errorf("Unable to create %s : %v", userDataPath, err) + return fmt.Errorf("unable to create %s : %v", userDataPath, err) } cmd := exec.CommandContext(ctx, "xorriso", "-as", "mkisofs", "-R", "-V", "config-2", @@ -70,7 +70,7 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, cmd.SysProcAttr = attr err = cmd.Run() if err != nil { - return fmt.Errorf("Unable to create cloudinit iso image %v", err) + return fmt.Errorf("unable to create cloudinit iso image %v", err) } return nil diff --git a/qemu/qmp.go b/qemu/qmp.go index 6e4d4f3559..3811e052dd 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -329,14 +329,14 @@ func (q *QMP) errorDesc(errorData interface{}) (string, error) { // convert error to json data, err := json.Marshal(errorData) if err != nil { - return "", fmt.Errorf("Unable to extract error information: %v", err) + return "", fmt.Errorf("unable to extract error information: %v", err) } // see: https://github.com/qemu/qemu/blob/stable-2.12/qapi/qmp-dispatch.c#L125 var qmpErr map[string]string // convert json to qmpError if err = json.Unmarshal(data, &qmpErr); err != nil { - return "", fmt.Errorf("Unable to convert json to qmpError: %v", err) + return "", fmt.Errorf("unable to convert json to qmpError: %v", err) } return qmpErr["desc"], nil @@ -404,7 +404,7 @@ func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { encodedCmd, err := json.Marshal(&cmdData) if err != nil { cmd.res <- qmpResult{ - err: fmt.Errorf("Unable to marhsall command %s: %v", + err: fmt.Errorf("unable to marhsall command %s: %v", cmd.name, err), } cmdQueue.Remove(cmdEl) @@ -419,7 +419,7 @@ func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { if err != nil { cmd.res <- qmpResult{ - err: fmt.Errorf("Unable to write command to qmp socket %v", err), + err: fmt.Errorf("unable to write command to qmp socket %v", err), } cmdQueue.Remove(cmdEl) } @@ -689,12 +689,12 @@ func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh case <-ctx.Done(): q.Shutdown() <-disconnectedCh - return nil, nil, fmt.Errorf("Canceled by caller") + return nil, nil, fmt.Errorf("canceled by caller") case <-disconnectedCh: - return nil, nil, fmt.Errorf("Lost connection to VM") + return nil, nil, fmt.Errorf("lost connection to VM") case q.version = <-connectedCh: if q.version == nil { - return nil, nil, fmt.Errorf("Failed to find QMP version information") + return nil, nil, fmt.Errorf("failed to find QMP version information") } } @@ -860,7 +860,7 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive } if !isSCSIDriver { - return fmt.Errorf("Invalid SCSI driver provided %s", driver) + return fmt.Errorf("invalid SCSI driver provided %s", driver) } args := map[string]interface{}{ @@ -1171,13 +1171,13 @@ func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableC // convert response to json data, err := json.Marshal(response) if err != nil { - return nil, fmt.Errorf("Unable to extract CPU information: %v", err) + return nil, fmt.Errorf("unable to extract CPU information: %v", err) } var cpus []HotpluggableCPU // convert json to []HotpluggableCPU if err = json.Unmarshal(data, &cpus); err != nil { - return nil, fmt.Errorf("Unable to convert json to hotpluggable CPU: %v", err) + return nil, fmt.Errorf("unable to convert json to hotpluggable CPU: %v", err) } return cpus, nil @@ -1211,7 +1211,7 @@ func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, erro // convert response to json data, err := json.Marshal(response) if err != nil { - return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + return nil, fmt.Errorf("unable to extract memory devices information: %v", err) } var memoryDevices []MemoryDevices @@ -1235,7 +1235,7 @@ func (q *QMP) ExecQueryCpus(ctx context.Context) ([]CPUInfo, error) { // convert response to json data, err := json.Marshal(response) if err != nil { - return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + return nil, fmt.Errorf("unable to extract memory devices information: %v", err) } var cpuInfo []CPUInfo @@ -1259,7 +1259,7 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { // convert response to json data, err := json.Marshal(response) if err != nil { - return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + return nil, fmt.Errorf("unable to extract memory devices information: %v", err) } var cpuInfoFast []CPUInfoFast @@ -1434,12 +1434,12 @@ func (q *QMP) ExecuteQueryMigration(ctx context.Context) (MigrationStatus, error data, err := json.Marshal(response) if err != nil { - return MigrationStatus{}, fmt.Errorf("Unable to extract migrate status information: %v", err) + return MigrationStatus{}, fmt.Errorf("unable to extract migrate status information: %v", err) } var status MigrationStatus if err = json.Unmarshal(data, &status); err != nil { - return MigrationStatus{}, fmt.Errorf("Unable to convert migrate status information: %v", err) + return MigrationStatus{}, fmt.Errorf("unable to convert migrate status information: %v", err) } return status, nil From 5f2e630bdad07960f2ebd1a721c21ab8a44b9c87 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 16:06:49 +0100 Subject: [PATCH 167/264] Fix staticcheck (S1025) staticcheck was complaining as there were quite a lot of fmt.Sprintf("%s",d) in the code where d was either a string or had string as its underlying type. Signed-off-by: Mark Ryan --- qemu/qemu.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 8bd6afbef0..f209cae1c3 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -260,7 +260,7 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, fmt.Sprintf("%s", fsdev.Driver)) + deviceParams = append(deviceParams, string(fsdev.Driver)) if s := fsdev.Driver.disableModern(fsdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } @@ -346,7 +346,7 @@ func (cdev CharDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, fmt.Sprintf("%s", cdev.Driver)) + deviceParams = append(deviceParams, string(cdev.Driver)) if s := cdev.Driver.disableModern(cdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } @@ -627,7 +627,7 @@ func (dev SerialDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, fmt.Sprintf("%s", dev.Driver)) + deviceParams = append(deviceParams, string(dev.Driver)) if s := dev.Driver.disableModern(dev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } @@ -705,7 +705,7 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, fmt.Sprintf("%s", blkdev.Driver)) + deviceParams = append(deviceParams, string(blkdev.Driver)) if s := blkdev.Driver.disableModern(blkdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } @@ -910,7 +910,7 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } if s := driver.disableModern(scsiCon.DisableModern); s != "" { - devParams = append(devParams, fmt.Sprintf("%s", s)) + devParams = append(devParams, s) } if scsiCon.IOThread != "" { devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) @@ -1057,7 +1057,7 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { var qemuParams []string driver := VHostVSock - deviceParams = append(deviceParams, fmt.Sprintf("%s", driver)) + deviceParams = append(deviceParams, string(driver)) if s := driver.disableModern(vsock.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } @@ -1174,7 +1174,7 @@ func (b BalloonDevice) QemuParams(_ *Config) []string { deviceParams = append(deviceParams, "deflate-on-oom=off") } if s := driver.disableModern(b.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf("%s", s)) + deviceParams = append(deviceParams, string(s)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) @@ -1528,7 +1528,7 @@ func (config *Config) appendQMPSockets() { } qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type)) - qmpParams = append(qmpParams, fmt.Sprintf("%s", q.Name)) + qmpParams = append(qmpParams, q.Name) if q.Server == true { qmpParams = append(qmpParams, ",server") if q.NoWait == true { From f0172cd2a6a0e2701f89883df654cd318c832297 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 16:13:48 +0100 Subject: [PATCH 168/264] Fix staticcheck (S1002) staticcheck was complaining about code that looked like if x == true { } rather than if x { } This commit fixes the issue. Signed-off-by: Mark Ryan --- qemu/qemu.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f209cae1c3..47fc3fadf2 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -534,7 +534,7 @@ func (netdev NetDevice) QemuNetdevParams(config *Config) []string { netdevParams = append(netdevParams, netdev.Type.QemuNetdevParam()) netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID)) - if netdev.VHost == true { + if netdev.VHost { netdevParams = append(netdevParams, ",vhost=on") if len(netdev.VhostFDs) > 0 { var fdParams []string @@ -710,11 +710,11 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) - if blkdev.SCSI == false { + if !blkdev.SCSI { deviceParams = append(deviceParams, ",scsi=off") } - if blkdev.WCE == false { + if !blkdev.WCE { deviceParams = append(deviceParams, ",config-wce=off") } @@ -1523,15 +1523,15 @@ func (config *Config) appendCPUModel() { func (config *Config) appendQMPSockets() { for _, q := range config.QMPSockets { - if q.Valid() == false { + if !q.Valid() { continue } qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type)) qmpParams = append(qmpParams, q.Name) - if q.Server == true { + if q.Server { qmpParams = append(qmpParams, ",server") - if q.NoWait == true { + if q.NoWait { qmpParams = append(qmpParams, ",nowait") } } @@ -1543,7 +1543,7 @@ func (config *Config) appendQMPSockets() { func (config *Config) appendDevices() { for _, d := range config.Devices { - if d.Valid() == false { + if !d.Valid() { continue } @@ -1611,7 +1611,7 @@ func (config *Config) appendCPUs() error { } func (config *Config) appendRTC() { - if config.RTC.Valid() == false { + if !config.RTC.Valid() { return } @@ -1663,7 +1663,7 @@ func (config *Config) appendKernel() { } func (config *Config) appendMemoryKnobs() { - if config.Knobs.HugePages == true { + if config.Knobs.HugePages { if config.Memory.Size != "" { dimmName := "dimm1" objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=/dev/hugepages,share=on,prealloc=on" @@ -1675,7 +1675,7 @@ func (config *Config) appendMemoryKnobs() { config.qemuParams = append(config.qemuParams, "-numa") config.qemuParams = append(config.qemuParams, numaMemParam) } - } else if config.Knobs.MemPrealloc == true { + } else if config.Knobs.MemPrealloc { if config.Memory.Size != "" { dimmName := "dimm1" objMemParam := "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + ",prealloc=on" @@ -1687,11 +1687,11 @@ func (config *Config) appendMemoryKnobs() { config.qemuParams = append(config.qemuParams, "-numa") config.qemuParams = append(config.qemuParams, numaMemParam) } - } else if config.Knobs.FileBackedMem == true { + } else if config.Knobs.FileBackedMem { if config.Memory.Size != "" && config.Memory.Path != "" { dimmName := "dimm1" objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=" + config.Memory.Path - if config.Knobs.FileBackedMemShared == true { + if config.Knobs.FileBackedMemShared { objMemParam += ",share=on" } numaMemParam := "node,memdev=" + dimmName @@ -1706,45 +1706,45 @@ func (config *Config) appendMemoryKnobs() { } func (config *Config) appendKnobs() { - if config.Knobs.NoUserConfig == true { + if config.Knobs.NoUserConfig { config.qemuParams = append(config.qemuParams, "-no-user-config") } - if config.Knobs.NoDefaults == true { + if config.Knobs.NoDefaults { config.qemuParams = append(config.qemuParams, "-nodefaults") } - if config.Knobs.NoGraphic == true { + if config.Knobs.NoGraphic { config.qemuParams = append(config.qemuParams, "-nographic") } - if config.Knobs.Daemonize == true { + if config.Knobs.Daemonize { config.qemuParams = append(config.qemuParams, "-daemonize") } config.appendMemoryKnobs() - if config.Knobs.Realtime == true { + if config.Knobs.Realtime { config.qemuParams = append(config.qemuParams, "-realtime") // This path is redundant as the default behaviour is locked memory // Realtime today does not control any other feature even though // other features may be added in the future // https://lists.gnu.org/archive/html/qemu-devel/2012-12/msg03330.html - if config.Knobs.Mlock == true { + if config.Knobs.Mlock { config.qemuParams = append(config.qemuParams, "mlock=on") } else { config.qemuParams = append(config.qemuParams, "mlock=off") } } else { // In order to turn mlock off we need the -realtime option as well - if config.Knobs.Mlock == false { + if !config.Knobs.Mlock { //Enable realtime anyway just to get the right swapping behaviour config.qemuParams = append(config.qemuParams, "-realtime") config.qemuParams = append(config.qemuParams, "mlock=off") } } - if config.Knobs.Stopped == true { + if config.Knobs.Stopped { config.qemuParams = append(config.qemuParams, "-S") } } From cb2ce9339c38b3d145660475b859406fb0148a01 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 16:17:24 +0100 Subject: [PATCH 169/264] Fix staticcheck S1008 static check was complaining about code that looked like if x == "" { return false } return true when what it wants to see is return x != "". This commit fixes the issue. Signed-off-by: Mark Ryan --- qemu/qemu.go | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 47fc3fadf2..9703018d57 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -842,11 +842,7 @@ type VFIODevice struct { // Valid returns true if the VFIODevice structure is valid and complete. func (vfioDev VFIODevice) Valid() bool { - if vfioDev.BDF == "" { - return false - } - - return true + return vfioDev.BDF != "" } // QemuParams returns the qemu parameters built out of this vfio device. @@ -889,11 +885,7 @@ type SCSIController struct { // Valid returns true if the SCSIController structure is valid and complete. func (scsiCon SCSIController) Valid() bool { - if scsiCon.ID == "" { - return false - } - - return true + return scsiCon.ID != "" } // QemuParams returns the qemu parameters built out of this SCSIController device. @@ -1094,11 +1086,7 @@ type RngDevice struct { // Valid returns true if the RngDevice structure is valid and complete. func (v RngDevice) Valid() bool { - if v.ID == "" { - return false - } - - return true + return v.ID != "" } // QemuParams returns the qemu parameters built out of the RngDevice. @@ -1184,11 +1172,7 @@ func (b BalloonDevice) QemuParams(_ *Config) []string { // Valid returns true if the balloonDevice structure is valid and complete. func (b BalloonDevice) Valid() bool { - if b.ID == "" { - return false - } - - return true + return b.ID != "" } // RTCBaseType is the qemu RTC base time type. From 932fdc7f501525156fef7fbba38e4dd5ca46f9ee Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 16:19:20 +0100 Subject: [PATCH 170/264] Fix staticcheck S1023 By removing a redundant return statement. Signed-off-by: Mark Ryan --- qemu/qemu_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 622f47ea70..2af0ded79e 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -30,8 +30,6 @@ const volumeUUID = "67d86208-b46c-4465-9018-e14187d4010" func testAppend(structure interface{}, expected string, t *testing.T) { var config Config testConfigAppend(&config, structure, expected, t) - - return } func testConfigAppend(config *Config, structure interface{}, expected string, t *testing.T) { From ad310f9fde4fffaafdea0138fc8773b8481cc7bb Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 16:20:23 +0100 Subject: [PATCH 171/264] Fix staticcheck S1023 Static check was complaining about code that looked like _ = <-ch when it wants to see simply <-ch There was only one instance of this in govmm and this commit fixes that instance. Signed-off-by: Mark Ryan --- qemu/qmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 3811e052dd..74431c12d4 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -525,7 +525,7 @@ func (q *QMP) mainLoop() { } /* #nosec */ _ = q.conn.Close() - _ = <-fromVMCh + <-fromVMCh failOutstandingCommands(cmdQueue) close(q.disconnectedCh) }() From 1f51b4386be382f21810cdc01f3cd4df0a946f45 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 28 Jan 2019 16:31:51 +0100 Subject: [PATCH 172/264] Update the versions of Go used to build GoVMM The .travis file was building GoVMM with some old of date versions of Go that seem to be incompatible with the latest versions of gometalinter. This commit updates the .travis file so that we build against 1.10 and 1.11. Signed-off-by: Mark Ryan --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b8eaf3eb6f..41aec9bb00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.8 - - 1.9 + - "1.10" + - "1.11" - tip go_import_path: github.com/intel/govmm From 4692f6b965d2c8777104dfcb7283d95aacfdb730 Mon Sep 17 00:00:00 2001 From: Nitesh Konkar Date: Mon, 28 Jan 2019 18:48:53 +0530 Subject: [PATCH 173/264] qmp: Conditionally pass threadID and socketID when CPU device add For vCPU hotplug to work on ppc64le, we need not pass threadID and socketID. So conditionally pass arguments when executing CPU device add. Fixes: #83 Signed-off-by: Nitesh Konkar niteshkonkar@in.ibm.com --- qemu/qmp.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 74431c12d4..9b21d96480 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1144,14 +1144,21 @@ func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsd // ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command. // driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within // node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the -// thread number within core the CPU belongs to. +// thread number within core the CPU belongs to. Note that socketID and threadID are not a requirement for +// architecures like ppc64le. func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, coreID, threadID, romfile string) error { args := map[string]interface{}{ - "driver": driver, - "id": cpuID, - "socket-id": socketID, - "core-id": coreID, - "thread-id": threadID, + "driver": driver, + "id": cpuID, + "core-id": coreID, + } + + if socketID != "" { + args["socket-id"] = socketID + } + + if threadID != "" { + args["thread-id"] = threadID } if isVirtioPCI[DeviceDriver(driver)] { From 3c84b1daa3d132d76cbf86061c0b1f876d12917c Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 5 Jul 2018 16:04:31 +0100 Subject: [PATCH 174/264] govmm: add VhostUserFS vhost-user device type The QEMU vhost-user-fs-pci device provides virtio-fs host<->guest file system sharing (https://virtio-fs.gitlab.io/). The device is instantiated like this: $ qemu -chardev socket,path=/tmp/vhost-fs.sock,id=chr0 -device vhost-user-fs-pci,tag=myfs,chardev=chr0,cache-size=4G,versiontable=/dev/shm/fuse_shared_versions This patch adds the VhostUserFS DeviceDriver and command-line generation for this QEMU device. Signed-off-by: Stefan Hajnoczi --- qemu/qemu.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 9703018d57..c2f734bc52 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -92,6 +92,9 @@ const ( //VhostUserBlk represents a block vhostuser device type. VhostUserBlk DeviceDriver = "vhost-user-blk-pci" + //VhostUserFS represents a virtio-fs vhostuser device type + VhostUserFS DeviceDriver = "vhost-user-fs-pci" + // PCIBridgeDriver represents a PCI bridge device type. PCIBridgeDriver DeviceDriver = "pci-bridge" @@ -740,11 +743,14 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { // VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { - SocketPath string //path to vhostuser socket on host - CharDevID string - TypeDevID string //variable QEMU parameter based on value of VhostUserType - Address string //used for MAC address in net case - VhostUserType DeviceDriver + SocketPath string //path to vhostuser socket on host + CharDevID string + TypeDevID string //variable QEMU parameter based on value of VhostUserType + Address string //used for MAC address in net case + Tag string //virtio-fs volume id for mounting inside guest + CacheSize uint32 //virtio-fs DAX cache size in GiB + SharedVersions bool //enable virtio-fs shared version metadata + VhostUserType DeviceDriver // ROMFile specifies the ROM file being used for this device. ROMFile string @@ -767,6 +773,10 @@ func (vhostuserDev VhostUserDevice) Valid() bool { return false } case VhostUserBlk: + case VhostUserFS: + if vhostuserDev.Tag == "" { + return false + } default: return false } @@ -809,6 +819,15 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { devParams = append(devParams, "logical_block_size=4096") devParams = append(devParams, "size=512M") devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + case VhostUserFS: + driver = VhostUserFS + devParams = append(devParams, string(driver)) + devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) + devParams = append(devParams, fmt.Sprintf("cache-size=%dG", vhostuserDev.CacheSize)) + if vhostuserDev.SharedVersions { + devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") + } default: return nil } From 5712b1198ec3d28acd4727df7924ec3038ce093f Mon Sep 17 00:00:00 2001 From: jiangpengfei Date: Tue, 12 Mar 2019 20:59:39 -0400 Subject: [PATCH 175/264] qemu/qmp: fix readLoop() reuse scanner.Bytes() underlying array problem Since []byte channel type transfer slice info(include slice underlying array pointer, len, cap) between channel sender and receiver. scanner.Bytes() function returned slice's underlying array may point to data that will be overwritten by a subsequent call to Scan(reference from: https://golang.org/pkg/bufio/#Scanner.Bytes), which may make consecutive scan() call write the read data into the same underlying array which causes receiver read mixed data,so we need to copy line to new allocated space and then send to channel receiver to solve this problem. Fixes: #88 Signed-off-by: jiangpengfei --- qemu/qmp.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9b21d96480..28a76b0b03 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -247,7 +247,16 @@ func (q *QMP) readLoop(fromVMCh chan<- []byte) { if q.cfg.Logger.V(1) { q.cfg.Logger.Infof("%s", string(line)) } - fromVMCh <- line + + // Since []byte channel type transfer slice info(include slice underlying array pointer, len, cap) + // between channel sender and receiver. scanner.Bytes() returned slice's underlying array + // may point to data that will be overwritten by a subsequent call to Scan(reference from: + // https://golang.org/pkg/bufio/#Scanner.Bytes), which may make receiver read mixed data, + // so we need to copy line to new allocated space and then send to channel receiver + sendLine := make([]byte, len(line)) + copy(sendLine, line) + + fromVMCh <- sendLine } close(fromVMCh) } From 694a7b1c6119668da26d7c5ac223cdb5f6632149 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Thu, 4 Apr 2019 10:35:24 -0600 Subject: [PATCH 176/264] qemu/qmp: re-implement mainLoop In newer versions of QEMU, like 4.0-rc2, QMP events can be thrown even before the QMP-version response, one example of this behaviour is when a virtio serial is closed and a VSERPORT_CHANGE event is thrown. Re-implement mainLoop to check the data received from the VM channel, since it's not a guarantee that the first data read from the VM channel is the QMP version. fixes https://github.com/kata-containers/runtime/issues/1474 Signed-off-by: Julio Montes --- qemu/qmp.go | 55 ++++++++++++++++++++------------------------- qemu/qmp_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9b21d96480..f35aa3c31e 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -459,7 +459,6 @@ func (q *QMP) parseVersion(version []byte) *QMPVersion { for _, k := range []string{"QMP", "version", "qemu"} { versionMap, _ = versionMap[k].(map[string]interface{}) if versionMap == nil { - q.cfg.Logger.Errorf("Invalid QMP greeting: %s", string(version)) return nil } } @@ -530,31 +529,9 @@ func (q *QMP) mainLoop() { close(q.disconnectedCh) }() - var version []byte var cmdDoneCh <-chan struct{} - -DONE: - for { - var ok bool - select { - case cmd, ok := <-q.cmdCh: - if !ok { - return - } - _ = cmdQueue.PushBack(&cmd) - case version, ok = <-fromVMCh: - if !ok { - return - } - if cmdQueue.Len() >= 1 { - q.writeNextQMPCommand(cmdQueue) - cmdDoneCh = currentCommandDoneCh(cmdQueue) - } - break DONE - } - } - - q.connectedCh <- q.parseVersion(version) + var version *QMPVersion + ready := false for { select { @@ -564,21 +541,37 @@ DONE: } _ = cmdQueue.PushBack(&cmd) - // We only want to execute the new cmd if there - // are no other commands pending. If there are - // commands pending our new command will get - // run when the pending commands complete. - - if cmdQueue.Len() == 1 { + // We only want to execute the new cmd if QMP is + // ready and there are no other commands pending. + // If there are commands pending our new command + // will get run when the pending commands complete. + if ready && cmdQueue.Len() == 1 { q.writeNextQMPCommand(cmdQueue) cmdDoneCh = currentCommandDoneCh(cmdQueue) } + case line, ok := <-fromVMCh: if !ok { return } + + if !ready { + // Not ready yet. Check if line is the QMP version. + // Sometimes QMP events are thrown before the QMP version, + // hence it's not a guarantee that the first data read from + // the channel is the QMP version. + version = q.parseVersion(line) + if version != nil { + q.connectedCh <- version + ready = true + } + // Do not process QMP input to avoid deadlocks. + break + } + q.processQMPInput(line, cmdQueue) cmdDoneCh = currentCommandDoneCh(cmdQueue) + case <-cmdDoneCh: q.cancelCurrentCommand(cmdQueue) cmdDoneCh = currentCommandDoneCh(cmdQueue) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index b12c798a17..61710669f8 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -103,6 +103,19 @@ func newQMPTestCommandBuffer(t *testing.T) *qmpTestCommandBuffer { return b } +func newQMPTestCommandBufferNoGreeting(t *testing.T) *qmpTestCommandBuffer { + b := &qmpTestCommandBuffer{ + newDataCh: make(chan []byte, 1), + t: t, + buf: bytes.NewBuffer([]byte{}), + forceFail: make(chan struct{}), + } + b.cmds = make([]qmpTestCommand, 0, 8) + b.events = make([]qmpTestEvent, 0, 8) + b.results = make([]qmpTestResult, 0, 8) + return b +} + func (b *qmpTestCommandBuffer) startEventLoop(wg *sync.WaitGroup) { wg.Add(1) go func() { @@ -1504,3 +1517,48 @@ func TestExecuteNVDIMMDeviceAdd(t *testing.T) { q.Shutdown() <-disconnectedCh } + +func TestMainLoopEventBeforeGreeting(t *testing.T) { + const ( + seconds = 1352167040730 + microseconds = 123456 + ) + + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBufferNoGreeting(t) + + // Add events + var wg sync.WaitGroup + buf.AddEvent("VSERPORT_CHANGE", time.Millisecond*100, + map[string]interface{}{ + "open": false, + "id": "channel0", + }, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microseconds, + }) + buf.AddEvent("POWERDOWN", time.Millisecond*200, nil, + map[string]interface{}{ + "seconds": seconds, + "microseconds": microseconds, + }) + + // register a channel to receive events + eventCh := make(chan QMPEvent) + cfg := QMPConfig{EventCh: eventCh, Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + + // Start events, this will lead to a deadlock if mainLoop is not implemented + // correctly + buf.startEventLoop(&wg) + wg.Wait() + + // Send greeting and check version + buf.newDataCh <- []byte(qmpHello) + checkVersion(t, connectedCh) + + q.Shutdown() + <-disconnectedCh +} From 058cda06036dfae0c4ba9db70fb3047cb11f9313 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 9 Apr 2019 10:21:43 +0100 Subject: [PATCH 177/264] qemu: use MiB instead of Gib for virtio-fs cache size QEMU supports finer-grained units than GiB. Change the cache size to MiB so users have more control over the cache size. Note that changing the semantics of the CacheSize field is fine because there are no users of this API yet. kata-runtime will be the first users and prefers MiB instead of GiB. Signed-off-by: Stefan Hajnoczi --- qemu/qemu.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index c2f734bc52..16e268d686 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -748,7 +748,7 @@ type VhostUserDevice struct { TypeDevID string //variable QEMU parameter based on value of VhostUserType Address string //used for MAC address in net case Tag string //virtio-fs volume id for mounting inside guest - CacheSize uint32 //virtio-fs DAX cache size in GiB + CacheSize uint32 //virtio-fs DAX cache size in MiB SharedVersions bool //enable virtio-fs shared version metadata VhostUserType DeviceDriver @@ -824,7 +824,7 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { devParams = append(devParams, string(driver)) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) - devParams = append(devParams, fmt.Sprintf("cache-size=%dG", vhostuserDev.CacheSize)) + devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) if vhostuserDev.SharedVersions { devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") } From 7d3deea4fc16adce85593e9debdc24d8010a1fba Mon Sep 17 00:00:00 2001 From: lifupan Date: Mon, 15 Apr 2019 23:14:49 -0400 Subject: [PATCH 178/264] qemu: Add a virtio-blk-pci device driver support Add a pci bus based virtio block device driver support. Fixes:#92 Signed-off-by: lifupan --- qemu/qemu.go | 3 +++ qemu/qemu_arch_base.go | 1 + 2 files changed, 4 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 16e268d686..a360ec9213 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -71,6 +71,9 @@ const ( // VirtioBlock is the block device driver. VirtioBlock DeviceDriver = "virtio-blk" + // VirtioBlockPCI is a pci bus block device driver + VirtioBlockPCI DeviceDriver = "virtio-blk-pci" + // Console is the console device driver. Console DeviceDriver = "virtconsole" diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index 521741fa6a..ca9d78e73f 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -47,6 +47,7 @@ var isVirtioPCI = map[DeviceDriver]bool{ VirtioNetPCI: true, VirtioSerial: true, VirtioBlock: true, + VirtioBlockPCI: true, Console: false, VirtioSerialPort: false, VHostVSock: true, From f0f18dd0f2c1447255e6e42121ab0e8ffd03286b Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Mon, 20 May 2019 22:11:10 -0700 Subject: [PATCH 179/264] qmp: add virtio-blk multiqueue Hotplug virtio-blk with multiqueue support. Signed-off-by: Peng Tao --- qemu/qmp.go | 10 +++++++--- qemu/qmp_test.go | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 1e2542fb6d..bada7bc4e5 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -25,6 +25,7 @@ import ( "io" "net" "os" + "strconv" "syscall" "time" @@ -1058,12 +1059,12 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { // ExecutePCIDeviceAdd is the PCI version of ExecuteDeviceAdd. This function can be used // to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the -// device address on its parent bus. bus is optional. shared denotes if the drive can be shared -// allowing it to be passed more than once. +// device address on its parent bus. bus is optional. queues specifies the number of queues of +// a block device. shared denotes if the drive can be shared allowing it to be passed more than once. // disableModern indicates if virtio version 1.0 should be replaced by the // former version 0.9, as there is a KVM bug that occurs when using virtio // 1.0 in nested environments. -func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, shared, disableModern bool) error { +func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, queues int, shared, disableModern bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -1076,6 +1077,9 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } + if queues > 0 { + args["num-queues"] = strconv.Itoa(queues) + } if isVirtioPCI[DeviceDriver(driver)] { args["romfile"] = romfile diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 61710669f8..5b11e94f64 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1025,7 +1025,7 @@ func TestQMPPCIDeviceAdd(t *testing.T) { blockdevID := fmt.Sprintf("drive_%s", volumeUUID) devID := fmt.Sprintf("device_%s", volumeUUID) err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, - "virtio-blk-pci", "0x1", "", "", true, false) + "virtio-blk-pci", "0x1", "", "", 1, true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } From f695ddf8f36d13b1b3e1c6fa2f5af926953ea9a0 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Fri, 14 Jun 2019 00:24:26 -0700 Subject: [PATCH 180/264] qemu: add migration incoming defer support qemu commandline supports -incoming defer and qmp supports migrate-incoming uri. Signed-off-by: Peng Tao --- qemu/qemu.go | 4 ++++ qemu/qemu_test.go | 10 ++++++++++ qemu/qmp.go | 8 ++++++++ qemu/qmp_test.go | 17 +++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index a360ec9213..f281d4ecba 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1397,6 +1397,8 @@ const ( MigrationFD = 1 // MigrationExec is the migration incoming type based on commands. MigrationExec = 2 + // MigrationDefer is the defer incoming type + MigrationDefer = 3 ) // Incoming controls migration source preparation @@ -1779,6 +1781,8 @@ func (config *Config) appendIncoming() { case MigrationFD: chFDs := config.appendFDs([]*os.File{config.Incoming.FD}) uri = fmt.Sprintf("fd:%d", chFDs[0]) + case MigrationDefer: + uri = "defer" default: return } diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 2af0ded79e..f5692f5013 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -725,6 +725,16 @@ func TestAppendIncomingExec(t *testing.T) { testAppend(source, incomingStringExec, t) } +var incomingStringDefer = "-S -incoming defer" + +func TestAppendIncomingDefer(t *testing.T) { + source := Incoming{ + MigrationType: MigrationDefer, + } + + testAppend(source, incomingStringDefer, t) +} + func TestBadName(t *testing.T) { c := &Config{} c.appendName() diff --git a/qemu/qmp.go b/qemu/qmp.go index bada7bc4e5..df2ab2ed54 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1457,3 +1457,11 @@ func (q *QMP) ExecuteQueryMigration(ctx context.Context) (MigrationStatus, error return status, nil } + +// ExecuteMigrationIncoming start migration from incoming uri. +func (q *QMP) ExecuteMigrationIncoming(ctx context.Context, uri string) error { + args := map[string]interface{}{ + "uri": uri, + } + return q.executeCommand(ctx, "migrate-incoming", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 5b11e94f64..0925420359 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1349,6 +1349,23 @@ func TestExecuteVirtSerialPortAdd(t *testing.T) { <-disconnectedCh } +// Check migration incoming +func TestExecuteMigrationIncoming(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("migrate-incoming", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecuteMigrationIncoming(context.Background(), "uri") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks migration status func TestExecuteQueryMigration(t *testing.T) { connectedCh := make(chan *QMPVersion) From 0c900f596ebab7d13201177280e396af4840d61b Mon Sep 17 00:00:00 2001 From: Ganesh Maharaj Mahalingam Date: Tue, 18 Jun 2019 06:16:18 -0700 Subject: [PATCH 181/264] Allow sharing of memory backend file Hotplugged memory could be backed by a file on the host with sharing turned on. This change allows qmp to pass that option to a govmm. Signed-off-by: Ganesh Maharaj Mahalingam --- qemu/qmp.go | 5 ++++- qemu/qmp_test.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index df2ab2ed54..359e236e69 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1285,7 +1285,7 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { } // ExecHotplugMemory adds size of MiB memory to the guest -func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int) error { +func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int, share bool) error { props := map[string]interface{}{"size": uint64(size) << 20} args := map[string]interface{}{ "qom-type": qomtype, @@ -1295,6 +1295,9 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string if mempath != "" { props["mem-path"] = mempath } + if share { + props["share"] = true + } err := q.executeCommand(ctx, "object-add", args, nil) if err != nil { return err diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 0925420359..125743f47b 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1273,7 +1273,7 @@ func TestExecHotplugMemory(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecHotplugMemory(context.Background(), "memory-backend-ram", "mem0", "", 128) + err := q.ExecHotplugMemory(context.Background(), "memory-backend-ram", "mem0", "", 128, true) if err != nil { t.Fatalf("Unexpected error: %v\n", err) } From 9cf98da0be6ba09161259910f9d9a4ec0428b476 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Mon, 1 Jul 2019 13:43:00 +0200 Subject: [PATCH 182/264] s390x: add devno support DevNo is used to identify the ccw device for s390x systems Signed-off-by: Alice Frosi --- qemu/qemu.go | 69 ++++++++++++++++++++++++++++++++++++++++++ qemu/qemu_arch_base.go | 3 ++ qemu/qemus390x.go | 20 ++++++++++++ qemu/qmp.go | 18 ++++++++--- 4 files changed, 106 insertions(+), 4 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f281d4ecba..31943e0a1c 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -249,6 +249,9 @@ type FSDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the FSDevice structure is valid and complete. @@ -275,6 +278,9 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { if isVirtioPCI[fsdev.Driver] { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) } + if isVirtioCCW[fsdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", fsdev.DevNo)) + } fsParams = append(fsParams, string(fsdev.FSDriver)) fsParams = append(fsParams, fmt.Sprintf(",id=%s", fsdev.ID)) @@ -335,6 +341,9 @@ type CharDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the CharDevice structure is valid and complete. @@ -368,6 +377,10 @@ func (cdev CharDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", cdev.ROMFile)) } + if isVirtioCCW[cdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", cdev.DevNo)) + } + cdevParams = append(cdevParams, string(cdev.Backend)) cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) if cdev.Backend == Socket { @@ -450,6 +463,9 @@ type NetDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the NetDevice structure is valid and complete. @@ -488,6 +504,7 @@ func (netdev NetDevice) mqParameter() string { vectors := len(netdev.FDs)*2 + 2 p = append(p, fmt.Sprintf(",vectors=%d", vectors)) } + return strings.Join(p, "") } @@ -526,6 +543,10 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", netdev.ROMFile)) } + if isVirtioCCW[netdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", netdev.DevNo)) + } + return deviceParams } @@ -617,6 +638,9 @@ type SerialDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the SerialDevice structure is valid and complete. @@ -642,6 +666,10 @@ func (dev SerialDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) } + if isVirtioCCW[dev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", dev.DevNo)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) @@ -694,6 +722,9 @@ type BlockDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the BlockDevice structure is valid and complete. @@ -728,6 +759,10 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", blkdev.ROMFile)) } + if isVirtioCCW[blkdev.Driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", blkdev.DevNo)) + } + 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)) @@ -860,6 +895,9 @@ type VFIODevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the VFIODevice structure is valid and complete. @@ -879,6 +917,10 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) } + if isVirtioCCW[driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vfioDev.DevNo)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) @@ -903,6 +945,9 @@ type SCSIController struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the SCSIController structure is valid and complete. @@ -933,6 +978,10 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { devParams = append(devParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) } + if isVirtioCCW[driver] { + devParams = append(devParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) @@ -1041,6 +1090,9 @@ type VSOCKDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } const ( @@ -1086,6 +1138,10 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vsock.ROMFile)) } + if isVirtioCCW[driver] { + deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vsock.DevNo)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) @@ -1104,6 +1160,8 @@ type RngDevice struct { Period uint // ROMFile specifies the ROM file being used for this device. ROMFile string + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // Valid returns true if the RngDevice structure is valid and complete. @@ -1131,6 +1189,10 @@ func (v RngDevice) QemuParams(_ *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", v.ROMFile)) } + if isVirtioCCW[driver] { + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", v.DevNo)) + } + if v.Filename != "" { objectParams = append(objectParams, "filename="+v.Filename) } @@ -1160,6 +1222,9 @@ type BalloonDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // DevNo identifies the ccw devices for s390x architecture + DevNo string } // QemuParams returns the qemu parameters built out of the BalloonDevice. @@ -1178,6 +1243,10 @@ func (b BalloonDevice) QemuParams(_ *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } + if isVirtioCCW[driver] { + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", b.DevNo)) + } + if b.DeflateOnOOM { deviceParams = append(deviceParams, "deflate-on-oom=on") } else { diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index ca9d78e73f..a7bbed558e 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -61,6 +61,9 @@ var isVirtioPCI = map[DeviceDriver]bool{ PCIePCIBridgeDriver: true, } +// isVirtioCCW is a dummy map to return always false on no-s390x arch +var isVirtioCCW = map[DeviceDriver]bool{} + // QemuNetdevParam converts to the QEMU -netdev parameter notation func (n NetDeviceType) QemuNetdevParam() string { switch n { diff --git a/qemu/qemus390x.go b/qemu/qemus390x.go index 7c45c3edd9..c94d53a28b 100644 --- a/qemu/qemus390x.go +++ b/qemu/qemus390x.go @@ -63,6 +63,26 @@ var isVirtioPCI = map[DeviceDriver]bool{ PCIePCIBridgeDriver: false, } +// isVirtioCCW returns if the device is a ccw device +var isVirtioCCW = map[DeviceDriver]bool{ + NVDIMM: false, + Virtio9P: true, + VirtioNetCCW: true, + VirtioSerial: true, + VirtioBlock: true, + Console: false, + VirtioSerialPort: false, + VHostVSock: true, + VirtioRng: true, + VirtioBalloon: true, + VhostUserSCSI: false, + VhostUserBlk: false, + Vfio: true, + VirtioScsi: true, + PCIBridgeDriver: false, + PCIePCIBridgeDriver: false, +} + // QemuDeviceParam converts to the QEMU -device parameter notation // This function has been reimplemented for the s390x architecture to deal // with the VHOSTUSER case. Vhost user devices are not implemented on s390x diff --git a/qemu/qmp.go b/qemu/qmp.go index 359e236e69..665f69e9d2 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -822,9 +822,13 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b "driver": driver, "drive": blockdevID, } - if bus != "" { + + if isVirtioCCW[DeviceDriver(driver)] { + args["devno"] = bus + } else if bus != "" { args["bus"] = bus } + if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } @@ -870,8 +874,14 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive "id": devID, "driver": driver, "drive": blockdevID, - "bus": bus, } + + if isVirtioCCW[DeviceDriver(driver)] { + args["devno"] = bus + } else { + args["bus"] = bus + } + if scsiID >= 0 { args["scsi-id"] = scsiID } @@ -1023,13 +1033,13 @@ func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAd // using the device_add command. devID is the id of the device to add. // Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. // queues is the number of queues of a nic. -func (q *QMP) ExecuteNetCCWDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus string, queues int) error { +func (q *QMP) ExecuteNetCCWDeviceAdd(ctx context.Context, netdevID, devID, macAddr, bus string, queues int) error { args := map[string]interface{}{ "id": devID, "driver": VirtioNetCCW, "netdev": netdevID, "mac": macAddr, - "addr": addr, + "devno": bus, } if queues > 0 { From 65cc343f7b8e8cfc17df23d1f5611b0bc57ba1b9 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Mon, 1 Jul 2019 13:50:20 +0200 Subject: [PATCH 183/264] test: add devno in the tests for s390x Add test with devno param Signed-off-by: Alice Frosi --- qemu/qemu_arch_base_test.go | 2 -- qemu/qemu_test.go | 55 +++++++++++++++++++++++++++++++++++-- qemu/qemus390x_test.go | 27 +++++++++--------- qemu/qmp_test.go | 2 +- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 93eac3ee56..108a0bfdbd 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -24,8 +24,6 @@ var ( deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,romfile=efi-virtio.rom" deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" - deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,romfile=efi-virtio.rom" - deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom" deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index f5692f5013..c9538e836a 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -27,6 +27,13 @@ import ( const agentUUID = "4cb19522-1e18-439a-883a-f9b2a3a95f5e" const volumeUUID = "67d86208-b46c-4465-9018-e14187d4010" +var ( + deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,romfile=efi-virtio.rom" + deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" +) + +const DevNo = "fe.1.1234" + func testAppend(structure interface{}, expected string, t *testing.T) { var config Config testConfigAppend(&config, structure, expected, t) @@ -136,6 +143,10 @@ func TestAppendDeviceFS(t *testing.T) { ROMFile: "efi-virtio.rom", } + if isVirtioCCW[fsdev.Driver] { + fsdev.DevNo = DevNo + } + testAppend(fsdev, deviceFSString, t) } @@ -153,6 +164,10 @@ func TestAppendDeviceNetwork(t *testing.T) { ROMFile: "efi-virtio.rom", } + if isVirtioCCW[netdev.Driver] { + netdev.DevNo = DevNo + } + testAppend(netdev, deviceNetworkString, t) } @@ -180,6 +195,9 @@ func TestAppendDeviceNetworkMq(t *testing.T) { DisableModern: true, ROMFile: "efi-virtio.rom", } + if isVirtioCCW[netdev.Driver] { + netdev.DevNo = DevNo + } testAppend(netdev, deviceNetworkStringMq, t) } @@ -201,6 +219,10 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { ROMFile: romfile, } + if !isVirtioPCI[netdev.Driver] { + t.Skip("Test valid only for PCI devices") + } + testAppend(netdev, deviceNetworkPCIString, t) } @@ -231,6 +253,10 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { ROMFile: romfile, } + if !isVirtioPCI[netdev.Driver] { + t.Skip("Test valid only for PCI devices") + } + testAppend(netdev, deviceNetworkPCIStringMq, t) } @@ -241,6 +267,9 @@ func TestAppendDeviceSerial(t *testing.T) { DisableModern: true, ROMFile: romfile, } + if isVirtioCCW[sdev.Driver] { + sdev.DevNo = DevNo + } testAppend(sdev, deviceSerialString, t) } @@ -256,7 +285,9 @@ func TestAppendDeviceSerialPort(t *testing.T) { Path: "/tmp/char.sock", Name: "channel.0", } - + if isVirtioCCW[chardev.Driver] { + chardev.DevNo = DevNo + } testAppend(chardev, deviceSerialPortString, t) } @@ -273,7 +304,9 @@ func TestAppendDeviceBlock(t *testing.T) { DisableModern: true, ROMFile: romfile, } - + if isVirtioCCW[blkdev.Driver] { + blkdev.DevNo = DevNo + } testAppend(blkdev, deviceBlockString, t) } @@ -283,6 +316,10 @@ func TestAppendDeviceVFIO(t *testing.T) { ROMFile: romfile, } + if isVirtioCCW[Vfio] { + vfioDevice.DevNo = DevNo + } + testAppend(vfioDevice, deviceVFIOString, t) } @@ -295,6 +332,10 @@ func TestAppendVSOCK(t *testing.T) { ROMFile: romfile, } + if isVirtioCCW[VHostVSock] { + vsockDevice.DevNo = DevNo + } + testAppend(vsockDevice, deviceVSOCKString, t) } @@ -334,6 +375,11 @@ func TestAppendVirtioRng(t *testing.T) { ROMFile: romfile, } + if isVirtioCCW[VirtioRng] { + rngDevice.DevNo = DevNo + deviceString += ",devno=" + rngDevice.DevNo + } + testAppend(rngDevice, objectString+" "+deviceString, t) rngDevice.Filename = "/dev/urandom" @@ -389,6 +435,11 @@ func TestAppendDeviceSCSIController(t *testing.T) { ID: "foo", ROMFile: romfile, } + + if isVirtioCCW[VirtioScsi] { + scsiCon.DevNo = DevNo + } + testAppend(scsiCon, deviceSCSIControllerStr, t) scsiCon.Bus = "pci.0" diff --git a/qemu/qemus390x_test.go b/qemu/qemus390x_test.go index 1aac28434e..6b87d7b40c 100644 --- a/qemu/qemus390x_test.go +++ b/qemu/qemus390x_test.go @@ -23,17 +23,15 @@ import "testing" // -pci devices don't play well with Z hence replace them with corresponding -ccw devices // See https://wiki.qemu.org/Documentation/Platforms/S390X var ( - deviceFSString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" - deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef" - deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,mq=on" - deviceNetworkPCIString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff" - deviceNetworkPCIStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,bus=/pci-bus/pcie.0,addr=ff,mq=on" - deviceSerialString = "-device virtio-serial-ccw,id=serial0" - deviceVSOCKString = "-device vhost-vsock-ccw,id=vhost-vsock-pci0,guest-cid=4" - deviceVFIOString = "-device vfio-ccw,host=02:10.0" - deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo" - deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1" - deviceBlockString = "-device virtio-blk,drive=hd0,scsi=off,config-wce=off -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + deviceFSString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs,devno=" + DevNo + " -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" + deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,devno=" + DevNo + deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,mq=on,devno=" + DevNo + deviceSerialString = "-device virtio-serial-ccw,id=serial0,devno=" + DevNo + deviceVSOCKString = "-device vhost-vsock-ccw,id=vhost-vsock-pci0,guest-cid=4,devno=" + DevNo + deviceVFIOString = "-device vfio-ccw,host=02:10.0,devno=" + DevNo + deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo,devno=" + DevNo + deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1,devno=" + DevNo + deviceBlockString = "-device virtio-blk,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + " -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" romfile = "" @@ -46,12 +44,13 @@ func TestAppendVirtioBalloon(t *testing.T) { var deviceString = "-device " + string(VirtioBalloon) deviceString += ",id=" + balloonDevice.ID + balloonDevice.DevNo = DevNo + devnoOptios := ",devno=" + DevNo var OnDeflateOnOMM = ",deflate-on-oom=on" var OffDeflateOnOMM = ",deflate-on-oom=off" - - testAppend(balloonDevice, deviceString+OffDeflateOnOMM, t) + testAppend(balloonDevice, deviceString+devnoOptios+OffDeflateOnOMM, t) balloonDevice.DeflateOnOOM = true - testAppend(balloonDevice, deviceString+OnDeflateOnOMM, t) + testAppend(balloonDevice, deviceString+devnoOptios+OnDeflateOnOMM, t) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 125743f47b..8e4374e20b 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -557,7 +557,7 @@ func TestQMPNetCCWDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecuteNetCCWDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", 8) + err := q.ExecuteNetCCWDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", DevNo, 8) if err != nil { t.Fatalf("Unexpected error %v", err) } From 713d0d94066fe296cde0436f5d55ab8ee3e14f83 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Fri, 17 May 2019 11:42:08 +0200 Subject: [PATCH 184/264] s390x: add virtio-blk-ccw type In order to hotplug virtio-blk, on s390x the CCW device drivers is used instad of PCI. Signed-off-by: Alice Frosi --- qemu/qemu.go | 3 +++ qemu/qemus390x.go | 1 + 2 files changed, 4 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 31943e0a1c..0d46e84798 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -103,6 +103,9 @@ const ( // PCIePCIBridgeDriver represents a PCIe to PCI bridge device type. PCIePCIBridgeDriver DeviceDriver = "pcie-pci-bridge" + + // VirtioBlockCCW is the CCW block device driver + VirtioBlockCCW DeviceDriver = "virtio-blk-ccw" ) // disableModern returns the parameters with the disable-modern option. diff --git a/qemu/qemus390x.go b/qemu/qemus390x.go index c94d53a28b..c52edd67ce 100644 --- a/qemu/qemus390x.go +++ b/qemu/qemus390x.go @@ -70,6 +70,7 @@ var isVirtioCCW = map[DeviceDriver]bool{ VirtioNetCCW: true, VirtioSerial: true, VirtioBlock: true, + VirtioBlockCCW: true, Console: false, VirtioSerialPort: false, VHostVSock: true, From 8fd28e23acab0f464096502403211b972240480a Mon Sep 17 00:00:00 2001 From: Ace-Tang Date: Thu, 4 Jul 2019 21:59:17 +0800 Subject: [PATCH 185/264] Support x-pci-vendor-id and x-pci-device-id pass to qemu since some vendor id like 1ded can not be identified by virtio-pci driver, so upper level need to pass a specified vendor id to qemu. the upper level will change unavailable id and pass it to qemu. Signed-off-by: Ace-Tang --- qemu/qemu.go | 12 ++++++++++++ qemu/qemu_arch_base_test.go | 2 +- qemu/qemu_test.go | 6 ++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 0d46e84798..bd883f881b 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -901,6 +901,12 @@ type VFIODevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // VendorID specifies vendor id + VendorID string + + // DeviceID specifies device id + DeviceID string } // Valid returns true if the VFIODevice structure is valid and complete. @@ -917,6 +923,12 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf("%s,host=%s", driver, vfioDev.BDF)) if isVirtioPCI[driver] { + if vfioDev.VendorID != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-vendor-id=%s", vfioDev.VendorID)) + } + if vfioDev.DeviceID != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-device-id=%s", vfioDev.DeviceID)) + } deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) } diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 108a0bfdbd..76714a4823 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -27,7 +27,7 @@ var ( deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom" deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" - deviceVFIOString = "-device vfio-pci,host=02:10.0,romfile=efi-virtio.rom" + deviceVFIOString = "-device vfio-pci,host=02:10.0,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678,romfile=efi-virtio.rom" deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,disable-modern=false,romfile=efi-virtio.rom" deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index c9538e836a..5307c2d9bf 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -312,8 +312,10 @@ func TestAppendDeviceBlock(t *testing.T) { func TestAppendDeviceVFIO(t *testing.T) { vfioDevice := VFIODevice{ - BDF: "02:10.0", - ROMFile: romfile, + BDF: "02:10.0", + ROMFile: romfile, + VendorID: "0x1234", + DeviceID: "0x5678", } if isVirtioCCW[Vfio] { From a5c119086ac5db5ddfa9ae13c60b292f6f4b5721 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 15 Jul 2019 17:35:07 +0000 Subject: [PATCH 186/264] qemu: support x86 SMP die In QEMU 4.1 the CPU topology for x86 will change to: `socket > die > core > thread`. Add `die-id` field to `CPUProperties` and include it in CPU hotplugging Signed-off-by: Julio Montes --- qemu/qmp.go | 9 ++++++++- qemu/qmp_test.go | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 665f69e9d2..db75ba7c83 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -149,6 +149,7 @@ type QMPVersion struct { type CPUProperties struct { Node int `json:"node-id"` Socket int `json:"socket-id"` + Die int `json:"die-id"` Core int `json:"core-id"` Thread int `json:"thread-id"` } @@ -1162,7 +1163,7 @@ func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsd // node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the // thread number within core the CPU belongs to. Note that socketID and threadID are not a requirement for // architecures like ppc64le. -func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, coreID, threadID, romfile string) error { +func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, dieID, coreID, threadID, romfile string) error { args := map[string]interface{}{ "driver": driver, "id": cpuID, @@ -1177,6 +1178,12 @@ func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, args["thread-id"] = threadID } + if q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1) { + if dieID != "" { + args["die-id"] = dieID + } + } + if isVirtioPCI[DeviceDriver(driver)] { args["romfile"] = romfile } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 8e4374e20b..7598c8cd03 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1069,9 +1069,14 @@ func TestQMPCPUDeviceAdd(t *testing.T) { driver := "qemu64-x86_64-cpu" cpuID := "cpu-0" socketID := "0" + dieID := "0" coreID := "1" threadID := "0" - err := q.ExecuteCPUDeviceAdd(context.Background(), driver, cpuID, socketID, coreID, threadID, "") + q.version = &QMPVersion{ + Major: 4, + Minor: 1, + } + err := q.ExecuteCPUDeviceAdd(context.Background(), driver, cpuID, socketID, dieID, coreID, threadID, "") if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -1090,6 +1095,7 @@ func TestQMPExecuteQueryHotpluggableCPUs(t *testing.T) { Properties: CPUProperties{ Node: 1, Socket: 3, + Die: 1, Core: 2, Thread: 4, }, From e0cf9d5c148c70877645acf82f9605938879f390 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Mon, 22 Jul 2019 13:28:30 +0200 Subject: [PATCH 187/264] qmp: add checks for the CPU toplogy Support for function isSocketIDSupported, isThreadIDSupported and isDieIDSupported. The functions check if the cpu driver and the qemu version support the id parameter. Fixes: #102 Signed-off-by: Alice Frosi --- qemu/qmp.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index db75ba7c83..2a645ca24f 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1158,6 +1158,30 @@ func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsd return q.executeCommand(ctx, "device_add", args, nil) } +// isSocketIDSupported returns if the cpu driver supports the socket id option +func isSocketIDSupported(driver string) bool { + if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" { + return false + } + return true +} + +// isThreadIDSupported returns if the cpu driver supports the thread id option +func isThreadIDSupported(driver string) bool { + if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" { + return false + } + return true +} + +// isDieIDSupported returns if the cpu driver and the qemu version support the die id option +func (q *QMP) isDieIDSupported(driver string) bool { + if (q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1)) && driver == "host-x86_64-cpu" { + return true + } + return false +} + // ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command. // driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within // node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the @@ -1170,15 +1194,15 @@ func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, "core-id": coreID, } - if socketID != "" { + if socketID != "" && isSocketIDSupported(driver) { args["socket-id"] = socketID } - if threadID != "" { + if threadID != "" && isThreadIDSupported(driver) { args["thread-id"] = threadID } - if q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1) { + if q.isDieIDSupported(driver) { if dieID != "" { args["die-id"] = dieID } From 68cdf64fe5070c8e25a938d563fbcced23571398 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Thu, 25 Jul 2019 15:53:33 +0200 Subject: [PATCH 188/264] test: add cpu topology tests Add cpu driver types in TestQMPCPUDeviceAdd Signed-off-by: Alice Frosi --- qemu/qmp_test.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 7598c8cd03..11b33a0e05 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1059,29 +1059,32 @@ func TestQMPPCIVFIOMediatedDeviceAdd(t *testing.T) { // Checks that CPU are correctly added using device_add func TestQMPCPUDeviceAdd(t *testing.T) { - connectedCh := make(chan *QMPVersion) - disconnectedCh := make(chan struct{}) - buf := newQMPTestCommandBuffer(t) - buf.AddCommand("device_add", nil, "return", nil) - cfg := QMPConfig{Logger: qmpTestLogger{}} - q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) - checkVersion(t, connectedCh) - driver := "qemu64-x86_64-cpu" + drivers := []string{"host-x86_64-cpu", "host-s390x-cpu", "host-powerpc64-cpu"} cpuID := "cpu-0" socketID := "0" dieID := "0" coreID := "1" threadID := "0" - q.version = &QMPVersion{ + version := &QMPVersion{ Major: 4, Minor: 1, } - err := q.ExecuteCPUDeviceAdd(context.Background(), driver, cpuID, socketID, dieID, coreID, threadID, "") - if err != nil { - t.Fatalf("Unexpected error %v", err) + for _, d := range drivers { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + q.version = version + err := q.ExecuteCPUDeviceAdd(context.Background(), d, cpuID, socketID, dieID, coreID, threadID, "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh } - q.Shutdown() - <-disconnectedCh } // Checks that hotpluggable CPUs are listed correctly From 79e0d5333d1545898661e5038e08de8a59be3ea1 Mon Sep 17 00:00:00 2001 From: Ning Bo Date: Thu, 1 Aug 2019 09:05:27 +0800 Subject: [PATCH 189/264] qmp: support command 'query-qmp-schema' The upper hyervisor manager application maybe need to wait some QMP event to control boot sequence, but the event we wanted maybe not exist in some older version, so we need query all QMP ABI and check the event is supported or not. related: kata-containers/runtime#1918 Signed-off-by: Ning Bo --- qemu/qmp.go | 36 ++++++++++++++++++++++++++++++++++++ qemu/qmp_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 2a645ca24f..186f3fadd1 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -84,6 +84,9 @@ type QMPConfig struct { // logger is used by the qmpStart function and all the go routines // it spawns to log information. Logger QMPLog + + // specify the capacity of buffer used by receive QMP response. + MaxCapacity int } type qmpEventFilter struct { @@ -242,8 +245,19 @@ type MigrationStatus struct { XbzrleCache MigrationXbzrleCache `json:"xbzrle-cache,omitempty"` } +// SchemaInfo represents all QMP wire ABI +type SchemaInfo struct { + MetaType string `json:"meta-type"` + Name string `json:"name"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) + if q.cfg.MaxCapacity > 0 { + buffer := make([]byte, q.cfg.MaxCapacity) + scanner.Buffer(buffer, q.cfg.MaxCapacity) + } + for scanner.Scan() { line := scanner.Bytes() if q.cfg.Logger.V(1) { @@ -260,6 +274,7 @@ func (q *QMP) readLoop(fromVMCh chan<- []byte) { fromVMCh <- sendLine } + q.cfg.Logger.Infof("sanner return error: %v", scanner.Err()) close(fromVMCh) } @@ -1509,3 +1524,24 @@ func (q *QMP) ExecuteMigrationIncoming(ctx context.Context, uri string) error { } return q.executeCommand(ctx, "migrate-incoming", args, nil) } + +// ExecQueryQmpSchema query all QMP wire ABI and returns a slice +func (q *QMP) ExecQueryQmpSchema(ctx context.Context) ([]SchemaInfo, error) { + response, err := q.executeCommandWithResponse(ctx, "query-qmp-schema", nil, nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("unable to extract memory devices information: %v", err) + } + + var schemaInfo []SchemaInfo + if err = json.Unmarshal(data, &schemaInfo); err != nil { + return nil, fmt.Errorf("unable to convert json to schemaInfo: %v", err) + } + + return schemaInfo, nil +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 11b33a0e05..067665371f 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1588,3 +1588,38 @@ func TestMainLoopEventBeforeGreeting(t *testing.T) { q.Shutdown() <-disconnectedCh } + +func TestQMPExecQueryQmpSchema(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + schemaInfo := []SchemaInfo{ + { + MetaType: "command", + Name: "object-add", + }, + { + MetaType: "event", + Name: "VSOCK_RUNNING", + }, + } + buf.AddCommand("query-qmp-schema", nil, "return", schemaInfo) + cfg := QMPConfig{ + Logger: qmpTestLogger{}, + MaxCapacity: 1024, + } + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + info, err := q.ExecQueryQmpSchema(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(schemaInfo) != 2 { + t.Fatalf("Expected schema infos length equals to 2\n") + } + if reflect.DeepEqual(info, schemaInfo) == false { + t.Fatalf("Expected %v equals to %v\n", info, schemaInfo) + } + q.Shutdown() + <-disconnectedCh +} From 30bfcaaa6d7e9fb92346554c57f1af443a44c8fe Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 13 Aug 2019 01:42:29 -0700 Subject: [PATCH 190/264] qemu: add debug logfile When LogFile is specified, output debug log there. Signed-off-by: Peng Tao --- qemu/qemu.go | 11 +++++++++++ qemu/qemu_test.go | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index bd883f881b..e1394831e6 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1557,6 +1557,9 @@ type Config struct { // PidFile is the -pidfile parameter PidFile string + // LogFile is the -D parameter + LogFile string + qemuParams []string } @@ -1880,6 +1883,13 @@ func (config *Config) appendPidFile() { } } +func (config *Config) appendLogFile() { + if config.LogFile != "" { + config.qemuParams = append(config.qemuParams, "-D") + config.qemuParams = append(config.qemuParams, config.LogFile) + } +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -1906,6 +1916,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendIOThreads() config.appendIncoming() config.appendPidFile() + config.appendLogFile() if err := config.appendCPUs(); err != nil { return "", err diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 5307c2d9bf..577660d783 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -712,7 +712,8 @@ func TestAppendQMPSocketServer(t *testing.T) { } var pidfile = "/run/vc/vm/iamsandboxid/pidfile" -var qemuString = "-name cc-qemu -cpu host -uuid " + agentUUID + " -pidfile " + pidfile +var logfile = "/run/vc/vm/iamsandboxid/logfile" +var qemuString = "-name cc-qemu -cpu host -uuid " + agentUUID + " -pidfile " + pidfile + " -D " + logfile func TestAppendStrings(t *testing.T) { config := Config{ @@ -721,12 +722,14 @@ func TestAppendStrings(t *testing.T) { UUID: agentUUID, CPUModel: "host", PidFile: pidfile, + LogFile: logfile, } config.appendName() config.appendCPUModel() config.appendUUID() config.appendPidFile() + config.appendLogFile() result := strings.Join(config.qemuParams, " ") if result != qemuString { From 234e0edfd72da840b759bcf5b6b65cb7b05952fd Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 13 Aug 2019 01:58:51 -0700 Subject: [PATCH 191/264] qemu: fix memory prealloc handling Memory preallocation is just a property of different memory backends. We should treat it similar to memory sharing property. Also rename FileBackedMemShared to MemShared as it is just another memory backend property that works with different memory backends not just file backed memory. Signed-off-by: Peng Tao --- qemu/qemu.go | 70 +++++++++++++------------------- qemu/qemu_test.go | 101 +++++++++++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 87 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index e1394831e6..4cb18d10ad 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1456,8 +1456,8 @@ type Knobs struct { // be set. FileBackedMem bool - // FileBackedMemShared will set the FileBackedMem device as shared. - FileBackedMemShared bool + // MemShared will set the memory device as shared. + MemShared bool // Mlock will control locking of memory // Only active when Realtime is set to true @@ -1758,46 +1758,34 @@ func (config *Config) appendKernel() { } func (config *Config) appendMemoryKnobs() { - if config.Knobs.HugePages { - if config.Memory.Size != "" { - dimmName := "dimm1" - objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=/dev/hugepages,share=on,prealloc=on" - numaMemParam := "node,memdev=" + dimmName - - config.qemuParams = append(config.qemuParams, "-object") - config.qemuParams = append(config.qemuParams, objMemParam) - - config.qemuParams = append(config.qemuParams, "-numa") - config.qemuParams = append(config.qemuParams, numaMemParam) - } - } else if config.Knobs.MemPrealloc { - if config.Memory.Size != "" { - dimmName := "dimm1" - objMemParam := "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + ",prealloc=on" - numaMemParam := "node,memdev=" + dimmName - - config.qemuParams = append(config.qemuParams, "-object") - config.qemuParams = append(config.qemuParams, objMemParam) - - config.qemuParams = append(config.qemuParams, "-numa") - config.qemuParams = append(config.qemuParams, numaMemParam) - } - } else if config.Knobs.FileBackedMem { - if config.Memory.Size != "" && config.Memory.Path != "" { - dimmName := "dimm1" - objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=" + config.Memory.Path - if config.Knobs.FileBackedMemShared { - objMemParam += ",share=on" - } - numaMemParam := "node,memdev=" + dimmName - - config.qemuParams = append(config.qemuParams, "-object") - config.qemuParams = append(config.qemuParams, objMemParam) - - config.qemuParams = append(config.qemuParams, "-numa") - config.qemuParams = append(config.qemuParams, numaMemParam) - } + if config.Memory.Size == "" { + return } + + var objMemParam, numaMemParam string + dimmName := "dimm1" + if config.Knobs.HugePages { + objMemParam = "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=/dev/hugepages" + numaMemParam = "node,memdev=" + dimmName + } else if config.Knobs.FileBackedMem && config.Memory.Path != "" { + objMemParam = "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=" + config.Memory.Path + numaMemParam = "node,memdev=" + dimmName + } else { + objMemParam = "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + numaMemParam = "node,memdev=" + dimmName + } + + if config.Knobs.MemShared { + objMemParam += ",share=on" + } + if config.Knobs.MemPrealloc { + objMemParam += ",prealloc=on" + } + config.qemuParams = append(config.qemuParams, "-object") + config.qemuParams = append(config.qemuParams, objMemParam) + + config.qemuParams = append(config.qemuParams, "-numa") + config.qemuParams = append(config.qemuParams, numaMemParam) } func (config *Config) appendKnobs() { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 577660d783..bc527ab6bc 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -488,16 +488,16 @@ func TestAppendEmptyDevice(t *testing.T) { func TestAppendKnobsAllTrue(t *testing.T) { var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on -S" knobs := Knobs{ - NoUserConfig: true, - NoDefaults: true, - NoGraphic: true, - Daemonize: true, - MemPrealloc: true, - FileBackedMem: true, - FileBackedMemShared: true, - Realtime: true, - Mlock: true, - Stopped: true, + NoUserConfig: true, + NoDefaults: true, + NoGraphic: true, + Daemonize: true, + MemPrealloc: true, + FileBackedMem: true, + MemShared: true, + Realtime: true, + Mlock: true, + Stopped: true, } testAppend(knobs, knobsString, t) @@ -506,15 +506,15 @@ func TestAppendKnobsAllTrue(t *testing.T) { func TestAppendKnobsAllFalse(t *testing.T) { var knobsString = "-realtime mlock=off" knobs := Knobs{ - NoUserConfig: false, - NoDefaults: false, - NoGraphic: false, - MemPrealloc: false, - FileBackedMem: false, - FileBackedMemShared: false, - Realtime: false, - Mlock: false, - Stopped: false, + NoUserConfig: false, + NoDefaults: false, + NoGraphic: false, + MemPrealloc: false, + FileBackedMem: false, + MemShared: false, + Realtime: false, + Mlock: false, + Stopped: false, } testAppend(knobs, knobsString, t) @@ -533,10 +533,10 @@ func TestAppendMemoryHugePages(t *testing.T) { testConfigAppend(conf, conf.Memory, memString, t) knobs := Knobs{ - HugePages: true, - MemPrealloc: true, - FileBackedMem: true, - FileBackedMemShared: true, + HugePages: true, + MemPrealloc: true, + FileBackedMem: true, + MemShared: true, } knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=/dev/hugepages,share=on,prealloc=on -numa node,memdev=dimm1" mlockFalseString := "-realtime mlock=off" @@ -557,17 +557,16 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { testConfigAppend(conf, conf.Memory, memString, t) knobs := Knobs{ - MemPrealloc: true, - FileBackedMem: true, - FileBackedMemShared: true, + MemPrealloc: true, + MemShared: true, } - knobsString := "-object memory-backend-ram,id=dimm1,size=1G,prealloc=on -numa node,memdev=dimm1" + knobsString := "-object memory-backend-ram,id=dimm1,size=1G,share=on,prealloc=on -numa node,memdev=dimm1" mlockFalseString := "-realtime mlock=off" testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } -func TestAppendMemoryFileBackedMemShared(t *testing.T) { +func TestAppendMemoryMemShared(t *testing.T) { conf := &Config{ Memory: Memory{ Size: "1G", @@ -580,8 +579,8 @@ func TestAppendMemoryFileBackedMemShared(t *testing.T) { testConfigAppend(conf, conf.Memory, memString, t) knobs := Knobs{ - FileBackedMem: true, - FileBackedMemShared: true, + FileBackedMem: true, + MemShared: true, } knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on -numa node,memdev=dimm1" mlockFalseString := "-realtime mlock=off" @@ -602,8 +601,8 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { testConfigAppend(conf, conf.Memory, memString, t) knobs := Knobs{ - FileBackedMem: true, - FileBackedMemShared: false, + FileBackedMem: true, + MemShared: false, } knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar -numa node,memdev=dimm1" mlockFalseString := "-realtime mlock=off" @@ -611,6 +610,29 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } +func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { + conf := &Config{ + Memory: Memory{ + Size: "1G", + Slots: 8, + MaxMem: "3G", + Path: "foobar", + }, + } + memString := "-m 1G,slots=8,maxmem=3G" + testConfigAppend(conf, conf.Memory, memString, t) + + knobs := Knobs{ + FileBackedMem: true, + MemShared: true, + MemPrealloc: true, + } + knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on,prealloc=on -numa node,memdev=dimm1" + mlockFalseString := "-realtime mlock=off" + + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) +} + var kernelString = "-kernel /opt/vmlinux.container -initrd /opt/initrd.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" func TestAppendKernel(t *testing.T) { @@ -987,7 +1009,7 @@ func TestBadMemoryKnobs(t *testing.T) { c = &Config{ Knobs: Knobs{ - HugePages: true, + MemShared: true, }, } c.appendMemoryKnobs() @@ -1004,19 +1026,6 @@ func TestBadMemoryKnobs(t *testing.T) { if len(c.qemuParams) != 0 { t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) } - - c = &Config{ - Knobs: Knobs{ - FileBackedMem: true, - }, - Memory: Memory{ - Size: "1024", - }, - } - c.appendMemoryKnobs() - if len(c.qemuParams) != 0 { - t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) - } } func TestBadKnobs(t *testing.T) { From 73555a409cbc8c3f612a0ac8e36294befb21da48 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 13 Aug 2019 02:06:46 -0700 Subject: [PATCH 192/264] qmp: add query-status API So that caller can find out guest status via qmp. Signed-off-by: Peng Tao --- qemu/qmp.go | 27 +++++++++++++++++++++++++++ qemu/qmp_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 186f3fadd1..473ce4afaa 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -251,6 +251,13 @@ type SchemaInfo struct { Name string `json:"name"` } +// StatusInfo represents guest running status +type StatusInfo struct { + Running bool `json:"running"` + SingleStep bool `json:"singlestep"` + Status string `json:"status"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) if q.cfg.MaxCapacity > 0 { @@ -1545,3 +1552,23 @@ func (q *QMP) ExecQueryQmpSchema(ctx context.Context) ([]SchemaInfo, error) { return schemaInfo, nil } + +// ExecuteQueryStatus queries guest status +func (q *QMP) ExecuteQueryStatus(ctx context.Context) (StatusInfo, error) { + response, err := q.executeCommandWithResponse(ctx, "query-status", nil, nil, nil) + if err != nil { + return StatusInfo{}, err + } + + data, err := json.Marshal(response) + if err != nil { + return StatusInfo{}, fmt.Errorf("unable to extract migrate status information: %v", err) + } + + var status StatusInfo + if err = json.Unmarshal(data, &status); err != nil { + return StatusInfo{}, fmt.Errorf("unable to convert migrate status information: %v", err) + } + + return status, nil +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 067665371f..bfad9d0dd6 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1623,3 +1623,30 @@ func TestQMPExecQueryQmpSchema(t *testing.T) { q.Shutdown() <-disconnectedCh } + +func TestQMPExecQueryQmpStatus(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + statusInfo := StatusInfo{ + Running: true, + SingleStep: false, + Status: "running", + } + buf.AddCommand("query-status", nil, "return", statusInfo) + cfg := QMPConfig{ + Logger: qmpTestLogger{}, + MaxCapacity: 1024, + } + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + info, err := q.ExecuteQueryStatus(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if reflect.DeepEqual(info, statusInfo) == false { + t.Fatalf("Expected %v equals to %v\n", info, statusInfo) + } + q.Shutdown() + <-disconnectedCh +} From 164bd8cd22d30ea07be6d480cc57d03bc0c44dce Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Tue, 13 Aug 2019 23:36:07 -0700 Subject: [PATCH 193/264] test/fmt: drop extra newlines They are unneeded. Signed-off-by: Peng Tao --- qemu/qemu_test.go | 2 +- qemu/qmp_test.go | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index bc527ab6bc..7caba015e9 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -64,7 +64,7 @@ func testConfigAppend(config *Config, structure interface{}, expected string, t case SMP: config.SMP = s if err := config.appendCPUs(); err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } case QMPSocket: diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index bfad9d0dd6..c5722765ab 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1110,13 +1110,13 @@ func TestQMPExecuteQueryHotpluggableCPUs(t *testing.T) { checkVersion(t, connectedCh) hotCPUs, err := q.ExecuteQueryHotpluggableCPUs(context.Background()) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } if len(hotCPUs) != 1 { t.Fatalf("Expected hot CPUs length equals to 1\n") } if reflect.DeepEqual(hotCPUs[0], hotCPU) == false { - t.Fatalf("Expected %v equals to %v\n", hotCPUs[0], hotCPU) + t.Fatalf("Expected %v equals to %v", hotCPUs[0], hotCPU) } q.Shutdown() <-disconnectedCh @@ -1146,13 +1146,13 @@ func TestQMPExecuteQueryMemoryDevices(t *testing.T) { checkVersion(t, connectedCh) memDevices, err := q.ExecQueryMemoryDevices(context.Background()) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } if len(memDevices) != 1 { t.Fatalf("Expected memory devices length equals to 1\n") } if reflect.DeepEqual(memDevices[0], memoryDevices) == false { - t.Fatalf("Expected %v equals to %v\n", memDevices[0], memoryDevices) + t.Fatalf("Expected %v equals to %v", memDevices[0], memoryDevices) } q.Shutdown() <-disconnectedCh @@ -1184,13 +1184,13 @@ func TestQMPExecuteQueryCpus(t *testing.T) { checkVersion(t, connectedCh) cpus, err := q.ExecQueryCpus(context.Background()) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } if len(cpus) != 1 { t.Fatalf("Expected memory devices length equals to 1\n") } if reflect.DeepEqual(cpus[0], cpuInfo) == false { - t.Fatalf("Expected %v equals to %v\n", cpus[0], cpuInfo) + t.Fatalf("Expected %v equals to %v", cpus[0], cpuInfo) } q.Shutdown() <-disconnectedCh @@ -1220,13 +1220,13 @@ func TestQMPExecuteQueryCpusFast(t *testing.T) { checkVersion(t, connectedCh) cpus, err := q.ExecQueryCpusFast(context.Background()) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } if len(cpus) != 1 { t.Fatalf("Expected memory devices length equals to 1\n") } if reflect.DeepEqual(cpus[0], cpuInfoFast) == false { - t.Fatalf("Expected %v equals to %v\n", cpus[0], cpuInfoFast) + t.Fatalf("Expected %v equals to %v", cpus[0], cpuInfoFast) } q.Shutdown() <-disconnectedCh @@ -1249,7 +1249,7 @@ func TestExecSetMigrationCaps(t *testing.T) { } err := q.ExecSetMigrationCaps(context.Background(), caps) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } q.Shutdown() <-disconnectedCh @@ -1266,7 +1266,7 @@ func TestExecSetMigrateArguments(t *testing.T) { checkVersion(t, connectedCh) err := q.ExecSetMigrateArguments(context.Background(), "exec:foobar") if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } q.Shutdown() <-disconnectedCh @@ -1284,7 +1284,7 @@ func TestExecHotplugMemory(t *testing.T) { checkVersion(t, connectedCh) err := q.ExecHotplugMemory(context.Background(), "memory-backend-ram", "mem0", "", 128, true) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } q.Shutdown() <-disconnectedCh @@ -1419,7 +1419,7 @@ func TestExecuteQueryMigration(t *testing.T) { t.Fatalf("Unexpected error %v", err) } if !reflect.DeepEqual(s, status) { - t.Fatalf("expected %v\n got %v", status, s) + t.Fatalf("expected %v got %v", status, s) } q.Shutdown() <-disconnectedCh @@ -1538,7 +1538,7 @@ func TestExecuteNVDIMMDeviceAdd(t *testing.T) { checkVersion(t, connectedCh) err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } q.Shutdown() <-disconnectedCh @@ -1612,13 +1612,13 @@ func TestQMPExecQueryQmpSchema(t *testing.T) { checkVersion(t, connectedCh) info, err := q.ExecQueryQmpSchema(context.Background()) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } if len(schemaInfo) != 2 { t.Fatalf("Expected schema infos length equals to 2\n") } if reflect.DeepEqual(info, schemaInfo) == false { - t.Fatalf("Expected %v equals to %v\n", info, schemaInfo) + t.Fatalf("Expected %v equals to %v", info, schemaInfo) } q.Shutdown() <-disconnectedCh @@ -1642,10 +1642,10 @@ func TestQMPExecQueryQmpStatus(t *testing.T) { checkVersion(t, connectedCh) info, err := q.ExecuteQueryStatus(context.Background()) if err != nil { - t.Fatalf("Unexpected error: %v\n", err) + t.Fatalf("Unexpected error: %v", err) } if reflect.DeepEqual(info, statusInfo) == false { - t.Fatalf("Expected %v equals to %v\n", info, statusInfo) + t.Fatalf("Expected %v equals to %v", info, statusInfo) } q.Shutdown() <-disconnectedCh From 9463486d58e8e481a6b79a6cb348d358a93cf23c Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Mon, 26 Aug 2019 16:05:17 +0200 Subject: [PATCH 194/264] s390x: dimm not supported Dimm is not supported on s390x Fixes: #106 Signed-off-by: Alice Frosi --- qemu/qemu.go | 4 +++- qemu/qemu_arch_base.go | 4 ++++ qemu/qemu_test.go | 20 ++++++++++++++++++++ qemu/qemus390x.go | 4 ++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 4cb18d10ad..daac268bdb 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1761,7 +1761,9 @@ func (config *Config) appendMemoryKnobs() { if config.Memory.Size == "" { return } - + if !isDimmSupported() { + return + } var objMemParam, numaMemParam string dimmName := "dimm1" if config.Knobs.HugePages { diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index a7bbed558e..f295099cee 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -105,3 +105,7 @@ func (n NetDeviceType) QemuDeviceParam() DeviceDriver { } } + +func isDimmSupported() bool { + return true +} diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 7caba015e9..3c2307858c 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -521,6 +521,10 @@ func TestAppendKnobsAllFalse(t *testing.T) { } func TestAppendMemoryHugePages(t *testing.T) { + if !isDimmSupported() { + t.Skip("Dimm not supported") + } + conf := &Config{ Memory: Memory{ Size: "1G", @@ -545,6 +549,10 @@ func TestAppendMemoryHugePages(t *testing.T) { } func TestAppendMemoryMemPrealloc(t *testing.T) { + if !isDimmSupported() { + t.Skip("Dimm not supported") + } + conf := &Config{ Memory: Memory{ Size: "1G", @@ -567,6 +575,10 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { } func TestAppendMemoryMemShared(t *testing.T) { + if !isDimmSupported() { + t.Skip("Dimm not supported") + } + conf := &Config{ Memory: Memory{ Size: "1G", @@ -589,6 +601,10 @@ func TestAppendMemoryMemShared(t *testing.T) { } func TestAppendMemoryFileBackedMem(t *testing.T) { + if !isDimmSupported() { + t.Skip("Dimm not supported") + } + conf := &Config{ Memory: Memory{ Size: "1G", @@ -611,6 +627,10 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { } func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { + if !isDimmSupported() { + t.Skip("Dimm not supported") + } + conf := &Config{ Memory: Memory{ Size: "1G", diff --git a/qemu/qemus390x.go b/qemu/qemus390x.go index c52edd67ce..0aeffceeb5 100644 --- a/qemu/qemus390x.go +++ b/qemu/qemus390x.go @@ -134,3 +134,7 @@ func (n NetDeviceType) QemuNetdevParam() string { } } + +func isDimmSupported() bool { + return false +} From cb9f640b4e6166c295e34f1ac4acea56e78940a6 Mon Sep 17 00:00:00 2001 From: Jan Schintag Date: Fri, 13 Sep 2019 08:58:23 +0200 Subject: [PATCH 195/264] virtio-blk: Add support for share-rw flag This allows multiple instances of qemu to share the same file for virtio-blk device. Fixes: #108 Signed-off-by: Jan Schintag --- qemu/qemu.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index daac268bdb..f2b80ccd16 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -728,6 +728,9 @@ type BlockDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // ShareRW enables multiple qemu instances to share the File + ShareRW bool } // Valid returns true if the BlockDevice structure is valid and complete. @@ -766,6 +769,10 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", blkdev.DevNo)) } + if blkdev.ShareRW { + deviceParams = append(deviceParams, fmt.Sprintf(",share-rw=on")) + } + 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)) From 175ac4993ec3b00973b667ac0865ecde631406a3 Mon Sep 17 00:00:00 2001 From: Chenbin Date: Sat, 21 Sep 2019 19:52:56 +0800 Subject: [PATCH 196/264] typo fix --- qemu/qmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 473ce4afaa..0067ea4500 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -281,7 +281,7 @@ func (q *QMP) readLoop(fromVMCh chan<- []byte) { fromVMCh <- sendLine } - q.cfg.Logger.Infof("sanner return error: %v", scanner.Err()) + q.cfg.Logger.Infof("scanner return error: %v", scanner.Err()) close(fromVMCh) } From 6d6b2d88927b6ac858190c70ec9c2185d1457965 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Tue, 17 Dec 2019 14:45:43 +0100 Subject: [PATCH 197/264] s390x: add s390x travis support Since we have travis support for s390x. Let's enable it Signed-off-by: Alice Frosi --- .travis.yml | 5 +++-- qemu/qemu_arch_base.go | 2 +- qemu/qemu_arch_base_test.go | 2 +- qemu/{qemus390x.go => qemu_s390x.go} | 2 +- qemu/{qemus390x_test.go => qemu_s390x_test.go} | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) rename qemu/{qemus390x.go => qemu_s390x.go} (99%) rename qemu/{qemus390x_test.go => qemu_s390x_test.go} (99%) diff --git a/.travis.yml b/.travis.yml index 41aec9bb00..162aa50420 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ go: - "1.10" - "1.11" - tip +arch: + - amd64 + - s390x go_import_path: github.com/intel/govmm @@ -20,5 +23,3 @@ script: - go env - $GOPATH/bin/goveralls -v -service=travis-ci - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... - - GOARCH=s390x go install ./... - - go test --tags s390x_test ./... diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index f295099cee..d73c34f9a0 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -1,4 +1,4 @@ -// +build !s390x,!s390x_test +// +build !s390x /* // Copyright contributors to the Virtual Machine Manager for Go project diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 76714a4823..242eb82000 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -1,4 +1,4 @@ -// +build !s390x,!s390x_test +// +build !s390x /* // Copyright contributors to the Virtual Machine Manager for Go project diff --git a/qemu/qemus390x.go b/qemu/qemu_s390x.go similarity index 99% rename from qemu/qemus390x.go rename to qemu/qemu_s390x.go index 0aeffceeb5..6e54f9b719 100644 --- a/qemu/qemus390x.go +++ b/qemu/qemu_s390x.go @@ -1,4 +1,4 @@ -// +build s390x s390x_test +// +build s390x /* // Copyright contributors to the Virtual Machine Manager for Go project diff --git a/qemu/qemus390x_test.go b/qemu/qemu_s390x_test.go similarity index 99% rename from qemu/qemus390x_test.go rename to qemu/qemu_s390x_test.go index 6b87d7b40c..a9cc66cc53 100644 --- a/qemu/qemus390x_test.go +++ b/qemu/qemu_s390x_test.go @@ -1,4 +1,4 @@ -// +build s390x s390x_test +// +build s390x /* // Copyright contributors to the Virtual Machine Manager for Go project From 13aeba09d56ce44e531cd0aaf531f01fe11acaee Mon Sep 17 00:00:00 2001 From: Liu Xiaodong Date: Tue, 14 Jan 2020 00:09:52 -0500 Subject: [PATCH 198/264] qmp: support command 'chardev-remove' So that caller can remove hotremove chardev via qmp Signed-off-by: Liu Xiaodong --- qemu/qmp.go | 11 +++++++++++ qemu/qmp_test.go | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 473ce4afaa..a22edba127 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -946,6 +946,17 @@ func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error { return q.executeCommand(ctx, "x-blockdev-del", args, nil) } +// ExecuteChardevDel deletes a char device by sending a chardev-remove command. +// chardevID is the id of the char device to be deleted. Typically, this will +// match the id passed to ExecuteCharDevUnixSocketAdd. It must be a valid QMP id. +func (q *QMP) ExecuteChardevDel(ctx context.Context, chardevID string) error { + args := map[string]interface{}{ + "id": chardevID, + } + + return q.executeCommand(ctx, "chardev-remove", args, nil) +} + // ExecuteNetdevAdd adds a Net device to a QEMU instance // using the netdev_add command. netdevID is the id of the device to add. // Must be valid QMP identifier. diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index c5722765ab..8137910a87 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -638,6 +638,28 @@ func TestQMPBlockdevDel(t *testing.T) { <-disconnectedCh } +// Checks that the chardev-remove command is correctly sent. +// +// We start a QMPLoop, send the chardev-remove command and stop the loop. +// +// The chardev-remove command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPChardevDel(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("chardev-remove", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + q.version = checkVersion(t, connectedCh) + err := q.ExecuteChardevDel(context.Background(), "chardev-0") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that the device_del command is correctly sent. // // We start a QMPLoop, send the device_del command and wait for it to complete. From e04be2cc3819c3b6822513262aeb4db24c395f8d Mon Sep 17 00:00:00 2001 From: Liu Xiaodong Date: Tue, 14 Jan 2020 00:13:26 -0500 Subject: [PATCH 199/264] qmp: add ExecutePCIVhostUserDevAdd API Caller can hotplug vhost-user device via qmp. The Qemu vhost-user device, like vhost-user-blk-pci and vhost-user-scsi-pci can be hotplugged by qmp API: ExecuteCharDevUnixSocketAdd() together with ExecutePCIVhostUserDevAdd() Signed-off-by: Liu Xiaodong --- qemu/qmp.go | 21 +++++++++++++++++++++ qemu/qmp_test.go | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index a22edba127..9d27dbeefb 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1135,6 +1135,27 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver return q.executeCommand(ctx, "device_add", args, nil) } +// ExecutePCIVhostUserDevAdd adds a vhost-user device to a QEMU instance using the device_add command. +// This function can be used to hot plug vhost-user devices on PCI(E) bridges. +// It receives the bus and the device address on its parent bus. bus is optional. +// devID is the id of the device to add.Must be valid QMP identifier. chardevID +// is the QMP identifier of character device using a unix socket as backend. +// driver is the name of vhost-user driver, like vhost-user-blk-pci. +func (q *QMP) ExecutePCIVhostUserDevAdd(ctx context.Context, driver, devID, chardevID, addr, bus string) error { + args := map[string]interface{}{ + "driver": driver, + "id": devID, + "chardev": chardevID, + "addr": addr, + } + + if bus != "" { + args["bus"] = bus + } + + return q.executeCommand(ctx, "device_add", args, nil) +} + // ExecuteVFIODeviceAdd adds a VFIO device to a QEMU instance // using the device_add command. devID is the id of the device to add. // Must be valid QMP identifier. bdf is the PCI bus-device-function diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 8137910a87..71f97bcadd 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1329,6 +1329,26 @@ func TestExecutePCIVSockAdd(t *testing.T) { <-disconnectedCh } +// Checks vhost-user-pci hotplug +func TestExecutePCIVhostUserDevAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + driver := "vhost-user-blk-pci" + devID := "vhost-user-blk0" + chardevID := "vhost-user-blk-char0" + err := q.ExecutePCIVhostUserDevAdd(context.Background(), driver, devID, chardevID, "1", "1") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks getfd func TestExecuteGetFdD(t *testing.T) { connectedCh := make(chan *QMPVersion) From 201fd0ae82643d980888c381b399464ebdded76a Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Sun, 19 Jan 2020 14:51:17 +0800 Subject: [PATCH 200/264] qmp: Add ExecMemdevAdd and ExecQomSet API Add ExecMemdevAdd and ExecQomSet API to support virtio-mem. Signed-off-by: Hui Zhu --- qemu/qmp.go | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9d27dbeefb..00e9fed279 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1379,8 +1379,8 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { return cpuInfoFast, nil } -// ExecHotplugMemory adds size of MiB memory to the guest -func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int, share bool) error { +// ExecMemdevAdd adds size of MiB memory device to the guest +func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, size int, share bool, driver, driverID string) error { props := map[string]interface{}{"size": uint64(size) << 20} args := map[string]interface{}{ "qom-type": qomtype, @@ -1400,17 +1400,17 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string defer func() { if err != nil { - q.cfg.Logger.Errorf("Unable to hotplug memory device: %v", err) + q.cfg.Logger.Errorf("Unable to add memory device %s: %v", id, err) err = q.executeCommand(ctx, "object-del", map[string]interface{}{"id": id}, nil) if err != nil { - q.cfg.Logger.Warningf("Unable to clean up memory object: %v", err) + q.cfg.Logger.Warningf("Unable to clean up memory object %s: %v", id, err) } } }() args = map[string]interface{}{ - "driver": "pc-dimm", - "id": "dimm" + id, + "driver": driver, + "id": driverID, "memdev": id, } err = q.executeCommand(ctx, "device_add", args, nil) @@ -1418,6 +1418,11 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string return err } +// ExecHotplugMemory adds size of MiB memory to the guest +func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int, share bool) error { + return q.ExecMemdevAdd(ctx, qomtype, id, mempath, size, share, "pc-dimm", "dimm"+id) +} + // ExecuteNVDIMMDeviceAdd adds a block device to a QEMU instance using // a NVDIMM driver with the device_add command. // id is the id of the device to add. It must be a valid QMP identifier. @@ -1604,3 +1609,14 @@ func (q *QMP) ExecuteQueryStatus(ctx context.Context) (StatusInfo, error) { return status, nil } + +// ExecQomSet qom-set path property value +func (q *QMP) ExecQomSet(ctx context.Context, path, property string, value uint64) error { + args := map[string]interface{}{ + "path": path, + "property": property, + "value": value, + } + + return q.executeCommand(ctx, "qom-set", args, nil) +} From 6667f4e90b476fdf1f6044a5388c1b9f18787e46 Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Tue, 21 Jan 2020 10:26:59 +0800 Subject: [PATCH 201/264] qmp_test: Add TestExecMemdevAdd and TestExecQomSet Add TestExecMemdevAdd and TestExecQomSet to qmp_test.go. They can test ExecMemdevAdd and ExecQomSet. Signed-off-by: Hui Zhu --- qemu/qmp_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 71f97bcadd..fc1b2a875e 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1294,6 +1294,24 @@ func TestExecSetMigrateArguments(t *testing.T) { <-disconnectedCh } +// Checks add memory device +func TestExecMemdevAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("object-add", nil, "return", nil) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecMemdevAdd(context.Background(), "memory-backend-ram", "mem0", "", 128, true, "virtio-mem-pci", "virtiomem0") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks hotplug memory func TestExecHotplugMemory(t *testing.T) { connectedCh := make(chan *QMPVersion) @@ -1692,3 +1710,20 @@ func TestQMPExecQueryQmpStatus(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks qom-set +func TestExecQomSet(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("qom-set", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + err := q.ExecQomSet(context.Background(), "virtiomem0", "requested-size", 1024) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + q.Shutdown() + <-disconnectedCh +} From f1252f6e1743db4f52933cf35dcd6c71a0ba36aa Mon Sep 17 00:00:00 2001 From: Jimmy Xu Date: Sun, 26 Jan 2020 21:44:11 +0800 Subject: [PATCH 202/264] qemu: Add pcie-root-port device support. --- qemu/qemu.go | 106 ++++++++++++++++++++++++++++++++++++ qemu/qemu_arch_base.go | 1 + qemu/qemu_arch_base_test.go | 82 ++++++++++++++++++++++++++++ qemu/qmp.go | 13 +++-- qemu/qmp_test.go | 21 +++++++ 5 files changed, 218 insertions(+), 5 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f2b80ccd16..c9e0eb1517 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -106,6 +106,9 @@ const ( // VirtioBlockCCW is the CCW block device driver VirtioBlockCCW DeviceDriver = "virtio-blk-ccw" + + // PCIeRootPort is a PCIe Root Port, the PCIe device should be hotplugged to this port. + PCIeRootPort DeviceDriver = "pcie-root-port" ) // disableModern returns the parameters with the disable-modern option. @@ -898,6 +901,102 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { return qemuParams } +// PCIeRootPortDevice represents a memory balloon device. +type PCIeRootPortDevice struct { + ID string // format: rp{n}, n>=0 + + Bus string // default is pcie.0 + Chassis string // (slot, chassis) pair is mandatory and must be unique for each pcie-root-port, >=0, default is 0x00 + Slot string // >=0, default is 0x00 + + Multifunction bool // true => "on", false => "off", default is off + Addr string // >=0, default is 0x00 + + // The PCIE-PCI bridge can be hot-plugged only into pcie-root-port that has 'bus-reserve' property value to + // provide secondary bus for the hot-plugged bridge. + BusReserve string + Pref64Reserve string // reserve prefetched MMIO aperture, 64-bit + Pref32Reserve string // reserve prefetched MMIO aperture, 32-bit + MemReserve string // reserve non-prefetched MMIO aperture, 32-bit *only* + IOReserve string // IO reservation + + ROMFile string // ROMFile specifies the ROM file being used for this device. +} + +// QemuParams returns the qemu parameters built out of the PCIeRootPortDevice. +func (b PCIeRootPortDevice) QemuParams(_ *Config) []string { + var qemuParams []string + var deviceParams []string + driver := PCIeRootPort + + deviceParams = append(deviceParams, fmt.Sprintf("%s,id=%s", driver, b.ID)) + + if b.Bus == "" { + b.Bus = "pcie.0" + } + deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", b.Bus)) + + if b.Chassis == "" { + b.Chassis = "0x00" + } + deviceParams = append(deviceParams, fmt.Sprintf("chassis=%s", b.Chassis)) + + if b.Slot == "" { + b.Slot = "0x00" + } + deviceParams = append(deviceParams, fmt.Sprintf("slot=%s", b.Slot)) + + multifunction := "off" + if b.Multifunction { + multifunction = "on" + if b.Addr == "" { + b.Addr = "0x00" + } + deviceParams = append(deviceParams, fmt.Sprintf("addr=%s", b.Addr)) + } + deviceParams = append(deviceParams, fmt.Sprintf("multifunction=%v", multifunction)) + + if b.BusReserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("bus-reserve=%s", b.BusReserve)) + } + + if b.Pref64Reserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("pref64-reserve=%s", b.Pref64Reserve)) + } + + if b.Pref32Reserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("pref32-reserve=%s", b.Pref32Reserve)) + } + + if b.MemReserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("mem-reserve=%s", b.MemReserve)) + } + + if b.IOReserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("io-reserve=%s", b.IOReserve)) + } + + if isVirtioPCI[driver] && b.ROMFile != "" { + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) + return qemuParams +} + +// Valid returns true if the PCIeRootPortDevice structure is valid and complete. +func (b PCIeRootPortDevice) Valid() bool { + // the "pref32-reserve" and "pref64-reserve" hints are mutually exclusive. + if b.Pref64Reserve != "" && b.Pref32Reserve != "" { + return false + } + if b.ID == "" { + return false + } + return true +} + // VFIODevice represents a qemu vfio device meant for direct access by guest OS. type VFIODevice struct { // Bus-Device-Function of device @@ -914,6 +1013,9 @@ type VFIODevice struct { // DeviceID specifies device id DeviceID string + + // Bus specifies device bus + Bus string } // Valid returns true if the VFIODevice structure is valid and complete. @@ -939,6 +1041,10 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) } + if vfioDev.Bus != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", vfioDev.Bus)) + } + if isVirtioCCW[driver] { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vfioDev.DevNo)) } diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go index d73c34f9a0..3e2e03d003 100644 --- a/qemu/qemu_arch_base.go +++ b/qemu/qemu_arch_base.go @@ -59,6 +59,7 @@ var isVirtioPCI = map[DeviceDriver]bool{ VirtioScsi: true, PCIBridgeDriver: true, PCIePCIBridgeDriver: true, + PCIeRootPort: true, } // isVirtioCCW is a dummy map to return always false on no-s390x arch diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 242eb82000..348f46538f 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -28,6 +28,10 @@ var ( deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" deviceVFIOString = "-device vfio-pci,host=02:10.0,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678,romfile=efi-virtio.rom" + devicePCIeRootPortSimpleString = "-device pcie-root-port,id=rp1,bus=pcie.0,chassis=0x00,slot=0x00,multifunction=off" + devicePCIeRootPortFullString = "-device pcie-root-port,id=rp2,bus=pcie.0,chassis=0x0,slot=0x1,addr=0x2,multifunction=on,bus-reserve=0x3,pref64-reserve=16G,mem-reserve=1G,io-reserve=512M,romfile=efi-virtio.rom" + deviceVFIOPCIeSimpleString = "-device vfio-pci,host=02:00.0,romfile=,bus=rp0" + deviceVFIOPCIeFullString = "-device vfio-pci,host=02:00.0,x-pci-vendor-id=0x10de,x-pci-device-id=0x15f8,romfile=efi-virtio.rom,bus=rp1" deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,disable-modern=false,romfile=efi-virtio.rom" deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" @@ -95,3 +99,81 @@ func TestAppendVirtioBalloon(t *testing.T) { testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OnDisableModern, t) } + +func TestAppendDevicePCIeRootPort(t *testing.T) { + var pcieRootPortID string + + // test empty ID + pcieRootPortDevice := PCIeRootPortDevice{} + if pcieRootPortDevice.Valid() { + t.Fatalf("failed to validdate empty ID") + } + + // test pref64_reserve and pre64_reserve + pcieRootPortID = "rp0" + pcieRootPortDevice = PCIeRootPortDevice{ + ID: pcieRootPortID, + Pref64Reserve: "16G", + Pref32Reserve: "256M", + } + if pcieRootPortDevice.Valid() { + t.Fatalf("failed to validate pref32-reserve and pref64-reserve for %v", pcieRootPortID) + } + + // default test + pcieRootPortID = "rp1" + pcieRootPortDevice = PCIeRootPortDevice{ + ID: pcieRootPortID, + } + if !pcieRootPortDevice.Valid() { + t.Fatalf("failed to validate for %v", pcieRootPortID) + } + testAppend(pcieRootPortDevice, devicePCIeRootPortSimpleString, t) + + // full test + pcieRootPortID = "rp2" + pcieRootPortDevice = PCIeRootPortDevice{ + ID: pcieRootPortID, + Multifunction: true, + Bus: "pcie.0", + Chassis: "0x0", + Slot: "0x1", + Addr: "0x2", + Pref64Reserve: "16G", + IOReserve: "512M", + MemReserve: "1G", + BusReserve: "0x3", + ROMFile: romfile, + } + if !pcieRootPortDevice.Valid() { + t.Fatalf("failed to validate for %v", pcieRootPortID) + } + testAppend(pcieRootPortDevice, devicePCIeRootPortFullString, t) +} + +func TestAppendDeviceVFIOPCIe(t *testing.T) { + // default test + pcieRootPortID := "rp0" + vfioDevice := VFIODevice{ + BDF: "02:00.0", + Bus: pcieRootPortID, + } + if isVirtioCCW[Vfio] { + vfioDevice.DevNo = DevNo + } + testAppend(vfioDevice, deviceVFIOPCIeSimpleString, t) + + // full test + pcieRootPortID = "rp1" + vfioDevice = VFIODevice{ + BDF: "02:00.0", + Bus: pcieRootPortID, + ROMFile: romfile, + VendorID: "0x10de", + DeviceID: "0x15f8", + } + if isVirtioCCW[Vfio] { + vfioDevice.DevNo = DevNo + } + testAppend(vfioDevice, deviceVFIOPCIeFullString, t) +} diff --git a/qemu/qmp.go b/qemu/qmp.go index 9d27dbeefb..f874fb6afe 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1156,17 +1156,20 @@ func (q *QMP) ExecutePCIVhostUserDevAdd(ctx context.Context, driver, devID, char return q.executeCommand(ctx, "device_add", args, nil) } -// ExecuteVFIODeviceAdd adds a VFIO device to a QEMU instance -// using the device_add command. devID is the id of the device to add. -// Must be valid QMP identifier. bdf is the PCI bus-device-function -// of the pci device. -func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, romfile string) error { +// ExecuteVFIODeviceAdd adds a VFIO device to a QEMU instance using the device_add command. +// devID is the id of the device to add. Must be valid QMP identifier. +// bdf is the PCI bus-device-function of the pci device. +// bus is optional. When hot plugging a PCIe device, the bus can be the ID of the pcie-root-port. +func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, bus, romfile string) error { args := map[string]interface{}{ "id": devID, "driver": Vfio, "host": bdf, "romfile": romfile, } + if bus != "" { + args["bus"] = bus + } return q.executeCommand(ctx, "device_add", args, nil) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 71f97bcadd..dcd8e68ab2 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1079,6 +1079,27 @@ func TestQMPPCIVFIOMediatedDeviceAdd(t *testing.T) { <-disconnectedCh } +func TestQMPPCIVFIOPCIeDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + bdf := "04:00.0" + bus := "rp0" + addr := "0x1" + romfile := "" + devID := fmt.Sprintf("device_%s", volumeUUID) + err := q.ExecutePCIVFIODeviceAdd(context.Background(), devID, bdf, addr, bus, romfile) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that CPU are correctly added using device_add func TestQMPCPUDeviceAdd(t *testing.T) { drivers := []string{"host-x86_64-cpu", "host-s390x-cpu", "host-powerpc64-cpu"} From 2ee53b00ca98a1c4b13ca7b3f1a29426bf14c3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Wed, 5 Feb 2020 21:09:47 +0100 Subject: [PATCH 203/264] qemu: Don't set ".cache-size=" when CacheSize is 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As there's no guarantee that ".cache-size" is a supported QEMU property, let's not add it to the QEMU command line when the user explicitly set virtio_fs_cache_size to zero. By not always setting ".cache-size" property we avoid errors like: ``` $ sudo podman --runtime=/usr/bin/kata-runtime run --security-opt label=disable -it fedora:31 /bin/bash Error: failed to launch qemu: exit status 1, error messages from qemu log: qemu-kvm: -device vhost-user-fs-pci,chardev=char-88c350403e95d3db,tag=kataShared,cache-size=0M: Property '.cache-size' not found: OCI runtime error ``` Signed-off-by: Fabiano Fidêncio --- qemu/qemu.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index c9e0eb1517..4346d6eb8f 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -875,7 +875,9 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { devParams = append(devParams, string(driver)) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) - devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) + if vhostuserDev.CacheSize != 0 { + devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) + } if vhostuserDev.SharedVersions { devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") } From 88a25a2d68117ca344c6924263a4c6fb0da24a24 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 7 Feb 2020 17:54:25 +0100 Subject: [PATCH 204/264] Refactor code to support multiple virtio transports at runtime Currently, virtio transports for each device are determined with architecture dependent build time conditionals. This isn't the ideal solution, as virtio transports aren't exactly tied to the host's architecture. For example, aarch64 VMs do support both PCI and MMIO devices, and after the recent introduction of the microvm machine type, that's also the case for x86_64. This patch extends each device that supports multiple transports with a VirtioTransport field, so users of the library can manually specify a transport for each device. To avoid breaking the compatibility, if VirtioTransport is empty a behavior equivalent to the legacy one is achieved by checking runtime.GOARCH and Config.Machine.Type. Keeping support for isVirtioPCI/isVirtioCCW in qmp.go is a bit tricky. Eventually, the hot-plug API should be extended so callers must manually specify the transport for the device. Signed-off-by: Sergio Lopez --- qemu/qemu.go | 592 +++++++++++++++++++++++++++++++----- qemu/qemu_arch_base.go | 112 ------- qemu/qemu_arch_base_test.go | 10 +- qemu/qemu_s390x.go | 140 --------- qemu/qemu_s390x_test.go | 4 +- qemu/qemu_test.go | 45 +-- qemu/qmp.go | 46 ++- 7 files changed, 566 insertions(+), 383 deletions(-) delete mode 100644 qemu/qemu_arch_base.go delete mode 100644 qemu/qemu_s390x.go diff --git a/qemu/qemu.go b/qemu/qemu.go index c9e0eb1517..da2c95643f 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -27,8 +27,10 @@ package qemu import ( "bytes" "fmt" + "log" "os" "os/exec" + "runtime" "strconv" "strings" "syscall" @@ -62,6 +64,9 @@ const ( // NVDIMM is the Non Volatile DIMM device driver. NVDIMM DeviceDriver = "nvdimm" + // VirtioNet is the virtio networking device driver. + VirtioNet DeviceDriver = "virtio-net" + // VirtioNetPCI is the virt-io pci networking device driver. VirtioNetPCI DeviceDriver = "virtio-net-pci" @@ -71,12 +76,15 @@ const ( // VirtioBlock is the block device driver. VirtioBlock DeviceDriver = "virtio-blk" - // VirtioBlockPCI is a pci bus block device driver - VirtioBlockPCI DeviceDriver = "virtio-blk-pci" - // Console is the console device driver. Console DeviceDriver = "virtconsole" + // Virtio9P is the 9pfs device driver. + Virtio9P DeviceDriver = "virtio-9p" + + // VirtioSerial is the serial device driver. + VirtioSerial DeviceDriver = "virtio-serial" + // VirtioSerialPort is the serial port device driver. VirtioSerialPort DeviceDriver = "virtserialport" @@ -87,16 +95,16 @@ const ( VirtioBalloon DeviceDriver = "virtio-balloon" //VhostUserSCSI represents a SCSI vhostuser device type. - VhostUserSCSI DeviceDriver = "vhost-user-scsi-pci" + VhostUserSCSI DeviceDriver = "vhost-user-scsi" //VhostUserNet represents a net vhostuser device type. - VhostUserNet DeviceDriver = "virtio-net-pci" + VhostUserNet DeviceDriver = "virtio-net" //VhostUserBlk represents a block vhostuser device type. - VhostUserBlk DeviceDriver = "vhost-user-blk-pci" + VhostUserBlk DeviceDriver = "vhost-user-blk" //VhostUserFS represents a virtio-fs vhostuser device type - VhostUserFS DeviceDriver = "vhost-user-fs-pci" + VhostUserFS DeviceDriver = "vhost-user-fs" // PCIBridgeDriver represents a PCI bridge device type. PCIBridgeDriver DeviceDriver = "pci-bridge" @@ -104,18 +112,87 @@ const ( // PCIePCIBridgeDriver represents a PCIe to PCI bridge device type. PCIePCIBridgeDriver DeviceDriver = "pcie-pci-bridge" - // VirtioBlockCCW is the CCW block device driver - VirtioBlockCCW DeviceDriver = "virtio-blk-ccw" + // VfioPCI is the vfio driver with PCI transport. + VfioPCI DeviceDriver = "vfio-pci" + + // VfioCCW is the vfio driver with CCW transport. + VfioCCW DeviceDriver = "vfio-ccw" + + // VHostVSockPCI is a generic Vsock vhost device with PCI transport. + VHostVSockPCI DeviceDriver = "vhost-vsock-pci" // PCIeRootPort is a PCIe Root Port, the PCIe device should be hotplugged to this port. PCIeRootPort DeviceDriver = "pcie-root-port" ) +func isDimmSupported(config *Config) bool { + switch runtime.GOARCH { + case "amd64", "386": + return true + default: + return false + } +} + +// VirtioTransport is the transport in use for a virtio device. +type VirtioTransport string + +const ( + // TransportPCI is the PCI transport for virtio device. + TransportPCI VirtioTransport = "pci" + + // TransportCCW is the CCW transport for virtio devices. + TransportCCW VirtioTransport = "ccw" + + // TransportMMIO is the MMIO transport for virtio devices. + TransportMMIO VirtioTransport = "mmio" +) + +// defaultTransport returns the default transport for the current combination +// of host's architecture and QEMU machine type. +func (transport VirtioTransport) defaultTransport(config *Config) VirtioTransport { + switch runtime.GOARCH { + case "amd64", "386": + return TransportPCI + case "s390x": + return TransportCCW + default: + return TransportPCI + } +} + +// isVirtioPCI returns true if the transport is PCI. +func (transport VirtioTransport) isVirtioPCI(config *Config) bool { + if transport == "" { + transport = transport.defaultTransport(config) + } + + return transport == TransportPCI +} + +// isVirtioCCW returns true if the transport is CCW. +func (transport VirtioTransport) isVirtioCCW(config *Config) bool { + if transport == "" { + transport = transport.defaultTransport(config) + } + + return transport == TransportCCW +} + +// getName returns the name of the current transport. +func (transport VirtioTransport) getName(config *Config) string { + if transport == "" { + transport = transport.defaultTransport(config) + } + + return string(transport) +} + // disableModern returns the parameters with the disable-modern option. // In case the device driver is not a PCI device and it doesn't have the option // an empty string is returned. -func (driver DeviceDriver) disableModern(disable bool) string { - if !isVirtioPCI[driver] { +func (transport VirtioTransport) disableModern(config *Config, disable bool) string { + if !transport.isVirtioPCI(config) { return "" } @@ -258,6 +335,17 @@ type FSDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// Virtio9PTransport is a map of the virtio-9p device name that corresponds +// to each transport. +var Virtio9PTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-9p-pci", + TransportCCW: "virtio-9p-ccw", + TransportMMIO: "virtio-9p-device", } // Valid returns true if the FSDevice structure is valid and complete. @@ -275,16 +363,16 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, string(fsdev.Driver)) - if s := fsdev.Driver.disableModern(fsdev.DisableModern); s != "" { + deviceParams = append(deviceParams, fsdev.deviceName(config)) + if s := fsdev.Transport.disableModern(config, fsdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) - if isVirtioPCI[fsdev.Driver] { + if fsdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) } - if isVirtioCCW[fsdev.Driver] { + if fsdev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", fsdev.DevNo)) } @@ -302,6 +390,21 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU shared filesystem device name for the current +// combination of driver and transport. +func (fsdev FSDevice) deviceName(config *Config) string { + if fsdev.Transport == "" { + fsdev.Transport = fsdev.Transport.defaultTransport(config) + } + + switch fsdev.Driver { + case Virtio9P: + return Virtio9PTransport[fsdev.Transport] + } + + return string(fsdev.Driver) +} + // CharDeviceBackend is the character device backend for qemu type CharDeviceBackend string @@ -350,6 +453,17 @@ type CharDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// VirtioSerialTransport is a map of the virtio-serial device name that +// corresponds to each transport. +var VirtioSerialTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-serial-pci", + TransportCCW: "virtio-serial-ccw", + TransportMMIO: "virtio-serial-device", } // Valid returns true if the CharDevice structure is valid and complete. @@ -367,9 +481,11 @@ func (cdev CharDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, string(cdev.Driver)) - if s := cdev.Driver.disableModern(cdev.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, cdev.deviceName(config)) + if cdev.Driver == VirtioSerial { + if s := cdev.Transport.disableModern(config, cdev.DisableModern); s != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + } } if cdev.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", cdev.Bus)) @@ -379,11 +495,11 @@ func (cdev CharDevice) QemuParams(config *Config) []string { if cdev.Name != "" { deviceParams = append(deviceParams, fmt.Sprintf(",name=%s", cdev.Name)) } - if isVirtioPCI[cdev.Driver] { + if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", cdev.ROMFile)) } - if isVirtioCCW[cdev.Driver] { + if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", cdev.DevNo)) } @@ -404,6 +520,21 @@ func (cdev CharDevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (cdev CharDevice) deviceName(config *Config) string { + if cdev.Transport == "" { + cdev.Transport = cdev.Transport.defaultTransport(config) + } + + switch cdev.Driver { + case VirtioSerial: + return VirtioSerialTransport[cdev.Transport] + } + + return string(cdev.Driver) +} + // NetDeviceType is a qemu networking device type. type NetDeviceType string @@ -427,6 +558,80 @@ const ( VHOSTUSER NetDeviceType = "vhostuser" ) +// QemuNetdevParam converts to the QEMU -netdev parameter notation +func (n NetDeviceType) QemuNetdevParam(netdev *NetDevice, config *Config) string { + if netdev.Transport == "" { + netdev.Transport = netdev.Transport.defaultTransport(config) + } + + switch n { + case TAP: + return "tap" + case MACVTAP: + return "tap" + case IPVTAP: + return "tap" + case VETHTAP: + return "tap" // -netdev type=tap -device virtio-net-pci + case VFIO: + if netdev.Transport == TransportMMIO { + log.Fatal("vfio devices are not support with the MMIO transport") + } + return "" // -device vfio-pci (no netdev) + case VHOSTUSER: + if netdev.Transport == TransportCCW { + log.Fatal("vhost-user devices are not supported on IBM Z") + } + return "vhost-user" // -netdev type=vhost-user (no device) + default: + return "" + + } +} + +// QemuDeviceParam converts to the QEMU -device parameter notation +func (n NetDeviceType) QemuDeviceParam(netdev *NetDevice, config *Config) DeviceDriver { + if netdev.Transport == "" { + netdev.Transport = netdev.Transport.defaultTransport(config) + } + + var device string + + switch n { + case TAP: + device = "virtio-net" + case MACVTAP: + device = "virtio-net" + case IPVTAP: + device = "virtio-net" + case VETHTAP: + device = "virtio-net" // -netdev type=tap -device virtio-net-pci + case VFIO: + if netdev.Transport == TransportMMIO { + log.Fatal("vfio devices are not support with the MMIO transport") + } + device = "vfio" // -device vfio-pci (no netdev) + case VHOSTUSER: + if netdev.Transport == TransportCCW { + log.Fatal("vhost-user devices are not supported on IBM Z") + } + return "" // -netdev type=vhost-user (no device) + default: + return "" + } + + switch netdev.Transport { + case TransportPCI: + return DeviceDriver(device + "-pci") + case TransportCCW: + return DeviceDriver(device + "-ccw") + case TransportMMIO: + return DeviceDriver(device + "-device") + default: + return "" + } +} + // NetDevice represents a guest networking device type NetDevice struct { // Type is the netdev type (e.g. tap). @@ -472,6 +677,17 @@ type NetDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// VirtioNetTransport is a map of the virtio-net device name that corresponds +// to each transport. +var VirtioNetTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-net-pci", + TransportCCW: "virtio-net-ccw", + TransportMMIO: "virtio-net-device", } // Valid returns true if the NetDevice structure is valid and complete. @@ -494,10 +710,10 @@ func (netdev NetDevice) Valid() bool { // vector flag is required. If the driver is a CCW type than the vector flag is not implemented and only // multi-queue option mq needs to be activated. See comment in libvirt code at // https://github.com/libvirt/libvirt/blob/6e7e965dcd3d885739129b1454ce19e819b54c25/src/qemu/qemu_command.c#L3633 -func (netdev NetDevice) mqParameter() string { +func (netdev NetDevice) mqParameter(config *Config) string { p := []string{",mq=on"} - if isVirtioPCI[netdev.Driver] { + if netdev.Transport.isVirtioPCI(config) { // https://www.linux-kvm.org/page/Multiqueue // -netdev tap,vhost=on,queues=N // enable mq and specify msix vectors in qemu cmdline @@ -518,11 +734,12 @@ func (netdev NetDevice) mqParameter() string { func (netdev NetDevice) QemuDeviceParams(config *Config) []string { var deviceParams []string - if netdev.Type.QemuDeviceParam() == "" { + driver := netdev.Type.QemuDeviceParam(&netdev, config) + if driver == "" { return nil } - deviceParams = append(deviceParams, fmt.Sprintf("driver=%s", netdev.Type.QemuDeviceParam())) + deviceParams = append(deviceParams, fmt.Sprintf("driver=%s", driver)) deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) @@ -536,20 +753,20 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) } } - if s := netdev.Driver.disableModern(netdev.DisableModern); s != "" { + if s := netdev.Transport.disableModern(config, netdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if len(netdev.FDs) > 0 { // Note: We are appending to the device params here - deviceParams = append(deviceParams, netdev.mqParameter()) + deviceParams = append(deviceParams, netdev.mqParameter(config)) } - if isVirtioPCI[netdev.Driver] { + if netdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", netdev.ROMFile)) } - if isVirtioCCW[netdev.Driver] { + if netdev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", netdev.DevNo)) } @@ -560,11 +777,12 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { func (netdev NetDevice) QemuNetdevParams(config *Config) []string { var netdevParams []string - if netdev.Type.QemuNetdevParam() == "" { + netdevType := netdev.Type.QemuNetdevParam(&netdev, config) + if netdevType == "" { return nil } - netdevParams = append(netdevParams, netdev.Type.QemuNetdevParam()) + netdevParams = append(netdevParams, netdevType) netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID)) if netdev.VHost { @@ -612,7 +830,7 @@ func (netdev NetDevice) QemuParams(config *Config) []string { return nil // implicit error } - if netdev.Type.QemuNetdevParam() != "" { + if netdev.Type.QemuNetdevParam(&netdev, config) != "" { netdevParams = netdev.QemuNetdevParams(config) if netdevParams != nil { qemuParams = append(qemuParams, "-netdev") @@ -620,7 +838,7 @@ func (netdev NetDevice) QemuParams(config *Config) []string { } } - if netdev.Type.QemuDeviceParam() != "" { + if netdev.Type.QemuDeviceParam(&netdev, config) != "" { deviceParams = netdev.QemuDeviceParams(config) if deviceParams != nil { qemuParams = append(qemuParams, "-device") @@ -647,6 +865,9 @@ type SerialDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport } // Valid returns true if the SerialDevice structure is valid and complete. @@ -663,16 +884,16 @@ func (dev SerialDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, string(dev.Driver)) - if s := dev.Driver.disableModern(dev.DisableModern); s != "" { + deviceParams = append(deviceParams, dev.deviceName(config)) + if s := dev.Transport.disableModern(config, dev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) - if isVirtioPCI[dev.Driver] { + if dev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) } - if isVirtioCCW[dev.Driver] { + if dev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", dev.DevNo)) } @@ -682,6 +903,21 @@ func (dev SerialDevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (dev SerialDevice) deviceName(config *Config) string { + if dev.Transport == "" { + dev.Transport = dev.Transport.defaultTransport(config) + } + + switch dev.Driver { + case VirtioSerial: + return VirtioSerialTransport[dev.Transport] + } + + return string(dev.Driver) +} + // BlockDeviceInterface defines the type of interface the device is connected to. type BlockDeviceInterface string @@ -734,6 +970,17 @@ type BlockDevice struct { // ShareRW enables multiple qemu instances to share the File ShareRW bool + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// VirtioBlockTransport is a map of the virtio-blk device name that corresponds +// to each transport. +var VirtioBlockTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-blk-pci", + TransportCCW: "virtio-blk-ccw", + TransportMMIO: "virtio-blk-device", } // Valid returns true if the BlockDevice structure is valid and complete. @@ -751,8 +998,8 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - deviceParams = append(deviceParams, string(blkdev.Driver)) - if s := blkdev.Driver.disableModern(blkdev.DisableModern); s != "" { + deviceParams = append(deviceParams, blkdev.deviceName(config)) + if s := blkdev.Transport.disableModern(config, blkdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) @@ -764,11 +1011,11 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, ",config-wce=off") } - if isVirtioPCI[blkdev.Driver] { + if blkdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", blkdev.ROMFile)) } - if isVirtioCCW[blkdev.Driver] { + if blkdev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", blkdev.DevNo)) } @@ -791,6 +1038,21 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (blkdev BlockDevice) deviceName(config *Config) string { + if blkdev.Transport == "" { + blkdev.Transport = blkdev.Transport.defaultTransport(config) + } + + switch blkdev.Driver { + case VirtioBlock: + return VirtioBlockTransport[blkdev.Transport] + } + + return string(blkdev.Driver) +} + // VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { @@ -805,6 +1067,41 @@ type VhostUserDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// VhostUserNetTransport is a map of the virtio-net device name that +// corresponds to each transport. +var VhostUserNetTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-net-pci", + TransportCCW: "virtio-net-ccw", + TransportMMIO: "virtio-net-device", +} + +// VhostUserSCSITransport is a map of the vhost-user-scsi device name that +// corresponds to each transport. +var VhostUserSCSITransport = map[VirtioTransport]string{ + TransportPCI: "vhost-user-scsi-pci", + TransportCCW: "vhost-user-scsi-ccw", + TransportMMIO: "vhost-user-scsi-device", +} + +// VhostUserBlkTransport is a map of the vhost-user-blk device name that +// corresponds to each transport. +var VhostUserBlkTransport = map[VirtioTransport]string{ + TransportPCI: "vhost-user-blk-pci", + TransportCCW: "vhost-user-blk-ccw", + TransportMMIO: "vhost-user-blk-device", +} + +// VhostUserFSTransport is a map of the vhost-user-fs device name that +// corresponds to each transport. +var VhostUserFSTransport = map[VirtioTransport]string{ + TransportPCI: "vhost-user-fs-pci", + TransportCCW: "vhost-user-fs-ccw", + TransportMMIO: "vhost-user-fs-device", } // Valid returns true if there is a valid structure defined for VhostUserDevice @@ -841,7 +1138,7 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { var charParams []string var netParams []string var devParams []string - var driver DeviceDriver + var driver string charParams = append(charParams, "socket") charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) @@ -850,29 +1147,45 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { switch vhostuserDev.VhostUserType { // if network based vhost device: case VhostUserNet: - driver = VhostUserNet + driver = vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + netParams = append(netParams, "type=vhost-user") netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) netParams = append(netParams, "vhostforce") - devParams = append(devParams, string(driver)) + devParams = append(devParams, driver) devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) case VhostUserSCSI: - driver = VhostUserSCSI - devParams = append(devParams, string(driver)) + driver = vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + + devParams = append(devParams, driver) devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) case VhostUserBlk: - driver = VhostUserBlk - devParams = append(devParams, string(driver)) + driver = vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + + devParams = append(devParams, driver) devParams = append(devParams, "logical_block_size=4096") devParams = append(devParams, "size=512M") devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) case VhostUserFS: - driver = VhostUserFS - devParams = append(devParams, string(driver)) + driver = vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + + devParams = append(devParams, driver) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) @@ -883,7 +1196,7 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { return nil } - if isVirtioPCI[driver] { + if vhostuserDev.Transport.isVirtioPCI(config) { devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } @@ -901,6 +1214,27 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (vhostuserDev VhostUserDevice) deviceName(config *Config) string { + if vhostuserDev.Transport == "" { + vhostuserDev.Transport = vhostuserDev.Transport.defaultTransport(config) + } + + switch vhostuserDev.VhostUserType { + case VhostUserNet: + return VhostUserNetTransport[vhostuserDev.Transport] + case VhostUserSCSI: + return VhostUserSCSITransport[vhostuserDev.Transport] + case VhostUserBlk: + return VhostUserBlkTransport[vhostuserDev.Transport] + case VhostUserFS: + return VhostUserFSTransport[vhostuserDev.Transport] + default: + return "" + } +} + // PCIeRootPortDevice represents a memory balloon device. type PCIeRootPortDevice struct { ID string // format: rp{n}, n>=0 @@ -921,10 +1255,13 @@ type PCIeRootPortDevice struct { IOReserve string // IO reservation ROMFile string // ROMFile specifies the ROM file being used for this device. + + // Transport is the virtio transport for this device. + Transport VirtioTransport } // QemuParams returns the qemu parameters built out of the PCIeRootPortDevice. -func (b PCIeRootPortDevice) QemuParams(_ *Config) []string { +func (b PCIeRootPortDevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := PCIeRootPort @@ -976,7 +1313,7 @@ func (b PCIeRootPortDevice) QemuParams(_ *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf("io-reserve=%s", b.IOReserve)) } - if isVirtioPCI[driver] && b.ROMFile != "" { + if b.Transport.isVirtioPCI(config) && b.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } @@ -1016,6 +1353,17 @@ type VFIODevice struct { // Bus specifies device bus Bus string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// VFIODeviceTransport is a map of the vfio device name that corresponds to +// each transport. +var VFIODeviceTransport = map[VirtioTransport]string{ + TransportPCI: "vfio-pci", + TransportCCW: "vfio-ccw", + TransportMMIO: "vfio-device", } // Valid returns true if the VFIODevice structure is valid and complete. @@ -1028,10 +1376,10 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string - driver := Vfio + driver := vfioDev.deviceName(config) deviceParams = append(deviceParams, fmt.Sprintf("%s,host=%s", driver, vfioDev.BDF)) - if isVirtioPCI[driver] { + if vfioDev.Transport.isVirtioPCI(config) { if vfioDev.VendorID != "" { deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-vendor-id=%s", vfioDev.VendorID)) } @@ -1045,7 +1393,7 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", vfioDev.Bus)) } - if isVirtioCCW[driver] { + if vfioDev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vfioDev.DevNo)) } @@ -1055,6 +1403,16 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (vfioDev VFIODevice) deviceName(config *Config) string { + if vfioDev.Transport == "" { + vfioDev.Transport = vfioDev.Transport.defaultTransport(config) + } + + return VFIODeviceTransport[vfioDev.Transport] +} + // SCSIController represents a SCSI controller device. type SCSIController struct { ID string @@ -1076,6 +1434,17 @@ type SCSIController struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// SCSIControllerTransport is a map of the virtio-scsi device name that +// corresponds to each transport. +var SCSIControllerTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-scsi-pci", + TransportCCW: "virtio-scsi-ccw", + TransportMMIO: "virtio-scsi-device", } // Valid returns true if the SCSIController structure is valid and complete. @@ -1088,7 +1457,7 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { var qemuParams []string var devParams []string - driver := VirtioScsi + driver := scsiCon.deviceName(config) devParams = append(devParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) if scsiCon.Bus != "" { devParams = append(devParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) @@ -1096,17 +1465,17 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { if scsiCon.Addr != "" { devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } - if s := driver.disableModern(scsiCon.DisableModern); s != "" { + if s := scsiCon.Transport.disableModern(config, scsiCon.DisableModern); s != "" { devParams = append(devParams, s) } if scsiCon.IOThread != "" { devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) } - if isVirtioPCI[driver] { + if scsiCon.Transport.isVirtioPCI(config) { devParams = append(devParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) } - if isVirtioCCW[driver] { + if scsiCon.Transport.isVirtioCCW(config) { devParams = append(devParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) } @@ -1116,6 +1485,16 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (scsiCon SCSIController) deviceName(config *Config) string { + if scsiCon.Transport == "" { + scsiCon.Transport = scsiCon.Transport.defaultTransport(config) + } + + return SCSIControllerTransport[scsiCon.Transport] +} + // BridgeType is the type of the bridge type BridgeType uint @@ -1194,7 +1573,8 @@ func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { } } - if isVirtioPCI[driver] { + var transport VirtioTransport + if transport.isVirtioPCI(config) { deviceParam = append(deviceParam, fmt.Sprintf(",romfile=%s", bridgeDev.ROMFile)) } @@ -1221,6 +1601,17 @@ type VSOCKDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// VSOCKDeviceTransport is a map of the vhost-vsock device name that +// corresponds to each transport. +var VSOCKDeviceTransport = map[VirtioTransport]string{ + TransportPCI: "vhost-vsock-pci", + TransportCCW: "vhost-vsock-ccw", + TransportMMIO: "vhost-vsock-device", } const ( @@ -1250,9 +1641,9 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string - driver := VHostVSock + driver := vsock.deviceName(config) deviceParams = append(deviceParams, string(driver)) - if s := driver.disableModern(vsock.DisableModern); s != "" { + if s := vsock.Transport.disableModern(config, vsock.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if vsock.VHostFD != nil { @@ -1262,11 +1653,11 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID)) - if isVirtioPCI[driver] { + if vsock.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vsock.ROMFile)) } - if isVirtioCCW[driver] { + if vsock.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vsock.DevNo)) } @@ -1276,6 +1667,16 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (vsock VSOCKDevice) deviceName(config *Config) string { + if vsock.Transport == "" { + vsock.Transport = vsock.Transport.defaultTransport(config) + } + + return VSOCKDeviceTransport[vsock.Transport] +} + // RngDevice represents a random number generator device. type RngDevice struct { // ID is the device ID @@ -1290,6 +1691,16 @@ type RngDevice struct { ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// RngDeviceTransport is a map of the virtio-rng device name that corresponds +// to each transport. +var RngDeviceTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-rng-pci", + TransportCCW: "virtio-rng-ccw", + TransportMMIO: "virtio-rng-device", } // Valid returns true if the RngDevice structure is valid and complete. @@ -1298,7 +1709,7 @@ func (v RngDevice) Valid() bool { } // QemuParams returns the qemu parameters built out of the RngDevice. -func (v RngDevice) QemuParams(_ *Config) []string { +func (v RngDevice) QemuParams(config *Config) []string { var qemuParams []string //-object rng-random,filename=/dev/hwrng,id=rng0 @@ -1306,18 +1717,17 @@ func (v RngDevice) QemuParams(_ *Config) []string { //-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 var deviceParams []string - driver := VirtioRng objectParams = append(objectParams, "rng-random") objectParams = append(objectParams, "id="+v.ID) - deviceParams = append(deviceParams, string(driver)) + deviceParams = append(deviceParams, v.deviceName(config)) deviceParams = append(deviceParams, "rng="+v.ID) - if isVirtioPCI[driver] { + if v.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", v.ROMFile)) } - if isVirtioCCW[driver] { + if v.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", v.DevNo)) } @@ -1342,6 +1752,16 @@ func (v RngDevice) QemuParams(_ *Config) []string { return qemuParams } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (v RngDevice) deviceName(config *Config) string { + if v.Transport == "" { + v.Transport = v.Transport.defaultTransport(config) + } + + return RngDeviceTransport[v.Transport] +} + // BalloonDevice represents a memory balloon device. type BalloonDevice struct { DeflateOnOOM bool @@ -1353,25 +1773,35 @@ type BalloonDevice struct { // DevNo identifies the ccw devices for s390x architecture DevNo string + + // Transport is the virtio transport for this device. + Transport VirtioTransport +} + +// BalloonDeviceTransport is a map of the virtio-balloon device name that +// corresponds to each transport. +var BalloonDeviceTransport = map[VirtioTransport]string{ + TransportPCI: "virtio-balloon-pci", + TransportCCW: "virtio-balloon-ccw", + TransportMMIO: "virtio-balloon-device", } // QemuParams returns the qemu parameters built out of the BalloonDevice. -func (b BalloonDevice) QemuParams(_ *Config) []string { +func (b BalloonDevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string - driver := VirtioBalloon - deviceParams = append(deviceParams, string(driver)) + deviceParams = append(deviceParams, b.deviceName(config)) if b.ID != "" { deviceParams = append(deviceParams, "id="+b.ID) } - if isVirtioPCI[driver] { + if b.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } - if isVirtioCCW[driver] { + if b.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", b.DevNo)) } @@ -1380,7 +1810,7 @@ func (b BalloonDevice) QemuParams(_ *Config) []string { } else { deviceParams = append(deviceParams, "deflate-on-oom=off") } - if s := driver.disableModern(b.DisableModern); s != "" { + if s := b.Transport.disableModern(config, b.DisableModern); s != "" { deviceParams = append(deviceParams, string(s)) } qemuParams = append(qemuParams, "-device") @@ -1394,6 +1824,16 @@ func (b BalloonDevice) Valid() bool { return b.ID != "" } +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (b BalloonDevice) deviceName(config *Config) string { + if b.Transport == "" { + b.Transport = b.Transport.defaultTransport(config) + } + + return BalloonDeviceTransport[b.Transport] +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string @@ -1874,7 +2314,7 @@ func (config *Config) appendMemoryKnobs() { if config.Memory.Size == "" { return } - if !isDimmSupported() { + if !isDimmSupported(config) { return } var objMemParam, numaMemParam string diff --git a/qemu/qemu_arch_base.go b/qemu/qemu_arch_base.go deleted file mode 100644 index 3e2e03d003..0000000000 --- a/qemu/qemu_arch_base.go +++ /dev/null @@ -1,112 +0,0 @@ -// +build !s390x - -/* -// Copyright contributors to the Virtual Machine Manager for Go project -// -// 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 - -const ( - // Virtio9P is the 9pfs device driver. - Virtio9P DeviceDriver = "virtio-9p-pci" - - // VirtioSerial is the serial device driver. - VirtioSerial DeviceDriver = "virtio-serial-pci" - - // VirtioNet is the virt-io pci networking device driver. - VirtioNet DeviceDriver = VirtioNetPCI - - // Vfio is the vfio driver - Vfio DeviceDriver = "vfio-pci" - - // VirtioScsi is the virtio-scsi device - VirtioScsi DeviceDriver = "virtio-scsi-pci" - - // VHostVSock is a generic Vsock vhost device - VHostVSock DeviceDriver = "vhost-vsock-pci" -) - -// isVirtioPCI is a map indicating if a DeviceDriver is considered as a -// virtio PCI device, which is helpful to determine if the option "romfile" -// applies or not to this specific device. -var isVirtioPCI = map[DeviceDriver]bool{ - NVDIMM: false, - Virtio9P: true, - VirtioNetPCI: true, - VirtioSerial: true, - VirtioBlock: true, - VirtioBlockPCI: true, - Console: false, - VirtioSerialPort: false, - VHostVSock: true, - VirtioRng: true, - VirtioBalloon: true, - VhostUserSCSI: true, - VhostUserBlk: true, - Vfio: true, - VirtioScsi: true, - PCIBridgeDriver: true, - PCIePCIBridgeDriver: true, - PCIeRootPort: true, -} - -// isVirtioCCW is a dummy map to return always false on no-s390x arch -var isVirtioCCW = map[DeviceDriver]bool{} - -// QemuNetdevParam converts to the QEMU -netdev parameter notation -func (n NetDeviceType) QemuNetdevParam() string { - switch n { - case TAP: - return "tap" - case MACVTAP: - return "tap" - case IPVTAP: - return "tap" - case VETHTAP: - return "tap" // -netdev type=tap -device virtio-net-pci - case VFIO: - return "" // -device vfio-pci (no netdev) - case VHOSTUSER: - return "vhost-user" // -netdev type=vhost-user (no device) - default: - return "" - - } -} - -// QemuDeviceParam converts to the QEMU -device parameter notation -func (n NetDeviceType) QemuDeviceParam() DeviceDriver { - switch n { - case TAP: - return "virtio-net-pci" - case MACVTAP: - return "virtio-net-pci" - case IPVTAP: - return "virtio-net-pci" - case VETHTAP: - return "virtio-net-pci" // -netdev type=tap -device virtio-net-pci - case VFIO: - return "vfio-pci" // -device vfio-pci (no netdev) - case VHOSTUSER: - return "" // -netdev type=vhost-user (no device) - default: - return "" - - } -} - -func isDimmSupported() bool { - return true -} diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 348f46538f..d1eb5d24bf 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -36,7 +36,7 @@ var ( deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" - deviceBlockString = "-device virtio-blk,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" romfile = "efi-virtio.rom" @@ -81,7 +81,7 @@ func TestAppendVirtioBalloon(t *testing.T) { ROMFile: romfile, } - var deviceString = "-device " + string(VirtioBalloon) + var deviceString = "-device " + string(VirtioBalloon) + "-" + string(TransportPCI) deviceString += ",id=" + balloonDevice.ID + ",romfile=" + balloonDevice.ROMFile var OnDeflateOnOMM = ",deflate-on-oom=on" @@ -158,9 +158,6 @@ func TestAppendDeviceVFIOPCIe(t *testing.T) { BDF: "02:00.0", Bus: pcieRootPortID, } - if isVirtioCCW[Vfio] { - vfioDevice.DevNo = DevNo - } testAppend(vfioDevice, deviceVFIOPCIeSimpleString, t) // full test @@ -172,8 +169,5 @@ func TestAppendDeviceVFIOPCIe(t *testing.T) { VendorID: "0x10de", DeviceID: "0x15f8", } - if isVirtioCCW[Vfio] { - vfioDevice.DevNo = DevNo - } testAppend(vfioDevice, deviceVFIOPCIeFullString, t) } diff --git a/qemu/qemu_s390x.go b/qemu/qemu_s390x.go deleted file mode 100644 index 6e54f9b719..0000000000 --- a/qemu/qemu_s390x.go +++ /dev/null @@ -1,140 +0,0 @@ -// +build s390x - -/* -// Copyright contributors to the Virtual Machine Manager for Go project -// -// 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 "log" - -// IBM Z uses CCW devices intead of PCI devices. -// See https://wiki.qemu.org/Documentation/Platforms/S390X -const ( - // Virtio9P is the 9pfs device driver. - Virtio9P DeviceDriver = "virtio-9p-ccw" - - // VirtioSerial is the serial device driver. - VirtioSerial DeviceDriver = "virtio-serial-ccw" - - // VirtioNet is the virt-io ccw networking device driver. - VirtioNet DeviceDriver = VirtioNetCCW - - // Vfio is the vfio driver - Vfio DeviceDriver = "vfio-ccw" - - // VirtioScsi is the virtio-scsi device - VirtioScsi DeviceDriver = "virtio-scsi-ccw" - - // VHostVSock is a generic Vsock Device - VHostVSock DeviceDriver = "vhost-vsock-ccw" -) - -// isVirtioPCI is a fake map on s390x to always avoid the "romfile" -// option -var isVirtioPCI = map[DeviceDriver]bool{ - NVDIMM: false, - Virtio9P: false, - VirtioNetCCW: false, - VirtioSerial: false, - VirtioBlock: false, - Console: false, - VirtioSerialPort: false, - VHostVSock: false, - VirtioRng: false, - VirtioBalloon: false, - VhostUserSCSI: false, - VhostUserBlk: false, - Vfio: false, - VirtioScsi: false, - PCIBridgeDriver: false, - PCIePCIBridgeDriver: false, -} - -// isVirtioCCW returns if the device is a ccw device -var isVirtioCCW = map[DeviceDriver]bool{ - NVDIMM: false, - Virtio9P: true, - VirtioNetCCW: true, - VirtioSerial: true, - VirtioBlock: true, - VirtioBlockCCW: true, - Console: false, - VirtioSerialPort: false, - VHostVSock: true, - VirtioRng: true, - VirtioBalloon: true, - VhostUserSCSI: false, - VhostUserBlk: false, - Vfio: true, - VirtioScsi: true, - PCIBridgeDriver: false, - PCIePCIBridgeDriver: false, -} - -// QemuDeviceParam converts to the QEMU -device parameter notation -// This function has been reimplemented for the s390x architecture to deal -// with the VHOSTUSER case. Vhost user devices are not implemented on s390x -// architecture. For further details see issue -// https://github.com/kata-containers/runtime/issues/659 -func (n NetDeviceType) QemuDeviceParam() string { - switch n { - case TAP: - return string(VirtioNet) - case MACVTAP: - return string(VirtioNet) - case IPVTAP: - return string(VirtioNet) - case VETHTAP: - return string(VirtioNet) - case VFIO: - return string(Vfio) - case VHOSTUSER: - log.Fatal("vhost-user devices are not supported on IBM Z") - return "" - default: - return "" - } -} - -// QemuNetdevParam converts to the QEMU -netdev parameter notation -// This function has been reimplemented for the s390x architecture to deal -// with the VHOSTUSER case. Vhost user devices are not implemented on s390x -// architecture. For further details see issue -// https://github.com/kata-containers/runtime/issues/659 -func (n NetDeviceType) QemuNetdevParam() string { - switch n { - case TAP: - return "tap" - case MACVTAP: - return "tap" - case IPVTAP: - return "tap" - case VETHTAP: - return "tap" - case VFIO: - return "" - case VHOSTUSER: - log.Fatal("vhost-user devices are not supported on IBM Z") - return "" - default: - return "" - - } -} - -func isDimmSupported() bool { - return false -} diff --git a/qemu/qemu_s390x_test.go b/qemu/qemu_s390x_test.go index a9cc66cc53..e455add55f 100644 --- a/qemu/qemu_s390x_test.go +++ b/qemu/qemu_s390x_test.go @@ -31,7 +31,7 @@ var ( deviceVFIOString = "-device vfio-ccw,host=02:10.0,devno=" + DevNo deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo,devno=" + DevNo deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1,devno=" + DevNo - deviceBlockString = "-device virtio-blk,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + " -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + deviceBlockString = "-device virtio-blk-ccw,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + " -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" romfile = "" @@ -42,7 +42,7 @@ func TestAppendVirtioBalloon(t *testing.T) { ID: "balloon", } - var deviceString = "-device " + string(VirtioBalloon) + var deviceString = "-device " + string(VirtioBalloon) + "-" + string(TransportCCW) deviceString += ",id=" + balloonDevice.ID balloonDevice.DevNo = DevNo devnoOptios := ",devno=" + DevNo diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 3c2307858c..879642ceed 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -143,7 +143,7 @@ func TestAppendDeviceFS(t *testing.T) { ROMFile: "efi-virtio.rom", } - if isVirtioCCW[fsdev.Driver] { + if fsdev.Transport.isVirtioCCW(nil) { fsdev.DevNo = DevNo } @@ -164,7 +164,7 @@ func TestAppendDeviceNetwork(t *testing.T) { ROMFile: "efi-virtio.rom", } - if isVirtioCCW[netdev.Driver] { + if netdev.Transport.isVirtioCCW(nil) { netdev.DevNo = DevNo } @@ -195,7 +195,7 @@ func TestAppendDeviceNetworkMq(t *testing.T) { DisableModern: true, ROMFile: "efi-virtio.rom", } - if isVirtioCCW[netdev.Driver] { + if netdev.Transport.isVirtioCCW(nil) { netdev.DevNo = DevNo } @@ -219,7 +219,7 @@ func TestAppendDeviceNetworkPCI(t *testing.T) { ROMFile: romfile, } - if !isVirtioPCI[netdev.Driver] { + if !netdev.Transport.isVirtioPCI(nil) { t.Skip("Test valid only for PCI devices") } @@ -253,7 +253,7 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { ROMFile: romfile, } - if !isVirtioPCI[netdev.Driver] { + if !netdev.Transport.isVirtioPCI(nil) { t.Skip("Test valid only for PCI devices") } @@ -267,7 +267,7 @@ func TestAppendDeviceSerial(t *testing.T) { DisableModern: true, ROMFile: romfile, } - if isVirtioCCW[sdev.Driver] { + if sdev.Transport.isVirtioCCW(nil) { sdev.DevNo = DevNo } @@ -285,7 +285,7 @@ func TestAppendDeviceSerialPort(t *testing.T) { Path: "/tmp/char.sock", Name: "channel.0", } - if isVirtioCCW[chardev.Driver] { + if chardev.Transport.isVirtioCCW(nil) { chardev.DevNo = DevNo } testAppend(chardev, deviceSerialPortString, t) @@ -304,7 +304,7 @@ func TestAppendDeviceBlock(t *testing.T) { DisableModern: true, ROMFile: romfile, } - if isVirtioCCW[blkdev.Driver] { + if blkdev.Transport.isVirtioCCW(nil) { blkdev.DevNo = DevNo } testAppend(blkdev, deviceBlockString, t) @@ -318,7 +318,7 @@ func TestAppendDeviceVFIO(t *testing.T) { DeviceID: "0x5678", } - if isVirtioCCW[Vfio] { + if vfioDevice.Transport.isVirtioCCW(nil) { vfioDevice.DevNo = DevNo } @@ -334,7 +334,7 @@ func TestAppendVSOCK(t *testing.T) { ROMFile: romfile, } - if isVirtioCCW[VHostVSock] { + if vsockDevice.Transport.isVirtioCCW(nil) { vsockDevice.DevNo = DevNo } @@ -368,16 +368,19 @@ func TestVSOCKValid(t *testing.T) { func TestAppendVirtioRng(t *testing.T) { var objectString = "-object rng-random,id=rng0" - var deviceString = "-device " + string(VirtioRng) + ",rng=rng0" - if romfile != "" { - deviceString = deviceString + ",romfile=efi-virtio.rom" - } + var deviceString = "-device " + string(VirtioRng) + rngDevice := RngDevice{ ID: "rng0", ROMFile: romfile, } - if isVirtioCCW[VirtioRng] { + deviceString += "-" + string(rngDevice.Transport.getName(nil)) + ",rng=rng0" + if romfile != "" { + deviceString = deviceString + ",romfile=efi-virtio.rom" + } + + if rngDevice.Transport.isVirtioCCW(nil) { rngDevice.DevNo = DevNo deviceString += ",devno=" + rngDevice.DevNo } @@ -438,7 +441,7 @@ func TestAppendDeviceSCSIController(t *testing.T) { ROMFile: romfile, } - if isVirtioCCW[VirtioScsi] { + if scsiCon.Transport.isVirtioCCW(nil) { scsiCon.DevNo = DevNo } @@ -521,7 +524,7 @@ func TestAppendKnobsAllFalse(t *testing.T) { } func TestAppendMemoryHugePages(t *testing.T) { - if !isDimmSupported() { + if !isDimmSupported(nil) { t.Skip("Dimm not supported") } @@ -549,7 +552,7 @@ func TestAppendMemoryHugePages(t *testing.T) { } func TestAppendMemoryMemPrealloc(t *testing.T) { - if !isDimmSupported() { + if !isDimmSupported(nil) { t.Skip("Dimm not supported") } @@ -575,7 +578,7 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { } func TestAppendMemoryMemShared(t *testing.T) { - if !isDimmSupported() { + if !isDimmSupported(nil) { t.Skip("Dimm not supported") } @@ -601,7 +604,7 @@ func TestAppendMemoryMemShared(t *testing.T) { } func TestAppendMemoryFileBackedMem(t *testing.T) { - if !isDimmSupported() { + if !isDimmSupported(nil) { t.Skip("Dimm not supported") } @@ -627,7 +630,7 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { } func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { - if !isDimmSupported() { + if !isDimmSupported(nil) { t.Skip("Dimm not supported") } diff --git a/qemu/qmp.go b/qemu/qmp.go index ae56266981..83ac94a043 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -846,7 +846,9 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b "drive": blockdevID, } - if isVirtioCCW[DeviceDriver(driver)] { + var transport VirtioTransport + + if transport.isVirtioCCW(nil) { args["devno"] = bus } else if bus != "" { args["bus"] = bus @@ -855,7 +857,7 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } - if isVirtioPCI[DeviceDriver(driver)] { + if transport.isVirtioPCI(nil) { args["romfile"] = romfile if disableModern { @@ -897,12 +899,7 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive "id": devID, "driver": driver, "drive": blockdevID, - } - - if isVirtioCCW[DeviceDriver(driver)] { - args["devno"] = bus - } else { - args["bus"] = bus + "bus": bus, } if scsiID >= 0 { @@ -914,13 +911,6 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { args["share-rw"] = "on" } - if isVirtioPCI[DeviceDriver(driver)] { - args["romfile"] = romfile - - if disableModern { - args["disable-modern"] = disableModern - } - } return q.executeCommand(ctx, "device_add", args, nil) } @@ -1124,7 +1114,10 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver if queues > 0 { args["num-queues"] = strconv.Itoa(queues) } - if isVirtioPCI[DeviceDriver(driver)] { + + var transport VirtioTransport + + if transport.isVirtioPCI(nil) { args["romfile"] = romfile if disableModern { @@ -1161,9 +1154,18 @@ func (q *QMP) ExecutePCIVhostUserDevAdd(ctx context.Context, driver, devID, char // bdf is the PCI bus-device-function of the pci device. // bus is optional. When hot plugging a PCIe device, the bus can be the ID of the pcie-root-port. func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, bus, romfile string) error { + var driver string + var transport VirtioTransport + + if transport.isVirtioCCW(nil) { + driver = string(VfioCCW) + } else { + driver = string(VfioPCI) + } + args := map[string]interface{}{ "id": devID, - "driver": Vfio, + "driver": driver, "host": bdf, "romfile": romfile, } @@ -1181,7 +1183,7 @@ func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, bus, romfile func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus, romfile string) error { args := map[string]interface{}{ "id": devID, - "driver": Vfio, + "driver": VfioPCI, "host": bdf, "addr": addr, "romfile": romfile, @@ -1201,7 +1203,7 @@ func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus, romfile string) error { args := map[string]interface{}{ "id": devID, - "driver": Vfio, + "driver": VfioPCI, "sysfsdev": sysfsdev, "romfile": romfile, } @@ -1265,10 +1267,6 @@ func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, } } - if isVirtioPCI[DeviceDriver(driver)] { - args["romfile"] = romfile - } - return q.executeCommand(ctx, "device_add", args, nil) } @@ -1477,7 +1475,7 @@ func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { // 1.0 in nested environments. func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus, romfile string, disableModern bool) error { args := map[string]interface{}{ - "driver": VHostVSock, + "driver": VHostVSockPCI, "id": id, "guest-cid": guestCID, "vhostfd": vhostfd, From 3700c55dd766d37e17af354fb9975dc801619d62 Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Wed, 19 Feb 2020 05:21:18 -0800 Subject: [PATCH 205/264] qemu: add block device readonly support So that we can attach it readonly. Signed-off-by: Peng Tao --- qemu/qemu.go | 7 +++++++ qemu/qemu_arch_base_test.go | 2 +- qemu/qemu_s390x_test.go | 2 +- qemu/qemu_test.go | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index d774d35e85..a5e5dfaf96 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -971,6 +971,9 @@ type BlockDevice struct { // ShareRW enables multiple qemu instances to share the File ShareRW bool + // ReadOnly sets the block device in readonly mode + ReadOnly bool + // Transport is the virtio transport for this device. Transport VirtioTransport } @@ -1029,6 +1032,10 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { blkParams = append(blkParams, fmt.Sprintf(",format=%s", blkdev.Format)) blkParams = append(blkParams, fmt.Sprintf(",if=%s", blkdev.Interface)) + if blkdev.ReadOnly { + blkParams = append(blkParams, ",readonly") + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index d1eb5d24bf..9e96f7847c 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -36,7 +36,7 @@ var ( deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" - deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" romfile = "efi-virtio.rom" diff --git a/qemu/qemu_s390x_test.go b/qemu/qemu_s390x_test.go index e455add55f..9e9cb4024b 100644 --- a/qemu/qemu_s390x_test.go +++ b/qemu/qemu_s390x_test.go @@ -31,7 +31,7 @@ var ( deviceVFIOString = "-device vfio-ccw,host=02:10.0,devno=" + DevNo deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo,devno=" + DevNo deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1,devno=" + DevNo - deviceBlockString = "-device virtio-blk-ccw,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + " -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none" + deviceBlockString = "-device virtio-blk-ccw,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + ",share-rw=on -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" romfile = "" diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 879642ceed..02f9bd88c2 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -303,6 +303,8 @@ func TestAppendDeviceBlock(t *testing.T) { WCE: false, DisableModern: true, ROMFile: romfile, + ShareRW: true, + ReadOnly: true, } if blkdev.Transport.isVirtioCCW(nil) { blkdev.DevNo = DevNo From 5378725f1156bc3f7d75e054f3c3ad61d0b79e93 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 21 Feb 2020 15:34:53 +0000 Subject: [PATCH 206/264] qemu: add pmem flag to memory-backend-file According to QEMU's nvdimm documentation: When 'pmem' is 'on' and QEMU is built with libpmem support, QEMU will take necessary operations to guarantee the persistence of its own writes to the vNVDIMM backend. Signed-off-by: Julio Montes --- qemu/qmp.go | 13 +++++++++++-- qemu/qmp_test.go | 7 ++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 83ac94a043..bf9a77dda1 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1428,8 +1428,9 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string // a NVDIMM driver with the device_add command. // id is the id of the device to add. It must be a valid QMP identifier. // mempath is the path of the device to add, e.g., /dev/rdb0. size is -// the data size of the device. -func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64) error { +// the data size of the device. pmem is to guarantee the persistence of QEMU writes +// to the vNVDIMM backend. +func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64, pmem *bool) error { args := map[string]interface{}{ "qom-type": "memory-backend-file", "id": "nvdimmbackmem" + id, @@ -1439,6 +1440,14 @@ func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, si "share": true, }, } + + if q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1) { + if pmem != nil { + props := args["props"].(map[string]interface{}) + props["pmem"] = *pmem + } + } + err := q.executeCommand(ctx, "object-add", args, nil) if err != nil { return err diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index c7b9c9e4d2..5bc909fd47 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1617,7 +1617,12 @@ func TestExecuteNVDIMMDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024) + q.version = &QMPVersion{ + Major: 4, + Minor: 1, + } + pmem := true + err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024, &pmem) if err != nil { t.Fatalf("Unexpected error: %v", err) } From 787c86b7e53b448684da5f0aaf3549750295900a Mon Sep 17 00:00:00 2001 From: Liam Merwick Date: Wed, 22 Apr 2020 14:28:37 +0000 Subject: [PATCH 207/264] qemu: Add microvm machine type support Following on from #111 which added support for multiple virtio transports, add code to use virtio-mmio as the transport when booting a guest with the microvm machine type and add a microvm case when checking for NUMA support. Also add a test case for machine string parsing. Signed-off-by: Liam Merwick --- qemu/qemu.go | 12 ++++++++++++ qemu/qemu_test.go | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index a5e5dfaf96..ed211d3ccb 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -51,6 +51,11 @@ type Machine struct { Options string } +const ( + // MachineTypeMicrovm is the QEMU microvm machine type for amd64 + MachineTypeMicrovm string = "microvm" +) + // Device is the qemu device interface. type Device interface { Valid() bool @@ -128,6 +133,10 @@ const ( func isDimmSupported(config *Config) bool { switch runtime.GOARCH { case "amd64", "386": + if config != nil && config.Machine.Type == MachineTypeMicrovm { + // microvm does not support NUMA + return false + } return true default: return false @@ -153,6 +162,9 @@ const ( func (transport VirtioTransport) defaultTransport(config *Config) VirtioTransport { switch runtime.GOARCH { case "amd64", "386": + if config != nil && config.Machine.Type == MachineTypeMicrovm { + return TransportMMIO + } return TransportPCI case "s390x": return TransportCCW diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 02f9bd88c2..f1de632c81 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -108,6 +108,14 @@ func TestAppendMachine(t *testing.T) { Options: "gic-version=host,usb=off", } testAppend(machine, machineString, t) + + machineString = "-machine microvm,accel=kvm,pic=off,pit=off" + machine = Machine{ + Type: "microvm", + Acceleration: "kvm", + Options: "pic=off,pit=off", + } + testAppend(machine, machineString, t) } func TestAppendEmptyMachine(t *testing.T) { From 0e98b613a848b261056de71ee1c964220b1fa186 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 24 Apr 2020 15:10:51 +0000 Subject: [PATCH 208/264] qemu: Add max_ports option to virtio-serial device Allow API consumers to change the maximum number of ports in the virtio-serial devices, setting a lower number of ports can improve the boot time and reduce the attack surface. fixes #120 Signed-off-by: Julio Montes --- qemu/qemu.go | 6 ++++++ qemu/qemu_arch_base_test.go | 2 +- qemu/qemu_test.go | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index ed211d3ccb..92ad82a745 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -880,6 +880,9 @@ type SerialDevice struct { // Transport is the virtio transport for this device. Transport VirtioTransport + + // MaxPorts is the maximum number of ports for this device. + MaxPorts uint } // Valid returns true if the SerialDevice structure is valid and complete. @@ -903,6 +906,9 @@ func (dev SerialDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) if dev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) + if dev.Driver == VirtioSerial && dev.MaxPorts != 0 { + deviceParams = append(deviceParams, fmt.Sprintf(",max_ports=%d", dev.MaxPorts)) + } } if dev.Transport.isVirtioCCW(config) { diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 9e96f7847c..3e22ceeb03 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -24,7 +24,7 @@ var ( deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,romfile=efi-virtio.rom" deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" - deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom" + deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom,max_ports=2" deviceVhostUserNetString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -netdev type=vhost-user,id=net1,chardev=char1,vhostforce -device virtio-net-pci,netdev=net1,mac=00:11:22:33:44:55,romfile=efi-virtio.rom" deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" deviceVFIOString = "-device vfio-pci,host=02:10.0,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678,romfile=efi-virtio.rom" diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index f1de632c81..1aa9ac3f3d 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -274,6 +274,7 @@ func TestAppendDeviceSerial(t *testing.T) { ID: "serial0", DisableModern: true, ROMFile: romfile, + MaxPorts: 2, } if sdev.Transport.isVirtioCCW(nil) { sdev.DevNo = DevNo From 29529a5d72ea7394202e272d9b2a48fe6b978d8e Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Mon, 4 May 2020 22:56:21 +0800 Subject: [PATCH 209/264] Add rt clock definition for rtc clock in qemu There are three different types for the RTC clock: host, rt and vm. Add `rt` to the list of RTC clocks. Signed-off-by: Shuicheng Lin --- qemu/qemu.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index ed211d3ccb..3012ceb375 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1876,6 +1876,9 @@ const ( // Host is for using the host clock as a reference. Host RTCClock = "host" + // RT is for using the host monotonic clock as a reference. + RT RTCClock = "rt" + // VM is for using the guest clock as a reference VM RTCClock = "vm" ) @@ -1902,7 +1905,7 @@ type RTC struct { // Valid returns true if the RTC structure is valid and complete. func (rtc RTC) Valid() bool { - if rtc.Clock != Host && rtc.Clock != VM { + if rtc.Clock != Host && rtc.Clock != RT && rtc.Clock != VM { return false } From b2aa0225ac2468809e818dcc5f1bad5c95fa4507 Mon Sep 17 00:00:00 2001 From: "Pradipta Kr. Banerjee" Date: Wed, 13 May 2020 01:21:00 +0530 Subject: [PATCH 210/264] Enable Numa support for Power (ppc64le) architecture Fixes #124 Signed-off-by: bpradipt@in.ibm.com --- qemu/qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 878456af75..6ba204874f 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -132,7 +132,7 @@ const ( func isDimmSupported(config *Config) bool { switch runtime.GOARCH { - case "amd64", "386": + case "amd64", "386", "ppc64le": if config != nil && config.Machine.Type == MachineTypeMicrovm { // microvm does not support NUMA return false From e57e86e2eae348cb9bda809f6e78a766ce4cfcb5 Mon Sep 17 00:00:00 2001 From: Adrian Moreno Date: Tue, 10 Mar 2020 18:28:50 +0100 Subject: [PATCH 211/264] qemu: add IOMMU Device The following options can be provided Intremap: activates interrupt remapping DeviceIotlb: enables device IOTLB support for the vIOMMU CachingMode: enables Cahing Mode See: https://wiki.qemu.org/Features/VT-d Signed-off-by: Adrian Moreno --- qemu/qemu.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 6ba204874f..a149dad8b0 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1861,6 +1861,52 @@ func (b BalloonDevice) deviceName(config *Config) string { return BalloonDeviceTransport[b.Transport] } +// IommuDev represents a Intel IOMMU Device +type IommuDev struct { + Intremap bool + DeviceIotlb bool + CachingMode bool +} + +// Valid returns true if the IommuDev is valid +func (dev IommuDev) Valid() bool { + return true +} + +// deviceName the qemu device name +func (dev IommuDev) deviceName() string { + return "intel-iommu" +} + +// QemuParams returns the qemu parameters built out of the IommuDev. +func (dev IommuDev) QemuParams(_ *Config) []string { + var qemuParams []string + var deviceParams []string + + deviceParams = append(deviceParams, dev.deviceName()) + if dev.Intremap { + deviceParams = append(deviceParams, "intremap=on") + } else { + deviceParams = append(deviceParams, "intremap=off") + } + + if dev.DeviceIotlb { + deviceParams = append(deviceParams, "device-iotlb=on") + } else { + deviceParams = append(deviceParams, "device-iotlb=off") + } + + if dev.CachingMode { + deviceParams = append(deviceParams, "caching-mode=on") + } else { + deviceParams = append(deviceParams, "caching-mode=off") + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) + return qemuParams +} + // RTCBaseType is the qemu RTC base time type. type RTCBaseType string diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 1aa9ac3f3d..fe02bbdb42 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -1125,3 +1125,27 @@ func TestBadCPUs(t *testing.T) { t.Errorf("Error expected") } } + +var ( + vIommuString = "-device intel-iommu,intremap=on,device-iotlb=on,caching-mode=on" + vIommuNoCacheString = "-device intel-iommu,intremap=on,device-iotlb=on,caching-mode=off" +) + +func TestIommu(t *testing.T) { + iommu := IommuDev{ + Intremap: true, + DeviceIotlb: true, + CachingMode: true, + } + + if !iommu.Valid() { + t.Fatalf("iommu should be valid") + } + + testAppend(iommu, vIommuString, t) + + iommu.CachingMode = false + + testAppend(iommu, vIommuNoCacheString, t) + +} From cc538766613bd60ef4db042168d20dfc3119d553 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 29 May 2020 20:40:35 +0000 Subject: [PATCH 212/264] qemu/qmp: use boolean type for the vhost vhost is a Netdev Tap Option used to configure a host TAP network interface backend, according to the QMP API documentation the type for such option must be a boolean. Use boolean type for vhost option to fix the following error on recent versions of QEMU: ``` Invalid parameter type for 'vhost', expected: boolean ``` Signed-off-by: Julio Montes --- qemu/qmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index bf9a77dda1..5585a8792b 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -993,7 +993,7 @@ func (q *QMP) ExecuteNetdevAddByFds(ctx context.Context, netdevType, netdevID st } if len(vhostFdNames) > 0 { vhostFdNameStr := strings.Join(vhostFdNames, ":") - args["vhost"] = "on" + args["vhost"] = true args["vhostfds"] = vhostFdNameStr } From abca6f3ce994daac7abfba93b11f221804df6972 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 22 Jul 2020 11:36:14 -0500 Subject: [PATCH 213/264] Add multidevs option to fsdev multidevs specifies how to deal with multiple devices being shared with a 9p export. `multidevs=remap` fixes the following warning: ``` 9p: Multiple devices detected in same VirtFS export, which might lead to file ID collisions and severe misbehaviours on guest! You should either use a separate export for each device shared from host or use virtfs option 'multidevs=remap'! ``` Signed-off-by: Julio Montes --- qemu/qemu.go | 25 +++++++++++++++++++++++++ qemu/qemu_arch_base_test.go | 2 +- qemu/qemu_s390x_test.go | 2 +- qemu/qemu_test.go | 1 + 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index a149dad8b0..4716595cd7 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -288,6 +288,23 @@ func (object Object) QemuParams(config *Config) []string { return qemuParams } +// Virtio9PMultidev filesystem behaviour to deal +// with multiple devices being shared with a 9p export. +type Virtio9PMultidev string + +const ( + // Remap shares multiple devices with only one export. + Remap Virtio9PMultidev = "remap" + + // Warn assumes that only one device is shared by the same export. + // Only a warning message is logged (once) by qemu on host side. + // This is the default behaviour. + Warn Virtio9PMultidev = "warn" + + // Forbid like "warn" but also deny access to additional devices on guest. + Forbid Virtio9PMultidev = "forbid" +) + // FSDriver represents a qemu filesystem driver. type FSDriver string @@ -350,6 +367,10 @@ type FSDevice struct { // Transport is the virtio transport for this device. Transport VirtioTransport + + // Multidev is the filesystem behaviour to deal + // with multiple devices being shared with a 9p export + Multidev Virtio9PMultidev } // Virtio9PTransport is a map of the virtio-9p device name that corresponds @@ -393,6 +414,10 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { fsParams = append(fsParams, fmt.Sprintf(",path=%s", fsdev.Path)) fsParams = append(fsParams, fmt.Sprintf(",security_model=%s", fsdev.SecurityModel)) + if fsdev.Multidev != "" { + fsParams = append(fsParams, fmt.Sprintf(",multidevs=%s", fsdev.Multidev)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 3e22ceeb03..58de5d1eaa 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -21,7 +21,7 @@ package qemu import "testing" var ( - deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" + deviceFSString = "-device virtio-9p-pci,disable-modern=true,fsdev=workload9p,mount_tag=rootfs,romfile=efi-virtio.rom -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none,multidevs=remap" deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,romfile=efi-virtio.rom" deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-pci,netdev=tap0,mac=01:02:de:ad:be:ef,disable-modern=true,mq=on,vectors=6,romfile=efi-virtio.rom" deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom,max_ports=2" diff --git a/qemu/qemu_s390x_test.go b/qemu/qemu_s390x_test.go index 9e9cb4024b..459bb641d3 100644 --- a/qemu/qemu_s390x_test.go +++ b/qemu/qemu_s390x_test.go @@ -23,7 +23,7 @@ import "testing" // -pci devices don't play well with Z hence replace them with corresponding -ccw devices // See https://wiki.qemu.org/Documentation/Platforms/S390X var ( - deviceFSString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs,devno=" + DevNo + " -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none" + deviceFSString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs,devno=" + DevNo + " -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none,multidevs=remap" deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,devno=" + DevNo deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,mq=on,devno=" + DevNo deviceSerialString = "-device virtio-serial-ccw,id=serial0,devno=" + DevNo diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index fe02bbdb42..2e7669a151 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -149,6 +149,7 @@ func TestAppendDeviceFS(t *testing.T) { SecurityModel: None, DisableModern: true, ROMFile: "efi-virtio.rom", + Multidev: Remap, } if fsdev.Transport.isVirtioCCW(nil) { From 6645baf249b59a077ac1d4004a84afe28eb805dc Mon Sep 17 00:00:00 2001 From: Liam Merwick Date: Mon, 20 Jul 2020 13:56:17 +0100 Subject: [PATCH 214/264] qemu: Add NoReboot config Knob for qemuParams The Kata architecture does not support rebooting VMs (the lifecycle being start/exec/kill) and if a VM is killed (e.g. using sysrq-trigger), the VM does not exit fully and other layers do not notice the state change. Kata needs a way to tell QEMU to run with the '--no-reboot' option so that the guest VM exits and does not attempt to reboot. Add a NoReboot boolean Knob so when Knobs.NoReboot is set, the '--no-reboot' command-line option will be passed to QEMU on startup. Signed-off-by: Liam Merwick --- qemu/qemu.go | 7 +++++++ qemu/qemu_test.go | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 4716595cd7..d5a01c87a8 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2122,6 +2122,9 @@ type Knobs struct { // Realtime will enable realtime QEMU Realtime bool + + // Exit instead of rebooting + NoReboot bool } // IOThread allows IO to be performed on a separate thread. @@ -2457,6 +2460,10 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, "-nographic") } + if config.Knobs.NoReboot { + config.qemuParams = append(config.qemuParams, "--no-reboot") + } + if config.Knobs.Daemonize { config.qemuParams = append(config.qemuParams, "-daemonize") } diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 2e7669a151..30dedf9727 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -501,11 +501,12 @@ func TestAppendEmptyDevice(t *testing.T) { } func TestAppendKnobsAllTrue(t *testing.T) { - var knobsString = "-no-user-config -nodefaults -nographic -daemonize -realtime mlock=on -S" + var knobsString = "-no-user-config -nodefaults -nographic --no-reboot -daemonize -realtime mlock=on -S" knobs := Knobs{ NoUserConfig: true, NoDefaults: true, NoGraphic: true, + NoReboot: true, Daemonize: true, MemPrealloc: true, FileBackedMem: true, @@ -524,6 +525,7 @@ func TestAppendKnobsAllFalse(t *testing.T) { NoUserConfig: false, NoDefaults: false, NoGraphic: false, + NoReboot: false, MemPrealloc: false, FileBackedMem: false, MemShared: false, @@ -668,6 +670,18 @@ func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } +func TestNoRebootKnob(t *testing.T) { + conf := &Config{} + + knobs := Knobs{ + NoReboot: true, + } + knobsString := "--no-reboot" + mlockFalseString := "-realtime mlock=off" + + testConfigAppend(conf, knobs, knobsString+" "+mlockFalseString, t) +} + var kernelString = "-kernel /opt/vmlinux.container -initrd /opt/initrd.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" func TestAppendKernel(t *testing.T) { From cf0f05d2e9e77465823be132b8fb3b27f1b9d06b Mon Sep 17 00:00:00 2001 From: Qi Feng Huo Date: Wed, 29 Jul 2020 17:12:35 +0800 Subject: [PATCH 215/264] qemu: add iommu_platform knob for qemuParams Signed-off-by: Qi Feng Huo fix typo Signed-off-by: Qi Feng Huo qemu: remove useless fmt.Sprintf for qemuParams Signed-off-by: Qi Feng Huo fix test cases for s390x Signed-off-by: Qi Feng Huo --- qemu/qemu.go | 24 +++++++++++++++++++ qemu/qemu_s390x_test.go | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index d5a01c87a8..e326713e1e 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -406,6 +406,9 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) } if fsdev.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, ",iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", fsdev.DevNo)) } @@ -537,6 +540,9 @@ func (cdev CharDevice) QemuParams(config *Config) []string { } if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, ",iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", cdev.DevNo)) } @@ -804,6 +810,9 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { } if netdev.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, ",iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", netdev.DevNo)) } @@ -937,6 +946,9 @@ func (dev SerialDevice) QemuParams(config *Config) []string { } if dev.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, ",iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", dev.DevNo)) } @@ -1528,6 +1540,9 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { } if scsiCon.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + devParams = append(devParams, ",iommu_platform=on") + } devParams = append(devParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) } @@ -1710,6 +1725,9 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { } if vsock.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, ",iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vsock.DevNo)) } @@ -1780,6 +1798,9 @@ func (v RngDevice) QemuParams(config *Config) []string { } if v.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, ",iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", v.DevNo)) } @@ -2125,6 +2146,9 @@ type Knobs struct { // Exit instead of rebooting NoReboot bool + + // IOMMUPlatform will enable IOMMU for supported devices + IOMMUPlatform bool } // IOThread allows IO to be performed on a separate thread. diff --git a/qemu/qemu_s390x_test.go b/qemu/qemu_s390x_test.go index 459bb641d3..afc1afed30 100644 --- a/qemu/qemu_s390x_test.go +++ b/qemu/qemu_s390x_test.go @@ -24,6 +24,7 @@ import "testing" // See https://wiki.qemu.org/Documentation/Platforms/S390X var ( deviceFSString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs,devno=" + DevNo + " -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none,multidevs=remap" + deviceFSIOMMUString = "-device virtio-9p-ccw,fsdev=workload9p,mount_tag=rootfs,iommu_platform=on,devno=" + DevNo + " -fsdev local,id=workload9p,path=/var/lib/docker/devicemapper/mnt/e31ebda2,security_model=none,multidevs=remap" deviceNetworkString = "-netdev tap,id=tap0,vhost=on,ifname=ceth0,downscript=no,script=no -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,devno=" + DevNo deviceNetworkStringMq = "-netdev tap,id=tap0,vhost=on,fds=3:4 -device driver=virtio-net-ccw,netdev=tap0,mac=01:02:de:ad:be:ef,mq=on,devno=" + DevNo deviceSerialString = "-device virtio-serial-ccw,id=serial0,devno=" + DevNo @@ -54,3 +55,54 @@ func TestAppendVirtioBalloon(t *testing.T) { balloonDevice.DeflateOnOOM = true testAppend(balloonDevice, deviceString+devnoOptios+OnDeflateOnOMM, t) } + +func TestAppendDeviceFSCCW(t *testing.T) { + defaultKnobs := Knobs{ + NoUserConfig: true, + } + + fsdev := FSDevice{ + Driver: Virtio9P, + FSDriver: Local, + ID: "workload9p", + Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", + MountTag: "rootfs", + SecurityModel: None, + DisableModern: true, + ROMFile: "efi-virtio.rom", + Multidev: Remap, + Transport: TransportCCW, + DevNo: DevNo, + } + + var config Config + config.Knobs = defaultKnobs + + testConfigAppend(&config, fsdev, deviceFSString, t) +} + +func TestAppendDeviceFSCCWIOMMU(t *testing.T) { + defaultKnobs := Knobs{ + NoUserConfig: true, + IOMMUPlatform: true, + } + + fsdev := FSDevice{ + Driver: Virtio9P, + FSDriver: Local, + ID: "workload9p", + Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", + MountTag: "rootfs", + SecurityModel: None, + DisableModern: true, + ROMFile: "efi-virtio.rom", + Multidev: Remap, + Transport: TransportCCW, + DevNo: DevNo, + } + + var config Config + config.Knobs = defaultKnobs + + testConfigAppend(&config, fsdev, deviceFSIOMMUString, t) +} From 4831c6e0a3ffb330788285831ff4cfb0f3ce838d Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 17 Aug 2020 11:00:07 -0500 Subject: [PATCH 216/264] travis: Run coveralls after success Fix the following error: ``` Bad response status from coveralls: 422 {"message":"service_job_id (717167073) must be unique for Travis Jobs not supplying a Coveralls Repo Token","error":true} The command "$GOPATH/bin/goveralls -v -service=travis-ci" exited with 1. ``` fixes #135 Signed-off-by: Julio Montes --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 162aa50420..662abd1006 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,5 +21,7 @@ before_install: script: - go env - - $GOPATH/bin/goveralls -v -service=travis-ci - gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... + +after_success: + - $GOPATH/bin/goveralls -repotoken $COVERALLS_TOKEN -v -service=travis-ci From 1af1c0d783d10a16675ad5a1c05acdcb258d51e5 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 17 Aug 2020 11:39:54 -0500 Subject: [PATCH 217/264] github: enable github actions Use github actions to run unit tests. Github actions service looks more stable and reliable than travis. fixes #136 Signed-off-by: Julio Montes --- .github/workflows/main.yml | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..6f55612d3f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,40 @@ +on: ["push", "pull_request"] +name: Unit tests +jobs: + test: + strategy: + matrix: + go-version: [1.13.x, 1.14.x, 1.15.x] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + env: + GO111MODULE: off + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Setup GOPATH + run: | + gopath_org=$(go env GOPATH)/src/github.com/intel/ + mkdir -p ${gopath_org} + ln -s ${PWD} ${gopath_org} + - name: Checkout code + uses: actions/checkout@v2 + - name: Install gometalinter + run: | + go get github.com/alecthomas/gometalinter + $(go env GOPATH)/bin/gometalinter --install + - name: Running gometalinter + run: | + gopath_repo=$(go env GOPATH)/src/github.com/intel/govmm + pushd ${gopath_repo} + $(go env GOPATH)/bin/gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... + - name: Send coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gopath_repo=$(go env GOPATH)/src/github.com/intel/govmm + pushd ${gopath_repo} + go get github.com/mattn/goveralls + $(go env GOPATH)/bin/goveralls -v -service=github From f5bdd53ce635686c0304c5575d837e3bd06a674c Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Mon, 17 Aug 2020 13:03:11 -0500 Subject: [PATCH 218/264] travis: disable amd64 jobs move amd64 CI jobs to github actions Signed-off-by: Julio Montes --- .github/workflows/main.yml | 2 +- .travis.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f55612d3f..05784b1b5b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -on: ["push", "pull_request"] +on: ["pull_request"] name: Unit tests jobs: test: diff --git a/.travis.yml b/.travis.yml index 162aa50420..8ec1cbd8b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ go: - "1.11" - tip arch: - - amd64 - s390x go_import_path: github.com/intel/govmm From 39c372a2012caf6efcb50d23619a06f969c0370c Mon Sep 17 00:00:00 2001 From: Jakob-Naucke Date: Thu, 6 Aug 2020 14:39:26 +0200 Subject: [PATCH 219/264] Add support for hot-plugging IBM VFIO-AP devices Add ExecuteAPVFIOMediatedDeviceAdd to qmp.go, which executes a hotplug for an IBM Adjunct processor (AP) VFIO device (see also https://www.kernel.org/doc/html/latest/s390/vfio-ap.html ) Also includes the respective unittest and adds the VfioAP DeviceDriver constant to qemu.go. Pushing again due to incidental CI failure Fixes: #133 Signed-off-by: Jakob-Naucke Reviewed-by: alicefr --- qemu/qemu.go | 3 +++ qemu/qmp.go | 9 +++++++++ qemu/qmp_test.go | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index e326713e1e..9288f59f8a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -123,6 +123,9 @@ const ( // VfioCCW is the vfio driver with CCW transport. VfioCCW DeviceDriver = "vfio-ccw" + // VfioAP is the vfio driver with AP transport. + VfioAP DeviceDriver = "vfio-ap" + // VHostVSockPCI is a generic Vsock vhost device with PCI transport. VHostVSockPCI DeviceDriver = "vhost-vsock-pci" diff --git a/qemu/qmp.go b/qemu/qmp.go index 5585a8792b..9ee81d8872 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1217,6 +1217,15 @@ func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsd return q.executeCommand(ctx, "device_add", args, nil) } +// ExecuteAPVFIOMediatedDeviceAdd adds a VFIO mediated AP device to a QEMU instance using the device_add command. +func (q *QMP) ExecuteAPVFIOMediatedDeviceAdd(ctx context.Context, sysfsdev string) error { + args := map[string]interface{}{ + "driver": VfioAP, + "sysfsdev": sysfsdev, + } + return q.executeCommand(ctx, "device_add", args, nil) +} + // isSocketIDSupported returns if the cpu driver supports the socket id option func isSocketIDSupported(driver string) bool { if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" { diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 5bc909fd47..81cb399de4 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1100,6 +1100,23 @@ func TestQMPPCIVFIOPCIeDeviceAdd(t *testing.T) { <-disconnectedCh } +func TestQMPAPVFIOMediatedDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + sysfsDev := "/sys/devices/vfio_ap/matrix/a297db4a-f4c2-11e6-90f6-d3b88d6c9525" + err := q.ExecuteAPVFIOMediatedDeviceAdd(context.Background(), sysfsDev) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that CPU are correctly added using device_add func TestQMPCPUDeviceAdd(t *testing.T) { drivers := []string{"host-x86_64-cpu", "host-s390x-cpu", "host-powerpc64-cpu"} From 3d46d08a902297a211c7dd202b8f13d56d71052d Mon Sep 17 00:00:00 2001 From: David Gibson Date: Thu, 3 Sep 2020 14:02:44 +1000 Subject: [PATCH 220/264] Add qom-get function Add a function to access the qom-get QMP command so we can query information from qemu. Signed-off-by: David Gibson --- qemu/qmp.go | 15 +++++++++++++++ qemu/qmp_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index 4942727c42..fc3f1e3f3a 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1639,3 +1639,18 @@ func (q *QMP) ExecQomSet(ctx context.Context, path, property string, value uint6 return q.executeCommand(ctx, "qom-set", args, nil) } + +// ExecQomGet qom-get path property +func (q *QMP) ExecQomGet(ctx context.Context, path, property string) (interface{}, error) { + args := map[string]interface{}{ + "path": path, + "property": property, + } + + response, err := q.executeCommandWithResponse(ctx, "qom-get", args, nil, nil) + if err != nil { + return "", err + } + + return response, nil +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 81cb399de4..14bfd24921 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1770,3 +1770,27 @@ func TestExecQomSet(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks qom-get +func TestExecQomGet(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("qom-get", nil, "return", "container") + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + val, err := q.ExecQomGet(context.Background(), "/", "type") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + vals, ok := val.(string) + if !ok { + t.Fatalf("Unexpected type in qom-get") + } + if vals != "container" { + t.Fatalf("Unpexected value in qom-get") + } + q.Shutdown() + <-disconnectedCh +} From 9f309c2aa1c2157c3a8614f88d4a957b6515b25a Mon Sep 17 00:00:00 2001 From: "James O. D. Hunt" Date: Fri, 9 Oct 2020 09:10:10 +0100 Subject: [PATCH 221/264] misc: Update for new GitHub organisation name `govmm` is now part of the `kata-containers` GitHub organisation, so update to reflect this. Fixes: #145. Signed-off-by: James O. D. Hunt --- .github/workflows/main.yml | 6 +++--- .travis.yml | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 10 +++++----- qemu/examples_test.go | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05784b1b5b..0ced8ae572 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Setup GOPATH run: | - gopath_org=$(go env GOPATH)/src/github.com/intel/ + gopath_org=$(go env GOPATH)/src/github.com/kata-containers/ mkdir -p ${gopath_org} ln -s ${PWD} ${gopath_org} - name: Checkout code @@ -27,14 +27,14 @@ jobs: $(go env GOPATH)/bin/gometalinter --install - name: Running gometalinter run: | - gopath_repo=$(go env GOPATH)/src/github.com/intel/govmm + gopath_repo=$(go env GOPATH)/src/github.com/kata-containers/govmm pushd ${gopath_repo} $(go env GOPATH)/bin/gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... - name: Send coverage env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gopath_repo=$(go env GOPATH)/src/github.com/intel/govmm + gopath_repo=$(go env GOPATH)/src/github.com/kata-containers/govmm pushd ${gopath_repo} go get github.com/mattn/goveralls $(go env GOPATH)/bin/goveralls -v -service=github diff --git a/.travis.yml b/.travis.yml index e249dcf845..8dc92a8205 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ go: arch: - s390x -go_import_path: github.com/intel/govmm +go_import_path: github.com/kata-containers/govmm matrix: allow_failures: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99630bccf0..451903e0a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,12 +85,12 @@ We accept github pull requests. We request you give quality assurance some consideration by: * Adding go unit tests for changes where it makes sense. -* Enabling [Travis CI](https://travis-ci.org/intel/govmm) on your github fork of Virtual Machine Manager for Go to get continuous integration feedback on your dev/test branches. +* Enabling [Travis CI](https://travis-ci.org/kata-containers/govmm) on your github fork of Virtual Machine Manager for Go to get continuous integration feedback on your dev/test branches. ## Issue tracking If you have a problem, please let us know. If it's a bug not already documented, by all means please [open an -issue in github](https://github.com/intel/govmm/issues/new) so we all get visibility +issue in github](https://github.com/kata-containers/govmm/issues/new) so we all get visibility the problem and work toward resolution. Any security issues discovered with govmm should be reported by following the instructions on https://01.org/security. diff --git a/README.md b/README.md index 2bb92f0c47..e23bab54b8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Virtual Machine Manager for Go -[![Go Report Card](https://goreportcard.com/badge/github.com/intel/govmm)](https://goreportcard.com/report/github.com/intel/govmm) -[![Build Status](https://travis-ci.org/intel/govmm.svg?branch=master)](https://travis-ci.org/intel/govmm) -[![GoDoc](https://godoc.org/github.com/intel/govmm/qemu?status.svg)](https://godoc.org/github.com/intel/govmm/qemu) -[![Coverage Status](https://coveralls.io/repos/github/intel/govmm/badge.svg?branch=master)](https://coveralls.io/github/intel/govmm?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/kata-containers/govmm)](https://goreportcard.com/report/github.com/kata-containers/govmm) +[![Build Status](https://travis-ci.org/kata-containers/govmm.svg?branch=master)](https://travis-ci.org/kata-containers/govmm) +[![GoDoc](https://godoc.org/github.com/kata-containers/govmm/qemu?status.svg)](https://godoc.org/github.com/kata-containers/govmm/qemu) +[![Coverage Status](https://coveralls.io/repos/github/kata-containers/govmm/badge.svg?branch=master)](https://coveralls.io/github/kata-containers/govmm?branch=master) Virtual Machine Manager for Go (govmm) is a suite of packages that provide Go APIs for creating and managing virtual machines. There's currently support for only one hypervisor, qemu/kvm, support for which -is provided by the github.com/intel/govmm/qemu package. +is provided by the github.com/kata-containers/govmm/qemu package. The qemu package provides APIs for launching qemu instances and for managing those instances via QMP, once launched. VM instances can diff --git a/qemu/examples_test.go b/qemu/examples_test.go index 737d58913e..014e9be3b5 100644 --- a/qemu/examples_test.go +++ b/qemu/examples_test.go @@ -21,7 +21,7 @@ import ( "context" - "github.com/intel/govmm/qemu" + "github.com/kata-containers/govmm/qemu" ) func Example() { From 29ba5a90121534bf6245263d29a2c5cb24f33f75 Mon Sep 17 00:00:00 2001 From: Salvatore Mazzarino Date: Fri, 9 Oct 2020 10:17:58 +0200 Subject: [PATCH 222/264] qemu: add fw_cfg flag to config Signed-off-by: Salvatore Mazzarino --- qemu/qemu.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 39 ++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 9288f59f8a..05cf9e16b9 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2103,6 +2103,54 @@ type Kernel struct { Params string } +// FwCfg allows QEMU to pass entries to the guest +// File and Str are mutually exclusive +type FwCfg struct { + Name string + File string + Str string +} + +// Valid returns true if the FwCfg structure is valid and complete. +func (fwcfg FwCfg) Valid() bool { + if fwcfg.Name == "" { + return false + } + + if fwcfg.File != "" && fwcfg.Str != "" { + return false + } + + if fwcfg.File == "" && fwcfg.Str == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of the FwCfg object +func (fwcfg FwCfg) QemuParams(config *Config) []string { + var fwcfgParams []string + var qemuParams []string + + if config.FwCfg.Name != "" { + fwcfgParams = append(fwcfgParams, fmt.Sprintf("name=%q", config.FwCfg.Name)) + + if config.FwCfg.File != "" { + fwcfgParams = append(fwcfgParams, fmt.Sprintf(",file=%q", config.FwCfg.File)) + } + + if config.FwCfg.Str != "" { + fwcfgParams = append(fwcfgParams, fmt.Sprintf(",string=%q", config.FwCfg.Str)) + } + } + + qemuParams = append(qemuParams, "-fw_cfg") + qemuParams = append(qemuParams, strings.Join(fwcfgParams, "")) + + return qemuParams +} + // Knobs regroups a set of qemu boolean settings type Knobs struct { // NoUserConfig prevents qemu from loading user config files. @@ -2236,6 +2284,9 @@ type Config struct { // fds is a list of open file descriptors to be passed to the spawned qemu process fds []*os.File + // FwCfg is the -fw_cfg parameter + FwCfg FwCfg + IOThreads []IOThread // PidFile is the -pidfile parameter @@ -2568,6 +2619,19 @@ func (config *Config) appendLogFile() { } } +func (config *Config) appendFwCfg(logger QMPLog) { + if logger == nil { + logger = qmpNullLogger{} + } + + if !config.FwCfg.Valid() { + logger.Errorf("fw_cfg is not valid: %+v", config.FwCfg) + return + } + + config.qemuParams = append(config.qemuParams, config.FwCfg.QemuParams(config)...) +} + // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. @@ -2595,6 +2659,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendIncoming() config.appendPidFile() config.appendLogFile() + config.appendFwCfg(logger) if err := config.appendCPUs(); err != nil { return "", err diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 30dedf9727..6928ede4bc 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -44,6 +44,9 @@ func testConfigAppend(config *Config, structure interface{}, expected string, t case Machine: config.Machine = s config.appendMachine() + case FwCfg: + config.FwCfg = s + config.appendFwCfg(nil) case Device: config.Devices = []Device{s} @@ -1141,6 +1144,26 @@ func TestBadCPUs(t *testing.T) { } } +func TestBadFwcfg(t *testing.T) { + c := &Config{} + c.appendFwCfg(nil) + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } + + c = &Config{ + FwCfg: FwCfg{ + Name: "name=opt/com.mycompany/blob", + File: "./my_blob.bin", + Str: "foo", + }, + } + c.appendFwCfg(nil) + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + var ( vIommuString = "-device intel-iommu,intremap=on,device-iotlb=on,caching-mode=on" vIommuNoCacheString = "-device intel-iommu,intremap=on,device-iotlb=on,caching-mode=off" @@ -1164,3 +1187,19 @@ func TestIommu(t *testing.T) { testAppend(iommu, vIommuNoCacheString, t) } + +func TestAppendFwcfg(t *testing.T) { + fwcfgString := "-fw_cfg name=\"opt/com.mycompany/blob\",file=\"./my_blob.bin\"" + fwcfg := FwCfg{ + Name: "opt/com.mycompany/blob", + File: "./my_blob.bin", + } + testAppend(fwcfg, fwcfgString, t) + + fwcfgString = "-fw_cfg name=\"opt/com.mycompany/blob\",string=\"foo\"" + fwcfg = FwCfg{ + Name: "opt/com.mycompany/blob", + Str: "foo", + } + testAppend(fwcfg, fwcfgString, t) +} From cb0d339141c39fcc92bbf37bd0bc181499cd088f Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 9 Oct 2020 07:50:12 -0500 Subject: [PATCH 223/264] contributors: remove CONTRIBUTORS.md file Remove CONTRIBUTORS.md file since, this repo is now part of the kata-containers organization, the other repos don't have this file and we are not willing to maintain (update) it. Signed-off-by: Julio Montes --- CONTRIBUTORS.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index 7a822adbe0..0000000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,22 +0,0 @@ -This file is a partial list of contributors to the Virtual Machine -Manager for Go project. To see the full list of contributors, see the -revision history in source control. - -Contributors who wish to be recognized in this file should add -themselves (or their employer, as appropriate). - -- afrosi@de.ibm.com -- archana.m.shinde@intel.com -- caoruidong@huawei.com -- clare.chenhui@huawei.com -- eric.ernst@intel.com -- james.o.hunt@intel.com -- jose.carlos.venegas.munoz@intel.com -- julio.montes@intel.com -- manohar.r.castelino@intel.com -- mark.d.ryan@intel.com -- robert.bradford@intel.com -- sameo@linux.intel.com -- sebastien.boeuf@intel.com -- teawater@hyper.sh -- xinda.zhao@intel.com From 8cb8b24c05366f5f04137eefb65c789f6da92252 Mon Sep 17 00:00:00 2001 From: Salvatore Mazzarino Date: Mon, 12 Oct 2020 12:29:05 +0200 Subject: [PATCH 224/264] Make fw_cfg a slice Signed-off-by: Salvatore Mazzarino --- qemu/qemu.go | 36 ++++++++++++++++++++---------------- qemu/qemu_test.go | 16 +++++++++------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 05cf9e16b9..539c788925 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2133,21 +2133,23 @@ func (fwcfg FwCfg) QemuParams(config *Config) []string { var fwcfgParams []string var qemuParams []string - if config.FwCfg.Name != "" { - fwcfgParams = append(fwcfgParams, fmt.Sprintf("name=%q", config.FwCfg.Name)) + for _, f := range config.FwCfg { + if f.Name != "" { + fwcfgParams = append(fwcfgParams, fmt.Sprintf("name=%s", f.Name)) - if config.FwCfg.File != "" { - fwcfgParams = append(fwcfgParams, fmt.Sprintf(",file=%q", config.FwCfg.File)) + if f.File != "" { + fwcfgParams = append(fwcfgParams, fmt.Sprintf(",file=%s", f.File)) + } + + if f.Str != "" { + fwcfgParams = append(fwcfgParams, fmt.Sprintf(",string=%s", f.Str)) + } } - if config.FwCfg.Str != "" { - fwcfgParams = append(fwcfgParams, fmt.Sprintf(",string=%q", config.FwCfg.Str)) - } + qemuParams = append(qemuParams, "-fw_cfg") + qemuParams = append(qemuParams, strings.Join(fwcfgParams, "")) } - qemuParams = append(qemuParams, "-fw_cfg") - qemuParams = append(qemuParams, strings.Join(fwcfgParams, "")) - return qemuParams } @@ -2285,7 +2287,7 @@ type Config struct { fds []*os.File // FwCfg is the -fw_cfg parameter - FwCfg FwCfg + FwCfg []FwCfg IOThreads []IOThread @@ -2624,12 +2626,14 @@ func (config *Config) appendFwCfg(logger QMPLog) { logger = qmpNullLogger{} } - if !config.FwCfg.Valid() { - logger.Errorf("fw_cfg is not valid: %+v", config.FwCfg) - return - } + for _, f := range config.FwCfg { + if !f.Valid() { + logger.Errorf("fw_cfg is not valid: %+v", config.FwCfg) + continue + } - config.qemuParams = append(config.qemuParams, config.FwCfg.QemuParams(config)...) + config.qemuParams = append(config.qemuParams, f.QemuParams(config)...) + } } // LaunchQemu can be used to launch a new qemu instance. diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 6928ede4bc..f0cf812602 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -45,7 +45,7 @@ func testConfigAppend(config *Config, structure interface{}, expected string, t config.Machine = s config.appendMachine() case FwCfg: - config.FwCfg = s + config.FwCfg = []FwCfg{s} config.appendFwCfg(nil) case Device: @@ -1152,10 +1152,12 @@ func TestBadFwcfg(t *testing.T) { } c = &Config{ - FwCfg: FwCfg{ - Name: "name=opt/com.mycompany/blob", - File: "./my_blob.bin", - Str: "foo", + FwCfg: []FwCfg{ + { + Name: "name=opt/com.mycompany/blob", + File: "./my_blob.bin", + Str: "foo", + }, }, } c.appendFwCfg(nil) @@ -1189,14 +1191,14 @@ func TestIommu(t *testing.T) { } func TestAppendFwcfg(t *testing.T) { - fwcfgString := "-fw_cfg name=\"opt/com.mycompany/blob\",file=\"./my_blob.bin\"" + fwcfgString := "-fw_cfg name=opt/com.mycompany/blob,file=./my_blob.bin" fwcfg := FwCfg{ Name: "opt/com.mycompany/blob", File: "./my_blob.bin", } testAppend(fwcfg, fwcfgString, t) - fwcfgString = "-fw_cfg name=\"opt/com.mycompany/blob\",string=\"foo\"" + fwcfgString = "-fw_cfg name=opt/com.mycompany/blob,string=foo" fwcfg = FwCfg{ Name: "opt/com.mycompany/blob", Str: "foo", From 43d774d27b67690e7f7350a4e0da3cb41c7840f5 Mon Sep 17 00:00:00 2001 From: Salvatore Mazzarino Date: Mon, 12 Oct 2020 17:35:06 +0200 Subject: [PATCH 225/264] Add serial to blk device Signed-off-by: Salvatore Mazzarino --- qemu/qemu.go | 2 ++ qemu/qemu_arch_base_test.go | 2 +- qemu/qemu_s390x_test.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 05cf9e16b9..5b8dee85a0 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1084,6 +1084,8 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",share-rw=on")) } + deviceParams = append(deviceParams, fmt.Sprintf(",serial=%s", blkdev.ID)) + 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)) diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 58de5d1eaa..a5f26b8233 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -36,7 +36,7 @@ var ( deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" - deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" + deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on,serial=hd0 -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" romfile = "efi-virtio.rom" diff --git a/qemu/qemu_s390x_test.go b/qemu/qemu_s390x_test.go index afc1afed30..4b99f7ea56 100644 --- a/qemu/qemu_s390x_test.go +++ b/qemu/qemu_s390x_test.go @@ -32,7 +32,7 @@ var ( deviceVFIOString = "-device vfio-ccw,host=02:10.0,devno=" + DevNo deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo,devno=" + DevNo deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1,devno=" + DevNo - deviceBlockString = "-device virtio-blk-ccw,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + ",share-rw=on -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" + deviceBlockString = "-device virtio-blk-ccw,drive=hd0,scsi=off,config-wce=off,devno=" + DevNo + ",share-rw=on,serial=hd0 -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" romfile = "" From d7836877e98957dcf1f8338ab2434e31bdcf5b51 Mon Sep 17 00:00:00 2001 From: bin liu Date: Mon, 19 Oct 2020 16:59:37 +0800 Subject: [PATCH 226/264] qemu: add pvpanic device to get GUEST_PANICKED event Listening to the events channel from QEMU and a guest panic event issued, then we can get the event and do some work for the special event. Fixes: #152 Signed-off-by: bin liu --- qemu/qemu.go | 18 ++++++++++++++++++ qemu/qemu_test.go | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 05cf9e16b9..99a7677f2b 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1118,6 +1118,24 @@ func (blkdev BlockDevice) deviceName(config *Config) string { return string(blkdev.Driver) } +// PVPanicDevice represents a qemu pvpanic device. +type PVPanicDevice struct { + NoShutdown bool +} + +// Valid always returns true for pvpanic device +func (dev PVPanicDevice) Valid() bool { + return true +} + +// QemuParams returns the qemu parameters built out of this serial device. +func (dev PVPanicDevice) QemuParams(config *Config) []string { + if dev.NoShutdown { + return []string{"-device", "pvpanic", "-no-shutdown"} + } + return []string{"-device", "pvpanic"} +} + // VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 6928ede4bc..cabc57cc63 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -1203,3 +1203,18 @@ func TestAppendFwcfg(t *testing.T) { } testAppend(fwcfg, fwcfgString, t) } + +func TestAppendPVPanicDevice(t *testing.T) { + testCases := []struct { + dev Device + out string + }{ + {nil, ""}, + {PVPanicDevice{}, "-device pvpanic"}, + {PVPanicDevice{NoShutdown: true}, "-device pvpanic -no-shutdown"}, + } + + for _, tc := range testCases { + testAppend(tc.dev, tc.out, t) + } +} From b8cd7059019012e3d31e56344e7b07f4e4b4134b Mon Sep 17 00:00:00 2001 From: bin liu Date: Mon, 19 Oct 2020 17:09:12 +0800 Subject: [PATCH 227/264] qmp: add dump-guest-memory support By adding `dump-guest-memory` command, user can get kernel memory dump when guest panic occurred. Fixes: #152 Signed-off-by: bin liu --- qemu/qmp.go | 13 ++++++++++++- qemu/qmp_test.go | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index fc3f1e3f3a..53ba105a8e 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -268,7 +268,7 @@ func (q *QMP) readLoop(fromVMCh chan<- []byte) { for scanner.Scan() { line := scanner.Bytes() if q.cfg.Logger.V(1) { - q.cfg.Logger.Infof("%s", string(line)) + q.cfg.Logger.Infof("read from QMP: %s", string(line)) } // Since []byte channel type transfer slice info(include slice underlying array pointer, len, cap) @@ -1654,3 +1654,14 @@ func (q *QMP) ExecQomGet(ctx context.Context, path, property string) (interface{ return response, nil } + +// ExecuteDumpGuestMemory dump guest memory to host +func (q *QMP) ExecuteDumpGuestMemory(ctx context.Context, protocol string, paging bool, format string) error { + args := map[string]interface{}{ + "protocol": protocol, + "paging": paging, + "format": format, + } + + return q.executeCommand(ctx, "dump-guest-memory", args, nil) +} diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 14bfd24921..60457453cb 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1794,3 +1794,22 @@ func TestExecQomGet(t *testing.T) { q.Shutdown() <-disconnectedCh } + +// Checks dump-guest-memory +func TestExecuteDumpGuestMemory(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("dump-guest-memory", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + + err := q.ExecuteDumpGuestMemory(context.Background(), "file:/tmp/dump.xxx.yyy", false, "elf") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + q.Shutdown() + <-disconnectedCh +} From 2079c15c26adc3a0b4b7fae53b32133ef92fe420 Mon Sep 17 00:00:00 2001 From: Edmond AK Dantes Date: Wed, 21 Oct 2020 20:25:57 +0800 Subject: [PATCH 228/264] qemu: enable "-pflash" flash image can store some critical data like firmware, enable it here. Fixes: #140 Signed-off-by: Edmond AK Dantes --- qemu/qemu.go | 11 +++++++++++ qemu/qemu_test.go | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 9b97aba590..567a316f40 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2300,6 +2300,9 @@ type Config struct { // Bios is the -bios parameter Bios string + // PFlash specifies the parallel flash images (-pflash parameter) + PFlash []string + // Incoming controls migration source preparation Incoming Incoming @@ -2490,6 +2493,13 @@ func (config *Config) appendGlobalParam() { } } +func (config *Config) appendPFlashParam() { + for _, p := range config.PFlash { + config.qemuParams = append(config.qemuParams, "-pflash") + config.qemuParams = append(config.qemuParams, p) + } +} + func (config *Config) appendVGA() { if config.VGA != "" { config.qemuParams = append(config.qemuParams, "-vga") @@ -2675,6 +2685,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendDevices() config.appendRTC() config.appendGlobalParam() + config.appendPFlashParam() config.appendVGA() config.appendKnobs() config.appendKernel() diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 67b8147257..5555ef1a7e 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "os" + "reflect" "strings" "testing" ) @@ -1026,6 +1027,25 @@ func TestBadGlobalParam(t *testing.T) { } } +func TestBadPFlash(t *testing.T) { + c := &Config{} + c.appendPFlashParam() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestValidPFlash(t *testing.T) { + c := &Config{} + c.PFlash = []string{"flash0", "flash1"} + c.appendPFlashParam() + expected := []string{"-pflash", "flash0", "-pflash", "flash1"} + ok := reflect.DeepEqual(expected, c.qemuParams) + if !ok { + t.Errorf("Expected %v, found %v", expected, c.qemuParams) + } +} + func TestBadVGA(t *testing.T) { c := &Config{} c.appendVGA() From 0592c825362780372ffa472a22054f6f0188da77 Mon Sep 17 00:00:00 2001 From: Jianyong Wu Date: Wed, 18 Nov 2020 17:17:35 +0800 Subject: [PATCH 229/264] qemu: add arm64 to support list of dimm dimm is supported on arm64, so add is to check list. Signed-off-by: Jianyong Wu Fixes: #155 --- qemu/qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 567a316f40..9cfe39cbd1 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -135,7 +135,7 @@ const ( func isDimmSupported(config *Config) bool { switch runtime.GOARCH { - case "amd64", "386", "ppc64le": + case "amd64", "386", "ppc64le", "arm64": if config != nil && config.Machine.Type == MachineTypeMicrovm { // microvm does not support NUMA return false From e2eb549fcdac358ec6d7e3e4dfdfc6c3ddf67f21 Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Mon, 11 Jan 2021 15:27:51 -0800 Subject: [PATCH 230/264] qmp: Add ro argument for block-device hotplug funcs We should allow users to specify if a block device should be hotplugged as read-only. Fixes: #157 Signed-off-by: Eric Ernst --- qemu/qmp.go | 13 +++++++------ qemu/qmp_test.go | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 53ba105a8e..73d2ec4cff 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -773,11 +773,12 @@ func (q *QMP) ExecuteQuit(ctx context.Context) error { return q.executeCommand(ctx, "quit", nil, nil) } -func (q *QMP) blockdevAddBaseArgs(device, blockdevID string) (map[string]interface{}, map[string]interface{}) { +func (q *QMP) blockdevAddBaseArgs(device, blockdevID string, ro bool) (map[string]interface{}, map[string]interface{}) { var args map[string]interface{} blockdevArgs := map[string]interface{}{ - "driver": "raw", + "driver": "raw", + "read-only": ro, "file": map[string]interface{}{ "driver": "file", "filename": device, @@ -801,8 +802,8 @@ func (q *QMP) blockdevAddBaseArgs(device, blockdevID string) (map[string]interfa // path of the device to add, e.g., /dev/rdb0, and blockdevID is an identifier // used to name the device. As this identifier will be passed directly to QMP, // it must obey QMP's naming rules, e,g., it must start with a letter. -func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) error { - args, _ := q.blockdevAddBaseArgs(device, blockdevID) +func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string, ro bool) error { + args, _ := q.blockdevAddBaseArgs(device, blockdevID, ro) return q.executeCommand(ctx, "blockdev-add", args, nil) } @@ -814,8 +815,8 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string) // direct denotes whether use of O_DIRECT (bypass the host page cache) // is enabled. noFlush denotes whether flush requests for the device are // ignored. -func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevID string, direct, noFlush bool) error { - args, blockdevArgs := q.blockdevAddBaseArgs(device, blockdevID) +func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevID string, direct, noFlush, ro bool) error { + args, blockdevArgs := q.blockdevAddBaseArgs(device, blockdevID, ro) if q.version.Major < 2 || (q.version.Major == 2 && q.version.Minor < 9) { return fmt.Errorf("versions of qemu (%d.%d) older than 2.9 do not support set cache-related options for block devices", diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 60457453cb..5a7233f723 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -408,7 +408,7 @@ func TestQMPBlockdevAdd(t *testing.T) { q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevAdd(context.Background(), "/dev/rbd0", - fmt.Sprintf("drive_%s", volumeUUID)) + fmt.Sprintf("drive_%s", volumeUUID), false) if err != nil { t.Fatalf("Unexpected error %v", err) } @@ -432,7 +432,7 @@ func TestQMPBlockdevAddWithCache(t *testing.T) { q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevAddWithCache(context.Background(), "/dev/rbd0", - fmt.Sprintf("drive_%s", volumeUUID), true, true) + fmt.Sprintf("drive_%s", volumeUUID), true, true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } From 0d47025d051354d4ba7c4990b34445d34a22aa39 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 24 Mar 2021 10:32:31 -0600 Subject: [PATCH 231/264] qemu: add support for device loaders Devices loaders can be used to load some firmwares. Signed-off-by: Julio Montes --- qemu/qemu.go | 37 +++++++++++++++++++++++++++++++++++++ qemu/qemu_test.go | 17 +++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 9cfe39cbd1..8d982cc63b 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -131,6 +131,9 @@ const ( // PCIeRootPort is a PCIe Root Port, the PCIe device should be hotplugged to this port. PCIeRootPort DeviceDriver = "pcie-root-port" + + // Loader is the Loader device driver. + Loader DeviceDriver = "loader" ) func isDimmSupported(config *Config) bool { @@ -1138,6 +1141,40 @@ func (dev PVPanicDevice) QemuParams(config *Config) []string { return []string{"-device", "pvpanic"} } +// LoaderDevice represents a qemu loader device. +type LoaderDevice struct { + File string + ID string +} + +// Valid returns true if there is a valid structure defined for LoaderDevice +func (dev LoaderDevice) Valid() bool { + if dev.File == "" { + return false + } + + if dev.ID == "" { + return false + } + + return true +} + +// QemuParams returns the qemu parameters built out of this loader device. +func (dev LoaderDevice) QemuParams(config *Config) []string { + var qemuParams []string + var devParams []string + + devParams = append(devParams, "loader") + devParams = append(devParams, fmt.Sprintf("file=%s", dev.File)) + devParams = append(devParams, fmt.Sprintf("id=%s", dev.ID)) + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(devParams, ",")) + + return qemuParams +} + // VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 5555ef1a7e..39f5c427b8 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -1240,3 +1240,20 @@ func TestAppendPVPanicDevice(t *testing.T) { testAppend(tc.dev, tc.out, t) } } + +func TestLoaderDevice(t *testing.T) { + testCases := []struct { + dev Device + out string + }{ + {nil, ""}, + {LoaderDevice{}, ""}, + {LoaderDevice{File: "f"}, ""}, + {LoaderDevice{ID: "id"}, ""}, + {LoaderDevice{File: "f", ID: "id"}, "-device loader,file=f,id=id"}, + } + + for _, tc := range testCases { + testAppend(tc.dev, tc.out, t) + } +} From 6213dea42ae459f5ca05126d4496ff83b380a95b Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 24 Mar 2021 10:54:05 -0600 Subject: [PATCH 232/264] qemu: support QEMU 6 Use `on` and `off` to enable or disable features, `no` prefix is deprecated Signed-off-by: Julio Montes --- qemu/examples_test.go | 2 +- qemu/qemu.go | 6 +++--- qemu/qemu_test.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qemu/examples_test.go b/qemu/examples_test.go index 014e9be3b5..1946aeef82 100644 --- a/qemu/examples_test.go +++ b/qemu/examples_test.go @@ -34,7 +34,7 @@ func Example() { // kvm params = append(params, "-enable-kvm", "-cpu", "host") // qmp socket - params = append(params, "-daemonize", "-qmp", "unix:/tmp/qmp-socket,server,nowait") + params = append(params, "-daemonize", "-qmp", "unix:/tmp/qmp-socket,server=on,wait=off") // resources params = append(params, "-m", "370", "-smp", "cpus=2") diff --git a/qemu/qemu.go b/qemu/qemu.go index 9cfe39cbd1..000fb142a8 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -552,7 +552,7 @@ func (cdev CharDevice) QemuParams(config *Config) []string { cdevParams = append(cdevParams, string(cdev.Backend)) cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) if cdev.Backend == Socket { - cdevParams = append(cdevParams, fmt.Sprintf(",path=%s,server,nowait", cdev.Path)) + cdevParams = append(cdevParams, fmt.Sprintf(",path=%s,server=on,wait=off", cdev.Path)) } else { cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path)) } @@ -2385,9 +2385,9 @@ func (config *Config) appendQMPSockets() { qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type)) qmpParams = append(qmpParams, q.Name) if q.Server { - qmpParams = append(qmpParams, ",server") + qmpParams = append(qmpParams, ",server=on") if q.NoWait { - qmpParams = append(qmpParams, ",nowait") + qmpParams = append(qmpParams, ",wait=off") } } diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 5555ef1a7e..f21a09b856 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -288,7 +288,7 @@ func TestAppendDeviceSerial(t *testing.T) { testAppend(sdev, deviceSerialString, t) } -var deviceSerialPortString = "-device virtserialport,chardev=char0,id=channel0,name=channel.0 -chardev socket,id=char0,path=/tmp/char.sock,server,nowait" +var deviceSerialPortString = "-device virtserialport,chardev=char0,id=channel0,name=channel.0 -chardev socket,id=char0,path=/tmp/char.sock,server=on,wait=off" func TestAppendDeviceSerialPort(t *testing.T) { chardev := CharDevice{ @@ -741,7 +741,7 @@ func TestFailToAppendCPUs(t *testing.T) { } } -var qmpSingleSocketServerString = "-qmp unix:cc-qmp,server,nowait" +var qmpSingleSocketServerString = "-qmp unix:cc-qmp,server=on,wait=off" var qmpSingleSocketString = "-qmp unix:cc-qmp" func TestAppendSingleQMPSocketServer(t *testing.T) { @@ -765,7 +765,7 @@ func TestAppendSingleQMPSocket(t *testing.T) { testAppend(qmp, qmpSingleSocketString, t) } -var qmpSocketServerString = "-qmp unix:cc-qmp-1,server,nowait -qmp unix:cc-qmp-2,server,nowait" +var qmpSocketServerString = "-qmp unix:cc-qmp-1,server=on,wait=off -qmp unix:cc-qmp-2,server=on,wait=off" func TestAppendQMPSocketServer(t *testing.T) { qmp := []QMPSocket{ From 4b136f3f1cc487bd75b2adebff2b06233e318fea Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Thu, 25 Feb 2021 16:49:10 +0100 Subject: [PATCH 233/264] qemu: Append memory backend for non-DIMM setups Some architectures and setups do not support DIMM/NUMA. However, they can still use memory backends, provided a memory backend of the same ID is specified under -machine. This was introduced in QEMU 5.0. Enable this functionality in appendMemoryKnobs. Fixes: #160 Signed-off-by: Jakob Naucke --- qemu/qemu.go | 12 ++++---- qemu/qemu_test.go | 75 +++++++++++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 9cfe39cbd1..a1d05a9093 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2528,9 +2528,6 @@ func (config *Config) appendMemoryKnobs() { if config.Memory.Size == "" { return } - if !isDimmSupported(config) { - return - } var objMemParam, numaMemParam string dimmName := "dimm1" if config.Knobs.HugePages { @@ -2553,8 +2550,13 @@ func (config *Config) appendMemoryKnobs() { config.qemuParams = append(config.qemuParams, "-object") config.qemuParams = append(config.qemuParams, objMemParam) - config.qemuParams = append(config.qemuParams, "-numa") - config.qemuParams = append(config.qemuParams, numaMemParam) + if isDimmSupported(config) { + config.qemuParams = append(config.qemuParams, "-numa") + config.qemuParams = append(config.qemuParams, numaMemParam) + } else { + config.qemuParams = append(config.qemuParams, "-machine") + config.qemuParams = append(config.qemuParams, "memory-backend="+dimmName) + } } func (config *Config) appendKnobs() { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 5555ef1a7e..69caa1c207 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -542,10 +542,6 @@ func TestAppendKnobsAllFalse(t *testing.T) { } func TestAppendMemoryHugePages(t *testing.T) { - if !isDimmSupported(nil) { - t.Skip("Dimm not supported") - } - conf := &Config{ Memory: Memory{ Size: "1G", @@ -563,17 +559,22 @@ func TestAppendMemoryHugePages(t *testing.T) { FileBackedMem: true, MemShared: true, } - knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=/dev/hugepages,share=on,prealloc=on -numa node,memdev=dimm1" + objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=/dev/hugepages,share=on,prealloc=on" + numaMemString := "-numa node,memdev=dimm1" + memBackendString := "-machine memory-backend=dimm1" mlockFalseString := "-realtime mlock=off" + knobsString := objMemString + " " + if isDimmSupported(nil) { + knobsString += numaMemString + } else { + knobsString += memBackendString + } + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } func TestAppendMemoryMemPrealloc(t *testing.T) { - if !isDimmSupported(nil) { - t.Skip("Dimm not supported") - } - conf := &Config{ Memory: Memory{ Size: "1G", @@ -589,17 +590,22 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { MemPrealloc: true, MemShared: true, } - knobsString := "-object memory-backend-ram,id=dimm1,size=1G,share=on,prealloc=on -numa node,memdev=dimm1" + objMemString := "-object memory-backend-ram,id=dimm1,size=1G,share=on,prealloc=on" + numaMemString := "-numa node,memdev=dimm1" + memBackendString := "-machine memory-backend=dimm1" mlockFalseString := "-realtime mlock=off" + knobsString := objMemString + " " + if isDimmSupported(nil) { + knobsString += numaMemString + } else { + knobsString += memBackendString + } + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } func TestAppendMemoryMemShared(t *testing.T) { - if !isDimmSupported(nil) { - t.Skip("Dimm not supported") - } - conf := &Config{ Memory: Memory{ Size: "1G", @@ -615,17 +621,22 @@ func TestAppendMemoryMemShared(t *testing.T) { FileBackedMem: true, MemShared: true, } - knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on -numa node,memdev=dimm1" + objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on" + numaMemString := "-numa node,memdev=dimm1" + memBackendString := "-machine memory-backend=dimm1" mlockFalseString := "-realtime mlock=off" + knobsString := objMemString + " " + if isDimmSupported(nil) { + knobsString += numaMemString + } else { + knobsString += memBackendString + } + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } func TestAppendMemoryFileBackedMem(t *testing.T) { - if !isDimmSupported(nil) { - t.Skip("Dimm not supported") - } - conf := &Config{ Memory: Memory{ Size: "1G", @@ -641,17 +652,22 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { FileBackedMem: true, MemShared: false, } - knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar -numa node,memdev=dimm1" + objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar" + numaMemString := "-numa node,memdev=dimm1" + memBackendString := "-machine memory-backend=dimm1" mlockFalseString := "-realtime mlock=off" + knobsString := objMemString + " " + if isDimmSupported(nil) { + knobsString += numaMemString + } else { + knobsString += memBackendString + } + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { - if !isDimmSupported(nil) { - t.Skip("Dimm not supported") - } - conf := &Config{ Memory: Memory{ Size: "1G", @@ -668,9 +684,18 @@ func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { MemShared: true, MemPrealloc: true, } - knobsString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on,prealloc=on -numa node,memdev=dimm1" + objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on,prealloc=on" + numaMemString := "-numa node,memdev=dimm1" + memBackendString := "-machine memory-backend=dimm1" mlockFalseString := "-realtime mlock=off" + knobsString := objMemString + " " + if isDimmSupported(nil) { + knobsString += numaMemString + } else { + knobsString += memBackendString + } + testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) } From 3141894033970c96d70a3d64cf37f9a03223a0f9 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 30 Mar 2021 16:17:46 -0600 Subject: [PATCH 234/264] qemu: add support for tdx-guest object support tdx-guest guest objects Signed-off-by: Julio Montes --- qemu/qemu.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 53ac898407..3e6550f139 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -227,6 +227,9 @@ type ObjectType string const ( // MemoryBackendFile represents a guest memory mapped file. MemoryBackendFile ObjectType = "memory-backend-file" + + // TDXGuest represents a TDX object + TDXGuest ObjectType = "tdx-guest" ) // Object is a qemu object representation. @@ -249,6 +252,12 @@ type Object struct { // Size is the object size in bytes Size uint64 + + // Debug this is a debug object + Debug bool + + // File is the device file + File string } // Valid returns true if the Object structure is valid and complete. @@ -259,6 +268,11 @@ func (object Object) Valid() bool { return false } + case TDXGuest: + if object.ID == "" || object.File == "" || object.DeviceID == "" { + return false + } + default: return false } @@ -283,6 +297,13 @@ func (object Object) QemuParams(config *Config) []string { objectParams = append(objectParams, fmt.Sprintf(",size=%d", object.Size)) deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", object.ID)) + case TDXGuest: + objectParams = append(objectParams, string(object.Type)) + objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + if object.Debug { + objectParams = append(objectParams, ",debug=on") + } + deviceParams = append(deviceParams, fmt.Sprintf(",file=%s", object.File)) } qemuParams = append(qemuParams, "-device") From b3eac95b28b56f9c91787f0e232729568e8129fb Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Thu, 1 Apr 2021 08:20:14 -0700 Subject: [PATCH 235/264] qmp: remove frequent, chatty log In Kata, we are getting a *lot* of logs at runtime from QMP, in particular `read from QMP: xxxx` Ideally we'd set this to only be visible for trace, but I did not see this working when adding a V(7) check around these prints. To avoid filling journal with info that isn't useful, let's drop. Fixes: #165 Signed-off-by: Eric Ernst --- qemu/qmp.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 73d2ec4cff..97e924559c 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -267,10 +267,6 @@ func (q *QMP) readLoop(fromVMCh chan<- []byte) { for scanner.Scan() { line := scanner.Bytes() - if q.cfg.Logger.V(1) { - q.cfg.Logger.Infof("read from QMP: %s", string(line)) - } - // Since []byte channel type transfer slice info(include slice underlying array pointer, len, cap) // between channel sender and receiver. scanner.Bytes() returned slice's underlying array // may point to data that will be overwritten by a subsequent call to Scan(reference from: @@ -442,7 +438,6 @@ func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { } cmdQueue.Remove(cmdEl) } - q.cfg.Logger.Infof("%s", string(encodedCmd)) encodedCmd = append(encodedCmd, '\n') if unixConn, ok := q.conn.(*net.UnixConn); ok && len(cmd.oob) > 0 { _, _, err = unixConn.WriteMsgUnix(encodedCmd, cmd.oob, nil) From 511cf58b0cf4f1ffe16ca7943c9903c52e7ba7ca Mon Sep 17 00:00:00 2001 From: Michael Qiu Date: Sun, 18 Apr 2021 04:59:33 -0400 Subject: [PATCH 236/264] Fix qemu commandline issue with empty romfile Currently, if romfile field is empty, the commandline will shows like below: -device driver=virtio-net-pci,...,mq=on,vectors=4,romfile= This does not make sense, just remove this field in commandline Add unittest support. Signed-off-by: Michael Qiu --- qemu/qemu.go | 26 ++++++++++++++------------ qemu/qemu_arch_base_test.go | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 3e6550f139..f05932af8d 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -429,7 +429,7 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { } deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) - if fsdev.Transport.isVirtioPCI(config) { + if fsdev.Transport.isVirtioPCI(config) && fsdev.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) } if fsdev.Transport.isVirtioCCW(config) { @@ -562,7 +562,7 @@ func (cdev CharDevice) QemuParams(config *Config) []string { if cdev.Name != "" { deviceParams = append(deviceParams, fmt.Sprintf(",name=%s", cdev.Name)) } - if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioPCI(config) { + if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioPCI(config) && cdev.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", cdev.ROMFile)) } @@ -832,7 +832,7 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { deviceParams = append(deviceParams, netdev.mqParameter(config)) } - if netdev.Transport.isVirtioPCI(config) { + if netdev.Transport.isVirtioPCI(config) && netdev.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", netdev.ROMFile)) } @@ -965,7 +965,7 @@ func (dev SerialDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) - if dev.Transport.isVirtioPCI(config) { + if dev.Transport.isVirtioPCI(config) && dev.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) if dev.Driver == VirtioSerial && dev.MaxPorts != 0 { deviceParams = append(deviceParams, fmt.Sprintf(",max_ports=%d", dev.MaxPorts)) @@ -1096,7 +1096,7 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, ",config-wce=off") } - if blkdev.Transport.isVirtioPCI(config) { + if blkdev.Transport.isVirtioPCI(config) && blkdev.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", blkdev.ROMFile)) } @@ -1341,7 +1341,7 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { return nil } - if vhostuserDev.Transport.isVirtioPCI(config) { + if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } @@ -1531,7 +1531,9 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { if vfioDev.DeviceID != "" { deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-device-id=%s", vfioDev.DeviceID)) } - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) + if vfioDev.ROMFile != "" { + deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) + } } if vfioDev.Bus != "" { @@ -1616,7 +1618,7 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string { if scsiCon.IOThread != "" { devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) } - if scsiCon.Transport.isVirtioPCI(config) { + if scsiCon.Transport.isVirtioPCI(config) && scsiCon.ROMFile != "" { devParams = append(devParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) } @@ -1722,7 +1724,7 @@ func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { } var transport VirtioTransport - if transport.isVirtioPCI(config) { + if transport.isVirtioPCI(config) && bridgeDev.ROMFile != "" { deviceParam = append(deviceParam, fmt.Sprintf(",romfile=%s", bridgeDev.ROMFile)) } @@ -1801,7 +1803,7 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID)) - if vsock.Transport.isVirtioPCI(config) { + if vsock.Transport.isVirtioPCI(config) && vsock.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vsock.ROMFile)) } @@ -1874,7 +1876,7 @@ func (v RngDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, v.deviceName(config)) deviceParams = append(deviceParams, "rng="+v.ID) - if v.Transport.isVirtioPCI(config) { + if v.Transport.isVirtioPCI(config) && v.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", v.ROMFile)) } @@ -1951,7 +1953,7 @@ func (b BalloonDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, "id="+b.ID) } - if b.Transport.isVirtioPCI(config) { + if b.Transport.isVirtioPCI(config) && b.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index a5f26b8233..0bc34004e3 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -30,7 +30,7 @@ var ( deviceVFIOString = "-device vfio-pci,host=02:10.0,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678,romfile=efi-virtio.rom" devicePCIeRootPortSimpleString = "-device pcie-root-port,id=rp1,bus=pcie.0,chassis=0x00,slot=0x00,multifunction=off" devicePCIeRootPortFullString = "-device pcie-root-port,id=rp2,bus=pcie.0,chassis=0x0,slot=0x1,addr=0x2,multifunction=on,bus-reserve=0x3,pref64-reserve=16G,mem-reserve=1G,io-reserve=512M,romfile=efi-virtio.rom" - deviceVFIOPCIeSimpleString = "-device vfio-pci,host=02:00.0,romfile=,bus=rp0" + deviceVFIOPCIeSimpleString = "-device vfio-pci,host=02:00.0,bus=rp0" deviceVFIOPCIeFullString = "-device vfio-pci,host=02:00.0,x-pci-vendor-id=0x10de,x-pci-device-id=0x15f8,romfile=efi-virtio.rom,bus=rp1" deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,disable-modern=false,romfile=efi-virtio.rom" deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" From 3eaeda7f6df73558d4dc343b712d8bba87fecf7e Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Tue, 27 Apr 2021 17:48:17 +0200 Subject: [PATCH 237/264] qemu: Refactor vhostuserDev.QemuParams by splitting out the respective functionality to QemuNetParams, QemuSCSIParams, QemuBlkParams, and QemuFSParams. This allows adding functionality to these functions without going beyond the cyclomatic complexity of 15 mandated by the lint checks. Signed-off-by: Jakob Naucke --- qemu/qemu.go | 193 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 126 insertions(+), 67 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f05932af8d..adfaa4987b 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1275,90 +1275,149 @@ func (vhostuserDev VhostUserDevice) Valid() bool { return true } -// QemuParams returns the qemu parameters built out of this vhostuser device. -func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { +// QemuNetParams builds QEMU netdev and device parameters for a VhostUserNet device +func (vhostuserDev VhostUserDevice) QemuNetParams(config *Config) []string { var qemuParams []string - var charParams []string var netParams []string var devParams []string - var driver string - charParams = append(charParams, "socket") - charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) - charParams = append(charParams, fmt.Sprintf("path=%s", vhostuserDev.SocketPath)) - - switch vhostuserDev.VhostUserType { - // if network based vhost device: - case VhostUserNet: - driver = vhostuserDev.deviceName(config) - if driver == "" { - return nil - } - - netParams = append(netParams, "type=vhost-user") - netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) - netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) - netParams = append(netParams, "vhostforce") - - devParams = append(devParams, driver) - devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) - devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) - case VhostUserSCSI: - driver = vhostuserDev.deviceName(config) - if driver == "" { - return nil - } - - devParams = append(devParams, driver) - devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) - devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) - case VhostUserBlk: - driver = vhostuserDev.deviceName(config) - if driver == "" { - return nil - } - - devParams = append(devParams, driver) - devParams = append(devParams, "logical_block_size=4096") - devParams = append(devParams, "size=512M") - devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) - case VhostUserFS: - driver = vhostuserDev.deviceName(config) - if driver == "" { - return nil - } - - devParams = append(devParams, driver) - devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) - devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) - if vhostuserDev.CacheSize != 0 { - devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) - } - if vhostuserDev.SharedVersions { - devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") - } - default: + driver := vhostuserDev.deviceName(config) + if driver == "" { return nil } + netParams = append(netParams, "type=vhost-user") + netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) + netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + netParams = append(netParams, "vhostforce") + + devParams = append(devParams, driver) + devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) + devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) + if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } - qemuParams = append(qemuParams, "-chardev") - qemuParams = append(qemuParams, strings.Join(charParams, ",")) - - // if network based vhost device: - if vhostuserDev.VhostUserType == VhostUserNet { - qemuParams = append(qemuParams, "-netdev") - qemuParams = append(qemuParams, strings.Join(netParams, ",")) - } + qemuParams = append(qemuParams, "-netdev") + qemuParams = append(qemuParams, strings.Join(netParams, ",")) qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) return qemuParams } +// QemuSCSIParams builds QEMU device parameters for a VhostUserSCSI device +func (vhostuserDev VhostUserDevice) QemuSCSIParams(config *Config) []string { + var qemuParams []string + var devParams []string + + driver := vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + + devParams = append(devParams, driver) + devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) + devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + + if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { + devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(devParams, ",")) + + return qemuParams +} + +// QemuBlkParams builds QEMU device parameters for a VhostUserBlk device +func (vhostuserDev VhostUserDevice) QemuBlkParams(config *Config) []string { + var qemuParams []string + var devParams []string + + driver := vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + + devParams = append(devParams, driver) + devParams = append(devParams, "logical_block_size=4096") + devParams = append(devParams, "size=512M") + devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + + if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { + devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(devParams, ",")) + + return qemuParams +} + +// QemuFSParams builds QEMU device parameters for a VhostUserFS device +func (vhostuserDev VhostUserDevice) QemuFSParams(config *Config) []string { + var qemuParams []string + var devParams []string + + driver := vhostuserDev.deviceName(config) + if driver == "" { + return nil + } + + devParams = append(devParams, driver) + devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) + if vhostuserDev.CacheSize != 0 { + devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) + } + if vhostuserDev.SharedVersions { + devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") + } + if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { + devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + } + + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(devParams, ",")) + + return qemuParams +} + +// QemuParams returns the qemu parameters built out of this vhostuser device. +func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { + var qemuParams []string + var charParams []string + var devParams []string + + charParams = append(charParams, "socket") + charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) + charParams = append(charParams, fmt.Sprintf("path=%s", vhostuserDev.SocketPath)) + + qemuParams = append(qemuParams, "-chardev") + qemuParams = append(qemuParams, strings.Join(charParams, ",")) + + switch vhostuserDev.VhostUserType { + case VhostUserNet: + devParams = vhostuserDev.QemuNetParams(config) + case VhostUserSCSI: + devParams = vhostuserDev.QemuSCSIParams(config) + case VhostUserBlk: + devParams = vhostuserDev.QemuBlkParams(config) + case VhostUserFS: + devParams = vhostuserDev.QemuFSParams(config) + default: + return nil + } + + if devParams != nil { + return append(qemuParams, devParams...) + } + + return nil +} + // deviceName returns the QEMU device name for the current combination of // driver and transport. func (vhostuserDev VhostUserDevice) deviceName(config *Config) string { From abd3c7ea036dd329817fabd5e49c1dd87d978c1d Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Tue, 27 Apr 2021 17:53:42 +0200 Subject: [PATCH 238/264] qemu: VhostUserDevice CCW device numbers Add CCW (s390x) device numbers to VhostUserDevices, as is with other device types. Add them to VhostUserFS devices (the only type currently supported on s390x) when building QEMU parameters. Fixes: #170 Signed-off-by: Jakob Naucke --- qemu/qemu.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index adfaa4987b..61ab2cc20b 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1211,6 +1211,9 @@ type VhostUserDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + // DevNo identifies the CCW device for s390x. + DevNo string + // Transport is the virtio transport for this device. Transport VirtioTransport } @@ -1375,6 +1378,9 @@ func (vhostuserDev VhostUserDevice) QemuFSParams(config *Config) []string { if vhostuserDev.SharedVersions { devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") } + if vhostuserDev.Transport.isVirtioCCW(config) { + devParams = append(devParams, fmt.Sprintf("devno=%s", vhostuserDev.DevNo)) + } if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } From a6cec2d38cca916e98c218a037672ab0cbda16cc Mon Sep 17 00:00:00 2001 From: Sandeep Gupta Date: Tue, 4 May 2021 08:53:16 -0400 Subject: [PATCH 239/264] qemu: add support for SevGuest object Signed-off-by: Jim Cadden --- qemu/qemu.go | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 61ab2cc20b..bd9abd6e4f 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -230,6 +230,9 @@ const ( // TDXGuest represents a TDX object TDXGuest ObjectType = "tdx-guest" + + // SEVGuest represents an SEV guest object + SEVGuest ObjectType = "sev-guest" ) // Object is a qemu object representation. @@ -258,6 +261,14 @@ type Object struct { // File is the device file File string + + // CBitPos is the location of the C-bit in a guest page table entry + // This is only relevant for sev-guest objects + CBitPos uint32 + + // ReducedPhysBits is the reduction in the guest physical address space + // This is only relevant for sev-guest objects + ReducedPhysBits uint32 } // Valid returns true if the Object structure is valid and complete. @@ -272,6 +283,10 @@ func (object Object) Valid() bool { if object.ID == "" || object.File == "" || object.DeviceID == "" { return false } + case SEVGuest: + if object.ID == "" || object.File == "" || object.CBitPos == 0 || object.ReducedPhysBits == 0 { + return false + } default: return false @@ -284,11 +299,9 @@ func (object Object) Valid() bool { func (object Object) QemuParams(config *Config) []string { var objectParams []string var deviceParams []string + var driveParams []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)) @@ -296,6 +309,8 @@ func (object Object) QemuParams(config *Config) []string { objectParams = append(objectParams, fmt.Sprintf(",mem-path=%s", object.MemPath)) objectParams = append(objectParams, fmt.Sprintf(",size=%d", object.Size)) + deviceParams = append(deviceParams, string(object.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", object.ID)) case TDXGuest: objectParams = append(objectParams, string(object.Type)) @@ -303,14 +318,33 @@ func (object Object) QemuParams(config *Config) []string { if object.Debug { objectParams = append(objectParams, ",debug=on") } + deviceParams = append(deviceParams, string(object.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) deviceParams = append(deviceParams, fmt.Sprintf(",file=%s", object.File)) + case SEVGuest: + objectParams = append(objectParams, string(object.Type)) + objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + objectParams = append(objectParams, fmt.Sprintf(",cbitpos=%d", object.CBitPos)) + objectParams = append(objectParams, fmt.Sprintf(",reduced-phys-bits=%d", object.ReducedPhysBits)) + + driveParams = append(driveParams, "if=pflash,format=raw,readonly=on") + driveParams = append(driveParams, fmt.Sprintf(",file=%s", object.File)) } - qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + if len(deviceParams) > 0 { + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + } - qemuParams = append(qemuParams, "-object") - qemuParams = append(qemuParams, strings.Join(objectParams, "")) + if len(objectParams) > 0 { + qemuParams = append(qemuParams, "-object") + qemuParams = append(qemuParams, strings.Join(objectParams, "")) + } + + if len(driveParams) > 0 { + qemuParams = append(qemuParams, "-drive") + qemuParams = append(qemuParams, strings.Join(driveParams, "")) + } return qemuParams } From 7a367dc0a8df1d73ba3e41cbbc7bf8c108cf6081 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Wed, 28 Apr 2021 15:50:36 +0200 Subject: [PATCH 240/264] qemu: Simplify (Object).Valid() so that more object types can be added without going over cyclomatic complexity limits Signed-off-by: Jakob Naucke --- qemu/qemu.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index bd9abd6e4f..b70d65f971 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -275,24 +275,14 @@ type Object struct { func (object Object) Valid() bool { switch object.Type { case MemoryBackendFile: - if object.ID == "" || object.MemPath == "" || object.Size == 0 { - return false - } - + return object.ID != "" && object.MemPath != "" && object.Size != 0 case TDXGuest: - if object.ID == "" || object.File == "" || object.DeviceID == "" { - return false - } + return object.ID != "" && object.File != "" && object.DeviceID != "" case SEVGuest: - if object.ID == "" || object.File == "" || object.CBitPos == 0 || object.ReducedPhysBits == 0 { - return false - } - + return object.ID != "" && object.File != "" && object.CBitPos != 0 && object.ReducedPhysBits != 0 default: return false } - - return true } // QemuParams returns the qemu parameters built out of this Object device. From 03b55ea51dca2344ed2171640e87f6deccb7c754 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Wed, 28 Apr 2021 15:53:47 +0200 Subject: [PATCH 241/264] qemu: Add support for Secure Execution Secure Execution, also known as Protected Virtualization in QEMU, is a confidential computing technology for s390x (IBM Z & LinuxONE). Allow the respective object. Fixes: #172 Signed-off-by: Jakob Naucke --- qemu/qemu.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index b70d65f971..e01bf93383 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -233,6 +233,9 @@ const ( // SEVGuest represents an SEV guest object SEVGuest ObjectType = "sev-guest" + + // SecExecGuest represents an s390x Secure Execution (Protected Virtualization in QEMU) object + SecExecGuest ObjectType = "s390-pv-guest" ) // Object is a qemu object representation. @@ -280,6 +283,8 @@ func (object Object) Valid() bool { return object.ID != "" && object.File != "" && object.DeviceID != "" case SEVGuest: return object.ID != "" && object.File != "" && object.CBitPos != 0 && object.ReducedPhysBits != 0 + case SecExecGuest: + return object.ID != "" default: return false } @@ -319,6 +324,9 @@ func (object Object) QemuParams(config *Config) []string { driveParams = append(driveParams, "if=pflash,format=raw,readonly=on") driveParams = append(driveParams, fmt.Sprintf(",file=%s", object.File)) + case SecExecGuest: + objectParams = append(objectParams, string(object.Type)) + objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) } if len(deviceParams) > 0 { From c135681d9a85889ca9379058778b7da18c4b22c8 Mon Sep 17 00:00:00 2001 From: Amulyam24 Date: Wed, 19 May 2021 16:39:14 +0530 Subject: [PATCH 242/264] qemu: Add support for PEF Adding the support for Protected Execution Facility(PEF) is which is the confidential computing technology on ppc64le. Fixes: #174 Signed-off-by: Amulyam24 --- qemu/qemu.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index e01bf93383..1eddc79e2a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -134,6 +134,9 @@ const ( // Loader is the Loader device driver. Loader DeviceDriver = "loader" + + // SpaprTPMProxy is used for enabling guest to run in secure mode on ppc64le. + SpaprTPMProxy DeviceDriver = "spapr-tpm-proxy" ) func isDimmSupported(config *Config) bool { @@ -236,6 +239,8 @@ const ( // SecExecGuest represents an s390x Secure Execution (Protected Virtualization in QEMU) object SecExecGuest ObjectType = "s390-pv-guest" + // PEFGuest represent ppc64le PEF(Protected Execution Facility) object. + PEFGuest ObjectType = "pef-guest" ) // Object is a qemu object representation. @@ -285,6 +290,9 @@ func (object Object) Valid() bool { return object.ID != "" && object.File != "" && object.CBitPos != 0 && object.ReducedPhysBits != 0 case SecExecGuest: return object.ID != "" + case PEFGuest: + return object.ID != "" && object.File != "" + default: return false } @@ -327,6 +335,14 @@ func (object Object) QemuParams(config *Config) []string { case SecExecGuest: objectParams = append(objectParams, string(object.Type)) objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + case PEFGuest: + objectParams = append(objectParams, string(object.Type)) + objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + + deviceParams = append(deviceParams, string(object.Driver)) + deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) + deviceParams = append(deviceParams, fmt.Sprintf(",host-path=%s", object.File)) + } if len(deviceParams) > 0 { From 0e19ffb67ef2279706a9025c8535d9e8d31f5313 Mon Sep 17 00:00:00 2001 From: Marcel Apfelbaum Date: Sun, 20 Jun 2021 17:02:59 +0300 Subject: [PATCH 243/264] qemu: Allow hot-plugging memory devices on PCI bridges Currently virtio-mem-pci devices can be hotplugged only on the root bus. This doesn't work for PCIe machines like q35. Extend the API to optionally support hotplugging on PCI bridges. Fixes: #176 Signed-off-by: Marcel Apfelbaum --- qemu/qmp.go | 12 ++++++++++-- qemu/qmp_test.go | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 97e924559c..23c288dc7d 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1386,7 +1386,7 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { } // ExecMemdevAdd adds size of MiB memory device to the guest -func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, size int, share bool, driver, driverID string) error { +func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, size int, share bool, driver, driverID, addr, bus string) error { props := map[string]interface{}{"size": uint64(size) << 20} args := map[string]interface{}{ "qom-type": qomtype, @@ -1419,6 +1419,14 @@ func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, si "id": driverID, "memdev": id, } + + if bus != "" { + args["bus"] = bus + } + if addr != "" { + args["addr"] = addr + } + err = q.executeCommand(ctx, "device_add", args, nil) return err @@ -1426,7 +1434,7 @@ func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, si // ExecHotplugMemory adds size of MiB memory to the guest func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int, share bool) error { - return q.ExecMemdevAdd(ctx, qomtype, id, mempath, size, share, "pc-dimm", "dimm"+id) + return q.ExecMemdevAdd(ctx, qomtype, id, mempath, size, share, "pc-dimm", "dimm"+id, "", "") } // ExecuteNVDIMMDeviceAdd adds a block device to a QEMU instance using diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 5a7233f723..47fe11ecdd 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1342,7 +1342,7 @@ func TestExecMemdevAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - err := q.ExecMemdevAdd(context.Background(), "memory-backend-ram", "mem0", "", 128, true, "virtio-mem-pci", "virtiomem0") + err := q.ExecMemdevAdd(context.Background(), "memory-backend-ram", "mem0", "", 128, true, "virtio-mem-pci", "virtiomem0", "0x1", "pci-bridge-0") if err != nil { t.Fatalf("Unexpected error: %v", err) } From ff34d283dbfa21e568647c4f9fbb4200987b7ad3 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Fri, 16 Jul 2021 14:25:06 +0200 Subject: [PATCH 244/264] qemu: Consistent parameter building Always join by ",", do not put commas in the parameter slices. Always use the variable name `deviceParams`. Fixes: #180 Signed-off-by: Jakob Naucke --- qemu/qemu.go | 357 +++++++++++++++++++++++++-------------------------- 1 file changed, 178 insertions(+), 179 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 1eddc79e2a..7c4563fa49 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -308,56 +308,56 @@ func (object Object) QemuParams(config *Config) []string { 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)) + 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, string(object.Driver)) - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) - deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", object.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", object.DeviceID)) + deviceParams = append(deviceParams, fmt.Sprintf("memdev=%s", object.ID)) case TDXGuest: objectParams = append(objectParams, string(object.Type)) - objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) if object.Debug { - objectParams = append(objectParams, ",debug=on") + objectParams = append(objectParams, "debug=on") } deviceParams = append(deviceParams, string(object.Driver)) - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) - deviceParams = append(deviceParams, fmt.Sprintf(",file=%s", object.File)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", object.DeviceID)) + deviceParams = append(deviceParams, fmt.Sprintf("file=%s", object.File)) case SEVGuest: objectParams = append(objectParams, string(object.Type)) - objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) - objectParams = append(objectParams, fmt.Sprintf(",cbitpos=%d", object.CBitPos)) - objectParams = append(objectParams, fmt.Sprintf(",reduced-phys-bits=%d", object.ReducedPhysBits)) + objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) + objectParams = append(objectParams, fmt.Sprintf("cbitpos=%d", object.CBitPos)) + objectParams = append(objectParams, fmt.Sprintf("reduced-phys-bits=%d", object.ReducedPhysBits)) driveParams = append(driveParams, "if=pflash,format=raw,readonly=on") - driveParams = append(driveParams, fmt.Sprintf(",file=%s", object.File)) + driveParams = append(driveParams, fmt.Sprintf("file=%s", object.File)) case SecExecGuest: objectParams = append(objectParams, string(object.Type)) - objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) case PEFGuest: objectParams = append(objectParams, string(object.Type)) - objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) + objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) deviceParams = append(deviceParams, string(object.Driver)) - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) - deviceParams = append(deviceParams, fmt.Sprintf(",host-path=%s", object.File)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", object.DeviceID)) + deviceParams = append(deviceParams, fmt.Sprintf("host-path=%s", object.File)) } if len(deviceParams) > 0 { qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) } if len(objectParams) > 0 { qemuParams = append(qemuParams, "-object") - qemuParams = append(qemuParams, strings.Join(objectParams, "")) + qemuParams = append(qemuParams, strings.Join(objectParams, ",")) } if len(driveParams) > 0 { qemuParams = append(qemuParams, "-drive") - qemuParams = append(qemuParams, strings.Join(driveParams, "")) + qemuParams = append(qemuParams, strings.Join(driveParams, ",")) } return qemuParams @@ -473,34 +473,34 @@ func (fsdev FSDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fsdev.deviceName(config)) if s := fsdev.Transport.disableModern(config, fsdev.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, s) } - deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) - deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) + deviceParams = append(deviceParams, fmt.Sprintf("fsdev=%s", fsdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("mount_tag=%s", fsdev.MountTag)) if fsdev.Transport.isVirtioPCI(config) && fsdev.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", fsdev.ROMFile)) } if fsdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", fsdev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", fsdev.DevNo)) } 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)) + 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)) if fsdev.Multidev != "" { - fsParams = append(fsParams, fmt.Sprintf(",multidevs=%s", fsdev.Multidev)) + fsParams = append(fsParams, fmt.Sprintf("multidevs=%s", fsdev.Multidev)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) qemuParams = append(qemuParams, "-fsdev") - qemuParams = append(qemuParams, strings.Join(fsParams, "")) + qemuParams = append(qemuParams, strings.Join(fsParams, ",")) return qemuParams } @@ -599,41 +599,41 @@ func (cdev CharDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, cdev.deviceName(config)) if cdev.Driver == VirtioSerial { if s := cdev.Transport.disableModern(config, cdev.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, s) } } if cdev.Bus != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", cdev.Bus)) + deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", cdev.Bus)) } - deviceParams = append(deviceParams, fmt.Sprintf(",chardev=%s", cdev.ID)) - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", cdev.DeviceID)) + deviceParams = append(deviceParams, fmt.Sprintf("chardev=%s", cdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", cdev.DeviceID)) if cdev.Name != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",name=%s", cdev.Name)) + deviceParams = append(deviceParams, fmt.Sprintf("name=%s", cdev.Name)) } if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioPCI(config) && cdev.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", cdev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", cdev.ROMFile)) } if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { - deviceParams = append(deviceParams, ",iommu_platform=on") + deviceParams = append(deviceParams, "iommu_platform=on") } - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", cdev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", cdev.DevNo)) } cdevParams = append(cdevParams, string(cdev.Backend)) - cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) + cdevParams = append(cdevParams, fmt.Sprintf("id=%s", cdev.ID)) if cdev.Backend == Socket { - cdevParams = append(cdevParams, fmt.Sprintf(",path=%s,server=on,wait=off", cdev.Path)) + cdevParams = append(cdevParams, fmt.Sprintf("path=%s,server=on,wait=off", cdev.Path)) } else { - cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path)) + cdevParams = append(cdevParams, fmt.Sprintf("path=%s", cdev.Path)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) qemuParams = append(qemuParams, "-chardev") - qemuParams = append(qemuParams, strings.Join(cdevParams, "")) + qemuParams = append(qemuParams, strings.Join(cdevParams, ",")) return qemuParams } @@ -829,7 +829,7 @@ func (netdev NetDevice) Valid() bool { // multi-queue option mq needs to be activated. See comment in libvirt code at // https://github.com/libvirt/libvirt/blob/6e7e965dcd3d885739129b1454ce19e819b54c25/src/qemu/qemu_command.c#L3633 func (netdev NetDevice) mqParameter(config *Config) string { - p := []string{",mq=on"} + p := []string{"mq=on"} if netdev.Transport.isVirtioPCI(config) { // https://www.linux-kvm.org/page/Multiqueue @@ -842,10 +842,10 @@ func (netdev NetDevice) mqParameter(config *Config) string { // The agent implementation should do this to ensure that it is // always set vectors := len(netdev.FDs)*2 + 2 - p = append(p, fmt.Sprintf(",vectors=%d", vectors)) + p = append(p, fmt.Sprintf("vectors=%d", vectors)) } - return strings.Join(p, "") + return strings.Join(p, ",") } // QemuDeviceParams returns the -device parameters for this network device @@ -858,21 +858,21 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { } deviceParams = append(deviceParams, fmt.Sprintf("driver=%s", driver)) - deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) - deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) + deviceParams = append(deviceParams, fmt.Sprintf("netdev=%s", netdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("mac=%s", netdev.MACAddress)) if netdev.Bus != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", netdev.Bus)) + deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", netdev.Bus)) } if netdev.Addr != "" { addr, err := strconv.Atoi(netdev.Addr) if err == nil && addr >= 0 { - deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) + deviceParams = append(deviceParams, fmt.Sprintf("addr=%x", addr)) } } if s := netdev.Transport.disableModern(config, netdev.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, s) } if len(netdev.FDs) > 0 { @@ -881,14 +881,14 @@ func (netdev NetDevice) QemuDeviceParams(config *Config) []string { } if netdev.Transport.isVirtioPCI(config) && netdev.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", netdev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", netdev.ROMFile)) } if netdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { - deviceParams = append(deviceParams, ",iommu_platform=on") + deviceParams = append(deviceParams, "iommu_platform=on") } - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", netdev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", netdev.DevNo)) } return deviceParams @@ -904,17 +904,17 @@ func (netdev NetDevice) QemuNetdevParams(config *Config) []string { } netdevParams = append(netdevParams, netdevType) - netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID)) + netdevParams = append(netdevParams, fmt.Sprintf("id=%s", netdev.ID)) if netdev.VHost { - netdevParams = append(netdevParams, ",vhost=on") + netdevParams = append(netdevParams, "vhost=on") if len(netdev.VhostFDs) > 0 { var fdParams []string qemuFDs := config.appendFDs(netdev.VhostFDs) for _, fd := range qemuFDs { fdParams = append(fdParams, fmt.Sprintf("%d", fd)) } - netdevParams = append(netdevParams, fmt.Sprintf(",vhostfds=%s", strings.Join(fdParams, ":"))) + netdevParams = append(netdevParams, fmt.Sprintf("vhostfds=%s", strings.Join(fdParams, ":"))) } } @@ -926,15 +926,15 @@ func (netdev NetDevice) QemuNetdevParams(config *Config) []string { fdParams = append(fdParams, fmt.Sprintf("%d", fd)) } - netdevParams = append(netdevParams, fmt.Sprintf(",fds=%s", strings.Join(fdParams, ":"))) + netdevParams = append(netdevParams, fmt.Sprintf("fds=%s", strings.Join(fdParams, ":"))) } else { - netdevParams = append(netdevParams, fmt.Sprintf(",ifname=%s", netdev.IFName)) + netdevParams = append(netdevParams, fmt.Sprintf("ifname=%s", netdev.IFName)) if netdev.DownScript != "" { - netdevParams = append(netdevParams, fmt.Sprintf(",downscript=%s", netdev.DownScript)) + netdevParams = append(netdevParams, fmt.Sprintf("downscript=%s", netdev.DownScript)) } if netdev.Script != "" { - netdevParams = append(netdevParams, fmt.Sprintf(",script=%s", netdev.Script)) + netdevParams = append(netdevParams, fmt.Sprintf("script=%s", netdev.Script)) } } return netdevParams @@ -955,7 +955,7 @@ func (netdev NetDevice) QemuParams(config *Config) []string { netdevParams = netdev.QemuNetdevParams(config) if netdevParams != nil { qemuParams = append(qemuParams, "-netdev") - qemuParams = append(qemuParams, strings.Join(netdevParams, "")) + qemuParams = append(qemuParams, strings.Join(netdevParams, ",")) } } @@ -963,7 +963,7 @@ func (netdev NetDevice) QemuParams(config *Config) []string { deviceParams = netdev.QemuDeviceParams(config) if deviceParams != nil { qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) } } @@ -1010,25 +1010,25 @@ func (dev SerialDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, dev.deviceName(config)) if s := dev.Transport.disableModern(config, dev.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, s) } - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", dev.ID)) if dev.Transport.isVirtioPCI(config) && dev.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", dev.ROMFile)) if dev.Driver == VirtioSerial && dev.MaxPorts != 0 { - deviceParams = append(deviceParams, fmt.Sprintf(",max_ports=%d", dev.MaxPorts)) + deviceParams = append(deviceParams, fmt.Sprintf("max_ports=%d", dev.MaxPorts)) } } if dev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { - deviceParams = append(deviceParams, ",iommu_platform=on") + deviceParams = append(deviceParams, "iommu_platform=on") } - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", dev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", dev.DevNo)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1133,46 +1133,46 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, blkdev.deviceName(config)) if s := blkdev.Transport.disableModern(config, blkdev.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, s) } - deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("drive=%s", blkdev.ID)) if !blkdev.SCSI { - deviceParams = append(deviceParams, ",scsi=off") + deviceParams = append(deviceParams, "scsi=off") } if !blkdev.WCE { - deviceParams = append(deviceParams, ",config-wce=off") + deviceParams = append(deviceParams, "config-wce=off") } if blkdev.Transport.isVirtioPCI(config) && blkdev.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", blkdev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", blkdev.ROMFile)) } if blkdev.Transport.isVirtioCCW(config) { - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", blkdev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", blkdev.DevNo)) } if blkdev.ShareRW { - deviceParams = append(deviceParams, fmt.Sprintf(",share-rw=on")) + deviceParams = append(deviceParams, fmt.Sprintf("share-rw=on")) } - deviceParams = append(deviceParams, fmt.Sprintf(",serial=%s", blkdev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("serial=%s", blkdev.ID)) 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)) + 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)) if blkdev.ReadOnly { - blkParams = append(blkParams, ",readonly") + blkParams = append(blkParams, "readonly") } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) qemuParams = append(qemuParams, "-drive") - qemuParams = append(qemuParams, strings.Join(blkParams, "")) + qemuParams = append(qemuParams, strings.Join(blkParams, ",")) return qemuParams } @@ -1232,14 +1232,14 @@ func (dev LoaderDevice) Valid() bool { // QemuParams returns the qemu parameters built out of this loader device. func (dev LoaderDevice) QemuParams(config *Config) []string { var qemuParams []string - var devParams []string + var deviceParams []string - devParams = append(devParams, "loader") - devParams = append(devParams, fmt.Sprintf("file=%s", dev.File)) - devParams = append(devParams, fmt.Sprintf("id=%s", dev.ID)) + deviceParams = append(deviceParams, "loader") + deviceParams = append(deviceParams, fmt.Sprintf("file=%s", dev.File)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", dev.ID)) qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(devParams, ",")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1330,7 +1330,7 @@ func (vhostuserDev VhostUserDevice) Valid() bool { func (vhostuserDev VhostUserDevice) QemuNetParams(config *Config) []string { var qemuParams []string var netParams []string - var devParams []string + var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { @@ -1342,18 +1342,18 @@ func (vhostuserDev VhostUserDevice) QemuNetParams(config *Config) []string { netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) netParams = append(netParams, "vhostforce") - devParams = append(devParams, driver) - devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) - devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) + deviceParams = append(deviceParams, driver) + deviceParams = append(deviceParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) + deviceParams = append(deviceParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { - devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-netdev") qemuParams = append(qemuParams, strings.Join(netParams, ",")) qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(devParams, ",")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1361,23 +1361,23 @@ func (vhostuserDev VhostUserDevice) QemuNetParams(config *Config) []string { // QemuSCSIParams builds QEMU device parameters for a VhostUserSCSI device func (vhostuserDev VhostUserDevice) QemuSCSIParams(config *Config) []string { var qemuParams []string - var devParams []string + var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { return nil } - devParams = append(devParams, driver) - devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) - devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + deviceParams = append(deviceParams, driver) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) + deviceParams = append(deviceParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { - devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(devParams, ",")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1385,24 +1385,24 @@ func (vhostuserDev VhostUserDevice) QemuSCSIParams(config *Config) []string { // QemuBlkParams builds QEMU device parameters for a VhostUserBlk device func (vhostuserDev VhostUserDevice) QemuBlkParams(config *Config) []string { var qemuParams []string - var devParams []string + var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { return nil } - devParams = append(devParams, driver) - devParams = append(devParams, "logical_block_size=4096") - devParams = append(devParams, "size=512M") - devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + deviceParams = append(deviceParams, driver) + deviceParams = append(deviceParams, "logical_block_size=4096") + deviceParams = append(deviceParams, "size=512M") + deviceParams = append(deviceParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { - devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(devParams, ",")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1410,31 +1410,31 @@ func (vhostuserDev VhostUserDevice) QemuBlkParams(config *Config) []string { // QemuFSParams builds QEMU device parameters for a VhostUserFS device func (vhostuserDev VhostUserDevice) QemuFSParams(config *Config) []string { var qemuParams []string - var devParams []string + var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { return nil } - devParams = append(devParams, driver) - devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) - devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) + deviceParams = append(deviceParams, driver) + deviceParams = append(deviceParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) + deviceParams = append(deviceParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) if vhostuserDev.CacheSize != 0 { - devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) + deviceParams = append(deviceParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) } if vhostuserDev.SharedVersions { - devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") + deviceParams = append(deviceParams, "versiontable=/dev/shm/fuse_shared_versions") } if vhostuserDev.Transport.isVirtioCCW(config) { - devParams = append(devParams, fmt.Sprintf("devno=%s", vhostuserDev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", vhostuserDev.DevNo)) } if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { - devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(devParams, ",")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1443,7 +1443,7 @@ func (vhostuserDev VhostUserDevice) QemuFSParams(config *Config) []string { func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { var qemuParams []string var charParams []string - var devParams []string + var deviceParams []string charParams = append(charParams, "socket") charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) @@ -1454,19 +1454,19 @@ func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { switch vhostuserDev.VhostUserType { case VhostUserNet: - devParams = vhostuserDev.QemuNetParams(config) + deviceParams = vhostuserDev.QemuNetParams(config) case VhostUserSCSI: - devParams = vhostuserDev.QemuSCSIParams(config) + deviceParams = vhostuserDev.QemuSCSIParams(config) case VhostUserBlk: - devParams = vhostuserDev.QemuBlkParams(config) + deviceParams = vhostuserDev.QemuBlkParams(config) case VhostUserFS: - devParams = vhostuserDev.QemuFSParams(config) + deviceParams = vhostuserDev.QemuFSParams(config) default: return nil } - if devParams != nil { - return append(qemuParams, devParams...) + if deviceParams != nil { + return append(qemuParams, deviceParams...) } return nil @@ -1639,26 +1639,26 @@ func (vfioDev VFIODevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf("%s,host=%s", driver, vfioDev.BDF)) if vfioDev.Transport.isVirtioPCI(config) { if vfioDev.VendorID != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-vendor-id=%s", vfioDev.VendorID)) + deviceParams = append(deviceParams, fmt.Sprintf("x-pci-vendor-id=%s", vfioDev.VendorID)) } if vfioDev.DeviceID != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-device-id=%s", vfioDev.DeviceID)) + deviceParams = append(deviceParams, fmt.Sprintf("x-pci-device-id=%s", vfioDev.DeviceID)) } if vfioDev.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vfioDev.ROMFile)) } } if vfioDev.Bus != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", vfioDev.Bus)) + deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", vfioDev.Bus)) } if vfioDev.Transport.isVirtioCCW(config) { - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vfioDev.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", vfioDev.DevNo)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1715,35 +1715,35 @@ func (scsiCon SCSIController) Valid() bool { // QemuParams returns the qemu parameters built out of this SCSIController device. func (scsiCon SCSIController) QemuParams(config *Config) []string { var qemuParams []string - var devParams []string + var deviceParams []string driver := scsiCon.deviceName(config) - devParams = append(devParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) if scsiCon.Bus != "" { - devParams = append(devParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) + deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) } if scsiCon.Addr != "" { - devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) + deviceParams = append(deviceParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } if s := scsiCon.Transport.disableModern(config, scsiCon.DisableModern); s != "" { - devParams = append(devParams, s) + deviceParams = append(deviceParams, s) } if scsiCon.IOThread != "" { - devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) + deviceParams = append(deviceParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) } if scsiCon.Transport.isVirtioPCI(config) && scsiCon.ROMFile != "" { - devParams = append(devParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) } if scsiCon.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { - devParams = append(devParams, ",iommu_platform=on") + deviceParams = append(deviceParams, "iommu_platform=on") } - devParams = append(devParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(devParams, ",")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1813,36 +1813,36 @@ func (bridgeDev BridgeDevice) Valid() bool { // QemuParams returns the qemu parameters built out of this bridge device. func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { var qemuParams []string - var deviceParam []string + var deviceParams []string var driver DeviceDriver switch bridgeDev.Type { case PCIEBridge: driver = PCIePCIBridgeDriver - deviceParam = append(deviceParam, fmt.Sprintf("%s,bus=%s,id=%s", driver, bridgeDev.Bus, bridgeDev.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("%s,bus=%s,id=%s", driver, bridgeDev.Bus, bridgeDev.ID)) default: driver = PCIBridgeDriver shpc := "off" if bridgeDev.SHPC { shpc = "on" } - deviceParam = append(deviceParam, fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", driver, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc)) + deviceParams = append(deviceParams, fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", driver, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc)) } if bridgeDev.Addr != "" { addr, err := strconv.Atoi(bridgeDev.Addr) if err == nil && addr >= 0 { - deviceParam = append(deviceParam, fmt.Sprintf(",addr=%x", addr)) + deviceParams = append(deviceParams, fmt.Sprintf("addr=%x", addr)) } } var transport VirtioTransport if transport.isVirtioPCI(config) && bridgeDev.ROMFile != "" { - deviceParam = append(deviceParam, fmt.Sprintf(",romfile=%s", bridgeDev.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", bridgeDev.ROMFile)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParam, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1907,28 +1907,28 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { driver := vsock.deviceName(config) deviceParams = append(deviceParams, string(driver)) if s := vsock.Transport.disableModern(config, vsock.DisableModern); s != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) + deviceParams = append(deviceParams, s) } if vsock.VHostFD != nil { qemuFDs := config.appendFDs([]*os.File{vsock.VHostFD}) - deviceParams = append(deviceParams, fmt.Sprintf(",vhostfd=%d", qemuFDs[0])) + deviceParams = append(deviceParams, fmt.Sprintf("vhostfd=%d", qemuFDs[0])) } - deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID)) - deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID)) + deviceParams = append(deviceParams, fmt.Sprintf("id=%s", vsock.ID)) + deviceParams = append(deviceParams, fmt.Sprintf("%s=%d", VSOCKGuestCID, vsock.ContextID)) if vsock.Transport.isVirtioPCI(config) && vsock.ROMFile != "" { - deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vsock.ROMFile)) + deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vsock.ROMFile)) } if vsock.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { - deviceParams = append(deviceParams, ",iommu_platform=on") + deviceParams = append(deviceParams, "iommu_platform=on") } - deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vsock.DevNo)) + deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", vsock.DevNo)) } qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, "")) + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } @@ -1995,7 +1995,7 @@ func (v RngDevice) QemuParams(config *Config) []string { if v.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { - deviceParams = append(deviceParams, ",iommu_platform=on") + deviceParams = append(deviceParams, "iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", v.DevNo)) } @@ -2331,16 +2331,16 @@ func (fwcfg FwCfg) QemuParams(config *Config) []string { fwcfgParams = append(fwcfgParams, fmt.Sprintf("name=%s", f.Name)) if f.File != "" { - fwcfgParams = append(fwcfgParams, fmt.Sprintf(",file=%s", f.File)) + fwcfgParams = append(fwcfgParams, fmt.Sprintf("file=%s", f.File)) } if f.Str != "" { - fwcfgParams = append(fwcfgParams, fmt.Sprintf(",string=%s", f.Str)) + fwcfgParams = append(fwcfgParams, fmt.Sprintf("string=%s", f.Str)) } } qemuParams = append(qemuParams, "-fw_cfg") - qemuParams = append(qemuParams, strings.Join(fwcfgParams, "")) + qemuParams = append(qemuParams, strings.Join(fwcfgParams, ",")) } return qemuParams @@ -2530,15 +2530,15 @@ func (config *Config) appendMachine() { machineParams = append(machineParams, config.Machine.Type) if config.Machine.Acceleration != "" { - machineParams = append(machineParams, fmt.Sprintf(",accel=%s", config.Machine.Acceleration)) + machineParams = append(machineParams, fmt.Sprintf("accel=%s", config.Machine.Acceleration)) } if config.Machine.Options != "" { - machineParams = append(machineParams, fmt.Sprintf(",%s", config.Machine.Options)) + machineParams = append(machineParams, config.Machine.Options) } config.qemuParams = append(config.qemuParams, "-machine") - config.qemuParams = append(config.qemuParams, strings.Join(machineParams, "")) + config.qemuParams = append(config.qemuParams, strings.Join(machineParams, ",")) } } @@ -2555,17 +2555,16 @@ func (config *Config) appendQMPSockets() { continue } - qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type)) - qmpParams = append(qmpParams, q.Name) + qmpParams := append([]string{}, fmt.Sprintf("%s:%s", q.Type, q.Name)) if q.Server { - qmpParams = append(qmpParams, ",server=on") + qmpParams = append(qmpParams, "server=on") if q.NoWait { - qmpParams = append(qmpParams, ",wait=off") + qmpParams = append(qmpParams, "wait=off") } } config.qemuParams = append(config.qemuParams, "-qmp") - config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, "")) + config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, ",")) } } @@ -2593,15 +2592,15 @@ func (config *Config) appendMemory() { memoryParams = append(memoryParams, config.Memory.Size) if config.Memory.Slots > 0 { - memoryParams = append(memoryParams, fmt.Sprintf(",slots=%d", config.Memory.Slots)) + memoryParams = append(memoryParams, fmt.Sprintf("slots=%d", config.Memory.Slots)) } if config.Memory.MaxMem != "" { - memoryParams = append(memoryParams, fmt.Sprintf(",maxmem=%s", 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, "")) + config.qemuParams = append(config.qemuParams, strings.Join(memoryParams, ",")) } } @@ -2612,15 +2611,15 @@ func (config *Config) appendCPUs() error { SMPParams = append(SMPParams, fmt.Sprintf("%d", config.SMP.CPUs)) if config.SMP.Cores > 0 { - SMPParams = append(SMPParams, fmt.Sprintf(",cores=%d", config.SMP.Cores)) + 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)) + 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)) + SMPParams = append(SMPParams, fmt.Sprintf("sockets=%d", config.SMP.Sockets)) } if config.SMP.MaxCPUs > 0 { @@ -2628,11 +2627,11 @@ func (config *Config) appendCPUs() error { return fmt.Errorf("MaxCPUs %d must be equal to or greater than CPUs %d", config.SMP.MaxCPUs, config.SMP.CPUs) } - SMPParams = append(SMPParams, fmt.Sprintf(",maxcpus=%d", config.SMP.MaxCPUs)) + SMPParams = append(SMPParams, fmt.Sprintf("maxcpus=%d", config.SMP.MaxCPUs)) } config.qemuParams = append(config.qemuParams, "-smp") - config.qemuParams = append(config.qemuParams, strings.Join(SMPParams, "")) + config.qemuParams = append(config.qemuParams, strings.Join(SMPParams, ",")) } return nil @@ -2648,15 +2647,15 @@ func (config *Config) appendRTC() { 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)) + RTCParams = append(RTCParams, fmt.Sprintf("driftfix=%s", config.RTC.DriftFix)) } if config.RTC.Clock != "" { - RTCParams = append(RTCParams, fmt.Sprintf(",clock=%s", 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, "")) + config.qemuParams = append(config.qemuParams, strings.Join(RTCParams, ",")) } func (config *Config) appendGlobalParam() { From 0d21263a9b55b062360aa82219a4e8611b548587 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 21 Jul 2021 11:26:19 -0500 Subject: [PATCH 245/264] qemu: support read-only nvdimm Append `readonly=on` to a `memory-backend-file` object and `unarmed=on` to a `nvdimm` device when `ReadOnly` is set to `true` Signed-off-by: Julio Montes --- qemu/qemu.go | 8 ++++++++ qemu/qemu_test.go | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 7c4563fa49..ee30284630 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -277,6 +277,9 @@ type Object struct { // ReducedPhysBits is the reduction in the guest physical address space // This is only relevant for sev-guest objects ReducedPhysBits uint32 + + // ReadOnly specifies whether `MemPath` is opened read-only or read/write (default) + ReadOnly bool } // Valid returns true if the Object structure is valid and complete. @@ -315,6 +318,11 @@ func (object Object) QemuParams(config *Config) []string { deviceParams = append(deviceParams, string(object.Driver)) deviceParams = append(deviceParams, fmt.Sprintf("id=%s", object.DeviceID)) deviceParams = append(deviceParams, fmt.Sprintf("memdev=%s", object.ID)) + + if object.ReadOnly { + objectParams = append(objectParams, "readonly=on") + deviceParams = append(deviceParams, "unarmed=on") + } case TDXGuest: objectParams = append(objectParams, string(object.Type)) objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 34a9abb8f0..d4fcf613bd 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -128,7 +128,7 @@ func TestAppendEmptyMachine(t *testing.T) { testAppend(machine, "", t) } -var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0 -object memory-backend-file,id=mem0,mem-path=/root,size=65536" +var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0,unarmed=on -object memory-backend-file,id=mem0,mem-path=/root,size=65536,readonly=on" func TestAppendDeviceNVDIMM(t *testing.T) { object := Object{ @@ -138,6 +138,7 @@ func TestAppendDeviceNVDIMM(t *testing.T) { ID: "mem0", MemPath: "/root", Size: 1 << 16, + ReadOnly: true, } testAppend(object, deviceNVDIMMString, t) From 9d6e7970b66bff05e0342f06ce0db5beb60f7949 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 21 Jul 2021 11:38:07 -0500 Subject: [PATCH 246/264] go: support go modules Add go.mod file to support Golang 1.16.x Signed-off-by: Julio Montes --- go.mod | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..377ac043fd --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/kata-containers/govmm + +go 1.16 From 61b6378749e333238d77bc85926ddca5c13ab90f Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 21 Jul 2021 11:40:11 -0500 Subject: [PATCH 247/264] .github/workflows: reimplement github actions CI * Remove golang 1.13 and 1.14, add golang 1.16 * gometalinter has been deprecated, use golangci-lint instead Signed-off-by: Julio Montes --- .github/workflows/main.yml | 45 ++++++++++++-------------------------- .golangci.yml | 35 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 .golangci.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0ced8ae572..912d3d225d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,37 +4,20 @@ jobs: test: strategy: matrix: - go-version: [1.13.x, 1.14.x, 1.15.x] + go-version: [1.15.x, 1.16.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} - env: - GO111MODULE: off steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - name: Setup GOPATH - run: | - gopath_org=$(go env GOPATH)/src/github.com/kata-containers/ - mkdir -p ${gopath_org} - ln -s ${PWD} ${gopath_org} - - name: Checkout code - uses: actions/checkout@v2 - - name: Install gometalinter - run: | - go get github.com/alecthomas/gometalinter - $(go env GOPATH)/bin/gometalinter --install - - name: Running gometalinter - run: | - gopath_repo=$(go env GOPATH)/src/github.com/kata-containers/govmm - pushd ${gopath_repo} - $(go env GOPATH)/bin/gometalinter --tests --vendor --disable-all --enable=misspell --enable=vet --enable=ineffassign --enable=gofmt --enable=gocyclo --cyclo-over=15 --enable=golint --enable=errcheck --enable=deadcode --enable=staticcheck -enable=gas ./... - - name: Send coverage - env: - COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gopath_repo=$(go env GOPATH)/src/github.com/kata-containers/govmm - pushd ${gopath_repo} - go get github.com/mattn/goveralls - $(go env GOPATH)/bin/goveralls -v -service=github + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: latest + args: -c .golangci.yml -v + - name: go test + run: go test ./... diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000000..6b7fcdebe9 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,35 @@ +# Copyright (c) 2021 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +run: + concurrency: 4 + deadline: 600s + skip-dirs: + - vendor +# Ignore auto-generated protobuf code. + skip-files: + - ".*\\.pb\\.go$" + +linters: + disable-all: true + enable: + - deadcode + - gocyclo + - gofmt + - gosimple + - govet + - ineffassign + - misspell + - staticcheck + - structcheck + - typecheck + - unconvert + - unused + - varcheck + +linters-settings: + gocyclo: + min_complexity: 15 + unused: + check-exported: true From 335fa81667344214041348224ecabf1cf9b217d6 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Wed, 21 Jul 2021 15:08:12 -0500 Subject: [PATCH 248/264] qemu: fix golangci-lint errors fix golangci-lint errors Signed-off-by: Julio Montes --- qemu/qemu.go | 6 +++--- qemu/qemu_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 7c4563fa49..585eb712b4 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1153,7 +1153,7 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { } if blkdev.ShareRW { - deviceParams = append(deviceParams, fmt.Sprintf("share-rw=on")) + deviceParams = append(deviceParams, "share-rw=on") } deviceParams = append(deviceParams, fmt.Sprintf("serial=%s", blkdev.ID)) @@ -1905,7 +1905,7 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string { var qemuParams []string driver := vsock.deviceName(config) - deviceParams = append(deviceParams, string(driver)) + deviceParams = append(deviceParams, driver) if s := vsock.Transport.disableModern(config, vsock.DisableModern); s != "" { deviceParams = append(deviceParams, s) } @@ -2080,7 +2080,7 @@ func (b BalloonDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, "deflate-on-oom=off") } if s := b.Transport.disableModern(config, b.DisableModern); s != "" { - deviceParams = append(deviceParams, string(s)) + deviceParams = append(deviceParams, s) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 34a9abb8f0..96a111b09b 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -391,7 +391,7 @@ func TestAppendVirtioRng(t *testing.T) { ROMFile: romfile, } - deviceString += "-" + string(rngDevice.Transport.getName(nil)) + ",rng=rng0" + deviceString += "-" + rngDevice.Transport.getName(nil) + ",rng=rng0" if romfile != "" { deviceString = deviceString + ",romfile=efi-virtio.rom" } From 9518675e11330a1f675306347a0bd9984ca9c612 Mon Sep 17 00:00:00 2001 From: Liang Zhou Date: Tue, 20 Jul 2021 05:23:10 -0700 Subject: [PATCH 249/264] add support for "sandbox" feature to qemu Update the govmm code in order to support "sandbox" feature on qemu, which can introduce another protect layer on the host, to make the secure container more secure. Fixes: #185 Signed-off-by: Liang Zhou --- qemu/qemu.go | 11 +++++++++++ qemu/qemu_test.go | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index 2ebd6e4826..c4aada81e5 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2448,6 +2448,9 @@ type Config struct { // CPUModel is the CPU model to be used by qemu. CPUModel string + // SeccompSandbox is the qemu function which enables the seccomp feature + SeccompSandbox string + // Machine Machine Machine @@ -2524,6 +2527,13 @@ func (config *Config) appendFDs(fds []*os.File) []int { return fdInts } +func (config *Config) appendSeccompSandbox() { + if config.SeccompSandbox != "" { + config.qemuParams = append(config.qemuParams, "-sandbox") + config.qemuParams = append(config.qemuParams, config.SeccompSandbox) + } +} + func (config *Config) appendName() { if config.Name != "" { config.qemuParams = append(config.qemuParams, "-name") @@ -2877,6 +2887,7 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendPidFile() config.appendLogFile() config.appendFwCfg(logger) + config.appendSeccompSandbox() if err := config.appendCPUs(); err != nil { return "", err diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index c88bc0300d..bc514a9426 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -1072,6 +1072,25 @@ func TestValidPFlash(t *testing.T) { } } +func TestBadSeccompSandbox(t *testing.T) { + c := &Config{} + c.appendSeccompSandbox() + if len(c.qemuParams) != 0 { + t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) + } +} + +func TestValidSeccompSandbox(t *testing.T) { + c := &Config{} + c.SeccompSandbox = string("on,obsolete=deny") + c.appendSeccompSandbox() + expected := []string{"-sandbox", "on,obsolete=deny"} + ok := reflect.DeepEqual(expected, c.qemuParams) + if !ok { + t.Errorf("Expected %v, found %v", expected, c.qemuParams) + } +} + func TestBadVGA(t *testing.T) { c := &Config{} c.appendVGA() From 1b021929864fa45b643d9603d6615cc4b86235d7 Mon Sep 17 00:00:00 2001 From: David Gibson Date: Thu, 29 Jul 2021 13:29:28 +1000 Subject: [PATCH 250/264] Use 'host_device' driver for blockdev backends ExecuteBlockdevAdd() and ExecuteBlockdevAddWithCache() both appear to be intended to create block devices in the guest which backend onto a block device in the host. That seems to be the way that Kata always uses it. However blockdevAddBaseArgs(), used by both those functions always uses the "file" driver, which is only intended for use with regular file backends. Use of the "file" driver for host block devices was deprecated in qemu-3.0, and has been removed entirely in qemu-6.0 (commit 8d17adf34f5). We should be using the "host_device" driver instead. fixes #191 Signed-off-by: David Gibson --- qemu/qmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 23c288dc7d..8527ff015e 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -775,7 +775,7 @@ func (q *QMP) blockdevAddBaseArgs(device, blockdevID string, ro bool) (map[strin "driver": "raw", "read-only": ro, "file": map[string]interface{}{ - "driver": "file", + "driver": "host_device", "filename": device, }, } From 18352c36ec2a2eba8ffd161a7ca30c2cd7f8ba12 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Wed, 23 Jun 2021 17:14:20 +0200 Subject: [PATCH 251/264] qemu: Fix iommu_platform for vhost user CCW Enable iommu_platform for vhost user devices Fixes: #178 Signed-off-by: Jakob Naucke --- qemu/qemu.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index c4aada81e5..7d60bb7e13 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1435,6 +1435,9 @@ func (vhostuserDev VhostUserDevice) QemuFSParams(config *Config) []string { deviceParams = append(deviceParams, "versiontable=/dev/shm/fuse_shared_versions") } if vhostuserDev.Transport.isVirtioCCW(config) { + if config.Knobs.IOMMUPlatform { + deviceParams = append(deviceParams, "iommu_platform=on") + } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", vhostuserDev.DevNo)) } if vhostuserDev.Transport.isVirtioPCI(config) && vhostuserDev.ROMFile != "" { From d8cdf9aa2a1002c471230a5df6f056b268e4a9cd Mon Sep 17 00:00:00 2001 From: David Gibson Date: Tue, 3 Aug 2021 13:03:32 +1000 Subject: [PATCH 252/264] qemu: Drop support for versions older than 5.0 Kata requires version 5.2 (or 5.1 on ARM) anyway. Simplify code by dropping support for older versions. In any case explicit checks against version number aren't necessarily reliable for patched qemu versions. Signed-off-by: David Gibson --- README.md | 5 ++-- qemu/qmp.go | 61 +++++++++++++++--------------------------------- qemu/qmp_test.go | 9 ------- 3 files changed, 22 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index e23bab54b8..d96c4d9deb 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Virtual Machine Manager for Go (govmm) is a suite of packages that provide Go APIs for creating and managing virtual machines. There's -currently support for only one hypervisor, qemu/kvm, support for which -is provided by the github.com/kata-containers/govmm/qemu package. +currently support for only one hypervisor, qemu/kvm (version 5.0 and +later), support for which is provided by the +github.com/kata-containers/govmm/qemu package. The qemu package provides APIs for launching qemu instances and for managing those instances via QMP, once launched. VM instances can diff --git a/qemu/qmp.go b/qemu/qmp.go index 8527ff015e..9ebe905b04 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -719,6 +719,10 @@ func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh } } + if q.version.Major < 5 { + return nil, nil, fmt.Errorf("govmm requires qemu version 5.0 or later, this is qemu (%d.%d)", q.version.Major, q.version.Minor) + } + return q, q.version, nil } @@ -780,15 +784,8 @@ func (q *QMP) blockdevAddBaseArgs(device, blockdevID string, ro bool) (map[strin }, } - if q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 8) { - blockdevArgs["node-name"] = blockdevID - args = blockdevArgs - } else { - blockdevArgs["id"] = blockdevID - args = map[string]interface{}{ - "options": blockdevArgs, - } - } + blockdevArgs["node-name"] = blockdevID + args = blockdevArgs return args, blockdevArgs } @@ -813,11 +810,6 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string, func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevID string, direct, noFlush, ro bool) error { args, blockdevArgs := q.blockdevAddBaseArgs(device, blockdevID, ro) - if q.version.Major < 2 || (q.version.Major == 2 && q.version.Minor < 9) { - return fmt.Errorf("versions of qemu (%d.%d) older than 2.9 do not support set cache-related options for block devices", - q.version.Major, q.version.Minor) - } - blockdevArgs["cache"] = map[string]interface{}{ "direct": direct, "no-flush": noFlush, @@ -850,7 +842,7 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b args["bus"] = bus } - if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { + if shared { args["share-rw"] = "on" } if transport.isVirtioPCI(nil) { @@ -904,32 +896,22 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive if lun >= 0 { args["lun"] = lun } - if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { + if shared { args["share-rw"] = "on" } return q.executeCommand(ctx, "device_add", args, nil) } -// ExecuteBlockdevDel deletes a block device by sending a x-blockdev-del command -// for qemu versions < 2.9. It sends the updated blockdev-del command for qemu>=2.9. -// blockdevID is the id of the block device to be deleted. Typically, this will -// match the id passed to ExecuteBlockdevAdd. It must be a valid QMP id. +// ExecuteBlockdevDel deletes a block device by sending blockdev-del +// command. blockdevID is the id of the block device to be deleted. +// Typically, this will match the id passed to ExecuteBlockdevAdd. It +// must be a valid QMP id. func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error { args := map[string]interface{}{} - if q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 9) { - args["node-name"] = blockdevID - return q.executeCommand(ctx, "blockdev-del", args, nil) - } - - if q.version.Major == 2 && q.version.Minor == 8 { - args["node-name"] = blockdevID - } else { - args["id"] = blockdevID - } - - return q.executeCommand(ctx, "x-blockdev-del", args, nil) + args["node-name"] = blockdevID + return q.executeCommand(ctx, "blockdev-del", args, nil) } // ExecuteChardevDel deletes a char device by sending a chardev-remove command. @@ -1104,7 +1086,7 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver if bus != "" { args["bus"] = bus } - if shared && (q.version.Major > 2 || (q.version.Major == 2 && q.version.Minor >= 10)) { + if shared { args["share-rw"] = "on" } if queues > 0 { @@ -1240,10 +1222,7 @@ func isThreadIDSupported(driver string) bool { // isDieIDSupported returns if the cpu driver and the qemu version support the die id option func (q *QMP) isDieIDSupported(driver string) bool { - if (q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1)) && driver == "host-x86_64-cpu" { - return true - } - return false + return driver == "host-x86_64-cpu" } // ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command. @@ -1454,11 +1433,9 @@ func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, si }, } - if q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1) { - if pmem != nil { - props := args["props"].(map[string]interface{}) - props["pmem"] = *pmem - } + if pmem != nil { + props := args["props"].(map[string]interface{}) + props["pmem"] = *pmem } err := q.executeCommand(ctx, "object-add", args, nil) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 47fe11ecdd..38153f914e 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1125,10 +1125,6 @@ func TestQMPCPUDeviceAdd(t *testing.T) { dieID := "0" coreID := "1" threadID := "0" - version := &QMPVersion{ - Major: 4, - Minor: 1, - } for _, d := range drivers { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) @@ -1137,7 +1133,6 @@ func TestQMPCPUDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - q.version = version err := q.ExecuteCPUDeviceAdd(context.Background(), d, cpuID, socketID, dieID, coreID, threadID, "") if err != nil { t.Fatalf("Unexpected error %v", err) @@ -1634,10 +1629,6 @@ func TestExecuteNVDIMMDeviceAdd(t *testing.T) { cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) checkVersion(t, connectedCh) - q.version = &QMPVersion{ - Major: 4, - Minor: 1, - } pmem := true err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024, &pmem) if err != nil { From d27256f8635d3fa382d6cbd9f3a60f601773c4dc Mon Sep 17 00:00:00 2001 From: David Gibson Date: Tue, 3 Aug 2021 12:52:26 +1000 Subject: [PATCH 253/264] qmp: Don't use deprecated 'props' field for object-add Use of the 'props' argument to 'object-add' has been deprecated since QEMU 5.0 (commit 5f07c4d60d09) in favor of flattening the properties directly into the 'object-add' arguments. Support for 'props' is removed entirely in qemu 6.0 (commit 50243407457a). fixes #193 Signed-off-by: David Gibson --- qemu/qmp.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9ebe905b04..229a2e206b 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1366,17 +1366,16 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { // ExecMemdevAdd adds size of MiB memory device to the guest func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, size int, share bool, driver, driverID, addr, bus string) error { - props := map[string]interface{}{"size": uint64(size) << 20} args := map[string]interface{}{ "qom-type": qomtype, "id": id, - "props": props, + "size": uint64(size) << 20, } if mempath != "" { - props["mem-path"] = mempath + args["mem-path"] = mempath } if share { - props["share"] = true + args["share"] = true } err := q.executeCommand(ctx, "object-add", args, nil) if err != nil { @@ -1426,16 +1425,13 @@ func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, si args := map[string]interface{}{ "qom-type": "memory-backend-file", "id": "nvdimmbackmem" + id, - "props": map[string]interface{}{ - "mem-path": mempath, - "size": size, - "share": true, - }, + "mem-path": mempath, + "size": size, + "share": true, } if pmem != nil { - props := args["props"].(map[string]interface{}) - props["pmem"] = *pmem + args["pmem"] = *pmem } err := q.executeCommand(ctx, "object-add", args, nil) From 3a9a67499f410bfc7f6af78a783232f7a0045d7e Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Mon, 9 Aug 2021 13:44:33 -0700 Subject: [PATCH 254/264] qemu: Add credentials to qemu Cmd add credentials to the command attribute Fixes #2444 Signed-off-by: Feng Wang --- qemu/qemu.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index c4aada81e5..1152ffd72a 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2439,6 +2439,13 @@ type Config struct { // Ctx is the context used when launching qemu. Ctx context.Context + // User ID. + Uid uint32 + // Group ID. + Gid uint32 + // Supplementary group IDs. + Groups []uint32 + // Name is the qemu guest name Name string @@ -2898,8 +2905,15 @@ func LaunchQemu(config Config, logger QMPLog) (string, error) { ctx = context.Background() } + attr := syscall.SysProcAttr{} + attr.Credential = &syscall.Credential{ + Uid: config.Uid, + Gid: config.Gid, + Groups: config.Groups, + } + return LaunchCustomQemu(ctx, config.Path, config.qemuParams, - config.fds, nil, logger) + config.fds, &attr, logger) } // LaunchCustomQemu can be used to launch a new qemu instance. From 5c7998db048ab8a16ad6bfcd587623a84d0d3a41 Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Tue, 31 Aug 2021 16:32:09 +0800 Subject: [PATCH 255/264] QMP: Add ExecuteBlockdevAddWithDriverCache ExecuteBlockdevAddWithDriverCache has three one parameter driver than ExecuteBlockdevAddWithCache. Parameter driver can set the driver of block device. Fixes: #198 Signed-off-by: Hui Zhu --- qemu/qmp.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 229a2e206b..f8a33334c2 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -772,14 +772,14 @@ func (q *QMP) ExecuteQuit(ctx context.Context) error { return q.executeCommand(ctx, "quit", nil, nil) } -func (q *QMP) blockdevAddBaseArgs(device, blockdevID string, ro bool) (map[string]interface{}, map[string]interface{}) { +func (q *QMP) blockdevAddBaseArgs(driver, device, blockdevID string, ro bool) (map[string]interface{}, map[string]interface{}) { var args map[string]interface{} blockdevArgs := map[string]interface{}{ "driver": "raw", "read-only": ro, "file": map[string]interface{}{ - "driver": "host_device", + "driver": driver, "filename": device, }, } @@ -795,7 +795,7 @@ func (q *QMP) blockdevAddBaseArgs(device, blockdevID string, ro bool) (map[strin // used to name the device. As this identifier will be passed directly to QMP, // it must obey QMP's naming rules, e,g., it must start with a letter. func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string, ro bool) error { - args, _ := q.blockdevAddBaseArgs(device, blockdevID, ro) + args, _ := q.blockdevAddBaseArgs("host_device", device, blockdevID, ro) return q.executeCommand(ctx, "blockdev-add", args, nil) } @@ -808,7 +808,21 @@ func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string, // is enabled. noFlush denotes whether flush requests for the device are // ignored. func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevID string, direct, noFlush, ro bool) error { - args, blockdevArgs := q.blockdevAddBaseArgs(device, blockdevID, ro) + args, blockdevArgs := q.blockdevAddBaseArgs("host_device", device, blockdevID, ro) + + blockdevArgs["cache"] = map[string]interface{}{ + "direct": direct, + "no-flush": noFlush, + } + + return q.executeCommand(ctx, "blockdev-add", args, nil) +} + +// ExecuteBlockdevAddWithDriverCache has three one parameter driver +// than ExecuteBlockdevAddWithCache. +// Parameter driver can set the driver of block device. +func (q *QMP) ExecuteBlockdevAddWithDriverCache(ctx context.Context, driver, device, blockdevID string, direct, noFlush, ro bool) error { + args, blockdevArgs := q.blockdevAddBaseArgs(driver, device, blockdevID, ro) blockdevArgs["cache"] = map[string]interface{}{ "direct": direct, From de039da2a9fc8399438d46fd2f0dcd271edadeb5 Mon Sep 17 00:00:00 2001 From: David Gibson Date: Wed, 8 Sep 2021 11:48:52 +1000 Subject: [PATCH 256/264] govmm/qemu: Let IO/memory reservations be specified for bridge devices This adds fields to BridgeDevice struct to allow qemu's io-reserve, mem-reserve and pref64-reserve properties to be set for PCI bridges. This is needed for Kata's upcoming change to ACPI hotplug. fixes #200 Signed-off-by: David Gibson --- qemu/qemu.go | 19 +++++++++++++++++++ qemu/qemu_arch_base_test.go | 2 ++ qemu/qemu_test.go | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index ee814a9c6b..e57a4b26a9 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1802,6 +1802,15 @@ type BridgeDevice struct { // ROMFile specifies the ROM file being used for this device. ROMFile string + + // Address range reservations for devices behind the bridge + // NB: strings seem an odd choice, but if they were integers, + // they'd default to 0 by Go's rules in all the existing users + // who don't set them. 0 is a valid value for certain cases, + // but not you want by default. + IOReserve string + MemReserve string + Pref64Reserve string } // Valid returns true if the BridgeDevice structure is valid and complete. @@ -1852,6 +1861,16 @@ func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", bridgeDev.ROMFile)) } + if bridgeDev.IOReserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("io-reserve=%s", bridgeDev.IOReserve)) + } + if bridgeDev.MemReserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("mem-reserve=%s", bridgeDev.MemReserve)) + } + if bridgeDev.Pref64Reserve != "" { + deviceParams = append(deviceParams, fmt.Sprintf("pref64-reserve=%s", bridgeDev.Pref64Reserve)) + } + qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 0bc34004e3..27219f4989 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -1,3 +1,4 @@ +//go:build !s390x // +build !s390x /* @@ -38,6 +39,7 @@ var ( deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on,serial=hd0 -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" + devicePCIBridgeStringReserved = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=off,addr=ff,romfile=efi-virtio.rom,io-reserve=4k,mem-reserve=1m,pref64-reserve=1m" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" romfile = "efi-virtio.rom" ) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index bc514a9426..844ed085f4 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -486,6 +486,24 @@ func TestAppendPCIBridgeDevice(t *testing.T) { testAppend(bridge, devicePCIBridgeString, t) } +func TestAppendPCIBridgeDeviceWithReservations(t *testing.T) { + + bridge := BridgeDevice{ + Type: PCIBridge, + ID: "mybridge", + Bus: "/pci-bus/pcie.0", + Addr: "255", + Chassis: 5, + SHPC: false, + ROMFile: romfile, + IOReserve: "4k", + MemReserve: "1m", + Pref64Reserve: "1m", + } + + testAppend(bridge, devicePCIBridgeStringReserved, t) +} + func TestAppendPCIEBridgeDevice(t *testing.T) { bridge := BridgeDevice{ From 1ed52714c02205fec2e432470e0880f5519ee5cb Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Thu, 2 Sep 2021 15:42:22 -0700 Subject: [PATCH 257/264] qmp: wait for POWERDOWN event in ExecuteSystemPowerdown() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ExecuteSystemPowerdown issues `system_powerdown` and waits for `SHUTDOWN`. The event emitted is `POWERDOWN` per spec. Without this we get an error even though the VM has shutdown gracefully. Per QEMU spec: ``` POWERDOWN (Event) Emitted when the virtual machine is powered down through the power control system, such as via ACPI. Since 0.12 Example <- { "event": "POWERDOWN", "timestamp": { "seconds": 1267040730, "microseconds": 682951 } } SHUTDOWN (Event) Emitted when the virtual machine has shut down, indicating that qemu is about to exit. Arguments guest: boolean If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10) reason: ShutdownCause The ShutdownCause which resulted in the SHUTDOWN. (since 4.0) Note If the command-line option “-no-shutdown” has been specified, qemu will not exit, and a STOP event will eventually follow the SHUTDOWN event Since 0.12 Example <- { "event": "SHUTDOWN", "data": { "guest": true }, "timestamp": { "seconds": 1267040730, "microseconds": 682951 } } ``` Signed-off-by: Manohar Castelino --- qemu/qmp.go | 2 +- qemu/qmp_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index f8a33334c2..a7afc6dcd1 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -761,7 +761,7 @@ func (q *QMP) ExecuteCont(ctx context.Context) error { // This function will block until the SHUTDOWN event is received. func (q *QMP) ExecuteSystemPowerdown(ctx context.Context) error { filter := &qmpEventFilter{ - eventName: "SHUTDOWN", + eventName: "POWERDOWN", } return q.executeCommand(ctx, "system_powerdown", nil, filter) } diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index 38153f914e..d541b284ab 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -802,7 +802,7 @@ func TestQMPSystemPowerdown(t *testing.T) { disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) buf.AddCommand("system_powerdown", nil, "return", nil) - buf.AddEvent("SHUTDOWN", time.Millisecond*100, + buf.AddEvent("POWERDOWN", time.Millisecond*100, nil, map[string]interface{}{ "seconds": seconds, From fe83c208dceef6f9206db43abb5d6c7340df9a1f Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Thu, 2 Sep 2021 16:05:06 -0700 Subject: [PATCH 258/264] qemu: Add support for --no-shutdown Knob Add support for --no-shutdown Knob. This allows us to shutdown the VM without quitting QEMU. Note: Also fix the comment around --no-reboot to be more accurate. Signed-off-by: Manohar Castelino --- qemu/qemu.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index ee814a9c6b..13d8ab33a4 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2402,8 +2402,12 @@ type Knobs struct { Realtime bool // Exit instead of rebooting + // Prevents QEMU from rebooting in the event of a Triple Fault. NoReboot bool + // Don’t exit QEMU on guest shutdown, but instead only stop the emulation. + NoShutdown bool + // IOMMUPlatform will enable IOMMU for supported devices IOMMUPlatform bool } @@ -2776,6 +2780,10 @@ func (config *Config) appendKnobs() { config.qemuParams = append(config.qemuParams, "--no-reboot") } + if config.Knobs.NoShutdown { + config.qemuParams = append(config.qemuParams, "--no-shutdown") + } + if config.Knobs.Daemonize { config.qemuParams = append(config.qemuParams, "-daemonize") } From 9a2bbedac7a9c30f5dc5ef3bdd6b68fe196496c0 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Fri, 23 Jul 2021 17:41:22 +0200 Subject: [PATCH 259/264] qemu: Remove -realtime in favor of -overcommit as `-realtime` has been removed in QEMU 6. `-overcommit` has been supported since at least QEMU 3.1. Fixes: #189 Signed-off-by: Jakob Naucke --- qemu/qemu.go | 25 +++---------------------- qemu/qemu_test.go | 36 ++++++++---------------------------- 2 files changed, 11 insertions(+), 50 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f1c67e4fe3..aebc97c038 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -2411,15 +2411,11 @@ type Knobs struct { MemShared bool // Mlock will control locking of memory - // Only active when Realtime is set to true Mlock bool // Stopped will not start guest CPU at startup Stopped bool - // Realtime will enable realtime QEMU - Realtime bool - // Exit instead of rebooting // Prevents QEMU from rebooting in the event of a Triple Fault. NoReboot bool @@ -2809,24 +2805,9 @@ func (config *Config) appendKnobs() { config.appendMemoryKnobs() - if config.Knobs.Realtime { - config.qemuParams = append(config.qemuParams, "-realtime") - // This path is redundant as the default behaviour is locked memory - // Realtime today does not control any other feature even though - // other features may be added in the future - // https://lists.gnu.org/archive/html/qemu-devel/2012-12/msg03330.html - if config.Knobs.Mlock { - config.qemuParams = append(config.qemuParams, "mlock=on") - } else { - config.qemuParams = append(config.qemuParams, "mlock=off") - } - } else { - // In order to turn mlock off we need the -realtime option as well - if !config.Knobs.Mlock { - //Enable realtime anyway just to get the right swapping behaviour - config.qemuParams = append(config.qemuParams, "-realtime") - config.qemuParams = append(config.qemuParams, "mlock=off") - } + if config.Knobs.Mlock { + config.qemuParams = append(config.qemuParams, "-overcommit") + config.qemuParams = append(config.qemuParams, "mem-lock=on") } if config.Knobs.Stopped { diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 844ed085f4..07e55108cd 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -524,7 +524,7 @@ func TestAppendEmptyDevice(t *testing.T) { } func TestAppendKnobsAllTrue(t *testing.T) { - var knobsString = "-no-user-config -nodefaults -nographic --no-reboot -daemonize -realtime mlock=on -S" + var knobsString = "-no-user-config -nodefaults -nographic --no-reboot -daemonize -overcommit mem-lock=on -S" knobs := Knobs{ NoUserConfig: true, NoDefaults: true, @@ -534,7 +534,6 @@ func TestAppendKnobsAllTrue(t *testing.T) { MemPrealloc: true, FileBackedMem: true, MemShared: true, - Realtime: true, Mlock: true, Stopped: true, } @@ -543,7 +542,7 @@ func TestAppendKnobsAllTrue(t *testing.T) { } func TestAppendKnobsAllFalse(t *testing.T) { - var knobsString = "-realtime mlock=off" + var knobsString = "" knobs := Knobs{ NoUserConfig: false, NoDefaults: false, @@ -552,7 +551,6 @@ func TestAppendKnobsAllFalse(t *testing.T) { MemPrealloc: false, FileBackedMem: false, MemShared: false, - Realtime: false, Mlock: false, Stopped: false, } @@ -581,7 +579,6 @@ func TestAppendMemoryHugePages(t *testing.T) { objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=/dev/hugepages,share=on,prealloc=on" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" - mlockFalseString := "-realtime mlock=off" knobsString := objMemString + " " if isDimmSupported(nil) { @@ -590,7 +587,7 @@ func TestAppendMemoryHugePages(t *testing.T) { knobsString += memBackendString } - testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) + testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestAppendMemoryMemPrealloc(t *testing.T) { @@ -612,7 +609,6 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { objMemString := "-object memory-backend-ram,id=dimm1,size=1G,share=on,prealloc=on" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" - mlockFalseString := "-realtime mlock=off" knobsString := objMemString + " " if isDimmSupported(nil) { @@ -621,7 +617,7 @@ func TestAppendMemoryMemPrealloc(t *testing.T) { knobsString += memBackendString } - testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) + testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestAppendMemoryMemShared(t *testing.T) { @@ -643,7 +639,6 @@ func TestAppendMemoryMemShared(t *testing.T) { objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" - mlockFalseString := "-realtime mlock=off" knobsString := objMemString + " " if isDimmSupported(nil) { @@ -652,7 +647,7 @@ func TestAppendMemoryMemShared(t *testing.T) { knobsString += memBackendString } - testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) + testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestAppendMemoryFileBackedMem(t *testing.T) { @@ -674,7 +669,6 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" - mlockFalseString := "-realtime mlock=off" knobsString := objMemString + " " if isDimmSupported(nil) { @@ -683,7 +677,7 @@ func TestAppendMemoryFileBackedMem(t *testing.T) { knobsString += memBackendString } - testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) + testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { @@ -706,7 +700,6 @@ func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on,prealloc=on" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" - mlockFalseString := "-realtime mlock=off" knobsString := objMemString + " " if isDimmSupported(nil) { @@ -715,7 +708,7 @@ func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { knobsString += memBackendString } - testConfigAppend(conf, knobs, memString+" "+knobsString+" "+mlockFalseString, t) + testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestNoRebootKnob(t *testing.T) { @@ -725,9 +718,8 @@ func TestNoRebootKnob(t *testing.T) { NoReboot: true, } knobsString := "--no-reboot" - mlockFalseString := "-realtime mlock=off" - testConfigAppend(conf, knobs, knobsString+" "+mlockFalseString, t) + testConfigAppend(conf, knobs, knobsString, t) } var kernelString = "-kernel /opt/vmlinux.container -initrd /opt/initrd.container -append root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable" @@ -1163,18 +1155,6 @@ func TestBadMemoryKnobs(t *testing.T) { } } -func TestBadKnobs(t *testing.T) { - c := &Config{ - Knobs: Knobs{ - Mlock: true, - }, - } - c.appendKnobs() - if len(c.qemuParams) != 0 { - t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) - } -} - func TestBadBios(t *testing.T) { c := &Config{} c.appendBios() From 1d1a23134a28df661204f05714574e7f1fdf074c Mon Sep 17 00:00:00 2001 From: Manohar Castelino Date: Fri, 8 Oct 2021 12:48:30 -0700 Subject: [PATCH 260/264] qemu: Add support for legacy serial device - Add support for legacy serial device - Additionally add support for the file backend for chardev Legacy serial plus char backend file will allow us to support capture early boot messages. Signed-off-by: Manohar Castelino --- qemu/qemu.go | 50 +++++++++++++++++++++++++++++++++++++++++++++-- qemu/qemu_test.go | 22 +++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index f1c67e4fe3..b8f442b5f0 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -66,6 +66,9 @@ type Device interface { type DeviceDriver string const ( + // LegacySerial is the legacy serial device driver + LegacySerial DeviceDriver = "serial" + // NVDIMM is the Non Volatile DIMM device driver. NVDIMM DeviceDriver = "nvdimm" @@ -549,6 +552,9 @@ const ( // PTY creates a new pseudo-terminal on the host and connect to it. PTY CharDeviceBackend = "pty" + + // File sends traffic from the guest to a file on the host. + File CharDeviceBackend = "file" ) // CharDevice represents a qemu character device. @@ -637,8 +643,11 @@ func (cdev CharDevice) QemuParams(config *Config) []string { cdevParams = append(cdevParams, fmt.Sprintf("path=%s", cdev.Path)) } - qemuParams = append(qemuParams, "-device") - qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) + // Legacy serial is special. It does not follow the device + driver model + if cdev.Driver != LegacySerial { + qemuParams = append(qemuParams, "-device") + qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) + } qemuParams = append(qemuParams, "-chardev") qemuParams = append(qemuParams, strings.Join(cdevParams, ",")) @@ -978,6 +987,43 @@ func (netdev NetDevice) QemuParams(config *Config) []string { return qemuParams } +// LegacySerialDevice represents a qemu legacy serial device. +type LegacySerialDevice struct { + // ID is the serial device identifier. + // This maps to the char dev associated with the device + // as serial does not have a notion of id + // e.g: + // -chardev stdio,id=char0,mux=on,logfile=serial.log,signal=off -serial chardev:char0 + // -chardev file,id=char0,path=serial.log -serial chardev:char0 + Chardev string +} + +// Valid returns true if the LegacySerialDevice structure is valid and complete. +func (dev LegacySerialDevice) Valid() bool { + return dev.Chardev != "" +} + +// QemuParams returns the qemu parameters built out of this serial device. +func (dev LegacySerialDevice) QemuParams(config *Config) []string { + var deviceParam string + var qemuParams []string + + deviceParam = fmt.Sprintf("chardev:%s", dev.Chardev) + + qemuParams = append(qemuParams, "-serial") + qemuParams = append(qemuParams, deviceParam) + + return qemuParams +} + +/* Not used currently +// deviceName returns the QEMU device name for the current combination of +// driver and transport. +func (dev LegacySerialDevice) deviceName(config *Config) string { + return dev.Chardev +} +*/ + // SerialDevice represents a qemu serial device. type SerialDevice struct { // Driver is the qemu device driver diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index 844ed085f4..ab16b5aaee 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -274,6 +274,28 @@ func TestAppendDeviceNetworkPCIMq(t *testing.T) { testAppend(netdev, deviceNetworkPCIStringMq, t) } +var deviceLegacySerialString = "-serial chardev:tlserial0" + +func TestAppendLegacySerial(t *testing.T) { + sdev := LegacySerialDevice{ + Chardev: "tlserial0", + } + + testAppend(sdev, deviceLegacySerialString, t) +} + +var deviceLegacySerialPortString = "-chardev file,id=char0,path=/tmp/serial.log" + +func TestAppendDeviceLegacySerialPort(t *testing.T) { + chardev := CharDevice{ + Driver: LegacySerial, + Backend: File, + ID: "char0", + Path: "/tmp/serial.log", + } + testAppend(chardev, deviceLegacySerialPortString, t) +} + func TestAppendDeviceSerial(t *testing.T) { sdev := SerialDevice{ Driver: VirtioSerial, From 82cc01d24de4f56ff7fdbd22669594aa9aa9930d Mon Sep 17 00:00:00 2001 From: Shengjing Zhu Date: Sun, 7 Nov 2021 02:59:18 +0800 Subject: [PATCH 261/264] qemu: Fix 32 bit int overflow in test file Signed-off-by: Shengjing Zhu --- qemu/qmp_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index d541b284ab..83259290bb 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -670,7 +670,7 @@ func TestQMPChardevDel(t *testing.T) { // exit gracefully. We should also receive two events on the eventCh. func TestQMPDeviceDel(t *testing.T) { const ( - seconds = 1352167040730 + seconds = int64(1352167040730) microsecondsEv1 = 123456 microsecondsEv2 = 123556 device = "device_" + volumeUUID @@ -793,7 +793,7 @@ func TestQMPCancel(t *testing.T) { // as we've provisioned a SHUTDOWN event. The QMP loop should exit gracefully. func TestQMPSystemPowerdown(t *testing.T) { const ( - seconds = 1352167040730 + seconds = int64(1352167040730) microsecondsEv1 = 123456 ) @@ -926,7 +926,7 @@ func TestQMPEventedCommandCancelConcurrent(t *testing.T) { // shut down gracefully. func TestQMPEvents(t *testing.T) { const ( - seconds = 1352167040730 + seconds = int64(1352167040730) microsecondsEv1 = 123456 microsecondsEv2 = 123556 device = "device_" + volumeUUID @@ -1640,7 +1640,7 @@ func TestExecuteNVDIMMDeviceAdd(t *testing.T) { func TestMainLoopEventBeforeGreeting(t *testing.T) { const ( - seconds = 1352167040730 + seconds = int64(1352167040730) microseconds = 123456 ) From f971801b107ea70c814ee3eb1dbf95f3864c6aa9 Mon Sep 17 00:00:00 2001 From: bin liu Date: Thu, 18 Nov 2021 15:52:22 +0800 Subject: [PATCH 262/264] qemu: only set wait parameter for server mode socket based char device Now the `wait` is passed to qmp command, even at non-server mode. This will cause qemu return this error: 'wait' option is incompatible with socket in client connect mode Fixes: #205 Signed-off-by: bin liu --- qemu/qmp.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index a7afc6dcd1..2e30c2ba9d 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -1518,20 +1518,26 @@ func (q *QMP) ExecuteGetFD(ctx context.Context, fdname string, fd *os.File) erro // id is an identifier for the device, path specifies the local path of the unix socket, // wait is to block waiting for a client to connect, server specifies that the socket is a listening socket. func (q *QMP) ExecuteCharDevUnixSocketAdd(ctx context.Context, id, path string, wait, server bool) error { + data := map[string]interface{}{ + "server": server, + "addr": map[string]interface{}{ + "type": "unix", + "data": map[string]interface{}{ + "path": path, + }, + }, + } + + // wait is only valid for server mode + if server { + data["wait"] = wait + } + args := map[string]interface{}{ "id": id, "backend": map[string]interface{}{ "type": "socket", - "data": map[string]interface{}{ - "wait": wait, - "server": server, - "addr": map[string]interface{}{ - "type": "unix", - "data": map[string]interface{}{ - "path": path, - }, - }, - }, + "data": data, }, } return q.executeCommand(ctx, "chardev-add", args, nil) From b17f07395cbaacbfb53ee0f14933738d8dee60f1 Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Tue, 14 Dec 2021 11:55:19 -0600 Subject: [PATCH 263/264] qemu: update readonly flag for block devices since qemu 6.0, readonly flag for block devices must be enable or disable with `on` or `off` respectively. Signed-off-by: Julio Montes --- qemu/qemu.go | 2 +- qemu/qemu_arch_base_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu/qemu.go b/qemu/qemu.go index 831998c4b6..f6db8481d3 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -1219,7 +1219,7 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { blkParams = append(blkParams, fmt.Sprintf("if=%s", blkdev.Interface)) if blkdev.ReadOnly { - blkParams = append(blkParams, "readonly") + blkParams = append(blkParams, "readonly=on") } qemuParams = append(qemuParams, "-device") diff --git a/qemu/qemu_arch_base_test.go b/qemu/qemu_arch_base_test.go index 27219f4989..6676097071 100644 --- a/qemu/qemu_arch_base_test.go +++ b/qemu/qemu_arch_base_test.go @@ -37,7 +37,7 @@ var ( deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" deviceVhostUserBlkString = "-chardev socket,id=char2,path=/tmp/nonexistentsocket.socket -device vhost-user-blk-pci,logical_block_size=4096,size=512M,chardev=char2,romfile=efi-virtio.rom" - deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on,serial=hd0 -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly" + deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on,serial=hd0 -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly=on" devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" devicePCIBridgeStringReserved = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=off,addr=ff,romfile=efi-virtio.rom,io-reserve=4k,mem-reserve=1m,pref64-reserve=1m" devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" From 8939b0f8e0b57631841f6412a2c568e381392a2a Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 14 Jan 2022 13:11:03 -0600 Subject: [PATCH 264/264] qemu: add support for SGX Define and implement memory-backend-epc object Signed-off-by: Julio Montes --- qemu/qemu.go | 16 ++++++++++++++++ qemu/qemu_test.go | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/qemu/qemu.go b/qemu/qemu.go index f6db8481d3..c43ff57955 100644 --- a/qemu/qemu.go +++ b/qemu/qemu.go @@ -234,6 +234,9 @@ const ( // MemoryBackendFile represents a guest memory mapped file. MemoryBackendFile ObjectType = "memory-backend-file" + // MemoryBackendEPC represents a guest memory backend EPC for SGX. + MemoryBackendEPC ObjectType = "memory-backend-epc" + // TDXGuest represents a TDX object TDXGuest ObjectType = "tdx-guest" @@ -283,6 +286,9 @@ type Object struct { // ReadOnly specifies whether `MemPath` is opened read-only or read/write (default) ReadOnly bool + + // Prealloc enables memory preallocation + Prealloc bool } // Valid returns true if the Object structure is valid and complete. @@ -290,6 +296,8 @@ func (object Object) Valid() bool { switch object.Type { case MemoryBackendFile: return object.ID != "" && object.MemPath != "" && object.Size != 0 + case MemoryBackendEPC: + return object.ID != "" && object.Size != 0 case TDXGuest: return object.ID != "" && object.File != "" && object.DeviceID != "" case SEVGuest: @@ -326,6 +334,14 @@ func (object Object) QemuParams(config *Config) []string { objectParams = append(objectParams, "readonly=on") deviceParams = append(deviceParams, "unarmed=on") } + case MemoryBackendEPC: + objectParams = append(objectParams, string(object.Type)) + objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) + objectParams = append(objectParams, fmt.Sprintf("size=%d", object.Size)) + if object.Prealloc { + objectParams = append(objectParams, "prealloc=on") + } + case TDXGuest: objectParams = append(objectParams, string(object.Type)) objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID)) diff --git a/qemu/qemu_test.go b/qemu/qemu_test.go index e423661544..2e37b0e166 100644 --- a/qemu/qemu_test.go +++ b/qemu/qemu_test.go @@ -144,6 +144,19 @@ func TestAppendDeviceNVDIMM(t *testing.T) { testAppend(object, deviceNVDIMMString, t) } +var objectEPCString = "-object memory-backend-epc,id=epc0,size=65536,prealloc=on" + +func TestAppendEPCObject(t *testing.T) { + object := Object{ + Type: MemoryBackendEPC, + ID: "epc0", + Size: 1 << 16, + Prealloc: true, + } + + testAppend(object, objectEPCString, t) +} + func TestAppendDeviceFS(t *testing.T) { fsdev := FSDevice{ Driver: Virtio9P,