├── .github └── workflows │ └── main.yml ├── .gitignore ├── .golangci.yml ├── .travis.yml ├── CONTRIBUTING.md ├── COPYING ├── README.md ├── go.mod └── qemu ├── examples_test.go ├── image.go ├── qemu.go ├── qemu_arch_base_test.go ├── qemu_s390x_test.go ├── qemu_test.go ├── qmp.go └── qmp_test.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: ["pull_request"] 2 | name: Unit tests 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.15.x, 1.16.x] 8 | os: [ubuntu-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: golangci-lint 18 | uses: golangci/golangci-lint-action@v2 19 | with: 20 | version: latest 21 | args: -c .golangci.yml -v 22 | - name: go test 23 | run: go test ./... 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Intel Corporation 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | run: 6 | concurrency: 4 7 | deadline: 600s 8 | skip-dirs: 9 | - vendor 10 | # Ignore auto-generated protobuf code. 11 | skip-files: 12 | - ".*\\.pb\\.go$" 13 | 14 | linters: 15 | disable-all: true 16 | enable: 17 | - deadcode 18 | - gocyclo 19 | - gofmt 20 | - gosimple 21 | - govet 22 | - ineffassign 23 | - misspell 24 | - staticcheck 25 | - structcheck 26 | - typecheck 27 | - unconvert 28 | - unused 29 | - varcheck 30 | 31 | linters-settings: 32 | gocyclo: 33 | min_complexity: 15 34 | unused: 35 | check-exported: true 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10" 5 | - "1.11" 6 | - tip 7 | arch: 8 | - s390x 9 | 10 | go_import_path: github.com/kata-containers/govmm 11 | 12 | matrix: 13 | allow_failures: 14 | - go: tip 15 | 16 | before_install: 17 | - go get github.com/alecthomas/gometalinter 18 | - gometalinter --install 19 | - go get github.com/mattn/goveralls 20 | 21 | script: 22 | - go env 23 | - 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 ./... 24 | 25 | after_success: 26 | - $GOPATH/bin/goveralls -repotoken $COVERALLS_TOKEN -v -service=travis-ci 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Virtual Machine Manager for Go 2 | 3 | Virtual Machine Manager for Go is an open source project licensed under the [Apache v2 License] (https://opensource.org/licenses/Apache-2.0) 4 | 5 | ## Coding Style 6 | 7 | Virtual Machine Manager for Go follows the standard formatting recommendations and language idioms set out 8 | in [Effective Go](https://golang.org/doc/effective_go.html) and in the 9 | [Go Code Review Comments wiki](https://github.com/golang/go/wiki/CodeReviewComments). 10 | 11 | ## Certificate of Origin 12 | 13 | In order to get a clear contribution chain of trust we use the [signed-off-by language] (https://01.org/community/signed-process) 14 | used by the Linux kernel project. 15 | 16 | ## Patch format 17 | 18 | Beside the signed-off-by footer, we expect each patch to comply with the following format: 19 | 20 | ``` 21 | Change summary 22 | 23 | More detailed explanation of your changes: Why and how. 24 | Wrap it to 72 characters. 25 | See [here] (http://chris.beams.io/posts/git-commit/) 26 | for some more good advices. 27 | 28 | Fixes #NUMBER (or URL to the issue) 29 | 30 | Signed-off-by: 31 | ``` 32 | 33 | For example: 34 | 35 | ``` 36 | Fix poorly named identifiers 37 | 38 | One identifier, fnname, in func.go was poorly named. It has been renamed 39 | to fnName. Another identifier retval was not needed and has been removed 40 | entirely. 41 | 42 | Fixes #1 43 | 44 | Signed-off-by: Mark Ryan 45 | ``` 46 | 47 | ## New files 48 | 49 | Each Go source file in the Virtual Machine Manager for Go project must 50 | contain the following header: 51 | 52 | ``` 53 | /* 54 | // Copyright contributors to the Virtual Machine Manager for Go project 55 | // 56 | // Licensed under the Apache License, Version 2.0 (the "License"); 57 | // you may not use this file except in compliance with the License. 58 | // You may obtain a copy of the License at 59 | // 60 | // http://www.apache.org/licenses/LICENSE-2.0 61 | // 62 | // Unless required by applicable law or agreed to in writing, software 63 | // distributed under the License is distributed on an "AS IS" BASIS, 64 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | // See the License for the specific language governing permissions and 66 | // limitations under the License. 67 | */ 68 | ``` 69 | 70 | ## Contributors File 71 | 72 | This CONTRIBUTORS.md file is a partial list of contributors to the 73 | Virtual Machine Manager for Go project. To see the full list of 74 | contributors, see the revision history in source control. 75 | 76 | Contributors who wish to be recognized in this file should add 77 | themselves (or their employer, as appropriate). 78 | 79 | ## Pull requests 80 | 81 | We accept github pull requests. 82 | 83 | ## Quality Controls 84 | 85 | We request you give quality assurance some consideration by: 86 | 87 | * Adding go unit tests for changes where it makes sense. 88 | * 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. 89 | 90 | ## Issue tracking 91 | 92 | If you have a problem, please let us know. If it's a bug not already documented, by all means please [open an 93 | issue in github](https://github.com/kata-containers/govmm/issues/new) so we all get visibility 94 | the problem and work toward resolution. 95 | 96 | Any security issues discovered with govmm should be reported by following the instructions on https://01.org/security. 97 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Virtual Machine Manager for Go 2 | 3 | This project has been archived, as it's now [part of Kata 4 | Containers](https://github.com/kata-containers/kata-containers/tree/main/src/runtime/pkg/govmm). 5 | 6 | For issues, or pull-request, please, submit them directly to [Kata 7 | Containers](https://github.com/kata-containers/kata-containers), following [the 8 | Kata Containers' project 9 | guidelines](https://github.com/kata-containers/community/blob/main/CONTRIBUTING.md) 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kata-containers/govmm 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /qemu/examples_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright contributors to the Virtual Machine Manager for Go project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | */ 16 | 17 | package qemu_test 18 | 19 | import ( 20 | "time" 21 | 22 | "context" 23 | 24 | "github.com/kata-containers/govmm/qemu" 25 | ) 26 | 27 | func Example() { 28 | params := make([]string, 0, 32) 29 | 30 | // Rootfs 31 | params = append(params, "-drive", "file=/tmp/image.qcow2,if=virtio,aio=threads,format=qcow2") 32 | // Network 33 | params = append(params, "-net", "nic,model=virtio", "-net", "user") 34 | // kvm 35 | params = append(params, "-enable-kvm", "-cpu", "host") 36 | // qmp socket 37 | params = append(params, "-daemonize", "-qmp", "unix:/tmp/qmp-socket,server=on,wait=off") 38 | // resources 39 | params = append(params, "-m", "370", "-smp", "cpus=2") 40 | 41 | // LaunchCustomQemu should return as soon as the instance has launched as we 42 | // are using the --daemonize flag. It will set up a unix domain socket 43 | // called /tmp/qmp-socket that we can use to manage the instance. 44 | _, err := qemu.LaunchCustomQemu(context.Background(), "", params, nil, nil, nil) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | // This channel will be closed when the instance dies. 50 | disconnectedCh := make(chan struct{}) 51 | 52 | // Set up our options. We don't want any logging or to receive any events. 53 | cfg := qemu.QMPConfig{} 54 | 55 | // Start monitoring the qemu instance. This functon will block until we have 56 | // connect to the QMP socket and received the welcome message. 57 | q, _, err := qemu.QMPStart(context.Background(), "/tmp/qmp-socket", cfg, disconnectedCh) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | // This has to be the first command executed in a QMP session. 63 | err = q.ExecuteQMPCapabilities(context.Background()) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | // Let's try to shutdown the VM. If it hasn't shutdown in 10 seconds we'll 69 | // send a quit message. 70 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 71 | err = q.ExecuteSystemPowerdown(ctx) 72 | cancel() 73 | if err != nil { 74 | err = q.ExecuteQuit(context.Background()) 75 | if err != nil { 76 | panic(err) 77 | } 78 | } 79 | 80 | q.Shutdown() 81 | 82 | // disconnectedCh is closed when the VM exits. This line blocks until this 83 | // event occurs. 84 | <-disconnectedCh 85 | } 86 | -------------------------------------------------------------------------------- /qemu/image.go: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright contributors to the Virtual Machine Manager for Go project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | */ 16 | 17 | package qemu 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io/ioutil" 23 | "os" 24 | "os/exec" 25 | "path" 26 | "syscall" 27 | ) 28 | 29 | // CreateCloudInitISO creates a cloud-init ConfigDrive ISO image. This is 30 | // useful for configuring newly booted VMs. Before it can create the ISO 31 | // image it needs to create a file tree with the various files that will 32 | // make up the image. This directory is created under scratchDir and is 33 | // deleted when when the function returns, successfully or otherwise. ctx is 34 | // a context that can be used to timeout or cancel the image creation. 35 | // isoPath contains the desired path of the ISO image to be created. The 36 | // userdata and metadata parameters are byte slices that contain the 37 | // ConfigDrive userdata and metadata that will be stored with the ISO image. 38 | // The attrs parameter can be used to control aspects of the newly created 39 | // qemu process, such as the user and group under which it runs. It may be nil. 40 | func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string, 41 | userData, metaData []byte, attr *syscall.SysProcAttr) error { 42 | configDrivePath := path.Join(scratchDir, "clr-cloud-init") 43 | dataDirPath := path.Join(configDrivePath, "openstack", "latest") 44 | metaDataPath := path.Join(dataDirPath, "meta_data.json") 45 | userDataPath := path.Join(dataDirPath, "user_data") 46 | 47 | defer func() { 48 | /* #nosec */ 49 | _ = os.RemoveAll(configDrivePath) 50 | }() 51 | 52 | err := os.MkdirAll(dataDirPath, 0750) 53 | if err != nil { 54 | return fmt.Errorf("unable to create config drive directory %s : %v", 55 | dataDirPath, err) 56 | } 57 | 58 | err = ioutil.WriteFile(metaDataPath, metaData, 0644) 59 | if err != nil { 60 | return fmt.Errorf("unable to create %s : %v", metaDataPath, err) 61 | } 62 | 63 | err = ioutil.WriteFile(userDataPath, userData, 0644) 64 | if err != nil { 65 | return fmt.Errorf("unable to create %s : %v", userDataPath, err) 66 | } 67 | 68 | cmd := exec.CommandContext(ctx, "xorriso", "-as", "mkisofs", "-R", "-V", "config-2", 69 | "-o", isoPath, configDrivePath) 70 | cmd.SysProcAttr = attr 71 | err = cmd.Run() 72 | if err != nil { 73 | return fmt.Errorf("unable to create cloudinit iso image %v", err) 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /qemu/qemu_arch_base_test.go: -------------------------------------------------------------------------------- 1 | //go:build !s390x 2 | // +build !s390x 3 | 4 | /* 5 | // Copyright contributors to the Virtual Machine Manager for Go project 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | */ 19 | 20 | package qemu 21 | 22 | import "testing" 23 | 24 | var ( 25 | 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" 26 | 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" 27 | 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" 28 | deviceSerialString = "-device virtio-serial-pci,disable-modern=true,id=serial0,romfile=efi-virtio.rom,max_ports=2" 29 | 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" 30 | deviceVSOCKString = "-device vhost-vsock-pci,disable-modern=true,id=vhost-vsock-pci0,guest-cid=4,romfile=efi-virtio.rom" 31 | deviceVFIOString = "-device vfio-pci,host=02:10.0,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678,romfile=efi-virtio.rom" 32 | devicePCIeRootPortSimpleString = "-device pcie-root-port,id=rp1,bus=pcie.0,chassis=0x00,slot=0x00,multifunction=off" 33 | 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" 34 | deviceVFIOPCIeSimpleString = "-device vfio-pci,host=02:00.0,bus=rp0" 35 | deviceVFIOPCIeFullString = "-device vfio-pci,host=02:00.0,x-pci-vendor-id=0x10de,x-pci-device-id=0x15f8,romfile=efi-virtio.rom,bus=rp1" 36 | deviceSCSIControllerStr = "-device virtio-scsi-pci,id=foo,disable-modern=false,romfile=efi-virtio.rom" 37 | deviceSCSIControllerBusAddrStr = "-device virtio-scsi-pci,id=foo,bus=pci.0,addr=00:04.0,disable-modern=true,iothread=iothread1,romfile=efi-virtio.rom" 38 | deviceVhostUserSCSIString = "-chardev socket,id=char1,path=/tmp/nonexistentsocket.socket -device vhost-user-scsi-pci,id=scsi1,chardev=char1,romfile=efi-virtio.rom" 39 | 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" 40 | deviceBlockString = "-device virtio-blk-pci,disable-modern=true,drive=hd0,scsi=off,config-wce=off,romfile=efi-virtio.rom,share-rw=on,serial=hd0 -drive id=hd0,file=/var/lib/vm.img,aio=threads,format=qcow2,if=none,readonly=on" 41 | devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff,romfile=efi-virtio.rom" 42 | 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" 43 | devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff,romfile=efi-virtio.rom" 44 | romfile = "efi-virtio.rom" 45 | ) 46 | 47 | func TestAppendDeviceVhostUser(t *testing.T) { 48 | 49 | vhostuserBlkDevice := VhostUserDevice{ 50 | SocketPath: "/tmp/nonexistentsocket.socket", 51 | CharDevID: "char2", 52 | TypeDevID: "", 53 | Address: "", 54 | VhostUserType: VhostUserBlk, 55 | ROMFile: romfile, 56 | } 57 | testAppend(vhostuserBlkDevice, deviceVhostUserBlkString, t) 58 | 59 | vhostuserSCSIDevice := VhostUserDevice{ 60 | SocketPath: "/tmp/nonexistentsocket.socket", 61 | CharDevID: "char1", 62 | TypeDevID: "scsi1", 63 | Address: "", 64 | VhostUserType: VhostUserSCSI, 65 | ROMFile: romfile, 66 | } 67 | testAppend(vhostuserSCSIDevice, deviceVhostUserSCSIString, t) 68 | 69 | vhostuserNetDevice := VhostUserDevice{ 70 | SocketPath: "/tmp/nonexistentsocket.socket", 71 | CharDevID: "char1", 72 | TypeDevID: "net1", 73 | Address: "00:11:22:33:44:55", 74 | VhostUserType: VhostUserNet, 75 | ROMFile: romfile, 76 | } 77 | testAppend(vhostuserNetDevice, deviceVhostUserNetString, t) 78 | } 79 | 80 | func TestAppendVirtioBalloon(t *testing.T) { 81 | balloonDevice := BalloonDevice{ 82 | ID: "balloon", 83 | ROMFile: romfile, 84 | } 85 | 86 | var deviceString = "-device " + string(VirtioBalloon) + "-" + string(TransportPCI) 87 | deviceString += ",id=" + balloonDevice.ID + ",romfile=" + balloonDevice.ROMFile 88 | 89 | var OnDeflateOnOMM = ",deflate-on-oom=on" 90 | var OffDeflateOnOMM = ",deflate-on-oom=off" 91 | 92 | var OnDisableModern = ",disable-modern=true" 93 | var OffDisableModern = ",disable-modern=false" 94 | 95 | testAppend(balloonDevice, deviceString+OffDeflateOnOMM+OffDisableModern, t) 96 | 97 | balloonDevice.DeflateOnOOM = true 98 | testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OffDisableModern, t) 99 | 100 | balloonDevice.DisableModern = true 101 | testAppend(balloonDevice, deviceString+OnDeflateOnOMM+OnDisableModern, t) 102 | 103 | } 104 | 105 | func TestAppendDevicePCIeRootPort(t *testing.T) { 106 | var pcieRootPortID string 107 | 108 | // test empty ID 109 | pcieRootPortDevice := PCIeRootPortDevice{} 110 | if pcieRootPortDevice.Valid() { 111 | t.Fatalf("failed to validdate empty ID") 112 | } 113 | 114 | // test pref64_reserve and pre64_reserve 115 | pcieRootPortID = "rp0" 116 | pcieRootPortDevice = PCIeRootPortDevice{ 117 | ID: pcieRootPortID, 118 | Pref64Reserve: "16G", 119 | Pref32Reserve: "256M", 120 | } 121 | if pcieRootPortDevice.Valid() { 122 | t.Fatalf("failed to validate pref32-reserve and pref64-reserve for %v", pcieRootPortID) 123 | } 124 | 125 | // default test 126 | pcieRootPortID = "rp1" 127 | pcieRootPortDevice = PCIeRootPortDevice{ 128 | ID: pcieRootPortID, 129 | } 130 | if !pcieRootPortDevice.Valid() { 131 | t.Fatalf("failed to validate for %v", pcieRootPortID) 132 | } 133 | testAppend(pcieRootPortDevice, devicePCIeRootPortSimpleString, t) 134 | 135 | // full test 136 | pcieRootPortID = "rp2" 137 | pcieRootPortDevice = PCIeRootPortDevice{ 138 | ID: pcieRootPortID, 139 | Multifunction: true, 140 | Bus: "pcie.0", 141 | Chassis: "0x0", 142 | Slot: "0x1", 143 | Addr: "0x2", 144 | Pref64Reserve: "16G", 145 | IOReserve: "512M", 146 | MemReserve: "1G", 147 | BusReserve: "0x3", 148 | ROMFile: romfile, 149 | } 150 | if !pcieRootPortDevice.Valid() { 151 | t.Fatalf("failed to validate for %v", pcieRootPortID) 152 | } 153 | testAppend(pcieRootPortDevice, devicePCIeRootPortFullString, t) 154 | } 155 | 156 | func TestAppendDeviceVFIOPCIe(t *testing.T) { 157 | // default test 158 | pcieRootPortID := "rp0" 159 | vfioDevice := VFIODevice{ 160 | BDF: "02:00.0", 161 | Bus: pcieRootPortID, 162 | } 163 | testAppend(vfioDevice, deviceVFIOPCIeSimpleString, t) 164 | 165 | // full test 166 | pcieRootPortID = "rp1" 167 | vfioDevice = VFIODevice{ 168 | BDF: "02:00.0", 169 | Bus: pcieRootPortID, 170 | ROMFile: romfile, 171 | VendorID: "0x10de", 172 | DeviceID: "0x15f8", 173 | } 174 | testAppend(vfioDevice, deviceVFIOPCIeFullString, t) 175 | } 176 | -------------------------------------------------------------------------------- /qemu/qemu_s390x_test.go: -------------------------------------------------------------------------------- 1 | // +build s390x 2 | 3 | /* 4 | // Copyright contributors to the Virtual Machine Manager for Go project 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | */ 18 | 19 | package qemu 20 | 21 | import "testing" 22 | 23 | // -pci devices don't play well with Z hence replace them with corresponding -ccw devices 24 | // See https://wiki.qemu.org/Documentation/Platforms/S390X 25 | var ( 26 | 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" 27 | 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" 28 | 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 29 | 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 30 | deviceSerialString = "-device virtio-serial-ccw,id=serial0,devno=" + DevNo 31 | deviceVSOCKString = "-device vhost-vsock-ccw,id=vhost-vsock-pci0,guest-cid=4,devno=" + DevNo 32 | deviceVFIOString = "-device vfio-ccw,host=02:10.0,devno=" + DevNo 33 | deviceSCSIControllerStr = "-device virtio-scsi-ccw,id=foo,devno=" + DevNo 34 | deviceSCSIControllerBusAddrStr = "-device virtio-scsi-ccw,id=foo,bus=pci.0,addr=00:04.0,iothread=iothread1,devno=" + DevNo 35 | 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" 36 | devicePCIBridgeString = "-device pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,chassis_nr=5,shpc=on,addr=ff" 37 | devicePCIEBridgeString = "-device pcie-pci-bridge,bus=/pci-bus/pcie.0,id=mybridge,addr=ff" 38 | romfile = "" 39 | ) 40 | 41 | func TestAppendVirtioBalloon(t *testing.T) { 42 | balloonDevice := BalloonDevice{ 43 | ID: "balloon", 44 | } 45 | 46 | var deviceString = "-device " + string(VirtioBalloon) + "-" + string(TransportCCW) 47 | deviceString += ",id=" + balloonDevice.ID 48 | balloonDevice.DevNo = DevNo 49 | devnoOptios := ",devno=" + DevNo 50 | 51 | var OnDeflateOnOMM = ",deflate-on-oom=on" 52 | var OffDeflateOnOMM = ",deflate-on-oom=off" 53 | testAppend(balloonDevice, deviceString+devnoOptios+OffDeflateOnOMM, t) 54 | 55 | balloonDevice.DeflateOnOOM = true 56 | testAppend(balloonDevice, deviceString+devnoOptios+OnDeflateOnOMM, t) 57 | } 58 | 59 | func TestAppendDeviceFSCCW(t *testing.T) { 60 | defaultKnobs := Knobs{ 61 | NoUserConfig: true, 62 | } 63 | 64 | fsdev := FSDevice{ 65 | Driver: Virtio9P, 66 | FSDriver: Local, 67 | ID: "workload9p", 68 | Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", 69 | MountTag: "rootfs", 70 | SecurityModel: None, 71 | DisableModern: true, 72 | ROMFile: "efi-virtio.rom", 73 | Multidev: Remap, 74 | Transport: TransportCCW, 75 | DevNo: DevNo, 76 | } 77 | 78 | var config Config 79 | config.Knobs = defaultKnobs 80 | 81 | testConfigAppend(&config, fsdev, deviceFSString, t) 82 | } 83 | 84 | func TestAppendDeviceFSCCWIOMMU(t *testing.T) { 85 | defaultKnobs := Knobs{ 86 | NoUserConfig: true, 87 | IOMMUPlatform: true, 88 | } 89 | 90 | fsdev := FSDevice{ 91 | Driver: Virtio9P, 92 | FSDriver: Local, 93 | ID: "workload9p", 94 | Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", 95 | MountTag: "rootfs", 96 | SecurityModel: None, 97 | DisableModern: true, 98 | ROMFile: "efi-virtio.rom", 99 | Multidev: Remap, 100 | Transport: TransportCCW, 101 | DevNo: DevNo, 102 | } 103 | 104 | var config Config 105 | config.Knobs = defaultKnobs 106 | 107 | testConfigAppend(&config, fsdev, deviceFSIOMMUString, t) 108 | } 109 | -------------------------------------------------------------------------------- /qemu/qemu_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright contributors to the Virtual Machine Manager for Go project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | */ 16 | 17 | package qemu 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | "reflect" 24 | "strings" 25 | "testing" 26 | ) 27 | 28 | const agentUUID = "4cb19522-1e18-439a-883a-f9b2a3a95f5e" 29 | const volumeUUID = "67d86208-b46c-4465-9018-e14187d4010" 30 | 31 | var ( 32 | 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" 33 | 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" 34 | ) 35 | 36 | const DevNo = "fe.1.1234" 37 | 38 | func testAppend(structure interface{}, expected string, t *testing.T) { 39 | var config Config 40 | testConfigAppend(&config, structure, expected, t) 41 | } 42 | 43 | func testConfigAppend(config *Config, structure interface{}, expected string, t *testing.T) { 44 | switch s := structure.(type) { 45 | case Machine: 46 | config.Machine = s 47 | config.appendMachine() 48 | case FwCfg: 49 | config.FwCfg = []FwCfg{s} 50 | config.appendFwCfg(nil) 51 | 52 | case Device: 53 | config.Devices = []Device{s} 54 | config.appendDevices() 55 | 56 | case Knobs: 57 | config.Knobs = s 58 | config.appendKnobs() 59 | 60 | case Kernel: 61 | config.Kernel = s 62 | config.appendKernel() 63 | 64 | case Memory: 65 | config.Memory = s 66 | config.appendMemory() 67 | 68 | case SMP: 69 | config.SMP = s 70 | if err := config.appendCPUs(); err != nil { 71 | t.Fatalf("Unexpected error: %v", err) 72 | } 73 | 74 | case QMPSocket: 75 | config.QMPSockets = []QMPSocket{s} 76 | config.appendQMPSockets() 77 | 78 | case []QMPSocket: 79 | config.QMPSockets = s 80 | config.appendQMPSockets() 81 | 82 | case RTC: 83 | config.RTC = s 84 | config.appendRTC() 85 | 86 | case IOThread: 87 | config.IOThreads = []IOThread{s} 88 | config.appendIOThreads() 89 | case Incoming: 90 | config.Incoming = s 91 | config.appendIncoming() 92 | } 93 | 94 | result := strings.Join(config.qemuParams, " ") 95 | if result != expected { 96 | t.Fatalf("Failed to append parameters [%s] != [%s]", result, expected) 97 | } 98 | } 99 | 100 | func TestAppendMachine(t *testing.T) { 101 | machineString := "-machine pc-lite,accel=kvm,kernel_irqchip,nvdimm" 102 | machine := Machine{ 103 | Type: "pc-lite", 104 | Acceleration: "kvm,kernel_irqchip,nvdimm", 105 | } 106 | testAppend(machine, machineString, t) 107 | 108 | machineString = "-machine pc-lite,accel=kvm,kernel_irqchip,nvdimm,gic-version=host,usb=off" 109 | machine = Machine{ 110 | Type: "pc-lite", 111 | Acceleration: "kvm,kernel_irqchip,nvdimm", 112 | Options: "gic-version=host,usb=off", 113 | } 114 | testAppend(machine, machineString, t) 115 | 116 | machineString = "-machine microvm,accel=kvm,pic=off,pit=off" 117 | machine = Machine{ 118 | Type: "microvm", 119 | Acceleration: "kvm", 120 | Options: "pic=off,pit=off", 121 | } 122 | testAppend(machine, machineString, t) 123 | } 124 | 125 | func TestAppendEmptyMachine(t *testing.T) { 126 | machine := Machine{} 127 | 128 | testAppend(machine, "", t) 129 | } 130 | 131 | var deviceNVDIMMString = "-device nvdimm,id=nv0,memdev=mem0,unarmed=on -object memory-backend-file,id=mem0,mem-path=/root,size=65536,readonly=on" 132 | 133 | func TestAppendDeviceNVDIMM(t *testing.T) { 134 | object := Object{ 135 | Driver: NVDIMM, 136 | Type: MemoryBackendFile, 137 | DeviceID: "nv0", 138 | ID: "mem0", 139 | MemPath: "/root", 140 | Size: 1 << 16, 141 | ReadOnly: true, 142 | } 143 | 144 | testAppend(object, deviceNVDIMMString, t) 145 | } 146 | 147 | var objectEPCString = "-object memory-backend-epc,id=epc0,size=65536,prealloc=on" 148 | 149 | func TestAppendEPCObject(t *testing.T) { 150 | object := Object{ 151 | Type: MemoryBackendEPC, 152 | ID: "epc0", 153 | Size: 1 << 16, 154 | Prealloc: true, 155 | } 156 | 157 | testAppend(object, objectEPCString, t) 158 | } 159 | 160 | func TestAppendDeviceFS(t *testing.T) { 161 | fsdev := FSDevice{ 162 | Driver: Virtio9P, 163 | FSDriver: Local, 164 | ID: "workload9p", 165 | Path: "/var/lib/docker/devicemapper/mnt/e31ebda2", 166 | MountTag: "rootfs", 167 | SecurityModel: None, 168 | DisableModern: true, 169 | ROMFile: "efi-virtio.rom", 170 | Multidev: Remap, 171 | } 172 | 173 | if fsdev.Transport.isVirtioCCW(nil) { 174 | fsdev.DevNo = DevNo 175 | } 176 | 177 | testAppend(fsdev, deviceFSString, t) 178 | } 179 | 180 | func TestAppendDeviceNetwork(t *testing.T) { 181 | netdev := NetDevice{ 182 | Driver: VirtioNet, 183 | Type: TAP, 184 | ID: "tap0", 185 | IFName: "ceth0", 186 | Script: "no", 187 | DownScript: "no", 188 | VHost: true, 189 | MACAddress: "01:02:de:ad:be:ef", 190 | DisableModern: true, 191 | ROMFile: "efi-virtio.rom", 192 | } 193 | 194 | if netdev.Transport.isVirtioCCW(nil) { 195 | netdev.DevNo = DevNo 196 | } 197 | 198 | testAppend(netdev, deviceNetworkString, t) 199 | } 200 | 201 | func TestAppendDeviceNetworkMq(t *testing.T) { 202 | foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") 203 | bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") 204 | 205 | defer func() { 206 | _ = foo.Close() 207 | _ = bar.Close() 208 | _ = os.Remove(foo.Name()) 209 | _ = os.Remove(bar.Name()) 210 | }() 211 | 212 | netdev := NetDevice{ 213 | Driver: VirtioNet, 214 | Type: TAP, 215 | ID: "tap0", 216 | IFName: "ceth0", 217 | Script: "no", 218 | DownScript: "no", 219 | FDs: []*os.File{foo, bar}, 220 | VHost: true, 221 | MACAddress: "01:02:de:ad:be:ef", 222 | DisableModern: true, 223 | ROMFile: "efi-virtio.rom", 224 | } 225 | if netdev.Transport.isVirtioCCW(nil) { 226 | netdev.DevNo = DevNo 227 | } 228 | 229 | testAppend(netdev, deviceNetworkStringMq, t) 230 | } 231 | 232 | func TestAppendDeviceNetworkPCI(t *testing.T) { 233 | 234 | netdev := NetDevice{ 235 | Driver: VirtioNet, 236 | Type: TAP, 237 | ID: "tap0", 238 | IFName: "ceth0", 239 | Bus: "/pci-bus/pcie.0", 240 | Addr: "255", 241 | Script: "no", 242 | DownScript: "no", 243 | VHost: true, 244 | MACAddress: "01:02:de:ad:be:ef", 245 | DisableModern: true, 246 | ROMFile: romfile, 247 | } 248 | 249 | if !netdev.Transport.isVirtioPCI(nil) { 250 | t.Skip("Test valid only for PCI devices") 251 | } 252 | 253 | testAppend(netdev, deviceNetworkPCIString, t) 254 | } 255 | 256 | func TestAppendDeviceNetworkPCIMq(t *testing.T) { 257 | foo, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") 258 | bar, _ := ioutil.TempFile(os.TempDir(), "govmm-qemu-test") 259 | 260 | defer func() { 261 | _ = foo.Close() 262 | _ = bar.Close() 263 | _ = os.Remove(foo.Name()) 264 | _ = os.Remove(bar.Name()) 265 | }() 266 | 267 | netdev := NetDevice{ 268 | Driver: VirtioNet, 269 | Type: TAP, 270 | ID: "tap0", 271 | IFName: "ceth0", 272 | Bus: "/pci-bus/pcie.0", 273 | Addr: "255", 274 | Script: "no", 275 | DownScript: "no", 276 | FDs: []*os.File{foo, bar}, 277 | VHost: true, 278 | MACAddress: "01:02:de:ad:be:ef", 279 | DisableModern: true, 280 | ROMFile: romfile, 281 | } 282 | 283 | if !netdev.Transport.isVirtioPCI(nil) { 284 | t.Skip("Test valid only for PCI devices") 285 | } 286 | 287 | testAppend(netdev, deviceNetworkPCIStringMq, t) 288 | } 289 | 290 | var deviceLegacySerialString = "-serial chardev:tlserial0" 291 | 292 | func TestAppendLegacySerial(t *testing.T) { 293 | sdev := LegacySerialDevice{ 294 | Chardev: "tlserial0", 295 | } 296 | 297 | testAppend(sdev, deviceLegacySerialString, t) 298 | } 299 | 300 | var deviceLegacySerialPortString = "-chardev file,id=char0,path=/tmp/serial.log" 301 | 302 | func TestAppendDeviceLegacySerialPort(t *testing.T) { 303 | chardev := CharDevice{ 304 | Driver: LegacySerial, 305 | Backend: File, 306 | ID: "char0", 307 | Path: "/tmp/serial.log", 308 | } 309 | testAppend(chardev, deviceLegacySerialPortString, t) 310 | } 311 | 312 | func TestAppendDeviceSerial(t *testing.T) { 313 | sdev := SerialDevice{ 314 | Driver: VirtioSerial, 315 | ID: "serial0", 316 | DisableModern: true, 317 | ROMFile: romfile, 318 | MaxPorts: 2, 319 | } 320 | if sdev.Transport.isVirtioCCW(nil) { 321 | sdev.DevNo = DevNo 322 | } 323 | 324 | testAppend(sdev, deviceSerialString, t) 325 | } 326 | 327 | var deviceSerialPortString = "-device virtserialport,chardev=char0,id=channel0,name=channel.0 -chardev socket,id=char0,path=/tmp/char.sock,server=on,wait=off" 328 | 329 | func TestAppendDeviceSerialPort(t *testing.T) { 330 | chardev := CharDevice{ 331 | Driver: VirtioSerialPort, 332 | Backend: Socket, 333 | ID: "char0", 334 | DeviceID: "channel0", 335 | Path: "/tmp/char.sock", 336 | Name: "channel.0", 337 | } 338 | if chardev.Transport.isVirtioCCW(nil) { 339 | chardev.DevNo = DevNo 340 | } 341 | testAppend(chardev, deviceSerialPortString, t) 342 | } 343 | 344 | func TestAppendDeviceBlock(t *testing.T) { 345 | blkdev := BlockDevice{ 346 | Driver: VirtioBlock, 347 | ID: "hd0", 348 | File: "/var/lib/vm.img", 349 | AIO: Threads, 350 | Format: QCOW2, 351 | Interface: NoInterface, 352 | SCSI: false, 353 | WCE: false, 354 | DisableModern: true, 355 | ROMFile: romfile, 356 | ShareRW: true, 357 | ReadOnly: true, 358 | } 359 | if blkdev.Transport.isVirtioCCW(nil) { 360 | blkdev.DevNo = DevNo 361 | } 362 | testAppend(blkdev, deviceBlockString, t) 363 | } 364 | 365 | func TestAppendDeviceVFIO(t *testing.T) { 366 | vfioDevice := VFIODevice{ 367 | BDF: "02:10.0", 368 | ROMFile: romfile, 369 | VendorID: "0x1234", 370 | DeviceID: "0x5678", 371 | } 372 | 373 | if vfioDevice.Transport.isVirtioCCW(nil) { 374 | vfioDevice.DevNo = DevNo 375 | } 376 | 377 | testAppend(vfioDevice, deviceVFIOString, t) 378 | } 379 | 380 | func TestAppendVSOCK(t *testing.T) { 381 | vsockDevice := VSOCKDevice{ 382 | ID: "vhost-vsock-pci0", 383 | ContextID: 4, 384 | VHostFD: nil, 385 | DisableModern: true, 386 | ROMFile: romfile, 387 | } 388 | 389 | if vsockDevice.Transport.isVirtioCCW(nil) { 390 | vsockDevice.DevNo = DevNo 391 | } 392 | 393 | testAppend(vsockDevice, deviceVSOCKString, t) 394 | } 395 | 396 | func TestVSOCKValid(t *testing.T) { 397 | vsockDevice := VSOCKDevice{ 398 | ID: "vhost-vsock-pci0", 399 | ContextID: MinimalGuestCID - 1, 400 | VHostFD: nil, 401 | DisableModern: true, 402 | } 403 | 404 | if vsockDevice.Valid() { 405 | t.Fatalf("VSOCK Context ID is not valid") 406 | } 407 | 408 | vsockDevice.ContextID = MaxGuestCID + 1 409 | 410 | if vsockDevice.Valid() { 411 | t.Fatalf("VSOCK Context ID is not valid") 412 | } 413 | 414 | vsockDevice.ID = "" 415 | 416 | if vsockDevice.Valid() { 417 | t.Fatalf("VSOCK ID is not valid") 418 | } 419 | } 420 | 421 | func TestAppendVirtioRng(t *testing.T) { 422 | var objectString = "-object rng-random,id=rng0" 423 | var deviceString = "-device " + string(VirtioRng) 424 | 425 | rngDevice := RngDevice{ 426 | ID: "rng0", 427 | ROMFile: romfile, 428 | } 429 | 430 | deviceString += "-" + rngDevice.Transport.getName(nil) + ",rng=rng0" 431 | if romfile != "" { 432 | deviceString = deviceString + ",romfile=efi-virtio.rom" 433 | } 434 | 435 | if rngDevice.Transport.isVirtioCCW(nil) { 436 | rngDevice.DevNo = DevNo 437 | deviceString += ",devno=" + rngDevice.DevNo 438 | } 439 | 440 | testAppend(rngDevice, objectString+" "+deviceString, t) 441 | 442 | rngDevice.Filename = "/dev/urandom" 443 | objectString += ",filename=" + rngDevice.Filename 444 | 445 | testAppend(rngDevice, objectString+" "+deviceString, t) 446 | 447 | rngDevice.MaxBytes = 20 448 | 449 | deviceString += fmt.Sprintf(",max-bytes=%d", rngDevice.MaxBytes) 450 | testAppend(rngDevice, objectString+" "+deviceString, t) 451 | 452 | rngDevice.Period = 500 453 | 454 | deviceString += fmt.Sprintf(",period=%d", rngDevice.Period) 455 | testAppend(rngDevice, objectString+" "+deviceString, t) 456 | 457 | } 458 | 459 | func TestVirtioRngValid(t *testing.T) { 460 | rng := RngDevice{ 461 | ID: "", 462 | } 463 | 464 | if rng.Valid() { 465 | t.Fatalf("rng should be not valid when ID is empty") 466 | } 467 | 468 | rng.ID = "rng0" 469 | if !rng.Valid() { 470 | t.Fatalf("rng should be valid") 471 | } 472 | 473 | } 474 | 475 | func TestVirtioBalloonValid(t *testing.T) { 476 | balloon := BalloonDevice{ 477 | ID: "", 478 | } 479 | 480 | if balloon.Valid() { 481 | t.Fatalf("balloon should be not valid when ID is empty") 482 | } 483 | 484 | balloon.ID = "balloon0" 485 | if !balloon.Valid() { 486 | t.Fatalf("balloon should be valid") 487 | } 488 | } 489 | 490 | func TestAppendDeviceSCSIController(t *testing.T) { 491 | scsiCon := SCSIController{ 492 | ID: "foo", 493 | ROMFile: romfile, 494 | } 495 | 496 | if scsiCon.Transport.isVirtioCCW(nil) { 497 | scsiCon.DevNo = DevNo 498 | } 499 | 500 | testAppend(scsiCon, deviceSCSIControllerStr, t) 501 | 502 | scsiCon.Bus = "pci.0" 503 | scsiCon.Addr = "00:04.0" 504 | scsiCon.DisableModern = true 505 | scsiCon.IOThread = "iothread1" 506 | testAppend(scsiCon, deviceSCSIControllerBusAddrStr, t) 507 | } 508 | 509 | func TestAppendPCIBridgeDevice(t *testing.T) { 510 | 511 | bridge := BridgeDevice{ 512 | Type: PCIBridge, 513 | ID: "mybridge", 514 | Bus: "/pci-bus/pcie.0", 515 | Addr: "255", 516 | Chassis: 5, 517 | SHPC: true, 518 | ROMFile: romfile, 519 | } 520 | 521 | testAppend(bridge, devicePCIBridgeString, t) 522 | } 523 | 524 | func TestAppendPCIBridgeDeviceWithReservations(t *testing.T) { 525 | 526 | bridge := BridgeDevice{ 527 | Type: PCIBridge, 528 | ID: "mybridge", 529 | Bus: "/pci-bus/pcie.0", 530 | Addr: "255", 531 | Chassis: 5, 532 | SHPC: false, 533 | ROMFile: romfile, 534 | IOReserve: "4k", 535 | MemReserve: "1m", 536 | Pref64Reserve: "1m", 537 | } 538 | 539 | testAppend(bridge, devicePCIBridgeStringReserved, t) 540 | } 541 | 542 | func TestAppendPCIEBridgeDevice(t *testing.T) { 543 | 544 | bridge := BridgeDevice{ 545 | Type: PCIEBridge, 546 | ID: "mybridge", 547 | Bus: "/pci-bus/pcie.0", 548 | Addr: "255", 549 | ROMFile: "efi-virtio.rom", 550 | } 551 | 552 | testAppend(bridge, devicePCIEBridgeString, t) 553 | } 554 | 555 | func TestAppendEmptyDevice(t *testing.T) { 556 | device := SerialDevice{} 557 | 558 | testAppend(device, "", t) 559 | } 560 | 561 | func TestAppendKnobsAllTrue(t *testing.T) { 562 | var knobsString = "-no-user-config -nodefaults -nographic --no-reboot -daemonize -overcommit mem-lock=on -S" 563 | knobs := Knobs{ 564 | NoUserConfig: true, 565 | NoDefaults: true, 566 | NoGraphic: true, 567 | NoReboot: true, 568 | Daemonize: true, 569 | MemPrealloc: true, 570 | FileBackedMem: true, 571 | MemShared: true, 572 | Mlock: true, 573 | Stopped: true, 574 | } 575 | 576 | testAppend(knobs, knobsString, t) 577 | } 578 | 579 | func TestAppendKnobsAllFalse(t *testing.T) { 580 | var knobsString = "" 581 | knobs := Knobs{ 582 | NoUserConfig: false, 583 | NoDefaults: false, 584 | NoGraphic: false, 585 | NoReboot: false, 586 | MemPrealloc: false, 587 | FileBackedMem: false, 588 | MemShared: false, 589 | Mlock: false, 590 | Stopped: false, 591 | } 592 | 593 | testAppend(knobs, knobsString, t) 594 | } 595 | 596 | func TestAppendMemoryHugePages(t *testing.T) { 597 | conf := &Config{ 598 | Memory: Memory{ 599 | Size: "1G", 600 | Slots: 8, 601 | MaxMem: "3G", 602 | Path: "foobar", 603 | }, 604 | } 605 | memString := "-m 1G,slots=8,maxmem=3G" 606 | testConfigAppend(conf, conf.Memory, memString, t) 607 | 608 | knobs := Knobs{ 609 | HugePages: true, 610 | MemPrealloc: true, 611 | FileBackedMem: true, 612 | MemShared: true, 613 | } 614 | objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=/dev/hugepages,share=on,prealloc=on" 615 | numaMemString := "-numa node,memdev=dimm1" 616 | memBackendString := "-machine memory-backend=dimm1" 617 | 618 | knobsString := objMemString + " " 619 | if isDimmSupported(nil) { 620 | knobsString += numaMemString 621 | } else { 622 | knobsString += memBackendString 623 | } 624 | 625 | testConfigAppend(conf, knobs, memString+" "+knobsString, t) 626 | } 627 | 628 | func TestAppendMemoryMemPrealloc(t *testing.T) { 629 | conf := &Config{ 630 | Memory: Memory{ 631 | Size: "1G", 632 | Slots: 8, 633 | MaxMem: "3G", 634 | Path: "foobar", 635 | }, 636 | } 637 | memString := "-m 1G,slots=8,maxmem=3G" 638 | testConfigAppend(conf, conf.Memory, memString, t) 639 | 640 | knobs := Knobs{ 641 | MemPrealloc: true, 642 | MemShared: true, 643 | } 644 | objMemString := "-object memory-backend-ram,id=dimm1,size=1G,share=on,prealloc=on" 645 | numaMemString := "-numa node,memdev=dimm1" 646 | memBackendString := "-machine memory-backend=dimm1" 647 | 648 | knobsString := objMemString + " " 649 | if isDimmSupported(nil) { 650 | knobsString += numaMemString 651 | } else { 652 | knobsString += memBackendString 653 | } 654 | 655 | testConfigAppend(conf, knobs, memString+" "+knobsString, t) 656 | } 657 | 658 | func TestAppendMemoryMemShared(t *testing.T) { 659 | conf := &Config{ 660 | Memory: Memory{ 661 | Size: "1G", 662 | Slots: 8, 663 | MaxMem: "3G", 664 | Path: "foobar", 665 | }, 666 | } 667 | memString := "-m 1G,slots=8,maxmem=3G" 668 | testConfigAppend(conf, conf.Memory, memString, t) 669 | 670 | knobs := Knobs{ 671 | FileBackedMem: true, 672 | MemShared: true, 673 | } 674 | objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on" 675 | numaMemString := "-numa node,memdev=dimm1" 676 | memBackendString := "-machine memory-backend=dimm1" 677 | 678 | knobsString := objMemString + " " 679 | if isDimmSupported(nil) { 680 | knobsString += numaMemString 681 | } else { 682 | knobsString += memBackendString 683 | } 684 | 685 | testConfigAppend(conf, knobs, memString+" "+knobsString, t) 686 | } 687 | 688 | func TestAppendMemoryFileBackedMem(t *testing.T) { 689 | conf := &Config{ 690 | Memory: Memory{ 691 | Size: "1G", 692 | Slots: 8, 693 | MaxMem: "3G", 694 | Path: "foobar", 695 | }, 696 | } 697 | memString := "-m 1G,slots=8,maxmem=3G" 698 | testConfigAppend(conf, conf.Memory, memString, t) 699 | 700 | knobs := Knobs{ 701 | FileBackedMem: true, 702 | MemShared: false, 703 | } 704 | objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar" 705 | numaMemString := "-numa node,memdev=dimm1" 706 | memBackendString := "-machine memory-backend=dimm1" 707 | 708 | knobsString := objMemString + " " 709 | if isDimmSupported(nil) { 710 | knobsString += numaMemString 711 | } else { 712 | knobsString += memBackendString 713 | } 714 | 715 | testConfigAppend(conf, knobs, memString+" "+knobsString, t) 716 | } 717 | 718 | func TestAppendMemoryFileBackedMemPrealloc(t *testing.T) { 719 | conf := &Config{ 720 | Memory: Memory{ 721 | Size: "1G", 722 | Slots: 8, 723 | MaxMem: "3G", 724 | Path: "foobar", 725 | }, 726 | } 727 | memString := "-m 1G,slots=8,maxmem=3G" 728 | testConfigAppend(conf, conf.Memory, memString, t) 729 | 730 | knobs := Knobs{ 731 | FileBackedMem: true, 732 | MemShared: true, 733 | MemPrealloc: true, 734 | } 735 | objMemString := "-object memory-backend-file,id=dimm1,size=1G,mem-path=foobar,share=on,prealloc=on" 736 | numaMemString := "-numa node,memdev=dimm1" 737 | memBackendString := "-machine memory-backend=dimm1" 738 | 739 | knobsString := objMemString + " " 740 | if isDimmSupported(nil) { 741 | knobsString += numaMemString 742 | } else { 743 | knobsString += memBackendString 744 | } 745 | 746 | testConfigAppend(conf, knobs, memString+" "+knobsString, t) 747 | } 748 | 749 | func TestNoRebootKnob(t *testing.T) { 750 | conf := &Config{} 751 | 752 | knobs := Knobs{ 753 | NoReboot: true, 754 | } 755 | knobsString := "--no-reboot" 756 | 757 | testConfigAppend(conf, knobs, knobsString, t) 758 | } 759 | 760 | 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" 761 | 762 | func TestAppendKernel(t *testing.T) { 763 | kernel := Kernel{ 764 | Path: "/opt/vmlinux.container", 765 | InitrdPath: "/opt/initrd.container", 766 | Params: "root=/dev/pmem0p1 rootflags=dax,data=ordered,errors=remount-ro rw rootfstype=ext4 tsc=reliable", 767 | } 768 | 769 | testAppend(kernel, kernelString, t) 770 | } 771 | 772 | var memoryString = "-m 2G,slots=2,maxmem=3G" 773 | 774 | func TestAppendMemory(t *testing.T) { 775 | memory := Memory{ 776 | Size: "2G", 777 | Slots: 2, 778 | MaxMem: "3G", 779 | Path: "", 780 | } 781 | 782 | testAppend(memory, memoryString, t) 783 | } 784 | 785 | var cpusString = "-smp 2,cores=1,threads=2,sockets=2,maxcpus=6" 786 | 787 | func TestAppendCPUs(t *testing.T) { 788 | smp := SMP{ 789 | CPUs: 2, 790 | Sockets: 2, 791 | Cores: 1, 792 | Threads: 2, 793 | MaxCPUs: 6, 794 | } 795 | 796 | testAppend(smp, cpusString, t) 797 | } 798 | 799 | func TestFailToAppendCPUs(t *testing.T) { 800 | config := Config{ 801 | SMP: SMP{ 802 | CPUs: 2, 803 | Sockets: 2, 804 | Cores: 1, 805 | Threads: 2, 806 | MaxCPUs: 1, 807 | }, 808 | } 809 | 810 | if err := config.appendCPUs(); err == nil { 811 | t.Fatalf("Expected appendCPUs to fail") 812 | } 813 | } 814 | 815 | var qmpSingleSocketServerString = "-qmp unix:cc-qmp,server=on,wait=off" 816 | var qmpSingleSocketString = "-qmp unix:cc-qmp" 817 | 818 | func TestAppendSingleQMPSocketServer(t *testing.T) { 819 | qmp := QMPSocket{ 820 | Type: "unix", 821 | Name: "cc-qmp", 822 | Server: true, 823 | NoWait: true, 824 | } 825 | 826 | testAppend(qmp, qmpSingleSocketServerString, t) 827 | } 828 | 829 | func TestAppendSingleQMPSocket(t *testing.T) { 830 | qmp := QMPSocket{ 831 | Type: Unix, 832 | Name: "cc-qmp", 833 | Server: false, 834 | } 835 | 836 | testAppend(qmp, qmpSingleSocketString, t) 837 | } 838 | 839 | var qmpSocketServerString = "-qmp unix:cc-qmp-1,server=on,wait=off -qmp unix:cc-qmp-2,server=on,wait=off" 840 | 841 | func TestAppendQMPSocketServer(t *testing.T) { 842 | qmp := []QMPSocket{ 843 | { 844 | Type: "unix", 845 | Name: "cc-qmp-1", 846 | Server: true, 847 | NoWait: true, 848 | }, 849 | { 850 | Type: "unix", 851 | Name: "cc-qmp-2", 852 | Server: true, 853 | NoWait: true, 854 | }, 855 | } 856 | 857 | testAppend(qmp, qmpSocketServerString, t) 858 | } 859 | 860 | var pidfile = "/run/vc/vm/iamsandboxid/pidfile" 861 | var logfile = "/run/vc/vm/iamsandboxid/logfile" 862 | var qemuString = "-name cc-qemu -cpu host -uuid " + agentUUID + " -pidfile " + pidfile + " -D " + logfile 863 | 864 | func TestAppendStrings(t *testing.T) { 865 | config := Config{ 866 | Path: "qemu", 867 | Name: "cc-qemu", 868 | UUID: agentUUID, 869 | CPUModel: "host", 870 | PidFile: pidfile, 871 | LogFile: logfile, 872 | } 873 | 874 | config.appendName() 875 | config.appendCPUModel() 876 | config.appendUUID() 877 | config.appendPidFile() 878 | config.appendLogFile() 879 | 880 | result := strings.Join(config.qemuParams, " ") 881 | if result != qemuString { 882 | t.Fatalf("Failed to append parameters [%s] != [%s]", result, qemuString) 883 | } 884 | } 885 | 886 | var rtcString = "-rtc base=utc,driftfix=slew,clock=host" 887 | 888 | func TestAppendRTC(t *testing.T) { 889 | rtc := RTC{ 890 | Base: UTC, 891 | Clock: Host, 892 | DriftFix: Slew, 893 | } 894 | 895 | testAppend(rtc, rtcString, t) 896 | } 897 | 898 | var ioThreadString = "-object iothread,id=iothread1" 899 | 900 | func TestAppendIOThread(t *testing.T) { 901 | ioThread := IOThread{ 902 | ID: "iothread1", 903 | } 904 | 905 | testAppend(ioThread, ioThreadString, t) 906 | } 907 | 908 | var incomingStringFD = "-S -incoming fd:3" 909 | 910 | func TestAppendIncomingFD(t *testing.T) { 911 | source := Incoming{ 912 | MigrationType: MigrationFD, 913 | FD: os.Stdout, 914 | } 915 | 916 | testAppend(source, incomingStringFD, t) 917 | } 918 | 919 | var incomingStringExec = "-S -incoming exec:test migration cmd" 920 | 921 | func TestAppendIncomingExec(t *testing.T) { 922 | source := Incoming{ 923 | MigrationType: MigrationExec, 924 | Exec: "test migration cmd", 925 | } 926 | 927 | testAppend(source, incomingStringExec, t) 928 | } 929 | 930 | var incomingStringDefer = "-S -incoming defer" 931 | 932 | func TestAppendIncomingDefer(t *testing.T) { 933 | source := Incoming{ 934 | MigrationType: MigrationDefer, 935 | } 936 | 937 | testAppend(source, incomingStringDefer, t) 938 | } 939 | 940 | func TestBadName(t *testing.T) { 941 | c := &Config{} 942 | c.appendName() 943 | if len(c.qemuParams) != 0 { 944 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 945 | } 946 | } 947 | 948 | func TestBadMachine(t *testing.T) { 949 | c := &Config{} 950 | c.appendMachine() 951 | if len(c.qemuParams) != 0 { 952 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 953 | } 954 | } 955 | 956 | func TestBadCPUModel(t *testing.T) { 957 | c := &Config{} 958 | c.appendCPUModel() 959 | if len(c.qemuParams) != 0 { 960 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 961 | } 962 | } 963 | 964 | func TestBadQMPSockets(t *testing.T) { 965 | c := &Config{} 966 | c.appendQMPSockets() 967 | if len(c.qemuParams) != 0 { 968 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 969 | } 970 | 971 | c = &Config{ 972 | QMPSockets: []QMPSocket{{}}, 973 | } 974 | 975 | c.appendQMPSockets() 976 | if len(c.qemuParams) != 0 { 977 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 978 | } 979 | 980 | c = &Config{ 981 | QMPSockets: []QMPSocket{{Name: "test"}}, 982 | } 983 | 984 | c.appendQMPSockets() 985 | if len(c.qemuParams) != 0 { 986 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 987 | } 988 | 989 | c = &Config{ 990 | QMPSockets: []QMPSocket{ 991 | { 992 | Name: "test", 993 | Type: QMPSocketType("ip"), 994 | }, 995 | }, 996 | } 997 | 998 | c.appendQMPSockets() 999 | if len(c.qemuParams) != 0 { 1000 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1001 | } 1002 | } 1003 | 1004 | func TestBadDevices(t *testing.T) { 1005 | c := &Config{} 1006 | c.appendDevices() 1007 | if len(c.qemuParams) != 0 { 1008 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1009 | } 1010 | 1011 | c = &Config{ 1012 | Devices: []Device{ 1013 | FSDevice{}, 1014 | FSDevice{ 1015 | ID: "id0", 1016 | MountTag: "tag", 1017 | }, 1018 | CharDevice{}, 1019 | CharDevice{ 1020 | ID: "id1", 1021 | }, 1022 | NetDevice{}, 1023 | NetDevice{ 1024 | ID: "id1", 1025 | IFName: "if", 1026 | Type: IPVTAP, 1027 | }, 1028 | SerialDevice{}, 1029 | SerialDevice{ 1030 | ID: "id0", 1031 | }, 1032 | BlockDevice{}, 1033 | BlockDevice{ 1034 | Driver: "drv", 1035 | ID: "id1", 1036 | }, 1037 | VhostUserDevice{}, 1038 | VhostUserDevice{ 1039 | CharDevID: "devid", 1040 | }, 1041 | VhostUserDevice{ 1042 | CharDevID: "devid", 1043 | SocketPath: "/var/run/sock", 1044 | }, 1045 | VhostUserDevice{ 1046 | CharDevID: "devid", 1047 | SocketPath: "/var/run/sock", 1048 | VhostUserType: VhostUserNet, 1049 | }, 1050 | VhostUserDevice{ 1051 | CharDevID: "devid", 1052 | SocketPath: "/var/run/sock", 1053 | VhostUserType: VhostUserSCSI, 1054 | }, 1055 | }, 1056 | } 1057 | 1058 | c.appendDevices() 1059 | if len(c.qemuParams) != 0 { 1060 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1061 | } 1062 | } 1063 | 1064 | func TestBadRTC(t *testing.T) { 1065 | c := &Config{} 1066 | c.appendRTC() 1067 | if len(c.qemuParams) != 0 { 1068 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1069 | } 1070 | 1071 | c = &Config{ 1072 | RTC: RTC{ 1073 | Clock: RTCClock("invalid"), 1074 | }, 1075 | } 1076 | c.appendRTC() 1077 | if len(c.qemuParams) != 0 { 1078 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1079 | } 1080 | 1081 | c = &Config{ 1082 | RTC: RTC{ 1083 | Clock: Host, 1084 | DriftFix: RTCDriftFix("invalid"), 1085 | }, 1086 | } 1087 | c.appendRTC() 1088 | if len(c.qemuParams) != 0 { 1089 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1090 | } 1091 | } 1092 | 1093 | func TestBadGlobalParam(t *testing.T) { 1094 | c := &Config{} 1095 | c.appendGlobalParam() 1096 | if len(c.qemuParams) != 0 { 1097 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1098 | } 1099 | } 1100 | 1101 | func TestBadPFlash(t *testing.T) { 1102 | c := &Config{} 1103 | c.appendPFlashParam() 1104 | if len(c.qemuParams) != 0 { 1105 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1106 | } 1107 | } 1108 | 1109 | func TestValidPFlash(t *testing.T) { 1110 | c := &Config{} 1111 | c.PFlash = []string{"flash0", "flash1"} 1112 | c.appendPFlashParam() 1113 | expected := []string{"-pflash", "flash0", "-pflash", "flash1"} 1114 | ok := reflect.DeepEqual(expected, c.qemuParams) 1115 | if !ok { 1116 | t.Errorf("Expected %v, found %v", expected, c.qemuParams) 1117 | } 1118 | } 1119 | 1120 | func TestBadSeccompSandbox(t *testing.T) { 1121 | c := &Config{} 1122 | c.appendSeccompSandbox() 1123 | if len(c.qemuParams) != 0 { 1124 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1125 | } 1126 | } 1127 | 1128 | func TestValidSeccompSandbox(t *testing.T) { 1129 | c := &Config{} 1130 | c.SeccompSandbox = string("on,obsolete=deny") 1131 | c.appendSeccompSandbox() 1132 | expected := []string{"-sandbox", "on,obsolete=deny"} 1133 | ok := reflect.DeepEqual(expected, c.qemuParams) 1134 | if !ok { 1135 | t.Errorf("Expected %v, found %v", expected, c.qemuParams) 1136 | } 1137 | } 1138 | 1139 | func TestBadVGA(t *testing.T) { 1140 | c := &Config{} 1141 | c.appendVGA() 1142 | if len(c.qemuParams) != 0 { 1143 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1144 | } 1145 | } 1146 | 1147 | func TestBadKernel(t *testing.T) { 1148 | c := &Config{} 1149 | c.appendKernel() 1150 | if len(c.qemuParams) != 0 { 1151 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1152 | } 1153 | } 1154 | 1155 | func TestBadMemoryKnobs(t *testing.T) { 1156 | c := &Config{} 1157 | c.appendMemoryKnobs() 1158 | if len(c.qemuParams) != 0 { 1159 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1160 | } 1161 | 1162 | c = &Config{ 1163 | Knobs: Knobs{ 1164 | HugePages: true, 1165 | }, 1166 | } 1167 | c.appendMemoryKnobs() 1168 | if len(c.qemuParams) != 0 { 1169 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1170 | } 1171 | 1172 | c = &Config{ 1173 | Knobs: Knobs{ 1174 | MemShared: true, 1175 | }, 1176 | } 1177 | c.appendMemoryKnobs() 1178 | if len(c.qemuParams) != 0 { 1179 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1180 | } 1181 | 1182 | c = &Config{ 1183 | Knobs: Knobs{ 1184 | MemPrealloc: true, 1185 | }, 1186 | } 1187 | c.appendMemoryKnobs() 1188 | if len(c.qemuParams) != 0 { 1189 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1190 | } 1191 | } 1192 | 1193 | func TestBadBios(t *testing.T) { 1194 | c := &Config{} 1195 | c.appendBios() 1196 | if len(c.qemuParams) != 0 { 1197 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1198 | } 1199 | } 1200 | 1201 | func TestBadIOThreads(t *testing.T) { 1202 | c := &Config{} 1203 | c.appendIOThreads() 1204 | if len(c.qemuParams) != 0 { 1205 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1206 | } 1207 | 1208 | c = &Config{ 1209 | IOThreads: []IOThread{{ID: ""}}, 1210 | } 1211 | c.appendIOThreads() 1212 | if len(c.qemuParams) != 0 { 1213 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1214 | } 1215 | } 1216 | 1217 | func TestBadIncoming(t *testing.T) { 1218 | c := &Config{} 1219 | c.appendIncoming() 1220 | if len(c.qemuParams) != 0 { 1221 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1222 | } 1223 | } 1224 | 1225 | func TestBadCPUs(t *testing.T) { 1226 | c := &Config{} 1227 | if err := c.appendCPUs(); err != nil { 1228 | t.Fatalf("No error expected got %v", err) 1229 | } 1230 | if len(c.qemuParams) != 0 { 1231 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1232 | } 1233 | 1234 | c = &Config{ 1235 | SMP: SMP{ 1236 | MaxCPUs: 1, 1237 | CPUs: 2, 1238 | }, 1239 | } 1240 | if c.appendCPUs() == nil { 1241 | t.Errorf("Error expected") 1242 | } 1243 | } 1244 | 1245 | func TestBadFwcfg(t *testing.T) { 1246 | c := &Config{} 1247 | c.appendFwCfg(nil) 1248 | if len(c.qemuParams) != 0 { 1249 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1250 | } 1251 | 1252 | c = &Config{ 1253 | FwCfg: []FwCfg{ 1254 | { 1255 | Name: "name=opt/com.mycompany/blob", 1256 | File: "./my_blob.bin", 1257 | Str: "foo", 1258 | }, 1259 | }, 1260 | } 1261 | c.appendFwCfg(nil) 1262 | if len(c.qemuParams) != 0 { 1263 | t.Errorf("Expected empty qemuParams, found %s", c.qemuParams) 1264 | } 1265 | } 1266 | 1267 | var ( 1268 | vIommuString = "-device intel-iommu,intremap=on,device-iotlb=on,caching-mode=on" 1269 | vIommuNoCacheString = "-device intel-iommu,intremap=on,device-iotlb=on,caching-mode=off" 1270 | ) 1271 | 1272 | func TestIommu(t *testing.T) { 1273 | iommu := IommuDev{ 1274 | Intremap: true, 1275 | DeviceIotlb: true, 1276 | CachingMode: true, 1277 | } 1278 | 1279 | if !iommu.Valid() { 1280 | t.Fatalf("iommu should be valid") 1281 | } 1282 | 1283 | testAppend(iommu, vIommuString, t) 1284 | 1285 | iommu.CachingMode = false 1286 | 1287 | testAppend(iommu, vIommuNoCacheString, t) 1288 | 1289 | } 1290 | 1291 | func TestAppendFwcfg(t *testing.T) { 1292 | fwcfgString := "-fw_cfg name=opt/com.mycompany/blob,file=./my_blob.bin" 1293 | fwcfg := FwCfg{ 1294 | Name: "opt/com.mycompany/blob", 1295 | File: "./my_blob.bin", 1296 | } 1297 | testAppend(fwcfg, fwcfgString, t) 1298 | 1299 | fwcfgString = "-fw_cfg name=opt/com.mycompany/blob,string=foo" 1300 | fwcfg = FwCfg{ 1301 | Name: "opt/com.mycompany/blob", 1302 | Str: "foo", 1303 | } 1304 | testAppend(fwcfg, fwcfgString, t) 1305 | } 1306 | 1307 | func TestAppendPVPanicDevice(t *testing.T) { 1308 | testCases := []struct { 1309 | dev Device 1310 | out string 1311 | }{ 1312 | {nil, ""}, 1313 | {PVPanicDevice{}, "-device pvpanic"}, 1314 | {PVPanicDevice{NoShutdown: true}, "-device pvpanic -no-shutdown"}, 1315 | } 1316 | 1317 | for _, tc := range testCases { 1318 | testAppend(tc.dev, tc.out, t) 1319 | } 1320 | } 1321 | 1322 | func TestLoaderDevice(t *testing.T) { 1323 | testCases := []struct { 1324 | dev Device 1325 | out string 1326 | }{ 1327 | {nil, ""}, 1328 | {LoaderDevice{}, ""}, 1329 | {LoaderDevice{File: "f"}, ""}, 1330 | {LoaderDevice{ID: "id"}, ""}, 1331 | {LoaderDevice{File: "f", ID: "id"}, "-device loader,file=f,id=id"}, 1332 | } 1333 | 1334 | for _, tc := range testCases { 1335 | testAppend(tc.dev, tc.out, t) 1336 | } 1337 | } 1338 | -------------------------------------------------------------------------------- /qemu/qmp.go: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright contributors to the Virtual Machine Manager for Go project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | */ 16 | 17 | package qemu 18 | 19 | import ( 20 | "bufio" 21 | "container/list" 22 | "encoding/json" 23 | "errors" 24 | "fmt" 25 | "io" 26 | "net" 27 | "os" 28 | "strconv" 29 | "syscall" 30 | "time" 31 | 32 | "context" 33 | "strings" 34 | ) 35 | 36 | // QMPLog is a logging interface used by the qemu package to log various 37 | // interesting pieces of information. Rather than introduce a dependency 38 | // on a given logging package, qemu presents this interface that allows 39 | // clients to provide their own logging type which they can use to 40 | // seamlessly integrate qemu's logs into their own logs. A QMPLog 41 | // implementation can be specified in the QMPConfig structure. 42 | type QMPLog interface { 43 | // V returns true if the given argument is less than or equal 44 | // to the implementation's defined verbosity level. 45 | V(int32) bool 46 | 47 | // Infof writes informational output to the log. A newline will be 48 | // added to the output if one is not provided. 49 | Infof(string, ...interface{}) 50 | 51 | // Warningf writes warning output to the log. A newline will be 52 | // added to the output if one is not provided. 53 | Warningf(string, ...interface{}) 54 | 55 | // Errorf writes error output to the log. A newline will be 56 | // added to the output if one is not provided. 57 | Errorf(string, ...interface{}) 58 | } 59 | 60 | type qmpNullLogger struct{} 61 | 62 | func (l qmpNullLogger) V(level int32) bool { 63 | return false 64 | } 65 | 66 | func (l qmpNullLogger) Infof(format string, v ...interface{}) { 67 | } 68 | 69 | func (l qmpNullLogger) Warningf(format string, v ...interface{}) { 70 | } 71 | 72 | func (l qmpNullLogger) Errorf(format string, v ...interface{}) { 73 | } 74 | 75 | // QMPConfig is a configuration structure that can be used to specify a 76 | // logger and a channel to which logs and QMP events are to be sent. If 77 | // neither of these fields are specified, or are set to nil, no logs will be 78 | // written and no QMP events will be reported to the client. 79 | type QMPConfig struct { 80 | // eventCh can be specified by clients who wish to receive QMP 81 | // events. 82 | EventCh chan<- QMPEvent 83 | 84 | // logger is used by the qmpStart function and all the go routines 85 | // it spawns to log information. 86 | Logger QMPLog 87 | 88 | // specify the capacity of buffer used by receive QMP response. 89 | MaxCapacity int 90 | } 91 | 92 | type qmpEventFilter struct { 93 | eventName string 94 | dataKey string 95 | dataValue string 96 | } 97 | 98 | // QMPEvent contains a single QMP event, sent on the QMPConfig.EventCh channel. 99 | type QMPEvent struct { 100 | // The name of the event, e.g., DEVICE_DELETED 101 | Name string 102 | 103 | // The data associated with the event. The contents of this map are 104 | // unprocessed by the qemu package. It is simply the result of 105 | // unmarshalling the QMP json event. Here's an example map 106 | // map[string]interface{}{ 107 | // "driver": "virtio-blk-pci", 108 | // "drive": "drive_3437843748734873483", 109 | // } 110 | Data map[string]interface{} 111 | 112 | // The event's timestamp converted to a time.Time object. 113 | Timestamp time.Time 114 | } 115 | 116 | type qmpResult struct { 117 | response interface{} 118 | err error 119 | } 120 | 121 | type qmpCommand struct { 122 | ctx context.Context 123 | res chan qmpResult 124 | name string 125 | args map[string]interface{} 126 | filter *qmpEventFilter 127 | resultReceived bool 128 | oob []byte 129 | } 130 | 131 | // QMP is a structure that contains the internal state used by startQMPLoop and 132 | // the go routines it spwans. All the contents of this structure are private. 133 | type QMP struct { 134 | cmdCh chan qmpCommand 135 | conn io.ReadWriteCloser 136 | cfg QMPConfig 137 | connectedCh chan<- *QMPVersion 138 | disconnectedCh chan struct{} 139 | version *QMPVersion 140 | } 141 | 142 | // QMPVersion contains the version number and the capabailities of a QEMU 143 | // instance, as reported in the QMP greeting message. 144 | type QMPVersion struct { 145 | Major int 146 | Minor int 147 | Micro int 148 | Capabilities []string 149 | } 150 | 151 | // CPUProperties contains the properties of a CPU instance 152 | type CPUProperties struct { 153 | Node int `json:"node-id"` 154 | Socket int `json:"socket-id"` 155 | Die int `json:"die-id"` 156 | Core int `json:"core-id"` 157 | Thread int `json:"thread-id"` 158 | } 159 | 160 | // HotpluggableCPU represents a hotpluggable CPU 161 | type HotpluggableCPU struct { 162 | Type string `json:"type"` 163 | VcpusCount int `json:"vcpus-count"` 164 | Properties CPUProperties `json:"props"` 165 | QOMPath string `json:"qom-path"` 166 | } 167 | 168 | // MemoryDevicesData cotains the data describes a memory device 169 | type MemoryDevicesData struct { 170 | Slot int `json:"slot"` 171 | Node int `json:"node"` 172 | Addr uint64 `json:"addr"` 173 | Memdev string `json:"memdev"` 174 | ID string `json:"id"` 175 | Hotpluggable bool `json:"hotpluggable"` 176 | Hotplugged bool `json:"hotplugged"` 177 | Size uint64 `json:"size"` 178 | } 179 | 180 | // MemoryDevices represents memory devices of vm 181 | type MemoryDevices struct { 182 | Data MemoryDevicesData `json:"data"` 183 | Type string `json:"type"` 184 | } 185 | 186 | // CPUInfo represents information about each virtual CPU 187 | type CPUInfo struct { 188 | CPU int `json:"CPU"` 189 | Current bool `json:"current"` 190 | Halted bool `json:"halted"` 191 | QomPath string `json:"qom_path"` 192 | Arch string `json:"arch"` 193 | Pc int `json:"pc"` 194 | ThreadID int `json:"thread_id"` 195 | Props CPUProperties `json:"props"` 196 | } 197 | 198 | // CPUInfoFast represents information about each virtual CPU 199 | type CPUInfoFast struct { 200 | CPUIndex int `json:"cpu-index"` 201 | QomPath string `json:"qom-path"` 202 | Arch string `json:"arch"` 203 | ThreadID int `json:"thread-id"` 204 | Target string `json:"target"` 205 | Props CPUProperties `json:"props"` 206 | } 207 | 208 | // MigrationRAM represents migration ram status 209 | type MigrationRAM struct { 210 | Total int64 `json:"total"` 211 | Remaining int64 `json:"remaining"` 212 | Transferred int64 `json:"transferred"` 213 | TotalTime int64 `json:"total-time"` 214 | SetupTime int64 `json:"setup-time"` 215 | ExpectedDowntime int64 `json:"expected-downtime"` 216 | Duplicate int64 `json:"duplicate"` 217 | Normal int64 `json:"normal"` 218 | NormalBytes int64 `json:"normal-bytes"` 219 | DirtySyncCount int64 `json:"dirty-sync-count"` 220 | } 221 | 222 | // MigrationDisk represents migration disk status 223 | type MigrationDisk struct { 224 | Total int64 `json:"total"` 225 | Remaining int64 `json:"remaining"` 226 | Transferred int64 `json:"transferred"` 227 | } 228 | 229 | // MigrationXbzrleCache represents migration XbzrleCache status 230 | type MigrationXbzrleCache struct { 231 | CacheSize int64 `json:"cache-size"` 232 | Bytes int64 `json:"bytes"` 233 | Pages int64 `json:"pages"` 234 | CacheMiss int64 `json:"cache-miss"` 235 | CacheMissRate int64 `json:"cache-miss-rate"` 236 | Overflow int64 `json:"overflow"` 237 | } 238 | 239 | // MigrationStatus represents migration status of a vm 240 | type MigrationStatus struct { 241 | Status string `json:"status"` 242 | Capabilities []map[string]interface{} `json:"capabilities,omitempty"` 243 | RAM MigrationRAM `json:"ram,omitempty"` 244 | Disk MigrationDisk `json:"disk,omitempty"` 245 | XbzrleCache MigrationXbzrleCache `json:"xbzrle-cache,omitempty"` 246 | } 247 | 248 | // SchemaInfo represents all QMP wire ABI 249 | type SchemaInfo struct { 250 | MetaType string `json:"meta-type"` 251 | Name string `json:"name"` 252 | } 253 | 254 | // StatusInfo represents guest running status 255 | type StatusInfo struct { 256 | Running bool `json:"running"` 257 | SingleStep bool `json:"singlestep"` 258 | Status string `json:"status"` 259 | } 260 | 261 | func (q *QMP) readLoop(fromVMCh chan<- []byte) { 262 | scanner := bufio.NewScanner(q.conn) 263 | if q.cfg.MaxCapacity > 0 { 264 | buffer := make([]byte, q.cfg.MaxCapacity) 265 | scanner.Buffer(buffer, q.cfg.MaxCapacity) 266 | } 267 | 268 | for scanner.Scan() { 269 | line := scanner.Bytes() 270 | // Since []byte channel type transfer slice info(include slice underlying array pointer, len, cap) 271 | // between channel sender and receiver. scanner.Bytes() returned slice's underlying array 272 | // may point to data that will be overwritten by a subsequent call to Scan(reference from: 273 | // https://golang.org/pkg/bufio/#Scanner.Bytes), which may make receiver read mixed data, 274 | // so we need to copy line to new allocated space and then send to channel receiver 275 | sendLine := make([]byte, len(line)) 276 | copy(sendLine, line) 277 | 278 | fromVMCh <- sendLine 279 | } 280 | q.cfg.Logger.Infof("scanner return error: %v", scanner.Err()) 281 | close(fromVMCh) 282 | } 283 | 284 | func (q *QMP) processQMPEvent(cmdQueue *list.List, name interface{}, data interface{}, 285 | timestamp interface{}) { 286 | 287 | strname, ok := name.(string) 288 | if !ok { 289 | return 290 | } 291 | 292 | var eventData map[string]interface{} 293 | if data != nil { 294 | eventData, _ = data.(map[string]interface{}) 295 | } 296 | 297 | cmdEl := cmdQueue.Front() 298 | if cmdEl != nil { 299 | cmd := cmdEl.Value.(*qmpCommand) 300 | filter := cmd.filter 301 | if filter != nil { 302 | if filter.eventName == strname { 303 | match := filter.dataKey == "" 304 | if !match && eventData != nil { 305 | match = eventData[filter.dataKey] == filter.dataValue 306 | } 307 | if match { 308 | if cmd.resultReceived { 309 | q.finaliseCommand(cmdEl, cmdQueue, true) 310 | } else { 311 | cmd.filter = nil 312 | } 313 | } 314 | } 315 | } 316 | } 317 | 318 | if q.cfg.EventCh != nil { 319 | ev := QMPEvent{ 320 | Name: strname, 321 | Data: eventData, 322 | } 323 | if timestamp != nil { 324 | timestamp, ok := timestamp.(map[string]interface{}) 325 | if ok { 326 | seconds, _ := timestamp["seconds"].(float64) 327 | microseconds, _ := timestamp["microseconds"].(float64) 328 | ev.Timestamp = time.Unix(int64(seconds), int64(microseconds)) 329 | } 330 | } 331 | 332 | q.cfg.EventCh <- ev 333 | } 334 | } 335 | 336 | func (q *QMP) finaliseCommandWithResponse(cmdEl *list.Element, cmdQueue *list.List, succeeded bool, response interface{}) { 337 | cmd := cmdEl.Value.(*qmpCommand) 338 | cmdQueue.Remove(cmdEl) 339 | select { 340 | case <-cmd.ctx.Done(): 341 | default: 342 | if succeeded { 343 | cmd.res <- qmpResult{response: response} 344 | } else { 345 | cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed: %v", response)} 346 | } 347 | } 348 | if cmdQueue.Len() > 0 { 349 | q.writeNextQMPCommand(cmdQueue) 350 | } 351 | } 352 | 353 | func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeeded bool) { 354 | q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, nil) 355 | } 356 | 357 | func (q *QMP) errorDesc(errorData interface{}) (string, error) { 358 | // convert error to json 359 | data, err := json.Marshal(errorData) 360 | if err != nil { 361 | return "", fmt.Errorf("unable to extract error information: %v", err) 362 | } 363 | 364 | // see: https://github.com/qemu/qemu/blob/stable-2.12/qapi/qmp-dispatch.c#L125 365 | var qmpErr map[string]string 366 | // convert json to qmpError 367 | if err = json.Unmarshal(data, &qmpErr); err != nil { 368 | return "", fmt.Errorf("unable to convert json to qmpError: %v", err) 369 | } 370 | 371 | return qmpErr["desc"], nil 372 | } 373 | 374 | func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) { 375 | var vmData map[string]interface{} 376 | err := json.Unmarshal(line, &vmData) 377 | if err != nil { 378 | q.cfg.Logger.Warningf("Unable to decode response [%s] from VM: %v", 379 | string(line), err) 380 | return 381 | } 382 | if evname, found := vmData["event"]; found { 383 | q.processQMPEvent(cmdQueue, evname, vmData["data"], vmData["timestamp"]) 384 | return 385 | } 386 | 387 | response, succeeded := vmData["return"] 388 | errData, failed := vmData["error"] 389 | 390 | if !succeeded && !failed { 391 | return 392 | } 393 | 394 | cmdEl := cmdQueue.Front() 395 | if cmdEl == nil { 396 | q.cfg.Logger.Warningf("Unexpected command response received [%s] from VM", 397 | string(line)) 398 | return 399 | } 400 | cmd := cmdEl.Value.(*qmpCommand) 401 | if failed || cmd.filter == nil { 402 | if errData != nil { 403 | desc, err := q.errorDesc(errData) 404 | if err != nil { 405 | q.cfg.Logger.Infof("Get error description failed: %v", err) 406 | } else { 407 | response = desc 408 | } 409 | } 410 | q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, response) 411 | } else { 412 | cmd.resultReceived = true 413 | } 414 | } 415 | 416 | func currentCommandDoneCh(cmdQueue *list.List) <-chan struct{} { 417 | cmdEl := cmdQueue.Front() 418 | if cmdEl == nil { 419 | return nil 420 | } 421 | cmd := cmdEl.Value.(*qmpCommand) 422 | return cmd.ctx.Done() 423 | } 424 | 425 | func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) { 426 | cmdEl := cmdQueue.Front() 427 | cmd := cmdEl.Value.(*qmpCommand) 428 | cmdData := make(map[string]interface{}) 429 | cmdData["execute"] = cmd.name 430 | if cmd.args != nil { 431 | cmdData["arguments"] = cmd.args 432 | } 433 | encodedCmd, err := json.Marshal(&cmdData) 434 | if err != nil { 435 | cmd.res <- qmpResult{ 436 | err: fmt.Errorf("unable to marhsall command %s: %v", 437 | cmd.name, err), 438 | } 439 | cmdQueue.Remove(cmdEl) 440 | } 441 | encodedCmd = append(encodedCmd, '\n') 442 | if unixConn, ok := q.conn.(*net.UnixConn); ok && len(cmd.oob) > 0 { 443 | _, _, err = unixConn.WriteMsgUnix(encodedCmd, cmd.oob, nil) 444 | } else { 445 | _, err = q.conn.Write(encodedCmd) 446 | } 447 | 448 | if err != nil { 449 | cmd.res <- qmpResult{ 450 | err: fmt.Errorf("unable to write command to qmp socket %v", err), 451 | } 452 | cmdQueue.Remove(cmdEl) 453 | } 454 | } 455 | 456 | func failOutstandingCommands(cmdQueue *list.List) { 457 | for e := cmdQueue.Front(); e != nil; e = e.Next() { 458 | cmd := e.Value.(*qmpCommand) 459 | select { 460 | case cmd.res <- qmpResult{ 461 | err: errors.New("exitting QMP loop, command cancelled"), 462 | }: 463 | case <-cmd.ctx.Done(): 464 | } 465 | } 466 | } 467 | 468 | func (q *QMP) cancelCurrentCommand(cmdQueue *list.List) { 469 | cmdEl := cmdQueue.Front() 470 | cmd := cmdEl.Value.(*qmpCommand) 471 | if cmd.resultReceived { 472 | q.finaliseCommand(cmdEl, cmdQueue, false) 473 | } else { 474 | cmd.filter = nil 475 | } 476 | } 477 | 478 | func (q *QMP) parseVersion(version []byte) *QMPVersion { 479 | var qmp map[string]interface{} 480 | err := json.Unmarshal(version, &qmp) 481 | if err != nil { 482 | q.cfg.Logger.Errorf("Invalid QMP greeting: %s", string(version)) 483 | return nil 484 | } 485 | 486 | versionMap := qmp 487 | for _, k := range []string{"QMP", "version", "qemu"} { 488 | versionMap, _ = versionMap[k].(map[string]interface{}) 489 | if versionMap == nil { 490 | return nil 491 | } 492 | } 493 | 494 | micro, _ := versionMap["micro"].(float64) 495 | minor, _ := versionMap["minor"].(float64) 496 | major, _ := versionMap["major"].(float64) 497 | capabilities, _ := qmp["QMP"].(map[string]interface{})["capabilities"].([]interface{}) 498 | stringcaps := make([]string, 0, len(capabilities)) 499 | for _, c := range capabilities { 500 | if cap, ok := c.(string); ok { 501 | stringcaps = append(stringcaps, cap) 502 | } 503 | } 504 | return &QMPVersion{Major: int(major), 505 | Minor: int(minor), 506 | Micro: int(micro), 507 | Capabilities: stringcaps, 508 | } 509 | } 510 | 511 | // The qemu package allows multiple QMP commands to be submitted concurrently 512 | // from different Go routines. Unfortunately, QMP doesn't really support parallel 513 | // commands as there is no way reliable way to associate a command response 514 | // with a request. For this reason we need to submit our commands to 515 | // QMP serially. The qemu package performs this serialisation using a 516 | // queue (cmdQueue owned by mainLoop). We use a queue rather than a simple 517 | // mutex so we can support cancelling of commands (see below) and ordered 518 | // execution of commands, i.e., if command B is issued before command C, 519 | // it should be executed before command C even if both commands are initially 520 | // blocked waiting for command A to finish. This would be hard to achieve with 521 | // a simple mutex. 522 | // 523 | // Cancelling is a little tricky. Commands such as ExecuteQMPCapabilities 524 | // can be cancelled by cancelling or timing out their contexts. When a 525 | // command is cancelled the calling function, e.g., ExecuteQMPCapabilities, 526 | // will return but we may not be able to remove the command's entry from 527 | // the command queue or issue the next command. There are two scenarios 528 | // here. 529 | // 530 | // 1. The command has been processed by QMP, i.e., we have received a 531 | // return or an error, but is still blocking as it is waiting for 532 | // an event. For example, the ExecuteDeviceDel blocks until a DEVICE_DELETED 533 | // event is received. When such a command is cancelled we can remove it 534 | // from the queue and start issuing the next command. When the DEVICE_DELETED 535 | // event eventually arrives it will just be ignored. 536 | // 537 | // 2. The command has not been processed by QMP. In this case the command 538 | // needs to remain on the cmdQueue until the response to this command is 539 | // received from QMP. During this time no new commands can be issued. When the 540 | // response is received, it is discarded (as no one is interested in the result 541 | // any more), the entry is removed from the cmdQueue and we can proceed to 542 | // execute the next command. 543 | 544 | func (q *QMP) mainLoop() { 545 | cmdQueue := list.New().Init() 546 | fromVMCh := make(chan []byte) 547 | go q.readLoop(fromVMCh) 548 | 549 | defer func() { 550 | if q.cfg.EventCh != nil { 551 | close(q.cfg.EventCh) 552 | } 553 | /* #nosec */ 554 | _ = q.conn.Close() 555 | <-fromVMCh 556 | failOutstandingCommands(cmdQueue) 557 | close(q.disconnectedCh) 558 | }() 559 | 560 | var cmdDoneCh <-chan struct{} 561 | var version *QMPVersion 562 | ready := false 563 | 564 | for { 565 | select { 566 | case cmd, ok := <-q.cmdCh: 567 | if !ok { 568 | return 569 | } 570 | _ = cmdQueue.PushBack(&cmd) 571 | 572 | // We only want to execute the new cmd if QMP is 573 | // ready and there are no other commands pending. 574 | // If there are commands pending our new command 575 | // will get run when the pending commands complete. 576 | if ready && cmdQueue.Len() == 1 { 577 | q.writeNextQMPCommand(cmdQueue) 578 | cmdDoneCh = currentCommandDoneCh(cmdQueue) 579 | } 580 | 581 | case line, ok := <-fromVMCh: 582 | if !ok { 583 | return 584 | } 585 | 586 | if !ready { 587 | // Not ready yet. Check if line is the QMP version. 588 | // Sometimes QMP events are thrown before the QMP version, 589 | // hence it's not a guarantee that the first data read from 590 | // the channel is the QMP version. 591 | version = q.parseVersion(line) 592 | if version != nil { 593 | q.connectedCh <- version 594 | ready = true 595 | } 596 | // Do not process QMP input to avoid deadlocks. 597 | break 598 | } 599 | 600 | q.processQMPInput(line, cmdQueue) 601 | cmdDoneCh = currentCommandDoneCh(cmdQueue) 602 | 603 | case <-cmdDoneCh: 604 | q.cancelCurrentCommand(cmdQueue) 605 | cmdDoneCh = currentCommandDoneCh(cmdQueue) 606 | } 607 | } 608 | } 609 | 610 | func startQMPLoop(conn io.ReadWriteCloser, cfg QMPConfig, 611 | connectedCh chan<- *QMPVersion, disconnectedCh chan struct{}) *QMP { 612 | q := &QMP{ 613 | cmdCh: make(chan qmpCommand), 614 | conn: conn, 615 | cfg: cfg, 616 | connectedCh: connectedCh, 617 | disconnectedCh: disconnectedCh, 618 | } 619 | go q.mainLoop() 620 | return q 621 | } 622 | 623 | func (q *QMP) executeCommandWithResponse(ctx context.Context, name string, args map[string]interface{}, 624 | oob []byte, filter *qmpEventFilter) (interface{}, error) { 625 | var err error 626 | var response interface{} 627 | resCh := make(chan qmpResult) 628 | select { 629 | case <-q.disconnectedCh: 630 | err = errors.New("exitting QMP loop, command cancelled") 631 | case q.cmdCh <- qmpCommand{ 632 | ctx: ctx, 633 | res: resCh, 634 | name: name, 635 | args: args, 636 | filter: filter, 637 | oob: oob, 638 | }: 639 | } 640 | 641 | if err != nil { 642 | return response, err 643 | } 644 | 645 | select { 646 | case res := <-resCh: 647 | err = res.err 648 | response = res.response 649 | case <-ctx.Done(): 650 | err = ctx.Err() 651 | } 652 | 653 | return response, err 654 | } 655 | 656 | func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]interface{}, 657 | filter *qmpEventFilter) error { 658 | 659 | _, err := q.executeCommandWithResponse(ctx, name, args, nil, filter) 660 | return err 661 | } 662 | 663 | // QMPStart connects to a unix domain socket maintained by a QMP instance. It 664 | // waits to receive the QMP welcome message via the socket and spawns some go 665 | // routines to manage the socket. The function returns a *QMP which can be 666 | // used by callers to send commands to the QEMU instance or to close the 667 | // socket and all the go routines that have been spawned to monitor it. A 668 | // *QMPVersion is also returned. This structure contains the version and 669 | // capabilities information returned by the QEMU instance in its welcome 670 | // message. 671 | // 672 | // socket contains the path to the domain socket. cfg contains some options 673 | // that can be specified by the caller, namely where the qemu package should 674 | // send logs and QMP events. disconnectedCh is a channel that must be supplied 675 | // by the caller. It is closed when an error occurs openning or writing to 676 | // or reading from the unix domain socket. This implies that the QEMU instance 677 | // that opened the socket has closed. 678 | // 679 | // If this function returns without error, callers should call QMP.Shutdown 680 | // when they wish to stop monitoring the QMP instance. This is not strictly 681 | // necessary if the QEMU instance exits and the disconnectedCh is closed, but 682 | // doing so will not cause any problems. 683 | // 684 | // Commands can be sent to the QEMU instance via the QMP.Execute methods. 685 | // These commands are executed serially, even if the QMP.Execute methods 686 | // are called from different go routines. The QMP.Execute methods will 687 | // block until they have received a success or failure message from QMP, 688 | // i.e., {"return": {}} or {"error":{}}, and in some cases certain events 689 | // are received. 690 | // 691 | // QEMU currently requires that the "qmp_capabilties" command is sent before any 692 | // other command. Therefore you must call qmp.ExecuteQMPCapabilities() before 693 | // you execute any other command. 694 | func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh chan struct{}) (*QMP, *QMPVersion, error) { 695 | if cfg.Logger == nil { 696 | cfg.Logger = qmpNullLogger{} 697 | } 698 | dialer := net.Dialer{Cancel: ctx.Done()} 699 | conn, err := dialer.Dial("unix", socket) 700 | if err != nil { 701 | cfg.Logger.Warningf("Unable to connect to unix socket (%s): %v", socket, err) 702 | close(disconnectedCh) 703 | return nil, nil, err 704 | } 705 | 706 | connectedCh := make(chan *QMPVersion) 707 | 708 | q := startQMPLoop(conn, cfg, connectedCh, disconnectedCh) 709 | select { 710 | case <-ctx.Done(): 711 | q.Shutdown() 712 | <-disconnectedCh 713 | return nil, nil, fmt.Errorf("canceled by caller") 714 | case <-disconnectedCh: 715 | return nil, nil, fmt.Errorf("lost connection to VM") 716 | case q.version = <-connectedCh: 717 | if q.version == nil { 718 | return nil, nil, fmt.Errorf("failed to find QMP version information") 719 | } 720 | } 721 | 722 | if q.version.Major < 5 { 723 | return nil, nil, fmt.Errorf("govmm requires qemu version 5.0 or later, this is qemu (%d.%d)", q.version.Major, q.version.Minor) 724 | } 725 | 726 | return q, q.version, nil 727 | } 728 | 729 | // Shutdown closes the domain socket used to monitor a QEMU instance and 730 | // terminates all the go routines spawned by QMPStart to manage that instance. 731 | // QMP.Shutdown does not shut down the running instance. Calling QMP.Shutdown 732 | // will result in the disconnectedCh channel being closed, indicating that we 733 | // have lost connection to the QMP instance. In this case it does not indicate 734 | // that the instance has quit. 735 | // 736 | // QMP.Shutdown should not be called concurrently with other QMP methods. It 737 | // should not be called twice on the same QMP instance. 738 | // 739 | // Calling QMP.Shutdown after the disconnectedCh channel is closed is permitted but 740 | // will not have any effect. 741 | func (q *QMP) Shutdown() { 742 | close(q.cmdCh) 743 | } 744 | 745 | // ExecuteQMPCapabilities executes the qmp_capabilities command on the instance. 746 | func (q *QMP) ExecuteQMPCapabilities(ctx context.Context) error { 747 | return q.executeCommand(ctx, "qmp_capabilities", nil, nil) 748 | } 749 | 750 | // ExecuteStop sends the stop command to the instance. 751 | func (q *QMP) ExecuteStop(ctx context.Context) error { 752 | return q.executeCommand(ctx, "stop", nil, nil) 753 | } 754 | 755 | // ExecuteCont sends the cont command to the instance. 756 | func (q *QMP) ExecuteCont(ctx context.Context) error { 757 | return q.executeCommand(ctx, "cont", nil, nil) 758 | } 759 | 760 | // ExecuteSystemPowerdown sends the system_powerdown command to the instance. 761 | // This function will block until the SHUTDOWN event is received. 762 | func (q *QMP) ExecuteSystemPowerdown(ctx context.Context) error { 763 | filter := &qmpEventFilter{ 764 | eventName: "POWERDOWN", 765 | } 766 | return q.executeCommand(ctx, "system_powerdown", nil, filter) 767 | } 768 | 769 | // ExecuteQuit sends the quit command to the instance, terminating 770 | // the QMP instance immediately. 771 | func (q *QMP) ExecuteQuit(ctx context.Context) error { 772 | return q.executeCommand(ctx, "quit", nil, nil) 773 | } 774 | 775 | func (q *QMP) blockdevAddBaseArgs(driver, device, blockdevID string, ro bool) (map[string]interface{}, map[string]interface{}) { 776 | var args map[string]interface{} 777 | 778 | blockdevArgs := map[string]interface{}{ 779 | "driver": "raw", 780 | "read-only": ro, 781 | "file": map[string]interface{}{ 782 | "driver": driver, 783 | "filename": device, 784 | }, 785 | } 786 | 787 | blockdevArgs["node-name"] = blockdevID 788 | args = blockdevArgs 789 | 790 | return args, blockdevArgs 791 | } 792 | 793 | // ExecuteBlockdevAdd sends a blockdev-add to the QEMU instance. device is the 794 | // path of the device to add, e.g., /dev/rdb0, and blockdevID is an identifier 795 | // used to name the device. As this identifier will be passed directly to QMP, 796 | // it must obey QMP's naming rules, e,g., it must start with a letter. 797 | func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, device, blockdevID string, ro bool) error { 798 | args, _ := q.blockdevAddBaseArgs("host_device", device, blockdevID, ro) 799 | 800 | return q.executeCommand(ctx, "blockdev-add", args, nil) 801 | } 802 | 803 | // ExecuteBlockdevAddWithCache has two more parameters direct and noFlush 804 | // than ExecuteBlockdevAdd. 805 | // They are cache-related options for block devices that are described in 806 | // https://github.com/qemu/qemu/blob/master/qapi/block-core.json. 807 | // direct denotes whether use of O_DIRECT (bypass the host page cache) 808 | // is enabled. noFlush denotes whether flush requests for the device are 809 | // ignored. 810 | func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, device, blockdevID string, direct, noFlush, ro bool) error { 811 | args, blockdevArgs := q.blockdevAddBaseArgs("host_device", device, blockdevID, ro) 812 | 813 | blockdevArgs["cache"] = map[string]interface{}{ 814 | "direct": direct, 815 | "no-flush": noFlush, 816 | } 817 | 818 | return q.executeCommand(ctx, "blockdev-add", args, nil) 819 | } 820 | 821 | // ExecuteBlockdevAddWithDriverCache has three one parameter driver 822 | // than ExecuteBlockdevAddWithCache. 823 | // Parameter driver can set the driver of block device. 824 | func (q *QMP) ExecuteBlockdevAddWithDriverCache(ctx context.Context, driver, device, blockdevID string, direct, noFlush, ro bool) error { 825 | args, blockdevArgs := q.blockdevAddBaseArgs(driver, device, blockdevID, ro) 826 | 827 | blockdevArgs["cache"] = map[string]interface{}{ 828 | "direct": direct, 829 | "no-flush": noFlush, 830 | } 831 | 832 | return q.executeCommand(ctx, "blockdev-add", args, nil) 833 | } 834 | 835 | // ExecuteDeviceAdd adds the guest portion of a device to a QEMU instance 836 | // using the device_add command. blockdevID should match the blockdevID passed 837 | // to a previous call to ExecuteBlockdevAdd. devID is the id of the device to 838 | // add. Both strings must be valid QMP identifiers. driver is the name of the 839 | // driver,e.g., virtio-blk-pci, and bus is the name of the bus. bus is optional. 840 | // shared denotes if the drive can be shared allowing it to be passed more than once. 841 | // disableModern indicates if virtio version 1.0 should be replaced by the 842 | // former version 0.9, as there is a KVM bug that occurs when using virtio 843 | // 1.0 in nested environments. 844 | func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern bool) error { 845 | args := map[string]interface{}{ 846 | "id": devID, 847 | "driver": driver, 848 | "drive": blockdevID, 849 | } 850 | 851 | var transport VirtioTransport 852 | 853 | if transport.isVirtioCCW(nil) { 854 | args["devno"] = bus 855 | } else if bus != "" { 856 | args["bus"] = bus 857 | } 858 | 859 | if shared { 860 | args["share-rw"] = "on" 861 | } 862 | if transport.isVirtioPCI(nil) { 863 | args["romfile"] = romfile 864 | 865 | if disableModern { 866 | args["disable-modern"] = disableModern 867 | } 868 | } 869 | 870 | return q.executeCommand(ctx, "device_add", args, nil) 871 | } 872 | 873 | // ExecuteSCSIDeviceAdd adds the guest portion of a block device to a QEMU instance 874 | // using a SCSI driver with the device_add command. blockdevID should match the 875 | // blockdevID passed to a previous call to ExecuteBlockdevAdd. devID is the id of 876 | // the device to add. Both strings must be valid QMP identifiers. driver is the name of the 877 | // scsi driver,e.g., scsi-hd, and bus is the name of a SCSI controller bus. 878 | // scsiID is the SCSI id, lun is logical unit number. scsiID and lun are optional, a negative value 879 | // for scsiID and lun is ignored. shared denotes if the drive can be shared allowing it 880 | // to be passed more than once. 881 | // disableModern indicates if virtio version 1.0 should be replaced by the 882 | // former version 0.9, as there is a KVM bug that occurs when using virtio 883 | // 1.0 in nested environments. 884 | func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, scsiID, lun int, shared, disableModern bool) error { 885 | // TBD: Add drivers for scsi passthrough like scsi-generic and scsi-block 886 | drivers := []string{"scsi-hd", "scsi-cd", "scsi-disk"} 887 | 888 | isSCSIDriver := false 889 | for _, d := range drivers { 890 | if driver == d { 891 | isSCSIDriver = true 892 | break 893 | } 894 | } 895 | 896 | if !isSCSIDriver { 897 | return fmt.Errorf("invalid SCSI driver provided %s", driver) 898 | } 899 | 900 | args := map[string]interface{}{ 901 | "id": devID, 902 | "driver": driver, 903 | "drive": blockdevID, 904 | "bus": bus, 905 | } 906 | 907 | if scsiID >= 0 { 908 | args["scsi-id"] = scsiID 909 | } 910 | if lun >= 0 { 911 | args["lun"] = lun 912 | } 913 | if shared { 914 | args["share-rw"] = "on" 915 | } 916 | 917 | return q.executeCommand(ctx, "device_add", args, nil) 918 | } 919 | 920 | // ExecuteBlockdevDel deletes a block device by sending blockdev-del 921 | // command. blockdevID is the id of the block device to be deleted. 922 | // Typically, this will match the id passed to ExecuteBlockdevAdd. It 923 | // must be a valid QMP id. 924 | func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error { 925 | args := map[string]interface{}{} 926 | 927 | args["node-name"] = blockdevID 928 | return q.executeCommand(ctx, "blockdev-del", args, nil) 929 | } 930 | 931 | // ExecuteChardevDel deletes a char device by sending a chardev-remove command. 932 | // chardevID is the id of the char device to be deleted. Typically, this will 933 | // match the id passed to ExecuteCharDevUnixSocketAdd. It must be a valid QMP id. 934 | func (q *QMP) ExecuteChardevDel(ctx context.Context, chardevID string) error { 935 | args := map[string]interface{}{ 936 | "id": chardevID, 937 | } 938 | 939 | return q.executeCommand(ctx, "chardev-remove", args, nil) 940 | } 941 | 942 | // ExecuteNetdevAdd adds a Net device to a QEMU instance 943 | // using the netdev_add command. netdevID is the id of the device to add. 944 | // Must be valid QMP identifier. 945 | func (q *QMP) ExecuteNetdevAdd(ctx context.Context, netdevType, netdevID, ifname, downscript, script string, queues int) error { 946 | args := map[string]interface{}{ 947 | "type": netdevType, 948 | "id": netdevID, 949 | "ifname": ifname, 950 | "downscript": downscript, 951 | "script": script, 952 | } 953 | if queues > 1 { 954 | args["queues"] = queues 955 | } 956 | 957 | return q.executeCommand(ctx, "netdev_add", args, nil) 958 | } 959 | 960 | // ExecuteNetdevChardevAdd adds a Net device to a QEMU instance 961 | // using the netdev_add command. netdevID is the id of the device to add. 962 | // Must be valid QMP identifier. 963 | func (q *QMP) ExecuteNetdevChardevAdd(ctx context.Context, netdevType, netdevID, chardev string, queues int) error { 964 | args := map[string]interface{}{ 965 | "type": netdevType, 966 | "id": netdevID, 967 | "chardev": chardev, 968 | } 969 | if queues > 1 { 970 | args["queues"] = queues 971 | } 972 | 973 | return q.executeCommand(ctx, "netdev_add", args, nil) 974 | } 975 | 976 | // ExecuteNetdevAddByFds adds a Net device to a QEMU instance 977 | // using the netdev_add command by fds and vhostfds. netdevID is the id of the device to add. 978 | // Must be valid QMP identifier. 979 | func (q *QMP) ExecuteNetdevAddByFds(ctx context.Context, netdevType, netdevID string, fdNames, vhostFdNames []string) error { 980 | fdNameStr := strings.Join(fdNames, ":") 981 | args := map[string]interface{}{ 982 | "type": netdevType, 983 | "id": netdevID, 984 | "fds": fdNameStr, 985 | } 986 | if len(vhostFdNames) > 0 { 987 | vhostFdNameStr := strings.Join(vhostFdNames, ":") 988 | args["vhost"] = true 989 | args["vhostfds"] = vhostFdNameStr 990 | } 991 | 992 | return q.executeCommand(ctx, "netdev_add", args, nil) 993 | } 994 | 995 | // ExecuteNetdevDel deletes a Net device from a QEMU instance 996 | // using the netdev_del command. netdevID is the id of the device to delete. 997 | func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error { 998 | args := map[string]interface{}{ 999 | "id": netdevID, 1000 | } 1001 | return q.executeCommand(ctx, "netdev_del", args, nil) 1002 | } 1003 | 1004 | // ExecuteNetPCIDeviceAdd adds a Net PCI device to a QEMU instance 1005 | // using the device_add command. devID is the id of the device to add. 1006 | // Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. 1007 | // queues is the number of queues of a nic. 1008 | // disableModern indicates if virtio version 1.0 should be replaced by the 1009 | // former version 0.9, as there is a KVM bug that occurs when using virtio 1010 | // 1.0 in nested environments. 1011 | func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus, romfile string, queues int, disableModern bool) error { 1012 | args := map[string]interface{}{ 1013 | "id": devID, 1014 | "driver": VirtioNetPCI, 1015 | "romfile": romfile, 1016 | } 1017 | 1018 | if bus != "" { 1019 | args["bus"] = bus 1020 | } 1021 | if addr != "" { 1022 | args["addr"] = addr 1023 | } 1024 | if macAddr != "" { 1025 | args["mac"] = macAddr 1026 | } 1027 | if netdevID != "" { 1028 | args["netdev"] = netdevID 1029 | } 1030 | if disableModern { 1031 | args["disable-modern"] = disableModern 1032 | } 1033 | 1034 | if queues > 0 { 1035 | // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) 1036 | // -device virtio-net-pci,mq=on,vectors=2N+2... 1037 | // enable mq in guest by 'ethtool -L eth0 combined $queue_num' 1038 | // Clearlinux automatically sets up the queues properly 1039 | // The agent implementation should do this to ensure that it is 1040 | // always set 1041 | args["mq"] = "on" 1042 | args["vectors"] = 2*queues + 2 1043 | } 1044 | 1045 | return q.executeCommand(ctx, "device_add", args, nil) 1046 | } 1047 | 1048 | // ExecuteNetCCWDeviceAdd adds a Net CCW device to a QEMU instance 1049 | // using the device_add command. devID is the id of the device to add. 1050 | // Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add. 1051 | // queues is the number of queues of a nic. 1052 | func (q *QMP) ExecuteNetCCWDeviceAdd(ctx context.Context, netdevID, devID, macAddr, bus string, queues int) error { 1053 | args := map[string]interface{}{ 1054 | "id": devID, 1055 | "driver": VirtioNetCCW, 1056 | "netdev": netdevID, 1057 | "mac": macAddr, 1058 | "devno": bus, 1059 | } 1060 | 1061 | if queues > 0 { 1062 | args["mq"] = "on" 1063 | } 1064 | 1065 | return q.executeCommand(ctx, "device_add", args, nil) 1066 | } 1067 | 1068 | // ExecuteDeviceDel deletes guest portion of a QEMU device by sending a 1069 | // device_del command. devId is the identifier of the device to delete. 1070 | // Typically it would match the devID parameter passed to an earlier call 1071 | // to ExecuteDeviceAdd. It must be a valid QMP identidier. 1072 | // 1073 | // This method blocks until a DEVICE_DELETED event is received for devID. 1074 | func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { 1075 | args := map[string]interface{}{ 1076 | "id": devID, 1077 | } 1078 | filter := &qmpEventFilter{ 1079 | eventName: "DEVICE_DELETED", 1080 | dataKey: "device", 1081 | dataValue: devID, 1082 | } 1083 | return q.executeCommand(ctx, "device_del", args, filter) 1084 | } 1085 | 1086 | // ExecutePCIDeviceAdd is the PCI version of ExecuteDeviceAdd. This function can be used 1087 | // to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the 1088 | // device address on its parent bus. bus is optional. queues specifies the number of queues of 1089 | // a block device. shared denotes if the drive can be shared allowing it to be passed more than once. 1090 | // disableModern indicates if virtio version 1.0 should be replaced by the 1091 | // former version 0.9, as there is a KVM bug that occurs when using virtio 1092 | // 1.0 in nested environments. 1093 | func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, queues int, shared, disableModern bool) error { 1094 | args := map[string]interface{}{ 1095 | "id": devID, 1096 | "driver": driver, 1097 | "drive": blockdevID, 1098 | "addr": addr, 1099 | } 1100 | if bus != "" { 1101 | args["bus"] = bus 1102 | } 1103 | if shared { 1104 | args["share-rw"] = "on" 1105 | } 1106 | if queues > 0 { 1107 | args["num-queues"] = strconv.Itoa(queues) 1108 | } 1109 | 1110 | var transport VirtioTransport 1111 | 1112 | if transport.isVirtioPCI(nil) { 1113 | args["romfile"] = romfile 1114 | 1115 | if disableModern { 1116 | args["disable-modern"] = disableModern 1117 | } 1118 | } 1119 | 1120 | return q.executeCommand(ctx, "device_add", args, nil) 1121 | } 1122 | 1123 | // ExecutePCIVhostUserDevAdd adds a vhost-user device to a QEMU instance using the device_add command. 1124 | // This function can be used to hot plug vhost-user devices on PCI(E) bridges. 1125 | // It receives the bus and the device address on its parent bus. bus is optional. 1126 | // devID is the id of the device to add.Must be valid QMP identifier. chardevID 1127 | // is the QMP identifier of character device using a unix socket as backend. 1128 | // driver is the name of vhost-user driver, like vhost-user-blk-pci. 1129 | func (q *QMP) ExecutePCIVhostUserDevAdd(ctx context.Context, driver, devID, chardevID, addr, bus string) error { 1130 | args := map[string]interface{}{ 1131 | "driver": driver, 1132 | "id": devID, 1133 | "chardev": chardevID, 1134 | "addr": addr, 1135 | } 1136 | 1137 | if bus != "" { 1138 | args["bus"] = bus 1139 | } 1140 | 1141 | return q.executeCommand(ctx, "device_add", args, nil) 1142 | } 1143 | 1144 | // ExecuteVFIODeviceAdd adds a VFIO device to a QEMU instance using the device_add command. 1145 | // devID is the id of the device to add. Must be valid QMP identifier. 1146 | // bdf is the PCI bus-device-function of the pci device. 1147 | // bus is optional. When hot plugging a PCIe device, the bus can be the ID of the pcie-root-port. 1148 | func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, bus, romfile string) error { 1149 | var driver string 1150 | var transport VirtioTransport 1151 | 1152 | if transport.isVirtioCCW(nil) { 1153 | driver = string(VfioCCW) 1154 | } else { 1155 | driver = string(VfioPCI) 1156 | } 1157 | 1158 | args := map[string]interface{}{ 1159 | "id": devID, 1160 | "driver": driver, 1161 | "host": bdf, 1162 | "romfile": romfile, 1163 | } 1164 | if bus != "" { 1165 | args["bus"] = bus 1166 | } 1167 | return q.executeCommand(ctx, "device_add", args, nil) 1168 | } 1169 | 1170 | // ExecutePCIVFIODeviceAdd adds a VFIO device to a QEMU instance using the device_add command. 1171 | // This function can be used to hot plug VFIO devices on PCI(E) bridges, unlike 1172 | // ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. 1173 | // bus is optional. devID is the id of the device to add.Must be valid QMP identifier. bdf is the 1174 | // PCI bus-device-function of the pci device. 1175 | func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus, romfile string) error { 1176 | args := map[string]interface{}{ 1177 | "id": devID, 1178 | "driver": VfioPCI, 1179 | "host": bdf, 1180 | "addr": addr, 1181 | "romfile": romfile, 1182 | } 1183 | 1184 | if bus != "" { 1185 | args["bus"] = bus 1186 | } 1187 | return q.executeCommand(ctx, "device_add", args, nil) 1188 | } 1189 | 1190 | // ExecutePCIVFIOMediatedDeviceAdd adds a VFIO mediated device to a QEMU instance using the device_add command. 1191 | // This function can be used to hot plug VFIO mediated devices on PCI(E) bridges or root bus, unlike 1192 | // ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus. 1193 | // devID is the id of the device to add. Must be valid QMP identifier. sysfsdev is the VFIO mediated device. 1194 | // 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. 1195 | func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus, romfile string) error { 1196 | args := map[string]interface{}{ 1197 | "id": devID, 1198 | "driver": VfioPCI, 1199 | "sysfsdev": sysfsdev, 1200 | "romfile": romfile, 1201 | } 1202 | 1203 | if bus != "" { 1204 | args["bus"] = bus 1205 | } 1206 | if addr != "" { 1207 | args["addr"] = addr 1208 | } 1209 | return q.executeCommand(ctx, "device_add", args, nil) 1210 | } 1211 | 1212 | // ExecuteAPVFIOMediatedDeviceAdd adds a VFIO mediated AP device to a QEMU instance using the device_add command. 1213 | func (q *QMP) ExecuteAPVFIOMediatedDeviceAdd(ctx context.Context, sysfsdev string) error { 1214 | args := map[string]interface{}{ 1215 | "driver": VfioAP, 1216 | "sysfsdev": sysfsdev, 1217 | } 1218 | return q.executeCommand(ctx, "device_add", args, nil) 1219 | } 1220 | 1221 | // isSocketIDSupported returns if the cpu driver supports the socket id option 1222 | func isSocketIDSupported(driver string) bool { 1223 | if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" { 1224 | return false 1225 | } 1226 | return true 1227 | } 1228 | 1229 | // isThreadIDSupported returns if the cpu driver supports the thread id option 1230 | func isThreadIDSupported(driver string) bool { 1231 | if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" { 1232 | return false 1233 | } 1234 | return true 1235 | } 1236 | 1237 | // isDieIDSupported returns if the cpu driver and the qemu version support the die id option 1238 | func (q *QMP) isDieIDSupported(driver string) bool { 1239 | return driver == "host-x86_64-cpu" 1240 | } 1241 | 1242 | // ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command. 1243 | // driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within 1244 | // node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the 1245 | // thread number within core the CPU belongs to. Note that socketID and threadID are not a requirement for 1246 | // architecures like ppc64le. 1247 | func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, dieID, coreID, threadID, romfile string) error { 1248 | args := map[string]interface{}{ 1249 | "driver": driver, 1250 | "id": cpuID, 1251 | "core-id": coreID, 1252 | } 1253 | 1254 | if socketID != "" && isSocketIDSupported(driver) { 1255 | args["socket-id"] = socketID 1256 | } 1257 | 1258 | if threadID != "" && isThreadIDSupported(driver) { 1259 | args["thread-id"] = threadID 1260 | } 1261 | 1262 | if q.isDieIDSupported(driver) { 1263 | if dieID != "" { 1264 | args["die-id"] = dieID 1265 | } 1266 | } 1267 | 1268 | return q.executeCommand(ctx, "device_add", args, nil) 1269 | } 1270 | 1271 | // ExecuteQueryHotpluggableCPUs returns a slice with the list of hotpluggable CPUs 1272 | func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableCPU, error) { 1273 | response, err := q.executeCommandWithResponse(ctx, "query-hotpluggable-cpus", nil, nil, nil) 1274 | if err != nil { 1275 | return nil, err 1276 | } 1277 | 1278 | // convert response to json 1279 | data, err := json.Marshal(response) 1280 | if err != nil { 1281 | return nil, fmt.Errorf("unable to extract CPU information: %v", err) 1282 | } 1283 | 1284 | var cpus []HotpluggableCPU 1285 | // convert json to []HotpluggableCPU 1286 | if err = json.Unmarshal(data, &cpus); err != nil { 1287 | return nil, fmt.Errorf("unable to convert json to hotpluggable CPU: %v", err) 1288 | } 1289 | 1290 | return cpus, nil 1291 | } 1292 | 1293 | // ExecSetMigrationCaps sets migration capabilities 1294 | func (q *QMP) ExecSetMigrationCaps(ctx context.Context, caps []map[string]interface{}) error { 1295 | args := map[string]interface{}{ 1296 | "capabilities": caps, 1297 | } 1298 | 1299 | return q.executeCommand(ctx, "migrate-set-capabilities", args, nil) 1300 | } 1301 | 1302 | // ExecSetMigrateArguments sets the command line used for migration 1303 | func (q *QMP) ExecSetMigrateArguments(ctx context.Context, url string) error { 1304 | args := map[string]interface{}{ 1305 | "uri": url, 1306 | } 1307 | 1308 | return q.executeCommand(ctx, "migrate", args, nil) 1309 | } 1310 | 1311 | // ExecQueryMemoryDevices returns a slice with the list of memory devices 1312 | func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, error) { 1313 | response, err := q.executeCommandWithResponse(ctx, "query-memory-devices", nil, nil, nil) 1314 | if err != nil { 1315 | return nil, err 1316 | } 1317 | 1318 | // convert response to json 1319 | data, err := json.Marshal(response) 1320 | if err != nil { 1321 | return nil, fmt.Errorf("unable to extract memory devices information: %v", err) 1322 | } 1323 | 1324 | var memoryDevices []MemoryDevices 1325 | // convert json to []MemoryDevices 1326 | if err = json.Unmarshal(data, &memoryDevices); err != nil { 1327 | return nil, fmt.Errorf("unable to convert json to memory devices: %v", err) 1328 | } 1329 | 1330 | return memoryDevices, nil 1331 | } 1332 | 1333 | // ExecQueryCpus returns a slice with the list of `CpuInfo` 1334 | // Since qemu 2.12, we have `query-cpus-fast` as a better choice in production 1335 | // we can still choose `ExecQueryCpus` for compatibility though not recommended. 1336 | func (q *QMP) ExecQueryCpus(ctx context.Context) ([]CPUInfo, error) { 1337 | response, err := q.executeCommandWithResponse(ctx, "query-cpus", nil, nil, nil) 1338 | if err != nil { 1339 | return nil, err 1340 | } 1341 | 1342 | // convert response to json 1343 | data, err := json.Marshal(response) 1344 | if err != nil { 1345 | return nil, fmt.Errorf("unable to extract memory devices information: %v", err) 1346 | } 1347 | 1348 | var cpuInfo []CPUInfo 1349 | // convert json to []CPUInfo 1350 | if err = json.Unmarshal(data, &cpuInfo); err != nil { 1351 | return nil, fmt.Errorf("unable to convert json to CPUInfo: %v", err) 1352 | } 1353 | 1354 | return cpuInfo, nil 1355 | } 1356 | 1357 | // ExecQueryCpusFast returns a slice with the list of `CpuInfoFast` 1358 | // This is introduced since 2.12, it does not incur a performance penalty and 1359 | // should be used in production instead of query-cpus. 1360 | func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { 1361 | response, err := q.executeCommandWithResponse(ctx, "query-cpus-fast", nil, nil, nil) 1362 | if err != nil { 1363 | return nil, err 1364 | } 1365 | 1366 | // convert response to json 1367 | data, err := json.Marshal(response) 1368 | if err != nil { 1369 | return nil, fmt.Errorf("unable to extract memory devices information: %v", err) 1370 | } 1371 | 1372 | var cpuInfoFast []CPUInfoFast 1373 | // convert json to []CPUInfoFast 1374 | if err = json.Unmarshal(data, &cpuInfoFast); err != nil { 1375 | return nil, fmt.Errorf("unable to convert json to CPUInfoFast: %v", err) 1376 | } 1377 | 1378 | return cpuInfoFast, nil 1379 | } 1380 | 1381 | // ExecMemdevAdd adds size of MiB memory device to the guest 1382 | func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, size int, share bool, driver, driverID, addr, bus string) error { 1383 | args := map[string]interface{}{ 1384 | "qom-type": qomtype, 1385 | "id": id, 1386 | "size": uint64(size) << 20, 1387 | } 1388 | if mempath != "" { 1389 | args["mem-path"] = mempath 1390 | } 1391 | if share { 1392 | args["share"] = true 1393 | } 1394 | err := q.executeCommand(ctx, "object-add", args, nil) 1395 | if err != nil { 1396 | return err 1397 | } 1398 | 1399 | defer func() { 1400 | if err != nil { 1401 | q.cfg.Logger.Errorf("Unable to add memory device %s: %v", id, err) 1402 | err = q.executeCommand(ctx, "object-del", map[string]interface{}{"id": id}, nil) 1403 | if err != nil { 1404 | q.cfg.Logger.Warningf("Unable to clean up memory object %s: %v", id, err) 1405 | } 1406 | } 1407 | }() 1408 | 1409 | args = map[string]interface{}{ 1410 | "driver": driver, 1411 | "id": driverID, 1412 | "memdev": id, 1413 | } 1414 | 1415 | if bus != "" { 1416 | args["bus"] = bus 1417 | } 1418 | if addr != "" { 1419 | args["addr"] = addr 1420 | } 1421 | 1422 | err = q.executeCommand(ctx, "device_add", args, nil) 1423 | 1424 | return err 1425 | } 1426 | 1427 | // ExecHotplugMemory adds size of MiB memory to the guest 1428 | func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int, share bool) error { 1429 | return q.ExecMemdevAdd(ctx, qomtype, id, mempath, size, share, "pc-dimm", "dimm"+id, "", "") 1430 | } 1431 | 1432 | // ExecuteNVDIMMDeviceAdd adds a block device to a QEMU instance using 1433 | // a NVDIMM driver with the device_add command. 1434 | // id is the id of the device to add. It must be a valid QMP identifier. 1435 | // mempath is the path of the device to add, e.g., /dev/rdb0. size is 1436 | // the data size of the device. pmem is to guarantee the persistence of QEMU writes 1437 | // to the vNVDIMM backend. 1438 | func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64, pmem *bool) error { 1439 | args := map[string]interface{}{ 1440 | "qom-type": "memory-backend-file", 1441 | "id": "nvdimmbackmem" + id, 1442 | "mem-path": mempath, 1443 | "size": size, 1444 | "share": true, 1445 | } 1446 | 1447 | if pmem != nil { 1448 | args["pmem"] = *pmem 1449 | } 1450 | 1451 | err := q.executeCommand(ctx, "object-add", args, nil) 1452 | if err != nil { 1453 | return err 1454 | } 1455 | 1456 | args = map[string]interface{}{ 1457 | "driver": "nvdimm", 1458 | "id": "nvdimm" + id, 1459 | "memdev": "nvdimmbackmem" + id, 1460 | } 1461 | if err = q.executeCommand(ctx, "device_add", args, nil); err != nil { 1462 | q.cfg.Logger.Errorf("Unable to hotplug NVDIMM device: %v", err) 1463 | err2 := q.executeCommand(ctx, "object-del", map[string]interface{}{"id": "nvdimmbackmem" + id}, nil) 1464 | if err2 != nil { 1465 | q.cfg.Logger.Warningf("Unable to clean up memory object: %v", err2) 1466 | } 1467 | } 1468 | 1469 | return err 1470 | } 1471 | 1472 | // ExecuteBalloon sets the size of the balloon, hence updates the memory 1473 | // allocated for the VM. 1474 | func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error { 1475 | args := map[string]interface{}{ 1476 | "value": bytes, 1477 | } 1478 | return q.executeCommand(ctx, "balloon", args, nil) 1479 | } 1480 | 1481 | // ExecutePCIVSockAdd adds a vhost-vsock-pci bus 1482 | // disableModern indicates if virtio version 1.0 should be replaced by the 1483 | // former version 0.9, as there is a KVM bug that occurs when using virtio 1484 | // 1.0 in nested environments. 1485 | func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus, romfile string, disableModern bool) error { 1486 | args := map[string]interface{}{ 1487 | "driver": VHostVSockPCI, 1488 | "id": id, 1489 | "guest-cid": guestCID, 1490 | "vhostfd": vhostfd, 1491 | "addr": addr, 1492 | "romfile": romfile, 1493 | } 1494 | 1495 | if bus != "" { 1496 | args["bus"] = bus 1497 | } 1498 | 1499 | if disableModern { 1500 | args["disable-modern"] = disableModern 1501 | } 1502 | 1503 | return q.executeCommand(ctx, "device_add", args, nil) 1504 | } 1505 | 1506 | // ExecuteGetFD sends a file descriptor via SCM rights and assigns it a name 1507 | func (q *QMP) ExecuteGetFD(ctx context.Context, fdname string, fd *os.File) error { 1508 | oob := syscall.UnixRights(int(fd.Fd())) 1509 | args := map[string]interface{}{ 1510 | "fdname": fdname, 1511 | } 1512 | 1513 | _, err := q.executeCommandWithResponse(ctx, "getfd", args, oob, nil) 1514 | return err 1515 | } 1516 | 1517 | // ExecuteCharDevUnixSocketAdd adds a character device using as backend a unix socket, 1518 | // id is an identifier for the device, path specifies the local path of the unix socket, 1519 | // wait is to block waiting for a client to connect, server specifies that the socket is a listening socket. 1520 | func (q *QMP) ExecuteCharDevUnixSocketAdd(ctx context.Context, id, path string, wait, server bool) error { 1521 | data := map[string]interface{}{ 1522 | "server": server, 1523 | "addr": map[string]interface{}{ 1524 | "type": "unix", 1525 | "data": map[string]interface{}{ 1526 | "path": path, 1527 | }, 1528 | }, 1529 | } 1530 | 1531 | // wait is only valid for server mode 1532 | if server { 1533 | data["wait"] = wait 1534 | } 1535 | 1536 | args := map[string]interface{}{ 1537 | "id": id, 1538 | "backend": map[string]interface{}{ 1539 | "type": "socket", 1540 | "data": data, 1541 | }, 1542 | } 1543 | return q.executeCommand(ctx, "chardev-add", args, nil) 1544 | } 1545 | 1546 | // ExecuteVirtSerialPortAdd adds a virtserialport. 1547 | // id is an identifier for the virtserialport, name is a name for the virtserialport and 1548 | // it will be visible in the VM, chardev is the character device id previously added. 1549 | func (q *QMP) ExecuteVirtSerialPortAdd(ctx context.Context, id, name, chardev string) error { 1550 | args := map[string]interface{}{ 1551 | "driver": VirtioSerialPort, 1552 | "id": id, 1553 | "name": name, 1554 | "chardev": chardev, 1555 | } 1556 | 1557 | return q.executeCommand(ctx, "device_add", args, nil) 1558 | } 1559 | 1560 | // ExecuteQueryMigration queries migration progress. 1561 | func (q *QMP) ExecuteQueryMigration(ctx context.Context) (MigrationStatus, error) { 1562 | response, err := q.executeCommandWithResponse(ctx, "query-migrate", nil, nil, nil) 1563 | if err != nil { 1564 | return MigrationStatus{}, err 1565 | } 1566 | 1567 | data, err := json.Marshal(response) 1568 | if err != nil { 1569 | return MigrationStatus{}, fmt.Errorf("unable to extract migrate status information: %v", err) 1570 | } 1571 | 1572 | var status MigrationStatus 1573 | if err = json.Unmarshal(data, &status); err != nil { 1574 | return MigrationStatus{}, fmt.Errorf("unable to convert migrate status information: %v", err) 1575 | } 1576 | 1577 | return status, nil 1578 | } 1579 | 1580 | // ExecuteMigrationIncoming start migration from incoming uri. 1581 | func (q *QMP) ExecuteMigrationIncoming(ctx context.Context, uri string) error { 1582 | args := map[string]interface{}{ 1583 | "uri": uri, 1584 | } 1585 | return q.executeCommand(ctx, "migrate-incoming", args, nil) 1586 | } 1587 | 1588 | // ExecQueryQmpSchema query all QMP wire ABI and returns a slice 1589 | func (q *QMP) ExecQueryQmpSchema(ctx context.Context) ([]SchemaInfo, error) { 1590 | response, err := q.executeCommandWithResponse(ctx, "query-qmp-schema", nil, nil, nil) 1591 | if err != nil { 1592 | return nil, err 1593 | } 1594 | 1595 | // convert response to json 1596 | data, err := json.Marshal(response) 1597 | if err != nil { 1598 | return nil, fmt.Errorf("unable to extract memory devices information: %v", err) 1599 | } 1600 | 1601 | var schemaInfo []SchemaInfo 1602 | if err = json.Unmarshal(data, &schemaInfo); err != nil { 1603 | return nil, fmt.Errorf("unable to convert json to schemaInfo: %v", err) 1604 | } 1605 | 1606 | return schemaInfo, nil 1607 | } 1608 | 1609 | // ExecuteQueryStatus queries guest status 1610 | func (q *QMP) ExecuteQueryStatus(ctx context.Context) (StatusInfo, error) { 1611 | response, err := q.executeCommandWithResponse(ctx, "query-status", nil, nil, nil) 1612 | if err != nil { 1613 | return StatusInfo{}, err 1614 | } 1615 | 1616 | data, err := json.Marshal(response) 1617 | if err != nil { 1618 | return StatusInfo{}, fmt.Errorf("unable to extract migrate status information: %v", err) 1619 | } 1620 | 1621 | var status StatusInfo 1622 | if err = json.Unmarshal(data, &status); err != nil { 1623 | return StatusInfo{}, fmt.Errorf("unable to convert migrate status information: %v", err) 1624 | } 1625 | 1626 | return status, nil 1627 | } 1628 | 1629 | // ExecQomSet qom-set path property value 1630 | func (q *QMP) ExecQomSet(ctx context.Context, path, property string, value uint64) error { 1631 | args := map[string]interface{}{ 1632 | "path": path, 1633 | "property": property, 1634 | "value": value, 1635 | } 1636 | 1637 | return q.executeCommand(ctx, "qom-set", args, nil) 1638 | } 1639 | 1640 | // ExecQomGet qom-get path property 1641 | func (q *QMP) ExecQomGet(ctx context.Context, path, property string) (interface{}, error) { 1642 | args := map[string]interface{}{ 1643 | "path": path, 1644 | "property": property, 1645 | } 1646 | 1647 | response, err := q.executeCommandWithResponse(ctx, "qom-get", args, nil, nil) 1648 | if err != nil { 1649 | return "", err 1650 | } 1651 | 1652 | return response, nil 1653 | } 1654 | 1655 | // ExecuteDumpGuestMemory dump guest memory to host 1656 | func (q *QMP) ExecuteDumpGuestMemory(ctx context.Context, protocol string, paging bool, format string) error { 1657 | args := map[string]interface{}{ 1658 | "protocol": protocol, 1659 | "paging": paging, 1660 | "format": format, 1661 | } 1662 | 1663 | return q.executeCommand(ctx, "dump-guest-memory", args, nil) 1664 | } 1665 | -------------------------------------------------------------------------------- /qemu/qmp_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright contributors to the Virtual Machine Manager for Go project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | */ 16 | 17 | package qemu 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "errors" 23 | "fmt" 24 | "log" 25 | "os" 26 | "reflect" 27 | "sync" 28 | "testing" 29 | "time" 30 | 31 | "context" 32 | ) 33 | 34 | const ( 35 | microStr = "50" 36 | minorStr = "9" 37 | majorStr = "2" 38 | micro = 50 39 | minor = 9 40 | major = 2 41 | cap1 = "one" 42 | cap2 = "two" 43 | qmpHello = `{ "QMP": { "version": { "qemu": { "micro": ` + microStr + `, "minor": ` + minorStr + `, "major": ` + majorStr + ` }, "package": ""}, "capabilities": ["` + cap1 + `","` + cap2 + `"]}}` + "\n" 44 | ) 45 | 46 | type qmpTestLogger struct{} 47 | 48 | func (l qmpTestLogger) V(level int32) bool { 49 | return true 50 | } 51 | 52 | func (l qmpTestLogger) Infof(format string, v ...interface{}) { 53 | log.Printf(format, v...) 54 | } 55 | 56 | func (l qmpTestLogger) Warningf(format string, v ...interface{}) { 57 | l.Infof(format, v...) 58 | } 59 | 60 | func (l qmpTestLogger) Errorf(format string, v ...interface{}) { 61 | l.Infof(format, v...) 62 | } 63 | 64 | type qmpTestCommand struct { 65 | name string 66 | args map[string]interface{} 67 | } 68 | 69 | type qmpTestEvent struct { 70 | name string 71 | data map[string]interface{} 72 | timestamp map[string]interface{} 73 | after time.Duration 74 | } 75 | 76 | type qmpTestResult struct { 77 | result string 78 | data interface{} 79 | } 80 | 81 | type qmpTestCommandBuffer struct { 82 | newDataCh chan []byte 83 | t *testing.T 84 | buf *bytes.Buffer 85 | cmds []qmpTestCommand 86 | events []qmpTestEvent 87 | results []qmpTestResult 88 | currentCmd int 89 | forceFail chan struct{} 90 | } 91 | 92 | func newQMPTestCommandBuffer(t *testing.T) *qmpTestCommandBuffer { 93 | b := &qmpTestCommandBuffer{ 94 | newDataCh: make(chan []byte, 1), 95 | t: t, 96 | buf: bytes.NewBuffer([]byte{}), 97 | forceFail: make(chan struct{}), 98 | } 99 | b.cmds = make([]qmpTestCommand, 0, 8) 100 | b.events = make([]qmpTestEvent, 0, 8) 101 | b.results = make([]qmpTestResult, 0, 8) 102 | b.newDataCh <- []byte(qmpHello) 103 | return b 104 | } 105 | 106 | func newQMPTestCommandBufferNoGreeting(t *testing.T) *qmpTestCommandBuffer { 107 | b := &qmpTestCommandBuffer{ 108 | newDataCh: make(chan []byte, 1), 109 | t: t, 110 | buf: bytes.NewBuffer([]byte{}), 111 | forceFail: make(chan struct{}), 112 | } 113 | b.cmds = make([]qmpTestCommand, 0, 8) 114 | b.events = make([]qmpTestEvent, 0, 8) 115 | b.results = make([]qmpTestResult, 0, 8) 116 | return b 117 | } 118 | 119 | func (b *qmpTestCommandBuffer) startEventLoop(wg *sync.WaitGroup) { 120 | wg.Add(1) 121 | go func() { 122 | for _, ev := range b.events { 123 | time.Sleep(ev.after) 124 | eventMap := map[string]interface{}{ 125 | "event": ev.name, 126 | } 127 | 128 | if ev.data != nil { 129 | eventMap["data"] = ev.data 130 | } 131 | 132 | if ev.timestamp != nil { 133 | eventMap["timestamp"] = ev.timestamp 134 | } 135 | 136 | encodedEvent, err := json.Marshal(&eventMap) 137 | if err != nil { 138 | b.t.Errorf("Unable to encode event: %v", err) 139 | } 140 | encodedEvent = append(encodedEvent, '\n') 141 | b.newDataCh <- encodedEvent 142 | } 143 | wg.Done() 144 | }() 145 | } 146 | 147 | func (b *qmpTestCommandBuffer) AddCommand(name string, args map[string]interface{}, 148 | result string, data interface{}) { 149 | b.cmds = append(b.cmds, qmpTestCommand{name, args}) 150 | b.results = append(b.results, qmpTestResult{result, data}) 151 | } 152 | 153 | func (b *qmpTestCommandBuffer) AddEvent(name string, after time.Duration, 154 | data map[string]interface{}, timestamp map[string]interface{}) { 155 | b.events = append(b.events, qmpTestEvent{ 156 | name: name, 157 | data: data, 158 | timestamp: timestamp, 159 | after: after, 160 | }) 161 | } 162 | 163 | func (b *qmpTestCommandBuffer) Close() error { 164 | close(b.newDataCh) 165 | return nil 166 | } 167 | 168 | func (b *qmpTestCommandBuffer) Read(p []byte) (n int, err error) { 169 | if b.buf.Len() == 0 { 170 | ok := false 171 | var data []byte 172 | select { 173 | case <-b.forceFail: 174 | return 0, errors.New("Connection shutdown") 175 | case data, ok = <-b.newDataCh: 176 | select { 177 | case <-b.forceFail: 178 | return 0, errors.New("Connection shutdown") 179 | default: 180 | } 181 | } 182 | if !ok { 183 | return 0, nil 184 | } 185 | _, err := b.buf.Write(data) 186 | if err != nil { 187 | if err != nil { 188 | b.t.Errorf("Unable to buffer result: %v", err) 189 | } 190 | } 191 | } 192 | return b.buf.Read(p) 193 | } 194 | 195 | func (b *qmpTestCommandBuffer) Write(p []byte) (int, error) { 196 | var cmdJSON map[string]interface{} 197 | currentCmd := b.currentCmd 198 | b.currentCmd++ 199 | if currentCmd >= len(b.cmds) { 200 | b.t.Fatalf("Unexpected command") 201 | } 202 | err := json.Unmarshal(p, &cmdJSON) 203 | if err != nil { 204 | b.t.Fatalf("Unexpected command") 205 | } 206 | cmdName := cmdJSON["execute"] 207 | gotCmdName := cmdName.(string) 208 | result := b.results[currentCmd].result 209 | if gotCmdName != b.cmds[currentCmd].name { 210 | b.t.Errorf("Unexpected command. Expected %s found %s", 211 | b.cmds[currentCmd].name, gotCmdName) 212 | result = "error" 213 | } 214 | resultMap := make(map[string]interface{}) 215 | resultMap[result] = b.results[currentCmd].data 216 | encodedRes, err := json.Marshal(&resultMap) 217 | if err != nil { 218 | b.t.Errorf("Unable to encode result: %v", err) 219 | } 220 | encodedRes = append(encodedRes, '\n') 221 | b.newDataCh <- encodedRes 222 | return len(p), nil 223 | } 224 | 225 | func checkVersion(t *testing.T, connectedCh <-chan *QMPVersion) *QMPVersion { 226 | var version *QMPVersion 227 | select { 228 | case <-time.After(time.Second): 229 | t.Fatal("Timed out waiting for qmp to connect") 230 | case version = <-connectedCh: 231 | } 232 | 233 | if version == nil { 234 | t.Fatal("Invalid version information received") 235 | } 236 | if version.Micro != micro || version.Minor != minor || 237 | version.Major != major { 238 | t.Fatal("Invalid version number") 239 | } 240 | 241 | if len(version.Capabilities) != 2 { 242 | if version.Capabilities[0] != cap1 || version.Capabilities[1] != cap2 { 243 | t.Fatal("Invalid capabilities") 244 | } 245 | } 246 | 247 | return version 248 | } 249 | 250 | // Checks that a QMP Loop can be started and shutdown. 251 | // 252 | // We start a QMPLoop and shut it down. 253 | // 254 | // Loop should start up and shutdown correctly. The version information 255 | // returned from startQMPLoop should be correct. 256 | func TestQMPStartStopLoop(t *testing.T) { 257 | connectedCh := make(chan *QMPVersion) 258 | disconnectedCh := make(chan struct{}) 259 | buf := newQMPTestCommandBuffer(t) 260 | cfg := QMPConfig{Logger: qmpTestLogger{}} 261 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 262 | checkVersion(t, connectedCh) 263 | q.Shutdown() 264 | <-disconnectedCh 265 | } 266 | 267 | // Checks that a call to QMPStart with an invalid path exits gracefully. 268 | // 269 | // We call QMPStart with an invalid path. 270 | // 271 | // An error should be returned and the disconnected channel should be closed. 272 | func TestQMPStartBadPath(t *testing.T) { 273 | cfg := QMPConfig{Logger: qmpTestLogger{}} 274 | disconnectedCh := make(chan struct{}) 275 | q, _, err := QMPStart(context.Background(), "", cfg, disconnectedCh) 276 | if err == nil { 277 | t.Errorf("Expected error") 278 | q.Shutdown() 279 | } 280 | <-disconnectedCh 281 | } 282 | 283 | // Checks that the qmp_capabilities command is correctly sent. 284 | // 285 | // We start a QMPLoop, send the qmp_capabilities command and stop the 286 | // loop. 287 | // 288 | // The qmp_capabilities should be correctly sent and the QMP loop 289 | // should exit gracefully. 290 | func TestQMPCapabilities(t *testing.T) { 291 | connectedCh := make(chan *QMPVersion) 292 | disconnectedCh := make(chan struct{}) 293 | buf := newQMPTestCommandBuffer(t) 294 | buf.AddCommand("qmp_capabilities", nil, "return", nil) 295 | cfg := QMPConfig{Logger: qmpTestLogger{}} 296 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 297 | checkVersion(t, connectedCh) 298 | err := q.ExecuteQMPCapabilities(context.Background()) 299 | if err != nil { 300 | t.Fatalf("Unexpected error %v", err) 301 | } 302 | q.Shutdown() 303 | <-disconnectedCh 304 | } 305 | 306 | // Checks that an error returned by a QMP command is correctly handled. 307 | // 308 | // We start a QMPLoop, send the qmp_capabilities command and stop the 309 | // loop. 310 | // 311 | // The qmp_capabilities command fails and yet we should exit gracefully. 312 | func TestQMPBadCapabilities(t *testing.T) { 313 | connectedCh := make(chan *QMPVersion) 314 | disconnectedCh := make(chan struct{}) 315 | buf := newQMPTestCommandBuffer(t) 316 | buf.AddCommand("qmp_capabilities", nil, "error", nil) 317 | cfg := QMPConfig{Logger: qmpTestLogger{}} 318 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 319 | checkVersion(t, connectedCh) 320 | err := q.ExecuteQMPCapabilities(context.Background()) 321 | if err == nil { 322 | t.Fatalf("Expected error") 323 | } 324 | q.Shutdown() 325 | <-disconnectedCh 326 | } 327 | 328 | // Checks that the stop command is correctly sent. 329 | // 330 | // We start a QMPLoop, send the stop command and stop the 331 | // loop. 332 | // 333 | // The stop command should be correctly sent and the QMP loop 334 | // should exit gracefully. 335 | func TestQMPStop(t *testing.T) { 336 | connectedCh := make(chan *QMPVersion) 337 | disconnectedCh := make(chan struct{}) 338 | buf := newQMPTestCommandBuffer(t) 339 | buf.AddCommand("stop", nil, "return", nil) 340 | cfg := QMPConfig{Logger: qmpTestLogger{}} 341 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 342 | checkVersion(t, connectedCh) 343 | err := q.ExecuteStop(context.Background()) 344 | if err != nil { 345 | t.Fatalf("Unexpected error %v", err) 346 | } 347 | q.Shutdown() 348 | <-disconnectedCh 349 | } 350 | 351 | // Checks that the cont command is correctly sent. 352 | // 353 | // We start a QMPLoop, send the cont command and stop the 354 | // loop. 355 | // 356 | // The cont command should be correctly sent and the QMP loop 357 | // should exit gracefully. 358 | func TestQMPCont(t *testing.T) { 359 | connectedCh := make(chan *QMPVersion) 360 | disconnectedCh := make(chan struct{}) 361 | buf := newQMPTestCommandBuffer(t) 362 | buf.AddCommand("cont", nil, "return", nil) 363 | cfg := QMPConfig{Logger: qmpTestLogger{}} 364 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 365 | checkVersion(t, connectedCh) 366 | err := q.ExecuteCont(context.Background()) 367 | if err != nil { 368 | t.Fatalf("Unexpected error %v", err) 369 | } 370 | q.Shutdown() 371 | <-disconnectedCh 372 | } 373 | 374 | // Checks that the quit command is correctly sent. 375 | // 376 | // We start a QMPLoop, send the quit command and wait for the loop to exit. 377 | // 378 | // The quit command should be correctly sent and the QMP loop should exit 379 | // gracefully without the test calling q.Shutdown(). 380 | func TestQMPQuit(t *testing.T) { 381 | connectedCh := make(chan *QMPVersion) 382 | disconnectedCh := make(chan struct{}) 383 | buf := newQMPTestCommandBuffer(t) 384 | buf.AddCommand("quit", nil, "return", nil) 385 | cfg := QMPConfig{Logger: qmpTestLogger{}} 386 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 387 | checkVersion(t, connectedCh) 388 | err := q.ExecuteQuit(context.Background()) 389 | if err != nil { 390 | t.Fatalf("Unexpected error %v", err) 391 | } 392 | close(buf.forceFail) 393 | <-disconnectedCh 394 | } 395 | 396 | // Checks that the blockdev-add command is correctly sent. 397 | // 398 | // We start a QMPLoop, send the blockdev-add command and stop the loop. 399 | // 400 | // The blockdev-add command should be correctly sent and the QMP loop should 401 | // exit gracefully. 402 | func TestQMPBlockdevAdd(t *testing.T) { 403 | connectedCh := make(chan *QMPVersion) 404 | disconnectedCh := make(chan struct{}) 405 | buf := newQMPTestCommandBuffer(t) 406 | buf.AddCommand("blockdev-add", nil, "return", nil) 407 | cfg := QMPConfig{Logger: qmpTestLogger{}} 408 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 409 | q.version = checkVersion(t, connectedCh) 410 | err := q.ExecuteBlockdevAdd(context.Background(), "/dev/rbd0", 411 | fmt.Sprintf("drive_%s", volumeUUID), false) 412 | if err != nil { 413 | t.Fatalf("Unexpected error %v", err) 414 | } 415 | q.Shutdown() 416 | <-disconnectedCh 417 | } 418 | 419 | // Checks that the blockdev-add with cache options command is correctly sent. 420 | // 421 | // We start a QMPLoop, send the blockdev-add with cache options 422 | // command and stop the loop. 423 | // 424 | // The blockdev-add with cache options command should be correctly sent and 425 | // the QMP loop should exit gracefully. 426 | func TestQMPBlockdevAddWithCache(t *testing.T) { 427 | connectedCh := make(chan *QMPVersion) 428 | disconnectedCh := make(chan struct{}) 429 | buf := newQMPTestCommandBuffer(t) 430 | buf.AddCommand("blockdev-add", nil, "return", nil) 431 | cfg := QMPConfig{Logger: qmpTestLogger{}} 432 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 433 | q.version = checkVersion(t, connectedCh) 434 | err := q.ExecuteBlockdevAddWithCache(context.Background(), "/dev/rbd0", 435 | fmt.Sprintf("drive_%s", volumeUUID), true, true, false) 436 | if err != nil { 437 | t.Fatalf("Unexpected error %v", err) 438 | } 439 | q.Shutdown() 440 | <-disconnectedCh 441 | } 442 | 443 | // Checks that the netdev_add command is correctly sent. 444 | // 445 | // We start a QMPLoop, send the netdev_add command and stop the loop. 446 | // 447 | // The netdev_add command should be correctly sent and the QMP loop should 448 | // exit gracefully. 449 | func TestQMPNetdevAdd(t *testing.T) { 450 | connectedCh := make(chan *QMPVersion) 451 | disconnectedCh := make(chan struct{}) 452 | buf := newQMPTestCommandBuffer(t) 453 | buf.AddCommand("netdev_add", nil, "return", nil) 454 | cfg := QMPConfig{Logger: qmpTestLogger{}} 455 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 456 | q.version = checkVersion(t, connectedCh) 457 | err := q.ExecuteNetdevAdd(context.Background(), "tap", "br0", "tap0", "no", "no", 8) 458 | if err != nil { 459 | t.Fatalf("Unexpected error %v", err) 460 | } 461 | q.Shutdown() 462 | <-disconnectedCh 463 | } 464 | 465 | // Checks that the netdev_add command is correctly sent. 466 | // 467 | // We start a QMPLoop, send the netdev_add command and stop the loop. 468 | // 469 | // The netdev_add command should be correctly sent and the QMP loop should 470 | // exit gracefully. 471 | func TestQMPNetdevChardevAdd(t *testing.T) { 472 | connectedCh := make(chan *QMPVersion) 473 | disconnectedCh := make(chan struct{}) 474 | buf := newQMPTestCommandBuffer(t) 475 | buf.AddCommand("netdev_add", nil, "return", nil) 476 | cfg := QMPConfig{Logger: qmpTestLogger{}} 477 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 478 | q.version = checkVersion(t, connectedCh) 479 | err := q.ExecuteNetdevChardevAdd(context.Background(), "tap", "br0", "chr0", 8) 480 | if err != nil { 481 | t.Fatalf("Unexpected error %v", err) 482 | } 483 | q.Shutdown() 484 | <-disconnectedCh 485 | } 486 | 487 | // Checks that the netdev_add command with fds is correctly sent. 488 | // 489 | // We start a QMPLoop, send the netdev_add command with fds and stop the loop. 490 | // 491 | // The netdev_add command with fds should be correctly sent and the QMP loop should 492 | // exit gracefully. 493 | func TestQMPNetdevAddByFds(t *testing.T) { 494 | connectedCh := make(chan *QMPVersion) 495 | disconnectedCh := make(chan struct{}) 496 | buf := newQMPTestCommandBuffer(t) 497 | buf.AddCommand("netdev_add", nil, "return", nil) 498 | buf.AddCommand("netdev_add", nil, "return", nil) 499 | cfg := QMPConfig{Logger: qmpTestLogger{}} 500 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 501 | q.version = checkVersion(t, connectedCh) 502 | err := q.ExecuteNetdevAddByFds(context.Background(), "tap", "br0", nil, []string{}) 503 | if err != nil { 504 | t.Fatalf("Unexpected error %v", err) 505 | } 506 | err = q.ExecuteNetdevAddByFds(context.Background(), "tap", "br1", nil, []string{"3"}) 507 | if err != nil { 508 | t.Fatalf("Unexpected error %v", err) 509 | } 510 | q.Shutdown() 511 | <-disconnectedCh 512 | } 513 | 514 | // Checks that the netdev_del command is correctly sent. 515 | // 516 | // We start a QMPLoop, send the netdev_del command and stop the loop. 517 | // 518 | // The netdev_del command should be correctly sent and the QMP loop should 519 | // exit gracefully. 520 | func TestQMPNetdevDel(t *testing.T) { 521 | connectedCh := make(chan *QMPVersion) 522 | disconnectedCh := make(chan struct{}) 523 | buf := newQMPTestCommandBuffer(t) 524 | buf.AddCommand("netdev_del", nil, "return", nil) 525 | cfg := QMPConfig{Logger: qmpTestLogger{}} 526 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 527 | q.version = checkVersion(t, connectedCh) 528 | err := q.ExecuteNetdevDel(context.Background(), "br0") 529 | if err != nil { 530 | t.Fatalf("Unexpected error %v", err) 531 | } 532 | q.Shutdown() 533 | <-disconnectedCh 534 | } 535 | 536 | func TestQMPNetPCIDeviceAdd(t *testing.T) { 537 | connectedCh := make(chan *QMPVersion) 538 | disconnectedCh := make(chan struct{}) 539 | buf := newQMPTestCommandBuffer(t) 540 | buf.AddCommand("device_add", nil, "return", nil) 541 | cfg := QMPConfig{Logger: qmpTestLogger{}} 542 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 543 | checkVersion(t, connectedCh) 544 | err := q.ExecuteNetPCIDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", "0x7", "", "", 8, false) 545 | if err != nil { 546 | t.Fatalf("Unexpected error %v", err) 547 | } 548 | q.Shutdown() 549 | <-disconnectedCh 550 | } 551 | 552 | func TestQMPNetCCWDeviceAdd(t *testing.T) { 553 | connectedCh := make(chan *QMPVersion) 554 | disconnectedCh := make(chan struct{}) 555 | buf := newQMPTestCommandBuffer(t) 556 | buf.AddCommand("device_add", nil, "return", nil) 557 | cfg := QMPConfig{Logger: qmpTestLogger{}} 558 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 559 | checkVersion(t, connectedCh) 560 | err := q.ExecuteNetCCWDeviceAdd(context.Background(), "br0", "virtio-0", "02:42:ac:11:00:02", DevNo, 8) 561 | if err != nil { 562 | t.Fatalf("Unexpected error %v", err) 563 | } 564 | q.Shutdown() 565 | <-disconnectedCh 566 | } 567 | 568 | // Checks that the device_add command is correctly sent. 569 | // 570 | // We start a QMPLoop, send the device_add command and stop the loop. 571 | // 572 | // The device_add command should be correctly sent and the QMP loop should 573 | // exit gracefully. 574 | func TestQMPDeviceAdd(t *testing.T) { 575 | connectedCh := make(chan *QMPVersion) 576 | disconnectedCh := make(chan struct{}) 577 | buf := newQMPTestCommandBuffer(t) 578 | buf.AddCommand("device_add", nil, "return", nil) 579 | cfg := QMPConfig{Logger: qmpTestLogger{}} 580 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 581 | q.version = checkVersion(t, connectedCh) 582 | blockdevID := fmt.Sprintf("drive_%s", volumeUUID) 583 | devID := fmt.Sprintf("device_%s", volumeUUID) 584 | err := q.ExecuteDeviceAdd(context.Background(), blockdevID, devID, 585 | "virtio-blk-pci", "", "", true, false) 586 | if err != nil { 587 | t.Fatalf("Unexpected error %v", err) 588 | } 589 | q.Shutdown() 590 | <-disconnectedCh 591 | } 592 | 593 | // Checks that the device_add command for scsi is correctly sent. 594 | // 595 | // We start a QMPLoop, send the device_add command and stop the loop. 596 | // 597 | // The device_add command should be correctly sent and the QMP loop should 598 | // exit gracefully. 599 | func TestQMPSCSIDeviceAdd(t *testing.T) { 600 | connectedCh := make(chan *QMPVersion) 601 | disconnectedCh := make(chan struct{}) 602 | buf := newQMPTestCommandBuffer(t) 603 | buf.AddCommand("device_add", nil, "return", nil) 604 | cfg := QMPConfig{Logger: qmpTestLogger{}} 605 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 606 | q.version = checkVersion(t, connectedCh) 607 | blockdevID := fmt.Sprintf("drive_%s", volumeUUID) 608 | devID := fmt.Sprintf("device_%s", volumeUUID) 609 | err := q.ExecuteSCSIDeviceAdd(context.Background(), blockdevID, devID, 610 | "scsi-hd", "scsi0.0", "", 1, 2, true, false) 611 | if err != nil { 612 | t.Fatalf("Unexpected error %v", err) 613 | } 614 | q.Shutdown() 615 | <-disconnectedCh 616 | } 617 | 618 | // Checks that the blockdev-del command is correctly sent. 619 | // 620 | // We start a QMPLoop, send the blockdev-del command and stop the loop. 621 | // 622 | // The blockdev-del command should be correctly sent and the QMP loop should 623 | // exit gracefully. 624 | func TestQMPBlockdevDel(t *testing.T) { 625 | connectedCh := make(chan *QMPVersion) 626 | disconnectedCh := make(chan struct{}) 627 | buf := newQMPTestCommandBuffer(t) 628 | buf.AddCommand("blockdev-del", nil, "return", nil) 629 | cfg := QMPConfig{Logger: qmpTestLogger{}} 630 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 631 | q.version = checkVersion(t, connectedCh) 632 | err := q.ExecuteBlockdevDel(context.Background(), 633 | fmt.Sprintf("drive_%s", volumeUUID)) 634 | if err != nil { 635 | t.Fatalf("Unexpected error %v", err) 636 | } 637 | q.Shutdown() 638 | <-disconnectedCh 639 | } 640 | 641 | // Checks that the chardev-remove command is correctly sent. 642 | // 643 | // We start a QMPLoop, send the chardev-remove command and stop the loop. 644 | // 645 | // The chardev-remove command should be correctly sent and the QMP loop should 646 | // exit gracefully. 647 | func TestQMPChardevDel(t *testing.T) { 648 | connectedCh := make(chan *QMPVersion) 649 | disconnectedCh := make(chan struct{}) 650 | buf := newQMPTestCommandBuffer(t) 651 | buf.AddCommand("chardev-remove", nil, "return", nil) 652 | cfg := QMPConfig{Logger: qmpTestLogger{}} 653 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 654 | q.version = checkVersion(t, connectedCh) 655 | err := q.ExecuteChardevDel(context.Background(), "chardev-0") 656 | if err != nil { 657 | t.Fatalf("Unexpected error %v", err) 658 | } 659 | q.Shutdown() 660 | <-disconnectedCh 661 | } 662 | 663 | // Checks that the device_del command is correctly sent. 664 | // 665 | // We start a QMPLoop, send the device_del command and wait for it to complete. 666 | // This command generates some events so we start a separate go routine to check 667 | // that they are received. 668 | // 669 | // The device_del command should be correctly sent and the QMP loop should 670 | // exit gracefully. We should also receive two events on the eventCh. 671 | func TestQMPDeviceDel(t *testing.T) { 672 | const ( 673 | seconds = int64(1352167040730) 674 | microsecondsEv1 = 123456 675 | microsecondsEv2 = 123556 676 | device = "device_" + volumeUUID 677 | path = "/dev/rbd0" 678 | ) 679 | 680 | var wg sync.WaitGroup 681 | connectedCh := make(chan *QMPVersion) 682 | disconnectedCh := make(chan struct{}) 683 | buf := newQMPTestCommandBuffer(t) 684 | buf.AddCommand("device_del", nil, "return", nil) 685 | buf.AddEvent("DEVICE_DELETED", time.Millisecond*200, 686 | map[string]interface{}{ 687 | "path": path, 688 | }, 689 | map[string]interface{}{ 690 | "seconds": seconds, 691 | "microseconds": microsecondsEv1, 692 | }) 693 | buf.AddEvent("DEVICE_DELETED", time.Millisecond*200, 694 | map[string]interface{}{ 695 | "device": device, 696 | "path": path, 697 | }, 698 | map[string]interface{}{ 699 | "seconds": seconds, 700 | "microseconds": microsecondsEv2, 701 | }) 702 | eventCh := make(chan QMPEvent) 703 | cfg := QMPConfig{EventCh: eventCh, Logger: qmpTestLogger{}} 704 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 705 | wg.Add(1) 706 | go func() { 707 | for i := 0; i < 2; i++ { 708 | select { 709 | case <-eventCh: 710 | case <-time.After(time.Second): 711 | t.Error("Timedout waiting for event") 712 | } 713 | } 714 | wg.Done() 715 | }() 716 | checkVersion(t, connectedCh) 717 | buf.startEventLoop(&wg) 718 | err := q.ExecuteDeviceDel(context.Background(), 719 | fmt.Sprintf("device_%s", volumeUUID)) 720 | if err != nil { 721 | t.Fatalf("Unexpected error %v", err) 722 | } 723 | q.Shutdown() 724 | <-disconnectedCh 725 | wg.Wait() 726 | } 727 | 728 | // Checks that contexts can be used to timeout a command. 729 | // 730 | // We start a QMPLoop and send the device_del command with a context that times 731 | // out after 1 second. We don't however arrangefor any DEVICE_DELETED events 732 | // to be sent so the device_del command should not complete normally. We then 733 | // shutdown the QMP loop. 734 | // 735 | // The device_del command should timeout after 1 second and the QMP loop 736 | // should exit gracefully. 737 | func TestQMPDeviceDelTimeout(t *testing.T) { 738 | var wg sync.WaitGroup 739 | connectedCh := make(chan *QMPVersion) 740 | disconnectedCh := make(chan struct{}) 741 | buf := newQMPTestCommandBuffer(t) 742 | buf.AddCommand("device_del", nil, "return", nil) 743 | cfg := QMPConfig{Logger: qmpTestLogger{}} 744 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 745 | checkVersion(t, connectedCh) 746 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 747 | err := q.ExecuteDeviceDel(ctx, 748 | fmt.Sprintf("device_%s", volumeUUID)) 749 | cancel() 750 | if err != context.DeadlineExceeded { 751 | t.Fatalf("Timeout expected found %v", err) 752 | } 753 | q.Shutdown() 754 | <-disconnectedCh 755 | wg.Wait() 756 | } 757 | 758 | // Checks that contexts can be used to cancel a command. 759 | // 760 | // We start a QMPLoop and send two qmp_capabilities commands, cancelling 761 | // the first. The second is allowed to proceed normally. 762 | // 763 | // The first call to ExecuteQMPCapabilities should fail with 764 | // context.Canceled. The second should succeed. 765 | func TestQMPCancel(t *testing.T) { 766 | connectedCh := make(chan *QMPVersion) 767 | disconnectedCh := make(chan struct{}) 768 | buf := newQMPTestCommandBuffer(t) 769 | buf.AddCommand("qmp_capabilities", nil, "return", nil) 770 | buf.AddCommand("qmp_capabilities", nil, "return", nil) 771 | cfg := QMPConfig{Logger: qmpTestLogger{}} 772 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 773 | checkVersion(t, connectedCh) 774 | ctx, cancel := context.WithCancel(context.Background()) 775 | cancel() 776 | err := q.ExecuteQMPCapabilities(ctx) 777 | if err != context.Canceled { 778 | t.Fatalf("Unexpected error %v", err) 779 | } 780 | err = q.ExecuteQMPCapabilities(context.Background()) 781 | if err != nil { 782 | t.Fatalf("Unexpected error %v", err) 783 | } 784 | q.Shutdown() 785 | <-disconnectedCh 786 | } 787 | 788 | // Checks that the system_powerdown command is correctly sent. 789 | // 790 | // We start a QMPLoop, send the system_powerdown command and stop the loop. 791 | // 792 | // The system_powerdown command should be correctly sent and should return 793 | // as we've provisioned a SHUTDOWN event. The QMP loop should exit gracefully. 794 | func TestQMPSystemPowerdown(t *testing.T) { 795 | const ( 796 | seconds = int64(1352167040730) 797 | microsecondsEv1 = 123456 798 | ) 799 | 800 | var wg sync.WaitGroup 801 | connectedCh := make(chan *QMPVersion) 802 | disconnectedCh := make(chan struct{}) 803 | buf := newQMPTestCommandBuffer(t) 804 | buf.AddCommand("system_powerdown", nil, "return", nil) 805 | buf.AddEvent("POWERDOWN", time.Millisecond*100, 806 | nil, 807 | map[string]interface{}{ 808 | "seconds": seconds, 809 | "microseconds": microsecondsEv1, 810 | }) 811 | cfg := QMPConfig{Logger: qmpTestLogger{}} 812 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 813 | checkVersion(t, connectedCh) 814 | buf.startEventLoop(&wg) 815 | err := q.ExecuteSystemPowerdown(context.Background()) 816 | if err != nil { 817 | t.Fatalf("Unexpected error %v", err) 818 | } 819 | q.Shutdown() 820 | <-disconnectedCh 821 | wg.Wait() 822 | } 823 | 824 | // Checks that event commands can be cancelled. 825 | // 826 | // We start a QMPLoop, send the system_powerdown command. This command 827 | // will time out after 1 second as the SHUTDOWN event never arrives. 828 | // We then send a quit command to terminate the session. 829 | // 830 | // The system_powerdown command should be correctly sent but should block 831 | // waiting for the SHUTDOWN event and should be successfully cancelled. 832 | // The quit command should be successfully received and the QMP loop should 833 | // exit gracefully. 834 | func TestQMPEventedCommandCancel(t *testing.T) { 835 | var wg sync.WaitGroup 836 | connectedCh := make(chan *QMPVersion) 837 | disconnectedCh := make(chan struct{}) 838 | buf := newQMPTestCommandBuffer(t) 839 | buf.AddCommand("system_powerdown", nil, "return", nil) 840 | buf.AddCommand("quit", nil, "return", nil) 841 | cfg := QMPConfig{Logger: qmpTestLogger{}} 842 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 843 | checkVersion(t, connectedCh) 844 | buf.startEventLoop(&wg) 845 | ctx, cancelFN := context.WithTimeout(context.Background(), time.Second) 846 | err := q.ExecuteSystemPowerdown(ctx) 847 | cancelFN() 848 | if err == nil { 849 | t.Fatalf("Expected SystemPowerdown to fail") 850 | } 851 | err = q.ExecuteQuit(context.Background()) 852 | if err != nil { 853 | t.Fatalf("Unexpected error %v", err) 854 | } 855 | q.Shutdown() 856 | <-disconnectedCh 857 | wg.Wait() 858 | } 859 | 860 | // Checks that queued commands execute after an evented command is cancelled. 861 | // 862 | // This test is similar to the previous test with the exception that it 863 | // tries to ensure that a second command is placed on the QMP structure's 864 | // command queue before the evented command is cancelled. This allows us 865 | // to test a slightly different use case. We start a QMPLoop, send the 866 | // system_powerdown command. We do this by sending the command directly 867 | // down the QMP.cmdCh rather than calling a higher level function as this 868 | // allows us to ensure that we have another command queued before we 869 | // timeout the first command. We then send a qmp_capabilities command and 870 | // then we shutdown. 871 | // 872 | // The system_powerdown command should be correctly sent but should block 873 | // waiting for the SHUTDOWN event and should be successfully cancelled. 874 | // The query_capabilities command should be successfully received and the 875 | // QMP loop should exit gracefully. 876 | func TestQMPEventedCommandCancelConcurrent(t *testing.T) { 877 | var wg sync.WaitGroup 878 | connectedCh := make(chan *QMPVersion) 879 | disconnectedCh := make(chan struct{}) 880 | buf := newQMPTestCommandBuffer(t) 881 | 882 | buf.AddCommand("system_powerdown", nil, "error", nil) 883 | buf.AddCommand("qmp_capabilities", nil, "return", nil) 884 | 885 | cfg := QMPConfig{Logger: qmpTestLogger{}} 886 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 887 | checkVersion(t, connectedCh) 888 | buf.startEventLoop(&wg) 889 | 890 | resCh := make(chan qmpResult) 891 | ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) 892 | q.cmdCh <- qmpCommand{ 893 | ctx: ctx, 894 | res: resCh, 895 | name: "system_powerdown", 896 | filter: &qmpEventFilter{ 897 | eventName: "SHUTDOWN", 898 | }, 899 | } 900 | 901 | var cmdWg sync.WaitGroup 902 | cmdWg.Add(1) 903 | go func() { 904 | err := q.ExecuteQMPCapabilities(context.Background()) 905 | if err != nil { 906 | t.Errorf("Unexpected error %v", err) 907 | } 908 | cmdWg.Done() 909 | }() 910 | 911 | <-resCh 912 | cancelFn() 913 | cmdWg.Wait() 914 | q.Shutdown() 915 | <-disconnectedCh 916 | wg.Wait() 917 | } 918 | 919 | // Checks that events can be received and parsed. 920 | // 921 | // Two events are provisioned and the QMPLoop is started with an valid eventCh. 922 | // We wait for both events to be received and check that their contents are 923 | // correct. We then shutdown the QMP loop. 924 | // 925 | // Both events are received and their contents are correct. The QMP loop should 926 | // shut down gracefully. 927 | func TestQMPEvents(t *testing.T) { 928 | const ( 929 | seconds = int64(1352167040730) 930 | microsecondsEv1 = 123456 931 | microsecondsEv2 = 123556 932 | device = "device_" + volumeUUID 933 | path = "/dev/rbd0" 934 | ) 935 | var wg sync.WaitGroup 936 | connectedCh := make(chan *QMPVersion) 937 | disconnectedCh := make(chan struct{}) 938 | buf := newQMPTestCommandBuffer(t) 939 | buf.AddEvent("DEVICE_DELETED", time.Millisecond*100, 940 | map[string]interface{}{ 941 | "device": device, 942 | "path": path, 943 | }, 944 | map[string]interface{}{ 945 | "seconds": seconds, 946 | "microseconds": microsecondsEv1, 947 | }) 948 | buf.AddEvent("POWERDOWN", time.Millisecond*200, nil, 949 | map[string]interface{}{ 950 | "seconds": seconds, 951 | "microseconds": microsecondsEv2, 952 | }) 953 | eventCh := make(chan QMPEvent) 954 | cfg := QMPConfig{EventCh: eventCh, Logger: qmpTestLogger{}} 955 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 956 | checkVersion(t, connectedCh) 957 | buf.startEventLoop(&wg) 958 | 959 | ev := <-eventCh 960 | if ev.Name != "DEVICE_DELETED" { 961 | t.Errorf("incorrect event name received. Expected %s, found %s", 962 | "DEVICE_DELETED", ev.Name) 963 | } 964 | if ev.Timestamp != time.Unix(seconds, microsecondsEv1) { 965 | t.Error("incorrect timestamp") 966 | } 967 | deviceName := ev.Data["device"].(string) 968 | if deviceName != device { 969 | t.Errorf("Unexpected device field. Expected %s, found %s", 970 | "device_"+volumeUUID, device) 971 | } 972 | pathName := ev.Data["path"].(string) 973 | if pathName != path { 974 | t.Errorf("Unexpected path field. Expected %s, found %s", 975 | "/dev/rbd0", path) 976 | } 977 | 978 | ev = <-eventCh 979 | if ev.Name != "POWERDOWN" { 980 | t.Errorf("incorrect event name received. Expected %s, found %s", 981 | "POWERDOWN", ev.Name) 982 | } 983 | if ev.Timestamp != time.Unix(seconds, microsecondsEv2) { 984 | t.Error("incorrect timestamp") 985 | } 986 | if ev.Data != nil { 987 | t.Errorf("event data expected to be nil") 988 | } 989 | 990 | q.Shutdown() 991 | 992 | select { 993 | case _, ok := <-eventCh: 994 | if ok { 995 | t.Errorf("Expected eventCh to be closed") 996 | } 997 | case <-time.After(time.Second): 998 | t.Error("Timed out waiting for eventCh to close") 999 | } 1000 | 1001 | <-disconnectedCh 1002 | wg.Wait() 1003 | } 1004 | 1005 | // Checks that commands issued after the QMP loop exits fail (and don't hang) 1006 | // 1007 | // We start the QMP loop but force it to fail immediately simulating a QEMU 1008 | // instance exit. We then send two qmp_cabilities commands. 1009 | // 1010 | // Both commands should fail with an error. The QMP loop should exit. 1011 | func TestQMPLostLoop(t *testing.T) { 1012 | connectedCh := make(chan *QMPVersion) 1013 | disconnectedCh := make(chan struct{}) 1014 | buf := newQMPTestCommandBuffer(t) 1015 | 1016 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1017 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1018 | checkVersion(t, connectedCh) 1019 | close(buf.forceFail) 1020 | buf.AddCommand("qmp_capabilities", nil, "return", nil) 1021 | err := q.ExecuteQMPCapabilities(context.Background()) 1022 | if err == nil { 1023 | t.Error("Expected executeQMPCapabilities to fail") 1024 | } 1025 | <-disconnectedCh 1026 | buf.AddCommand("qmp_capabilities", nil, "return", nil) 1027 | err = q.ExecuteQMPCapabilities(context.Background()) 1028 | if err == nil { 1029 | t.Error("Expected executeQMPCapabilities to fail") 1030 | } 1031 | } 1032 | 1033 | // Checks that PCI devices are correctly added using device_add. 1034 | // 1035 | // We start a QMPLoop, send the device_add command and stop the loop. 1036 | // 1037 | // The device_add command should be correctly sent and the QMP loop should 1038 | // exit gracefully. 1039 | func TestQMPPCIDeviceAdd(t *testing.T) { 1040 | connectedCh := make(chan *QMPVersion) 1041 | disconnectedCh := make(chan struct{}) 1042 | buf := newQMPTestCommandBuffer(t) 1043 | buf.AddCommand("device_add", nil, "return", nil) 1044 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1045 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1046 | q.version = checkVersion(t, connectedCh) 1047 | blockdevID := fmt.Sprintf("drive_%s", volumeUUID) 1048 | devID := fmt.Sprintf("device_%s", volumeUUID) 1049 | err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, 1050 | "virtio-blk-pci", "0x1", "", "", 1, true, false) 1051 | if err != nil { 1052 | t.Fatalf("Unexpected error %v", err) 1053 | } 1054 | q.Shutdown() 1055 | <-disconnectedCh 1056 | } 1057 | 1058 | // Checks that PCI VFIO mediated devices are correctly added using device_add. 1059 | // 1060 | // We start a QMPLoop, send the device_add command and stop the loop. 1061 | // 1062 | // The device_add command should be correctly sent and the QMP loop should 1063 | // exit gracefully. 1064 | func TestQMPPCIVFIOMediatedDeviceAdd(t *testing.T) { 1065 | connectedCh := make(chan *QMPVersion) 1066 | disconnectedCh := make(chan struct{}) 1067 | buf := newQMPTestCommandBuffer(t) 1068 | buf.AddCommand("device_add", nil, "return", nil) 1069 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1070 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1071 | checkVersion(t, connectedCh) 1072 | sysfsDev := "/sys/bus/pci/devices/0000:00:02.0/a297db4a-f4c2-11e6-90f6-d3b88d6c9525" 1073 | devID := fmt.Sprintf("device_%s", volumeUUID) 1074 | err := q.ExecutePCIVFIOMediatedDeviceAdd(context.Background(), devID, sysfsDev, "0x1", "", "") 1075 | if err != nil { 1076 | t.Fatalf("Unexpected error %v", err) 1077 | } 1078 | q.Shutdown() 1079 | <-disconnectedCh 1080 | } 1081 | 1082 | func TestQMPPCIVFIOPCIeDeviceAdd(t *testing.T) { 1083 | connectedCh := make(chan *QMPVersion) 1084 | disconnectedCh := make(chan struct{}) 1085 | buf := newQMPTestCommandBuffer(t) 1086 | buf.AddCommand("device_add", nil, "return", nil) 1087 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1088 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1089 | checkVersion(t, connectedCh) 1090 | bdf := "04:00.0" 1091 | bus := "rp0" 1092 | addr := "0x1" 1093 | romfile := "" 1094 | devID := fmt.Sprintf("device_%s", volumeUUID) 1095 | err := q.ExecutePCIVFIODeviceAdd(context.Background(), devID, bdf, addr, bus, romfile) 1096 | if err != nil { 1097 | t.Fatalf("Unexpected error %v", err) 1098 | } 1099 | q.Shutdown() 1100 | <-disconnectedCh 1101 | } 1102 | 1103 | func TestQMPAPVFIOMediatedDeviceAdd(t *testing.T) { 1104 | connectedCh := make(chan *QMPVersion) 1105 | disconnectedCh := make(chan struct{}) 1106 | buf := newQMPTestCommandBuffer(t) 1107 | buf.AddCommand("device_add", nil, "return", nil) 1108 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1109 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1110 | checkVersion(t, connectedCh) 1111 | sysfsDev := "/sys/devices/vfio_ap/matrix/a297db4a-f4c2-11e6-90f6-d3b88d6c9525" 1112 | err := q.ExecuteAPVFIOMediatedDeviceAdd(context.Background(), sysfsDev) 1113 | if err != nil { 1114 | t.Fatalf("Unexpected error %v", err) 1115 | } 1116 | q.Shutdown() 1117 | <-disconnectedCh 1118 | } 1119 | 1120 | // Checks that CPU are correctly added using device_add 1121 | func TestQMPCPUDeviceAdd(t *testing.T) { 1122 | drivers := []string{"host-x86_64-cpu", "host-s390x-cpu", "host-powerpc64-cpu"} 1123 | cpuID := "cpu-0" 1124 | socketID := "0" 1125 | dieID := "0" 1126 | coreID := "1" 1127 | threadID := "0" 1128 | for _, d := range drivers { 1129 | connectedCh := make(chan *QMPVersion) 1130 | disconnectedCh := make(chan struct{}) 1131 | buf := newQMPTestCommandBuffer(t) 1132 | buf.AddCommand("device_add", nil, "return", nil) 1133 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1134 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1135 | checkVersion(t, connectedCh) 1136 | err := q.ExecuteCPUDeviceAdd(context.Background(), d, cpuID, socketID, dieID, coreID, threadID, "") 1137 | if err != nil { 1138 | t.Fatalf("Unexpected error %v", err) 1139 | } 1140 | q.Shutdown() 1141 | <-disconnectedCh 1142 | } 1143 | } 1144 | 1145 | // Checks that hotpluggable CPUs are listed correctly 1146 | func TestQMPExecuteQueryHotpluggableCPUs(t *testing.T) { 1147 | connectedCh := make(chan *QMPVersion) 1148 | disconnectedCh := make(chan struct{}) 1149 | buf := newQMPTestCommandBuffer(t) 1150 | hotCPU := HotpluggableCPU{ 1151 | Type: "host-x86", 1152 | VcpusCount: 5, 1153 | Properties: CPUProperties{ 1154 | Node: 1, 1155 | Socket: 3, 1156 | Die: 1, 1157 | Core: 2, 1158 | Thread: 4, 1159 | }, 1160 | QOMPath: "/abc/123/rgb", 1161 | } 1162 | buf.AddCommand("query-hotpluggable-cpus", nil, "return", []interface{}{hotCPU}) 1163 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1164 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1165 | checkVersion(t, connectedCh) 1166 | hotCPUs, err := q.ExecuteQueryHotpluggableCPUs(context.Background()) 1167 | if err != nil { 1168 | t.Fatalf("Unexpected error: %v", err) 1169 | } 1170 | if len(hotCPUs) != 1 { 1171 | t.Fatalf("Expected hot CPUs length equals to 1\n") 1172 | } 1173 | if reflect.DeepEqual(hotCPUs[0], hotCPU) == false { 1174 | t.Fatalf("Expected %v equals to %v", hotCPUs[0], hotCPU) 1175 | } 1176 | q.Shutdown() 1177 | <-disconnectedCh 1178 | } 1179 | 1180 | // Checks that memory devices are listed correctly 1181 | func TestQMPExecuteQueryMemoryDevices(t *testing.T) { 1182 | connectedCh := make(chan *QMPVersion) 1183 | disconnectedCh := make(chan struct{}) 1184 | buf := newQMPTestCommandBuffer(t) 1185 | memoryDevices := MemoryDevices{ 1186 | Type: "dimm", 1187 | Data: MemoryDevicesData{ 1188 | Slot: 1, 1189 | Node: 0, 1190 | Addr: 1234, 1191 | Memdev: "dimm1", 1192 | ID: "mem1", 1193 | Hotpluggable: true, 1194 | Hotplugged: false, 1195 | Size: 1234, 1196 | }, 1197 | } 1198 | buf.AddCommand("query-memory-devices", nil, "return", []interface{}{memoryDevices}) 1199 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1200 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1201 | checkVersion(t, connectedCh) 1202 | memDevices, err := q.ExecQueryMemoryDevices(context.Background()) 1203 | if err != nil { 1204 | t.Fatalf("Unexpected error: %v", err) 1205 | } 1206 | if len(memDevices) != 1 { 1207 | t.Fatalf("Expected memory devices length equals to 1\n") 1208 | } 1209 | if reflect.DeepEqual(memDevices[0], memoryDevices) == false { 1210 | t.Fatalf("Expected %v equals to %v", memDevices[0], memoryDevices) 1211 | } 1212 | q.Shutdown() 1213 | <-disconnectedCh 1214 | } 1215 | 1216 | // Checks that cpus are listed correctly 1217 | func TestQMPExecuteQueryCpus(t *testing.T) { 1218 | connectedCh := make(chan *QMPVersion) 1219 | disconnectedCh := make(chan struct{}) 1220 | buf := newQMPTestCommandBuffer(t) 1221 | cpuInfo := CPUInfo{ 1222 | CPU: 1, 1223 | Current: false, 1224 | Halted: false, 1225 | Arch: "x86_64", 1226 | QomPath: "/tmp/testQom", 1227 | Pc: 123456, 1228 | ThreadID: 123457, 1229 | Props: CPUProperties{ 1230 | Node: 0, 1231 | Socket: 1, 1232 | Core: 1, 1233 | Thread: 1966, 1234 | }, 1235 | } 1236 | buf.AddCommand("query-cpus", nil, "return", []interface{}{cpuInfo}) 1237 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1238 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1239 | checkVersion(t, connectedCh) 1240 | cpus, err := q.ExecQueryCpus(context.Background()) 1241 | if err != nil { 1242 | t.Fatalf("Unexpected error: %v", err) 1243 | } 1244 | if len(cpus) != 1 { 1245 | t.Fatalf("Expected memory devices length equals to 1\n") 1246 | } 1247 | if reflect.DeepEqual(cpus[0], cpuInfo) == false { 1248 | t.Fatalf("Expected %v equals to %v", cpus[0], cpuInfo) 1249 | } 1250 | q.Shutdown() 1251 | <-disconnectedCh 1252 | } 1253 | 1254 | // Checks that cpus are listed correctly 1255 | func TestQMPExecuteQueryCpusFast(t *testing.T) { 1256 | connectedCh := make(chan *QMPVersion) 1257 | disconnectedCh := make(chan struct{}) 1258 | buf := newQMPTestCommandBuffer(t) 1259 | cpuInfoFast := CPUInfoFast{ 1260 | CPUIndex: 1, 1261 | Arch: "x86", 1262 | Target: "x86_64", 1263 | QomPath: "/tmp/testQom", 1264 | ThreadID: 123457, 1265 | Props: CPUProperties{ 1266 | Node: 0, 1267 | Socket: 1, 1268 | Core: 1, 1269 | Thread: 1966, 1270 | }, 1271 | } 1272 | buf.AddCommand("query-cpus-fast", nil, "return", []interface{}{cpuInfoFast}) 1273 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1274 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1275 | checkVersion(t, connectedCh) 1276 | cpus, err := q.ExecQueryCpusFast(context.Background()) 1277 | if err != nil { 1278 | t.Fatalf("Unexpected error: %v", err) 1279 | } 1280 | if len(cpus) != 1 { 1281 | t.Fatalf("Expected memory devices length equals to 1\n") 1282 | } 1283 | if reflect.DeepEqual(cpus[0], cpuInfoFast) == false { 1284 | t.Fatalf("Expected %v equals to %v", cpus[0], cpuInfoFast) 1285 | } 1286 | q.Shutdown() 1287 | <-disconnectedCh 1288 | } 1289 | 1290 | // Checks that migrate capabilities can be set 1291 | func TestExecSetMigrationCaps(t *testing.T) { 1292 | connectedCh := make(chan *QMPVersion) 1293 | disconnectedCh := make(chan struct{}) 1294 | buf := newQMPTestCommandBuffer(t) 1295 | buf.AddCommand("migrate-set-capabilities", nil, "return", nil) 1296 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1297 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1298 | checkVersion(t, connectedCh) 1299 | caps := []map[string]interface{}{ 1300 | { 1301 | "capability": "bypass-shared-memory", 1302 | "state": true, 1303 | }, 1304 | } 1305 | err := q.ExecSetMigrationCaps(context.Background(), caps) 1306 | if err != nil { 1307 | t.Fatalf("Unexpected error: %v", err) 1308 | } 1309 | q.Shutdown() 1310 | <-disconnectedCh 1311 | } 1312 | 1313 | // Checks that migrate arguments can be set 1314 | func TestExecSetMigrateArguments(t *testing.T) { 1315 | connectedCh := make(chan *QMPVersion) 1316 | disconnectedCh := make(chan struct{}) 1317 | buf := newQMPTestCommandBuffer(t) 1318 | buf.AddCommand("migrate", nil, "return", nil) 1319 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1320 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1321 | checkVersion(t, connectedCh) 1322 | err := q.ExecSetMigrateArguments(context.Background(), "exec:foobar") 1323 | if err != nil { 1324 | t.Fatalf("Unexpected error: %v", err) 1325 | } 1326 | q.Shutdown() 1327 | <-disconnectedCh 1328 | } 1329 | 1330 | // Checks add memory device 1331 | func TestExecMemdevAdd(t *testing.T) { 1332 | connectedCh := make(chan *QMPVersion) 1333 | disconnectedCh := make(chan struct{}) 1334 | buf := newQMPTestCommandBuffer(t) 1335 | buf.AddCommand("object-add", nil, "return", nil) 1336 | buf.AddCommand("device_add", nil, "return", nil) 1337 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1338 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1339 | checkVersion(t, connectedCh) 1340 | err := q.ExecMemdevAdd(context.Background(), "memory-backend-ram", "mem0", "", 128, true, "virtio-mem-pci", "virtiomem0", "0x1", "pci-bridge-0") 1341 | if err != nil { 1342 | t.Fatalf("Unexpected error: %v", err) 1343 | } 1344 | q.Shutdown() 1345 | <-disconnectedCh 1346 | } 1347 | 1348 | // Checks hotplug memory 1349 | func TestExecHotplugMemory(t *testing.T) { 1350 | connectedCh := make(chan *QMPVersion) 1351 | disconnectedCh := make(chan struct{}) 1352 | buf := newQMPTestCommandBuffer(t) 1353 | buf.AddCommand("object-add", nil, "return", nil) 1354 | buf.AddCommand("device_add", nil, "return", nil) 1355 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1356 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1357 | checkVersion(t, connectedCh) 1358 | err := q.ExecHotplugMemory(context.Background(), "memory-backend-ram", "mem0", "", 128, true) 1359 | if err != nil { 1360 | t.Fatalf("Unexpected error: %v", err) 1361 | } 1362 | q.Shutdown() 1363 | <-disconnectedCh 1364 | } 1365 | 1366 | // Checks vsock-pci hotplug 1367 | func TestExecutePCIVSockAdd(t *testing.T) { 1368 | connectedCh := make(chan *QMPVersion) 1369 | disconnectedCh := make(chan struct{}) 1370 | buf := newQMPTestCommandBuffer(t) 1371 | buf.AddCommand("device_add", nil, "return", nil) 1372 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1373 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1374 | checkVersion(t, connectedCh) 1375 | err := q.ExecutePCIVSockAdd(context.Background(), "vsock-pci0", "3", "1", "1", "1", "", true) 1376 | if err != nil { 1377 | t.Fatalf("Unexpected error %v", err) 1378 | } 1379 | q.Shutdown() 1380 | <-disconnectedCh 1381 | } 1382 | 1383 | // Checks vhost-user-pci hotplug 1384 | func TestExecutePCIVhostUserDevAdd(t *testing.T) { 1385 | connectedCh := make(chan *QMPVersion) 1386 | disconnectedCh := make(chan struct{}) 1387 | buf := newQMPTestCommandBuffer(t) 1388 | buf.AddCommand("device_add", nil, "return", nil) 1389 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1390 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1391 | checkVersion(t, connectedCh) 1392 | driver := "vhost-user-blk-pci" 1393 | devID := "vhost-user-blk0" 1394 | chardevID := "vhost-user-blk-char0" 1395 | err := q.ExecutePCIVhostUserDevAdd(context.Background(), driver, devID, chardevID, "1", "1") 1396 | if err != nil { 1397 | t.Fatalf("Unexpected error %v", err) 1398 | } 1399 | q.Shutdown() 1400 | <-disconnectedCh 1401 | } 1402 | 1403 | // Checks getfd 1404 | func TestExecuteGetFdD(t *testing.T) { 1405 | connectedCh := make(chan *QMPVersion) 1406 | disconnectedCh := make(chan struct{}) 1407 | buf := newQMPTestCommandBuffer(t) 1408 | buf.AddCommand("getfd", nil, "return", nil) 1409 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1410 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1411 | checkVersion(t, connectedCh) 1412 | err := q.ExecuteGetFD(context.Background(), "foo", os.NewFile(0, "foo")) 1413 | if err != nil { 1414 | t.Fatalf("Unexpected error %v", err) 1415 | } 1416 | q.Shutdown() 1417 | <-disconnectedCh 1418 | } 1419 | 1420 | // Checks chardev-add unix socket 1421 | func TestExecuteCharDevUnixSocketAdd(t *testing.T) { 1422 | connectedCh := make(chan *QMPVersion) 1423 | disconnectedCh := make(chan struct{}) 1424 | buf := newQMPTestCommandBuffer(t) 1425 | buf.AddCommand("chardev-add", nil, "return", nil) 1426 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1427 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1428 | checkVersion(t, connectedCh) 1429 | err := q.ExecuteCharDevUnixSocketAdd(context.Background(), "foo", "foo.sock", false, true) 1430 | if err != nil { 1431 | t.Fatalf("Unexpected error %v", err) 1432 | } 1433 | q.Shutdown() 1434 | <-disconnectedCh 1435 | } 1436 | 1437 | // Checks virtio serial port hotplug 1438 | func TestExecuteVirtSerialPortAdd(t *testing.T) { 1439 | connectedCh := make(chan *QMPVersion) 1440 | disconnectedCh := make(chan struct{}) 1441 | buf := newQMPTestCommandBuffer(t) 1442 | buf.AddCommand("device_add", nil, "return", nil) 1443 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1444 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1445 | checkVersion(t, connectedCh) 1446 | err := q.ExecuteVirtSerialPortAdd(context.Background(), "foo", "foo.channel", "foo") 1447 | if err != nil { 1448 | t.Fatalf("Unexpected error %v", err) 1449 | } 1450 | q.Shutdown() 1451 | <-disconnectedCh 1452 | } 1453 | 1454 | // Check migration incoming 1455 | func TestExecuteMigrationIncoming(t *testing.T) { 1456 | connectedCh := make(chan *QMPVersion) 1457 | disconnectedCh := make(chan struct{}) 1458 | buf := newQMPTestCommandBuffer(t) 1459 | buf.AddCommand("migrate-incoming", nil, "return", nil) 1460 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1461 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1462 | checkVersion(t, connectedCh) 1463 | err := q.ExecuteMigrationIncoming(context.Background(), "uri") 1464 | if err != nil { 1465 | t.Fatalf("Unexpected error %v", err) 1466 | } 1467 | q.Shutdown() 1468 | <-disconnectedCh 1469 | } 1470 | 1471 | // Checks migration status 1472 | func TestExecuteQueryMigration(t *testing.T) { 1473 | connectedCh := make(chan *QMPVersion) 1474 | disconnectedCh := make(chan struct{}) 1475 | buf := newQMPTestCommandBuffer(t) 1476 | status := MigrationStatus{ 1477 | Status: "completed", 1478 | RAM: MigrationRAM{ 1479 | Total: 100, 1480 | Remaining: 101, 1481 | Transferred: 101, 1482 | TotalTime: 101, 1483 | SetupTime: 101, 1484 | ExpectedDowntime: 101, 1485 | Duplicate: 101, 1486 | Normal: 101, 1487 | NormalBytes: 101, 1488 | DirtySyncCount: 101, 1489 | }, 1490 | Disk: MigrationDisk{ 1491 | Total: 200, 1492 | Remaining: 200, 1493 | Transferred: 200, 1494 | }, 1495 | XbzrleCache: MigrationXbzrleCache{ 1496 | CacheSize: 300, 1497 | Bytes: 300, 1498 | Pages: 300, 1499 | CacheMiss: 300, 1500 | CacheMissRate: 300, 1501 | Overflow: 300, 1502 | }, 1503 | } 1504 | caps := map[string]interface{}{"foo": true} 1505 | status.Capabilities = append(status.Capabilities, caps) 1506 | buf.AddCommand("query-migrate", nil, "return", interface{}(status)) 1507 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1508 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1509 | checkVersion(t, connectedCh) 1510 | s, err := q.ExecuteQueryMigration(context.Background()) 1511 | if err != nil { 1512 | t.Fatalf("Unexpected error %v", err) 1513 | } 1514 | if !reflect.DeepEqual(s, status) { 1515 | t.Fatalf("expected %v got %v", status, s) 1516 | } 1517 | q.Shutdown() 1518 | <-disconnectedCh 1519 | } 1520 | 1521 | // Checks balloon 1522 | func TestExecuteBalloon(t *testing.T) { 1523 | connectedCh := make(chan *QMPVersion) 1524 | disconnectedCh := make(chan struct{}) 1525 | buf := newQMPTestCommandBuffer(t) 1526 | buf.AddCommand("balloon", nil, "return", nil) 1527 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1528 | 1529 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1530 | checkVersion(t, connectedCh) 1531 | err := q.ExecuteBalloon(context.Background(), 1073741824) 1532 | if err != nil { 1533 | t.Fatalf("Unexpected error %v", err) 1534 | } 1535 | q.Shutdown() 1536 | <-disconnectedCh 1537 | } 1538 | 1539 | func TestErrorDesc(t *testing.T) { 1540 | errDesc := "Somthing err messages" 1541 | errData := map[string]string{ 1542 | "class": "GenericError", 1543 | "desc": errDesc, 1544 | } 1545 | 1546 | connectedCh := make(chan *QMPVersion) 1547 | disconnectedCh := make(chan struct{}) 1548 | buf := newQMPTestCommandBuffer(t) 1549 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1550 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1551 | checkVersion(t, connectedCh) 1552 | 1553 | desc, err := q.errorDesc(errData) 1554 | if err != nil { 1555 | t.Fatalf("Unexpected error '%v'", err) 1556 | } 1557 | if desc != errDesc { 1558 | t.Fatalf("expected '%v'\n got '%v'", errDesc, desc) 1559 | } 1560 | 1561 | q.Shutdown() 1562 | <-disconnectedCh 1563 | } 1564 | 1565 | func TestExecCommandFailed(t *testing.T) { 1566 | errDesc := "unable to map backing store for guest RAM: Cannot allocate memory" 1567 | errData := map[string]string{ 1568 | "class": "GenericError", 1569 | "desc": errDesc, 1570 | } 1571 | 1572 | connectedCh := make(chan *QMPVersion) 1573 | disconnectedCh := make(chan struct{}) 1574 | buf := newQMPTestCommandBuffer(t) 1575 | buf.AddCommand("object-add", nil, "error", errData) 1576 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1577 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1578 | checkVersion(t, connectedCh) 1579 | 1580 | _, err := q.executeCommandWithResponse(context.Background(), "object-add", nil, nil, nil) 1581 | if err == nil { 1582 | t.Fatalf("expected error but got nil") 1583 | } 1584 | 1585 | expectedString := "QMP command failed: " + errDesc 1586 | if err.Error() != expectedString { 1587 | t.Fatalf("expected '%v' but got '%v'", expectedString, err) 1588 | } 1589 | 1590 | q.Shutdown() 1591 | <-disconnectedCh 1592 | } 1593 | 1594 | func TestExecCommandFailedWithInnerError(t *testing.T) { 1595 | errData := map[string]string{ 1596 | "class": "GenericError", 1597 | "descFieldInvalid": "Invalid", 1598 | } 1599 | 1600 | connectedCh := make(chan *QMPVersion) 1601 | disconnectedCh := make(chan struct{}) 1602 | buf := newQMPTestCommandBuffer(t) 1603 | buf.AddCommand("object-add", nil, "error", errData) 1604 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1605 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1606 | checkVersion(t, connectedCh) 1607 | 1608 | _, err := q.executeCommandWithResponse(context.Background(), "object-add", nil, nil, nil) 1609 | if err == nil { 1610 | t.Fatalf("expected error but got nil") 1611 | } 1612 | 1613 | expectedString := "QMP command failed: " 1614 | if err.Error() != expectedString { 1615 | t.Fatalf("expected '%v' but got '%v'", expectedString, err) 1616 | } 1617 | 1618 | q.Shutdown() 1619 | <-disconnectedCh 1620 | } 1621 | 1622 | // Checks NVDIMM device add 1623 | func TestExecuteNVDIMMDeviceAdd(t *testing.T) { 1624 | connectedCh := make(chan *QMPVersion) 1625 | disconnectedCh := make(chan struct{}) 1626 | buf := newQMPTestCommandBuffer(t) 1627 | buf.AddCommand("object-add", nil, "return", nil) 1628 | buf.AddCommand("device_add", nil, "return", nil) 1629 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1630 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1631 | checkVersion(t, connectedCh) 1632 | pmem := true 1633 | err := q.ExecuteNVDIMMDeviceAdd(context.Background(), "nvdimm0", "/dev/rbd0", 1024, &pmem) 1634 | if err != nil { 1635 | t.Fatalf("Unexpected error: %v", err) 1636 | } 1637 | q.Shutdown() 1638 | <-disconnectedCh 1639 | } 1640 | 1641 | func TestMainLoopEventBeforeGreeting(t *testing.T) { 1642 | const ( 1643 | seconds = int64(1352167040730) 1644 | microseconds = 123456 1645 | ) 1646 | 1647 | connectedCh := make(chan *QMPVersion) 1648 | disconnectedCh := make(chan struct{}) 1649 | buf := newQMPTestCommandBufferNoGreeting(t) 1650 | 1651 | // Add events 1652 | var wg sync.WaitGroup 1653 | buf.AddEvent("VSERPORT_CHANGE", time.Millisecond*100, 1654 | map[string]interface{}{ 1655 | "open": false, 1656 | "id": "channel0", 1657 | }, 1658 | map[string]interface{}{ 1659 | "seconds": seconds, 1660 | "microseconds": microseconds, 1661 | }) 1662 | buf.AddEvent("POWERDOWN", time.Millisecond*200, nil, 1663 | map[string]interface{}{ 1664 | "seconds": seconds, 1665 | "microseconds": microseconds, 1666 | }) 1667 | 1668 | // register a channel to receive events 1669 | eventCh := make(chan QMPEvent) 1670 | cfg := QMPConfig{EventCh: eventCh, Logger: qmpTestLogger{}} 1671 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1672 | 1673 | // Start events, this will lead to a deadlock if mainLoop is not implemented 1674 | // correctly 1675 | buf.startEventLoop(&wg) 1676 | wg.Wait() 1677 | 1678 | // Send greeting and check version 1679 | buf.newDataCh <- []byte(qmpHello) 1680 | checkVersion(t, connectedCh) 1681 | 1682 | q.Shutdown() 1683 | <-disconnectedCh 1684 | } 1685 | 1686 | func TestQMPExecQueryQmpSchema(t *testing.T) { 1687 | connectedCh := make(chan *QMPVersion) 1688 | disconnectedCh := make(chan struct{}) 1689 | buf := newQMPTestCommandBuffer(t) 1690 | schemaInfo := []SchemaInfo{ 1691 | { 1692 | MetaType: "command", 1693 | Name: "object-add", 1694 | }, 1695 | { 1696 | MetaType: "event", 1697 | Name: "VSOCK_RUNNING", 1698 | }, 1699 | } 1700 | buf.AddCommand("query-qmp-schema", nil, "return", schemaInfo) 1701 | cfg := QMPConfig{ 1702 | Logger: qmpTestLogger{}, 1703 | MaxCapacity: 1024, 1704 | } 1705 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1706 | checkVersion(t, connectedCh) 1707 | info, err := q.ExecQueryQmpSchema(context.Background()) 1708 | if err != nil { 1709 | t.Fatalf("Unexpected error: %v", err) 1710 | } 1711 | if len(schemaInfo) != 2 { 1712 | t.Fatalf("Expected schema infos length equals to 2\n") 1713 | } 1714 | if reflect.DeepEqual(info, schemaInfo) == false { 1715 | t.Fatalf("Expected %v equals to %v", info, schemaInfo) 1716 | } 1717 | q.Shutdown() 1718 | <-disconnectedCh 1719 | } 1720 | 1721 | func TestQMPExecQueryQmpStatus(t *testing.T) { 1722 | connectedCh := make(chan *QMPVersion) 1723 | disconnectedCh := make(chan struct{}) 1724 | buf := newQMPTestCommandBuffer(t) 1725 | statusInfo := StatusInfo{ 1726 | Running: true, 1727 | SingleStep: false, 1728 | Status: "running", 1729 | } 1730 | buf.AddCommand("query-status", nil, "return", statusInfo) 1731 | cfg := QMPConfig{ 1732 | Logger: qmpTestLogger{}, 1733 | MaxCapacity: 1024, 1734 | } 1735 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1736 | checkVersion(t, connectedCh) 1737 | info, err := q.ExecuteQueryStatus(context.Background()) 1738 | if err != nil { 1739 | t.Fatalf("Unexpected error: %v", err) 1740 | } 1741 | if reflect.DeepEqual(info, statusInfo) == false { 1742 | t.Fatalf("Expected %v equals to %v", info, statusInfo) 1743 | } 1744 | q.Shutdown() 1745 | <-disconnectedCh 1746 | } 1747 | 1748 | // Checks qom-set 1749 | func TestExecQomSet(t *testing.T) { 1750 | connectedCh := make(chan *QMPVersion) 1751 | disconnectedCh := make(chan struct{}) 1752 | buf := newQMPTestCommandBuffer(t) 1753 | buf.AddCommand("qom-set", nil, "return", nil) 1754 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1755 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1756 | checkVersion(t, connectedCh) 1757 | err := q.ExecQomSet(context.Background(), "virtiomem0", "requested-size", 1024) 1758 | if err != nil { 1759 | t.Fatalf("Unexpected error: %v", err) 1760 | } 1761 | q.Shutdown() 1762 | <-disconnectedCh 1763 | } 1764 | 1765 | // Checks qom-get 1766 | func TestExecQomGet(t *testing.T) { 1767 | connectedCh := make(chan *QMPVersion) 1768 | disconnectedCh := make(chan struct{}) 1769 | buf := newQMPTestCommandBuffer(t) 1770 | buf.AddCommand("qom-get", nil, "return", "container") 1771 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1772 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1773 | checkVersion(t, connectedCh) 1774 | val, err := q.ExecQomGet(context.Background(), "/", "type") 1775 | if err != nil { 1776 | t.Fatalf("Unexpected error: %v", err) 1777 | } 1778 | vals, ok := val.(string) 1779 | if !ok { 1780 | t.Fatalf("Unexpected type in qom-get") 1781 | } 1782 | if vals != "container" { 1783 | t.Fatalf("Unpexected value in qom-get") 1784 | } 1785 | q.Shutdown() 1786 | <-disconnectedCh 1787 | } 1788 | 1789 | // Checks dump-guest-memory 1790 | func TestExecuteDumpGuestMemory(t *testing.T) { 1791 | connectedCh := make(chan *QMPVersion) 1792 | disconnectedCh := make(chan struct{}) 1793 | buf := newQMPTestCommandBuffer(t) 1794 | buf.AddCommand("dump-guest-memory", nil, "return", nil) 1795 | cfg := QMPConfig{Logger: qmpTestLogger{}} 1796 | q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) 1797 | checkVersion(t, connectedCh) 1798 | 1799 | err := q.ExecuteDumpGuestMemory(context.Background(), "file:/tmp/dump.xxx.yyy", false, "elf") 1800 | if err != nil { 1801 | t.Fatalf("Unexpected error: %v", err) 1802 | } 1803 | 1804 | q.Shutdown() 1805 | <-disconnectedCh 1806 | } 1807 | --------------------------------------------------------------------------------