Fix iallocator instance info
[ganeti-local] / lib / hypervisor.py
index 366a8ab..5c60c6e 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#
 #
 
 # Copyright (C) 2006, 2007 Google Inc.
@@ -30,12 +30,10 @@ from cStringIO import StringIO
 from ganeti import utils
 from ganeti import logger
 from ganeti import ssconf
+from ganeti import constants
+from ganeti import errors
 from ganeti.errors import HypervisorError
 
-_HT_XEN30 = "xen-3.0"
-_HT_FAKE = "fake"
-
-VALID_HTYPES = (_HT_XEN30, _HT_FAKE)
 
 def GetHypervisor():
   """Return a Hypervisor instance.
@@ -45,12 +43,14 @@ def GetHypervisor():
 
   """
   ht_kind = ssconf.SimpleStore().GetHypervisorType()
-  if ht_kind == _HT_XEN30:
-    cls = XenHypervisor
-  elif ht_kind == _HT_FAKE:
+  if ht_kind == constants.HT_XEN_PVM30:
+    cls = XenPvmHypervisor
+  elif ht_kind == constants.HT_FAKE:
     cls = FakeHypervisor
+  elif ht_kind == constants.HT_XEN_HVM31:
+    cls = XenHvmHypervisor
   else:
-    raise HypervisorError, "Unknown hypervisor type '%s'" % ht_kind
+    raise HypervisorError("Unknown hypervisor type '%s'" % ht_kind)
   return cls()
 
 
@@ -72,6 +72,10 @@ class BaseHypervisor(object):
     """Stop an instance."""
     raise NotImplementedError
 
+  def RebootInstance(self, instance):
+    """Reboot an instance."""
+    raise NotImplementedError
+
   def ListInstances(self):
     """Get the list of running instances."""
     raise NotImplementedError
@@ -109,7 +113,7 @@ class BaseHypervisor(object):
     raise NotImplementedError
 
   @staticmethod
-  def GetShellCommandForConsole(instance_name):
+  def GetShellCommandForConsole(instance):
     """Return a command for connecting to the console of an instance.
 
     """
@@ -123,46 +127,19 @@ class BaseHypervisor(object):
 
 
 class XenHypervisor(BaseHypervisor):
-  """Xen hypervisor interface"""
+  """Xen generic hypervisor interface
+
+  This is the Xen base class used for both Xen PVM and HVM. It contains
+  all the functionality that is identical for both.
+
+  """
 
   @staticmethod
   def _WriteConfigFile(instance, block_devices, extra_args):
-    """Create a Xen 3.0 config file.
+    """Write the Xen config file for the instance.
 
     """
-    config = StringIO()
-    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
-    config.write("kernel = '/boot/vmlinuz-2.6-xenU'\n")
-    config.write("memory = %d\n" % instance.memory)
-    config.write("vcpus = %d\n" % instance.vcpus)
-    config.write("name = '%s'\n" % instance.name)
-
-    vif_data = []
-    for nic in instance.nics:
-      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
-      ip = getattr(nic, "ip", None)
-      if ip is not None:
-        nic_str += ", ip=%s" % ip
-      vif_data.append("'%s'" % nic_str)
-
-    config.write("vif = [%s]\n" % ",".join(vif_data))
-
-    disk_data = ["'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
-                 for cfdev, rldev in block_devices]
-    config.write("disk = [%s]\n" % ",".join(disk_data))
-
-    config.write("root = '/dev/sda ro'\n")
-    config.write("on_poweroff = 'destroy'\n")
-    config.write("on_reboot = 'restart'\n")
-    config.write("on_crash = 'restart'\n")
-    if extra_args:
-      config.write("extra = '%s'\n" % extra_args)
-    # just in case it exists
-    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
-    f = open("/etc/xen/%s" % instance.name, "w")
-    f.write(config.getvalue())
-    f.close()
-    return True
+    raise NotImplementedError
 
   @staticmethod
   def _RemoveConfigFile(instance):
@@ -193,12 +170,8 @@ class XenHypervisor(BaseHypervisor):
       raise HypervisorError("xm list failed, retries exceeded (%s): %s" %
                             (result.fail_reason, result.stderr))
 
-    # skip over the heading and the domain 0 line (optional)
-    if include_node:
-      to_skip = 1
-    else:
-      to_skip = 2
-    lines = result.stdout.splitlines()[to_skip:]
+    # skip over the heading
+    lines = result.stdout.splitlines()[1:]
     result = []
     for line in lines:
       # The format of lines is:
@@ -215,7 +188,11 @@ class XenHypervisor(BaseHypervisor):
       except ValueError, err:
         raise HypervisorError("Can't parse output of xm list,"
                               " line: %s, error: %s" % (line, err))
-      result.append(data)
+
+      # skip the Domain-0 (optional)
+      if include_node or data[0] != 'Domain-0':
+        result.append(data)
+
     return result
 
   def ListInstances(self):
@@ -258,8 +235,8 @@ class XenHypervisor(BaseHypervisor):
     result = utils.RunCmd(["xm", "create", instance.name])
 
     if result.failed:
-      raise HypervisorError("Failed to start instance %s: %s" %
-                            (instance.name, result.fail_reason))
+      raise HypervisorError("Failed to start instance %s: %s (%s)" %
+                            (instance.name, result.fail_reason, result.output))
 
   def StopInstance(self, instance, force=False):
     """Stop an instance."""
@@ -274,6 +251,14 @@ class XenHypervisor(BaseHypervisor):
       raise HypervisorError("Failed to stop instance %s: %s" %
                             (instance.name, result.fail_reason))
 
+  def RebootInstance(self, instance):
+    """Reboot an instance."""
+    result = utils.RunCmd(["xm", "reboot", instance.name])
+
+    if result.failed:
+      raise HypervisorError("Failed to reboot instance %s: %s" %
+                            (instance.name, result.fail_reason))
+
   def GetNodeInfo(self):
     """Return information about the node.
 
