check for QEMU response errors

When performing QEMU monitor commands, libvirt will return StatusOK even
when the underlying QEMU process fails to perform the command.

This modifies Run() to check for QEMU errors.

I'm not entirely happy with the hacky modifications to the test library
to handle this scenario. The test framework is in obvious need for a
complete refactor. For now this will have to work.
This commit is contained in:
Ben LeMasurier 2016-07-19 10:27:31 -06:00
parent beeb8df345
commit 759a8c0337
No known key found for this signature in database
GPG Key ID: 248D430AE8E74189
3 changed files with 82 additions and 2 deletions

View File

@ -19,6 +19,7 @@ package libvirt
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -68,6 +69,14 @@ type DomainEvent struct {
Details []byte Details []byte
} }
// qemuError represents a QEMU process error.
type qemuError struct {
Error struct {
Class string `json:"class"`
Description string `json:"desc"`
} `json:"error"`
}
// Connect establishes communication with the libvirt server. // Connect establishes communication with the libvirt server.
// The underlying libvirt socket connection must be previously established. // The underlying libvirt socket connection must be previously established.
func (l *Libvirt) Connect() error { func (l *Libvirt) Connect() error {
@ -225,10 +234,16 @@ func (l *Libvirt) Run(dom string, cmd []byte) ([]byte, error) {
} }
res := <-resp res := <-resp
// check for libvirt errors
if res.Status != StatusOK { if res.Status != StatusOK {
return nil, decodeError(res.Payload) return nil, decodeError(res.Payload)
} }
// check for QEMU process errors
if err = getQEMUError(res); err != nil {
return nil, err
}
r := bytes.NewReader(res.Payload) r := bytes.NewReader(res.Payload)
dec := xdr.NewDecoder(r) dec := xdr.NewDecoder(r)
data, _, err := dec.DecodeFixedOpaque(int32(r.Len())) data, _, err := dec.DecodeFixedOpaque(int32(r.Len()))
@ -238,7 +253,7 @@ func (l *Libvirt) Run(dom string, cmd []byte) ([]byte, error) {
// drop QMP control characters from start of line, and drop // drop QMP control characters from start of line, and drop
// any trailing NULL characters from the end // any trailing NULL characters from the end
return bytes.TrimRight(data[4:], "\x00"), err return bytes.TrimRight(data[4:], "\x00"), nil
} }
// Version returns the version of the libvirt daemon. // Version returns the version of the libvirt daemon.
@ -308,6 +323,29 @@ func (l *Libvirt) lookup(name string) (*Domain, error) {
return &d, nil return &d, nil
} }
// getQEMUError checks the provided response for QEMU process errors.
// If an error is found, it is extracted an returned, otherwise nil.
func getQEMUError(r response) error {
pl := bytes.NewReader(r.Payload)
dec := xdr.NewDecoder(pl)
s, _, err := dec.DecodeString()
if err != nil {
return err
}
var e qemuError
if err = json.Unmarshal([]byte(s), &e); err != nil {
return err
}
if e.Error.Description != "" {
return errors.New(e.Error.Description)
}
return nil
}
// New configures a new Libvirt RPC connection. // New configures a new Libvirt RPC connection.
func New(conn net.Conn) *Libvirt { func New(conn net.Conn) *Libvirt {
l := &Libvirt{ l := &Libvirt{

View File

@ -148,6 +148,17 @@ func TestRun(t *testing.T) {
} }
} }
func TestRunFail(t *testing.T) {
conn := libvirttest.New()
conn.Fail = true
l := New(conn)
_, err := l.Run("test", []byte(`{"drive-foo"}`))
if err == nil {
t.Error("expected qemu error")
}
}
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
conn := libvirttest.New() conn := libvirttest.New()
l := New(conn) l := New(conn)

View File

@ -120,6 +120,32 @@ var testRunReply = []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} }
var testRunReplyFail = []byte{
0x00, 0x00, 0x00, 0x8c, // length
0x20, 0x00, 0x80, 0x87, // program
0x00, 0x00, 0x00, 0x01, // version
0x00, 0x00, 0x00, 0x01, // procedure
0x00, 0x00, 0x00, 0x01, // type
0x00, 0x00, 0x00, 0x0a, // serial
0x00, 0x00, 0x00, 0x00, // status
// {"id":"libvirt-68","error":{"class":"CommandNotFound","desc":"The command drive-foo has not been found"}}`
0x00, 0x00, 0x00, 0x69, 0x7b, 0x22, 0x69, 0x64,
0x22, 0x3a, 0x22, 0x6c, 0x69, 0x62, 0x76, 0x69,
0x72, 0x74, 0x2d, 0x36, 0x38, 0x22, 0x2c, 0x22,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x7b,
0x22, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, 0x3a,
0x22, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64,
0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x22,
0x3a, 0x22, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x72,
0x69, 0x76, 0x65, 0x2d, 0x66, 0x6f, 0x6f, 0x20,
0x68, 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20,
0x62, 0x65, 0x65, 0x6e, 0x20, 0x66, 0x6f, 0x75,
0x6e, 0x64, 0x22, 0x7d, 0x7d, 0x00, 0x00, 0x00,
}
var testDomainsReply = []byte{ var testDomainsReply = []byte{
0x00, 0x00, 0x00, 0x6c, // length 0x00, 0x00, 0x00, 0x6c, // length
0x20, 0x00, 0x80, 0x86, // program 0x20, 0x00, 0x80, 0x86, // program
@ -171,6 +197,7 @@ var testVersionReply = []byte{
type MockLibvirt struct { type MockLibvirt struct {
net.Conn net.Conn
Test net.Conn Test net.Conn
Fail bool
serial uint32 serial uint32
} }
@ -233,9 +260,13 @@ func (m *MockLibvirt) handleQEMU(procedure uint32, conn net.Conn) {
case constants.QEMUConnectDomainMonitorEventDeregister: case constants.QEMUConnectDomainMonitorEventDeregister:
conn.Write(m.reply(testDeregisterEvent)) conn.Write(m.reply(testDeregisterEvent))
case constants.QEMUDomainMonitor: case constants.QEMUDomainMonitor:
if m.Fail {
conn.Write(m.reply(testRunReplyFail))
} else {
conn.Write(m.reply(testRunReply)) conn.Write(m.reply(testRunReply))
} }
} }
}
// reply automatically injects the correct serial // reply automatically injects the correct serial
// number into the provided response buffer. // number into the provided response buffer.