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