Merge pull request #4 from digitalocean/qemu-errors
check for QEMU response errors
This commit is contained in:
		
							
								
								
									
										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,7 +260,11 @@ 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: | ||||||
| 		conn.Write(m.reply(testRunReply)) | 		if m.Fail { | ||||||
|  | 			conn.Write(m.reply(testRunReplyFail)) | ||||||
|  | 		} else { | ||||||
|  | 			conn.Write(m.reply(testRunReply)) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user