Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 1 | # QEMU Monitor Protocol Python class |
| 2 | # |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 3 | # Copyright (C) 2009, 2010 Red Hat Inc. |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 4 | # |
| 5 | # Authors: |
| 6 | # Luiz Capitulino <lcapitulino@redhat.com> |
| 7 | # |
| 8 | # This work is licensed under the terms of the GNU GPL, version 2. See |
| 9 | # the COPYING file in the top-level directory. |
| 10 | |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 11 | import json |
| 12 | import errno |
| 13 | import socket |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 14 | |
| 15 | class QMPError(Exception): |
| 16 | pass |
| 17 | |
| 18 | class QMPConnectError(QMPError): |
| 19 | pass |
| 20 | |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 21 | class QMPCapabilitiesError(QMPError): |
| 22 | pass |
| 23 | |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 24 | class QEMUMonitorProtocol: |
Stefan Hajnoczi | 37628f1 | 2011-05-25 19:48:01 +0100 | [diff] [blame] | 25 | def __init__(self, address, server=False): |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 26 | """ |
| 27 | Create a QEMUMonitorProtocol class. |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 28 | |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 29 | @param address: QEMU address, can be either a unix socket path (string) |
| 30 | or a tuple in the form ( address, port ) for a TCP |
| 31 | connection |
Stefan Hajnoczi | 37628f1 | 2011-05-25 19:48:01 +0100 | [diff] [blame] | 32 | @param server: server mode listens on the socket (bool) |
| 33 | @raise socket.error on socket connection errors |
| 34 | @note No connection is established, this is done by the connect() or |
| 35 | accept() methods |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 36 | """ |
| 37 | self.__events = [] |
| 38 | self.__address = address |
| 39 | self.__sock = self.__get_sock() |
Stefan Hajnoczi | 37628f1 | 2011-05-25 19:48:01 +0100 | [diff] [blame] | 40 | if server: |
| 41 | self.__sock.bind(self.__address) |
| 42 | self.__sock.listen(1) |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 43 | |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 44 | def __get_sock(self): |
| 45 | if isinstance(self.__address, tuple): |
| 46 | family = socket.AF_INET |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 47 | else: |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 48 | family = socket.AF_UNIX |
| 49 | return socket.socket(family, socket.SOCK_STREAM) |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 50 | |
Stefan Hajnoczi | 37628f1 | 2011-05-25 19:48:01 +0100 | [diff] [blame] | 51 | def __negotiate_capabilities(self): |
| 52 | self.__sockfile = self.__sock.makefile() |
| 53 | greeting = self.__json_read() |
| 54 | if greeting is None or not greeting.has_key('QMP'): |
| 55 | raise QMPConnectError |
| 56 | # Greeting seems ok, negotiate capabilities |
| 57 | resp = self.cmd('qmp_capabilities') |
| 58 | if "return" in resp: |
| 59 | return greeting |
| 60 | raise QMPCapabilitiesError |
| 61 | |
Stefan Hajnoczi | 91b8edd | 2011-05-25 19:48:00 +0100 | [diff] [blame] | 62 | def __json_read(self, only_event=False): |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 63 | while True: |
| 64 | data = self.__sockfile.readline() |
| 65 | if not data: |
| 66 | return |
| 67 | resp = json.loads(data) |
| 68 | if 'event' in resp: |
| 69 | self.__events.append(resp) |
Stefan Hajnoczi | 91b8edd | 2011-05-25 19:48:00 +0100 | [diff] [blame] | 70 | if not only_event: |
| 71 | continue |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 72 | return resp |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 73 | |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 74 | error = socket.error |
| 75 | |
| 76 | def connect(self): |
| 77 | """ |
| 78 | Connect to the QMP Monitor and perform capabilities negotiation. |
| 79 | |
| 80 | @return QMP greeting dict |
| 81 | @raise socket.error on socket connection errors |
| 82 | @raise QMPConnectError if the greeting is not received |
| 83 | @raise QMPCapabilitiesError if fails to negotiate capabilities |
| 84 | """ |
| 85 | self.__sock.connect(self.__address) |
Stefan Hajnoczi | 37628f1 | 2011-05-25 19:48:01 +0100 | [diff] [blame] | 86 | return self.__negotiate_capabilities() |
| 87 | |
| 88 | def accept(self): |
| 89 | """ |
| 90 | Await connection from QMP Monitor and perform capabilities negotiation. |
| 91 | |
| 92 | @return QMP greeting dict |
| 93 | @raise socket.error on socket connection errors |
| 94 | @raise QMPConnectError if the greeting is not received |
| 95 | @raise QMPCapabilitiesError if fails to negotiate capabilities |
| 96 | """ |
| 97 | self.__sock, _ = self.__sock.accept() |
| 98 | return self.__negotiate_capabilities() |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 99 | |
| 100 | def cmd_obj(self, qmp_cmd): |
| 101 | """ |
| 102 | Send a QMP command to the QMP Monitor. |
| 103 | |
| 104 | @param qmp_cmd: QMP command to be sent as a Python dict |
| 105 | @return QMP response as a Python dict or None if the connection has |
| 106 | been closed |
| 107 | """ |
| 108 | try: |
| 109 | self.__sock.sendall(json.dumps(qmp_cmd)) |
| 110 | except socket.error, err: |
| 111 | if err[0] == errno.EPIPE: |
| 112 | return |
| 113 | raise socket.error(err) |
| 114 | return self.__json_read() |
| 115 | |
| 116 | def cmd(self, name, args=None, id=None): |
| 117 | """ |
| 118 | Build a QMP command and send it to the QMP Monitor. |
| 119 | |
| 120 | @param name: command name (string) |
| 121 | @param args: command arguments (dict) |
| 122 | @param id: command id (dict, list, string or int) |
| 123 | """ |
| 124 | qmp_cmd = { 'execute': name } |
| 125 | if args: |
| 126 | qmp_cmd['arguments'] = args |
| 127 | if id: |
| 128 | qmp_cmd['id'] = id |
| 129 | return self.cmd_obj(qmp_cmd) |
| 130 | |
Stefan Hajnoczi | 91b8edd | 2011-05-25 19:48:00 +0100 | [diff] [blame] | 131 | def get_events(self, wait=False): |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 132 | """ |
| 133 | Get a list of available QMP events. |
Stefan Hajnoczi | 91b8edd | 2011-05-25 19:48:00 +0100 | [diff] [blame] | 134 | |
| 135 | @param wait: block until an event is available (bool) |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 136 | """ |
| 137 | self.__sock.setblocking(0) |
| 138 | try: |
| 139 | self.__json_read() |
| 140 | except socket.error, err: |
| 141 | if err[0] == errno.EAGAIN: |
| 142 | # No data available |
| 143 | pass |
| 144 | self.__sock.setblocking(1) |
Stefan Hajnoczi | 91b8edd | 2011-05-25 19:48:00 +0100 | [diff] [blame] | 145 | if not self.__events and wait: |
| 146 | self.__json_read(only_event=True) |
Luiz Capitulino | 1d00a07 | 2010-10-27 17:43:34 -0200 | [diff] [blame] | 147 | return self.__events |
| 148 | |
| 149 | def clear_events(self): |
| 150 | """ |
| 151 | Clear current list of pending events. |
| 152 | """ |
| 153 | self.__events = [] |
| 154 | |
| 155 | def close(self): |
| 156 | self.__sock.close() |
| 157 | self.__sockfile.close() |