Initial Commit
This commit is contained in:
commit
2ccd33a8df
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 [![GoDoc](http://godoc.org/github.com/digitalocean/go-libvirt?status.svg)](http://godoc.org/github.com/digitalocean/go-libvirt) [![Build Status](https://travis-ci.org/digitalocean/go-libvirt.svg?branch=master)](https://travis-ci.org/digitalocean/go-libvirt) [![Report Card](http://goreportcard.com/badge/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
|
Loading…
Reference in New Issue
Block a user