1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Qemu monitor control classes
32
33 """
34
35
36 import os
37 import stat
38 import errno
39 import socket
40 import StringIO
41
42 from ganeti import errors
43 from ganeti import utils
44 from ganeti import serializer
48 """QMP command not supported by the monitor.
49
50 This is raised in case a QmpMonitor instance is asked to execute a command
51 not supported by the instance.
52
53 This is a KVM-specific exception, intended to assist in falling back to using
54 the human monitor for operations QMP does not support.
55
56 """
57 pass
58
61 """QEMU Messaging Protocol (QMP) message.
62
63 """
65 """Creates a new QMP message based on the passed data.
66
67 """
68 if not isinstance(data, dict):
69 raise TypeError("QmpMessage must be initialized with a dict")
70
71 self.data = data
72
74 """Get the value of the required field if present, or None.
75
76 Overrides the [] operator to provide access to the message data,
77 returning None if the required item is not in the message
78 @return: the value of the field_name field, or None if field_name
79 is not contained in the message
80
81 """
82 return self.data.get(field_name, None)
83
85 """Set the value of the required field_name to field_value.
86
87 """
88 self.data[field_name] = field_value
89
91 """Return the number of fields stored in this QmpMessage.
92
93 """
94 return len(self.data)
95
97 """Delete the specified element from the QmpMessage.
98
99 """
100 del(self.data[key])
101
102 @staticmethod
104 """Build a QmpMessage from a JSON encoded string.
105
106 @type json_string: str
107 @param json_string: JSON string representing the message
108 @rtype: L{QmpMessage}
109 @return: a L{QmpMessage} built from json_string
110
111 """
112
113 data = serializer.LoadJson(json_string)
114 return QmpMessage(data)
115
119
121
122
123 return self.data == other.data
124
127 _SOCKET_TIMEOUT = 5
128
130 """Instantiates the MonitorSocket object.
131
132 @type monitor_filename: string
133 @param monitor_filename: the filename of the UNIX raw socket on which the
134 monitor (QMP or simple one) is listening
135
136 """
137 self.monitor_filename = monitor_filename
138 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
139
140
141 self.sock.settimeout(self._SOCKET_TIMEOUT)
142 self._connected = False
143
145 sock_stat = None
146 try:
147 sock_stat = os.stat(self.monitor_filename)
148 except EnvironmentError, err:
149 if err.errno == errno.ENOENT:
150 raise errors.HypervisorError("No monitor socket found")
151 else:
152 raise errors.HypervisorError("Error checking monitor socket: %s",
153 utils.ErrnoOrStr(err))
154 if not stat.S_ISSOCK(sock_stat.st_mode):
155 raise errors.HypervisorError("Monitor socket is not a socket")
156
158 """Make sure that the connection is established.
159
160 """
161 if not self._connected:
162 raise errors.ProgrammerError("To use a MonitorSocket you need to first"
163 " invoke connect() on it")
164
166 """Connects to the monitor.
167
168 Connects to the UNIX socket
169
170 @raise errors.HypervisorError: when there are communication errors
171
172 """
173 if self._connected:
174 raise errors.ProgrammerError("Cannot connect twice")
175
176 self._check_socket()
177
178
179 try:
180 self.sock.connect(self.monitor_filename)
181 except EnvironmentError:
182 raise errors.HypervisorError("Can't connect to qmp socket")
183 self._connected = True
184
186 """Closes the socket
187
188 It cannot be used after this call.
189
190 """
191 self.sock.close()
192
195 """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
196
197 """
198 _FIRST_MESSAGE_KEY = "QMP"
199 _EVENT_KEY = "event"
200 _ERROR_KEY = "error"
201 _RETURN_KEY = "return"
202 _ACTUAL_KEY = ACTUAL_KEY = "actual"
203 _ERROR_CLASS_KEY = "class"
204 _ERROR_DESC_KEY = "desc"
205 _EXECUTE_KEY = "execute"
206 _ARGUMENTS_KEY = "arguments"
207 _VERSION_KEY = "version"
208 _PACKAGE_KEY = "package"
209 _QEMU_KEY = "qemu"
210 _CAPABILITIES_COMMAND = "qmp_capabilities"
211 _QUERY_COMMANDS = "query-commands"
212 _MESSAGE_END_TOKEN = "\r\n"
213
215 super(QmpConnection, self).__init__(monitor_filename)
216 self._buf = ""
217 self.supported_commands = None
218
222
223 - def __exit__(self, exc_type, exc_value, tb):
225
227 """Connects to the QMP monitor.
228
229 Connects to the UNIX socket and makes sure that we can actually send and
230 receive data to the kvm instance via QMP.
231
232 @raise errors.HypervisorError: when there are communication errors
233 @raise errors.ProgrammerError: when there are data serialization errors
234
235 """
236 super(QmpConnection, self).connect()
237
238
239 greeting = self._Recv()
240 if not greeting[self._FIRST_MESSAGE_KEY]:
241 self._connected = False
242 raise errors.HypervisorError("kvm: QMP communication error (wrong"
243 " server greeting")
244
245
246
247 version_info = greeting[self._FIRST_MESSAGE_KEY][self._VERSION_KEY]
248
249 self.version = (version_info[self._QEMU_KEY]["major"],
250 version_info[self._QEMU_KEY]["minor"],
251 version_info[self._QEMU_KEY]["micro"])
252 self.package = version_info[self._PACKAGE_KEY].strip()
253
254
255
256 self._buf = ""
257
258
259
260
261 self.Execute(self._CAPABILITIES_COMMAND)
262 self.supported_commands = self._GetSupportedCommands()
263
265 """Extract and parse a QMP message from the given buffer.
266
267 Seeks for a QMP message in the given buf. If found, it parses it and
268 returns it together with the rest of the characters in the buf.
269 If no message is found, returns None and the whole buffer.
270
271 @raise errors.ProgrammerError: when there are data serialization errors
272
273 """
274 message = None
275
276
277 pos = buf.find(self._MESSAGE_END_TOKEN)
278 if pos >= 0:
279 try:
280 message = QmpMessage.BuildFromJsonString(buf[:pos + 1])
281 except Exception, err:
282 raise errors.ProgrammerError("QMP data serialization error: %s" % err)
283 buf = buf[pos + 1:]
284
285 return (message, buf)
286
288 """Receives a message from QMP and decodes the received JSON object.
289
290 @rtype: QmpMessage
291 @return: the received message
292 @raise errors.HypervisorError: when there are communication errors
293 @raise errors.ProgrammerError: when there are data serialization errors
294
295 """
296 self._check_connection()
297
298
299 (message, self._buf) = self._ParseMessage(self._buf)
300 if message:
301 return message
302
303 recv_buffer = StringIO.StringIO(self._buf)
304 recv_buffer.seek(len(self._buf))
305 try:
306 while True:
307 data = self.sock.recv(4096)
308 if not data:
309 break
310 recv_buffer.write(data)
311
312 (message, self._buf) = self._ParseMessage(recv_buffer.getvalue())
313 if message:
314 return message
315
316 except socket.timeout, err:
317 raise errors.HypervisorError("Timeout while receiving a QMP message: "
318 "%s" % (err))
319 except socket.error, err:
320 raise errors.HypervisorError("Unable to receive data from KVM using the"
321 " QMP protocol: %s" % err)
322
323 - def _Send(self, message):
324 """Encodes and sends a message to KVM using QMP.
325
326 @type message: QmpMessage
327 @param message: message to send to KVM
328 @raise errors.HypervisorError: when there are communication errors
329 @raise errors.ProgrammerError: when there are data serialization errors
330
331 """
332 self._check_connection()
333 try:
334 message_str = str(message)
335 except Exception, err:
336 raise errors.ProgrammerError("QMP data deserialization error: %s" % err)
337
338 try:
339 self.sock.sendall(message_str)
340 except socket.timeout, err:
341 raise errors.HypervisorError("Timeout while sending a QMP message: "
342 "%s (%s)" % (err.string, err.errno))
343 except socket.error, err:
344 raise errors.HypervisorError("Unable to send data from KVM using the"
345 " QMP protocol: %s" % err)
346
348 """Update the list of supported commands.
349
350 """
351 result = self.Execute(self._QUERY_COMMANDS)
352 return frozenset(com["name"] for com in result)
353
354 - def Execute(self, command, arguments=None):
355 """Executes a QMP command and returns the response of the server.
356
357 @type command: str
358 @param command: the command to execute
359 @type arguments: dict
360 @param arguments: dictionary of arguments to be passed to the command
361 @rtype: dict
362 @return: dictionary representing the received JSON object
363 @raise errors.HypervisorError: when there are communication errors
364 @raise errors.ProgrammerError: when there are data serialization errors
365
366 """
367 self._check_connection()
368
369
370
371 if (self.supported_commands is not None and
372 command not in self.supported_commands):
373 raise QmpCommandNotSupported("Instance does not support the '%s'"
374 " QMP command." % command)
375
376 message = QmpMessage({self._EXECUTE_KEY: command})
377 if arguments:
378 message[self._ARGUMENTS_KEY] = arguments
379 self._Send(message)
380
381
382
383
384
385
386 while True:
387 response = self._Recv()
388 err = response[self._ERROR_KEY]
389 if err:
390 raise errors.HypervisorError("kvm: error executing the %s"
391 " command: %s (%s):" %
392 (command,
393 err[self._ERROR_DESC_KEY],
394 err[self._ERROR_CLASS_KEY]))
395
396 elif response[self._EVENT_KEY]:
397
398 continue
399
400 return response[self._RETURN_KEY]
401