Statistics
| Branch: | Tag: | Revision:

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)