LUSetInstanceParams: save cluster
[ganeti-local] / lib / hypervisor / hv_kvm.py
index c40b5e3..e211475 100644 (file)
@@ -27,39 +27,108 @@ import os
 import os.path
 import re
 import tempfile
 import os.path
 import re
 import tempfile
+import time
+import logging
 from cStringIO import StringIO
 
 from ganeti import utils
 from ganeti import constants
 from ganeti import errors
 from cStringIO import StringIO
 
 from ganeti import utils
 from ganeti import constants
 from ganeti import errors
+from ganeti import serializer
+from ganeti import objects
 from ganeti.hypervisor import hv_base
 
 
 class KVMHypervisor(hv_base.BaseHypervisor):
 from ganeti.hypervisor import hv_base
 
 
 class KVMHypervisor(hv_base.BaseHypervisor):
-  """Fake hypervisor interface.
+  """KVM hypervisor interface"""
 
 
-  This can be used for testing the ganeti code without having to have
-  a real virtualisation software installed.
-
-  """
   _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
   _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
-  _PIDS_DIR = _ROOT_DIR + "/pid"
-  _CTRL_DIR = _ROOT_DIR + "/ctrl"
-  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR]
-
-  PARAMETERS = [
-    constants.HV_KERNEL_PATH,
-    constants.HV_INITRD_PATH,
-    constants.HV_ACPI,
+  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
+  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
+  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
+  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
+
+  PARAMETERS = {
+    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_VNC_BIND_ADDRESS: \
+    (False, lambda x: (utils.IsValidIP(x) or utils.IsAbsNormPath(x)),
+     "the VNC bind address must be either a valid IP address or an absolute"
+     " pathname", None, None),
+    constants.HV_VNC_TLS: hv_base.NO_CHECK,
+    constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
+    constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
+    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
+    constants.HV_BOOT_ORDER: \
+    hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
+    constants.HV_NIC_TYPE: \
+    hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
+    constants.HV_DISK_TYPE: \
+    hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
+    constants.HV_USB_MOUSE: \
+    hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
+    }
+
+  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
+                                    re.M | re.I)
+
+  _KVM_NETWORK_SCRIPT = constants.SYSCONFDIR + "/ganeti/kvm-vif-bridge"
+
+  ANCILLARY_FILES = [
+    _KVM_NETWORK_SCRIPT,
     ]
 
   def __init__(self):
     hv_base.BaseHypervisor.__init__(self)
     # Let's make sure the directories we need exist, even if the RUN_DIR lives
     # in a tmpfs filesystem or has been otherwise wiped out.
     ]
 
   def __init__(self):
     hv_base.BaseHypervisor.__init__(self)
     # Let's make sure the directories we need exist, even if the RUN_DIR lives
     # in a tmpfs filesystem or has been otherwise wiped out.
-    for mydir in self._DIRS:
-      if not os.path.exists(mydir):
-        os.mkdir(mydir)
+    dirs = [(dir, constants.RUN_DIRS_MODE) for dir in self._DIRS]
+    utils.EnsureDirs(dirs)
+
+  def _InstancePidAlive(self, instance_name):
+    """Returns the instance pid and pidfile
+
+    """
+    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
+    pid = utils.ReadPidFile(pidfile)
+    alive = utils.IsProcessAlive(pid)
+
+    return (pidfile, pid, alive)
+
+  @classmethod
+  def _InstanceMonitor(cls, instance_name):
+    """Returns the instance monitor socket name
+
+    """
+    return '%s/%s.monitor' % (cls._CTRL_DIR, instance_name)
+
+  @classmethod
+  def _InstanceSerial(cls, instance_name):
+    """Returns the instance serial socket name
+
+    """
+    return '%s/%s.serial' % (cls._CTRL_DIR, instance_name)
+
+  @classmethod
+  def _InstanceKVMRuntime(cls, instance_name):
+    """Returns the instance KVM runtime filename
+
+    """
+    return '%s/%s.runtime' % (cls._CONF_DIR, instance_name)
+
+  @classmethod
+  def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
+    """Removes an instance's rutime sockets/files.
+
+    """
+    utils.RemoveFile(pidfile)
+    utils.RemoveFile(cls._InstanceMonitor(instance_name))
+    utils.RemoveFile(cls._InstanceSerial(instance_name))
+    utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
 
   def _WriteNetScript(self, instance, seq, nic):
     """Write a script to connect a net interface to the proper bridge.
 
   def _WriteNetScript(self, instance, seq, nic):
     """Write a script to connect a net interface to the proper bridge.
