diff options
Diffstat (limited to 'vendor/github.com/digitalocean/go-qemu/qmp/socket.go')
-rw-r--r-- | vendor/github.com/digitalocean/go-qemu/qmp/socket.go | 274 |
1 files changed, 274 insertions, 0 deletions
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 +} |