Revision 2becae3f

b/lib/hypervisor/hv_kvm.py
37 37
import socket
38 38
import stat
39 39
import StringIO
40
import copy
41
from bitarray import bitarray
40 42
try:
41 43
  import affinity   # pylint: disable=F0401
42 44
except ImportError:
43 45
  affinity = None
46
try:
47
  import fdsend   # pylint: disable=F0401
48
except ImportError:
49
  nic_hotplug = False
44 50

  
45 51
from ganeti import utils
46 52
from ganeti import constants
......
79 85
  constants.HV_KVM_SPICE_USE_TLS,
80 86
  ])
81 87

  
88
FREE = bitarray("0")
89

  
90
def _GenerateDeviceKVMId(dev_type, dev):
91

  
92
  if not dev or not dev.pci:
93
    return None
94

  
95
  return "%s-pci-%d" % (dev_type.lower(), dev.pci)
96

  
97

  
98
def _UpdatePCISlots(dev, pci_reservations):
99
  """Update pci configuration for a stopped instance
100

  
101
  If dev has a pci slot the reserve it, else find first available.
102

  
103
  """
104
  if dev.pci:
105
    free = dev.pci
106
  else:
107
    [free] = pci_reservations.search(FREE, 1) # pylint: disable=E1103
108
    if not free:
109
      raise errors.HypervisorError("All PCI slots occupied")
110
    dev.pci = int(free)
111

  
112
  pci_reservations[free] = True
113

  
114

  
115
def _RemoveFromRuntimeEntry(devices, device, fn):
116
  [rem] = [x for x in fn(devices) if x.uuid == device.uuid]
117
  try:
118
    devices.remove(rem)
119
  except (ValueError, IndexError):
120
    logging.info("No device with uuid %s in runtime file", device.uuid)
121

  
82 122

  
83 123
def _GetTunFeatures(fd, _ioctl=fcntl.ioctl):
84 124
  """Retrieves supported TUN features from file descriptor.
......
569 609
  _BOOT_RE = re.compile(r"^-drive\s([^-]|(?<!^)-)*,boot=on\|off", re.M | re.S)
570 610
  _UUID_RE = re.compile(r"^-uuid\s", re.M)
571 611

  
612
  _INFO_PCI_RE = re.compile(r'Bus.*device[ ]*(\d+).*')
613
  _INFO_PCI_CMD = "info pci"
614
  _INFO_VERSION_RE = \
615
    re.compile(r'^QEMU (\d+)\.(\d+)(\.(\d+))?.*monitor.*', re.M)
616
  _INFO_VERSION_CMD = "info version"
617

  
618
  _DEFAULT_PCI_RESERVATIONS = "11110000000000000000000000000000"
619

  
572 620
  ANCILLARY_FILES = [
573 621
    _KVM_NETWORK_SCRIPT,
574 622
    ]
......
1021 1069
        data.append(info)
1022 1070
    return data
1023 1071

  
1072
  def _GetExistingDeviceKVMId(self, instance, dev_type, dev):
1073
    (_, kvm_nics, __, block_devices) = self._LoadKVMRuntime(instance)
1074
    if dev_type == "NIC":
1075
      found = [n for n in kvm_nics
1076
               if n.uuid == dev.uuid]
1077
    elif dev_type == "DISK":
1078
      found = [d for d, _ in block_devices
1079
               if d.uuid == dev.uuid]
1080
    dev_info = None
1081
    if found:
1082
      dev_info = found[0]
1083
    return _GenerateDeviceKVMId(dev_type, dev_info)
1084

  
1085
  def _GenerateKVMBlockDevicesOptions(self, instance, kvm_cmd, block_devices,
1086
                                      pci_reservations, kvmhelp):
1087

  
1088
    hvp = instance.hvparams
1089
    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
1090

  
1091
    # whether this is an older KVM version that uses the boot=on flag
1092
    # on devices
1093
    needs_boot_flag = self._BOOT_RE.search(kvmhelp)
1094

  
1095
    disk_type = hvp[constants.HV_DISK_TYPE]
1096
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
1097
      #TODO: parse kvm -device ? output
1098
      disk_model = "virtio-blk-pci"
1099
      if_val = ",if=virtio"
1100
    else:
1101
      if_val = ",if=%s" % disk_type
1102
    # Cache mode
1103
    disk_cache = hvp[constants.HV_DISK_CACHE]
1104
    if instance.disk_template in constants.DTS_EXT_MIRROR:
1105
      if disk_cache != "none":
1106
        # TODO: make this a hard error, instead of a silent overwrite
1107
        logging.warning("KVM: overriding disk_cache setting '%s' with 'none'"
1108
                        " to prevent shared storage corruption on migration",
1109
                        disk_cache)
1110
      cache_val = ",cache=none"
1111
    elif disk_cache != constants.HT_CACHE_DEFAULT:
1112
      cache_val = ",cache=%s" % disk_cache
1113
    else:
1114
      cache_val = ""
1115
    for cfdev, dev_path in block_devices:
1116
      if cfdev.mode != constants.DISK_RDWR:
1117
        raise errors.HypervisorError("Instance has read-only disks which"
1118
                                     " are not supported by KVM")
1119
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
1120
      boot_val = ""
1121
      if boot_disk:
1122
        kvm_cmd.extend(["-boot", "c"])
1123
        boot_disk = False
1124
        if needs_boot_flag and disk_type != constants.HT_DISK_IDE:
1125
          boot_val = ",boot=on"
1126
      drive_val = "file=%s,format=raw%s%s" % \
1127
                  (dev_path, boot_val, cache_val)
1128
      _UpdatePCISlots(cfdev, pci_reservations)
1129
      kvm_devid = _GenerateDeviceKVMId("DISK", cfdev)
1130
      if kvm_devid:
1131
        #TODO: name id after model
1132
        drive_val += (",if=none,id=%s" % kvm_devid)
1133
        drive_val += (",bus=0,unit=%d" % cfdev.pci)
1134
      else:
1135
        drive_val += if_val
1136

  
1137
      kvm_cmd.extend(["-drive", drive_val])
1138

  
1139
      if kvm_devid:
1140
        dev_val = ("%s,drive=%s,id=%s" %
1141
                    (disk_model, kvm_devid, kvm_devid))
1142
        dev_val += ",bus=pci.0,addr=%s" % hex(cfdev.pci)
1143
        kvm_cmd.extend(["-device", dev_val])
1144

  
1145
    return kvm_cmd
1146

  
1024 1147
  def _GenerateKVMRuntime(self, instance, block_devices, startup_paused,
1025 1148
                          kvmhelp):
1026 1149
    """Generate KVM information to start an instance.