@@ -85,9 +154,9 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     script.write("export BRIDGE=%s\n" % nic.bridge)
     script.write("export INTERFACE=$1\n")
     # TODO: make this configurable at ./configure time
     script.write("export BRIDGE=%s\n" % nic.bridge)
     script.write("export INTERFACE=$1\n")
     # TODO: make this configurable at ./configure time
-    script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
+    script.write("if [ -x '%s' ]; then\n" % self._KVM_NETWORK_SCRIPT)
     script.write("  # Execute the user-specific vif file\n")
     script.write("  # Execute the user-specific vif file\n")
-    script.write("  /etc/ganeti/kvm-vif-bridge\n")
+    script.write("  %s\n" % self._KVM_NETWORK_SCRIPT)
     script.write("else\n")
     script.write("  # Connect the interface to the bridge\n")
     script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
     script.write("else\n")
     script.write("  # Connect the interface to the bridge\n")
     script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
@@ -124,9 +193,8 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     @return: tuple (name, id, memory, vcpus, stat, times)
 
     """
     @return: tuple (name, id, memory, vcpus, stat, times)
 
     """
-    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
-    pid = utils.ReadPidFile(pidfile)
-    if not utils.IsProcessAlive(pid):
+    pidfile, pid, alive = self._InstancePidAlive(instance_name)
+    if not alive:
       return None
 
     cmdline_file = "/proc/%s/cmdline" % pid
       return None
 
     cmdline_file = "/proc/%s/cmdline" % pid
@@ -136,7 +204,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
         cmdline = fh.read()
       finally:
         fh.close()
         cmdline = fh.read()
       finally:
         fh.close()
-    except IOError, err:
+    except EnvironmentError, err:
       raise errors.HypervisorError("Failed to list instance %s: %s" %
                                    (instance_name, err))
 
       raise errors.HypervisorError("Failed to list instance %s: %s" %
                                    (instance_name, err))
 
@@ -149,9 +217,9 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     while arg_list:
       arg =  arg_list.pop(0)
       if arg == '-m':
     while arg_list:
       arg =  arg_list.pop(0)
       if arg == '-m':
-        memory = arg_list.pop(0)
+        memory = int(arg_list.pop(0))
       elif arg == '-smp':
       elif arg == '-smp':
-        vcpus = arg_list.pop(0)
+        vcpus = int(arg_list.pop(0))
 
     return (instance_name, pid, memory, vcpus, stat, times)
 
 
     return (instance_name, pid, memory, vcpus, stat, times)
 
@@ -165,20 +233,20 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     for name in os.listdir(self._PIDS_DIR):
       filename = "%s/%s" % (self._PIDS_DIR, name)
       if utils.IsProcessAlive(utils.ReadPidFile(filename)):
     for name in os.listdir(self._PIDS_DIR):
       filename = "%s/%s" % (self._PIDS_DIR, name)
       if utils.IsProcessAlive(utils.ReadPidFile(filename)):
-        data.append(self.GetInstanceInfo(name))
+        try:
+          info = self.GetInstanceInfo(name)
+        except errors.HypervisorError, err:
+          continue
+        if info:
+          data.append(info)
 
     return data
 
 
     return data
 
-  def StartInstance(self, instance, block_devices, extra_args):
-    """Start an instance.
+  def _GenerateKVMRuntime(self, instance, block_devices):
+    """Generate KVM information to start an instance.
 
     """
 
     """
