#
#
-# 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
import logging
from ganeti import constants
-from ganeti import errors # pylint: disable-msg=W0611
+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
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
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_GANETI_DIR + "/lxc"
- _LOG_FILE = constants.LOG_DIR + "hv_lxc.log"
+ _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
_DEVS = [
"c 1:3", # /dev/null
"c 1:5", # /dev/zero
_DIR_MODE = 0755
PARAMETERS = {
+ constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
}
def __init__(self):
"""
return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
- def ListInstances(self):
+ @classmethod
+ def _InstanceLogFile(cls, instance_name):
+ """Return the log file for an instance.
+
+ """
+ return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
+
+ @classmethod
+ def _GetCgroupMountPoint(cls):
+ for _, mountpoint, fstype, _ in utils.GetMounts():
+ if fstype == "cgroup":
+ return mountpoint
+ raise errors.HypervisorError("The cgroup filesystem is not mounted")
+
+ @classmethod
+ def _GetCgroupCpuList(cls, instance_name):
+ """Return the list of CPU ids for an instance.
+
+ """
+ cgroup = cls._GetCgroupMountPoint()
+ try:
+ cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
+ instance_name,
+ "cpuset.cpus"))
+ except EnvironmentError, err:
+ raise errors.HypervisorError("Getting CPU list for instance"
+ " %s failed: %s" % (instance_name, err))
+
+ return utils.ParseCpuMask(cpus)
+
+ @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("Can't run lxc-ls: %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("Can't run lxc-info: %s" % result.output)
+ 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 (instance_name, 0, 0, 0, 0, 0)
- return None
+ if state != "RUNNING":
+ return None
- def GetAllInstancesInfo(self):
+ cpu_list = self._GetCgroupCpuList(instance_name)
+ memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
+ return (instance_name, 0, memory, len(cpu_list), 0, 0)
+
+ 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),...]
"""
- # TODO: read container info from the cgroup mountpoint
data = []
- for name in self.ListInstances():
- data.append((name, 0, 0, 0, 0, 0))
+ 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):
- """Create an lxc.conf file for an instance"""
+ """Create an lxc.conf file for an instance.
+
+ """
out = []
# hostname
out.append("lxc.utsname = %s" % instance.name)
# TODO: additional mounts, if we disable CAP_SYS_ADMIN
+ # CPUs
+ if instance.hvparams[constants.HV_CPU_MASK]:
+ cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
+ cpus_in_mask = len(cpu_list)
+ if cpus_in_mask != instance.beparams["vcpus"]:
+ raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
+ " the number of CPUs in the"
+ " cpu_mask (%d)" %
+ (instance.beparams["vcpus"],
+ cpus_in_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")
return "\n".join(out) + "\n"
- def StartInstance(self, instance, block_devices):
+ def StartInstance(self, instance, block_devices, startup_paused):
"""Start an instance.
- For LCX, we try to mount the block device and execute 'lxc-start
- start' (we use volatile containers).
+ For LXC, we try to mount the block device and execute 'lxc-start'.
+ We use volatile containers.
"""
root_dir = self._InstanceDir(instance.name)
try:
utils.EnsureDirs([(root_dir, self._DIR_MODE)])
except errors.GenericError, err:
- raise HypervisorError("Cannot create instance directory: %s", str(err))
+ raise HypervisorError("Creating instance directory failed: %s", str(err))
conf_file = self._InstanceConfFile(instance.name)
utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
+ log_file = self._InstanceLogFile(instance.name)
+ if not os.path.exists(log_file):
+ try:
+ utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
+ except EnvironmentError, err:
+ raise errors.HypervisorError("Creating hypervisor log file %s for"
+ " instance %s failed: %s" %
+ (log_file, instance.name, err))
+
if not os.path.ismount(root_dir):
if not block_devices:
raise HypervisorError("LXC needs at least one disk")
sda_dev_path = block_devices[0][1]
result = utils.RunCmd(["mount", sda_dev_path, root_dir])
if result.failed:
- raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
- # TODO: replace the global log file with a per-instance log file
+ raise HypervisorError("Mounting the root dir of LXC instance %s"
+ " failed: %s" % (instance.name, result.output))
result = utils.RunCmd(["lxc-start", "-n", instance.name,
- "-o", self._LOG_FILE, "-l", "DEBUG",
+ "-o", log_file,
+ "-l", "DEBUG",
"-f", conf_file, "-d"])
if result.failed:
raise HypervisorError("Running the lxc-start script failed: %s" %
if not retry and not force:
result = utils.RunCmd(["chroot", root_dir, "poweroff"])
if result.failed:
- raise HypervisorError("Can't run 'poweroff' for the instance: %s" %
- result.output)
+ raise HypervisorError("Running 'poweroff' on the instance"
+ " failed: %s" % result.output)
time.sleep(2)
result = utils.RunCmd(["lxc-stop", "-n", name])
if result.failed:
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:
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)" %
+ raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
(result.output, msg))
def RebootInstance(self, instance):
raise HypervisorError("The LXC hypervisor doesn't implement the"
" reboot functionality")
- def GetNodeInfo(self):
+ 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 lxc instances don't have memory limits
+ pass
+
+ 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
return self.GetLinuxNodeInfo()
@classmethod
- def GetShellCommandForConsole(cls, instance, hvparams, beparams):
+ def GetInstanceConsole(cls, instance, hvparams, beparams):
"""Return a command for connecting to the console of an instance.
"""
- return "lxc-console -n %s" % instance.name
+ return objects.InstanceConsole(instance=instance.name,
+ kind=constants.CONS_SSH,
+ host=instance.primary_node,
+ 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()
@param live: whether to do a live or non-live migration
"""
- raise HypervisorError("Migration not supported by the LXC hypervisor")
+ raise HypervisorError("Migration is not supported by the LXC 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
+
+ """
+ raise HypervisorError("Migration is not supported by the LXC hypervisor")