......
1089 1212

  
1090 1213
    kernel_path = hvp[constants.HV_KERNEL_PATH]
1091 1214
    if kernel_path:
1092
      boot_disk = boot_cdrom = boot_floppy = boot_network = False
1215
      boot_cdrom = boot_floppy = boot_network = False
1093 1216
    else:
1094
      boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
1095 1217
      boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
1096 1218
      boot_floppy = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_FLOPPY
1097 1219
      boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
......
1107 1229
    needs_boot_flag = self._BOOT_RE.search(kvmhelp)
1108 1230

  
1109 1231
    disk_type = hvp[constants.HV_DISK_TYPE]
1110
    if disk_type == constants.HT_DISK_PARAVIRTUAL:
1111
      if_val = ",if=virtio"
1112
    else:
1113
      if_val = ",if=%s" % disk_type
1114
    # Cache mode
1115
    disk_cache = hvp[constants.HV_DISK_CACHE]
1116
    if instance.disk_template in constants.DTS_EXT_MIRROR:
1117
      if disk_cache != "none":
1118
        # TODO: make this a hard error, instead of a silent overwrite
1119
        logging.warning("KVM: overriding disk_cache setting '%s' with 'none'"
1120
                        " to prevent shared storage corruption on migration",
1121
                        disk_cache)
1122
      cache_val = ",cache=none"
1123
    elif disk_cache != constants.HT_CACHE_DEFAULT:
1124
      cache_val = ",cache=%s" % disk_cache
1125
    else:
1126
      cache_val = ""
1127
    for cfdev, dev_path in block_devices:
1128
      if cfdev.mode != constants.DISK_RDWR:
1129
        raise errors.HypervisorError("Instance has read-only disks which"
1130
                                     " are not supported by KVM")
1131
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
1132
      boot_val = ""
1133
      if boot_disk:
1134
        kvm_cmd.extend(["-boot", "c"])
1135
        boot_disk = False
1136
        if needs_boot_flag and disk_type != constants.HT_DISK_IDE:
1137
          boot_val = ",boot=on"
1138

  
1139
      drive_val = "file=%s,format=raw%s%s%s" % (dev_path, if_val, boot_val,
1140
                                                cache_val)
1141
      kvm_cmd.extend(["-drive", drive_val])
1142 1232

  
1143 1233
    #Now we can specify a different device type for CDROM devices.
1144 1234
    cdrom_disk_type = hvp[constants.HV_KVM_CDROM_DISK_TYPE]
......
1407 1497
    kvm_nics = instance.nics
