pax_global_header00006660000000000000000000000064141422325310014507gustar00rootroot0000000000000052 comment=7ef47d7fc80748815a575886f123ac02ee52b7b0 golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/000077500000000000000000000000001414223253100230775ustar00rootroot00000000000000golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/.github/000077500000000000000000000000001414223253100244375ustar00rootroot00000000000000golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/.github/workflows/000077500000000000000000000000001414223253100264745ustar00rootroot00000000000000golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/.github/workflows/main.yml000066400000000000000000000010701414223253100301410ustar00rootroot00000000000000on: ["pull_request"] name: Unit tests jobs: test: strategy: matrix: go-version: [1.15.x, 1.16.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - 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 ./... golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/.gitignore000066400000000000000000000000031414223253100250600ustar00rootroot00000000000000*~ golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/.golangci.yml000066400000000000000000000010411414223253100254570ustar00rootroot00000000000000# 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 golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/.travis.yml000066400000000000000000000012031414223253100252040ustar00rootroot00000000000000language: go go: - "1.10" - "1.11" - tip arch: - s390x go_import_path: github.com/kata-containers/govmm matrix: allow_failures: - go: tip before_install: - go get github.com/alecthomas/gometalinter - gometalinter --install - go get github.com/mattn/goveralls script: - go env - 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 golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/CONTRIBUTING.md000066400000000000000000000061351414223253100253350ustar00rootroot00000000000000# 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 ``` ## 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. ## 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/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/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. golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/COPYING000066400000000000000000000261361414223253100241420ustar00rootroot00000000000000 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. golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/README.md000066400000000000000000000023711414223253100243610ustar00rootroot00000000000000# Virtual Machine Manager for Go [![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 (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 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. golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/go.mod000066400000000000000000000000611414223253100242020ustar00rootroot00000000000000module github.com/kata-containers/govmm go 1.16 golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/000077500000000000000000000000001414223253100240465ustar00rootroot00000000000000golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/examples_test.go000066400000000000000000000050661414223253100272610ustar00rootroot00000000000000/* // 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_test import ( "time" "context" "github.com/kata-containers/govmm/qemu" ) 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=on,wait=off") // resources params = append(params, "-m", "370", "-smp", "cpus=2") // 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, 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 } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/image.go000066400000000000000000000052111414223253100254560ustar00rootroot00000000000000/* // 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 ( "context" "fmt" "io/ioutil" "os" "os/exec" "path" "syscall" ) // 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. // 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, attr *syscall.SysProcAttr) 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() { /* #nosec */ _ = os.RemoveAll(configDrivePath) }() err := os.MkdirAll(dataDirPath, 0750) 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) cmd.SysProcAttr = attr err = cmd.Run() if err != nil { return fmt.Errorf("unable to create cloudinit iso image %v", err) } return nil } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/qemu.go000066400000000000000000002514571414223253100253620ustar00rootroot00000000000000/* // 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 provides methods and types for launching and managing QEMU // instances. Instances can be launched with the LaunchQemu function and // managed thereafter via QMPStart and the QMP object that this function // returns. To manage a qemu instance after it has been launched you need // to pass the -qmp option during launch requesting the qemu instance to create // a QMP unix domain manageent socket, e.g., // -qmp unix:/tmp/qmp-socket,server,nowait. For more information see the // example below. package qemu import ( "bytes" "fmt" "log" "os" "os/exec" "runtime" "strconv" "strings" "syscall" "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 // Options are options for the machine type // For example gic-version=host and usb=off 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 QemuParams(config *Config) []string } // DeviceDriver is the device driver string. 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" // 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" // VirtioNetCCW is the virt-io ccw networking device driver. VirtioNetCCW DeviceDriver = "virtio-net-ccw" // VirtioBlock is the block device driver. VirtioBlock DeviceDriver = "virtio-blk" // 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" // VirtioRng is the paravirtualized RNG device driver. VirtioRng DeviceDriver = "virtio-rng" // VirtioBalloon is the memory balloon device driver. VirtioBalloon DeviceDriver = "virtio-balloon" //VhostUserSCSI represents a SCSI vhostuser device type. VhostUserSCSI DeviceDriver = "vhost-user-scsi" //VhostUserNet represents a net vhostuser device type. VhostUserNet DeviceDriver = "virtio-net" //VhostUserBlk represents a block vhostuser device type. VhostUserBlk DeviceDriver = "vhost-user-blk" //VhostUserFS represents a virtio-fs vhostuser device type VhostUserFS DeviceDriver = "vhost-user-fs" // 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" // VfioPCI is the vfio driver with PCI transport. VfioPCI DeviceDriver = "vfio-pci" // 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" // 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" // SpaprTPMProxy is used for enabling guest to run in secure mode on ppc64le. SpaprTPMProxy DeviceDriver = "spapr-tpm-proxy" ) func isDimmSupported(config *Config) bool { switch runtime.GOARCH { case "amd64", "386", "ppc64le", "arm64": if config != nil && config.Machine.Type == MachineTypeMicrovm { // microvm does not support NUMA return false } 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": if config != nil && config.Machine.Type == MachineTypeMicrovm { return TransportMMIO } 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 (transport VirtioTransport) disableModern(config *Config, disable bool) string { if !transport.isVirtioPCI(config) { return "" } if disable { return "disable-modern=true" } return "disable-modern=false" } // 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" // TDXGuest represents a TDX object TDXGuest ObjectType = "tdx-guest" // 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" // PEFGuest represent ppc64le PEF(Protected Execution Facility) object. PEFGuest ObjectType = "pef-guest" ) // Object is a qemu object representation. type Object struct { // Driver is the qemu device driver Driver DeviceDriver // Type is the qemu object type. Type ObjectType // ID is the user defined object ID. ID string // DeviceID is the user defined device ID. DeviceID string // MemPath is the object's memory path. // This is only relevant for memory objects MemPath string // Size is the object size in bytes Size uint64 // Debug this is a debug object Debug bool // 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 // 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. func (object Object) Valid() bool { switch object.Type { case MemoryBackendFile: return object.ID != "" && object.MemPath != "" && object.Size != 0 case TDXGuest: 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 != "" case PEFGuest: return object.ID != "" && object.File != "" default: return false } } // QemuParams returns the qemu parameters built out of this Object device. func (object Object) QemuParams(config *Config) []string { var objectParams []string var deviceParams []string var driveParams []string var qemuParams []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)) 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)) 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)) 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 { qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) } 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 } // 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 // 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 FSDriver = "handle" // Proxy is the proxy qemu filesystem driver. Proxy FSDriver = "proxy" ) const ( // None is like passthrough without failure reports. None SecurityModelType = "none" // PassThrough uses the same credentials on both the host and guest. PassThrough SecurityModelType = "passthrough" // MappedXattr stores some files attributes as extended attributes. MappedXattr SecurityModelType = "mapped-xattr" // MappedFile stores some files attributes in the .virtfs directory. MappedFile SecurityModelType = "mapped-file" ) // FSDevice represents a qemu filesystem configuration. type FSDevice struct { // Driver is the qemu device driver Driver DeviceDriver // FSDriver is the filesystem driver backend. FSDriver FSDriver // ID is the filesystem identifier. ID string // Path is the host root path for this filesystem. Path string // MountTag is the device filesystem mount point tag. MountTag string // SecurityModel is the security model for this filesystem device. SecurityModel SecurityModelType // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // 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 // 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. func (fsdev FSDevice) Valid() bool { if fsdev.ID == "" || fsdev.Path == "" || fsdev.MountTag == "" { return false } return true } // QemuParams returns the qemu parameters built out of this filesystem device. func (fsdev FSDevice) QemuParams(config *Config) []string { var fsParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, fsdev.deviceName(config)) if s := fsdev.Transport.disableModern(config, fsdev.DisableModern); s != "" { deviceParams = append(deviceParams, s) } 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)) } if fsdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } 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)) if fsdev.Multidev != "" { fsParams = append(fsParams, fmt.Sprintf("multidevs=%s", fsdev.Multidev)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) qemuParams = append(qemuParams, "-fsdev") qemuParams = append(qemuParams, strings.Join(fsParams, ",")) 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 const ( // Pipe creates a 2 way connection to the guest. Pipe CharDeviceBackend = "pipe" // Socket creates a 2 way stream socket (TCP or Unix). Socket CharDeviceBackend = "socket" // CharConsole sends traffic from the guest to QEMU's standard output. CharConsole CharDeviceBackend = "console" // Serial sends traffic from the guest to a serial device on the host. Serial CharDeviceBackend = "serial" // TTY is an alias for Serial. TTY CharDeviceBackend = "tty" // 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. type CharDevice struct { Backend CharDeviceBackend // 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 ID string Path string Name string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // 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. func (cdev CharDevice) Valid() bool { if cdev.ID == "" || cdev.Path == "" { return false } return true } // QemuParams returns the qemu parameters built out of this character device. func (cdev CharDevice) QemuParams(config *Config) []string { var cdevParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, cdev.deviceName(config)) if cdev.Driver == VirtioSerial { if s := cdev.Transport.disableModern(config, cdev.DisableModern); s != "" { deviceParams = append(deviceParams, s) } } 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 != "" { 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)) } 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)) } 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=on,wait=off", cdev.Path)) } else { cdevParams = append(cdevParams, fmt.Sprintf("path=%s", cdev.Path)) } // 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, ",")) 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 const ( // TAP is a TAP networking device type. TAP NetDeviceType = "tap" // MACVTAP is a macvtap networking device type. MACVTAP NetDeviceType = "macvtap" // IPVTAP is a ipvtap virtual networking device type. IPVTAP NetDeviceType = "ipvtap" // VETHTAP is a veth-tap virtual networking device type. VETHTAP NetDeviceType = "vethtap" // VFIO is a direct assigned PCI device or PCI VF VFIO NetDeviceType = "VFIO" // VHOSTUSER is a vhost-user port (socket) 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). Type NetDeviceType // Driver is the qemu device driver Driver DeviceDriver // ID is the netdevice identifier. ID string // 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 // Script is the tap interface configuration script. Script string // FDs represents the list of already existing file descriptors to be used. // This is mostly useful for mq support. FDs []*os.File VhostFDs []*os.File // VHost enables virtio device emulation from the host kernel instead of from qemu. VHost bool // MACAddress is the networking device interface MAC address. MACAddress string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // 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. func (netdev NetDevice) Valid() bool { if netdev.ID == "" || netdev.IFName == "" { return false } switch netdev.Type { case TAP: return true case MACVTAP: return true default: return false } } // 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(config *Config) string { p := []string{"mq=on"} 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 // (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 driver := netdev.Type.QemuDeviceParam(&netdev, config) if driver == "" { return nil } 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)) 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 s := netdev.Transport.disableModern(config, netdev.DisableModern); s != "" { deviceParams = append(deviceParams, s) } if len(netdev.FDs) > 0 { // Note: We are appending to the device params here deviceParams = append(deviceParams, netdev.mqParameter(config)) } if netdev.Transport.isVirtioPCI(config) && 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, fmt.Sprintf("devno=%s", netdev.DevNo)) } return deviceParams } // QemuNetdevParams returns the -netdev parameters for this network device func (netdev NetDevice) QemuNetdevParams(config *Config) []string { var netdevParams []string netdevType := netdev.Type.QemuNetdevParam(&netdev, config) if netdevType == "" { return nil } netdevParams = append(netdevParams, netdevType) netdevParams = append(netdevParams, fmt.Sprintf("id=%s", netdev.ID)) if netdev.VHost { 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 { 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.Type.QemuNetdevParam(&netdev, config) != "" { netdevParams = netdev.QemuNetdevParams(config) if netdevParams != nil { qemuParams = append(qemuParams, "-netdev") qemuParams = append(qemuParams, strings.Join(netdevParams, ",")) } } if netdev.Type.QemuDeviceParam(&netdev, config) != "" { deviceParams = netdev.QemuDeviceParams(config) if deviceParams != nil { qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) } } 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 Driver DeviceDriver // ID is the serial device identifier. ID string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // 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. func (dev SerialDevice) Valid() bool { if dev.Driver == "" || dev.ID == "" { return false } return true } // QemuParams returns the qemu parameters built out of this serial device. func (dev SerialDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, dev.deviceName(config)) if s := dev.Transport.disableModern(config, dev.DisableModern); s != "" { deviceParams = append(deviceParams, s) } 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)) if dev.Driver == VirtioSerial && dev.MaxPorts != 0 { 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, fmt.Sprintf("devno=%s", dev.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) 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 // 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 BlockDeviceInterface = "scsi" ) const ( // Threads is the pthread asynchronous I/O implementation. Threads BlockDeviceAIO = "threads" // Native is the pthread asynchronous I/O implementation. Native BlockDeviceAIO = "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 // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // 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 } // 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. func (blkdev BlockDevice) Valid() bool { if blkdev.Driver == "" || blkdev.ID == "" || blkdev.File == "" { return false } return true } // QemuParams returns the qemu parameters built out of this block device. func (blkdev BlockDevice) QemuParams(config *Config) []string { var blkParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, blkdev.deviceName(config)) if s := blkdev.Transport.disableModern(config, blkdev.DisableModern); s != "" { deviceParams = append(deviceParams, s) } deviceParams = append(deviceParams, fmt.Sprintf("drive=%s", blkdev.ID)) if !blkdev.SCSI { deviceParams = append(deviceParams, "scsi=off") } if !blkdev.WCE { deviceParams = append(deviceParams, "config-wce=off") } if blkdev.Transport.isVirtioPCI(config) && 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)) } if blkdev.ShareRW { deviceParams = append(deviceParams, "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)) 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, ",")) qemuParams = append(qemuParams, "-drive") qemuParams = append(qemuParams, strings.Join(blkParams, ",")) 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) } // 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"} } // 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 deviceParams []string 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(deviceParams, ",")) return qemuParams } // 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 Tag string //virtio-fs volume id for mounting inside guest CacheSize uint32 //virtio-fs DAX cache size in MiB SharedVersions bool //enable virtio-fs shared version metadata VhostUserType DeviceDriver // 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 } // 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 func (vhostuserDev VhostUserDevice) Valid() bool { 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: case VhostUserFS: if vhostuserDev.Tag == "" { return false } default: return false } return true } // QemuNetParams builds QEMU netdev and device parameters for a VhostUserNet device func (vhostuserDev VhostUserDevice) QemuNetParams(config *Config) []string { var qemuParams []string var netParams []string var deviceParams []string 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") 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 != "" { 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(deviceParams, ",")) return qemuParams } // QemuSCSIParams builds QEMU device parameters for a VhostUserSCSI device func (vhostuserDev VhostUserDevice) QemuSCSIParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { return nil } 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 != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } // QemuBlkParams builds QEMU device parameters for a VhostUserBlk device func (vhostuserDev VhostUserDevice) QemuBlkParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { return nil } 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 != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } // QemuFSParams builds QEMU device parameters for a VhostUserFS device func (vhostuserDev VhostUserDevice) QemuFSParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := vhostuserDev.deviceName(config) if driver == "" { return nil } 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 { deviceParams = append(deviceParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) } if vhostuserDev.SharedVersions { 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 != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) 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 deviceParams []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: deviceParams = vhostuserDev.QemuNetParams(config) case VhostUserSCSI: deviceParams = vhostuserDev.QemuSCSIParams(config) case VhostUserBlk: deviceParams = vhostuserDev.QemuBlkParams(config) case VhostUserFS: deviceParams = vhostuserDev.QemuFSParams(config) default: return nil } if deviceParams != nil { return append(qemuParams, deviceParams...) } return nil } // 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 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. // 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 *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 b.Transport.isVirtioPCI(config) && 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 BDF string // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // VendorID specifies vendor id VendorID string // DeviceID specifies device id DeviceID string // 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. func (vfioDev VFIODevice) Valid() bool { return vfioDev.BDF != "" } // 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 := vfioDev.deviceName(config) 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)) } if 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)) } } if 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)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) 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 // Bus on which the SCSI controller is attached, this is optional Bus string // Addr is the PCI address offset, this is optional Addr string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // 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 // 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. func (scsiCon SCSIController) Valid() bool { return scsiCon.ID != "" } // QemuParams returns the qemu parameters built out of this SCSIController device. func (scsiCon SCSIController) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := scsiCon.deviceName(config) deviceParams = append(deviceParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) if scsiCon.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) } if scsiCon.Addr != "" { deviceParams = append(deviceParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } if s := scsiCon.Transport.disableModern(config, scsiCon.DisableModern); s != "" { deviceParams = append(deviceParams, s) } if scsiCon.IOThread != "" { deviceParams = append(deviceParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) } if scsiCon.Transport.isVirtioPCI(config) && scsiCon.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) } if scsiCon.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, "iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) 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 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 // PCI Slot Addr string // 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. 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 var deviceParams []string var driver DeviceDriver switch bridgeDev.Type { case PCIEBridge: driver = PCIePCIBridgeDriver 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" } 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 { deviceParams = append(deviceParams, fmt.Sprintf("addr=%x", addr)) } } var transport VirtioTransport if transport.isVirtioPCI(config) && bridgeDev.ROMFile != "" { 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, ",")) return qemuParams } // VSOCKDevice represents a AF_VSOCK socket. type VSOCKDevice struct { ID string ContextID uint64 // VHostFD vhost file descriptor that holds the ContextID VHostFD *os.File // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // 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 ( // MinimalGuestCID is the smallest valid context ID for a guest. MinimalGuestCID uint64 = 3 // MaxGuestCID is the largest valid context ID for a guest. MaxGuestCID uint64 = 1<<32 - 1 ) const ( // 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 || vsock.ContextID > MaxGuestCID { return false } return true } // QemuParams returns the qemu parameters built out of the VSOCK device. func (vsock VSOCKDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string driver := vsock.deviceName(config) deviceParams = append(deviceParams, driver) if s := vsock.Transport.disableModern(config, vsock.DisableModern); 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("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)) } if vsock.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, "iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", vsock.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) 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 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 // ROMFile specifies the ROM file being used for this device. 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. func (v RngDevice) Valid() bool { return v.ID != "" } // QemuParams returns the qemu parameters built out of the RngDevice. func (v RngDevice) QemuParams(config *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, v.deviceName(config)) deviceParams = append(deviceParams, "rng="+v.ID) if v.Transport.isVirtioPCI(config) && v.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", v.ROMFile)) } if v.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, "iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", v.DevNo)) } 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 } // 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 DisableModern bool ID string // ROMFile specifies the ROM file being used for this device. ROMFile string // 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 *Config) []string { var qemuParams []string var deviceParams []string deviceParams = append(deviceParams, b.deviceName(config)) if b.ID != "" { deviceParams = append(deviceParams, "id="+b.ID) } if b.Transport.isVirtioPCI(config) && b.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } if b.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", b.DevNo)) } if b.DeflateOnOOM { deviceParams = append(deviceParams, "deflate-on-oom=on") } else { deviceParams = append(deviceParams, "deflate-on-oom=off") } if s := b.Transport.disableModern(config, b.DisableModern); s != "" { deviceParams = append(deviceParams, s) } 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 { 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] } // 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 // 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 RTCBaseType = "localtime" ) 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" ) 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 RTCDriftFix = "none" ) // RTC represents a qemu Real Time Clock configuration. type RTC struct { // Base is the RTC start time. Base RTCBaseType // Clock is the is the RTC clock driver. Clock RTCClock // DriftFix is the drift fixing mechanism. DriftFix RTCDriftFix } // Valid returns true if the RTC structure is valid and complete. func (rtc RTC) Valid() bool { if rtc.Clock != Host && rtc.Clock != RT && rtc.Clock != VM { return false } if rtc.DriftFix != Slew && rtc.DriftFix != NoDriftFix { return false } return true } // QMPSocketType is the type of socket used for QMP communication. type QMPSocketType string const ( // Unix socket for QMP. Unix QMPSocketType = "unix" ) // QMPSocket represents a qemu QMP socket configuration. type QMPSocket struct { // Type is the socket type (e.g. "unix"). Type QMPSocketType // Name is the socket name. Name string // Server tells if this is a server socket. Server bool // NoWait tells if qemu should block waiting for a client to connect. NoWait bool } // Valid returns true if the QMPSocket structure is valid and complete. func (qmp QMPSocket) Valid() bool { if qmp.Type == "" || qmp.Name == "" { return false } if qmp.Type != Unix { return false } return true } // SMP is the multi processors configuration structure. type SMP struct { // CPUs is the number of VCPUs made available to qemu. CPUs uint32 // Cores is the number of cores made available to qemu. Cores uint32 // Threads is the number of threads made available to qemu. Threads uint32 // Sockets is the number of sockets made available to qemu. Sockets uint32 // 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. 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 // 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. 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 } // 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 for _, f := range config.FwCfg { if f.Name != "" { fwcfgParams = append(fwcfgParams, fmt.Sprintf("name=%s", f.Name)) if f.File != "" { fwcfgParams = append(fwcfgParams, fmt.Sprintf("file=%s", f.File)) } if f.Str != "" { fwcfgParams = append(fwcfgParams, fmt.Sprintf("string=%s", f.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. NoUserConfig bool // NoDefaults prevents qemu from creating default devices. NoDefaults bool // NoGraphic completely disables graphic output. NoGraphic bool // 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 // FileBackedMem requires Memory.Size and Memory.Path of the VM to // be set. FileBackedMem bool // MemShared will set the memory device as shared. MemShared bool // Mlock will control locking of memory Mlock bool // Stopped will not start guest CPU at startup Stopped 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 } // IOThread allows IO to be performed on a separate thread. 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 // MigrationDefer is the defer incoming type MigrationDefer = 3 ) // 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 { // Path is the qemu binary path. Path string // 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 // UUID is the qemu process UUID. UUID string // 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 // QMPSockets is a slice of QMP socket description. QMPSockets []QMPSocket // Devices is a list of devices for qemu to create and drive. Devices []Device // RTC is the qemu Real Time Clock configuration RTC RTC // VGA is the qemu VGA mode. VGA string // Kernel is the guest kernel configuration. Kernel Kernel // Memory is the guest memory configuration. Memory Memory // SMP is the quest multi processors configuration. SMP SMP // GlobalParam is the -global parameter. GlobalParam string // Knobs is a set of qemu boolean settings. Knobs Knobs // Bios is the -bios parameter Bios string // PFlash specifies the parallel flash images (-pflash parameter) PFlash []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 // FwCfg is the -fw_cfg parameter FwCfg []FwCfg IOThreads []IOThread // PidFile is the -pidfile parameter PidFile string // LogFile is the -D parameter LogFile string 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) 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") config.qemuParams = append(config.qemuParams, config.Name) } } func (config *Config) appendMachine() { if config.Machine.Type != "" { var machineParams []string machineParams = append(machineParams, config.Machine.Type) if config.Machine.Acceleration != "" { machineParams = append(machineParams, fmt.Sprintf("accel=%s", config.Machine.Acceleration)) } if config.Machine.Options != "" { machineParams = append(machineParams, config.Machine.Options) } config.qemuParams = append(config.qemuParams, "-machine") config.qemuParams = append(config.qemuParams, strings.Join(machineParams, ",")) } } func (config *Config) appendCPUModel() { if config.CPUModel != "" { config.qemuParams = append(config.qemuParams, "-cpu") config.qemuParams = append(config.qemuParams, config.CPUModel) } } func (config *Config) appendQMPSockets() { for _, q := range config.QMPSockets { if !q.Valid() { continue } qmpParams := append([]string{}, fmt.Sprintf("%s:%s", q.Type, q.Name)) if q.Server { qmpParams = append(qmpParams, "server=on") if q.NoWait { qmpParams = append(qmpParams, "wait=off") } } config.qemuParams = append(config.qemuParams, "-qmp") config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, ",")) } } func (config *Config) appendDevices() { for _, d := range config.Devices { if !d.Valid() { continue } config.qemuParams = append(config.qemuParams, d.QemuParams(config)...) } } func (config *Config) appendUUID() { if config.UUID != "" { config.qemuParams = append(config.qemuParams, "-uuid") config.qemuParams = append(config.qemuParams, config.UUID) } } func (config *Config) appendMemory() { if config.Memory.Size != "" { var memoryParams []string memoryParams = append(memoryParams, config.Memory.Size) if config.Memory.Slots > 0 { memoryParams = append(memoryParams, fmt.Sprintf("slots=%d", config.Memory.Slots)) } if config.Memory.MaxMem != "" { memoryParams = append(memoryParams, fmt.Sprintf("maxmem=%s", config.Memory.MaxMem)) } config.qemuParams = append(config.qemuParams, "-m") config.qemuParams = append(config.qemuParams, strings.Join(memoryParams, ",")) } } func (config *Config) appendCPUs() error { 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)) } 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() { if !config.RTC.Valid() { return } var RTCParams []string RTCParams = append(RTCParams, fmt.Sprintf("base=%s", string(config.RTC.Base))) if config.RTC.DriftFix != "" { RTCParams = append(RTCParams, fmt.Sprintf("driftfix=%s", config.RTC.DriftFix)) } if config.RTC.Clock != "" { RTCParams = append(RTCParams, fmt.Sprintf("clock=%s", config.RTC.Clock)) } config.qemuParams = append(config.qemuParams, "-rtc") config.qemuParams = append(config.qemuParams, strings.Join(RTCParams, ",")) } func (config *Config) appendGlobalParam() { if config.GlobalParam != "" { config.qemuParams = append(config.qemuParams, "-global") config.qemuParams = append(config.qemuParams, config.GlobalParam) } } func (config *Config) 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") config.qemuParams = append(config.qemuParams, config.VGA) } } func (config *Config) appendKernel() { if config.Kernel.Path != "" { config.qemuParams = append(config.qemuParams, "-kernel") config.qemuParams = append(config.qemuParams, config.Kernel.Path) if config.Kernel.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) } } } func (config *Config) appendMemoryKnobs() { 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) 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() { if config.Knobs.NoUserConfig { config.qemuParams = append(config.qemuParams, "-no-user-config") } if config.Knobs.NoDefaults { config.qemuParams = append(config.qemuParams, "-nodefaults") } if config.Knobs.NoGraphic { config.qemuParams = append(config.qemuParams, "-nographic") } if config.Knobs.NoReboot { 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") } config.appendMemoryKnobs() if config.Knobs.Mlock { config.qemuParams = append(config.qemuParams, "-overcommit") config.qemuParams = append(config.qemuParams, "mem-lock=on") } if config.Knobs.Stopped { config.qemuParams = append(config.qemuParams, "-S") } } func (config *Config) appendBios() { if config.Bios != "" { config.qemuParams = append(config.qemuParams, "-bios") config.qemuParams = append(config.qemuParams, config.Bios) } } 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)) } } } 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]) case MigrationDefer: uri = "defer" default: return } 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) } } func (config *Config) appendLogFile() { if config.LogFile != "" { config.qemuParams = append(config.qemuParams, "-D") config.qemuParams = append(config.qemuParams, config.LogFile) } } func (config *Config) appendFwCfg(logger QMPLog) { if logger == nil { logger = qmpNullLogger{} } for _, f := range config.FwCfg { if !f.Valid() { logger.Errorf("fw_cfg is not valid: %+v", config.FwCfg) continue } config.qemuParams = append(config.qemuParams, f.QemuParams(config)...) } } // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. // // This function writes its log output via logger parameter. // // The function will block until the launched qemu process exits. "", nil // will be returned if the launch succeeds. Otherwise a string containing // the contents of stderr + a Go error object will be returned. func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendName() config.appendUUID() config.appendMachine() config.appendCPUModel() config.appendQMPSockets() config.appendMemory() config.appendDevices() config.appendRTC() config.appendGlobalParam() config.appendPFlashParam() config.appendVGA() config.appendKnobs() config.appendKernel() config.appendBios() config.appendIOThreads() config.appendIncoming() config.appendPidFile() config.appendLogFile() config.appendFwCfg(logger) config.appendSeccompSandbox() if err := config.appendCPUs(); err != nil { return "", err } ctx := config.Ctx if ctx == nil { 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, &attr, logger) } // LaunchCustomQemu can be used to launch a new qemu instance. // // The path parameter is used to pass the qemu executable path. // // 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 // 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, attr *syscall.SysProcAttr, logger QMPLog) (string, error) { if logger == nil { logger = qmpNullLogger{} } errStr := "" if path == "" { path = "qemu-system-x86_64" } /* #nosec */ cmd := exec.CommandContext(ctx, path, params...) if len(fds) > 0 { logger.Infof("Adding extra file %v", fds) cmd.ExtraFiles = fds } cmd.SysProcAttr = attr var stderr bytes.Buffer cmd.Stderr = &stderr logger.Infof("launching %s with: %v", path, params) err := cmd.Run() if err != nil { logger.Errorf("Unable to launch %s: %v", path, err) errStr = stderr.String() logger.Errorf("%s", errStr) } return errStr, err } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/qemu_arch_base_test.go000066400000000000000000000163351414223253100304020ustar00rootroot00000000000000//go:build !s390x // +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" 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,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" 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,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" 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" ) 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) + "-" + string(TransportPCI) 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) } 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, } testAppend(vfioDevice, deviceVFIOPCIeSimpleString, t) // full test pcieRootPortID = "rp1" vfioDevice = VFIODevice{ BDF: "02:00.0", Bus: pcieRootPortID, ROMFile: romfile, VendorID: "0x10de", DeviceID: "0x15f8", } testAppend(vfioDevice, deviceVFIOPCIeFullString, t) } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/qemu_s390x_test.go000066400000000000000000000103051414223253100273500ustar00rootroot00000000000000// +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,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 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-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 = "" ) func TestAppendVirtioBalloon(t *testing.T) { balloonDevice := BalloonDevice{ ID: "balloon", } var deviceString = "-device " + string(VirtioBalloon) + "-" + string(TransportCCW) 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+devnoOptios+OffDeflateOnOMM, 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) } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/qemu_test.go000066400000000000000000000705401414223253100264110ustar00rootroot00000000000000/* // 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 ( "fmt" "io/ioutil" "os" "reflect" "strings" "testing" ) 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) } func testConfigAppend(config *Config, structure interface{}, expected string, t *testing.T) { switch s := structure.(type) { case Machine: config.Machine = s config.appendMachine() case FwCfg: config.FwCfg = []FwCfg{s} config.appendFwCfg(nil) case Device: config.Devices = []Device{s} config.appendDevices() case Knobs: config.Knobs = s config.appendKnobs() case Kernel: config.Kernel = s config.appendKernel() case Memory: config.Memory = s config.appendMemory() case SMP: config.SMP = s if err := config.appendCPUs(); err != nil { t.Fatalf("Unexpected error: %v", err) } case QMPSocket: config.QMPSockets = []QMPSocket{s} config.appendQMPSockets() case []QMPSocket: config.QMPSockets = s config.appendQMPSockets() case RTC: config.RTC = s config.appendRTC() case IOThread: config.IOThreads = []IOThread{s} config.appendIOThreads() case Incoming: config.Incoming = s config.appendIncoming() } result := strings.Join(config.qemuParams, " ") if result != expected { t.Fatalf("Failed to append parameters [%s] != [%s]", result, expected) } } 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) 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) { machine := Machine{} testAppend(machine, "", t) } 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{ Driver: NVDIMM, Type: MemoryBackendFile, DeviceID: "nv0", ID: "mem0", MemPath: "/root", Size: 1 << 16, ReadOnly: true, } testAppend(object, deviceNVDIMMString, t) } func TestAppendDeviceFS(t *testing.T) { 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, } if fsdev.Transport.isVirtioCCW(nil) { fsdev.DevNo = DevNo } testAppend(fsdev, deviceFSString, t) } 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, ROMFile: "efi-virtio.rom", } if netdev.Transport.isVirtioCCW(nil) { netdev.DevNo = DevNo } testAppend(netdev, deviceNetworkString, t) } func TestAppendDeviceNetworkMq(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") defer func() { _ = foo.Close() _ = bar.Close() _ = os.Remove(foo.Name()) _ = 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", DisableModern: true, ROMFile: "efi-virtio.rom", } if netdev.Transport.isVirtioCCW(nil) { netdev.DevNo = DevNo } testAppend(netdev, deviceNetworkStringMq, t) } func TestAppendDeviceNetworkPCI(t *testing.T) { netdev := NetDevice{ Driver: VirtioNet, 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, ROMFile: romfile, } if !netdev.Transport.isVirtioPCI(nil) { t.Skip("Test valid only for PCI devices") } testAppend(netdev, deviceNetworkPCIString, t) } func TestAppendDeviceNetworkPCIMq(t *testing.T) { foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") defer func() { _ = foo.Close() _ = bar.Close() _ = os.Remove(foo.Name()) _ = os.Remove(bar.Name()) }() netdev := NetDevice{ Driver: VirtioNet, 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, ROMFile: romfile, } if !netdev.Transport.isVirtioPCI(nil) { t.Skip("Test valid only for PCI devices") } 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, ID: "serial0", DisableModern: true, ROMFile: romfile, MaxPorts: 2, } if sdev.Transport.isVirtioCCW(nil) { sdev.DevNo = DevNo } testAppend(sdev, deviceSerialString, t) } 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{ Driver: VirtioSerialPort, Backend: Socket, ID: "char0", DeviceID: "channel0", Path: "/tmp/char.sock", Name: "channel.0", } if chardev.Transport.isVirtioCCW(nil) { chardev.DevNo = DevNo } testAppend(chardev, deviceSerialPortString, t) } func TestAppendDeviceBlock(t *testing.T) { blkdev := BlockDevice{ Driver: VirtioBlock, ID: "hd0", File: "/var/lib/vm.img", AIO: Threads, Format: QCOW2, Interface: NoInterface, SCSI: false, WCE: false, DisableModern: true, ROMFile: romfile, ShareRW: true, ReadOnly: true, } if blkdev.Transport.isVirtioCCW(nil) { blkdev.DevNo = DevNo } testAppend(blkdev, deviceBlockString, t) } func TestAppendDeviceVFIO(t *testing.T) { vfioDevice := VFIODevice{ BDF: "02:10.0", ROMFile: romfile, VendorID: "0x1234", DeviceID: "0x5678", } if vfioDevice.Transport.isVirtioCCW(nil) { vfioDevice.DevNo = DevNo } testAppend(vfioDevice, deviceVFIOString, t) } func TestAppendVSOCK(t *testing.T) { vsockDevice := VSOCKDevice{ ID: "vhost-vsock-pci0", ContextID: 4, VHostFD: nil, DisableModern: true, ROMFile: romfile, } if vsockDevice.Transport.isVirtioCCW(nil) { vsockDevice.DevNo = DevNo } testAppend(vsockDevice, deviceVSOCKString, t) } func TestVSOCKValid(t *testing.T) { vsockDevice := VSOCKDevice{ ID: "vhost-vsock-pci0", ContextID: MinimalGuestCID - 1, VHostFD: nil, DisableModern: true, } if vsockDevice.Valid() { 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() { t.Fatalf("VSOCK ID is not valid") } } func TestAppendVirtioRng(t *testing.T) { var objectString = "-object rng-random,id=rng0" var deviceString = "-device " + string(VirtioRng) rngDevice := RngDevice{ ID: "rng0", ROMFile: romfile, } deviceString += "-" + 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 } 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") } } 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") } } func TestAppendDeviceSCSIController(t *testing.T) { scsiCon := SCSIController{ ID: "foo", ROMFile: romfile, } if scsiCon.Transport.isVirtioCCW(nil) { scsiCon.DevNo = DevNo } testAppend(scsiCon, deviceSCSIControllerStr, t) scsiCon.Bus = "pci.0" scsiCon.Addr = "00:04.0" scsiCon.DisableModern = true scsiCon.IOThread = "iothread1" testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) } func TestAppendPCIBridgeDevice(t *testing.T) { bridge := BridgeDevice{ Type: PCIBridge, ID: "mybridge", Bus: "/pci-bus/pcie.0", Addr: "255", Chassis: 5, SHPC: true, ROMFile: romfile, } 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{ Type: PCIEBridge, ID: "mybridge", Bus: "/pci-bus/pcie.0", Addr: "255", ROMFile: "efi-virtio.rom", } testAppend(bridge, devicePCIEBridgeString, t) } func TestAppendEmptyDevice(t *testing.T) { device := SerialDevice{} testAppend(device, "", t) } func TestAppendKnobsAllTrue(t *testing.T) { var knobsString = "-no-user-config -nodefaults -nographic --no-reboot -daemonize -overcommit mem-lock=on -S" knobs := Knobs{ NoUserConfig: true, NoDefaults: true, NoGraphic: true, NoReboot: true, Daemonize: true, MemPrealloc: true, FileBackedMem: true, MemShared: true, Mlock: true, Stopped: true, } testAppend(knobs, knobsString, t) } func TestAppendKnobsAllFalse(t *testing.T) { var knobsString = "" knobs := Knobs{ NoUserConfig: false, NoDefaults: false, NoGraphic: false, NoReboot: false, MemPrealloc: false, FileBackedMem: false, MemShared: false, Mlock: false, Stopped: false, } 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, MemShared: true, } 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" knobsString := objMemString + " " if isDimmSupported(nil) { knobsString += numaMemString } else { knobsString += memBackendString } testConfigAppend(conf, knobs, memString+" "+knobsString, 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, MemShared: true, } objMemString := "-object memory-backend-ram,id=dimm1,size=1G,share=on,prealloc=on" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" knobsString := objMemString + " " if isDimmSupported(nil) { knobsString += numaMemString } else { knobsString += memBackendString } testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestAppendMemoryMemShared(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, } objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" knobsString := objMemString + " " if isDimmSupported(nil) { knobsString += numaMemString } else { knobsString += memBackendString } testConfigAppend(conf, knobs, memString+" "+knobsString, 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, MemShared: false, } objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar" numaMemString := "-numa node,memdev=dimm1" memBackendString := "-machine memory-backend=dimm1" knobsString := objMemString + " " if isDimmSupported(nil) { knobsString += numaMemString } else { knobsString += memBackendString } testConfigAppend(conf, knobs, memString+" "+knobsString, 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, } 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" knobsString := objMemString + " " if isDimmSupported(nil) { knobsString += numaMemString } else { knobsString += memBackendString } testConfigAppend(conf, knobs, memString+" "+knobsString, t) } func TestNoRebootKnob(t *testing.T) { conf := &Config{} knobs := Knobs{ NoReboot: true, } knobsString := "--no-reboot" 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" func TestAppendKernel(t *testing.T) { kernel := Kernel{ 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) } var memoryString = "-m 2G,slots=2,maxmem=3G" func TestAppendMemory(t *testing.T) { memory := Memory{ Size: "2G", Slots: 2, MaxMem: "3G", Path: "", } testAppend(memory, memoryString, t) } var cpusString = "-smp 2,cores=1,threads=2,sockets=2,maxcpus=6" func TestAppendCPUs(t *testing.T) { smp := SMP{ CPUs: 2, 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=on,wait=off" var qmpSingleSocketString = "-qmp unix:cc-qmp" func TestAppendSingleQMPSocketServer(t *testing.T) { qmp := QMPSocket{ Type: "unix", Name: "cc-qmp", Server: true, NoWait: true, } testAppend(qmp, qmpSingleSocketServerString, t) } func TestAppendSingleQMPSocket(t *testing.T) { qmp := QMPSocket{ Type: Unix, Name: "cc-qmp", Server: false, } testAppend(qmp, qmpSingleSocketString, t) } 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{ { 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 pidfile = "/run/vc/vm/iamsandboxid/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{ Path: "qemu", Name: "cc-qemu", 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 { 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) } var ioThreadString = "-object iothread,id=iothread1" func TestAppendIOThread(t *testing.T) { ioThread := IOThread{ ID: "iothread1", } 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) } 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() 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 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 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() 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{ MemShared: 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) } } 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") } } 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" ) 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) } 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) } 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) } } 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) } } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/qmp.go000066400000000000000000001470521414223253100252030ustar00rootroot00000000000000/* // 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 ( "bufio" "container/list" "encoding/json" "errors" "fmt" "io" "net" "os" "strconv" "syscall" "time" "context" "strings" ) // 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 // specify the capacity of buffer used by receive QMP response. MaxCapacity int } 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 { response interface{} err error } type qmpCommand struct { ctx context.Context res chan qmpResult name string args map[string]interface{} filter *qmpEventFilter resultReceived bool oob []byte } // 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{} version *QMPVersion } // 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 } // CPUProperties contains the properties of a CPU instance 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"` } // 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"` } // 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"` } // 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"` } // 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"` } // SchemaInfo represents all QMP wire ABI type SchemaInfo struct { MetaType string `json:"meta-type"` 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 { buffer := make([]byte, q.cfg.MaxCapacity) scanner.Buffer(buffer, q.cfg.MaxCapacity) } for scanner.Scan() { line := scanner.Bytes() // 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 } q.cfg.Logger.Infof("scanner return error: %v", scanner.Err()) 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) 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{response: response} } else { cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed: %v", response)} } } if cmdQueue.Len() > 0 { q.writeNextQMPCommand(cmdQueue) } } func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeeded bool) { 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) 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 } response, succeeded := vmData["return"] errData, 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 { 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 } } 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) 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) } encodedCmd = append(encodedCmd, '\n') 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), } 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) 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) 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 { 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, } } // 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) go q.readLoop(fromVMCh) defer func() { if q.cfg.EventCh != nil { close(q.cfg.EventCh) } /* #nosec */ _ = q.conn.Close() <-fromVMCh failOutstandingCommands(cmdQueue) close(q.disconnectedCh) }() var cmdDoneCh <-chan struct{} var version *QMPVersion ready := false for { select { case cmd, ok := <-q.cmdCh: if !ok { return } _ = cmdQueue.PushBack(&cmd) // 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) } } } 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) executeCommandWithResponse(ctx context.Context, name string, args map[string]interface{}, oob []byte, filter *qmpEventFilter) (interface{}, error) { var err error var response interface{} 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, oob: oob, }: } if err != nil { 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, nil, filter) 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. // // 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{} } 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) 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 q.version = <-connectedCh: if q.version == nil { return nil, nil, fmt.Errorf("failed to find QMP version information") } } 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 } // 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: "POWERDOWN", } 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) } 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": driver, "filename": device, }, } blockdevArgs["node-name"] = blockdevID args = blockdevArgs 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, ro bool) error { args, _ := q.blockdevAddBaseArgs("host_device", device, blockdevID, ro) 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, ro bool) error { 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, "no-flush": noFlush, } 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. // 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) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, "drive": blockdevID, } var transport VirtioTransport if transport.isVirtioCCW(nil) { args["devno"] = bus } else if bus != "" { args["bus"] = bus } if shared { args["share-rw"] = "on" } if transport.isVirtioPCI(nil) { args["romfile"] = romfile if disableModern { args["disable-modern"] = disableModern } } 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. 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) 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"} 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 } if shared { args["share-rw"] = "on" } return q.executeCommand(ctx, "device_add", args, nil) } // 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{}{} args["node-name"] = blockdevID return q.executeCommand(ctx, "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. 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) } // 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. func (q *QMP) ExecuteNetdevAddByFds(ctx context.Context, netdevType, netdevID string, fdNames, vhostFdNames []string) error { fdNameStr := strings.Join(fdNames, ":") args := map[string]interface{}{ "type": netdevType, "id": netdevID, "fds": fdNameStr, } if len(vhostFdNames) > 0 { vhostFdNameStr := strings.Join(vhostFdNames, ":") args["vhost"] = true args["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 { 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. // queues is the number of queues of a nic. // 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, "romfile": romfile, } if bus != "" { args["bus"] = bus } if addr != "" { args["addr"] = addr } if macAddr != "" { args["mac"] = macAddr } 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) // -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) } // 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, bus string, queues int) error { args := map[string]interface{}{ "id": devID, "driver": VirtioNetCCW, "netdev": netdevID, "mac": macAddr, "devno": bus, } 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 // 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) } // 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. 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, queues int, shared, disableModern bool) error { args := map[string]interface{}{ "id": devID, "driver": driver, "drive": blockdevID, "addr": addr, } if bus != "" { args["bus"] = bus } if shared { args["share-rw"] = "on" } if queues > 0 { args["num-queues"] = strconv.Itoa(queues) } var transport VirtioTransport if transport.isVirtioPCI(nil) { args["romfile"] = romfile if disableModern { args["disable-modern"] = disableModern } } 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 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": driver, "host": bdf, "romfile": romfile, } if bus != "" { args["bus"] = bus } 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, romfile string) error { args := map[string]interface{}{ "id": devID, "driver": VfioPCI, "host": bdf, "addr": addr, "romfile": romfile, } if bus != "" { args["bus"] = 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 or root bus, unlike // 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, romfile string) error { args := map[string]interface{}{ "id": devID, "driver": VfioPCI, "sysfsdev": sysfsdev, "romfile": romfile, } if bus != "" { args["bus"] = bus } if addr != "" { args["addr"] = addr } 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" { 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 { return driver == "host-x86_64-cpu" } // 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. Note that socketID and threadID are not a requirement for // architecures like ppc64le. func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, dieID, coreID, threadID, romfile string) error { args := map[string]interface{}{ "driver": driver, "id": cpuID, "core-id": coreID, } if socketID != "" && isSocketIDSupported(driver) { args["socket-id"] = socketID } if threadID != "" && isThreadIDSupported(driver) { args["thread-id"] = threadID } if q.isDieIDSupported(driver) { if dieID != "" { args["die-id"] = dieID } } 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, 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 } // 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) } // 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) } // 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 } // 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 } // 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 { args := map[string]interface{}{ "qom-type": qomtype, "id": id, "size": uint64(size) << 20, } if mempath != "" { args["mem-path"] = mempath } if share { args["share"] = true } err := q.executeCommand(ctx, "object-add", args, nil) if err != nil { return err } defer func() { if err != nil { 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 %s: %v", id, err) } } }() args = map[string]interface{}{ "driver": driver, "id": driverID, "memdev": id, } if bus != "" { args["bus"] = bus } if addr != "" { args["addr"] = addr } err = q.executeCommand(ctx, "device_add", args, nil) 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. // mempath is the path of the device to add, e.g., /dev/rdb0. size is // 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, "mem-path": mempath, "size": size, "share": true, } if pmem != nil { args["pmem"] = *pmem } 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 { args := map[string]interface{}{ "value": bytes, } return q.executeCommand(ctx, "balloon", args, nil) } // 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": VHostVSockPCI, "id": id, "guest-cid": guestCID, "vhostfd": vhostfd, "addr": addr, "romfile": romfile, } if bus != "" { args["bus"] = bus } if disableModern { args["disable-modern"] = disableModern } 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 } // 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) } // 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) } // 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 } // 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) } // 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 } // 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 } // 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) } // 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 } // 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) } golang-github-kata-containers-govmm-0.0~git20211108.e438cc5/qemu/qmp_test.go000066400000000000000000001516771414223253100262520ustar00rootroot00000000000000/* // 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 ( "bytes" "encoding/json" "errors" "fmt" "log" "os" "reflect" "sync" "testing" "time" "context" ) const ( microStr = "50" minorStr = "9" majorStr = "2" micro = 50 minor = 9 major = 2 cap1 = "one" cap2 = "two" qmpHello = `{ "QMP": { "version": { "qemu": { "micro": ` + microStr + `, "minor": ` + minorStr + `, "major": ` + majorStr + ` }, "package": ""}, "capabilities": ["` + cap1 + `","` + cap2 + `"]}}` + "\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 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 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() { 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) AddCommand(name string, args map[string]interface{}, result string, data interface{}) { b.cmds = append(b.cmds, qmpTestCommand{name, args}) 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{} currentCmd := b.currentCmd b.currentCmd++ if 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[currentCmd].result if gotCmdName != b.cmds[currentCmd].name { b.t.Errorf("Unexpected command. Expected %s found %s", b.cmds[currentCmd].name, gotCmdName) result = "error" } resultMap := make(map[string]interface{}) resultMap[result] = b.results[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) *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") } } return version } // 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 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 // 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.AddCommand("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 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 // 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.AddCommand("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.AddCommand("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.AddCommand("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.AddCommand("blockdev-add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevAdd(context.Background(), "/dev/rbd0", fmt.Sprintf("drive_%s", volumeUUID), false) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-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, false) 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. // // 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_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. // // 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) 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, []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) } 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", "", "", 8, false) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-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", DevNo, 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. // // 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.AddCommand("device_add", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 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", "", "", true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-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) 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, true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-disconnectedCh } // Checks that the blockdev-del command is correctly sent. // // We start a QMPLoop, send the blockdev-del command and stop the loop. // // The blockdev-del command should be correctly sent and the QMP loop should // exit gracefully. func TestQMPBlockdevDel(t *testing.T) { connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) buf.AddCommand("blockdev-del", nil, "return", nil) cfg := QMPConfig{Logger: qmpTestLogger{}} q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) q.version = checkVersion(t, connectedCh) err := q.ExecuteBlockdevDel(context.Background(), fmt.Sprintf("drive_%s", volumeUUID)) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-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. // 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 = int64(1352167040730) microsecondsEv1 = 123456 microsecondsEv2 = 123556 device = "device_" + volumeUUID path = "/dev/rbd0" ) var wg sync.WaitGroup connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) buf.AddCommand("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", 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) { var wg sync.WaitGroup connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) buf.AddCommand("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", 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.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) 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 = int64(1352167040730) microsecondsEv1 = 123456 ) var wg sync.WaitGroup connectedCh := make(chan *QMPVersion) disconnectedCh := make(chan struct{}) buf := newQMPTestCommandBuffer(t) buf.AddCommand("system_powerdown", nil, "return", nil) buf.AddEvent("POWERDOWN", 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 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.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) 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.AddCommand("system_powerdown", nil, "error", nil) buf.AddCommand("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. // 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 = int64(1352167040730) microsecondsEv1 = 123456 microsecondsEv2 = 123556 device = "device_" + 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_"+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.AddCommand("qmp_capabilities", nil, "return", nil) err := q.ExecuteQMPCapabilities(context.Background()) if err == nil { t.Error("Expected executeQMPCapabilities to fail") } <-disconnectedCh buf.AddCommand("qmp_capabilities", nil, "return", nil) err = q.ExecuteQMPCapabilities(context.Background()) if err == nil { 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) 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", "", "", 1, true, false) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-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 } 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 } 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"} cpuID := "cpu-0" socketID := "0" dieID := "0" coreID := "1" threadID := "0" 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) err := q.ExecuteCPUDeviceAdd(context.Background(), d, cpuID, socketID, dieID, coreID, threadID, "") if err != nil { t.Fatalf("Unexpected error %v", err) } 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, Die: 1, 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", 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", hotCPUs[0], hotCPU) } q.Shutdown() <-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", 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", memDevices[0], memoryDevices) } q.Shutdown() <-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", 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", 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", 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", cpus[0], cpuInfoFast) } 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", err) } 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", err) } q.Shutdown() <-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", "0x1", "pci-bridge-0") if err != nil { t.Fatalf("Unexpected error: %v", err) } 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, true) if err != nil { t.Fatalf("Unexpected error: %v", err) } 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", "1", "1", "1", "", true) if err != nil { t.Fatalf("Unexpected error %v", err) } q.Shutdown() <-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) 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 } // 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 } // 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 } // 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) 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 got %v", status, s) } 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 } 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 } // 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) pmem := true err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024, &pmem) if err != nil { t.Fatalf("Unexpected error: %v", err) } q.Shutdown() <-disconnectedCh } func TestMainLoopEventBeforeGreeting(t *testing.T) { const ( seconds = int64(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 } 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", 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", info, schemaInfo) } 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", err) } if reflect.DeepEqual(info, statusInfo) == false { t.Fatalf("Expected %v equals to %v", info, statusInfo) } 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 } // 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 } // 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 }