Support 'viridian' parameter in Xen HVM
[ganeti-local] / lib / hypervisor / hv_chroot.py
index 0b642e0..5d88447 100644 (file)
@@ -27,11 +27,12 @@ import os
 import os.path
 import time
 import logging
-from cStringIO import StringIO
 
 from ganeti import constants
-from ganeti import errors
+from ganeti import errors # pylint: disable=W0611
 from ganeti import utils
+from ganeti import objects
+from ganeti import pathutils
 from ganeti.hypervisor import hv_base
 from ganeti.errors import HypervisorError
 
@@ -58,21 +59,17 @@ class ChrootManager(hv_base.BaseHypervisor):
     - instance alive check is based on whether any process is using the chroot
 
   """
-  _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
+  _ROOT_DIR = pathutils.RUN_DIR + "/chroot-hypervisor"
 
   PARAMETERS = {
     constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
                                "must be an absolute normalized path",
-                               None, None)
+                               None, None),
     }
 
   def __init__(self):
     hv_base.BaseHypervisor.__init__(self)
-    if not os.path.exists(self._ROOT_DIR):
-      os.mkdir(self._ROOT_DIR)
-    if not os.path.isdir(self._ROOT_DIR):
-      raise HypervisorError("Needed path %s is not a directory" %
-                            self._ROOT_DIR)
+    utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
 
   @staticmethod
   def _IsDirLive(path):
@@ -88,41 +85,40 @@ class ChrootManager(hv_base.BaseHypervisor):
   def _GetMountSubdirs(path):
     """Return the list of mountpoints under a given path.
 
-    This function is Linux-specific.
+    """
+    result = []
+    for _, mountpoint, _, _ in utils.GetMounts():
+      if (mountpoint.startswith(path) and
+          mountpoint != path):
+        result.append(mountpoint)
+
+    result.sort(key=lambda x: x.count("/"), reverse=True)
+    return result
+
+  @classmethod
+  def _InstanceDir(cls, instance_name):
+    """Return the root directory for an instance.
 
     """
-    #TODO(iustin): investigate and document non-linux options
-    #(e.g. via mount output)
-    data = []
-    fh = open("/proc/mounts", "r")
-    try:
-      for line in fh:
-        fstype, mountpoint, rest = line.split(" ", 2)
-        if (mountpoint.startswith(path) and
-            mountpoint != path):
-          data.append(mountpoint)
-    finally:
-      fh.close()
-    data.sort(key=lambda x: x.count("/"), reverse=True)
-    return data
+    return utils.PathJoin(cls._ROOT_DIR, instance_name)
 
   def ListInstances(self):
     """Get the list of running instances.
 
     """
     return [name for name in os.listdir(self._ROOT_DIR)
-            if self._IsDirLive(os.path.join(self._ROOT_DIR, name))]
+            if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
 
   def GetInstanceInfo(self, instance_name):
     """Get instance properties.
 
-    Args:
-      instance_name: the instance name
+    @type instance_name: string
+    @param instance_name: the instance name
+
+    @return: (name, id, memory, vcpus, stat, times)
 
-    Returns:
-      (name, id, memory, vcpus, stat, times)
     """
-    dir_name = "%s/%s" % (self._ROOT_DIR, instance_name)
+    dir_name = self._InstanceDir(instance_name)
     if not self._IsDirLive(dir_name):
       raise HypervisorError("Instance %s is not running" % instance_name)
     return (instance_name, 0, 0, 0, 0, 0)
@@ -130,24 +126,24 @@ class ChrootManager(hv_base.BaseHypervisor):
   def GetAllInstancesInfo(self):
     """Get properties of all instances.
 
-    Returns:
-      [(name, id, memory, vcpus, stat, times),...]
+    @return: [(name, id, memory, vcpus, stat, times),...]
+
     """
     data = []
     for file_name in os.listdir(self._ROOT_DIR):
-      path = os.path.join(self._ROOT_DIR, file_name)
+      path = utils.PathJoin(self._ROOT_DIR, file_name)
       if self._IsDirLive(path):
         data.append((file_name, 0, 0, 0, 0, 0))
     return data
 
-  def StartInstance(self, instance, block_devices):
+  def StartInstance(self, instance, block_devices, startup_paused):
     """Start an instance.
 
     For the chroot manager, we try to mount the block device and
     execute '/ganeti-chroot start'.
 
     """
-    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
+    root_dir = self._InstanceDir(instance.name)
     if not os.path.exists(root_dir):
       try:
         os.mkdir(root_dir)
@@ -171,7 +167,7 @@ class ChrootManager(hv_base.BaseHypervisor):
       raise HypervisorError("Can't run the chroot start script: %s" %
                             result.output)
 
-  def StopInstance(self, instance, force=False):
+  def StopInstance(self, instance, force=False, retry=False, name=None):
     """Stop an instance.
 
     This method has complicated cleanup tests, as we must:
@@ -180,41 +176,54 @@ class ChrootManager(hv_base.BaseHypervisor):
       - finally unmount the instance dir
 
     """
-    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
-    if not os.path.exists(root_dir):
+    if name is None:
+      name = instance.name
+
+    root_dir = self._InstanceDir(name)
+    if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
       return
 
-    if self._IsDirLive(root_dir):
+    # Run the chroot stop script only once
+    if not retry and not force:
       result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
       if result.failed:
         raise HypervisorError("Can't run the chroot stop script: %s" %
                               result.output)
-      retry = 20
-      while not force and self._IsDirLive(root_dir) and retry > 0:
-        time.sleep(1)
-        retry -= 1
-        if retry < 10:
-          result = utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
-      retry = 5
-      while force and self._IsDirLive(root_dir) and retry > 0:
-        time.sleep(1)
-        retry -= 1
-        utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
-      if self._IsDirLive(root_dir):
+
+    if not force:
+      utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
+    else:
+      utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
+      # 2 seconds at most should be enough for KILL to take action
+      time.sleep(2)
+
+    if self._IsDirLive(root_dir):
+      if force:
         raise HypervisorError("Can't stop the processes using the chroot")
+      return
+
+  def CleanupInstance(self, instance_name):
+    """Cleanup after a stopped instance
+
+    """
+    root_dir = self._InstanceDir(instance_name)
+
+    if not os.path.exists(root_dir):
+      return
+
+    if self._IsDirLive(root_dir):
+      raise HypervisorError("Processes are still using the chroot")
+
     for mpath in self._GetMountSubdirs(root_dir):
       utils.RunCmd(["umount", mpath])
-    retry = 10
-    while retry > 0:
-      result = utils.RunCmd(["umount", root_dir])
-      if not result.failed:
-        break
-      retry -= 1
-      time.sleep(1)
+
+    result = utils.RunCmd(["umount", root_dir])
     if result.failed:
-      logging.error("Processes still alive in the chroot: %s",
-                    utils.RunCmd("fuser -vm %s" % root_dir).output)
-      raise HypervisorError("Can't umount the chroot dir: %s" % result.output)
+      msg = ("Processes still alive in the chroot: %s" %
+             utils.RunCmd("fuser -vm %s" % root_dir).output)
+      logging.error(msg)
+      raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
+                            (result.output, msg))
 
   def RebootInstance(self, instance):
     """Reboot an instance.
@@ -225,6 +234,18 @@ class ChrootManager(hv_base.BaseHypervisor):
     raise HypervisorError("The chroot manager doesn't implement the"
                           " reboot functionality")
 
+  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
+
+    """
+    # Currently chroots don't have memory limits
+    pass
+
   def GetNodeInfo(self):
     """Return information about the node.
 
@@ -239,22 +260,64 @@ class ChrootManager(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
-    """Return a command for connecting to the console of an instance.
+  def GetInstanceConsole(cls, instance, # pylint: disable=W0221
+                         hvparams, beparams, root_dir=None):
+    """Return information for connecting to the console of an instance.
 
     """
-    root_dir = "%s/%s" % (cls._ROOT_DIR, instance.name)
-    if not os.path.ismount(root_dir):
-      raise HypervisorError("Instance %s is not running" % instance.name)
+    if root_dir is None:
+      root_dir = cls._InstanceDir(instance.name)
+      if not os.path.ismount(root_dir):
+        raise HypervisorError("Instance %s is not running" % instance.name)
 
-    return "chroot %s" % root_dir
+    return objects.InstanceConsole(instance=instance.name,
+                                   kind=constants.CONS_SSH,
+                                   host=instance.primary_node,
+                                   user=constants.SSH_CONSOLE_USER,
+                                   command=["chroot", root_dir])
 
   def Verify(self):
     """Verify the hypervisor.
 
-    For the chroot manager, it just checks the existence of the base
-    dir.
+    For the chroot manager, it just checks the existence of the base dir.
+
+    @return: Problem description if something is wrong, C{None} otherwise
+
+    """
+    if os.path.exists(self._ROOT_DIR):
+      return None
+    else:
+      return "The required directory '%s' does not exist" % self._ROOT_DIR
+
+  @classmethod
+  def PowercycleNode(cls):
+    """Chroot powercycle, just a wrapper over Linux powercycle.
+
+    """
+    cls.LinuxPowercycle()
+
+  def MigrateInstance(self, instance, target, live):
+    """Migrate an instance.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance to be migrated
+    @type target: string
+    @param target: hostname (usually ip) of the target node
+    @type live: boolean
+    @param live: whether to do a live or non-live migration
+
+    """
+    raise HypervisorError("Migration not supported by the chroot hypervisor")
+
+  def GetMigrationStatus(self, instance):
+    """Get the migration status
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance that is being migrated
+    @rtype: L{objects.MigrationStatus}
+    @return: the status of the current migration (one of
+             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
+             progress info that can be retrieved from the hypervisor
 
     """
-    if not os.path.exists(self._ROOT_DIR):
-      return "The required directory '%s' does not exist." % self._ROOT_DIR
+    raise HypervisorError("Migration not supported by the chroot hypervisor")