1408 1498
    hvparams = hvp
1409 1499

  
1410
    return (kvm_cmd, kvm_nics, hvparams)
1500
    return (kvm_cmd, kvm_nics, hvparams, block_devices)
1411 1501

  
1412 1502
  def _WriteKVMRuntime(self, instance_name, data):
1413 1503
    """Write an instance's KVM runtime
......
1433 1523
    """Save an instance's KVM runtime
1434 1524

  
1435 1525
    """
1436
    kvm_cmd, kvm_nics, hvparams = kvm_runtime
1526
    kvm_cmd, kvm_nics, hvparams, block_devices = kvm_runtime
1527

  
1437 1528
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
1438
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
1529
    serialized_blockdevs = [(blk.ToDict(), link) for blk, link in block_devices]
1530
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams,
1531
                                      serialized_blockdevs))
1532

  
1439 1533
    self._WriteKVMRuntime(instance.name, serialized_form)
1440 1534

  
1441 1535
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
......
1444 1538
    """
1445 1539
    if not serialized_runtime:
1446 1540
      serialized_runtime = self._ReadKVMRuntime(instance.name)
1541

  
1447 1542
    loaded_runtime = serializer.Load(serialized_runtime)
1448
    kvm_cmd, serialized_nics, hvparams = loaded_runtime
1543
    if len(loaded_runtime)==3:
1544
      serialized_blockdevs = []
1545
      kvm_cmd, serialized_nics, hvparams = loaded_runtime
1546
    else:
1547
      kvm_cmd, serialized_nics, hvparams, serialized_blockdevs = loaded_runtime
1548

  
1449 1549
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
1450
    return (kvm_cmd, kvm_nics, hvparams)
1550
    block_devices = [(objects.Disk.FromDict(sdisk), link)
1551
                     for sdisk, link in serialized_blockdevs]
1552

  
1553
    return (kvm_cmd, kvm_nics, hvparams, block_devices)
1451 1554

  
1452 1555
  def _RunKVMCmd(self, name, kvm_cmd, tap_fds=None):
1453 1556
    """Run the KVM cmd and check for errors
......
1472 1575
    if not self._InstancePidAlive(name)[2]:
1473 1576
      raise errors.HypervisorError("Failed to start instance %s" % name)
1474 1577

  
1578
  # pylint: disable=R0914
1475 1579
  def _ExecuteKVMRuntime(self, instance, kvm_runtime, kvmhelp, incoming=None):
1476 1580
    """Execute a KVM cmd, after completing it with some last minute data.
1477 1581

  
......
1495 1599

  
1496 1600
    temp_files = []
1497 1601

  
1498
    kvm_cmd, kvm_nics, up_hvp = kvm_runtime
1602
    kvm_cmd, kvm_nics, up_hvp, block_devices = kvm_runtime
1499 1603
    # the first element of kvm_cmd is always the path to the kvm binary
1500 1604
    kvm_path = kvm_cmd[0]
1605

  
1606
    kvm_cmd_runtime = copy.deepcopy(kvm_cmd)
1607

  
1501 1608
    up_hvp = objects.FillDict(conf_hvp, up_hvp)
1502 1609

  
1503 1610
    # We know it's safe to run as a different user upon migration, so we'll use
......
1516 1623
      utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap)
1517 1624
      kvm_cmd.extend(["-k", keymap_path])
1518 1625

  
1626
    pci_reservations = bitarray(self._DEFAULT_PCI_RESERVATIONS)
1627

  
1628
    kvm_cmd = self._GenerateKVMBlockDevicesOptions(instance, kvm_cmd,
1629
                                                   block_devices,
1630
                                                   pci_reservations,
1631
                                                   kvmhelp)
1632

  
1519 1633
    # We have reasons to believe changing something like the nic driver/type
1520 1634
    # upon migration won't exactly fly with the instance kernel, so for nic
1521 1635
    # related parameters we'll use up_hvp
......
1556 1670
        tapfds.append(tapfd)
1557 1671
        taps.append(tapname)
1558 1672
        if kvm_supports_netdev:
1559
          nic_val = "%s,mac=%s,netdev=netdev%s" % (nic_model, nic.mac, nic_seq)
1560
          tap_val = "type=tap,id=netdev%s,fd=%d%s" % (nic_seq, tapfd, tap_extra)
1673
          nic_val = "%s,mac=%s" % (nic_model, nic.mac)
1674
          _UpdatePCISlots(nic, pci_reservations)
1675
          kvm_devid = _GenerateDeviceKVMId("NIC", nic)
