Revision b693125f lib/hypervisor/hv_kvm.py

b/lib/hypervisor/hv_kvm.py
36 36
import shutil
37 37
import socket
38 38
import StringIO
39
try:
40
  import affinity
41
except ImportError:
42
  affinity = None
39 43

  
40 44
from ganeti import utils
41 45
from ganeti import constants
......
50 54

  
51 55

  
52 56
_KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
57
_KVM_START_PAUSED_FLAG = "-S"
53 58

  
54 59
# TUN/TAP driver constants, taken from <linux/if_tun.h>
55 60
# They are architecture-independent and already hardcoded in qemu-kvm source,
......
473 478

  
474 479
  _VERSION_RE = re.compile(r"\b(\d+)\.(\d+)\.(\d+)\b")
475 480

  
481
  _CPU_INFO_RE = re.compile(r"cpu\s+\#(\d+).*thread_id\s*=\s*(\d+)", re.I)
482
  _CPU_INFO_CMD = "info cpus"
483
  _CONT_CMD = "cont"
484

  
476 485
  ANCILLARY_FILES = [
477 486
    _KVM_NETWORK_SCRIPT,
478 487
    ]
......
742 751
                                   " Network configuration script output: %s" %
743 752
                                   (tap, result.fail_reason, result.output))
744 753

  
754
  @staticmethod
755
  def _VerifyAffinityPackage():
756
    if affinity is None:
757
      raise errors.HypervisorError("affinity Python package not"
758
        " found; cannot use CPU pinning under KVM")
759

  
760
  @staticmethod
761
  def _BuildAffinityCpuMask(cpu_list):
762
    """Create a CPU mask suitable for sched_setaffinity from a list of
763
    CPUs.
764

  
765
    See man taskset for more info on sched_setaffinity masks.
766
    For example: [ 0, 2, 5, 6 ] will return 101 (0x65, 0..01100101).
767

  
768
    @type cpu_list: list of int
769
    @param cpu_list: list of physical CPU numbers to map to vCPUs in order
770
    @rtype: int
771
    @return: a bit mask of CPU affinities
772

  
773
    """
774
    if cpu_list == constants.CPU_PINNING_OFF:
775
      return constants.CPU_PINNING_ALL_KVM
776
    else:
777
      return sum(2 ** cpu for cpu in cpu_list)
778

  
779
  @classmethod
780
  def _AssignCpuAffinity(cls, cpu_mask, process_id, thread_dict):
781
    """Change CPU affinity for running VM according to given CPU mask.
782

  
783
    @param cpu_mask: CPU mask as given by the user. e.g. "0-2,4:all:1,3"
784
    @type cpu_mask: string
785
    @param process_id: process ID of KVM process. Used to pin entire VM
786
                       to physical CPUs.
787
    @type process_id: int
788
    @param thread_dict: map of virtual CPUs to KVM thread IDs
789
    @type thread_dict: dict int:int
790

  
791
    """
792

  
793
    # Convert the string CPU mask to a list of list of int's
794
    cpu_list = utils.ParseMultiCpuMask(cpu_mask)
795

  
796
    if len(cpu_list) == 1:
797
      all_cpu_mapping = cpu_list[0]
798
      if all_cpu_mapping == constants.CPU_PINNING_OFF:
799
        # If CPU pinning has 1 entry that's "all", then do nothing
800
        pass
801
      else:
802
        # If CPU pinning has one non-all entry, map the entire VM to
803
        # one set of physical CPUs
804
        cls._VerifyAffinityPackage()
805
        affinity.set_process_affinity_mask(process_id,
806
          cls._BuildAffinityCpuMask(all_cpu_mapping))
807
    else:
808
      # The number of vCPUs mapped should match the number of vCPUs
809
      # reported by KVM. This was already verified earlier, so
810
      # here only as a sanity check.
811
      assert len(thread_dict) == len(cpu_list)
812
      cls._VerifyAffinityPackage()
813

  
814
      # For each vCPU, map it to the proper list of physical CPUs
815
      for vcpu, i in zip(cpu_list, range(len(cpu_list))):
816
        affinity.set_process_affinity_mask(thread_dict[i],
817
          cls._BuildAffinityCpuMask(vcpu))
818

  
819
  def _GetVcpuThreadIds(self, instance_name):
