Remove XEN_CMD from constants, adjust to PowercycleNode
[ganeti-local] / lib / hypervisor / hv_lxc.py
index 49fd77a..819df79 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
 #
 #
 
-# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2010, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ from ganeti import constants
 from ganeti import errors # pylint: disable=W0611
 from ganeti import utils
 from ganeti import objects
 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
 
 from ganeti.hypervisor import hv_base
 from ganeti.errors import HypervisorError
 
@@ -39,19 +40,9 @@ from ganeti.errors import HypervisorError
 class LXCHypervisor(hv_base.BaseHypervisor):
   """LXC-based virtualization.
 
 class LXCHypervisor(hv_base.BaseHypervisor):
   """LXC-based virtualization.
 
-  Since current (Spring 2010) distributions are not yet ready for
-  running under a container, the following changes must be done
-  manually:
-    - remove udev
-    - disable the kernel log component of sysklogd/rsyslog/etc.,
-      otherwise they will fail to read the log, and at least rsyslog
-      will fill the filesystem with error messages
-
   TODO:
     - move hardcoded parameters into hypervisor parameters, once we
       have the container-parameter support
   TODO:
     - move hardcoded parameters into hypervisor parameters, once we
       have the container-parameter support
-    - implement memory limits, but only optionally, depending on host
-      kernel support
 
   Problems/issues:
     - LXC is very temperamental; in daemon mode, it succeeds or fails
 
   Problems/issues:
     - LXC is very temperamental; in daemon mode, it succeeds or fails
@@ -59,13 +50,9 @@ class LXCHypervisor(hv_base.BaseHypervisor):
       indication, and when failing it can leave network interfaces
       around, and future successful startups will list the instance
       twice
       indication, and when failing it can leave network interfaces
       around, and future successful startups will list the instance
       twice
-    - shutdown sequence of containers leaves the init 'dead', and the
-      container effectively stopped, but LXC still believes the
-      container to be running; need to investigate using the
-      notify_on_release and release_agent feature of cgroups
 
   """
 
   """
-  _ROOT_DIR = constants.RUN_DIR + "/lxc"
+  _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
   _DEVS = [
     "c 1:3",   # /dev/null
     "c 1:5",   # /dev/zero
   _DEVS = [
     "c 1:3",   # /dev/null
     "c 1:5",   # /dev/zero
@@ -145,7 +132,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     """
     cgroup = cls._GetCgroupMountPoint()
     try:
     """
     cgroup = cls._GetCgroupMountPoint()
     try:
-      cpus = utils.ReadFile(utils.PathJoin(cgroup,
+      cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
                                            instance_name,
                                            "cpuset.cpus"))
     except EnvironmentError, err:
                                            instance_name,
                                            "cpuset.cpus"))
     except EnvironmentError, err:
@@ -154,49 +141,72 @@ class LXCHypervisor(hv_base.BaseHypervisor):
 
     return utils.ParseCpuMask(cpus)
 
 
     return utils.ParseCpuMask(cpus)
 
-  def ListInstances(self):
+  @classmethod
+  def _GetCgroupMemoryLimit(cls, instance_name):
+    """Return the memory limit for an instance
+
+    """
+    cgroup = cls._GetCgroupMountPoint()
+    try:
+      memory = int(utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
+                                                 instance_name,
+                                                 "memory.limit_in_bytes")))
+    except EnvironmentError:
+      # memory resource controller may be disabled, ignore
+      memory = 0
+
+    return memory
+
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances.
 
     """
     """Get the list of running instances.
 
     """
-    result = utils.RunCmd(["lxc-ls"])
-    if result.failed:
-      raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
-    return result.stdout.splitlines()
+    return [iinfo[0] for iinfo in self.GetAllInstancesInfo()]
 
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
     @type instance_name: string
     @param instance_name: the instance name
     """Get instance properties.
 
     @type instance_name: string
     @param instance_name: the instance name
-
+    @type hvparams: dict of strings
+    @param hvparams: hvparams to be used with this instance
+    @rtype: tuple of strings
     @return: (name, id, memory, vcpus, stat, times)
 
     """
     # TODO: read container info from the cgroup mountpoint
 
     @return: (name, id, memory, vcpus, stat, times)
 
     """
     # TODO: read container info from the cgroup mountpoint
 
-    result = utils.RunCmd(["lxc-info", "-n", instance_name])
+    result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
     if result.failed:
       raise errors.HypervisorError("Running lxc-info failed: %s" %
                                    result.output)
     # lxc-info output examples:
     if result.failed:
       raise errors.HypervisorError("Running lxc-info failed: %s" %
                                    result.output)
     # lxc-info output examples:
-    # 'ganeti-lxc-test1' is STOPPED
-    # 'ganeti-lxc-test1' is RUNNING
+    # 'state: STOPPED
+    # 'state: RUNNING
     _, state = result.stdout.rsplit(None, 1)
     if state != "RUNNING":
       return None
 
     cpu_list = self._GetCgroupCpuList(instance_name)
     _, state = result.stdout.rsplit(None, 1)
     if state != "RUNNING":
       return None
 
     cpu_list = self._GetCgroupCpuList(instance_name)