-    temp_files = []
-    pidfile = self._PIDS_DIR + "/%s" % instance.name
-    if utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
-      raise errors.HypervisorError("Failed to start instance %s: %s" %
-                                   (instance.name, "already running"))
-
+    pidfile, pid, alive = self._InstancePidAlive(instance.name)
     kvm = constants.KVM_PATH
     kvm_cmd = [kvm]
     kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
     kvm = constants.KVM_PATH
     kvm_cmd = [kvm]
     kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
@@ -189,55 +257,193 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     kvm_cmd.extend(['-daemonize'])
     if not instance.hvparams[constants.HV_ACPI]:
       kvm_cmd.extend(['-no-acpi'])
     kvm_cmd.extend(['-daemonize'])
     if not instance.hvparams[constants.HV_ACPI]:
       kvm_cmd.extend(['-no-acpi'])
-    if not instance.nics:
-      kvm_cmd.extend(['-net', 'none'])
-    else:
-      nic_seq = 0
-      for nic in instance.nics:
-        script = self._WriteNetScript(instance, nic_seq, nic)
-        # FIXME: handle other models
-        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
-        kvm_cmd.extend(['-net', nic_val])
-        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
-        temp_files.append(script)
-        nic_seq += 1
 
 
-    boot_drive = True
+    hvp = instance.hvparams
+    boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
+    boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
+    boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
+
+    if boot_network:
+      kvm_cmd.extend(['-boot', 'n'])
+
+    disk_type = hvp[constants.HV_DISK_TYPE]
+    if disk_type == constants.HT_DISK_PARAVIRTUAL:
+      if_val = ',if=virtio'
+    else:
+      if_val = ',if=%s' % disk_type
     for cfdev, dev_path in block_devices:
     for cfdev, dev_path in block_devices:
+      if cfdev.mode != constants.DISK_RDWR:
+        raise errors.HypervisorError("Instance has read-only disks which"
+                                     " are not supported by KVM")
       # TODO: handle FD_LOOP and FD_BLKTAP (?)
       # TODO: handle FD_LOOP and FD_BLKTAP (?)
-      if boot_drive:
+      if boot_disk:
+        kvm_cmd.extend(['-boot', 'c'])
         boot_val = ',boot=on'
         boot_val = ',boot=on'
-        boot_drive = False
+        # We only boot from the first disk
+        boot_disk = False
       else:
         boot_val = ''
 
       else:
         boot_val = ''
 
-      # TODO: handle different if= types
-      if_val = ',if=virtio'
-
       drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
       kvm_cmd.extend(['-drive', drive_val])
 
       drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
       kvm_cmd.extend(['-drive', drive_val])
 
