Proof-of-Concept HVM support for Ganeti.
authorAlexander Schreiber <als@google.com>
Thu, 20 Dec 2007 16:47:52 +0000 (16:47 +0000)
committerAlexander Schreiber <als@google.com>
Thu, 20 Dec 2007 16:47:52 +0000 (16:47 +0000)
This patch implements a first proof-of-concept for HVM support in Ganeti.
Due to the nature of this patch, it is intended for test environments
only.

Reviewed-by: iustinp

lib/cmdlib.py
lib/constants.py
lib/hypervisor.py
scripts/gnt-cluster

index 5cdedf6..b9c8014 100644 (file)
@@ -499,6 +499,12 @@ class LUInitCluster(LogicalUnit):
     if config.ConfigWriter.IsCluster():
       raise errors.OpPrereqError("Cluster is already initialised")
 
+    if self.op.hypervisor_type == constants.HT_XEN_HVM31:
+      if not os.path.exists(constants.VNC_PASSWORD_FILE):
+        raise errors.OpPrereqError("Please prepare the cluster VNC"
+                                   "password file %s" %
+                                   constants.VNC_PASSWORD_FILE)
+
     self.hostname = hostname = utils.HostInfo()
 
     if hostname.ip.startswith("127."):
@@ -1470,6 +1476,11 @@ class LUAddNode(LogicalUnit):
                                  primary_ip=primary_ip,
                                  secondary_ip=secondary_ip)
 
+    if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
+      if not os.path.exists(constants.VNC_PASSWORD_FILE):
+        raise errors.OpPrereqError("Cluster VNC password file %s missing" %
+                                   constants.VNC_PASSWORD_FILE)
+
   def Exec(self, feedback_fn):
     """Adds the new node to the cluster.
 
@@ -1589,6 +1600,8 @@ class LUAddNode(LogicalUnit):
                        (fname, to_node))
 
     to_copy = ss.GetFileList()
+    if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
+      to_copy.append(constants.VNC_PASSWORD_FILE)
     for fname in to_copy:
       if not ssh.CopyFileToNode(node, fname):
         logger.Error("could not copy file %s to node %s" % (fname, node))
@@ -3028,7 +3041,11 @@ class LUCreateInstance(LogicalUnit):
     if self.inst_ip is not None:
       nic.ip = self.inst_ip
 
-    network_port = None  # placeholder assignment for later
+    ht_kind = self.sstore.GetHypervisorType()
+    if ht_kind in constants.HTS_REQ_PORT:
+      network_port = self.cfg.AllocatePort()
+    else:
+      network_port = None
 
     disks = _GenerateDiskTemplate(self.cfg,
                                   self.op.disk_template,
index 365472f..10c2644 100644 (file)
@@ -151,5 +151,9 @@ INSTANCE_REBOOT_FULL = "full"
 # Hypervisor constants
 HT_XEN_PVM30 = "xen-3.0"
 HT_FAKE = "fake"
+HT_XEN_HVM31 = "xen-hvm-3.1"
+HYPER_TYPES = frozenset([HT_XEN_PVM30, HT_FAKE, HT_XEN_HVM31])
+HTS_REQ_PORT = frozenset([HT_XEN_HVM31])
 
-HYPER_TYPES = frozenset([HT_XEN_PVM30, HT_FAKE])
+HT_HVM_VNC_BASE_PORT = 5900
+VNC_PASSWORD_FILE = _autoconf.SYSCONFDIR + "/ganeti/vnc-cluster-password"
index 130c1e6..534114c 100644 (file)
@@ -31,6 +31,7 @@ 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
 
 
@@ -46,6 +47,8 @@ def GetHypervisor():
     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)
   return cls()
@@ -540,3 +543,102 @@ 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"""
+
+  @staticmethod
+  def _WriteConfigFile(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")
+    config.write("boot = 'dc'\n")
+    config.write("sdl = 0\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))
+
+    disk_data = ["'phy:%s,%s,w'" %
+                 (rldev.dev_path, cfdev.iv_name.replace("sd", "ioemu:hd"))
+                 for cfdev, rldev in block_devices]
+    iso = "'file:/srv/ganeti/iso/hvm-install.iso,hdc:cdrom,r'"
+    config.write("disk = [%s, %s]\n" % (",".join(disk_data), 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))
index 424b239..d857086 100755 (executable)
@@ -257,8 +257,11 @@ commands = {
                         " addresses",
                         metavar="ADDRESS", default=None),
             make_option("-t", "--hypervisor-type", dest="hypervisor_type",
-                        help="Specify the hypervisor type (xen-3.0, fake)",
-                        metavar="TYPE", choices=["xen-3.0", "fake"],
+                        help="Specify the hypervisor type "
+                        "(xen-3.0, fake, xen-hvm-3.1)",
+                        metavar="TYPE", choices=["xen-3.0",
+                                                 "fake",
+                                                 "xen-hvm-3.1"],
                         default="xen-3.0",),
             make_option("-m", "--mac-prefix", dest="mac_prefix",
                         help="Specify the mac prefix for the instance IP"