root / lib / hypervisor / hv_kvm / monitor.py @ d9982f38
History | View | Annotate | Download (21.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 |
import logging |
41 |
try:
|
42 |
import fdsend # pylint: disable=F0401 |
43 |
except ImportError: |
44 |
fdsend = None
|
45 |
|
46 |
from bitarray import bitarray |
47 |
|
48 |
from ganeti import errors |
49 |
from ganeti import utils |
50 |
from ganeti import serializer |
51 |
|
52 |
|
53 |
class QmpCommandNotSupported(errors.HypervisorError): |
54 |
"""QMP command not supported by the monitor.
|
55 |
|
56 |
This is raised in case a QmpMonitor instance is asked to execute a command
|
57 |
not supported by the instance.
|
58 |
|
59 |
This is a KVM-specific exception, intended to assist in falling back to using
|
60 |
the human monitor for operations QMP does not support.
|
61 |
|
62 |
"""
|
63 |
pass
|
64 |
|
65 |
|
66 |
class QmpMessage(object): |
67 |
"""QEMU Messaging Protocol (QMP) message.
|
68 |
|
69 |
"""
|
70 |
def __init__(self, data): |
71 |
"""Creates a new QMP message based on the passed data.
|
72 |
|
73 |
"""
|
74 |
if not isinstance(data, dict): |
75 |
raise TypeError("QmpMessage must be initialized with a dict") |
76 |
|
77 |
self.data = data
|
78 |
|
79 |
def __getitem__(self, field_name): |
80 |
"""Get the value of the required field if present, or None.
|
81 |
|
82 |
Overrides the [] operator to provide access to the message data,
|
83 |
returning None if the required item is not in the message
|
84 |
@return: the value of the field_name field, or None if field_name
|
85 |
is not contained in the message
|
86 |
|
87 |
"""
|
88 |
return self.data.get(field_name, None) |
89 |
|
90 |
def __setitem__(self, field_name, field_value): |
91 |
"""Set the value of the required field_name to field_value.
|
92 |
|
93 |
"""
|
94 |
self.data[field_name] = field_value
|
95 |
|
96 |
def __len__(self): |
97 |
"""Return the number of fields stored in this QmpMessage.
|
98 |
|
99 |
"""
|
100 |
return len(self.data) |
101 |
|
102 |
def __delitem__(self, key): |
103 |
"""Delete the specified element from the QmpMessage.
|
104 |
|
105 |
"""
|
106 |
del(self.data[key]) |
107 |
|
108 |
@staticmethod
|
109 |
def BuildFromJsonString(json_string): |
110 |
"""Build a QmpMessage from a JSON encoded string.
|
111 |
|
112 |
@type json_string: str
|
113 |
@param json_string: JSON string representing the message
|
114 |
@rtype: L{QmpMessage}
|
115 |
@return: a L{QmpMessage} built from json_string
|
116 |
|
117 |
"""
|
118 |
# Parse the string
|
119 |
data = serializer.LoadJson(json_string) |
120 |
return QmpMessage(data)
|
121 |
|
122 |
def __str__(self): |
123 |
# The protocol expects the JSON object to be sent as a single line.
|
124 |
return serializer.DumpJson(self.data) |
125 |
|
126 |
def __eq__(self, other): |
127 |
# When comparing two QmpMessages, we are interested in comparing
|
128 |
# their internal representation of the message data
|
129 |
return self.data == other.data |
130 |
|
131 |
|
132 |
class MonitorSocket(object): |
133 |
_SOCKET_TIMEOUT = 5
|
134 |
|
135 |
def __init__(self, monitor_filename): |
136 |
"""Instantiates the MonitorSocket object.
|
137 |
|
138 |
@type monitor_filename: string
|
139 |
@param monitor_filename: the filename of the UNIX raw socket on which the
|
140 |
monitor (QMP or simple one) is listening
|
141 |
|
142 |
"""
|
143 |
self.monitor_filename = monitor_filename
|
144 |
self._connected = False |
145 |
|
146 |
def _check_socket(self): |
147 |
sock_stat = None
|
148 |
try:
|
149 |
sock_stat = os.stat(self.monitor_filename)
|
150 |
except EnvironmentError, err: |
151 |
if err.errno == errno.ENOENT:
|
152 |
raise errors.HypervisorError("No monitor socket found") |
153 |
else:
|
154 |
raise errors.HypervisorError("Error checking monitor socket: %s", |
155 |
utils.ErrnoOrStr(err)) |
156 |
if not stat.S_ISSOCK(sock_stat.st_mode): |
157 |
raise errors.HypervisorError("Monitor socket is not a socket") |
158 |
|
159 |
def _check_connection(self): |
160 |
"""Make sure that the connection is established.
|
161 |
|
162 |
"""
|
163 |
if not self._connected: |
164 |
raise errors.ProgrammerError("To use a MonitorSocket you need to first" |
165 |
" invoke connect() on it")
|
166 |
|
167 |
def connect(self): |
168 |
"""Connect to the monitor socket if not already connected.
|
169 |
|
170 |
"""
|
171 |
if not self._connected: |
172 |
self._connect()
|
173 |
|
174 |
def is_connected(self): |
175 |
"""Return whether there is a connection to the socket or not.
|
176 |
|
177 |
"""
|
178 |
return self._connected |
179 |
|
180 |
def _connect(self): |
181 |
"""Connects to the monitor.
|
182 |
|
183 |
Connects to the UNIX socket
|
184 |
|
185 |
@raise errors.HypervisorError: when there are communication errors
|
186 |
|
187 |
"""
|
188 |
if self._connected: |
189 |
raise errors.ProgrammerError("Cannot connect twice") |
190 |
|
191 |
self._check_socket()
|
192 |
|
193 |
# Check file existance/stuff
|
194 |
try:
|
195 |
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
196 |
# We want to fail if the server doesn't send a complete message
|
197 |
# in a reasonable amount of time
|
198 |
self.sock.settimeout(self._SOCKET_TIMEOUT) |
199 |
self.sock.connect(self.monitor_filename) |
200 |
except EnvironmentError: |
201 |
raise errors.HypervisorError("Can't connect to qmp socket") |
202 |
self._connected = True |
203 |
|
204 |
def close(self): |
205 |
"""Closes the socket
|
206 |
|
207 |
It cannot be used after this call.
|
208 |
|
209 |
"""
|
210 |
if self._connected: |
211 |
self._close()
|
212 |
|
213 |
def _close(self): |
214 |
self.sock.close()
|
215 |
self._connected = False |
216 |
|
217 |
|
218 |
def _ensure_connection(fn): |
219 |
"""Decorator that wraps MonitorSocket external methods"""
|
220 |
def wrapper(*args, **kwargs): |
221 |
"""Ensure proper connect/close and exception propagation"""
|
222 |
mon = args[0]
|
223 |
already_connected = mon.is_connected() |
224 |
mon.connect() |
225 |
try:
|
226 |
ret = fn(*args, **kwargs) |
227 |
finally:
|
228 |
# In general this decorator wraps external methods.
|
229 |
# Here we close the connection only if we initiated it before,
|
230 |
# to protect us from using the socket after closing it
|
231 |
# in case we invoke a decorated method internally by accident.
|
232 |
if not already_connected: |
233 |
mon.close() |
234 |
return ret
|
235 |
return wrapper
|
236 |
|
237 |
|
238 |
class QmpConnection(MonitorSocket): |
239 |
"""Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
|
240 |
|
241 |
"""
|
242 |
_FIRST_MESSAGE_KEY = "QMP"
|
243 |
_EVENT_KEY = "event"
|
244 |
_ERROR_KEY = "error"
|
245 |
_RETURN_KEY = "return"
|
246 |
_ACTUAL_KEY = ACTUAL_KEY = "actual"
|
247 |
_ERROR_CLASS_KEY = "class"
|
248 |
_ERROR_DESC_KEY = "desc"
|
249 |
_EXECUTE_KEY = "execute"
|
250 |
_ARGUMENTS_KEY = "arguments"
|
251 |
_VERSION_KEY = "version"
|
252 |
_PACKAGE_KEY = "package"
|
253 |
_QEMU_KEY = "qemu"
|
254 |
_CAPABILITIES_COMMAND = "qmp_capabilities"
|
255 |
_QUERY_COMMANDS = "query-commands"
|
256 |
_MESSAGE_END_TOKEN = "\r\n"
|
257 |
_QEMU_PCI_SLOTS = 32 # The number of PCI slots QEMU exposes by default |
258 |
|
259 |
def __init__(self, monitor_filename): |
260 |
super(QmpConnection, self).__init__(monitor_filename) |
261 |
self._buf = "" |
262 |
self.supported_commands = None |
263 |
|
264 |
def __enter__(self): |
265 |
self.connect()
|
266 |
return self |
267 |
|
268 |
def __exit__(self, exc_type, exc_value, tb): |
269 |
self.close()
|
270 |
|
271 |
def connect(self): |
272 |
"""Connects to the QMP monitor.
|
273 |
|
274 |
Connects to the UNIX socket and makes sure that we can actually send and
|
275 |
receive data to the kvm instance via QMP.
|
276 |
|
277 |
@raise errors.HypervisorError: when there are communication errors
|
278 |
@raise errors.ProgrammerError: when there are data serialization errors
|
279 |
|
280 |
"""
|
281 |
super(QmpConnection, self).connect() |
282 |
# Check if we receive a correct greeting message from the server
|
283 |
# (As per the QEMU Protocol Specification 0.1 - section 2.2)
|
284 |
greeting = self._Recv()
|
285 |
if not greeting[self._FIRST_MESSAGE_KEY]: |
286 |
self._connected = False |
287 |
raise errors.HypervisorError("kvm: QMP communication error (wrong" |
288 |
" server greeting")
|
289 |
|
290 |
# Extract the version info from the greeting and make it available to users
|
291 |
# of the monitor.
|
292 |
version_info = greeting[self._FIRST_MESSAGE_KEY][self._VERSION_KEY] |
293 |
|
294 |
self.version = (version_info[self._QEMU_KEY]["major"], |
295 |
version_info[self._QEMU_KEY]["minor"], |
296 |
version_info[self._QEMU_KEY]["micro"]) |
297 |
self.package = version_info[self._PACKAGE_KEY].strip() |
298 |
|
299 |
# This is needed because QMP can return more than one greetings
|
300 |
# see https://groups.google.com/d/msg/ganeti-devel/gZYcvHKDooU/SnukC8dgS5AJ
|
301 |
self._buf = "" |
302 |
|
303 |
# Let's put the monitor in command mode using the qmp_capabilities
|
304 |
# command, or else no command will be executable.
|
305 |
# (As per the QEMU Protocol Specification 0.1 - section 4)
|
306 |
self.Execute(self._CAPABILITIES_COMMAND) |
307 |
self.supported_commands = self._GetSupportedCommands() |
308 |
|
309 |
def _ParseMessage(self, buf): |
310 |
"""Extract and parse a QMP message from the given buffer.
|
311 |
|
312 |
Seeks for a QMP message in the given buf. If found, it parses it and
|
313 |
returns it together with the rest of the characters in the buf.
|
314 |
If no message is found, returns None and the whole buffer.
|
315 |
|
316 |
@raise errors.ProgrammerError: when there are data serialization errors
|
317 |
|
318 |
"""
|
319 |
message = None
|
320 |
# Check if we got the message end token (CRLF, as per the QEMU Protocol
|
321 |
# Specification 0.1 - Section 2.1.1)
|
322 |
pos = buf.find(self._MESSAGE_END_TOKEN)
|
323 |
if pos >= 0: |
324 |
try:
|
325 |
message = QmpMessage.BuildFromJsonString(buf[:pos + 1])
|
326 |
except Exception, err: |
327 |
raise errors.ProgrammerError("QMP data serialization error: %s" % err) |
328 |
buf = buf[pos + 1:]
|
329 |
|
330 |
return (message, buf)
|
331 |
|
332 |
def _Recv(self): |
333 |
"""Receives a message from QMP and decodes the received JSON object.
|
334 |
|
335 |
@rtype: QmpMessage
|
336 |
@return: the received message
|
337 |
@raise errors.HypervisorError: when there are communication errors
|
338 |
@raise errors.ProgrammerError: when there are data serialization errors
|
339 |
|
340 |
"""
|
341 |
self._check_connection()
|
342 |
|
343 |
# Check if there is already a message in the buffer
|
344 |
(message, self._buf) = self._ParseMessage(self._buf) |
345 |
if message:
|
346 |
return message
|
347 |
|
348 |
recv_buffer = StringIO.StringIO(self._buf)
|
349 |
recv_buffer.seek(len(self._buf)) |
350 |
try:
|
351 |
while True: |
352 |
data = self.sock.recv(4096) |
353 |
if not data: |
354 |
break
|
355 |
recv_buffer.write(data) |
356 |
|
357 |
(message, self._buf) = self._ParseMessage(recv_buffer.getvalue()) |
358 |
if message:
|
359 |
return message
|
360 |
|
361 |
except socket.timeout, err:
|
362 |
raise errors.HypervisorError("Timeout while receiving a QMP message: " |
363 |
"%s" % (err))
|
364 |
except socket.error, err:
|
365 |
raise errors.HypervisorError("Unable to receive data from KVM using the" |
366 |
" QMP protocol: %s" % err)
|
367 |
|
368 |
def _Send(self, message): |
369 |
"""Encodes and sends a message to KVM using QMP.
|
370 |
|
371 |
@type message: QmpMessage
|
372 |
@param message: message to send to KVM
|
373 |
@raise errors.HypervisorError: when there are communication errors
|
374 |
@raise errors.ProgrammerError: when there are data serialization errors
|
375 |
|
376 |
"""
|
377 |
self._check_connection()
|
378 |
try:
|
379 |
message_str = str(message)
|
380 |
except Exception, err: |
381 |
raise errors.ProgrammerError("QMP data deserialization error: %s" % err) |
382 |
|
383 |
try:
|
384 |
self.sock.sendall(message_str)
|
385 |
except socket.timeout, err:
|
386 |
raise errors.HypervisorError("Timeout while sending a QMP message: " |
387 |
"%s (%s)" % (err.string, err.errno))
|
388 |
except socket.error, err:
|
389 |
raise errors.HypervisorError("Unable to send data from KVM using the" |
390 |
" QMP protocol: %s" % err)
|
391 |
|
392 |
def _GetSupportedCommands(self): |
393 |
"""Update the list of supported commands.
|
394 |
|
395 |
"""
|
396 |
result = self.Execute(self._QUERY_COMMANDS) |
397 |
return frozenset(com["name"] for com in result) |
398 |
|
399 |
def Execute(self, command, arguments=None): |
400 |
"""Executes a QMP command and returns the response of the server.
|
401 |
|
402 |
@type command: str
|
403 |
@param command: the command to execute
|
404 |
@type arguments: dict
|
405 |
@param arguments: dictionary of arguments to be passed to the command
|
406 |
@rtype: dict
|
407 |
@return: dictionary representing the received JSON object
|
408 |
@raise errors.HypervisorError: when there are communication errors
|
409 |
@raise errors.ProgrammerError: when there are data serialization errors
|
410 |
|
411 |
"""
|
412 |
self._check_connection()
|
413 |
|
414 |
# During the first calls of Execute, the list of supported commands has not
|
415 |
# yet been populated, so we can't use it.
|
416 |
if (self.supported_commands is not None and |
417 |
command not in self.supported_commands): |
418 |
raise QmpCommandNotSupported("Instance does not support the '%s'" |
419 |
" QMP command." % command)
|
420 |
|
421 |
message = QmpMessage({self._EXECUTE_KEY: command})
|
422 |
if arguments:
|
423 |
message[self._ARGUMENTS_KEY] = arguments
|
424 |
self._Send(message)
|
425 |
|
426 |
ret = self._GetResponse(command)
|
427 |
# log important qmp commands..
|
428 |
if command not in [self._QUERY_COMMANDS, self._CAPABILITIES_COMMAND]: |
429 |
logging.debug("QMP %s %s: %s\n", command, arguments, ret)
|
430 |
return ret
|
431 |
|
432 |
def _GetResponse(self, command): |
433 |
"""Parse the QMP response
|
434 |
|
435 |
If error key found in the response message raise HypervisorError.
|
436 |
Ignore any async event and thus return the response message
|
437 |
related to command.
|
438 |
|
439 |
"""
|
440 |
# According the the QMP specification, there are only two reply types to a
|
441 |
# command: either error (containing the "error" key) or success (containing
|
442 |
# the "return" key). There is also a third possibility, that of an
|
443 |
# (unrelated to the command) asynchronous event notification, identified by
|
444 |
# the "event" key.
|
445 |
while True: |
446 |
response = self._Recv()
|
447 |
err = response[self._ERROR_KEY]
|
448 |
if err:
|
449 |
raise errors.HypervisorError("kvm: error executing the %s" |
450 |
" command: %s (%s):" %
|
451 |
(command, |
452 |
err[self._ERROR_DESC_KEY],
|
453 |
err[self._ERROR_CLASS_KEY]))
|
454 |
|
455 |
elif response[self._EVENT_KEY]: |
456 |
# Filter-out any asynchronous events
|
457 |
continue
|
458 |
|
459 |
return response[self._RETURN_KEY] |
460 |
|
461 |
@_ensure_connection
|
462 |
def HotAddNic(self, nic, devid, tapfds=None, vhostfds=None, features=None): |
463 |
"""Hot-add a NIC
|
464 |
|
465 |
First pass the tapfds, then netdev_add and then device_add
|
466 |
|
467 |
"""
|
468 |
if tapfds is None: |
469 |
tapfds = [] |
470 |
if vhostfds is None: |
471 |
vhostfds = [] |
472 |
if features is None: |
473 |
features = {} |
474 |
|
475 |
enable_vhost = features.get("vhost", False) |
476 |
enable_mq, virtio_net_queues = features.get("mq", (False, 1)) |
477 |
|
478 |
fdnames = [] |
479 |
for i, fd in enumerate(tapfds): |
480 |
fdname = "%s-%d" % (devid, i)
|
481 |
self._GetFd(fd, fdname)
|
482 |
fdnames.append(fdname) |
483 |
|
484 |
arguments = { |
485 |
"type": "tap", |
486 |
"id": devid,
|
487 |
"fds": ":".join(fdnames), |
488 |
} |
489 |
if enable_vhost:
|
490 |
fdnames = [] |
491 |
for i, fd in enumerate(vhostfds): |
492 |
fdname = "%s-vhost-%d" % (devid, i)
|
493 |
self._GetFd(fd, fdname)
|
494 |
fdnames.append(fdname) |
495 |
|
496 |
arguments.update({ |
497 |
"vhost": "on", |
498 |
"vhostfds": ":".join(fdnames), |
499 |
}) |
500 |
self.Execute("netdev_add", arguments) |
501 |
|
502 |
arguments = { |
503 |
"driver": "virtio-net-pci", |
504 |
"id": devid,
|
505 |
"bus": "pci.0", |
506 |
"addr": hex(nic.pci), |
507 |
"netdev": devid,
|
508 |
"mac": nic.mac,
|
509 |
} |
510 |
if enable_mq:
|
511 |
arguments.update({ |
512 |
"mq": "on", |
513 |
"vectors": (2 * virtio_net_queues + 1), |
514 |
}) |
515 |
self.Execute("device_add", arguments) |
516 |
|
517 |
@_ensure_connection
|
518 |
def HotDelNic(self, devid): |
519 |
"""Hot-del a NIC
|
520 |
|
521 |
"""
|
522 |
self.Execute("device_del", {"id": devid}) |
523 |
self.Execute("netdev_del", {"id": devid}) |
524 |
|
525 |
@_ensure_connection
|
526 |
def HotAddDisk(self, disk, devid, uri): |
527 |
"""Hot-add a disk
|
528 |
|
529 |
Try opening the device to obtain a fd and pass it with SCM_RIGHTS. This
|
530 |
will be omitted in case of userspace access mode (open will fail).
|
531 |
Then use blockdev-add and then device_add.
|
532 |
|
533 |
"""
|
534 |
if os.path.exists(uri):
|
535 |
fd = os.open(uri, os.O_RDWR) |
536 |
fdset = self._AddFd(fd)
|
537 |
os.close(fd) |
538 |
filename = "/dev/fdset/%s" % fdset
|
539 |
else:
|
540 |
# The uri is not a file.
|
541 |
# This can happen if a userspace uri is provided.
|
542 |
filename = uri |
543 |
fdset = None
|
544 |
|
545 |
arguments = { |
546 |
"options": {
|
547 |
"driver": "raw", |
548 |
"id": devid,
|
549 |
"file": {
|
550 |
"driver": "file", |
551 |
"filename": filename,
|
552 |
} |
553 |
} |
554 |
} |
555 |
self.Execute("blockdev-add", arguments) |
556 |
|
557 |
if fdset is not None: |
558 |
self._RemoveFdset(fdset)
|
559 |
|
560 |
arguments = { |
561 |
"driver": "virtio-blk-pci", |
562 |
"id": devid,
|
563 |
"bus": "pci.0", |
564 |
"addr": hex(disk.pci), |
565 |
"drive": devid,
|
566 |
} |
567 |
self.Execute("device_add", arguments) |
568 |
|
569 |
@_ensure_connection
|
570 |
def HotDelDisk(self, devid): |
571 |
"""Hot-del a Disk
|
572 |
|
573 |
Note that drive_del is not supported yet in qmp and thus should
|
574 |
be invoked from HMP.
|
575 |
|
576 |
"""
|
577 |
self.Execute("device_del", {"id": devid}) |
578 |
#TODO: uncomment when drive_del gets implemented in upstream qemu
|
579 |
# self.Execute("drive_del", {"id": devid})
|
580 |
|
581 |
def _GetPCIDevices(self): |
582 |
"""Get the devices of the first PCI bus of a running instance.
|
583 |
|
584 |
"""
|
585 |
self._check_connection()
|
586 |
pci = self.Execute("query-pci") |
587 |
bus = pci[0]
|
588 |
devices = bus["devices"]
|
589 |
return devices
|
590 |
|
591 |
@_ensure_connection
|
592 |
def HasPCIDevice(self, device, devid): |
593 |
"""Check if a specific device exists or not on a running instance.
|
594 |
|
595 |
It will match the PCI slot of the device and the id currently
|
596 |
obtained by _GenerateDeviceKVMId().
|
597 |
|
598 |
"""
|
599 |
for d in self._GetPCIDevices(): |
600 |
if d["qdev_id"] == devid and d["slot"] == device.pci: |
601 |
return True |
602 |
|
603 |
return False |
604 |
|
605 |
@_ensure_connection
|
606 |
def GetFreePCISlot(self): |
607 |
"""Get the first available PCI slot of a running instance.
|
608 |
|
609 |
"""
|
610 |
slots = bitarray(self._QEMU_PCI_SLOTS)
|
611 |
slots.setall(False) # pylint: disable=E1101 |
612 |
for d in self._GetPCIDevices(): |
613 |
slot = d["slot"]
|
614 |
slots[slot] = True
|
615 |
|
616 |
return utils.GetFreeSlot(slots)
|
617 |
|
618 |
@_ensure_connection
|
619 |
def CheckDiskHotAddSupport(self): |
620 |
"""Check if disk hotplug is possible
|
621 |
|
622 |
Hotplug is *not* supported in case:
|
623 |
- fdsend module is missing
|
624 |
- add-fd and blockdev-add qmp commands are not supported
|
625 |
|
626 |
"""
|
627 |
def _raise(reason): |
628 |
raise errors.HotplugError("Cannot hot-add disk: %s." % reason) |
629 |
|
630 |
if not fdsend: |
631 |
_raise("fdsend python module is missing")
|
632 |
|
633 |
if "add-fd" not in self.supported_commands: |
634 |
_raise("add-fd qmp command is not supported")
|
635 |
|
636 |
if "blockdev-add" not in self.supported_commands: |
637 |
_raise("blockdev-add qmp command is not supported")
|
638 |
|
639 |
@_ensure_connection
|
640 |
def CheckNicHotAddSupport(self): |
641 |
"""Check if NIC hotplug is possible
|
642 |
|
643 |
Hotplug is *not* supported in case:
|
644 |
- fdsend module is missing
|
645 |
- getfd and netdev_add qmp commands are not supported
|
646 |
|
647 |
"""
|
648 |
def _raise(reason): |
649 |
raise errors.HotplugError("Cannot hot-add NIC: %s." % reason) |
650 |
|
651 |
if not fdsend: |
652 |
_raise("fdsend python module is missing")
|
653 |
|
654 |
if "getfd" not in self.supported_commands: |
655 |
_raise("getfd qmp command is not supported")
|
656 |
|
657 |
if "netdev_add" not in self.supported_commands: |
658 |
_raise("netdev_add qmp command is not supported")
|
659 |
|
660 |
def _GetFd(self, fd, fdname): |
661 |
"""Wrapper around the getfd qmp command
|
662 |
|
663 |
Use fdsend to send an fd to a running process via SCM_RIGHTS and then use
|
664 |
the getfd qmp command to name it properly so that it can be used
|
665 |
later by NIC hotplugging.
|
666 |
|
667 |
@type fd: int
|
668 |
@param fd: The file descriptor to pass
|
669 |
@raise errors.HypervisorError: If getfd fails for some reason
|
670 |
|
671 |
"""
|
672 |
self._check_connection()
|
673 |
try:
|
674 |
fdsend.sendfds(self.sock, " ", fds=[fd]) |
675 |
arguments = { |
676 |
"fdname": fdname,
|
677 |
} |
678 |
self.Execute("getfd", arguments) |
679 |
except errors.HypervisorError, err:
|
680 |
logging.info("Passing fd %s via SCM_RIGHTS failed: %s", fd, err)
|
681 |
raise
|
682 |
|
683 |
def _AddFd(self, fd): |
684 |
"""Wrapper around add-fd qmp command
|
685 |
|
686 |
Use fdsend to send fd to a running process via SCM_RIGHTS and then add-fd
|
687 |
qmp command to add it to an fdset so that it can be used later by
|
688 |
disk hotplugging.
|
689 |
|
690 |
@type fd: int
|
691 |
@param fd: The file descriptor to pass
|
692 |
|
693 |
@return: The fdset ID that the fd has been added to
|
694 |
@raise errors.HypervisorError: If add-fd fails for some reason
|
695 |
|
696 |
"""
|
697 |
self._check_connection()
|
698 |
try:
|
699 |
fdsend.sendfds(self.sock, " ", fds=[fd]) |
700 |
# Omit fdset-id and let qemu create a new one (see qmp-commands.hx)
|
701 |
response = self.Execute("add-fd") |
702 |
fdset = response["fdset-id"]
|
703 |
except errors.HypervisorError, err:
|
704 |
logging.info("Passing fd %s via SCM_RIGHTS failed: %s", fd, err)
|
705 |
raise
|
706 |
|
707 |
return fdset
|
708 |
|
709 |
def _RemoveFdset(self, fdset): |
710 |
"""Wrapper around remove-fd qmp command
|
711 |
|
712 |
Remove the file descriptor previously passed. After qemu has dup'd the fd
|
713 |
(e.g. during disk hotplug), it can be safely removed.
|
714 |
|
715 |
"""
|
716 |
self._check_connection()
|
717 |
# Omit the fd to cleanup all fds in the fdset (see qemu/qmp-commands.hx)
|
718 |
try:
|
719 |
self.Execute("remove-fd", {"fdset-id": fdset}) |
720 |
except errors.HypervisorError, err:
|
721 |
# There is no big deal if we cannot remove an fdset. This cleanup here is
|
722 |
# done on a best effort basis. Upon next hot-add a new fdset will be
|
723 |
# created. If we raise an exception here, that is after drive_add has
|
724 |
# succeeded, the whole hot-add action will fail and the runtime file will
|
725 |
# not be updated which will make the instance non migrate-able
|
726 |
logging.info("Removing fdset with id %s failed: %s", fdset, err)
|