Initial Commit

This commit is contained in:
Ben LeMasurier 2016-05-19 18:05:37 -06:00
commit 2ccd33a8df
13 changed files with 2013 additions and 0 deletions

13
.travis.yml Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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