-    return (instance_name, 0, 0, len(cpu_list), 0, 0)
+    memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
+    return (instance_name, 0, memory, len(cpu_list), 0, 0)
 
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameter
     @return: [(name, id, memory, vcpus, stat, times),...]
 
     """
     data = []
     @return: [(name, id, memory, vcpus, stat, times),...]
 
     """
     data = []
-    for name in self.ListInstances():
-      data.append(self.GetInstanceInfo(name))
+    for name in os.listdir(self._ROOT_DIR):
+      try:
+        info = self.GetInstanceInfo(name)
+      except errors.HypervisorError:
+        continue
+      if info:
+        data.append(info)
     return data
 
   def _CreateConfigFile(self, instance, root_dir):
     return data
 
   def _CreateConfigFile(self, instance, root_dir):
@@ -239,6 +249,17 @@ class LXCHypervisor(hv_base.BaseHypervisor):
       out.append("lxc.cgroup.cpuset.cpus = %s" %
                  instance.hvparams[constants.HV_CPU_MASK])
 
       out.append("lxc.cgroup.cpuset.cpus = %s" %
                  instance.hvparams[constants.HV_CPU_MASK])
 
+    # Memory
+    # Conditionally enable, memory resource controller might be disabled
+    cgroup = self._GetCgroupMountPoint()
+    if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')):
+      out.append("lxc.cgroup.memory.limit_in_bytes = %dM" %
+                 instance.beparams[constants.BE_MAXMEM])
+
+    if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')):
+      out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" %
+                 instance.beparams[constants.BE_MAXMEM])
+
     # Device control
     # deny direct device access
     out.append("lxc.cgroup.devices.deny = a")
     # Device control
     # deny direct device access
     out.append("lxc.cgroup.devices.deny = a")
@@ -269,7 +290,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
   def StartInstance(self, instance, block_devices, startup_paused):
     """Start an instance.
 
   def StartInstance(self, instance, block_devices, startup_paused):
     """Start an instance.
 
-    For LCX, we try to mount the block device and execute 'lxc-start'.
+    For LXC, we try to mount the block device and execute 'lxc-start'.
     We use volatile containers.
 
     """
     We use volatile containers.
 
     """
@@ -337,6 +358,9 @@ class LXCHypervisor(hv_base.BaseHypervisor):
         logging.warning("Error while doing lxc-stop for %s: %s", name,
                         result.output)
 
         logging.warning("Error while doing lxc-stop for %s: %s", name,
                         result.output)
 
+    if not os.path.ismount(root_dir):
+      return
+
     for mpath in self._GetMountSubdirs(root_dir):
       result = utils.RunCmd(["umount", mpath])
       if result.failed:
     for mpath in self._GetMountSubdirs(root_dir):
       result = utils.RunCmd(["umount", mpath])
       if result.failed:
@@ -373,11 +397,14 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     # Currently lxc instances don't have memory limits
     pass
 
     # Currently lxc instances don't have memory limits
     pass
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
     This is just a wrapper over the base GetLinuxNodeInfo method.
 
     """Return information about the node.
 
     This is just a wrapper over the base GetLinuxNodeInfo method.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters, not used in this class
+
     @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
     @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
@@ -394,22 +421,40 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     return objects.InstanceConsole(instance=instance.name,
                                    kind=constants.CONS_SSH,
                                    host=instance.primary_node,
     return objects.InstanceConsole(instance=instance.name,
                                    kind=constants.CONS_SSH,
                                    host=instance.primary_node,
-                                   user=constants.GANETI_RUNAS,
+                                   user=constants.SSH_CONSOLE_USER,
                                    command=["lxc-console", "-n", instance.name])
 
                                    command=["lxc-console", "-n", instance.name])
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
     """Verify the hypervisor.
 
-    For the chroot manager, it just checks the existence of the base dir.
+    For the LXC manager, it just checks the existence of the base dir.
+
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against; not used here
+
+    @return: Problem description if something is wrong, C{None} otherwise
 
     """
 
     """
+    msgs = []
+
     if not os.path.exists(self._ROOT_DIR):
     if not os.path.exists(self._ROOT_DIR):
-      return "The required directory '%s' does not exist." % self._ROOT_DIR
+      msgs.append("The required directory '%s' does not exist" %
+                  self._ROOT_DIR)
+
+    try:
+      self._GetCgroupMountPoint()
+    except errors.HypervisorError, err:
+      msgs.append(str(err))
+
+    return self._FormatVerifyResults(msgs)
 
   @classmethod
 
   @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(cls, hvparams=None):
     """LXC powercycle, just a wrapper over Linux powercycle.
 
     """LXC powercycle, just a wrapper over Linux powercycle.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     cls.LinuxPowercycle()
 
     """
     cls.LinuxPowercycle()