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…
x
Reference in New Issue
Block a user