-    kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
+    iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
+    if iso_image:
+      options = ',format=raw,media=cdrom'
+      if boot_cdrom:
+        kvm_cmd.extend(['-boot', 'd'])
+        options = '%s,boot=on' % options
+      else:
+        options = '%s,if=virtio' % options
+      drive_val = 'file=%s%s' % (iso_image, options)
+      kvm_cmd.extend(['-drive', drive_val])
 
 
-    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
-    if initrd_path:
-      kvm_cmd.extend(['-initrd', initrd_path])
+    kernel_path = hvp[constants.HV_KERNEL_PATH]
+    if kernel_path:
+      kvm_cmd.extend(['-kernel', kernel_path])
+      initrd_path = hvp[constants.HV_INITRD_PATH]
+      if initrd_path:
+        kvm_cmd.extend(['-initrd', initrd_path])
+      root_append = ['root=%s' % hvp[constants.HV_ROOT_PATH],
+                     hvp[constants.HV_KERNEL_ARGS]]
+      if hvp[constants.HV_SERIAL_CONSOLE]:
+        root_append.append('console=ttyS0,38400')
+      kvm_cmd.extend(['-append', ' '.join(root_append)])
+
+    mouse_type = hvp[constants.HV_USB_MOUSE]
+    if mouse_type:
+      kvm_cmd.extend(['-usb'])
+      kvm_cmd.extend(['-usbdevice', mouse_type])
+
+    # FIXME: handle vnc password
+    vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
+    if vnc_bind_address:
+      if utils.IsValidIP(vnc_bind_address):
+        if instance.network_port > constants.VNC_BASE_PORT:
+          display = instance.network_port - constants.VNC_BASE_PORT
+          if vnc_bind_address == '0.0.0.0':
+            vnc_arg = ':%d' % (display)
+          else:
+            vnc_arg = '%s:%d' % (vnc_bind_address, display)
+        else:
+          logging.error("Network port is not a valid VNC display (%d < %d)."
+                        " Not starting VNC" %
+                        (instance.network_port,
+                         constants.VNC_BASE_PORT))
+          vnc_arg = 'none'
+
+        # Only allow tls and other option when not binding to a file, for now.
+        # kvm/qemu gets confused otherwise about the filename to use.
+        vnc_append = ''
+        if hvp[constants.HV_VNC_TLS]:
+          vnc_append = '%s,tls' % vnc_append
+          if hvp[constants.HV_VNC_X509_VERIFY]:
+            vnc_append = '%s,x509verify=%s' % (vnc_append,
+                                               hvp[constants.HV_VNC_X509])
+          elif hvp[constants.HV_VNC_X509]:
+            vnc_append = '%s,x509=%s' % (vnc_append,
+                                         hvp[constants.HV_VNC_X509])
+        vnc_arg = '%s%s' % (vnc_arg, vnc_append)
 
 
-    kvm_cmd.extend(['-append', 'console=ttyS0,38400 root=/dev/vda'])
+      else:
+        vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
 
 
-    #"hvm_boot_order",
-    #"hvm_cdrom_image_path",
+      kvm_cmd.extend(['-vnc', vnc_arg])
+    else:
+      kvm_cmd.extend(['-nographic'])
 
 
-    kvm_cmd.extend(['-nographic'])
-    # FIXME: handle vnc, if needed
-    # How do we decide whether to have it or not?? :(
-    #"vnc_bind_address",
-    #"network_port"
-    base_control = '%s/%s' % (self._CTRL_DIR, instance.name)
-    monitor_dev = 'unix:%s.monitor,server,nowait' % base_control
+    monitor_dev = 'unix:%s,server,nowait' % \
+      self._InstanceMonitor(instance.name)
     kvm_cmd.extend(['-monitor', monitor_dev])
     kvm_cmd.extend(['-monitor', monitor_dev])
