diff options
Diffstat (limited to 'vendor/github.com/digitalocean/go-qemu/qmp')
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/README.md | 104 | ||||
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/qmp.go | 94 | ||||
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/rpc.go | 107 | ||||
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/socket.go | 274 | ||||
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go | 27 | ||||
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go | 25 |
6 files changed, 631 insertions, 0 deletions
diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/README.md b/vendor/github.com/digitalocean/go-qemu/qmp/README.md new file mode 100644 index 000000000..729280b31 --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/README.md @@ -0,0 +1,104 @@ +QMP +=== + +Package `qmp` enables interaction with QEMU instances via the QEMU Machine Protocol (QMP). + +## Available Drivers + +### Libvirt + +If your environment is managed by Libvirt, QMP interaction must be proxied through the Libvirt daemon. This can be be done through two available drivers: + +#### RPC + +The RPC driver provides a pure Go implementation of Libvirt's RPC protocol. + +```go +//conn, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second) +conn, err := net.DialTimeout("tcp", "192.168.1.1:16509", 2*time.Second) +monitor := libvirtrpc.New("stage-lb-1", conn) +``` + +#### virsh + +A connection to the monitor socket is provided by proxing requests through the `virsh` executable. + +```go +monitor, err := qmp.NewLibvirtMonitor("qemu:///system", "stage-lb-1") +``` + +### Socket + +If your QEMU instances are not managed by libvirt, direct communication over its UNIX socket is available. + +```go +monitor, err := qmp.NewSocketMonitor("unix", "/var/lib/qemu/example.monitor", 2*time.Second) +``` + +## Examples + +Using the above to establish a new `qmp.Monitor`, the following examples provide a brief overview of QMP usage. + +_error checking omitted for the sake of brevity._ + +### Command Execution +```go +type StatusResult struct { + ID string `json:"id"` + Return struct { + Running bool `json:"running"` + Singlestep bool `json:"singlestep"` + Status string `json:"status"` + } `json:"return"` +} + +monitor.Connect() +defer monitor.Disconnect() + +cmd := []byte(`{ "execute": "query-status" }`) +raw, _ := monitor.Run(cmd) + +var result StatusResult +json.Unmarshal(raw, &result) + +fmt.Println(result.Return.Status) +``` + +``` +running +``` + +### Event Monitor + +```go +monitor.Connect() +defer monitor.Disconnect() + +stream, _ := monitor.Events() +for e := range stream { + log.Printf("EVENT: %s", e.Event) +} + +``` + +``` +$ virsh reboot example +Domain example is being rebooted +``` + +``` +EVENT: POWERDOWN +EVENT: SHUTDOWN +EVENT: STOP +EVENT: RESET +EVENT: RESUME +EVENT: RESET +... +``` + +## More information + +* [QEMU QMP Wiki](http://wiki.qemu.org/QMP) +* [QEMU QMP Intro](http://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/qmp-intro.txt;hb=HEAD) +* [QEMU QMP Events](http://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/qmp-events.txt;hb=HEAD) +* [QEMU QMP Spec](http://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/qmp-spec.txt;hb=HEAD) diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/qmp.go b/vendor/github.com/digitalocean/go-qemu/qmp/qmp.go new file mode 100644 index 000000000..b856260d3 --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/qmp.go @@ -0,0 +1,94 @@ +// Copyright 2016 The go-qemu 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 qmp enables interaction with QEMU instances +// via the QEMU Machine Protocol (QMP). +package qmp + +import ( + "context" + "errors" + "fmt" +) + +// ErrEventsNotSupported is returned by Events() if event streams +// are unsupported by either QEMU or libvirt. +var ErrEventsNotSupported = errors.New("event monitor is not supported") + +// Monitor represents a QEMU Machine Protocol socket. +// See: http://wiki.qemu.org/QMP +type Monitor interface { + Connect() error + Disconnect() error + Run(command []byte) (out []byte, err error) + Events(context.Context) (events <-chan Event, err error) +} + +// Command represents a QMP command. +type Command struct { + // Name of the command to run + Execute string `json:"execute"` + + // Optional arguments for the above command. + Args interface{} `json:"arguments,omitempty"` +} + +type response struct { + ID string `json:"id"` + Return interface{} `json:"return,omitempty"` + Error struct { + Class string `json:"class"` + Desc string `json:"desc"` + } `json:"error,omitempty"` +} + +func (r *response) Err() error { + if r.Error.Desc == "" { + return nil + } + + return errors.New(r.Error.Desc) +} + +// Event represents a QEMU QMP event. +// See http://wiki.qemu.org/QMP +type Event struct { + // Event name, e.g., BLOCK_JOB_COMPLETE + Event string `json:"event"` + + // Arbitrary event data + Data map[string]interface{} `json:"data"` + + // Event timestamp, provided by QEMU. + Timestamp struct { + Seconds int64 `json:"seconds"` + Microseconds int64 `json:"microseconds"` + } `json:"timestamp"` +} + +// Version is the QEMU version structure returned when a QMP connection is +// initiated. +type Version struct { + Package string `json:"package"` + QEMU struct { + Major int `json:"major"` + Micro int `json:"micro"` + Minor int `json:"minor"` + } `json:"qemu"` +} + +func (v Version) String() string { + q := v.QEMU + return fmt.Sprintf("%d.%d.%d", q.Major, q.Minor, q.Micro) +} diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/rpc.go b/vendor/github.com/digitalocean/go-qemu/qmp/rpc.go new file mode 100644 index 000000000..cb11c71fe --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/rpc.go @@ -0,0 +1,107 @@ +// Copyright 2016 The go-qemu 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 qmp + +import ( + "context" + "encoding/json" + "net" + + "github.com/digitalocean/go-libvirt" +) + +var _ Monitor = &LibvirtRPCMonitor{} + +// A LibvirtRPCMonitor implements LibVirt's remote procedure call protocol. +type LibvirtRPCMonitor struct { + l *libvirt.Libvirt + // Domain name as seen by libvirt, e.g., stage-lb-1 + Domain string +} + +// NewLibvirtRPCMonitor configures a new Libvirt RPC Monitor connection. +// The provided domain should be the name of the domain as seen +// by libvirt, e.g., stage-lb-1. +func NewLibvirtRPCMonitor(domain string, conn net.Conn) *LibvirtRPCMonitor { + l := libvirt.New(conn) + + return &LibvirtRPCMonitor{ + l: l, + Domain: domain, + } +} + +// Connect establishes communication with the libvirt server. +// The underlying libvirt socket connection must be previously established. +func (rpc *LibvirtRPCMonitor) Connect() error { + return rpc.l.Connect() +} + +// Disconnect shuts down communication with the libvirt server +// and closes the underlying net.Conn. +func (rpc *LibvirtRPCMonitor) Disconnect() error { + return rpc.l.Disconnect() +} + +// Events streams QEMU QMP Events until the provided context is cancelled. +// 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 (rpc *LibvirtRPCMonitor) Events(ctx context.Context) (<-chan Event, error) { + events, err := rpc.l.SubscribeQEMUEvents(ctx, rpc.Domain) + if err != nil { + return nil, err + } + + c := make(chan Event) + go func() { + // process events + for e := range events { + qe, err := qmpEvent(&e) + if err != nil { + close(c) + break + } + + c <- *qe + } + }() + + 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 (rpc *LibvirtRPCMonitor) Run(cmd []byte) ([]byte, error) { + return rpc.l.Run(rpc.Domain, cmd) +} + +// qmpEvent takes a libvirt DomainEvent and returns the QMP equivalent. +func qmpEvent(e *libvirt.DomainEvent) (*Event, error) { + var qe Event + + if e.Details != nil { + if err := json.Unmarshal(e.Details, &qe.Data); err != nil { + return nil, err + } + } + + qe.Event = e.Event + qe.Timestamp.Seconds = int64(e.Seconds) + qe.Timestamp.Microseconds = int64(e.Microseconds) + + return &qe, nil +} diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/socket.go b/vendor/github.com/digitalocean/go-qemu/qmp/socket.go new file mode 100644 index 000000000..541a88676 --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/socket.go @@ -0,0 +1,274 @@ +// Copyright 2016 The go-qemu 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 qmp + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net" + "os" + "sync" + "sync/atomic" + "time" +) + +// A SocketMonitor is a Monitor which speaks directly to a QEMU Machine Protocol +// (QMP) socket. Communication is performed directly using a QEMU monitor socket, +// typically using a UNIX socket or TCP connection. Multiple connections to the +// same domain are not permitted, and will result in the monitor blocking until +// the existing connection is closed. +type SocketMonitor struct { + // QEMU version reported by a connected monitor socket. + Version *Version + + // QEMU QMP capabiltiies reported by a connected monitor socket. + Capabilities []string + + // Underlying connection + c net.Conn + + // Serialize running command against domain + mu sync.Mutex + + // Send command responses and errors + stream <-chan streamResponse + + // Send domain events to listeners when available + listeners *int32 + events <-chan Event +} + +// NewSocketMonitor configures a connection to the provided QEMU monitor socket. +// An error is returned if the socket cannot be successfully dialed, or the +// dial attempt times out. +// +// NewSocketMonitor may dial the QEMU socket using a variety of connection types: +// NewSocketMonitor("unix", "/var/lib/qemu/example.monitor", 2 * time.Second) +// NewSocketMonitor("tcp", "8.8.8.8:4444", 2 * time.Second) +func NewSocketMonitor(network, addr string, timeout time.Duration) (*SocketMonitor, error) { + c, err := net.DialTimeout(network, addr, timeout) + if err != nil { + return nil, err + } + + mon := &SocketMonitor{ + c: c, + listeners: new(int32), + } + + return mon, nil +} + +// Listen creates a new SocketMonitor listening for a single connection to the provided socket file or address. +// An error is returned if unable to listen at the specified file path or port. +// +// Listen will wait for a QEMU socket connection using a variety connection types: +// Listen("unix", "/var/lib/qemu/example.monitor") +// Listen("tcp", "0.0.0.0:4444") +func Listen(network, addr string) (*SocketMonitor, error) { + l, err := net.Listen(network, addr) + if err != nil { + return nil, err + } + c, err := l.Accept() + defer l.Close() + if err != nil { + return nil, err + } + + mon := &SocketMonitor{ + c: c, + listeners: new(int32), + } + + return mon, nil +} + +// Disconnect closes the QEMU monitor socket connection. +func (mon *SocketMonitor) Disconnect() error { + atomic.StoreInt32(mon.listeners, 0) + err := mon.c.Close() + + return err +} + +// qmpCapabilities is the command which must be executed to perform the +// QEMU QMP handshake. +const qmpCapabilities = "qmp_capabilities" + +// Connect sets up a QEMU QMP connection by connecting directly to the QEMU +// monitor socket. An error is returned if the capabilities handshake does +// not succeed. +func (mon *SocketMonitor) Connect() error { + enc := json.NewEncoder(mon.c) + dec := json.NewDecoder(mon.c) + + // Check for banner on startup + var ban banner + if err := dec.Decode(&ban); err != nil { + return err + } + mon.Version = &ban.QMP.Version + mon.Capabilities = ban.QMP.Capabilities + + // Issue capabilities handshake + cmd := Command{Execute: qmpCapabilities} + if err := enc.Encode(cmd); err != nil { + return err + } + + // Check for no error on return + var r response + if err := dec.Decode(&r); err != nil { + return err + } + if err := r.Err(); err != nil { + return err + } + + // Initialize socket listener for command responses and asynchronous + // events + events := make(chan Event) + stream := make(chan streamResponse) + go mon.listen(mon.c, events, stream) + + mon.events = events + mon.stream = stream + + return nil +} + +// Events streams QEMU QMP Events. +// Events should only be called once per Socket. If used with a qemu.Domain, +// qemu.Domain.Events should be called to retrieve events instead. +func (mon *SocketMonitor) Events(context.Context) (<-chan Event, error) { + atomic.AddInt32(mon.listeners, 1) + return mon.events, nil +} + +// listen listens for incoming data from a QEMU monitor socket. It determines +// if the data is an asynchronous event or a response to a command, and returns +// the data on the appropriate channel. +func (mon *SocketMonitor) listen(r io.Reader, events chan<- Event, stream chan<- streamResponse) { + defer close(events) + defer close(stream) + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + var e Event + + b := scanner.Bytes() + if err := json.Unmarshal(b, &e); err != nil { + continue + } + + // If data does not have an event type, it must be in response to a command. + if e.Event == "" { + stream <- streamResponse{buf: b} + continue + } + + // If nobody is listening for events, do not bother sending them. + if atomic.LoadInt32(mon.listeners) == 0 { + continue + } + + events <- e + } + + if err := scanner.Err(); err != nil { + stream <- streamResponse{err: err} + } +} + +// 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 (mon *SocketMonitor) Run(command []byte) ([]byte, error) { + // Just call RunWithFile with no file + return mon.RunWithFile(command, nil) +} + +// RunWithFile behaves like Run but allows for passing a file through out-of-band data. +func (mon *SocketMonitor) RunWithFile(command []byte, file *os.File) ([]byte, error) { + // Only allow a single command to be run at a time to ensure that responses + // to a command cannot be mixed with responses from another command + mon.mu.Lock() + defer mon.mu.Unlock() + + if file == nil { + // Just send a normal command through. + if _, err := mon.c.Write(command); err != nil { + return nil, err + } + } else { + unixConn, ok := mon.c.(*net.UnixConn) + if !ok { + return nil, fmt.Errorf("RunWithFile only works with unix monitor sockets") + } + + oobSupported := false + for _, capability := range mon.Capabilities { + if capability == "oob" { + oobSupported = true + break + } + } + + if !oobSupported { + return nil, fmt.Errorf("The QEMU server doesn't support oob (needed for RunWithFile)") + } + + // Send the command along with the file descriptor. + oob := getUnixRights(file) + if _, _, err := unixConn.WriteMsgUnix(command, oob, nil); err != nil { + return nil, err + } + } + + // Wait for a response or error to our command + res := <-mon.stream + if res.err != nil { + return nil, res.err + } + + // Check for QEMU errors + var r response + if err := json.Unmarshal(res.buf, &r); err != nil { + return nil, err + } + if err := r.Err(); err != nil { + return nil, err + } + + return res.buf, nil +} + +// banner is a wrapper type around a Version. +type banner struct { + QMP struct { + Capabilities []string `json:"capabilities"` + Version Version `json:"version"` + } `json:"QMP"` +} + +// streamResponse is a struct sent over a channel in response to a command. +type streamResponse struct { + buf []byte + err error +} diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go b/vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go new file mode 100644 index 000000000..859ff64ea --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/socket_unix.go @@ -0,0 +1,27 @@ +// Copyright 2016 The go-qemu 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. + +// +build !windows + +package qmp + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func getUnixRights(file *os.File) []byte { + return unix.UnixRights(int(file.Fd())) +} diff --git a/vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go b/vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go new file mode 100644 index 000000000..b8a611dbf --- /dev/null +++ b/vendor/github.com/digitalocean/go-qemu/qmp/socket_windows.go @@ -0,0 +1,25 @@ +// Copyright 2016 The go-qemu 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. + +// +build windows + +package qmp + +import ( + "os" +) + +func getUnixRights(file *os.File) []byte { + return nil +} |