Statistics
| Branch: | Tag: | Revision:

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]