-    serial_dev = 'unix:%s.serial,server,nowait' % base_control
-    kvm_cmd.extend(['-serial', serial_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'])
+
+    # 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
+    hvparams = hvp
+
+    return (kvm_cmd, kvm_nics, hvparams)
+
+  def _WriteKVMRuntime(self, instance_name, data):
+    """Write an instance's KVM runtime
+
+    """
+    try:
+      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
+                      data=data)
+    except EnvironmentError, err:
+      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
+
+  def _ReadKVMRuntime(self, instance_name):
+    """Read an instance's KVM runtime
+
+    """
+    try:
+      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
+    except EnvironmentError, err:
+      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
+    return file_content
+
+  def _SaveKVMRuntime(self, instance, kvm_runtime):
+    """Save an instance's KVM runtime
+
+    """
+    kvm_cmd, kvm_nics, hvparams = kvm_runtime
+    serialized_nics = [nic.ToDict() for nic in kvm_nics]
+    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
+    self._WriteKVMRuntime(instance.name, serialized_form)
+
+  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
+    """Load an instance's KVM runtime
+
+    """
+    if not serialized_runtime:
+      serialized_runtime = self._ReadKVMRuntime(instance.name)
+    loaded_runtime = serializer.Load(serialized_runtime)
+    kvm_cmd, serialized_nics, hvparams = loaded_runtime
+    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
+    return (kvm_cmd, kvm_nics, hvparams)
+
+  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:
+      raise errors.HypervisorError("Failed to start instance %s: %s" %
+                                   (instance.name, "already running"))
+
+    temp_files = []
+
+    kvm_cmd, kvm_nics, hvparams = kvm_runtime
+
+    if not kvm_nics:
+      kvm_cmd.extend(['-net', 'none'])
+    else:
+      nic_type = hvparams[constants.HV_NIC_TYPE]
+      if nic_type == constants.HT_NIC_PARAVIRTUAL:
+        nic_model = "model=virtio"
+      else:
+        nic_model = "model=%s" % nic_type
+
+      for nic_seq, nic in enumerate(kvm_nics):
+        nic_val = "nic,macaddr=%s,%s" % (nic.mac, nic_model)
+        script = self._WriteNetScript(instance, nic_seq, nic)
+        kvm_cmd.extend(['-net', nic_val])
+        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:
 
     result = utils.RunCmd(kvm_cmd)
     if result.failed:
@@ -252,28 +458,67 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     for filename in temp_files:
       utils.RemoveFile(filename)
 
     for filename in temp_files:
       utils.RemoveFile(filename)
 
+  def StartInstance(self, instance, block_devices):
+    """Start an instance.
+
+    """
+    pidfile, pid, alive = self._InstancePidAlive(instance.name)
+    if alive:
+      raise errors.HypervisorError("Failed to start instance %s: %s" %
+                                   (instance.name, "already running"))
+
+    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices)
+    self._SaveKVMRuntime(instance, kvm_runtime)
+    self._ExecuteKVMRuntime(instance, kvm_runtime)
+
+  def _CallMonitorCommand(self, instance_name, command):
+    """Invoke a command on the instance monitor.
+
+    """
+    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
+             (utils.ShellQuote(command),
+              constants.SOCAT_PATH,
+              utils.ShellQuote(self._InstanceMonitor(instance_name))))
+    result = utils.RunCmd(socat)
+    if result.failed:
+      msg = ("Failed to send command '%s' to instance %s."
+             " output: %s, error: %s, fail_reason: %s" %
+             (command, instance_name,
+              result.stdout, result.stderr, result.fail_reason))
+      raise errors.HypervisorError(msg)
+
+    return result
+
+  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
+    """Wait for an instance  to power down.
+
+    """
+    # Wait up to $timeout seconds
+    end = time.time() + timeout
+    wait = 1
+    while time.time() < end and utils.IsProcessAlive(pid):
+      self._CallMonitorCommand(instance.name, 'system_powerdown')
+      time.sleep(wait)
+      # Make wait time longer for next try
+      if wait < 5:
+        wait *= 1.3
+
   def StopInstance(self, instance, force=False):
     """Stop an instance.
 
     """
   def StopInstance(self, instance, force=False):
     """Stop an instance.
 
     """
-    socat_bin = constants.SOCAT_PATH
-    pid_file = self._PIDS_DIR + "/%s" % instance.name
-    pid = utils.ReadPidFile(pid_file)
-    if pid > 0 and utils.IsProcessAlive(pid):
+    pidfile, pid, alive = self._InstancePidAlive(instance.name)
+    if pid > 0 and alive:
       if force or not instance.hvparams[constants.HV_ACPI]:
         utils.KillProcess(pid)
       else:
       if force or not instance.hvparams[constants.HV_ACPI]:
         utils.KillProcess(pid)
       else:
-        # This only works if the instance os has acpi support
-        monitor_socket = '%s/%s.monitor'  % (self._CTRL_DIR, instance.name)
-        socat = '%s -u STDIN UNIX-CONNECT:%s' % (socat_bin, monitor_socket)
-        command = "echo 'system_powerdown' | %s" % socat
-        result = utils.RunCmd(command)
-        if result.failed:
-          raise errors.HypervisorError("Failed to stop instance %s: %s" %
-                                       (instance.name, result.fail_reason))
+        self._RetryInstancePowerdown(instance, pid)
 
     if not utils.IsProcessAlive(pid):
 
     if not utils.IsProcessAlive(pid):