1676
          netdev = kvm_devid or "netdev%d" % nic_seq
1677
          nic_val += (",netdev=%s" % netdev)
1678
          if kvm_devid:
1679
            nic_val += (",id=%s,bus=pci.0,addr=%s" % (kvm_devid, hex(nic.pci)))
1680
          tap_val = ("type=tap,id=%s,fd=%d%s" %
1681
                     (netdev, tapfd, tap_extra))
1561 1682
          kvm_cmd.extend(["-netdev", tap_val, "-device", nic_val])
1562 1683
        else:
1563 1684
          nic_val = "nic,vlan=%s,macaddr=%s,model=%s" % (nic_seq,
......
1680 1801
      # explicitly requested resume the vm status.
1681 1802
      self._CallMonitorCommand(instance.name, self._CONT_CMD)
1682 1803

  
1804
    kvm_runtime_with_pci_info = (kvm_cmd_runtime, kvm_nics,
1805
                                 up_hvp, block_devices)
1806
    return kvm_runtime_with_pci_info
1807

  
1683 1808
  def StartInstance(self, instance, block_devices, startup_paused):
1684 1809
    """Start an instance.
1685 1810

  
......
1690 1815
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices,
1691 1816
                                           startup_paused, kvmhelp)
1692 1817
    self._SaveKVMRuntime(instance, kvm_runtime)
1693
    self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp)
1818
    kvm_runtime_with_pci_info = self._ExecuteKVMRuntime(instance, kvm_runtime,
1819
                                                        kvmhelp)
1820
    self._SaveKVMRuntime(instance, kvm_runtime_with_pci_info)
1694 1821

  
1695 1822
  def _CallMonitorCommand(self, instance_name, command):
1696 1823
    """Invoke a command on the instance monitor.
......
1716 1843

  
1717 1844
    return result
1718 1845

  
1846
  def _AnnotateFreePCISlot(self, instance, dev):
1847
    """Get the first available pci slot of a runnung instance.
1848

  
1849
    """
1850
    slots = bitarray(32)
1851
    slots.setall(False) # pylint: disable=E1101
1852
    output = self._CallMonitorCommand(instance.name, self._INFO_PCI_CMD)
1853
    for line in output.stdout.splitlines():
1854
      match = self._INFO_PCI_RE.search(line)
1855
      if match:
1856
        slot = int(match.group(1))
1857
        slots[slot] = True
1858

  
1859
    [free] = slots.search(FREE, 1) # pylint: disable=E1101
1860
    if not free:
1861
      raise errors.HypervisorError("All PCI slots occupied")
1862

  
1863
    dev.pci = int(free)
1864

  
1865
  def _TryHotplug(self, instance, dev_type):
1866
    """Get QEMU version from the instance's monitor.
1867

  
1868
    Hotplug is supported for running instances and for versions >= 1.0.
1869
    """
1870
    if dev_type == "DISK":
1871
      hvp = instance.hvparams
1872
      security_model = hvparams[constants.HV_SECURITY_MODEL]
1873
      use_chroot = hvp[constants.HV_KVM_USE_CHROOT]
1874
      if use_chroot or security_model != constants.HT_SM_NONE:
1875
        return False
1876
    output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
1877
    #TODO: search for netdev_add, drive_add, device_add.....
1878
    match = self._INFO_VERSION_RE.search(output.stdout)
1879
    if not match:
1880
      return False
1881
    v_major, v_min, _, _ = match.groups()
1882
    return (v_major, v_min) >= (1, 0)
1883

  
1884
  def _CallMonitorHotplugCommand(self, name, cmd):
1885
    output = self._CallMonitorCommand(name, cmd)
1886
    #TODO: parse output and check if succeeded
1887
    for line in output.stdout.splitlines():
1888
      logging.info("%s", line)
1889

  
1890
  def HotplugDevice(self, instance, action, dev_type, device, extra, seq):
1891
    if self._TryHotplug(instance, dev_type):
1892
      (kvm_cmd, kvm_nics, hvparams, \
1893
        block_devices) = self._LoadKVMRuntime(instance)
1894
      if action == "ADD":
1895
        self._AnnotateFreePCISlot(instance, device)
1896
        kvm_devid = _GenerateDeviceKVMId(dev_type, device)
1897
        if dev_type == "DISK":
1898
          self._HotAddDisk(instance,
1899
                           device, extra, seq, kvm_devid, block_devices)
1900
        elif dev_type == "NIC" and nic_hotplug:
1901
          self._HotAddNic(instance, device, extra, seq, kvm_devid, kvm_nics)
1902
      elif action == "REMOVE":
1903
        kvm_devid = self._GetExistingDeviceKVMId(instance, dev_type, device)
1904
        if dev_type == "DISK":
1905
          self._HotDelDisk(instance,
1906
                           device, extra, seq, kvm_devid, block_devices)
1907
        elif dev_type == "NIC" and nic_hotplug:
1908
          self._HotDelNic(instance, device, extra, seq, kvm_devid, kvm_nics)
1909
      self._SaveKVMRuntime(instance,
1910
                          (kvm_cmd, kvm_nics, hvparams, block_devices))
1911

  
1912
  def _HotAddDisk(self, instance, disk, dev_path, _, kvm_devid, block_devices):
1913
    """Hotadd new disk to the VM
1914

  
1915
    """
1916
    command = ("drive_add dummy file=%s,if=none,id=%s,format=raw" %
1917
               (dev_path, kvm_devid))
1918
    self._CallMonitorHotplugCommand(instance.name, command)
1919
    command = ("device_add virtio-blk-pci,bus=pci.0,addr=%s,"
1920
               "drive=%s,id=%s"
1921
               % (hex(disk.pci), kvm_devid, kvm_devid))
1922
    self._CallMonitorHotplugCommand(instance.name, command)
1923
    block_devices.append((disk, dev_path))
1924

  
1925
  def _HotAddNic(self, instance, nic, _, seq, kvm_devid, kvm_nics):
1926
    """Hotadd new nic to the VM
1927

  
1928
    """
1929
    (tap, fd) = _OpenTap()
1930
    self._PassTapFd(instance, fd, nic)
1931
    command = ("netdev_add tap,id=%s,fd=%s" % (kvm_devid, kvm_devid))
1932
    self._CallMonitorHotplugCommand(instance.name, command)
1933
    command = ("device_add virtio-net-pci,bus=pci.0,addr=%s,mac=%s,"
1934
               "netdev=%s,id=%s"
1935
               % (hex(nic.pci), nic.mac, kvm_devid, kvm_devid))
1936
    self._CallMonitorHotplugCommand(instance.name, command)
1937
    self._ConfigureNIC(instance, seq, nic, tap)
1938
    utils.WriteFile(self._InstanceNICFile(instance.name, seq), data=tap)
1939
    kvm_nics.append(nic)
1940

  
1941
  def _HotDelDisk(self, instance, disk, _, __, kvm_devid, block_devices):
1942
    """Hotdel disk to the VM
1943

  
1944
    """
1945
    command = "device_del %s" % kvm_devid
1946
    self._CallMonitorHotplugCommand(instance.name, command)
1947
    #command = "drive_del %s" % uuid
1948
    #self._CallMonitorHotplugCommand(instance.name, command)
1949
    _RemoveFromRuntimeEntry(block_devices, disk, lambda x: [d for d, l in x])
1950

  
1951
  def _HotDelNic(self, instance, nic, _, __, kvm_devid, kvm_nics):
1952
    """Hotadd new nic to the VM
1953

  
1954
    """
1955
    command = "device_del %s" % kvm_devid
1956
    self._CallMonitorHotplugCommand(instance.name, command)
1957
    command = "netdev_del %s" % kvm_devid
1958
    self._CallMonitorHotplugCommand(instance.name, command)
1959
    _RemoveFromRuntimeEntry(kvm_nics, nic, lambda x: x)
1960

  
1961
  def _PassTapFd(self, instance, fd, nic):
1962
    monsock = utils.ShellQuote(self._InstanceMonitor(instance.name))
1963
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1964
    s.connect(monsock)
1965
    kvm_devid = _GenerateDeviceKVMId("NIC", nic)
1966
    command = "getfd %s\n" % kvm_devid
1967
    fds = [fd]
1968
    logging.info("%s", fds)
1969
    fdsend.sendfds(s, command, fds = fds)
1970
    s.close()
1971

  
1719 1972
  @classmethod
1720 1973
  def _ParseKVMVersion(cls, text):
1721 1974
    """Parse the KVM version from the --help output.
......
1831 2084
    self._SaveKVMRuntime(instance, kvm_runtime)
1832 2085
    kvmpath = instance.hvparams[constants.HV_KVM_PATH]
1833 2086
    kvmhelp = self._GetKVMOutput(kvmpath, self._KVMOPT_HELP)
1834
    self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp)
2087
    kvm_runtime_with_pci_info = \
2088
      self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp)
2089
    self._SaveKVMRuntime(instance, kvm_runtime_with_pci_info)
1835 2090

  
1836 2091
  def MigrationInfo(self, instance):
1837 2092
    """Get instance information to perform a migration.

Also available in: Unified diff