diff --git a/Makefile.am b/Makefile.am index b1c4090641ecc5662a1796898b51bb5bbcd03cd7..a08dc348e668b8f3e848537d34394fbbbb2bc445 100644 --- a/Makefile.am +++ b/Makefile.am @@ -295,6 +295,7 @@ lib/_autoconf.py: Makefile stamp-directories echo "FILE_STORAGE_DIR = '$(FILE_STORAGE_DIR)'"; \ echo "IALLOCATOR_SEARCH_PATH = [$(IALLOCATOR_SEARCH_PATH)]"; \ echo "KVM_PATH = '$(KVM_PATH)'"; \ + echo "KVM_MIGRATION_PORT = '$(KVM_MIGRATION_PORT)'"; \ echo "SOCAT_PATH = '$(SOCAT_PATH)'"; \ } > $@ diff --git a/configure.ac b/configure.ac index 3df6caafa9a7864e60977985d5f1c602f4b560ee..60d68a2c8f9b563174cd36eddcbd6adc5e35e60f 100644 --- a/configure.ac +++ b/configure.ac @@ -99,6 +99,16 @@ AC_ARG_WITH([kvm-path], [kvm_path="/usr/bin/kvm"]) AC_SUBST(KVM_PATH, $kvm_path) +# --with-kvm-migration-port=... +AC_ARG_WITH([kvm-migration-port], + [AS_HELP_STRING([--with-kvm-migration-port=PORT], + [tcp port used for kvm instance live migration] + [ (default is 8102)] + )], + [kvm_migration_port="$withval"], + [kvm_migration_port="8102"]) +AC_SUBST(KVM_MIGRATION_PORT, $kvm_migration_port) + # --with-socat-path=... AC_ARG_WITH([socat-path], [AS_HELP_STRING([--with-socat-path=PATH], diff --git a/lib/constants.py b/lib/constants.py index 7250b4f788a09b8ad7854a8c2d0470417881aadc..0c44675a8d6aee0acf68493ea2a2cd26b149dd76 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -134,6 +134,7 @@ XEN_INITRD = _autoconf.XEN_INITRD KVM_PATH = _autoconf.KVM_PATH SOCAT_PATH = _autoconf.SOCAT_PATH +KVM_MIGRATION_PORT = _autoconf.KVM_MIGRATION_PORT VALUE_DEFAULT = "default" VALUE_AUTO = "auto" diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index f0c61fbbca1a1f2a8c4ebc047c943e72a975ad7d..e2a13eef9ea78840de9c34d418125916114a0eaf 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -28,6 +28,7 @@ import os.path import re import tempfile import time +import logging from cStringIO import StringIO from ganeti import utils @@ -53,6 +54,9 @@ class KVMHypervisor(hv_base.BaseHypervisor): constants.HV_ACPI, ] + _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)', + re.M | re.I) + def __init__(self): hv_base.BaseHypervisor.__init__(self) # Let's make sure the directories we need exist, even if the RUN_DIR lives @@ -284,19 +288,23 @@ class KVMHypervisor(hv_base.BaseHypervisor): serialized_form = serializer.Dump((kvm_cmd, serialized_nics)) self._WriteKVMRuntime(instance.name, serialized_form) - def _LoadKVMRuntime(self, instance): + def _LoadKVMRuntime(self, instance, serialized_runtime=None): """Load an instance's KVM runtime """ - serialized_form = self._ReadKVMRuntime(instance.name) - loaded_runtime = serializer.Load(serialized_form) + if not serialized_runtime: + serialized_runtime = self._ReadKVMRuntime(instance.name) + loaded_runtime = serializer.Load(serialized_runtime) kvm_cmd, serialized_nics = loaded_runtime kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics] return (kvm_cmd, kvm_nics) - def _ExecuteKVMRuntime(self, instance, kvm_runtime): + def _ExecuteKVMRuntime(self, instance, kvm_runtime, 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) + """ pidfile, pid, alive = self._InstancePidAlive(instance.name) if alive: @@ -317,6 +325,10 @@ class KVMHypervisor(hv_base.BaseHypervisor): kvm_cmd.extend(['-net', 'tap,script=%s' % script]) temp_files.append(script) + if incoming: + target, port = incoming + kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)]) + result = utils.RunCmd(kvm_cmd) if result.failed: raise errors.HypervisorError("Failed to start instance %s: %s (%s)" % @@ -415,6 +427,95 @@ class KVMHypervisor(hv_base.BaseHypervisor): self._SaveKVMRuntime(instance, kvm_runtime) self._ExecuteKVMRuntime(instance, kvm_runtime) + def MigrationInfo(self, instance): + """Get instance information to perform a migration. + + @type instance: L{objects.Instance} + @param instance: instance to be migrated + @rtype: string + @return: content of the KVM runtime file + + """ + return self._ReadKVMRuntime(instance.name) + + def AcceptInstance(self, instance, info, target): + """Prepare to accept an instance. + + @type instance: L{objects.Instance} + @param instance: instance to be accepted + @type info: string + @param info: content of the KVM runtime file on the source node + @type target: string + @param target: target host (usually ip), on this node + + """ + kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info) + incoming_address = (target, constants.KVM_MIGRATION_PORT) + self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address) + + def FinalizeMigration(self, instance, info, success): + """Finalize an instance migration. + + Stop the incoming mode KVM. + + @type instance: L{objects.Instance} + @param instance: instance whose migration is being aborted + + """ + if success: + self._WriteKVMRuntime(instance.name, info) + else: + self.StopInstance(instance, force=True) + + def MigrateInstance(self, instance_name, target, live): + """Migrate an instance to a target node. + + The migration will not be attempted if the instance is not + currently running. + + @type instance_name: string + @param instance_name: name of the instance to be migrated + @type target: string + @param target: ip address of the target node + @type live: boolean + @param live: perform a live migration + + """ + pidfile, pid, alive = self._InstancePidAlive(instance_name) + if not alive: + raise errors.HypervisorError("Instance not running, cannot migrate") + + if not live: + self._CallMonitorCommand(instance_name, 'stop') + + migrate_command = ('migrate -d tcp:%s:%s' % + (target, constants.KVM_MIGRATION_PORT)) + self._CallMonitorCommand(instance_name, migrate_command) + + info_command = 'info migrate' + done = False + while not done: + result = self._CallMonitorCommand(instance_name, info_command) + match = self._MIGRATION_STATUS_RE.search(result.stdout) + if not match: + raise errors.HypervisorError("Unknown 'info migrate' result: %s" % + result.stdout) + else: + status = match.group(1) + if status == 'completed': + done = True + elif status == 'active': + time.sleep(2) + else: + logging.info("KVM: unknown migration status '%s'" % status) + time.sleep(2) + + utils.KillProcess(pid) + utils.RemoveFile(pidfile) + utils.RemoveFile(self._InstanceMonitor(instance_name)) + utils.RemoveFile(self._InstanceSerial(instance_name)) + utils.RemoveFile(self._InstanceKVMRuntime(instance_name)) + def GetNodeInfo(self): """Return information about the node.