@@ -309,11 +294,11 @@ class XenHypervisor(BaseHypervisor):
     return result
 
   @staticmethod
-  def GetShellCommandForConsole(instance_name):
+  def GetShellCommandForConsole(instance):
     """Return a command for connecting to the console of an instance.
 
     """
-    return "xm console %s" % instance_name
+    raise NotImplementedError
 
 
   def Verify(self):
@@ -325,6 +310,123 @@ class XenHypervisor(BaseHypervisor):
     if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
       return "xend daemon is not running"
 
+  @staticmethod
+  def _GetConfigFileDiskData(disk_template, block_devices):
+    """Get disk directive for xen config file.
+
+    This method builds the xen config disk directive according to the
+    given disk_template and block_devices.
+
+    Args:
+      disk_template: String containing instance disk template
+      block_devices: List[tuple1,tuple2,...]
+        tuple: (cfdev, rldev)
+          cfdev: dict containing ganeti config disk part
+          rldev: ganeti.bdev.BlockDev object
+
+    Returns:
+      String containing disk directive for xen instance config file
+
+    """
+    FILE_DRIVER_MAP = {
+      constants.FD_LOOP: "file",
+      constants.FD_BLKTAP: "tap:aio",
+      }
+    disk_data = []
+    for cfdev, rldev in block_devices:
+      if cfdev.dev_type == constants.LD_FILE:
+        line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
+                                 rldev.dev_path, cfdev.iv_name)
+      else:
+        line = "'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
+      disk_data.append(line)
+
+    return disk_data
+
+
+class XenPvmHypervisor(XenHypervisor):
+  """Xen PVM hypervisor interface"""
+
+  @classmethod
+  def _WriteConfigFile(cls, instance, block_devices, extra_args):
+    """Write the Xen config file for the instance.
+
+    """
+    config = StringIO()
+    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
+
+    # kernel handling
+    if instance.kernel_path in (None, constants.VALUE_DEFAULT):
+      kpath = constants.XEN_KERNEL
+    else:
+      if not os.path.exists(instance.kernel_path):
+        raise errors.HypervisorError("The kernel %s for instance %s is"
+                                     " missing" % (instance.kernel_path,
+                                                   instance.name))
+      kpath = instance.kernel_path
+    config.write("kernel = '%s'\n" % kpath)
+
+    # initrd handling
+    if instance.initrd_path in (None, constants.VALUE_DEFAULT):
+      if os.path.exists(constants.XEN_INITRD):
+        initrd_path = constants.XEN_INITRD
+      else:
+        initrd_path = None
+    elif instance.initrd_path == constants.VALUE_NONE:
+      initrd_path = None
+    else:
+      if not os.path.exists(instance.initrd_path):
+        raise errors.HypervisorError("The initrd %s for instance %s is"
+                                     " missing" % (instance.initrd_path,
+                                                   instance.name))
+      initrd_path = instance.initrd_path
+
+    if initrd_path:
+      config.write("ramdisk = '%s'\n" % initrd_path)
+
+    # rest of the settings
+    config.write("memory = %d\n" % instance.memory)
+    config.write("vcpus = %d\n" % instance.vcpus)
+    config.write("name = '%s'\n" % instance.name)
+
+    vif_data = []
+    for nic in instance.nics:
+      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
+      ip = getattr(nic, "ip", None)
+      if ip is not None:
+        nic_str += ", ip=%s" % ip
+      vif_data.append("'%s'" % nic_str)
+
+    config.write("vif = [%s]\n" % ",".join(vif_data))
+    config.write("disk = [%s]\n" % ",".join(
+                 cls._GetConfigFileDiskData(instance.disk_template,
+                                            block_devices)))
+    config.write("root = '/dev/sda ro'\n")
+    config.write("on_poweroff = 'destroy'\n")
+    config.write("on_reboot = 'restart'\n")
+    config.write("on_crash = 'restart'\n")
+    if extra_args:
+      config.write("extra = '%s'\n" % extra_args)
+    # just in case it exists
+    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
+    try:
+      f = open("/etc/xen/%s" % instance.name, "w")
+      try:
+        f.write(config.getvalue())
+      finally:
+        f.close()
+    except IOError, err:
+      raise errors.OpExecError("Cannot write Xen instance confile"
+                               " file /etc/xen/%s: %s" % (instance.name, err))
+    return True
+
+  @staticmethod
+  def GetShellCommandForConsole(instance):
+    """Return a command for connecting to the console of an instance.
+
+    """
+    return "xm console %s" % instance.name
+
 
 class FakeHypervisor(BaseHypervisor):
   """Fake hypervisor interface.
@@ -333,7 +435,7 @@ class FakeHypervisor(BaseHypervisor):
   a real virtualisation software installed.
 
   """