-      utils.RemoveFile(pid_file)
+      self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
+      return True
+    else:
+      return False
 
   def RebootInstance(self, instance):
     """Reboot an instance.
 
   def RebootInstance(self, instance):
     """Reboot an instance.
@@ -282,68 +527,154 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     # For some reason if we do a 'send-key ctrl-alt-delete' to the control
     # socket the instance will stop, but now power up again. So we'll resort
     # to shutdown and restart.
     # For some reason if we do a 'send-key ctrl-alt-delete' to the control
     # socket the instance will stop, but now power up again. So we'll resort
     # to shutdown and restart.
-    self.StopInstance(instance)
-    self.StartInstance(instance)
+    pidfile, pid, alive = self._InstancePidAlive(instance.name)
+    if not alive:
+      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
+                                             (instance.name))
+    # StopInstance will delete the saved KVM runtime so:
+    # ...first load it...
+    kvm_runtime = self._LoadKVMRuntime(instance)
+    # ...now we can safely call StopInstance...
+    if not self.StopInstance(instance):
+      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)
+
+  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)
+        elif status == 'failed' or status == 'cancelled':
+          if not live:
+            self._CallMonitorCommand(instance_name, 'cont')
+          raise errors.HypervisorError("Migration %s at the kvm level" %
+                                       status)
+        else:
+          logging.info("KVM: unknown migration status '%s'" % status)
+          time.sleep(2)
+
+    utils.KillProcess(pid)
+    self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
 
   def GetNodeInfo(self):
     """Return information about the node.
 
 
   def GetNodeInfo(self):
     """Return information about the node.
 
+    This is just a wrapper over the base GetLinuxNodeInfo method.
+
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
           - memory_dom0: the memory used by the node itself, if available
 
     """
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
           - memory_dom0: the memory used by the node itself, if available
 
     """
-    # global ram usage from the xm info command
-    # memory                 : 3583
-    # free_memory            : 747
-    # note: in xen 3, memory has changed to total_memory
-    try:
-      fh = file("/proc/meminfo")
-      try:
-        data = fh.readlines()
-      finally:
-        fh.close()
-    except IOError, err:
-      raise errors.HypervisorError("Failed to list node info: %s" % err)
-
-    result = {}
-    sum_free = 0
-    for line in data:
-      splitfields = line.split(":", 1)
-
-      if len(splitfields) > 1:
-        key = splitfields[0].strip()
-        val = splitfields[1].strip()
-        if key == 'MemTotal':
-          result['memory_total'] = int(val.split()[0])/1024
-        elif key in ('MemFree', 'Buffers', 'Cached'):
-          sum_free += int(val.split()[0])/1024
-        elif key == 'Active':
-          result['memory_dom0'] = int(val.split()[0])/1024
-    result['memory_free'] = sum_free
-
-    cpu_total = 0
-    try:
-      fh = open("/proc/cpuinfo")
-      try:
-        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
-                                   fh.read()))
-      finally:
-        fh.close()
-    except EnvironmentError, err:
-      raise errors.HypervisorError("Failed to list node info: %s" % err)
-    result['cpu_total'] = cpu_total
-
-    return result
+    return self.GetLinuxNodeInfo()
 
 
-  @staticmethod
-  def GetShellCommandForConsole(instance):
+  @classmethod
+  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
     """Return a command for connecting to the console of an instance.
 
     """
