Package ganeti :: Package hypervisor :: Package hv_kvm :: Module monitor
[hide private]
[frames] | no frames]

Source Code for Module ganeti.hypervisor.hv_kvm.monitor

  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   
 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 
45 46 47 -class QmpCommandNotSupported(errors.HypervisorError):
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
59 60 -class QmpMessage(object):
61 """QEMU Messaging Protocol (QMP) message. 62 63 """
64 - def __init__(self, data):
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
73 - def __getitem__(self, field_name):
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
84 - def __setitem__(self, field_name, field_value):
85 """Set the value of the required field_name to field_value. 86 87 """ 88 self.data[field_name] = field_value
89
90 - def __len__(self):
91 """Return the number of fields stored in this QmpMessage. 92 93 """ 94 return len(self.data)
95
96 - def __delitem__(self, key):
97 """Delete the specified element from the QmpMessage. 98 99 """ 100 del(self.data[key])
101 102 @staticmethod
103 - def BuildFromJsonString(json_string):
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 # Parse the string 113 data = serializer.LoadJson(json_string) 114 return QmpMessage(data)
115
116 - def __str__(self):
117 # The protocol expects the JSON object to be sent as a single line. 118 return serializer.DumpJson(self.data)
119
120 - def __eq__(self, other):
121 # When comparing two QmpMessages, we are interested in comparing 122 # their internal representation of the message data 123 return self.data == other.data
124
125 126 -class MonitorSocket(object):
127 _SOCKET_TIMEOUT = 5 128
129 - def __init__(self, monitor_filename):
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 # We want to fail if the server doesn't send a complete message 140 # in a reasonable amount of time 141 self.sock.settimeout(self._SOCKET_TIMEOUT) 142 self._connected = False
143
144 - def _check_socket(self):
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
157 - def _check_connection(self):
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
165 - def connect(self):
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 # Check file existance/stuff 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
185 - def close(self):
186 """Closes the socket 187 188 It cannot be used after this call. 189 190 """ 191 self.sock.close()
192
193 194 -class QmpConnection(MonitorSocket):
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
214 - def __init__(self, monitor_filename):
215 super(QmpConnection, self).__init__(monitor_filename) 216 self._buf = "" 217 self.supported_commands = None
218
219 - def __enter__(self):
220 self.connect() 221 return self
222
223 - def __exit__(self, exc_type, exc_value, tb):
224 self.close()
225
226 - def connect(self):
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 # Check if we receive a correct greeting message from the server 238 # (As per the QEMU Protocol Specification 0.1 - section 2.2) 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 # Extract the version info from the greeting and make it available to users 246 # of the monitor. 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 # This is needed because QMP can return more than one greetings 255 # see https://groups.google.com/d/msg/ganeti-devel/gZYcvHKDooU/SnukC8dgS5AJ 256 self._buf = "" 257 258 # Let's put the monitor in command mode using the qmp_capabilities 259 # command, or else no command will be executable. 260 # (As per the QEMU Protocol Specification 0.1 - section 4) 261 self.Execute(self._CAPABILITIES_COMMAND) 262 self.supported_commands = self._GetSupportedCommands()
263
264 - def _ParseMessage(self, buf):
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 # Check if we got the message end token (CRLF, as per the QEMU Protocol 276 # Specification 0.1 - Section 2.1.1) 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
287 - def _Recv(self):
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 # Check if there is already a message in the buffer 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
347 - def _GetSupportedCommands(self):
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 # During the first calls of Execute, the list of supported commands has not 370 # yet been populated, so we can't use it. 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 # According the the QMP specification, there are only two reply types to a 382 # command: either error (containing the "error" key) or success (containing 383 # the "return" key). There is also a third possibility, that of an 384 # (unrelated to the command) asynchronous event notification, identified by 385 # the "event" key. 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 # Filter-out any asynchronous events 398 continue 399 400 return response[self._RETURN_KEY]
401