-  _ROOT_DIR = "/var/run/ganeti-fake-hypervisor"
+  _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
 
   def __init__(self):
     BaseHypervisor.__init__(self)
@@ -435,6 +537,14 @@ class FakeHypervisor(BaseHypervisor):
                             (instance.name, "not running"))
     utils.RemoveFile(file_name)
 
+  def RebootInstance(self, instance):
+    """Reboot an instance.
+
+    For the fake hypervisor, this does nothing.
+
+    """
+    return
+
   def GetNodeInfo(self):
     """Return information about the node.
 
@@ -477,7 +587,7 @@ class FakeHypervisor(BaseHypervisor):
     return result
 
   @staticmethod
-  def GetShellCommandForConsole(instance_name):
+  def GetShellCommandForConsole(instance):
     """Return a command for connecting to the console of an instance.
 
     """
@@ -492,3 +602,104 @@ class FakeHypervisor(BaseHypervisor):
     """
     if not os.path.exists(self._ROOT_DIR):
       return "The required directory '%s' does not exist." % self._ROOT_DIR
+
+
+class XenHvmHypervisor(XenHypervisor):
+  """Xen HVM hypervisor interface"""
+
+  @classmethod
+  def _WriteConfigFile(cls, instance, block_devices, extra_args):
+    """Create a Xen 3.1 HVM config file.
+
+    """
+    config = StringIO()
+    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
+    config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
+    config.write("builder = 'hvm'\n")
+    config.write("memory = %d\n" % instance.memory)
+    config.write("vcpus = %d\n" % instance.vcpus)
+    config.write("name = '%s'\n" % instance.name)
+    config.write("pae = 1\n")
+    config.write("acpi = 1\n")
+    config.write("apic = 1\n")
+    arch = os.uname()[4]
+    if '64' in arch:
+      config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
+    else:
+      config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
+    if instance.hvm_boot_order is None:
+      config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
+    else:
+      config.write("boot = '%s'\n" % instance.hvm_boot_order)
+    config.write("sdl = 0\n")
+    config.write("usb = 1\n");
+    config.write("usbdevice = 'tablet'\n");
+    config.write("vnc = 1\n")
+    config.write("vnclisten = '0.0.0.0'\n")
+
+    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
+      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
+      config.write("vncdisplay = %s\n" % display)
+      config.write("vncunused = 0\n")
+    else:
+      config.write("# vncdisplay = 1\n")
+      config.write("vncunused = 1\n")
+
+    try:
+      password_file = open(constants.VNC_PASSWORD_FILE, "r")
+      try:
+        password = password_file.readline()
+      finally:
+        password_file.close()
+    except IOError:
+      raise errors.OpExecError("failed to open VNC password file %s " %
+                               constants.VNC_PASSWORD_FILE)
+
+    config.write("vncpasswd = '%s'\n" % password.rstrip())
+
+    config.write("serial = 'pty'\n")
+    config.write("localtime = 1\n")
+
+    vif_data = []
+    for nic in instance.nics:
+      nic_str = "mac=%s, bridge=%s, type=ioemu" % (nic.mac, nic.bridge)
+      ip = getattr(nic, "ip", None)
+      if ip is not None:
+        nic_str += ", ip=%s" % ip
+      vif_data.append("'%s'" % nic_str)
+
+    config.write("vif = [%s]\n" % ",".join(vif_data))
+    iso = "'file:/srv/ganeti/iso/hvm-install.iso,hdc:cdrom,r'"
+    config.write("disk = [%s, %s]\n" % (",".join(
+                 cls._GetConfigFileDiskData(instance.disk_template,
+                                            block_devices)), iso))
+    config.write("on_poweroff = 'destroy'\n")
+    config.write("on_reboot = 'restart'\n")
+    config.write("on_crash = 'restart'\n")
+    if extra_args:
+      config.write("extra = '%s'\n" % extra_args)
+    # just in case it exists
+    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
+    try:
+      f = open("/etc/xen/%s" % instance.name, "w")
+      try:
+        f.write(config.getvalue())
+      finally:
+        f.close()
+    except IOError, err:
+      raise errors.OpExecError("Cannot write Xen instance confile"
+                               " file /etc/xen/%s: %s" % (instance.name, err))
+    return True
+
+  @staticmethod
+  def GetShellCommandForConsole(instance):
+    """Return a command for connecting to the console of an instance.
+
+    """
+    if instance.network_port is None:
+      raise errors.OpExecError("no console port defined for %s"
+                               % instance.name)
+    else:
+      raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
+                               % (instance.primary_node,
+                                  instance.network_port))