]>
Commit | Line | Data |
---|---|---|
cedebdac LC |
1 | # QEMU Monitor Protocol Python class |
2 | # | |
1d00a07d | 3 | # Copyright (C) 2009, 2010 Red Hat Inc. |
cedebdac LC |
4 | # |
5 | # Authors: | |
6 | # Luiz Capitulino <[email protected]> | |
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 | ||
1d00a07d LC |
11 | import json |
12 | import errno | |
13 | import socket | |
cedebdac LC |
14 | |
15 | class QMPError(Exception): | |
16 | pass | |
17 | ||
18 | class QMPConnectError(QMPError): | |
19 | pass | |
20 | ||
1d00a07d LC |
21 | class QMPCapabilitiesError(QMPError): |
22 | pass | |
23 | ||
cedebdac | 24 | class QEMUMonitorProtocol: |
37628f11 | 25 | def __init__(self, address, server=False): |
1d00a07d LC |
26 | """ |
27 | Create a QEMUMonitorProtocol class. | |
28 | ||
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 | |
37628f11 SH |
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 | |
1d00a07d LC |
36 | """ |
37 | self.__events = [] | |
38 | self.__address = address | |
39 | self.__sock = self.__get_sock() | |
37628f11 SH |
40 | if server: |
41 | self.__sock.bind(self.__address) | |
42 | self.__sock.listen(1) | |
1d00a07d LC |
43 | |
44 | def __get_sock(self): | |
45 | if isinstance(self.__address, tuple): | |
46 | family = socket.AF_INET | |
47 | else: | |
48 | family = socket.AF_UNIX | |
49 | return socket.socket(family, socket.SOCK_STREAM) | |
50 | ||
37628f11 SH |
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 | ||
91b8eddf | 62 | def __json_read(self, only_event=False): |
1d00a07d LC |
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) | |
91b8eddf SH |
70 | if not only_event: |
71 | continue | |
1d00a07d LC |
72 | return resp |
73 | ||
74 | error = socket.error | |
75 | ||
cedebdac | 76 | def connect(self): |
1d00a07d LC |
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) | |
37628f11 SH |
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() | |
cedebdac | 99 | |
1d00a07d LC |
100 | def cmd_obj(self, qmp_cmd): |
101 | """ | |
102 | Send a QMP command to the QMP Monitor. | |
cedebdac | 103 | |
1d00a07d LC |
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) | |
cedebdac LC |
114 | return self.__json_read() |
115 | ||
1d00a07d LC |
116 | def cmd(self, name, args=None, id=None): |
117 | """ | |
118 | Build a QMP command and send it to the QMP Monitor. | |
cedebdac | 119 | |
1d00a07d LC |
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 | ||
9f68f7fb AL |
131 | def command(self, cmd, **kwds): |
132 | ret = self.cmd(cmd, kwds) | |
133 | if ret.has_key('error'): | |
134 | raise Exception(ret['error']['desc']) | |
135 | return ret['return'] | |
136 | ||
91b8eddf | 137 | def get_events(self, wait=False): |
1d00a07d LC |
138 | """ |
139 | Get a list of available QMP events. | |
91b8eddf SH |
140 | |
141 | @param wait: block until an event is available (bool) | |
1d00a07d LC |
142 | """ |
143 | self.__sock.setblocking(0) | |
cedebdac | 144 | try: |
1d00a07d LC |
145 | self.__json_read() |
146 | except socket.error, err: | |
147 | if err[0] == errno.EAGAIN: | |
148 | # No data available | |
149 | pass | |
150 | self.__sock.setblocking(1) | |
91b8eddf SH |
151 | if not self.__events and wait: |
152 | self.__json_read(only_event=True) | |
1d00a07d LC |
153 | return self.__events |
154 | ||
155 | def clear_events(self): | |
156 | """ | |
157 | Clear current list of pending events. | |
158 | """ | |
159 | self.__events = [] | |
160 | ||
161 | def close(self): | |
162 | self.__sock.close() | |
163 | self.__sockfile.close() |