X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/3329f4dea673d11088966e0f991e3a40f9506206..8ef418bb92:/lib/hypervisor/hv_lxc.py diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py index 49fd77a..819df79 100644 --- a/lib/hypervisor/hv_lxc.py +++ b/lib/hypervisor/hv_lxc.py @@ -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 @@ -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 pathutils 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. - 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 - - implement memory limits, but only optionally, depending on host - kernel support 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 - - 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 @@ -145,7 +132,7 @@ class LXCHypervisor(hv_base.BaseHypervisor): """ cgroup = cls._GetCgroupMountPoint() try: - cpus = utils.ReadFile(utils.PathJoin(cgroup, + cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc', instance_name, "cpuset.cpus")) except EnvironmentError, err: @@ -154,49 +141,72 @@ class LXCHypervisor(hv_base.BaseHypervisor): 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. """ - 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 - + @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 - 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: - # '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) - 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. + @type hvparams: dict of strings + @param hvparams: hypervisor parameter @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): @@ -239,6 +249,17 @@ class LXCHypervisor(hv_base.BaseHypervisor): 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") @@ -269,7 +290,7 @@ class LXCHypervisor(hv_base.BaseHypervisor): 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. """ @@ -337,6 +358,9 @@ class LXCHypervisor(hv_base.BaseHypervisor): 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: @@ -373,11 +397,14 @@ class LXCHypervisor(hv_base.BaseHypervisor): # 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. + @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 @@ -394,22 +421,40 @@ class LXCHypervisor(hv_base.BaseHypervisor): 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]) - def Verify(self): + def Verify(self, hvparams=None): """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): - 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 - def PowercycleNode(cls): + def PowercycleNode(cls, hvparams=None): """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()