creole/creole/containers.py

225 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
#
##########################################################################
# creole.containers - management of LXC containers
# Copyright © 2012,2013 Pôle de compétences EOLE <eole@ac-dijon.fr>
#
# 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