224 lines
7.1 KiB
Python
224 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
|
|
|