820
    """Get a mapping of vCPU no. to thread IDs for the instance
821

  
822
    @type instance_name: string
823
    @param instance_name: instance in question
824
    @rtype: dictionary of int:int
825
    @return: a dictionary mapping vCPU numbers to thread IDs
826

  
827
    """
828
    result = {}
829
    output = self._CallMonitorCommand(instance_name, self._CPU_INFO_CMD)
830
    for line in output.stdout.splitlines():
831
      match = self._CPU_INFO_RE.search(line)
832
      if not match:
833
        continue
834
      grp = map(int, match.groups())
835
      result[grp[0]] = grp[1]
836

  
837
    return result
838

  
839
  def _ExecuteCpuAffinity(self, instance_name, cpu_mask, startup_paused):
840
    """Complete CPU pinning and resume instance execution if needed.
841

  
842
    @type instance_name: string
843
    @param instance_name: name of instance
844
    @type cpu_mask: string
845
    @param cpu_mask: CPU pinning mask as entered by user
846
    @type startup_paused: bool
847
    @param startup_paused: was instance requested to pause before startup
848

  
849
    """
850
    try:
851
      # Get KVM process ID, to be used if need to pin entire VM
852
      _, pid, _ = self._InstancePidAlive(instance_name)
853
      # Get vCPU thread IDs, to be used if need to pin vCPUs separately
854
      thread_dict = self._GetVcpuThreadIds(instance_name)
855
      # Run CPU pinning, based on configured mask
856
      self._AssignCpuAffinity(cpu_mask, pid, thread_dict)
857

  
858
    finally:
859
      # To control CPU pinning, the VM was started frozen, so we need
860
      # to resume its execution, but only if freezing was not
861
      # explicitly requested.
862
      # Note: this is done even when an exception occurred so the VM
863
      # is not unintentionally frozen.
864
      if not startup_paused:
865
        self._CallMonitorCommand(instance_name, self._CONT_CMD)
866

  
745 867
  def ListInstances(self):
746 868
    """Get the list of running instances.
747 869

  
......
808 930
    kvm_cmd.extend(["-daemonize"])
809 931
    if not instance.hvparams[constants.HV_ACPI]:
810 932
      kvm_cmd.extend(["-no-acpi"])
811
    if startup_paused:
812
      kvm_cmd.extend(["-S"])
813 933
    if instance.hvparams[constants.HV_REBOOT_BEHAVIOR] == \
814 934
        constants.INSTANCE_REBOOT_EXIT:
815 935
      kvm_cmd.extend(["-no-reboot"])
......
822 942

  
823 943
    self.ValidateParameters(hvp)
824 944

  
945
    if startup_paused:
946
      kvm_cmd.extend([_KVM_START_PAUSED_FLAG])
947

  
825 948
    if hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED:
826 949
      kvm_cmd.extend(["-enable-kvm"])
827 950
    elif hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_DISABLED:
......
1249 1372
        continue
1250 1373
      self._ConfigureNIC(instance, nic_seq, nic, taps[nic_seq])
1251 1374

  
1375
    # Before running the KVM command, capture wether the instance is
1376
    # supposed to start paused. This is used later when changing CPU
1377
    # affinity in order to know whether to resume instance execution.
1378
    startup_paused = _KVM_START_PAUSED_FLAG in kvm_cmd
1379

  
1380
    # Note: CPU pinning is using up_hvp since changes take effect
1381
    # during instance startup anyway, and to avoid problems when soft
1382
    # rebooting the instance.
1383
    if up_hvp.get(constants.HV_CPU_MASK, None):
1384
      cpu_pinning = True
1385
      if not startup_paused:
1386
        kvm_cmd.extend([_KVM_START_PAUSED_FLAG])
1387

  
1252 1388
    if security_model == constants.HT_SM_POOL:
1253 1389
      ss = ssconf.SimpleStore()
1254 1390
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n")
......
1300 1436
    for filename in temp_files:
1301 1437
      utils.RemoveFile(filename)
1302 1438

  
1439
    # If requested, set CPU affinity and resume instance execution
1440
    if cpu_pinning:
1441
      self._ExecuteCpuAffinity(instance.name, up_hvp[constants.HV_CPU_MASK],
1442
                               startup_paused)
1443

  
1303 1444
  def StartInstance(self, instance, block_devices, startup_paused):
1304 1445
    """Start an instance.
1305 1446

  

Also available in: Unified diff