Revision 6bf6870b

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
  fdsend = None
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-%s-pci-%d" % (dev_type.lower(), dev.uuid.split("-")[0], 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
  try:
117
    [rem] = [x for x in fn(devices) if x.uuid == device.uuid]
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 == constants.HOTPLUG_NIC:
1075
      found = [n for n in kvm_nics
1076
               if n.uuid == dev.uuid]
1077
    elif dev_type == constants.HOTPLUG_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 == constants.HOTPLUG_DISK:
1871
      hvp = instance.hvparams
1872
      security_model = hvp[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
    """ Generic method to hotplug device
1892

  
1893
    Depending on action and dev_type invoke the coresponding method that
1894
    does the actual hotplug (via KVM monitor commands).
1895
    Before and after, do all hotplug related steps (e.g. check if hotplug
1896
    is possible, annotate pci slot to device and generate/get existing
1897
    KVM device ids, read/write runtime file)
1898

  
1899
    """
1900
    if self._TryHotplug(instance, dev_type):
1901
      (kvm_cmd, kvm_nics, hvparams, \
1902
        block_devices) = self._LoadKVMRuntime(instance)
1903
      if action == constants.HOTPLUG_ADD:
1904
        self._AnnotateFreePCISlot(instance, device)
1905
        kvm_devid = _GenerateDeviceKVMId(dev_type, device)
1906
        if dev_type == constants.HOTPLUG_DISK:
1907
          self._HotAddDisk(instance,
1908
                           device, extra, seq, kvm_devid, block_devices)
1909
        elif dev_type == constants.HOTPLUG_NIC and fdsend:
1910
          self._HotAddNic(instance, device, extra, seq, kvm_devid, kvm_nics)
1911
      elif action == constants.HOTPLUG_REMOVE:
1912
        kvm_devid = self._GetExistingDeviceKVMId(instance, dev_type, device)
1913
        if dev_type == constants.HOTPLUG_DISK:
1914
          self._HotDelDisk(instance,
1915
                           device, extra, seq, kvm_devid, block_devices)
1916
        elif dev_type == constants.HOTPLUG_NIC and fdsend:
1917
          self._HotDelNic(instance, device, extra, seq, kvm_devid, kvm_nics)
1918
      self._SaveKVMRuntime(instance,
1919
                          (kvm_cmd, kvm_nics, hvparams, block_devices))
1920
      time.sleep(2)
1921

  
1922
  def _HotAddDisk(self, instance, disk, dev_path, _, kvm_devid, block_devices):
1923
    """Hotplug/add new disk to and instance
1924

  
1925
    """
1926
    command = ("drive_add dummy file=%s,if=none,id=%s,format=raw" %
1927
               (dev_path, kvm_devid))
1928
    self._CallMonitorHotplugCommand(instance.name, command)
1929
    command = ("device_add virtio-blk-pci,bus=pci.0,addr=%s,"
1930
               "drive=%s,id=%s"
1931
               % (hex(disk.pci), kvm_devid, kvm_devid))
1932
    self._CallMonitorHotplugCommand(instance.name, command)
1933
    block_devices.append((disk, dev_path))
1934

  
1935
  def _HotAddNic(self, instance, nic, _, seq, kvm_devid, kvm_nics):
1936
    """Hotplug/add nic to an instance
1937

  
1938
    """
1939
    (tap, fd) = _OpenTap()
1940
    self._PassTapFd(instance, fd, nic)
1941
    command = ("netdev_add tap,id=%s,fd=%s" % (kvm_devid, kvm_devid))
1942
    self._CallMonitorHotplugCommand(instance.name, command)
1943
    command = ("device_add virtio-net-pci,bus=pci.0,addr=%s,mac=%s,"
1944
               "netdev=%s,id=%s"
1945
               % (hex(nic.pci), nic.mac, kvm_devid, kvm_devid))
1946
    self._CallMonitorHotplugCommand(instance.name, command)
1947
    self._ConfigureNIC(instance, seq, nic, tap)
1948
    utils.WriteFile(self._InstanceNICFile(instance.name, seq), data=tap)
1949
    kvm_nics.append(nic)
1950

  
1951
  def _HotDelDisk(self, instance, disk, _, __, kvm_devid, block_devices):
1952
    """Hotplug/remove disk from an instance
1953

  
1954
    """
1955
    command = "device_del %s" % kvm_devid
1956
    self._CallMonitorHotplugCommand(instance.name, command)
1957
    #command = "drive_del %s" % uuid
1958
    #self._CallMonitorHotplugCommand(instance.name, command)
1959
    _RemoveFromRuntimeEntry(block_devices, disk, lambda x: [d for d, l in x])
1960

  
1961
  def _HotDelNic(self, instance, nic, _, __, kvm_devid, kvm_nics):
1962
    """Hotplug/remove existing nic from an instance
1963

  
1964
    """
1965
    command = "device_del %s" % kvm_devid
1966
    self._CallMonitorHotplugCommand(instance.name, command)
1967
    command = "netdev_del %s" % kvm_devid
1968
    self._CallMonitorHotplugCommand(instance.name, command)
1969
    _RemoveFromRuntimeEntry(kvm_nics, nic, lambda x: x)
1970

  
1971
  def _PassTapFd(self, instance, fd, nic):
1972
    """Pass file descriptor to kvm process via monitor socket using SCM_RIGHTS
1973

  
1974
    """
1975
    monsock = utils.ShellQuote(self._InstanceMonitor(instance.name))
1976
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1977
    s.connect(monsock)
1978
    kvm_devid = _GenerateDeviceKVMId("NIC", nic)
1979
    command = "getfd %s\n" % kvm_devid
1980
    fds = [fd]
1981
    logging.info("%s", fds)
1982
    fdsend.sendfds(s, command, fds = fds)
1983
    s.close()
1984

  
1719 1985
  @classmethod
1720 1986
  def _ParseKVMVersion(cls, text):
1721 1987
    """Parse the KVM version from the --help output.
......
1831 2097
    self._SaveKVMRuntime(instance, kvm_runtime)
1832 2098
    kvmpath = instance.hvparams[constants.HV_KVM_PATH]
1833 2099
    kvmhelp = self._GetKVMOutput(kvmpath, self._KVMOPT_HELP)
1834
    self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp)
2100
    kvm_runtime_with_pci_info = \
2101
      self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp)
2102
    self._SaveKVMRuntime(instance, kvm_runtime_with_pci_info)
1835 2103

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

Also available in: Unified diff