X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/527c0cf7cf45cc4a3a1cd09fbc1d43f7eaa32dbf..a8e8c0c6afd0a8c124571d47b07b1998d2aefc78:/lib/hypervisor/hv_kvm.py diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index 664ec61..98b271f 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2008, 2009, 2010, 2011 Google Inc. +# Copyright (C) 2008, 2009, 2010, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -49,12 +49,13 @@ from ganeti import serializer from ganeti import objects from ganeti import uidpool from ganeti import ssconf -from ganeti.hypervisor import hv_base from ganeti import netutils +from ganeti import pathutils +from ganeti.hypervisor import hv_base from ganeti.utils import wrapper as utils_wrapper -_KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge" +_KVM_NETWORK_SCRIPT = pathutils.CONF_DIR + "/kvm-vif-bridge" _KVM_START_PAUSED_FLAG = "-S" # TUN/TAP driver constants, taken from @@ -67,6 +68,17 @@ IFF_TAP = 0x0002 IFF_NO_PI = 0x1000 IFF_VNET_HDR = 0x4000 +#: SPICE parameters which depend on L{constants.HV_KVM_SPICE_BIND} +_SPICE_ADDITIONAL_PARAMS = frozenset([ + constants.HV_KVM_SPICE_IP_VERSION, + constants.HV_KVM_SPICE_PASSWORD_FILE, + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR, + constants.HV_KVM_SPICE_JPEG_IMG_COMPR, + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR, + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION, + constants.HV_KVM_SPICE_USE_TLS, + ]) + def _ProbeTapVnetHdr(fd): """Check whether to enable the IFF_VNET_HDR flag. @@ -135,6 +147,31 @@ def _OpenTap(vnet_hdr=True): return (ifname, tapfd) +def _BuildNetworkEnv(name, network, gateway, network6, gateway6, + network_type, mac_prefix, tags, env): + """Build environment variables concerning a Network. + + """ + if name: + env["NETWORK_NAME"] = name + if network: + env["NETWORK_SUBNET"] = network + if gateway: + env["NETWORK_GATEWAY"] = gateway + if network6: + env["NETWORK_SUBNET6"] = network6 + if gateway6: + env["NETWORK_GATEWAY6"] = gateway6 + if mac_prefix: + env["NETWORK_MAC_PREFIX"] = mac_prefix + if network_type: + env["NETWORK_TYPE"] = network_type + if tags: + env["NETWORK_TAGS"] = " ".join(tags) + + return env + + class QmpMessage: """QEMU Messaging Protocol (QMP) message. @@ -404,7 +441,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ CAN_MIGRATE = True - _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor" + _ROOT_DIR = pathutils.RUN_DIR + "/kvm-hypervisor" _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids _UIDS_DIR = _ROOT_DIR + "/uid" # contains instances reserved uids _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets @@ -420,15 +457,17 @@ class KVMHypervisor(hv_base.BaseHypervisor): # a separate directory, called 'chroot-quarantine'. _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine" _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR, _NICS_DIR, - _CHROOT_DIR, _CHROOT_QUARANTINE_DIR] + _CHROOT_DIR, _CHROOT_QUARANTINE_DIR, _KEYMAP_DIR] PARAMETERS = { + constants.HV_KVM_PATH: hv_base.REQ_FILE_CHECK, constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK, constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK, constants.HV_ROOT_PATH: hv_base.NO_CHECK, constants.HV_KERNEL_ARGS: hv_base.NO_CHECK, constants.HV_ACPI: hv_base.NO_CHECK, constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK, + constants.HV_SERIAL_SPEED: hv_base.NO_CHECK, constants.HV_VNC_BIND_ADDRESS: (False, lambda x: (netutils.IP4Address.IsValid(x) or utils.IsNormAbsPath(x)), @@ -446,17 +485,17 @@ class KVMHypervisor(hv_base.BaseHypervisor): None, None), constants.HV_KVM_SPICE_PASSWORD_FILE: hv_base.OPT_FILE_CHECK, constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR: - hv_base.ParamInSet(False, - constants.HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS), + hv_base.ParamInSet( + False, constants.HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS), constants.HV_KVM_SPICE_JPEG_IMG_COMPR: - hv_base.ParamInSet(False, - constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS), + hv_base.ParamInSet( + False, constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS), constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: - hv_base.ParamInSet(False, - constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS), + hv_base.ParamInSet( + False, constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS), constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: - hv_base.ParamInSet(False, - constants.HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS), + hv_base.ParamInSet( + False, constants.HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS), constants.HV_KVM_SPICE_AUDIO_COMPR: hv_base.NO_CHECK, constants.HV_KVM_SPICE_USE_TLS: hv_base.NO_CHECK, constants.HV_KVM_SPICE_TLS_CIPHERS: hv_base.NO_CHECK, @@ -476,8 +515,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES), constants.HV_KEYMAP: hv_base.NO_CHECK, constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK, - constants.HV_MIGRATION_BANDWIDTH: hv_base.NO_CHECK, - constants.HV_MIGRATION_DOWNTIME: hv_base.NO_CHECK, + constants.HV_MIGRATION_BANDWIDTH: hv_base.REQ_NONNEGATIVE_INT_CHECK, + constants.HV_MIGRATION_DOWNTIME: hv_base.REQ_NONNEGATIVE_INT_CHECK, constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK, constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, constants.HV_DISK_CACHE: @@ -493,6 +532,15 @@ class KVMHypervisor(hv_base.BaseHypervisor): constants.HV_REBOOT_BEHAVIOR: hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS), constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK, + constants.HV_CPU_TYPE: hv_base.NO_CHECK, + constants.HV_CPU_CORES: hv_base.OPT_NONNEGATIVE_INT_CHECK, + constants.HV_CPU_THREADS: hv_base.OPT_NONNEGATIVE_INT_CHECK, + constants.HV_CPU_SOCKETS: hv_base.OPT_NONNEGATIVE_INT_CHECK, + constants.HV_SOUNDHW: hv_base.NO_CHECK, + constants.HV_USB_DEVICES: hv_base.NO_CHECK, + constants.HV_VGA: hv_base.NO_CHECK, + constants.HV_KVM_EXTRA: hv_base.NO_CHECK, + constants.HV_KVM_MACHINE_VERSION: hv_base.NO_CHECK, } _MIGRATION_STATUS_RE = re.compile("Migration\s+status:\s+(\w+)", @@ -505,12 +553,27 @@ class KVMHypervisor(hv_base.BaseHypervisor): _MIGRATION_INFO_MAX_BAD_ANSWERS = 5 _MIGRATION_INFO_RETRY_DELAY = 2 - _VERSION_RE = re.compile(r"\b(\d+)\.(\d+)\.(\d+)\b") + _VERSION_RE = re.compile(r"\b(\d+)\.(\d+)(\.(\d+))?\b") _CPU_INFO_RE = re.compile(r"cpu\s+\#(\d+).*thread_id\s*=\s*(\d+)", re.I) _CPU_INFO_CMD = "info cpus" _CONT_CMD = "cont" + _DEFAULT_MACHINE_VERSION_RE = re.compile(r"^(\S+).*\(default\)", re.M) + _CHECK_MACHINE_VERSION_RE = \ + staticmethod(lambda x: re.compile(r"^(%s)[ ]+.*PC" % x, re.M)) + + _QMP_RE = re.compile(r"^-qmp\s", re.M) + _SPICE_RE = re.compile(r"^-spice\s", re.M) + _VHOST_RE = re.compile(r"^-net\s.*,vhost=on|off", re.M) + _ENABLE_KVM_RE = re.compile(r"^-enable-kvm\s", re.M) + _DISABLE_KVM_RE = re.compile(r"^-disable-kvm\s", re.M) + _NETDEV_RE = re.compile(r"^-netdev\s", re.M) + # match -drive.*boot=on|off on different lines, but in between accept only + # dashes not preceeded by a new line (which would mean another option + # different than -drive is starting) + _BOOT_RE = re.compile(r"^-drive\s([^-]|(? constants.VNC_BASE_PORT: @@ -1131,22 +1240,9 @@ class KVMHypervisor(hv_base.BaseHypervisor): vnc_arg = "unix:%s/%s.vnc" % (vnc_bind_address, instance.name) kvm_cmd.extend(["-vnc", vnc_arg]) - else: - kvm_cmd.extend(["-nographic"]) - - monitor_dev = ("unix:%s,server,nowait" % - self._InstanceMonitor(instance.name)) - kvm_cmd.extend(["-monitor", monitor_dev]) - if hvp[constants.HV_SERIAL_CONSOLE]: - serial_dev = ("unix:%s,server,nowait" % - self._InstanceSerial(instance.name)) - kvm_cmd.extend(["-serial", serial_dev]) - else: - kvm_cmd.extend(["-serial", "none"]) - - spice_bind = hvp[constants.HV_KVM_SPICE_BIND] - spice_ip_version = None - if spice_bind: + elif spice_bind: + # FIXME: this is wrong here; the iface ip address differs + # between systems, so it should be done in _ExecuteKVMRuntime if netutils.IsValidInterface(spice_bind): # The user specified a network interface, we have to figure out the IP # address. @@ -1186,10 +1282,12 @@ class KVMHypervisor(hv_base.BaseHypervisor): spice_arg = "addr=%s" % spice_address if hvp[constants.HV_KVM_SPICE_USE_TLS]: - spice_arg = "%s,tls-port=%s,x509-cacert-file=%s" % (spice_arg, - instance.network_port, constants.SPICE_CACERT_FILE) - spice_arg = "%s,x509-key-file=%s,x509-cert-file=%s" % (spice_arg, - constants.SPICE_CERT_FILE, constants.SPICE_CERT_FILE) + spice_arg = ("%s,tls-port=%s,x509-cacert-file=%s" % + (spice_arg, instance.network_port, + pathutils.SPICE_CACERT_FILE)) + spice_arg = ("%s,x509-key-file=%s,x509-cert-file=%s" % + (spice_arg, pathutils.SPICE_CERT_FILE, + pathutils.SPICE_CERT_FILE)) tls_ciphers = hvp[constants.HV_KVM_SPICE_TLS_CIPHERS] if tls_ciphers: spice_arg = "%s,tls-ciphers=%s" % (spice_arg, tls_ciphers) @@ -1223,12 +1321,19 @@ class KVMHypervisor(hv_base.BaseHypervisor): spice_arg = "%s,playback-compression=off" % spice_arg if not hvp[constants.HV_KVM_SPICE_USE_VDAGENT]: spice_arg = "%s,agent-mouse=off" % spice_arg + else: + # Enable the spice agent communication channel between the host and the + # agent. + kvm_cmd.extend(["-device", "virtio-serial-pci"]) + kvm_cmd.extend(["-device", "virtserialport,chardev=spicechannel0," + "name=com.redhat.spice.0"]) + kvm_cmd.extend(["-chardev", "spicevmc,id=spicechannel0,name=vdagent"]) logging.info("KVM: SPICE will listen on port %s", instance.network_port) kvm_cmd.extend(["-spice", spice_arg]) - # Tell kvm to use the paravirtualized graphic card, optimized for SPICE - kvm_cmd.extend(["-vga", "qxl"]) + else: + kvm_cmd.extend(["-nographic"]) if hvp[constants.HV_USE_LOCALTIME]: kvm_cmd.extend(["-localtime"]) @@ -1236,6 +1341,29 @@ class KVMHypervisor(hv_base.BaseHypervisor): if hvp[constants.HV_KVM_USE_CHROOT]: kvm_cmd.extend(["-chroot", self._InstanceChrootDir(instance.name)]) + # Add qemu-KVM -cpu param + if hvp[constants.HV_CPU_TYPE]: + kvm_cmd.extend(["-cpu", hvp[constants.HV_CPU_TYPE]]) + + # As requested by music lovers + if hvp[constants.HV_SOUNDHW]: + kvm_cmd.extend(["-soundhw", hvp[constants.HV_SOUNDHW]]) + + # Pass a -vga option if requested, or if spice is used, for backwards + # compatibility. + if hvp[constants.HV_VGA]: + kvm_cmd.extend(["-vga", hvp[constants.HV_VGA]]) + elif spice_bind: + kvm_cmd.extend(["-vga", "qxl"]) + + # Various types of usb devices, comma separated + if hvp[constants.HV_USB_DEVICES]: + for dev in hvp[constants.HV_USB_DEVICES].split(","): + kvm_cmd.extend(["-usbdevice", dev]) + + if hvp[constants.HV_KVM_EXTRA]: + kvm_cmd.extend([hvp[constants.HV_KVM_EXTRA]]) + # Save the current instance nics, but defer their expansion as parameters, # as we'll need to generate executable temp files for them. kvm_nics = instance.nics @@ -1306,11 +1434,13 @@ class KVMHypervisor(hv_base.BaseHypervisor): if not self._InstancePidAlive(name)[2]: raise errors.HypervisorError("Failed to start instance %s" % name) - def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None): - """Execute a KVM cmd, after completing it with some last minute data + def _ExecuteKVMRuntime(self, instance, kvm_runtime, kvmhelp, incoming=None): + """Execute a KVM cmd, after completing it with some last minute data. @type incoming: tuple of strings @param incoming: (target_host_ip, port) + @type kvmhelp: string + @param kvmhelp: output of kvm --help """ # Small _ExecuteKVMRuntime hv parameters programming howto: @@ -1330,7 +1460,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): kvm_cmd, kvm_nics, up_hvp = kvm_runtime up_hvp = objects.FillDict(conf_hvp, up_hvp) - _, v_major, v_min, _ = self._GetKVMVersion() + _, v_major, v_min, _ = self._ParseKVMVersion(kvmhelp) # We know it's safe to run as a different user upon migration, so we'll use # the latest conf, from conf_hvp. @@ -1338,6 +1468,16 @@ class KVMHypervisor(hv_base.BaseHypervisor): if security_model == constants.HT_SM_USER: kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]]) + keymap = conf_hvp[constants.HV_KEYMAP] + if keymap: + keymap_path = self._InstanceKeymapFile(name) + # If a keymap file is specified, KVM won't use its internal defaults. By + # first including the "en-us" layout, an error on loading the actual + # layout (e.g. because it can't be found) won't lead to a non-functional + # keyboard. A keyboard with incorrect keys is still better than none. + utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap) + kvm_cmd.extend(["-k", keymap_path]) + # We have reasons to believe changing something like the nic driver/type # upon migration won't exactly fly with the instance kernel, so for nic # related parameters we'll use up_hvp @@ -1358,12 +1498,12 @@ class KVMHypervisor(hv_base.BaseHypervisor): nic_model = "virtio" if up_hvp[constants.HV_VHOST_NET]: - # vhost_net is only available from version 0.13.0 or newer - if (v_major, v_min) >= (0, 13): + # check for vhost_net support + if self._VHOST_RE.search(kvmhelp): tap_extra = ",vhost=on" else: raise errors.HypervisorError("vhost_net is configured" - " but it is not available") + " but it is not available") else: nic_model = nic_type @@ -1371,7 +1511,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): tapname, tapfd = _OpenTap(vnet_hdr) tapfds.append(tapfd) taps.append(tapname) - if (v_major, v_min) >= (0, 12): + if self._NETDEV_RE.search(kvmhelp): nic_val = "%s,mac=%s,netdev=netdev%s" % (nic_model, nic.mac, nic_seq) tap_val = "type=tap,id=netdev%s,fd=%d%s" % (nic_seq, tapfd, tap_extra) kvm_cmd.extend(["-netdev", tap_val, "-device", nic_val]) @@ -1402,10 +1542,10 @@ class KVMHypervisor(hv_base.BaseHypervisor): constants.SECURE_DIR_MODE)]) # Automatically enable QMP if version is >= 0.14 - if (v_major, v_min) >= (0, 14): + if self._QMP_RE.search(kvmhelp): logging.debug("Enabling QMP") kvm_cmd.extend(["-qmp", "unix:%s,server,nowait" % - self._InstanceQmpMonitor(instance.name)]) + self._InstanceQmpMonitor(instance.name)]) # Configure the network now for starting instances and bridged interfaces, # during FinalizeMigration for incoming instances' routed interfaces @@ -1419,6 +1559,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): # instance is not already paused and if we are not going to accept a # migrating instance. In the latter case, pausing is not needed. start_kvm_paused = not (_KVM_START_PAUSED_FLAG in kvm_cmd) and not incoming + if start_kvm_paused: + kvm_cmd.extend([_KVM_START_PAUSED_FLAG]) # Note: CPU pinning is using up_hvp since changes take effect # during instance startup anyway, and to avoid problems when soft @@ -1426,8 +1568,6 @@ class KVMHypervisor(hv_base.BaseHypervisor): cpu_pinning = False if up_hvp.get(constants.HV_CPU_MASK, None): cpu_pinning = True - if start_kvm_paused: - kvm_cmd.extend([_KVM_START_PAUSED_FLAG]) if security_model == constants.HT_SM_POOL: ss = ssconf.SimpleStore() @@ -1484,26 +1624,29 @@ class KVMHypervisor(hv_base.BaseHypervisor): # If requested, set CPU affinity and resume instance execution if cpu_pinning: - try: - self._ExecuteCpuAffinity(instance.name, up_hvp[constants.HV_CPU_MASK]) - finally: - if start_kvm_paused: - # To control CPU pinning, the VM was started frozen, so we need - # to resume its execution, but only if freezing was not - # explicitly requested. - # Note: this is done even when an exception occurred so the VM - # is not unintentionally frozen. - self._CallMonitorCommand(instance.name, self._CONT_CMD) + self._ExecuteCpuAffinity(instance.name, up_hvp[constants.HV_CPU_MASK]) + + start_memory = self._InstanceStartupMemory(instance) + if start_memory < instance.beparams[constants.BE_MAXMEM]: + self.BalloonInstanceMemory(instance, start_memory) + + if start_kvm_paused: + # To control CPU pinning, ballooning, and vnc/spice passwords + # the VM was started in a frozen state. If freezing was not + # explicitly requested resume the vm status. + self._CallMonitorCommand(instance.name, self._CONT_CMD) def StartInstance(self, instance, block_devices, startup_paused): """Start an instance. """ self._CheckDown(instance.name) + kvmpath = instance.hvparams[constants.HV_KVM_PATH] + kvmhelp = self._GetKVMHelpOutput(kvmpath) kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, - startup_paused) + startup_paused, kvmhelp) self._SaveKVMRuntime(instance, kvm_runtime) - self._ExecuteKVMRuntime(instance, kvm_runtime) + self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp) def _CallMonitorCommand(self, instance_name, command): """Invoke a command on the instance monitor. @@ -1524,22 +1667,75 @@ class KVMHypervisor(hv_base.BaseHypervisor): return result @classmethod - def _GetKVMVersion(cls): - """Return the installed KVM version. + def _ParseKVMVersion(cls, text): + """Parse the KVM version from the --help output. + @type text: string + @param text: output of kvm --help @return: (version, v_maj, v_min, v_rev) - @raise L{errors.HypervisorError}: when the KVM version cannot be retrieved + @raise errors.HypervisorError: when the KVM version cannot be retrieved """ - result = utils.RunCmd([constants.KVM_PATH, "--help"]) - if result.failed: - raise errors.HypervisorError("Unable to get KVM version") - match = cls._VERSION_RE.search(result.output.splitlines()[0]) + match = cls._VERSION_RE.search(text.splitlines()[0]) if not match: raise errors.HypervisorError("Unable to get KVM version") - return (match.group(0), int(match.group(1)), int(match.group(2)), - int(match.group(3))) + v_all = match.group(0) + v_maj = int(match.group(1)) + v_min = int(match.group(2)) + if match.group(4): + v_rev = int(match.group(4)) + else: + v_rev = 0 + return (v_all, v_maj, v_min, v_rev) + + @classmethod + def _GetKVMHelpOutput(cls, kvm_path): + """Return the KVM help output. + + @return: output of kvm --help + @raise errors.HypervisorError: when the KVM help output cannot be retrieved + + """ + result = utils.RunCmd([kvm_path, "--help"]) + if result.failed: + raise errors.HypervisorError("Unable to get KVM help output") + return result.output + + @classmethod + def _GetKVMVersion(cls, kvm_path): + """Return the installed KVM version. + + @return: (version, v_maj, v_min, v_rev) + @raise errors.HypervisorError: when the KVM version cannot be retrieved + + """ + return cls._ParseKVMVersion(cls._GetKVMHelpOutput(kvm_path)) + + @classmethod + def _GetKVMSupportedMachinesOutput(cls, kvm_path): + """Return the output regarding supported machine versions. + + @return: output of kvm -M ? + @raise errors.HypervisorError: when the KVM help output cannot be retrieved + + """ + result = utils.RunCmd([kvm_path, "-M", "?"]) + if result.failed: + raise errors.HypervisorError("Unable to get kvm -M ? output") + return result.output + + @classmethod + def _GetDefaultMachineVersion(cls, kvm_path): + """Return the default hardware revision (e.g. pc-1.1) + + """ + output = cls._GetKVMSupportedMachinesOutput(kvm_path) + match = cls._DEFAULT_MACHINE_VERSION_RE.search(output) + if match: + return match.group(1) + else: + return "pc" def StopInstance(self, instance, force=False, retry=False, name=None): """Stop an instance. @@ -1587,7 +1783,9 @@ class KVMHypervisor(hv_base.BaseHypervisor): self.StopInstance(instance, force=True) # ...and finally we can save it again, and execute it... self._SaveKVMRuntime(instance, kvm_runtime) - self._ExecuteKVMRuntime(instance, kvm_runtime) + kvmpath = instance.hvparams[constants.HV_KVM_PATH] + kvmhelp = self._GetKVMHelpOutput(kvmpath) + self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp) def MigrationInfo(self, instance): """Get instance information to perform a migration. @@ -1613,7 +1811,10 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info) incoming_address = (target, instance.hvparams[constants.HV_MIGRATION_PORT]) - self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address) + kvmpath = instance.hvparams[constants.HV_KVM_PATH] + kvmhelp = self._GetKVMHelpOutput(kvmpath) + self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp, + incoming=incoming_address) def FinalizeMigrationDst(self, instance, info, success): """Finalize the instance migration on the target node. @@ -1671,11 +1872,11 @@ class KVMHypervisor(hv_base.BaseHypervisor): self._CallMonitorCommand(instance_name, "stop") migrate_command = ("migrate_set_speed %dm" % - instance.hvparams[constants.HV_MIGRATION_BANDWIDTH]) + instance.hvparams[constants.HV_MIGRATION_BANDWIDTH]) self._CallMonitorCommand(instance_name, migrate_command) migrate_command = ("migrate_set_downtime %dms" % - instance.hvparams[constants.HV_MIGRATION_DOWNTIME]) + instance.hvparams[constants.HV_MIGRATION_DOWNTIME]) self._CallMonitorCommand(instance_name, migrate_command) migrate_command = "migrate -d tcp:%s:%s" % (target, port) @@ -1735,8 +1936,18 @@ class KVMHypervisor(hv_base.BaseHypervisor): time.sleep(self._MIGRATION_INFO_RETRY_DELAY) - return objects.MigrationStatus(status=constants.HV_MIGRATION_FAILED, - info="Too many 'info migrate' broken answers") + return objects.MigrationStatus(status=constants.HV_MIGRATION_FAILED) + + def BalloonInstanceMemory(self, instance, mem): + """Balloon an instance memory to a certain value. + + @type instance: L{objects.Instance} + @param instance: instance to be accepted + @type mem: int + @param mem: actual memory size to use for instance runtime + + """ + self._CallMonitorCommand(instance.name, "balloon %d" % mem) def GetNodeInfo(self): """Return information about the node. @@ -1750,7 +1961,10 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ result = self.GetLinuxNodeInfo() - _, v_major, v_min, v_rev = self._GetKVMVersion() + # FIXME: this is the global kvm version, but the actual version can be + # customized as an hv parameter. we should use the nodegroup's default kvm + # path parameter here. + _, v_major, v_min, v_rev = self._GetKVMVersion(constants.KVM_PATH) result[constants.HV_NODEINFO_KEY_VERSION] = (v_major, v_min, v_rev) return result @@ -1760,7 +1974,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ if hvparams[constants.HV_SERIAL_CONSOLE]: - cmd = [constants.KVM_CONSOLE_WRAPPER, + cmd = [pathutils.KVM_CONSOLE_WRAPPER, constants.SOCAT_PATH, utils.ShellQuote(instance.name), utils.ShellQuote(cls._InstanceMonitor(instance.name)), "STDIO,%s" % cls._SocatUnixConsoleParams(), @@ -1768,7 +1982,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): return objects.InstanceConsole(instance=instance.name, kind=constants.CONS_SSH, host=instance.primary_node, - user=constants.GANETI_RUNAS, + user=constants.SSH_CONSOLE_USER, command=cmd) vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS] @@ -1798,6 +2012,9 @@ class KVMHypervisor(hv_base.BaseHypervisor): Check that the binary exists. """ + # FIXME: this is the global kvm version, but the actual version can be + # customized as an hv parameter. we should use the nodegroup's default kvm + # path parameter here. if not os.path.exists(constants.KVM_PATH): return "The kvm binary ('%s') does not exist." % constants.KVM_PATH if not os.path.exists(constants.SOCAT_PATH): @@ -1826,6 +2043,14 @@ class KVMHypervisor(hv_base.BaseHypervisor): (constants.HV_VNC_X509, constants.HV_VNC_X509_VERIFY)) + if hvparams[constants.HV_SERIAL_CONSOLE]: + serial_speed = hvparams[constants.HV_SERIAL_SPEED] + valid_speeds = constants.VALID_SERIAL_SPEEDS + if not serial_speed or serial_speed not in valid_speeds: + raise errors.HypervisorError("Invalid serial console speed, must be" + " one of: %s" % + utils.CommaJoin(valid_speeds)) + boot_order = hvparams[constants.HV_BOOT_ORDER] if (boot_order == constants.HT_BO_CDROM and not hvparams[constants.HV_CDROM_IMAGE_PATH]): @@ -1863,16 +2088,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): else: # All the other SPICE parameters depend on spice_bind being set. Raise an # error if any of them is set without it. - spice_additional_params = frozenset([ - constants.HV_KVM_SPICE_IP_VERSION, - constants.HV_KVM_SPICE_PASSWORD_FILE, - constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR, - constants.HV_KVM_SPICE_JPEG_IMG_COMPR, - constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR, - constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION, - constants.HV_KVM_SPICE_USE_TLS, - ]) - for param in spice_additional_params: + for param in _SPICE_ADDITIONAL_PARAMS: if hvparams[param]: raise errors.HypervisorError("spice: %s requires %s to be set" % (param, constants.HV_KVM_SPICE_BIND)) @@ -1888,6 +2104,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ super(KVMHypervisor, cls).ValidateParameters(hvparams) + kvm_path = hvparams[constants.HV_KVM_PATH] + security_model = hvparams[constants.HV_SECURITY_MODEL] if security_model == constants.HT_SM_USER: username = hvparams[constants.HV_SECURITY_DOMAIN] @@ -1905,11 +2123,11 @@ class KVMHypervisor(hv_base.BaseHypervisor): " only one of them can be used at a" " given time.") - # KVM version should be >= 0.14.0 - _, v_major, v_min, _ = cls._GetKVMVersion() - if (v_major, v_min) < (0, 14): + # check that KVM supports SPICE + kvmhelp = cls._GetKVMHelpOutput(kvm_path) + if not cls._SPICE_RE.search(kvmhelp): raise errors.HypervisorError("spice is configured, but it is not" - " available in versions of KVM < 0.14") + " supported according to kvm --help") # if spice_bind is not an IP address, it must be a valid interface bound_to_addr = (netutils.IP4Address.IsValid(spice_bind) @@ -1919,6 +2137,13 @@ class KVMHypervisor(hv_base.BaseHypervisor): " a valid IP address or interface name" % constants.HV_KVM_SPICE_BIND) + machine_version = hvparams[constants.HV_KVM_MACHINE_VERSION] + if machine_version: + output = cls._GetKVMSupportedMachinesOutput(kvm_path) + if not cls._CHECK_MACHINE_VERSION_RE(machine_version).search(output): + raise errors.HypervisorError("Unsupported machine version: %s" % + machine_version) + @classmethod def PowercycleNode(cls): """KVM powercycle, just a wrapper over Linux powercycle.