Initial Commit
This commit is contained in:
		
							
								
								
									
										13
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | language: go | ||||||
|  | go: | ||||||
|  |   - 1.6.2 | ||||||
|  | before_install: | ||||||
|  |   - go get github.com/golang/lint/golint | ||||||
|  | before_script: | ||||||
|  |   - go get -d ./... | ||||||
|  | script: | ||||||
|  |   - ./scripts/licensecheck.sh | ||||||
|  |   - go build ./... | ||||||
|  |   - ./scripts/golint.sh | ||||||
|  |   - go vet ./... | ||||||
|  |   - go test -v ./... | ||||||
							
								
								
									
										12
									
								
								AUTHORS
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								AUTHORS
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | Maintainer | ||||||
|  | ---------- | ||||||
|  | DigitalOcean, Inc | ||||||
|  |  | ||||||
|  | Original Authors | ||||||
|  | ---------------- | ||||||
|  | Ben LeMasurier	<blemasurier@digitalocean.com> | ||||||
|  | Matt Layher	<mlayher@digitalocean.com> | ||||||
|  |  | ||||||
|  | Contributors | ||||||
|  | ------------ | ||||||
|  | Justin Kim      <justin@digitalocean.com> | ||||||
							
								
								
									
										27
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | Contributing | ||||||
|  | ============ | ||||||
|  |  | ||||||
|  | The `go-libvirt` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/) | ||||||
|  | for contributions. | ||||||
|  |  | ||||||
|  | If you'd like to contribute to the project, please | ||||||
|  | [open an issue](https://github.com/digitalocean/go-libvirt/issues/new) or find an | ||||||
|  | [existing issue](https://github.com/digitalocean/go-libvirt/issues) that you'd like | ||||||
|  | to take on.  This ensures that efforts are not duplicated, and that a new feature | ||||||
|  | aligns with the focus of the rest of the repository. | ||||||
|  |  | ||||||
|  | Once your suggestion has been submitted and discussed, please be sure that your | ||||||
|  | code meets the following criteria: | ||||||
|  |   - code is completely `gofmt`'d | ||||||
|  |   - new features or codepaths have appropriate test coverage | ||||||
|  |   - `go test ./...` passes | ||||||
|  |   - `go vet ./...` passes | ||||||
|  |   - `golint ./...` returns no warnings, including documentation comment warnings | ||||||
|  |  | ||||||
|  | In addition, if this is your first time contributing to the `go-libvirt` project, | ||||||
|  | add your name and email address to the | ||||||
|  | [AUTHORS](https://github.com/digitalocean/go-libvirt/blob/master/AUTHORS) file | ||||||
|  | under the "Contributors" section using the format: | ||||||
|  | `First Last <email@example.com>`. | ||||||
|  |  | ||||||
|  | Finally, submit a pull request for review! | ||||||
							
								
								
									
										195
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | |||||||
|  | Apache License | ||||||
|  | ============== | ||||||
|  |  | ||||||
|  | _Version 2.0, January 2004_   | ||||||
|  | _<<http://www.apache.org/licenses/>>_ | ||||||
|  |  | ||||||
|  | ### Terms and Conditions for use, reproduction, and distribution | ||||||
|  |  | ||||||
|  | #### 1. Definitions | ||||||
|  |  | ||||||
|  | “License” shall mean the terms and conditions for use, reproduction, and | ||||||
|  | distribution as defined by Sections 1 through 9 of this document. | ||||||
|  |  | ||||||
|  | “Licensor” shall mean the copyright owner or entity authorized by the copyright | ||||||
|  | owner that is granting the License. | ||||||
|  |  | ||||||
|  | “Legal Entity” shall mean the union of the acting entity and all other entities | ||||||
|  | that control, are controlled by, or are under common control with that entity. | ||||||
|  | For the purposes of this definition, “control” means **(i)** the power, direct or | ||||||
|  | indirect, to cause the direction or management of such entity, whether by | ||||||
|  | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the | ||||||
|  | outstanding shares, or **(iii)** beneficial ownership of such entity. | ||||||
|  |  | ||||||
|  | “You” (or “Your”) shall mean an individual or Legal Entity exercising | ||||||
|  | permissions granted by this License. | ||||||
|  |  | ||||||
|  | “Source” form shall mean the preferred form for making modifications, including | ||||||
|  | but not limited to software source code, documentation source, and configuration | ||||||
|  | files. | ||||||
|  |  | ||||||
|  | “Object” form shall mean any form resulting from mechanical transformation or | ||||||
|  | translation of a Source form, including but not limited to compiled object code, | ||||||
|  | generated documentation, and conversions to other media types. | ||||||
|  |  | ||||||
|  | “Work” shall mean the work of authorship, whether in Source or Object form, made | ||||||
|  | available under the License, as indicated by a copyright notice that is included | ||||||
|  | in or attached to the work (an example is provided in the Appendix below). | ||||||
|  |  | ||||||
|  | “Derivative Works” shall mean any work, whether in Source or Object form, that | ||||||
|  | is based on (or derived from) the Work and for which the editorial revisions, | ||||||
|  | annotations, elaborations, or other modifications represent, as a whole, an | ||||||
|  | original work of authorship. For the purposes of this License, Derivative Works | ||||||
|  | shall not include works that remain separable from, or merely link (or bind by | ||||||
|  | name) to the interfaces of, the Work and Derivative Works thereof. | ||||||
|  |  | ||||||
|  | “Contribution” shall mean any work of authorship, including the original version | ||||||
|  | of the Work and any modifications or additions to that Work or Derivative Works | ||||||
|  | thereof, that is intentionally submitted to Licensor for inclusion in the Work | ||||||
|  | by the copyright owner or by an individual or Legal Entity authorized to submit | ||||||
|  | on behalf of the copyright owner. For the purposes of this definition, | ||||||
|  | “submitted” means any form of electronic, verbal, or written communication sent | ||||||
|  | to the Licensor or its representatives, including but not limited to | ||||||
|  | communication on electronic mailing lists, source code control systems, and | ||||||
|  | issue tracking systems that are managed by, or on behalf of, the Licensor for | ||||||
|  | the purpose of discussing and improving the Work, but excluding communication | ||||||
|  | that is conspicuously marked or otherwise designated in writing by the copyright | ||||||
|  | owner as “Not a Contribution.” | ||||||
|  |  | ||||||
|  | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf | ||||||
|  | of whom a Contribution has been received by Licensor and subsequently | ||||||
|  | incorporated within the Work. | ||||||
|  |  | ||||||
|  | #### 2. Grant of Copyright License | ||||||
|  |  | ||||||
|  | Subject to the terms and conditions of this License, each Contributor hereby | ||||||
|  | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, | ||||||
|  | irrevocable copyright license to reproduce, prepare Derivative Works of, | ||||||
|  | publicly display, publicly perform, sublicense, and distribute the Work and such | ||||||
|  | Derivative Works in Source or Object form. | ||||||
|  |  | ||||||
|  | #### 3. Grant of Patent License | ||||||
|  |  | ||||||
|  | Subject to the terms and conditions of this License, each Contributor hereby | ||||||
|  | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, | ||||||
|  | irrevocable (except as stated in this section) patent license to make, have | ||||||
|  | made, use, offer to sell, sell, import, and otherwise transfer the Work, where | ||||||
|  | such license applies only to those patent claims licensable by such Contributor | ||||||
|  | that are necessarily infringed by their Contribution(s) alone or by combination | ||||||
|  | of their Contribution(s) with the Work to which such Contribution(s) was | ||||||
|  | submitted. If You institute patent litigation against any entity (including a | ||||||
|  | cross-claim or counterclaim in a lawsuit) alleging that the Work or a | ||||||
|  | Contribution incorporated within the Work constitutes direct or contributory | ||||||
|  | patent infringement, then any patent licenses granted to You under this License | ||||||
|  | for that Work shall terminate as of the date such litigation is filed. | ||||||
|  |  | ||||||
|  | #### 4. Redistribution | ||||||
|  |  | ||||||
|  | You may reproduce and distribute copies of the Work or Derivative Works thereof | ||||||
|  | in any medium, with or without modifications, and in Source or Object form, | ||||||
|  | provided that You meet the following conditions: | ||||||
|  |  | ||||||
|  | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of | ||||||
|  | this License; and | ||||||
|  | * **(b)** You must cause any modified files to carry prominent notices stating that You | ||||||
|  | changed the files; and | ||||||
|  | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, | ||||||
|  | all copyright, patent, trademark, and attribution notices from the Source form | ||||||
|  | of the Work, excluding those notices that do not pertain to any part of the | ||||||
|  | Derivative Works; and | ||||||
|  | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any | ||||||
|  | Derivative Works that You distribute must include a readable copy of the | ||||||
|  | attribution notices contained within such NOTICE file, excluding those notices | ||||||
|  | that do not pertain to any part of the Derivative Works, in at least one of the | ||||||
|  | following places: within a NOTICE text file distributed as part of the | ||||||
|  | Derivative Works; within the Source form or documentation, if provided along | ||||||
|  | with the Derivative Works; or, within a display generated by the Derivative | ||||||
|  | Works, if and wherever such third-party notices normally appear. The contents of | ||||||
|  | the NOTICE file are for informational purposes only and do not modify the | ||||||
|  | License. You may add Your own attribution notices within Derivative Works that | ||||||
|  | You distribute, alongside or as an addendum to the NOTICE text from the Work, | ||||||
|  | provided that such additional attribution notices cannot be construed as | ||||||
|  | modifying the License. | ||||||
|  |  | ||||||
|  | You may add Your own copyright statement to Your modifications and may provide | ||||||
|  | additional or different license terms and conditions for use, reproduction, or | ||||||
|  | distribution of Your modifications, or for any such Derivative Works as a whole, | ||||||
|  | provided Your use, reproduction, and distribution of the Work otherwise complies | ||||||
|  | with the conditions stated in this License. | ||||||
|  |  | ||||||
|  | #### 5. Submission of Contributions | ||||||
|  |  | ||||||
|  | Unless You explicitly state otherwise, any Contribution intentionally submitted | ||||||
|  | for inclusion in the Work by You to the Licensor shall be under the terms and | ||||||
|  | conditions of this License, without any additional terms or conditions. | ||||||
|  | Notwithstanding the above, nothing herein shall supersede or modify the terms of | ||||||
|  | any separate license agreement you may have executed with Licensor regarding | ||||||
|  | such Contributions. | ||||||
|  |  | ||||||
|  | #### 6. Trademarks | ||||||
|  |  | ||||||
|  | This License does not grant permission to use the trade names, trademarks, | ||||||
|  | service marks, or product names of the Licensor, except as required for | ||||||
|  | reasonable and customary use in describing the origin of the Work and | ||||||
|  | reproducing the content of the NOTICE file. | ||||||
|  |  | ||||||
|  | #### 7. Disclaimer of Warranty | ||||||
|  |  | ||||||
|  | Unless required by applicable law or agreed to in writing, Licensor provides the | ||||||
|  | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, | ||||||
|  | including, without limitation, any warranties or conditions of TITLE, | ||||||
|  | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are | ||||||
|  | solely responsible for determining the appropriateness of using or | ||||||
|  | redistributing the Work and assume any risks associated with Your exercise of | ||||||
|  | permissions under this License. | ||||||
|  |  | ||||||
|  | #### 8. Limitation of Liability | ||||||
|  |  | ||||||
|  | In no event and under no legal theory, whether in tort (including negligence), | ||||||
|  | contract, or otherwise, unless required by applicable law (such as deliberate | ||||||
|  | and grossly negligent acts) or agreed to in writing, shall any Contributor be | ||||||
|  | liable to You for damages, including any direct, indirect, special, incidental, | ||||||
|  | or consequential damages of any character arising as a result of this License or | ||||||
|  | out of the use or inability to use the Work (including but not limited to | ||||||
|  | damages for loss of goodwill, work stoppage, computer failure or malfunction, or | ||||||
|  | any and all other commercial damages or losses), even if such Contributor has | ||||||
|  | been advised of the possibility of such damages. | ||||||
|  |  | ||||||
|  | #### 9. Accepting Warranty or Additional Liability | ||||||
|  |  | ||||||
|  | While redistributing the Work or Derivative Works thereof, You may choose to | ||||||
|  | offer, and charge a fee for, acceptance of support, warranty, indemnity, or | ||||||
|  | other liability obligations and/or rights consistent with this License. However, | ||||||
|  | in accepting such obligations, You may act only on Your own behalf and on Your | ||||||
|  | sole responsibility, not on behalf of any other Contributor, and only if You | ||||||
|  | agree to indemnify, defend, and hold each Contributor harmless for any liability | ||||||
|  | incurred by, or claims asserted against, such Contributor by reason of your | ||||||
|  | accepting any such warranty or additional liability. | ||||||
|  |  | ||||||
|  | _END OF TERMS AND CONDITIONS_ | ||||||
|  |  | ||||||
|  | ### APPENDIX: How to apply the Apache License to your work | ||||||
|  |  | ||||||
|  | To apply the Apache License to your work, attach the following boilerplate | ||||||
|  | notice, with the fields enclosed by brackets `[]` replaced with your own | ||||||
|  | identifying information. (Don't include the brackets!) The text should be | ||||||
|  | enclosed in the appropriate comment syntax for the file format. We also | ||||||
|  | recommend that a file or class name and description of purpose be included on | ||||||
|  | the same “printed page” as the copyright notice for easier identification within | ||||||
|  | third-party archives. | ||||||
|  |  | ||||||
|  |     Copyright [yyyy] [name of copyright owner] | ||||||
|  |      | ||||||
|  |     Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |     you may not use this file except in compliance with the License. | ||||||
|  |     You may obtain a copy of the License at | ||||||
|  |      | ||||||
|  |       http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |      | ||||||
|  |     Unless required by applicable law or agreed to in writing, software | ||||||
|  |     distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |     See the License for the specific language governing permissions and | ||||||
|  |     limitations under the License. | ||||||
|  |  | ||||||
							
								
								
									
										87
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | libvirt [](http://godoc.org/github.com/digitalocean/go-libvirt) [](https://travis-ci.org/digitalocean/go-libvirt) [](http://goreportcard.com/report/digitalocean/go-libvirt) | ||||||
|  | ==== | ||||||
|  |  | ||||||
|  | Package `libvirt` provides a pure Go interface for interacting with Libvirt. | ||||||
|  |  | ||||||
|  | Rather than using Libvirt's C bindings, this package makes use of | ||||||
|  | Libvirt's RPC interface, as documented [here](https://libvirt.org/internals/rpc.html). | ||||||
|  | Connections to the libvirt server may be local, or remote. RPC packets are encoded | ||||||
|  | using the XDR standard as defined by [RFC 4506](https://tools.ietf.org/html/rfc4506.html). | ||||||
|  |  | ||||||
|  | This should be considered a work in progress. Most functionaly provided by the C | ||||||
|  | bindings have not yet made their way into this library. [Pull requests are welcome](https://github.com/digitalocean/go-libvirt/blob/master/CONTRIBUTING.md)! | ||||||
|  | The definition of the RPC protocol is in the libvirt source tree under [src/rpc/virnetprotocol.x](https://github.com/libvirt/libvirt/blob/master/src/rpc/virnetprotocol.x). | ||||||
|  |  | ||||||
|  | Warning | ||||||
|  | ------- | ||||||
|  |  | ||||||
|  | All packages contained in this repository should be treated as **pre-production | ||||||
|  | software with an unstable API**. | ||||||
|  |  | ||||||
|  | While these package are reasonably well-tested and have seen some use inside of | ||||||
|  | DigitalOcean, there may be subtle bugs which could cause the packages to act | ||||||
|  | in unexpected ways.  Use at your own risk! | ||||||
|  |  | ||||||
|  | In addition, the API is not considered stable at this time.  If you would like | ||||||
|  | to include package `libvirt` in a project, we highly recommend vendoring it into | ||||||
|  | your project. | ||||||
|  |  | ||||||
|  | Example | ||||||
|  | ------- | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/digitalocean/go-libvirt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	//c, err := net.DialTimeout("tcp", "127.0.0.1:16509", 2*time.Second) | ||||||
|  | 	//c, err := net.DialTimeout("tcp", "192.168.1.12:16509", 2*time.Second) | ||||||
|  | 	c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("failed to dial libvirt: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l := libvirt.New(c) | ||||||
|  | 	if err := l.Connect(); err != nil { | ||||||
|  | 		log.Fatalf("failed to connect: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := l.Version() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("failed to retrieve libvirt version: %v", err) | ||||||
|  | 	} | ||||||
|  | 	fmt.Println("Version:", v) | ||||||
|  |  | ||||||
|  | 	domains, err := l.Domains() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("failed to retrieve domains: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Println("ID\tName\t\tUUID") | ||||||
|  | 	fmt.Printf("--------------------------------------------------------\n") | ||||||
|  | 	for _, d := range domains { | ||||||
|  | 		fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := l.Disconnect(); err != nil { | ||||||
|  | 		log.Fatal("failed to disconnect: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | Version: 1.3.4 | ||||||
|  | ID	Name		UUID | ||||||
|  | -------------------------------------------------------- | ||||||
|  | 1	Test-1		dc329f87d4de47198cfd2e21c6105b01 | ||||||
|  | 2	Test-2		dc229f87d4de47198cfd2e21c6105b01 | ||||||
|  | ``` | ||||||
							
								
								
									
										76
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Package libvirt provides a pure Go interface for Libvirt. | ||||||
|  |  | ||||||
|  | 	Rather than using Libvirt's C bindings, this package makes use of | ||||||
|  | 	Libvirt's RPC interface, as documented here: https://libvirt.org/internals/rpc.html. | ||||||
|  | 	Connections to the libvirt server may be local, or remote. RPC packets are encoded | ||||||
|  | 	using the XDR standard as defined by RFC 4506. | ||||||
|  |  | ||||||
|  | 	This should be considered a work in progress. Most functionaly provided by the C | ||||||
|  | 	bindings have not yet made their way into this library. Pull requests are welcome! | ||||||
|  | 	The definition of the RPC protocol is in the libvirt source tree under src/rpc/virnetprotocol.x. | ||||||
|  |  | ||||||
|  | 	Example usage: | ||||||
|  |  | ||||||
|  | 	package main | ||||||
|  |  | ||||||
|  | 	import ( | ||||||
|  | 		"fmt" | ||||||
|  | 		"log" | ||||||
|  | 		"net" | ||||||
|  | 		"time" | ||||||
|  |  | ||||||
|  | 		"github.com/digitalocean/go-libvirt" | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	func main() { | ||||||
|  | 		//c, err := net.DialTimeout("tcp", "127.0.0.1:16509", 2*time.Second) | ||||||
|  | 		//c, err := net.DialTimeout("tcp", "192.168.1.12:16509", 2*time.Second) | ||||||
|  | 		c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("failed to dial libvirt: %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		l := libvirt.New(c) | ||||||
|  | 		if err := l.Connect(); err != nil { | ||||||
|  | 			log.Fatalf("failed to connect: %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		v, err := l.Version() | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("failed to retrieve libvirt version: %v", err) | ||||||
|  | 		} | ||||||
|  | 		fmt.Println("Version:", v) | ||||||
|  |  | ||||||
|  | 		domains, err := l.Domains() | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("failed to retrieve domains: %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Println("ID\tName\t\tUUID") | ||||||
|  | 		fmt.Printf("--------------------------------------------------------\n") | ||||||
|  | 		for _, d := range domains { | ||||||
|  | 			fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := l.Disconnect(); err != nil { | ||||||
|  | 			log.Fatal("failed to disconnect: %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | package libvirt | ||||||
							
								
								
									
										324
									
								
								libvirt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								libvirt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | |||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | // Package libvirt is a pure Go implementation of the libvirt RPC protocol. | ||||||
|  | // For more information on the protocol, see https://libvirt.org/internals/l.html | ||||||
|  | package libvirt | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/davecgh/go-xdr/xdr2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrEventsNotSupported is returned by Events() if event streams | ||||||
|  | // are unsupported by either QEMU or libvirt. | ||||||
|  | var ErrEventsNotSupported = errors.New("event monitor is not supported") | ||||||
|  |  | ||||||
|  | // Libvirt implements LibVirt's remote procedure call protocol. | ||||||
|  | type Libvirt struct { | ||||||
|  | 	conn net.Conn | ||||||
|  | 	r    *bufio.Reader | ||||||
|  | 	w    *bufio.Writer | ||||||
|  |  | ||||||
|  | 	// method callbacks | ||||||
|  | 	cm        sync.Mutex | ||||||
|  | 	callbacks map[uint32]chan response | ||||||
|  |  | ||||||
|  | 	// event listeners | ||||||
|  | 	em     sync.Mutex | ||||||
|  | 	events map[uint32]chan *DomainEvent | ||||||
|  |  | ||||||
|  | 	// next request serial number | ||||||
|  | 	s uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Domain represents a domain as seen by libvirt. | ||||||
|  | type Domain struct { | ||||||
|  | 	Name string | ||||||
|  | 	UUID [uuidSize]byte | ||||||
|  | 	ID   int | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DomainEvent represents a libvirt domain event. | ||||||
|  | type DomainEvent struct { | ||||||
|  | 	CallbackID   uint32 | ||||||
|  | 	Domain       Domain | ||||||
|  | 	Event        string | ||||||
|  | 	Seconds      uint64 | ||||||
|  | 	Microseconds uint32 | ||||||
|  | 	Padding      uint8 | ||||||
|  | 	Details      []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Connect establishes communication with the libvirt server. | ||||||
|  | // The underlying libvirt socket connection must be previously established. | ||||||
|  | func (l *Libvirt) Connect() error { | ||||||
|  | 	return l.connect() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Disconnect shuts down communication with the libvirt server | ||||||
|  | // and closes the underlying net.Conn. | ||||||
|  | func (l *Libvirt) Disconnect() error { | ||||||
|  | 	// close event streams | ||||||
|  | 	for id := range l.events { | ||||||
|  | 		if err := l.removeStream(id); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// inform libvirt we're done | ||||||
|  | 	if err := l.disconnect(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return l.conn.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Domains returns a list of all domains managed by libvirt. | ||||||
|  | func (l *Libvirt) Domains() ([]Domain, error) { | ||||||
|  | 	// these are the flags as passed by `virsh`, defined in: | ||||||
|  | 	// src/remote/remote_protocol.x # remote_connect_list_all_domains_args | ||||||
|  | 	req := struct { | ||||||
|  | 		NeedResults uint32 | ||||||
|  | 		Flags       uint32 | ||||||
|  | 	}{ | ||||||
|  | 		NeedResults: 1, | ||||||
|  | 		Flags:       3, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf, err := encode(&req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := l.request(procConnectListAllDomains, programRemote, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r := <-resp | ||||||
|  | 	if r.Status != StatusOK { | ||||||
|  | 		return nil, decodeError(r.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result := struct { | ||||||
|  | 		Domains []Domain | ||||||
|  | 		Count   uint32 | ||||||
|  | 	}{} | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(r.Payload)) | ||||||
|  | 	_, err = dec.Decode(&result) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result.Domains, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Events streams domain events. | ||||||
|  | // If a problem is encountered setting up the event monitor connection | ||||||
|  | // an error will be returned. Errors encountered during streaming will | ||||||
|  | // cause the returned event channel to be closed. | ||||||
|  | func (l *Libvirt) Events(dom string) (<-chan DomainEvent, error) { | ||||||
|  | 	d, err := l.lookup(dom) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	payload := struct { | ||||||
|  | 		Padding [4]byte | ||||||
|  | 		Domain  Domain | ||||||
|  | 		Event   [2]byte | ||||||
|  | 		Flags   [2]byte | ||||||
|  | 	}{ | ||||||
|  | 		Padding: [4]byte{0x0, 0x0, 0x1, 0x0}, | ||||||
|  | 		Domain:  *d, | ||||||
|  | 		Event:   [2]byte{0x0, 0x0}, | ||||||
|  | 		Flags:   [2]byte{0x0, 0x0}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf, err := encode(&payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := l.request(qemuConnectDomainMonitorEventRegister, programQEMU, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := <-resp | ||||||
|  | 	if res.Status != StatusOK { | ||||||
|  | 		err := decodeError(res.Payload) | ||||||
|  | 		if err == ErrUnsupported { | ||||||
|  | 			return nil, ErrEventsNotSupported | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil, decodeError(res.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(res.Payload)) | ||||||
|  |  | ||||||
|  | 	cbID, _, err := dec.DecodeUint() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	stream := make(chan *DomainEvent) | ||||||
|  | 	l.addStream(cbID, stream) | ||||||
|  | 	c := make(chan DomainEvent) | ||||||
|  | 	go func() { | ||||||
|  | 		// process events | ||||||
|  | 		for e := range stream { | ||||||
|  | 			c <- *e | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Run executes the given QAPI command against a domain's QEMU instance. | ||||||
|  | // For a list of available QAPI commands, see: | ||||||
|  | //	http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD | ||||||
|  | func (l *Libvirt) Run(dom string, cmd []byte) ([]byte, error) { | ||||||
|  | 	d, err := l.lookup(dom) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	payload := struct { | ||||||
|  | 		Domain  Domain | ||||||
|  | 		Command []byte | ||||||
|  | 		Flags   uint32 | ||||||
|  | 	}{ | ||||||
|  | 		Domain:  *d, | ||||||
|  | 		Command: cmd, | ||||||
|  | 		Flags:   0, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf, err := encode(&payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := l.request(qemuDomainMonitor, programQEMU, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := <-resp | ||||||
|  | 	if res.Status != StatusOK { | ||||||
|  | 		return nil, decodeError(res.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r := bytes.NewReader(res.Payload) | ||||||
|  | 	dec := xdr.NewDecoder(r) | ||||||
|  | 	data, _, err := dec.DecodeFixedOpaque(int32(r.Len())) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// drop QMP control characters from start of line, and drop | ||||||
|  | 	// any trailing NULL characters from the end | ||||||
|  | 	return bytes.TrimRight(data[4:], "\x00"), err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Version returns the version of the libvirt daemon. | ||||||
|  | func (l *Libvirt) Version() (string, error) { | ||||||
|  | 	resp, err := l.request(procConnectGetLibVersion, programRemote, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r := <-resp | ||||||
|  | 	if r.Status != StatusOK { | ||||||
|  | 		return "", decodeError(r.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result := struct { | ||||||
|  | 		Version uint64 | ||||||
|  | 	}{} | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(r.Payload)) | ||||||
|  | 	_, err = dec.Decode(&result) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// The version is provided as an int following this formula: | ||||||
|  | 	// version * 1,000,000 + minor * 1000 + micro | ||||||
|  | 	// See src/libvirt-host.c # virConnectGetLibVersion | ||||||
|  | 	major := result.Version / 1000000 | ||||||
|  | 	result.Version %= 1000000 | ||||||
|  | 	minor := result.Version / 1000 | ||||||
|  | 	result.Version %= 1000 | ||||||
|  | 	micro := result.Version | ||||||
|  |  | ||||||
|  | 	versionString := fmt.Sprintf("%d.%d.%d", major, minor, micro) | ||||||
|  | 	return versionString, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // lookup returns a domain as seen by libvirt. | ||||||
|  | func (l *Libvirt) lookup(name string) (*Domain, error) { | ||||||
|  | 	payload := struct { | ||||||
|  | 		Name string | ||||||
|  | 	}{name} | ||||||
|  |  | ||||||
|  | 	buf, err := encode(&payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := l.request(procDomainLookupByName, programRemote, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r := <-resp | ||||||
|  | 	if r.Status != StatusOK { | ||||||
|  | 		return nil, decodeError(r.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(r.Payload)) | ||||||
|  |  | ||||||
|  | 	var d Domain | ||||||
|  | 	_, err = dec.Decode(&d) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &d, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New configures a new Libvirt RPC connection. | ||||||
|  | func New(conn net.Conn) *Libvirt { | ||||||
|  | 	l := &Libvirt{ | ||||||
|  | 		conn:      conn, | ||||||
|  | 		s:         0, | ||||||
|  | 		r:         bufio.NewReader(conn), | ||||||
|  | 		w:         bufio.NewWriter(conn), | ||||||
|  | 		callbacks: make(map[uint32]chan response), | ||||||
|  | 		events:    make(map[uint32]chan *DomainEvent), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go l.listen() | ||||||
|  |  | ||||||
|  | 	return l | ||||||
|  | } | ||||||
							
								
								
									
										162
									
								
								libvirt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								libvirt_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | |||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | package libvirt | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestConnect(t *testing.T) { | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  |  | ||||||
|  | 	err := l.Connect() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDisconnect(t *testing.T) { | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  |  | ||||||
|  | 	err := l.Disconnect() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDomains(t *testing.T) { | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  |  | ||||||
|  | 	domains, err := l.Domains() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wantLen := 2 | ||||||
|  | 	gotLen := len(domains) | ||||||
|  | 	if gotLen != wantLen { | ||||||
|  | 		t.Errorf("expected %d domains to be returned, got %d", wantLen, gotLen) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, d := range domains { | ||||||
|  | 		wantID := i + 1 | ||||||
|  | 		if d.ID != wantID { | ||||||
|  | 			t.Errorf("expected domain ID %q, got %q", wantID, d.ID) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		wantName := fmt.Sprintf("aaaaaaa-%d", i+1) | ||||||
|  | 		if d.Name != wantName { | ||||||
|  | 			t.Errorf("expected domain name %q, got %q", wantName, d.Name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEvents(t *testing.T) { | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  | 	done := make(chan struct{}) | ||||||
|  |  | ||||||
|  | 	stream, err := l.Events("test") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		var e DomainEvent | ||||||
|  | 		select { | ||||||
|  | 		case e = <-stream: | ||||||
|  | 		case <-time.After(time.Second * 5): | ||||||
|  | 			t.Error("expected event, received timeout") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		result := struct { | ||||||
|  | 			Device string `json:"device"` | ||||||
|  | 			Len    int    `json:"len"` | ||||||
|  | 			Offset int    `json:"offset"` | ||||||
|  | 			Speed  int    `json:"speed"` | ||||||
|  | 			Type   string `json:"type"` | ||||||
|  | 		}{} | ||||||
|  |  | ||||||
|  | 		if err := json.Unmarshal(e.Details, &result); err != nil { | ||||||
|  | 			t.Error(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		expected := "drive-ide0-0-0" | ||||||
|  | 		if result.Device != expected { | ||||||
|  | 			t.Errorf("expected device %q, got %q", expected, result.Device) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		done <- struct{}{} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// send an event to the listener goroutine | ||||||
|  | 	conn.test.Write(append(testEventHeader, testEvent...)) | ||||||
|  |  | ||||||
|  | 	// wait for completion | ||||||
|  | 	<-done | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRun(t *testing.T) { | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  |  | ||||||
|  | 	res, err := l.Run("test", []byte(`{"query-version"}`)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type version struct { | ||||||
|  | 		Return struct { | ||||||
|  | 			Package string `json:"package"` | ||||||
|  | 			QEMU    struct { | ||||||
|  | 				Major int `json:"major"` | ||||||
|  | 				Micro int `json:"micro"` | ||||||
|  | 				Minor int `json:"minor"` | ||||||
|  | 			} `json:"qemu"` | ||||||
|  | 		} `json:"return"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var v version | ||||||
|  | 	err = json.Unmarshal(res, &v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expected := 2 | ||||||
|  | 	if v.Return.QEMU.Major != expected { | ||||||
|  | 		t.Errorf("expected qemu major version %d, got %d", expected, v.Return.QEMU.Major) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestVersion(t *testing.T) { | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  |  | ||||||
|  | 	version, err := l.Version() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expected := "1.3.4" | ||||||
|  | 	if version != expected { | ||||||
|  | 		t.Errorf("expected version %q, got %q", expected, version) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										241
									
								
								mock_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								mock_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | |||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | package libvirt | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"net" | ||||||
|  | 	"sync/atomic" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var testDomainResponse = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x38, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x17, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  |  | ||||||
|  | 	// domain name ("test") | ||||||
|  | 	0x00, 0x00, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, | ||||||
|  |  | ||||||
|  | 	// uuid (dc229f87d4de47198cfd2e21c6105b01) | ||||||
|  | 	0xdc, 0x22, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, | ||||||
|  | 	0x8c, 0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, | ||||||
|  |  | ||||||
|  | 	// domain id (14) | ||||||
|  | 	0x00, 0x00, 0x00, 0x0e, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testRegisterEvent = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x20, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x87, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x04, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // callback id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testDeregisterEvent = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x1c, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x87, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x05, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testAuthReply = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x1c, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x42, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testConnectReply = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x1c, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testDisconnectReply = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x1c, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x02, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testRunReply = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x74, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x87, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  |  | ||||||
|  | 	// {"return":{"qemu":{"micro":1,"minor":5,"major":2},"package":""},"id":"libvirt-53"} | ||||||
|  | 	0x00, 0x00, 0x00, 0x52, 0x7b, 0x22, 0x72, 0x65, | ||||||
|  | 	0x74, 0x75, 0x72, 0x6e, 0x22, 0x3a, 0x7b, 0x22, | ||||||
|  | 	0x71, 0x65, 0x6d, 0x75, 0x22, 0x3a, 0x7b, 0x22, | ||||||
|  | 	0x6d, 0x69, 0x63, 0x72, 0x6f, 0x22, 0x3a, 0x31, | ||||||
|  | 	0x2c, 0x22, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x22, | ||||||
|  | 	0x3a, 0x35, 0x2c, 0x22, 0x6d, 0x61, 0x6a, 0x6f, | ||||||
|  | 	0x72, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x70, | ||||||
|  | 	0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x22, 0x3a, | ||||||
|  | 	0x22, 0x22, 0x7d, 0x2c, 0x22, 0x69, 0x64, 0x22, | ||||||
|  | 	0x3a, 0x22, 0x6c, 0x69, 0x62, 0x76, 0x69, 0x72, | ||||||
|  | 	0x74, 0x2d, 0x35, 0x33, 0x22, 0x7d, | ||||||
|  |  | ||||||
|  | 	// All trailing NULL characters should be removed | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testDomainsReply = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x6c, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x01, 0x11, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  |  | ||||||
|  | 	// struct of domains | ||||||
|  | 	0x00, 0x00, 0x00, 0x02, | ||||||
|  |  | ||||||
|  | 	// first domain | ||||||
|  | 	// name - aaaaaaa-1 | ||||||
|  | 	0x00, 0x00, 0x00, 0x09, 0x61, 0x61, 0x61, 0x61, | ||||||
|  | 	0x61, 0x61, 0x61, 0x2d, 0x31, 0x00, 0x00, 0x00, | ||||||
|  | 	// uuid - dc:32:9f:87:d4:de:47:19:8c:fd:2e:21:c6:10:5b:01 | ||||||
|  | 	0xdc, 0x32, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, | ||||||
|  | 	0x8c, 0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, | ||||||
|  | 	// id | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, | ||||||
|  |  | ||||||
|  | 	// second domain | ||||||
|  | 	// name - aaaaaaa-2 | ||||||
|  | 	0x00, 0x00, 0x00, 0x09, 0x61, 0x61, 0x61, 0x61, | ||||||
|  | 	0x61, 0x61, 0x61, 0x2d, 0x32, 0x00, 0x00, 0x00, | ||||||
|  | 	// uuid - dc:22:9f:87:d4:de:47:19:8c:fd:2e:21:c6:10:5b:01 | ||||||
|  | 	0xdc, 0x22, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, 0x8c, | ||||||
|  | 	0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, 0x00, 0x00, | ||||||
|  | 	// id | ||||||
|  | 	0x00, 0x02, 0x00, | ||||||
|  |  | ||||||
|  | 	// count of domains returned | ||||||
|  | 	0x00, 0x00, 0x02, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testVersionReply = []byte{ | ||||||
|  | 	0x00, 0x00, 0x00, 0x24, // length | ||||||
|  | 	0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 	0x00, 0x00, 0x00, 0x9d, // procedure | ||||||
|  | 	0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | 	0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0xfc, // version (1003004) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type mockLibvirt struct { | ||||||
|  | 	net.Conn | ||||||
|  | 	test   net.Conn | ||||||
|  | 	serial uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func setupTest() *mockLibvirt { | ||||||
|  | 	serv, conn := net.Pipe() | ||||||
|  |  | ||||||
|  | 	m := &mockLibvirt{ | ||||||
|  | 		Conn: conn, | ||||||
|  | 		test: serv, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go m.handle(serv) | ||||||
|  |  | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockLibvirt) handle(conn net.Conn) { | ||||||
|  | 	for { | ||||||
|  | 		buf := make([]byte, packetLengthSize+headerSize) | ||||||
|  | 		conn.Read(buf) | ||||||
|  |  | ||||||
|  | 		// extract program | ||||||
|  | 		prog := binary.BigEndian.Uint32(buf[4:8]) | ||||||
|  |  | ||||||
|  | 		// extract procedure | ||||||
|  | 		proc := binary.BigEndian.Uint32(buf[12:16]) | ||||||
|  |  | ||||||
|  | 		switch prog { | ||||||
|  | 		case programRemote: | ||||||
|  | 			m.handleRemote(proc, conn) | ||||||
|  | 		case programQEMU: | ||||||
|  | 			m.handleQEMU(proc, conn) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockLibvirt) handleRemote(procedure uint32, conn net.Conn) { | ||||||
|  | 	switch procedure { | ||||||
|  | 	case procAuthList: | ||||||
|  | 		conn.Write(m.reply(testAuthReply)) | ||||||
|  | 	case procConnectOpen: | ||||||
|  | 		conn.Write(m.reply(testConnectReply)) | ||||||
|  | 	case procConnectClose: | ||||||
|  | 		conn.Write(m.reply(testDisconnectReply)) | ||||||
|  | 	case procConnectGetLibVersion: | ||||||
|  | 		conn.Write(m.reply(testVersionReply)) | ||||||
|  | 	case procDomainLookupByName: | ||||||
|  | 		conn.Write(m.reply(testDomainResponse)) | ||||||
|  | 	case procConnectListAllDomains: | ||||||
|  | 		conn.Write(m.reply(testDomainsReply)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockLibvirt) handleQEMU(procedure uint32, conn net.Conn) { | ||||||
|  | 	switch procedure { | ||||||
|  | 	case qemuConnectDomainMonitorEventRegister: | ||||||
|  | 		conn.Write(m.reply(testRegisterEvent)) | ||||||
|  | 	case qemuConnectDomainMonitorEventDeregister: | ||||||
|  | 		conn.Write(m.reply(testDeregisterEvent)) | ||||||
|  | 	case qemuDomainMonitor: | ||||||
|  | 		conn.Write(m.reply(testRunReply)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // reply automatically injects the correct serial | ||||||
|  | // number into the provided response buffer. | ||||||
|  | func (m *mockLibvirt) reply(buf []byte) []byte { | ||||||
|  | 	atomic.AddUint32(&m.serial, 1) | ||||||
|  | 	binary.BigEndian.PutUint32(buf[20:24], m.serial) | ||||||
|  |  | ||||||
|  | 	return buf | ||||||
|  | } | ||||||
							
								
								
									
										482
									
								
								rpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										482
									
								
								rpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,482 @@ | |||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | package libvirt | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync/atomic" | ||||||
|  |  | ||||||
|  | 	"github.com/davecgh/go-xdr/xdr2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrUnsupported is returned if a procedure is not supported by libvirt | ||||||
|  | var ErrUnsupported = errors.New("unsupported procedure requested") | ||||||
|  |  | ||||||
|  | // request and response types | ||||||
|  | const ( | ||||||
|  | 	// Call is used when making calls to the remote server. | ||||||
|  | 	Call = iota | ||||||
|  |  | ||||||
|  | 	// Reply indicates a server reply. | ||||||
|  | 	Reply | ||||||
|  |  | ||||||
|  | 	// Message is an asynchronous notification. | ||||||
|  | 	Message | ||||||
|  |  | ||||||
|  | 	// Stream represents a stream data packet. | ||||||
|  | 	Stream | ||||||
|  |  | ||||||
|  | 	// CallWithFDs is used by a client to indicate the request has | ||||||
|  | 	// arguments with file descriptors. | ||||||
|  | 	CallWithFDs | ||||||
|  |  | ||||||
|  | 	// ReplyWithFDs is used by a server to indicate the request has | ||||||
|  | 	// arguments with file descriptors. | ||||||
|  | 	ReplyWithFDs | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // request and response statuses | ||||||
|  | const ( | ||||||
|  | 	// StatusOK is always set for method calls or events. | ||||||
|  | 	// For replies it indicates successful completion of the method. | ||||||
|  | 	// For streams it indicates confirmation of the end of file on the stream. | ||||||
|  | 	StatusOK = iota | ||||||
|  |  | ||||||
|  | 	// StatusError for replies indicates that the method call failed | ||||||
|  | 	// and error information is being returned. For streams this indicates | ||||||
|  | 	// that not all data was sent and the stream has aborted. | ||||||
|  | 	StatusError | ||||||
|  |  | ||||||
|  | 	// StatusContinue is only used for streams. | ||||||
|  | 	// This indicates that further data packets will be following. | ||||||
|  | 	StatusContinue | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // magic program numbers | ||||||
|  | // see: https://libvirt.org/git/?p=libvirt.git;a=blob_plain;f=src/remote/remote_protocol.x;hb=HEAD | ||||||
|  | const ( | ||||||
|  | 	programVersion   = 1 | ||||||
|  | 	programRemote    = 0x20008086 | ||||||
|  | 	programQEMU      = 0x20008087 | ||||||
|  | 	programKeepAlive = 0x6b656570 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // libvirt procedure identifiers | ||||||
|  | const ( | ||||||
|  | 	procConnectOpen           = 1 | ||||||
|  | 	procConnectClose          = 2 | ||||||
|  | 	procDomainLookupByName    = 23 | ||||||
|  | 	procAuthList              = 66 | ||||||
|  | 	procConnectGetLibVersion  = 157 | ||||||
|  | 	procConnectListAllDomains = 273 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // qemu procedure identifiers | ||||||
|  | const ( | ||||||
|  | 	qemuDomainMonitor                       = 1 | ||||||
|  | 	qemuConnectDomainMonitorEventRegister   = 4 | ||||||
|  | 	qemuConnectDomainMonitorEventDeregister = 5 | ||||||
|  | 	qemuDomainMonitorEvent                  = 6 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// packet length, in bytes. | ||||||
|  | 	packetLengthSize = 4 | ||||||
|  |  | ||||||
|  | 	// packet header, in bytes. | ||||||
|  | 	headerSize = 24 | ||||||
|  |  | ||||||
|  | 	// UUID size, in bytes. | ||||||
|  | 	uuidSize = 16 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // header is a libvirt rpc packet header | ||||||
|  | type header struct { | ||||||
|  | 	// Program identifier | ||||||
|  | 	Program uint32 | ||||||
|  |  | ||||||
|  | 	// Program version | ||||||
|  | 	Version uint32 | ||||||
|  |  | ||||||
|  | 	// Remote procedure identifier | ||||||
|  | 	Procedure uint32 | ||||||
|  |  | ||||||
|  | 	// Call type, e.g., Reply | ||||||
|  | 	Type uint32 | ||||||
|  |  | ||||||
|  | 	// Call serial number | ||||||
|  | 	Serial uint32 | ||||||
|  |  | ||||||
|  | 	// Request status, e.g., StatusOK | ||||||
|  | 	Status uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // packet represents a RPC request or response. | ||||||
|  | type packet struct { | ||||||
|  | 	// Size of packet, in bytes, including length. | ||||||
|  | 	// Len + Header + Payload | ||||||
|  | 	Len    uint32 | ||||||
|  | 	Header header | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // internal rpc response | ||||||
|  | type response struct { | ||||||
|  | 	Payload []byte | ||||||
|  | 	Status  uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // libvirt error response | ||||||
|  | type libvirtError struct { | ||||||
|  | 	Code     uint32 | ||||||
|  | 	DomainID uint32 | ||||||
|  | 	Padding  uint8 | ||||||
|  | 	Message  string | ||||||
|  | 	Level    uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Libvirt) connect() error { | ||||||
|  | 	payload := struct { | ||||||
|  | 		Padding [3]byte | ||||||
|  | 		Name    string | ||||||
|  | 		Flags   uint32 | ||||||
|  | 	}{ | ||||||
|  | 		Padding: [3]byte{0x1, 0x0, 0x0}, | ||||||
|  | 		Name:    "qemu:///system", | ||||||
|  | 		Flags:   0, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf, err := encode(&payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// libvirt requires that we call auth-list prior to connecting, | ||||||
|  | 	// event when no authentication is used. | ||||||
|  | 	resp, err := l.request(procAuthList, programRemote, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r := <-resp | ||||||
|  | 	if r.Status != StatusOK { | ||||||
|  | 		return decodeError(r.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err = l.request(procConnectOpen, programRemote, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r = <-resp | ||||||
|  | 	if r.Status != StatusOK { | ||||||
|  | 		return decodeError(r.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Libvirt) disconnect() error { | ||||||
|  | 	resp, err := l.request(procConnectClose, programRemote, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r := <-resp | ||||||
|  | 	if r.Status != StatusOK { | ||||||
|  | 		return decodeError(r.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // listen processes incoming data and routes | ||||||
|  | // responses to their respective callback handler. | ||||||
|  | func (l *Libvirt) listen() { | ||||||
|  | 	for { | ||||||
|  | 		// response packet length | ||||||
|  | 		length, err := pktlen(l.r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// When the underlying connection EOFs or is closed, stop | ||||||
|  | 			// this goroutine | ||||||
|  | 			if err == io.EOF || strings.Contains(err.Error(), "use of closed network connection") { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// invalid packet | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// response header | ||||||
|  | 		h, err := extractHeader(l.r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// invalid packet | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// payload: packet length minus what was previously read | ||||||
|  | 		size := int(length) - (packetLengthSize + headerSize) | ||||||
|  | 		buf := make([]byte, size) | ||||||
|  | 		for n := 0; n < size; { | ||||||
|  | 			nn, err := l.r.Read(buf) | ||||||
|  | 			if err != nil { | ||||||
|  | 				// invalid packet | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			n += nn | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// route response to caller | ||||||
|  | 		l.route(h, buf) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // callback sends rpc responses to their respective caller. | ||||||
|  | func (l *Libvirt) callback(id uint32, res response) { | ||||||
|  | 	c, ok := l.callbacks[id] | ||||||
|  | 	if ok { | ||||||
|  | 		c <- res | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.deregister(id) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // route sends incoming packets to their listeners. | ||||||
|  | func (l *Libvirt) route(h *header, buf []byte) { | ||||||
|  | 	// route events to their respective listener | ||||||
|  | 	if h.Program == programQEMU && h.Procedure == qemuDomainMonitorEvent { | ||||||
|  | 		l.stream(buf) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// send responses to caller | ||||||
|  | 	res := response{ | ||||||
|  | 		Payload: buf, | ||||||
|  | 		Status:  h.Status, | ||||||
|  | 	} | ||||||
|  | 	l.callback(h.Serial, res) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // serial provides atomic access to the next sequential request serial number. | ||||||
|  | func (l *Libvirt) serial() uint32 { | ||||||
|  | 	return atomic.AddUint32(&l.s, 1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // stream decodes domain events and sends them | ||||||
|  | // to the respective event listener. | ||||||
|  | func (l *Libvirt) stream(buf []byte) { | ||||||
|  | 	e, err := decodeEvent(buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// event was malformed, drop. | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// send to event listener | ||||||
|  | 	l.em.Lock() | ||||||
|  | 	c, ok := l.events[e.CallbackID] | ||||||
|  | 	l.em.Unlock() | ||||||
|  | 	if ok { | ||||||
|  | 		c <- e | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // addStream configures the routing for an event stream. | ||||||
|  | func (l *Libvirt) addStream(id uint32, stream chan *DomainEvent) { | ||||||
|  | 	l.em.Lock() | ||||||
|  | 	l.events[id] = stream | ||||||
|  | 	l.em.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // removeStream notifies the libvirt server to stop sending events | ||||||
|  | // for the provided callback id. Upon successful de-registration the | ||||||
|  | // callback handler is destroyed. | ||||||
|  | func (l *Libvirt) removeStream(id uint32) error { | ||||||
|  | 	close(l.events[id]) | ||||||
|  |  | ||||||
|  | 	payload := struct { | ||||||
|  | 		CallbackID uint32 | ||||||
|  | 	}{ | ||||||
|  | 		CallbackID: id, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf, err := encode(&payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := l.request(qemuConnectDomainMonitorEventDeregister, programQEMU, &buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := <-resp | ||||||
|  | 	if res.Status != StatusOK { | ||||||
|  | 		return decodeError(res.Payload) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.em.Lock() | ||||||
|  | 	delete(l.events, id) | ||||||
|  | 	l.em.Unlock() | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // register configures a method response callback | ||||||
|  | func (l *Libvirt) register(id uint32, c chan response) { | ||||||
|  | 	l.cm.Lock() | ||||||
|  | 	l.callbacks[id] = c | ||||||
|  | 	l.cm.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // deregister destroys a method response callback | ||||||
|  | func (l *Libvirt) deregister(id uint32) { | ||||||
|  | 	l.cm.Lock() | ||||||
|  | 	close(l.callbacks[id]) | ||||||
|  | 	delete(l.callbacks, id) | ||||||
|  | 	l.cm.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // request performs a libvirt RPC request. | ||||||
|  | // The returned channel is used by the caller to receive the asynchronous | ||||||
|  | // call response. The channel is closed once a response has been sent. | ||||||
|  | func (l *Libvirt) request(proc uint32, program uint32, payload *bytes.Buffer) (<-chan response, error) { | ||||||
|  | 	serial := l.serial() | ||||||
|  | 	c := make(chan response) | ||||||
|  |  | ||||||
|  | 	l.register(serial, c) | ||||||
|  |  | ||||||
|  | 	size := packetLengthSize + headerSize | ||||||
|  | 	if payload != nil { | ||||||
|  | 		size += payload.Len() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p := packet{ | ||||||
|  | 		Len: uint32(size), | ||||||
|  | 		Header: header{ | ||||||
|  | 			Program:   program, | ||||||
|  | 			Version:   programVersion, | ||||||
|  | 			Procedure: proc, | ||||||
|  | 			Type:      Call, | ||||||
|  | 			Serial:    serial, | ||||||
|  | 			Status:    StatusOK, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// write header | ||||||
|  | 	err := binary.Write(l.w, binary.BigEndian, p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// write payload | ||||||
|  | 	if payload != nil { | ||||||
|  | 		err = binary.Write(l.w, binary.BigEndian, payload.Bytes()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := l.w.Flush(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // encode XDR encodes the provided data. | ||||||
|  | func encode(data interface{}) (bytes.Buffer, error) { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	_, err := xdr.Marshal(&buf, data) | ||||||
|  |  | ||||||
|  | 	return buf, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // decodeError extracts an error message from the provider buffer. | ||||||
|  | func decodeError(buf []byte) error { | ||||||
|  | 	var e libvirtError | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(buf)) | ||||||
|  | 	_, err := dec.Decode(&e) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if strings.Contains(e.Message, "unknown procedure") { | ||||||
|  | 		return ErrUnsupported | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return errors.New(e.Message) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // decodeEvent extracts an event from the given byte slice. | ||||||
|  | // Errors encountered will be returned along with a nil event. | ||||||
|  | func decodeEvent(buf []byte) (*DomainEvent, error) { | ||||||
|  | 	var e DomainEvent | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(buf)) | ||||||
|  | 	_, err := dec.Decode(&e) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &e, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // pktlen determines the length of an incoming rpc response. | ||||||
|  | // If an error is encountered reading the provided Reader, the | ||||||
|  | // error is returned and response length will be 0. | ||||||
|  | func pktlen(r io.Reader) (uint32, error) { | ||||||
|  | 	buf := make([]byte, packetLengthSize) | ||||||
|  |  | ||||||
|  | 	for n := 0; n < cap(buf); { | ||||||
|  | 		nn, err := r.Read(buf) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		n += nn | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return binary.BigEndian.Uint32(buf), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // extractHeader returns the decoded header from an incoming response. | ||||||
|  | func extractHeader(r io.Reader) (*header, error) { | ||||||
|  | 	buf := make([]byte, headerSize) | ||||||
|  |  | ||||||
|  | 	for n := 0; n < cap(buf); { | ||||||
|  | 		nn, err := r.Read(buf) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		n += nn | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h := &header{ | ||||||
|  | 		Program:   binary.BigEndian.Uint32(buf[0:4]), | ||||||
|  | 		Version:   binary.BigEndian.Uint32(buf[4:8]), | ||||||
|  | 		Procedure: binary.BigEndian.Uint32(buf[8:12]), | ||||||
|  | 		Type:      binary.BigEndian.Uint32(buf[12:16]), | ||||||
|  | 		Serial:    binary.BigEndian.Uint32(buf[16:20]), | ||||||
|  | 		Status:    binary.BigEndian.Uint32(buf[20:24]), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return h, nil | ||||||
|  | } | ||||||
							
								
								
									
										348
									
								
								rpc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								rpc_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,348 @@ | |||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | package libvirt | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"sync" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/davecgh/go-xdr/xdr2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	// dc229f87d4de47198cfd2e21c6105b01 | ||||||
|  | 	testUUID = [uuidSize]byte{ | ||||||
|  | 		0xdc, 0x22, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, | ||||||
|  | 		0x8c, 0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testHeader = []byte{ | ||||||
|  | 		0x20, 0x00, 0x80, 0x86, // program | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, // procedure | ||||||
|  | 		0x00, 0x00, 0x00, 0x00, // type | ||||||
|  | 		0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 		0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testEventHeader = []byte{ | ||||||
|  | 		0x00, 0x00, 0x00, 0xb0, // length | ||||||
|  | 		0x20, 0x00, 0x80, 0x87, // program | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, // version | ||||||
|  | 		0x00, 0x00, 0x00, 0x06, // procedure | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, // type | ||||||
|  | 		0x00, 0x00, 0x00, 0x00, // serial | ||||||
|  | 		0x00, 0x00, 0x00, 0x00, // status | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testEvent = []byte{ | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, // callback id | ||||||
|  |  | ||||||
|  | 		// domain name ("test") | ||||||
|  | 		0x00, 0x00, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, | ||||||
|  |  | ||||||
|  | 		// uuid (dc229f87d4de47198cfd2e21c6105b01) | ||||||
|  | 		0xdc, 0x22, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, | ||||||
|  | 		0x8c, 0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, | ||||||
|  |  | ||||||
|  | 		// domain id (14) | ||||||
|  | 		0x00, 0x00, 0x00, 0x0e, | ||||||
|  |  | ||||||
|  | 		// event name (BLOCK_JOB_COMPLETED) | ||||||
|  | 		0x00, 0x00, 0x00, 0x13, 0x42, 0x4c, 0x4f, 0x43, | ||||||
|  | 		0x4b, 0x5f, 0x4a, 0x4f, 0x42, 0x5f, 0x43, 0x4f, | ||||||
|  | 		0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x00, | ||||||
|  |  | ||||||
|  | 		// seconds (1462211891) | ||||||
|  | 		0x00, 0x00, 0x00, 0x00, 0x57, 0x27, 0x95, 0x33, | ||||||
|  |  | ||||||
|  | 		// microseconds (931791) | ||||||
|  | 		0x00, 0x0e, 0x37, 0xcf, | ||||||
|  |  | ||||||
|  | 		// event json data | ||||||
|  | 		// ({"device":"drive-ide0-0-0","len":0,"offset":0,"speed":0,"type":"commit"}) | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, | ||||||
|  | 		0x7b, 0x22, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, | ||||||
|  | 		0x22, 0x3a, 0x22, 0x64, 0x72, 0x69, 0x76, 0x65, | ||||||
|  | 		0x2d, 0x69, 0x64, 0x65, 0x30, 0x2d, 0x30, 0x2d, | ||||||
|  | 		0x30, 0x22, 0x2c, 0x22, 0x6c, 0x65, 0x6e, 0x22, | ||||||
|  | 		0x3a, 0x30, 0x2c, 0x22, 0x6f, 0x66, 0x66, 0x73, | ||||||
|  | 		0x65, 0x74, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, | ||||||
|  | 		0x70, 0x65, 0x65, 0x64, 0x22, 0x3a, 0x30, 0x2c, | ||||||
|  | 		0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, | ||||||
|  | 		0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0x7d, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testErrorMessage = []byte{ | ||||||
|  | 		0x00, 0x00, 0x00, 0x37, // code | ||||||
|  | 		0x00, 0x00, 0x00, 0x0a, // domain id | ||||||
|  |  | ||||||
|  | 		// message ("Requested operation is not valid: domain is not running") | ||||||
|  | 		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, | ||||||
|  | 		0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, | ||||||
|  | 		0x64, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, | ||||||
|  | 		0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x6e, | ||||||
|  | 		0x6f, 0x74, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, | ||||||
|  | 		0x3a, 0x20, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, | ||||||
|  | 		0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, | ||||||
|  | 		0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x00, | ||||||
|  |  | ||||||
|  | 		// error level | ||||||
|  | 		0x00, 0x00, 0x00, 0x02, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testDomain = Domain{ | ||||||
|  | 		Name: "test-domain", | ||||||
|  | 		UUID: testUUID, | ||||||
|  | 		ID:   1, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestExtractHeader(t *testing.T) { | ||||||
|  | 	r := bytes.NewBuffer(testHeader) | ||||||
|  | 	h, err := extractHeader(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if h.Program != programRemote { | ||||||
|  | 		t.Errorf("expected Program %q, got %q", programRemote, h.Program) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if h.Version != programVersion { | ||||||
|  | 		t.Errorf("expected version %q, got %q", programVersion, h.Version) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if h.Procedure != procConnectOpen { | ||||||
|  | 		t.Errorf("expected procedure %q, got %q", procConnectOpen, h.Procedure) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if h.Type != Call { | ||||||
|  | 		t.Errorf("expected type %q, got %q", Call, h.Type) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if h.Status != StatusOK { | ||||||
|  | 		t.Errorf("expected status %q, got %q", StatusOK, h.Status) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPktLen(t *testing.T) { | ||||||
|  | 	data := []byte{0x00, 0x00, 0x00, 0xa} // uint32:10 | ||||||
|  | 	r := bytes.NewBuffer(data) | ||||||
|  |  | ||||||
|  | 	expected := uint32(10) | ||||||
|  | 	actual, err := pktlen(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if expected != actual { | ||||||
|  | 		t.Errorf("expected packet length %q, got %q", expected, actual) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeEvent(t *testing.T) { | ||||||
|  | 	e, err := decodeEvent(testEvent) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expCbID := uint32(1) | ||||||
|  | 	if e.CallbackID != expCbID { | ||||||
|  | 		t.Errorf("expected callback id %d, got %d", expCbID, e.CallbackID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expName := "test" | ||||||
|  | 	if e.Domain.Name != expName { | ||||||
|  | 		t.Errorf("expected domain %s, got %s", expName, e.Domain.Name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expUUID := testUUID | ||||||
|  | 	if !bytes.Equal(e.Domain.UUID[:], expUUID[:]) { | ||||||
|  | 		t.Errorf("expected uuid:\t%x, got\n\t\t\t%x", expUUID, e.Domain.UUID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expID := 14 | ||||||
|  | 	if e.Domain.ID != expID { | ||||||
|  | 		t.Errorf("expected id %d, got %d", expID, e.Domain.ID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expEvent := "BLOCK_JOB_COMPLETED" | ||||||
|  | 	if e.Event != expEvent { | ||||||
|  | 		t.Errorf("expected %s, got %s", expEvent, e.Event) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expSec := uint64(1462211891) | ||||||
|  | 	if e.Seconds != expSec { | ||||||
|  | 		t.Errorf("expected seconds to be %d, got %d", expSec, e.Seconds) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expMs := uint32(931791) | ||||||
|  | 	if e.Microseconds != expMs { | ||||||
|  | 		t.Errorf("expected microseconds to be %d, got %d", expMs, e.Microseconds) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expDetails := []byte(`{"device":"drive-ide0-0-0","len":0,"offset":0,"speed":0,"type":"commit"}`) | ||||||
|  | 	if e.Domain.ID != expID { | ||||||
|  | 		t.Errorf("expected data %s, got %s", expDetails, e.Details) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeError(t *testing.T) { | ||||||
|  | 	expected := "Requested operation is not valid: domain is not running" | ||||||
|  |  | ||||||
|  | 	err := decodeError(testErrorMessage) | ||||||
|  | 	if err.Error() != expected { | ||||||
|  | 		t.Errorf("expected error %s, got %s", expected, err.Error()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEncode(t *testing.T) { | ||||||
|  | 	data := "test" | ||||||
|  | 	buf, err := encode(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dec := xdr.NewDecoder(bytes.NewReader(buf.Bytes())) | ||||||
|  | 	res, _, err := dec.DecodeString() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if res != data { | ||||||
|  | 		t.Errorf("expected %s, got %s", data, res) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRegister(t *testing.T) { | ||||||
|  | 	l := &Libvirt{} | ||||||
|  | 	l.callbacks = make(map[uint32]chan response) | ||||||
|  | 	id := uint32(1) | ||||||
|  | 	c := make(chan response) | ||||||
|  |  | ||||||
|  | 	l.register(id, c) | ||||||
|  | 	if _, ok := l.callbacks[id]; !ok { | ||||||
|  | 		t.Error("expected callback to register") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDeregister(t *testing.T) { | ||||||
|  | 	id := uint32(1) | ||||||
|  |  | ||||||
|  | 	l := &Libvirt{} | ||||||
|  | 	l.callbacks = map[uint32]chan response{ | ||||||
|  | 		id: make(chan response), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.deregister(id) | ||||||
|  | 	if _, ok := l.callbacks[id]; ok { | ||||||
|  | 		t.Error("expected callback to deregister") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAddStream(t *testing.T) { | ||||||
|  | 	id := uint32(1) | ||||||
|  | 	c := make(chan *DomainEvent) | ||||||
|  |  | ||||||
|  | 	l := &Libvirt{} | ||||||
|  | 	l.events = make(map[uint32]chan *DomainEvent) | ||||||
|  |  | ||||||
|  | 	l.addStream(id, c) | ||||||
|  | 	if _, ok := l.events[id]; !ok { | ||||||
|  | 		t.Error("expected event stream to exist") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRemoveStream(t *testing.T) { | ||||||
|  | 	id := uint32(1) | ||||||
|  |  | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  | 	l.events[id] = make(chan *DomainEvent) | ||||||
|  |  | ||||||
|  | 	err := l.removeStream(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, ok := l.events[id]; ok { | ||||||
|  | 		t.Error("expected event stream to be removed") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStream(t *testing.T) { | ||||||
|  | 	id := uint32(1) | ||||||
|  | 	c := make(chan *DomainEvent, 1) | ||||||
|  |  | ||||||
|  | 	l := &Libvirt{} | ||||||
|  | 	l.events = map[uint32]chan *DomainEvent{ | ||||||
|  | 		id: c, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.stream(testEvent) | ||||||
|  | 	e := <-c | ||||||
|  |  | ||||||
|  | 	if e.Event != "BLOCK_JOB_COMPLETED" { | ||||||
|  | 		t.Error("expected event") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSerial(t *testing.T) { | ||||||
|  | 	count := uint32(10) | ||||||
|  | 	l := &Libvirt{} | ||||||
|  |  | ||||||
|  | 	var wg sync.WaitGroup | ||||||
|  | 	for i := 0; i < 10; i++ { | ||||||
|  | 		wg.Add(1) | ||||||
|  | 		go func() { | ||||||
|  | 			l.serial() | ||||||
|  | 			wg.Done() | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg.Wait() | ||||||
|  |  | ||||||
|  | 	expected := count + uint32(1) | ||||||
|  | 	actual := l.serial() | ||||||
|  | 	if expected != actual { | ||||||
|  | 		t.Errorf("expected serial to be %d, got %d", expected, actual) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestLookup(t *testing.T) { | ||||||
|  | 	id := uint32(1) | ||||||
|  | 	c := make(chan response) | ||||||
|  | 	name := "test" | ||||||
|  |  | ||||||
|  | 	conn := setupTest() | ||||||
|  | 	l := New(conn) | ||||||
|  |  | ||||||
|  | 	l.register(id, c) | ||||||
|  |  | ||||||
|  | 	d, err := l.lookup(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if d == nil { | ||||||
|  | 		t.Error("nil domain returned") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if d.Name != name { | ||||||
|  | 		t.Errorf("expected domain %s, got %s", name, d.Name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								scripts/golint.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								scripts/golint.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # Verify that all files are correctly golint'd. | ||||||
|  | EXIT=0 | ||||||
|  | GOLINT=$(golint ./...) | ||||||
|  |  | ||||||
|  | if [[ ! -z $GOLINT ]]; then | ||||||
|  | 	echo $GOLINT | ||||||
|  | 	EXIT=1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | exit $EXIT | ||||||
							
								
								
									
										34
									
								
								scripts/licensecheck.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								scripts/licensecheck.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # Verify that the correct license block is present in all Go source | ||||||
|  | # files. | ||||||
|  | read -r -d '' EXPECTED <<EndOfLicense | ||||||
|  | // Copyright 2016 The go-libvirt Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  | EndOfLicense | ||||||
|  |  | ||||||
|  | # Scan each Go source file for license. | ||||||
|  | EXIT=0 | ||||||
|  | GOFILES=$(find . -name "*.go") | ||||||
|  |  | ||||||
|  | for FILE in $GOFILES; do | ||||||
|  | 	BLOCK=$(head -n 14 $FILE) | ||||||
|  |  | ||||||
|  | 	if [ "$BLOCK" != "$EXPECTED" ]; then | ||||||
|  | 		echo "file missing license: $FILE" | ||||||
|  | 		EXIT=1 | ||||||
|  | 	fi | ||||||
|  | done | ||||||
|  |  | ||||||
|  | exit $EXIT | ||||||
		Reference in New Issue
	
	Block a user