root / lib / hypervisor / hv_kvm / monitor.py @ 13bb1b4c
History | View | Annotate | Download (12.8 kB)
1 |
#
|
---|---|
2 |
#
|
3 |
|
4 |
# Copyright (C) 2014 Google Inc.
|
5 |
# All rights reserved.
|
6 |
#
|
7 |
# Redistribution and use in source and binary forms, with or without
|
8 |
# modification, are permitted provided that the following conditions are
|
9 |
# met:
|
10 |
#
|
11 |
# 1. Redistributions of source code must retain the above copyright notice,
|
12 |
# this list of conditions and the following disclaimer.
|
13 |
#
|
14 |
# 2. Redistributions in binary form must reproduce the above copyright
|
15 |
# notice, this list of conditions and the following disclaimer in the
|
16 |
# documentation and/or other materials provided with the distribution.
|
17 |
#
|
18 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
19 |
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
20 |
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
21 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
22 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
23 |
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
24 |
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
25 |
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
26 |
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
27 |
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
28 |
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29 |
|
30 |
|
31 |
"""Qemu monitor control classes
|
32 |
|
33 |
"""
|
34 |
|
35 |
import os |
36 |
import stat |
37 |
import errno |
38 |
import socket |
39 |
import StringIO |
40 |
|
41 |
from ganeti import errors |
42 |
from ganeti import utils |
43 |
from ganeti import serializer |
44 |
|
45 |
|
46 |
class QmpCommandNotSupported(errors.HypervisorError): |
47 |
"""QMP command not supported by the monitor.
|
48 |
|
49 |
This is raised in case a QmpMonitor instance is asked to execute a command
|
50 |
not supported by the instance.
|
51 |
|
52 |
This is a KVM-specific exception, intended to assist in falling back to using
|
53 |
the human monitor for operations QMP does not support.
|
54 |
|
55 |
"""
|
56 |
pass
|
57 |
|
58 |
|
59 |
class QmpMessage(object): |
60 |
"""QEMU Messaging Protocol (QMP) message.
|
61 |
|
62 |
"""
|
63 |
def __init__(self, data): |
64 |
"""Creates a new QMP message based on the passed data.
|
65 |
|
66 |
"""
|
67 |
if not isinstance(data, dict): |
68 |
raise TypeError("QmpMessage must be initialized with a dict") |
69 |
|
70 |
self.data = data
|
71 |
|
72 |
def __getitem__(self, field_name): |
73 |
"""Get the value of the required field if present, or None.
|
74 |
|
75 |
Overrides the [] operator to provide access to the message data,
|
76 |
returning None if the required item is not in the message
|
77 |
@return: the value of the field_name field, or None if field_name
|
78 |
is not contained in the message
|
79 |
|
80 |
"""
|
81 |
return self.data.get(field_name, None) |
82 |
|
83 |
def __setitem__(self, field_name, field_value): |
84 |
"""Set the value of the required field_name to field_value.
|
85 |
|
86 |
"""
|
87 |
self.data[field_name] = field_value
|
88 |
|
89 |
def __len__(self): |
90 |
"""Return the number of fields stored in this QmpMessage.
|
91 |
|
92 |
"""
|
93 |
return len(self.data) |
94 |
|
95 |
def __delitem__(self, key): |
96 |
"""Delete the specified element from the QmpMessage.
|
97 |
|
98 |
"""
|
99 |
del(self.data[key]) |
100 |
|
101 |
@staticmethod
|
102 |
def BuildFromJsonString(json_string): |
103 |
"""Build a QmpMessage from a JSON encoded string.
|
104 |
|
105 |
@type json_string: str
|
106 |
@param json_string: JSON string representing the message
|
107 |
@rtype: L{QmpMessage}
|
108 |
@return: a L{QmpMessage} built from json_string
|
109 |
|
110 |
"""
|
111 |
# Parse the string
|
112 |
data = serializer.LoadJson(json_string) |
113 |
return QmpMessage(data)
|
114 |
|
115 |
def __str__(self): |
116 |
# The protocol expects the JSON object to be sent as a single line.
|
117 |
return serializer.DumpJson(self.data) |
118 |
|
119 |
def __eq__(self, other): |
120 |
# When comparing two QmpMessages, we are interested in comparing
|
121 |
# their internal representation of the message data
|
122 |
return self.data == other.data |
123 |
|
124 |
|
125 |
class MonitorSocket(object): |
126 |
_SOCKET_TIMEOUT = 5
|
127 |
|
128 |
def __init__(self, monitor_filename): |
129 |
"""Instantiates the MonitorSocket object.
|
130 |
|
131 |
@type monitor_filename: string
|
132 |
@param monitor_filename: the filename of the UNIX raw socket on which the
|
133 |
monitor (QMP or simple one) is listening
|
134 |
|
135 |
"""
|
136 |
self.monitor_filename = monitor_filename
|
137 |
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
138 |
# We want to fail if the server doesn't send a complete message
|
139 |
# in a reasonable amount of time
|
140 |
self.sock.settimeout(self._SOCKET_TIMEOUT) |
141 |
self._connected = False |
142 |
|
143 |
def _check_socket(self): |
144 |
sock_stat = None
|
145 |
try:
|
146 |
sock_stat = os.stat(self.monitor_filename)
|
147 |
except EnvironmentError, err: |
148 |
if err.errno == errno.ENOENT:
|
149 |
raise errors.HypervisorError("No monitor socket found") |
150 |
else:
|
151 |
raise errors.HypervisorError("Error checking monitor socket: %s", |
152 |
utils.ErrnoOrStr(err)) |
153 |
if not stat.S_ISSOCK(sock_stat.st_mode): |
154 |
raise errors.HypervisorError("Monitor socket is not a socket") |
155 |
|
156 |
def _check_connection(self): |
157 |
"""Make sure that the connection is established.
|
158 |
|
159 |
"""
|
160 |
if not self._connected: |
161 |
raise errors.ProgrammerError("To use a MonitorSocket you need to first" |
162 |
" invoke connect() on it")
|
163 |
|
164 |
def connect(self): |
165 |
"""Connects to the monitor.
|
166 |
|
167 |
Connects to the UNIX socket
|
168 |
|
169 |
@raise errors.HypervisorError: when there are communication errors
|
170 |
|
171 |
"""
|
172 |
if self._connected: |
173 |
raise errors.ProgrammerError("Cannot connect twice") |
174 |
|
175 |
self._check_socket()
|
176 |
|
177 |
# Check file existance/stuff
|
178 |
try:
|
179 |
self.sock.connect(self.monitor_filename) |
180 |
except EnvironmentError: |
181 |
raise errors.HypervisorError("Can't connect to qmp socket") |
182 |
self._connected = True |
183 |
|
184 |
def close(self): |
185 |
"""Closes the socket
|
186 |
|
187 |
It cannot be used after this call.
|
188 |
|
189 |
"""
|
190 |
self.sock.close()
|
191 |
|
192 |
|
193 |
class QmpConnection(MonitorSocket): |
194 |
"""Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
|
195 |
|
196 |
"""
|
197 |
_FIRST_MESSAGE_KEY = "QMP"
|
198 |
_EVENT_KEY = "event"
|
199 |
_ERROR_KEY = "error"
|
200 |
_RETURN_KEY = "return"
|
201 |
_ACTUAL_KEY = ACTUAL_KEY = "actual"
|
202 |
_ERROR_CLASS_KEY = "class"
|
203 |
_ERROR_DESC_KEY = "desc"
|
204 |
_EXECUTE_KEY = "execute"
|
205 |
_ARGUMENTS_KEY = "arguments"
|
206 |
_VERSION_KEY = "version"
|
207 |
_PACKAGE_KEY = "package"
|
208 |
_QEMU_KEY = "qemu"
|
209 |
_CAPABILITIES_COMMAND = "qmp_capabilities"
|
210 |
_QUERY_COMMANDS = "query-commands"
|
211 |
_MESSAGE_END_TOKEN = "\r\n"
|
212 |
|
213 |
def __init__(self, monitor_filename): |
214 |
super(QmpConnection, self).__init__(monitor_filename) |
215 |
self._buf = "" |
216 |
self.supported_commands = None |
217 |
|
218 |
def __enter__(self): |
219 |
self.connect()
|
220 |
return self |
221 |
|
222 |
def __exit__(self, exc_type, exc_value, tb): |
223 |
self.close()
|
224 |
|
225 |
def connect(self): |
226 |
"""Connects to the QMP monitor.
|
227 |
|
228 |
Connects to the UNIX socket and makes sure that we can actually send and
|
229 |
receive data to the kvm instance via QMP.
|
230 |
|
231 |
@raise errors.HypervisorError: when there are communication errors
|
232 |
@raise errors.ProgrammerError: when there are data serialization errors
|
233 |
|
234 |
"""
|
235 |
super(QmpConnection, self).connect() |
236 |
# Check if we receive a correct greeting message from the server
|
237 |
# (As per the QEMU Protocol Specification 0.1 - section 2.2)
|
238 |
greeting = self._Recv()
|
239 |
if not greeting[self._FIRST_MESSAGE_KEY]: |
240 |
self._connected = False |
241 |
raise errors.HypervisorError("kvm: QMP communication error (wrong" |
242 |
" server greeting")
|
243 |
|
244 |
# Extract the version info from the greeting and make it available to users
|
245 |
# of the monitor.
|
246 |
version_info = greeting[self._FIRST_MESSAGE_KEY][self._VERSION_KEY] |
247 |
|
248 |
self.version = (version_info[self._QEMU_KEY]["major"], |
249 |
version_info[self._QEMU_KEY]["minor"], |
250 |
version_info[self._QEMU_KEY]["micro"]) |
251 |
self.package = version_info[self._PACKAGE_KEY].strip() |
252 |
|
253 |
# This is needed because QMP can return more than one greetings
|
254 |
# see https://groups.google.com/d/msg/ganeti-devel/gZYcvHKDooU/SnukC8dgS5AJ
|
255 |
self._buf = "" |
256 |
|
257 |
# Let's put the monitor in command mode using the qmp_capabilities
|
258 |
# command, or else no command will be executable.
|
259 |
# (As per the QEMU Protocol Specification 0.1 - section 4)
|
260 |
self.Execute(self._CAPABILITIES_COMMAND) |
261 |
self.supported_commands = self._GetSupportedCommands() |
262 |
|
263 |
def _ParseMessage(self, buf): |
264 |
"""Extract and parse a QMP message from the given buffer.
|
265 |
|
266 |
Seeks for a QMP message in the given buf. If found, it parses it and
|
267 |
returns it together with the rest of the characters in the buf.
|
268 |
If no message is found, returns None and the whole buffer.
|
269 |
|
270 |
@raise errors.ProgrammerError: when there are data serialization errors
|
271 |
|
272 |
"""
|
273 |
message = None
|
274 |
# Check if we got the message end token (CRLF, as per the QEMU Protocol
|
275 |
# Specification 0.1 - Section 2.1.1)
|
276 |
pos = buf.find(self._MESSAGE_END_TOKEN)
|
277 |
if pos >= 0: |
278 |
try:
|
279 |
message = QmpMessage.BuildFromJsonString(buf[:pos + 1])
|
280 |
except Exception, err: |
281 |
raise errors.ProgrammerError("QMP data serialization error: %s" % err) |
282 |
buf = buf[pos + 1:]
|
283 |
|
284 |
return (message, buf)
|
285 |
|
286 |
def _Recv(self): |
287 |
"""Receives a message from QMP and decodes the received JSON object.
|
288 |
|
289 |
@rtype: QmpMessage
|
290 |
@return: the received message
|
291 |
@raise errors.HypervisorError: when there are communication errors
|
292 |
@raise errors.ProgrammerError: when there are data serialization errors
|
293 |
|
294 |
"""
|
295 |
self._check_connection()
|
296 |
|
297 |
# Check if there is already a message in the buffer
|
298 |
(message, self._buf) = self._ParseMessage(self._buf) |
299 |
if message:
|
300 |
return message
|
301 |
|
302 |
recv_buffer = StringIO.StringIO(self._buf)
|
303 |
recv_buffer.seek(len(self._buf)) |
304 |
try:
|
305 |
while True: |
306 |
data = self.sock.recv(4096) |
307 |
if not data: |
308 |
break
|
309 |
recv_buffer.write(data) |
310 |
|
311 |
(message, self._buf) = self._ParseMessage(recv_buffer.getvalue()) |
312 |
if message:
|
313 |
return message
|
314 |
|
315 |
except socket.timeout, err:
|
316 |
raise errors.HypervisorError("Timeout while receiving a QMP message: " |
317 |
"%s" % (err))
|
318 |
except socket.error, err:
|
319 |
raise errors.HypervisorError("Unable to receive data from KVM using the" |
320 |
" QMP protocol: %s" % err)
|
321 |
|
322 |
def _Send(self, message): |
323 |
"""Encodes and sends a message to KVM using QMP.
|
324 |
|
325 |
@type message: QmpMessage
|
326 |
@param message: message to send to KVM
|
327 |
@raise errors.HypervisorError: when there are communication errors
|
328 |
@raise errors.ProgrammerError: when there are data serialization errors
|
329 |
|
330 |
"""
|
331 |
self._check_connection()
|
332 |
try:
|
333 |
message_str = str(message)
|
334 |
except Exception, err: |
335 |
raise errors.ProgrammerError("QMP data deserialization error: %s" % err) |
336 |
|
337 |
try:
|
338 |
self.sock.sendall(message_str)
|
339 |
except socket.timeout, err:
|
340 |
raise errors.HypervisorError("Timeout while sending a QMP message: " |
341 |
"%s (%s)" % (err.string, err.errno))
|
342 |
except socket.error, err:
|
343 |
raise errors.HypervisorError("Unable to send data from KVM using the" |
344 |
" QMP protocol: %s" % err)
|
345 |
|
346 |
def _GetSupportedCommands(self): |
347 |
"""Update the list of supported commands.
|
348 |
|
349 |
"""
|
350 |
result = self.Execute(self._QUERY_COMMANDS) |
351 |
return frozenset(com["name"] for com in result) |
352 |
|
353 |
def Execute(self, command, arguments=None): |
354 |
"""Executes a QMP command and returns the response of the server.
|
355 |
|
356 |
@type command: str
|
357 |
@param command: the command to execute
|
358 |
@type arguments: dict
|
359 |
@param arguments: dictionary of arguments to be passed to the command
|
360 |
@rtype: dict
|
361 |
@return: dictionary representing the received JSON object
|
362 |
@raise errors.HypervisorError: when there are communication errors
|
363 |
@raise errors.ProgrammerError: when there are data serialization errors
|
364 |
|
365 |
"""
|
366 |
self._check_connection()
|
367 |
|
368 |
# During the first calls of Execute, the list of supported commands has not
|
369 |
# yet been populated, so we can't use it.
|
370 |
if (self.supported_commands is not None and |
371 |
command not in self.supported_commands): |
372 |
raise QmpCommandNotSupported("Instance does not support the '%s'" |
373 |
" QMP command." % command)
|
374 |
|
375 |
message = QmpMessage({self._EXECUTE_KEY: command})
|
376 |
if arguments:
|
377 |
message[self._ARGUMENTS_KEY] = arguments
|
378 |
self._Send(message)
|
379 |
|
380 |
# According the the QMP specification, there are only two reply types to a
|
381 |
# command: either error (containing the "error" key) or success (containing
|
382 |
# the "return" key). There is also a third possibility, that of an
|
383 |
# (unrelated to the command) asynchronous event notification, identified by
|
384 |
# the "event" key.
|
385 |
while True: |
386 |
response = self._Recv()
|
387 |
err = response[self._ERROR_KEY]
|
388 |
if err:
|
389 |
raise errors.HypervisorError("kvm: error executing the %s" |
390 |
" command: %s (%s):" %
|
391 |
(command, |
392 |
err[self._ERROR_DESC_KEY],
|
393 |
err[self._ERROR_CLASS_KEY]))
|
394 |
|
395 |
elif response[self._EVENT_KEY]: |
396 |
# Filter-out any asynchronous events
|
397 |
continue
|
398 |
|
399 |
return response[self._RETURN_KEY] |