-    # TODO: we can either try the serial socket or suggest vnc
-    return "echo Console not available for the kvm hypervisor yet"
+    if hvparams[constants.HV_SERIAL_CONSOLE]:
+      # FIXME: The socat shell is not perfect. In particular the way we start
+      # it ctrl+c will close it, rather than being passed to the other end.
+      # On the other hand if we pass the option 'raw' (or ignbrk=1) there
+      # will be no way of exiting socat (except killing it from another shell)
+      # and ctrl+c doesn't work anyway, printing ^C rather than being
+      # interpreted by kvm. For now we'll leave it this way, which at least
+      # allows a minimal interaction and changes on the machine.
+      shell_command = ("%s STDIO,echo=0,icanon=0 UNIX-CONNECT:%s" %
+                       (constants.SOCAT_PATH,
+                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
+    else:
+      shell_command = "echo 'No serial shell for instance %s'" % instance.name
+
+    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
+    if vnc_bind_address:
+      if instance.network_port > constants.VNC_BASE_PORT:
+        display = instance.network_port - constants.VNC_BASE_PORT
+        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
+                       " (display: %d)'" % (vnc_bind_address,
+                                            instance.network_port,
+                                            display))
+        shell_command = "%s; %s" % (vnc_command, shell_command)
+
+    return shell_command
 
   def Verify(self):
     """Verify the hypervisor.
 
   def Verify(self):
     """Verify the hypervisor.
@@ -361,9 +692,6 @@ class KVMHypervisor(hv_base.BaseHypervisor):
   def CheckParameterSyntax(cls, hvparams):
     """Check the given parameters for validity.
 
   def CheckParameterSyntax(cls, hvparams):
     """Check the given parameters for validity.
 
-    For the KVM hypervisor, this only check the existence of the
-    kernel.
-
     @type hvparams:  dict
     @param hvparams: dictionary with parameter names/value
     @raise errors.HypervisorError: when a parameter is not valid
     @type hvparams:  dict
     @param hvparams: dictionary with parameter names/value
     @raise errors.HypervisorError: when a parameter is not valid
@@ -371,31 +699,32 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     """
     super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
 
     """
     super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
 
-    if not hvparams[constants.HV_KERNEL_PATH]:
-      raise errors.HypervisorError("Need a kernel for the instance")
-
-    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
-      raise errors.HypervisorError("The kernel path must an absolute path")
-
-    if hvparams[constants.HV_INITRD_PATH]:
-      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
-        raise errors.HypervisorError("The initrd path must an absolute path"
-                                     ", if defined")
-
-  def ValidateParameters(self, hvparams):
-    """Check the given parameters for validity.
+    kernel_path = hvparams[constants.HV_KERNEL_PATH]
+    if kernel_path:
+      if not hvparams[constants.HV_ROOT_PATH]:
+        raise errors.HypervisorError("Need a root partition for the instance,"
+                                     " if a kernel is defined")
+
+    if (hvparams[constants.HV_VNC_X509_VERIFY] and
+        not hvparams[constants.HV_VNC_X509]):
+      raise errors.HypervisorError("%s must be defined, if %s is" %
+                                   (constants.HV_VNC_X509,
+                                    constants.HV_VNC_X509_VERIFY))
+
+    boot_order = hvparams[constants.HV_BOOT_ORDER]
+
+    if (boot_order == constants.HT_BO_CDROM and
+        not hvparams[constants.HV_CDROM_IMAGE_PATH]):
+      raise errors.HypervisorError("Cannot boot from cdrom without an"
+                                   " ISO path")
+    if (boot_order == constants.HT_BO_NETWORK and
+        hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
+      raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
+                                   " change the NIC type.")
 
 
-    For the KVM hypervisor, this checks the existence of the
-    kernel.
+  @classmethod
+  def PowercycleNode(cls):
+    """KVM powercycle, just a wrapper over Linux powercycle.
 
     """
 
     """
-    super(KVMHypervisor, self).ValidateParameters(hvparams)
-
-    kernel_path = hvparams[constants.HV_KERNEL_PATH]
-    if not os.path.isfile(kernel_path):
-      raise errors.HypervisorError("Instance kernel '%s' not found or"
-                                   " not a file" % kernel_path)
-    initrd_path = hvparams[constants.HV_INITRD_PATH]
-    if initrd_path and not os.path.isfile(initrd_path):
-      raise errors.HypervisorError("Instance initrd '%s' not found or"
-                                   " not a file" % initrd_path)
+    cls.LinuxPowercycle()