Revision b693125f
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