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:
parent
beeb8df345
commit
759a8c0337
40
libvirt.go
40
libvirt.go
@ -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{
|
||||||
|
@ -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)
|
||||||
|
@ -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,8 +260,12 @@ 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
|
||||||
|
Loading…
Reference in New Issue
Block a user