# -*- coding: utf-8 -*- # ########################################################################## # creole.containers - management of LXC containers # Copyright © 2012,2013 Pôle de compétences EOLE # # License CeCILL: # * in french: http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html # * in english http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ########################################################################## """Manage LXC containers """ from .client import CreoleClient, _CONTAINER_COMPONENTS from .config import VIRTENABLED_LOCKFILE, VIRTDISABLED_LOCKFILE from .error import VirtError from .config import templatedir, VIRTROOT from .template import CreoleTemplateEngine from pyeole.process import system_code, system_out, system_progress_out from pyeole.diagnose import test_tcp from .i18n import _ from distutils.spawn import find_executable from os.path import isdir from os.path import isfile, islink from os.path import ismount from os.path import join from os.path import dirname from os import access from os import F_OK from os import stat from os import symlink from os import makedirs from os import mknod from os import makedev from os import major from os import minor from os import unlink from stat import S_IFBLK from stat import S_ISBLK from hashlib import md5 from glob import glob import cjson import logging client = CreoleClient() log = logging.getLogger(__name__) _LXC_MD5 = '/etc/eole/lxc.md5' _LXC_LOG = '/var/log/isolation.log' _NOT_REALLY_LXC_CONTAINERS = ['root', 'all'] """List of container names that are not to be generated. """ _LXC_TEMPLATE = {'config': "lxc.config", 'fstab': "lxc.fstab", 'rootfs/etc/network/interfaces' : "lxc.interfaces", } """Creole templates for LXC containers. """ def is_lxc_locked(): """Check if the LXC virtualization is locked. The virtualization is locked after first ``instance`` of the server to avoid switching between modes. :return: ``enable`` if LXC is enabled, ``disable`` if LXC is disabled or ``None`` where there is no lockfile. """ if isfile(VIRTENABLED_LOCKFILE) and isfile(VIRTDISABLED_LOCKFILE): raise VirtError(_(u"Invalid LXC lock files state: both are present.")) elif isfile(VIRTENABLED_LOCKFILE): virtlocked = 'enable' elif isfile(VIRTDISABLED_LOCKFILE): virtlocked = 'disable' else: virtlocked = None return virtlocked def is_lxc_enabled(): """Check if LXC controller is enabled We do not accept to switch between enabled and disabled LXC, after first ``instance``, a lock file is set to check at each ``reconfigure``. :return: If the LXC container mode is enabled. :rtype: `bool` :raise VirtError: if state in inconsistent between configuration and lock files. """ containers_enabled = client.get_creole('mode_conteneur_actif', 'non') == 'oui' if containers_enabled and not find_executable('lxc-info'): raise VirtError(_(u'LXC is enabled but LXC commands not found in PATH.')) if containers_enabled and is_lxc_locked() == 'disable': raise VirtError(_(u"Server already instantiated in no containers mode, attempt to activate containers mode aborted.")) elif not containers_enabled and is_lxc_locked() == 'enable': raise VirtError(_(u"Server already instantiated in containers mode, attempt to activate no containers mode aborted.")) return containers_enabled def generate_lxc_container(name, logger=None): """Run creation of a container. Check if LXC is enabled and take care of ``root`` and ``all`` containers. :param name: name of the LXC container :type name: `str` """ if name not in _NOT_REALLY_LXC_CONTAINERS: if not test_tcp('localhost', client.get_creole('apt_cacher_port')): raise Exception(_('cacher not available, please start check log in /var/log/apt-cacher-ng/ and restart it with "service apt-cacher-ng start" command')) if isfile(_LXC_LOG): unlink(_LXC_LOG) cmd = ['lxc-create', '-n', name, '-t', 'eole'] log.debug('Run: {0}'.format(' '.join(cmd))) code, stdout, stderr = system_progress_out(cmd, _(u"Managing container {0}").format(name), logger) fh = open(_LXC_LOG, 'w') fh.write(stdout) fh.write(stderr) fh.close() if code != 0 and stdout.find(u"'{0}' already exists'".format(name)) >= 0: raise Exception(_('error during the process of container creation, more informations in {0}').format(_LXC_LOG)) path_container = client.get_creole('container_path_{0}'.format(name)) path_apt_eole_conf = join(path_container, 'etc', 'apt', 'apt-eole.conf') path_apt_eole = join(path_container, 'usr', 'sbin', 'apt-eole') if not isfile(path_apt_eole_conf) or not isfile(path_apt_eole): raise Exception(_('eole-common-pkg not installed in container, something goes wrong, more informations in {0}').format(_LXC_LOG)) def is_lxc_running(container): """Check if an LXC container is running. This check at LXC level and check TCP on port SSH. :param container: the container informations :type container: `dict` :return: if the container is running and reachable :rtype: `bool` """ return is_lxc_started(container) and test_tcp(container[u'ip'], 22) def is_lxc_started(container): """Check if an LXC container is started. This check at LXC level and check TCP on port SSH. :param container: the container informations :type container: `dict` :return: if the container is started :rtype: `bool` """ if not is_lxc_enabled() or container.get(u'path', None) == '': return True if container.get(u'name', None) is None: raise ValueError(_(u"Container has no name")) if container.get(u'ip', None) is None: raise ValueError(_(u"Container {0} has no IP").format(container[u'name'])) cmd = ['lxc-info', '--state', '--name', container[u'name']] code, stdout, stderr = system_out(cmd) return stdout.strip().endswith('RUNNING') def create_mount_point(group): """Create mount points in LXC. This is required for LXC to start. """ if 'fstabs' not in group: return for fstab in group['fstabs']: mount_point = fstab.get('mount_point', fstab['name']) full_path = join(group['path'], mount_point.lstrip('/')) if not isdir(full_path): makedirs(full_path) def lxc_need_restart(): def md5sum(file): return md5(open(file).read()).hexdigest() files = ['/etc/lxc/default.conf', '/etc/default/lxc-net'] files += glob('/opt/lxc/*/config') files += glob('/opt/lxc/*/fstab') md5s = [] for f in files: md5s.append(md5sum(f)) if not isfile(_LXC_MD5): ret = True else: try: old_md5s = cjson.decode(open(_LXC_MD5, 'r').read()) except cjson.DecodeError: ret = True else: ret = not old_md5s == md5s if ret: fh = open(_LXC_MD5, 'w') fh.write(cjson.encode(md5s)) fh.close() return ret