From b3d352c601df23f690fb329e06107f454024a7ff Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 27 Apr 2018 04:34:23 +0100 Subject: [PATCH] Add lxc container support. --- docs/api.rst | 14 ++++++++++ mitogen/core.py | 1 + mitogen/lxc.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++ mitogen/parent.py | 21 +++++++++++++-- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 mitogen/lxc.py diff --git a/docs/api.rst b/docs/api.rst index fa4e058c..6186ed4f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -712,6 +712,20 @@ Router Class Filename or complete path to the Docker binary. ``PATH`` will be searched if given as a filename. Defaults to ``docker``. + .. method:: lxc (container, lxc_attach_path=None, \**kwargs) + + Construct a context on the local machine within an LXC container. The + ``lxc-attach`` program must be available. + + Accepts all parameters accepted by :py:meth:`local`, in addition to: + + :param str container: + Existing container to connect to. Defaults to ``None``. + :param str lxc_attach_path: + Filename or complete path to the ``lxc-attach`` binary. ``PATH`` + will be searched if given as a filename. Defaults to + ``lxc-attach``. + .. method:: sudo (username=None, sudo_path=None, password=None, \**kwargs) Construct a context on the local machine over a ``sudo`` invocation. diff --git a/mitogen/core.py b/mitogen/core.py index 5e6a73a7..0dd669af 100644 --- a/mitogen/core.py +++ b/mitogen/core.py @@ -488,6 +488,7 @@ class Importer(object): 'docker', 'fakessh', 'fork', + 'lxc', 'master', 'parent', 'service', diff --git a/mitogen/lxc.py b/mitogen/lxc.py new file mode 100644 index 00000000..73babc01 --- /dev/null +++ b/mitogen/lxc.py @@ -0,0 +1,68 @@ +# Copyright 2017, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import logging + +import mitogen.core +import mitogen.parent + + +LOG = logging.getLogger(__name__) + + +class Stream(mitogen.parent.Stream): + create_child_args = { + # If lxc-attach finds any of stdin, stdout, stderr connected to a TTY, + # to prevent input injection it creates a proxy pty, forcing all IO to + # be buffered in <4KiB chunks. So ensure stderr is also routed to the + # socketpair. + 'merge_stdio': True + } + + container = None + lxc_attach_path = 'lxc-attach' + + def construct(self, container, lxc_attach_path=None, **kwargs): + super(Stream, self).construct(**kwargs) + if container: + self.container = container + if lxc_attach_path: + self.lxc_attach_path = lxc_attach_apth + + def connect(self): + super(Stream, self).connect() + self.name = 'lxc.' + self.container + + def get_boot_command(self): + bits = [ + self.lxc_attach_path, + '--clear-env', + '--name', self.container, + '--', + ] + return bits + super(Stream, self).get_boot_command() diff --git a/mitogen/parent.py b/mitogen/parent.py index db38fb7b..501cfce2 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -243,10 +243,17 @@ def create_socketpair(): return parentfp, childfp -def create_child(args): +def create_child(args, merge_stdio=False): """ Create a child process whose stdin/stdout is connected to a socket. + :param args: + Argument vector for execv() call. + :param bool merge_stdio: + If :data:`True`, arrange for `stderr` to be connected to the `stdout` + socketpair, rather than inherited from the parent process. This may be + necessary to ensure that not TTY is connected to any stdio handle, for + instance when using LXC. :returns: `(pid, socket_obj, :data:`None`)` """ @@ -257,11 +264,17 @@ def create_child(args): # O_NONBLOCK from Python's future stdin fd. mitogen.core.set_block(childfp.fileno()) + if merge_stdio: + extra = {'stderr': childfp} + else: + extra = {} + proc = subprocess.Popen( args=args, stdin=childfp, stdout=childfp, close_fds=True, + **extra ) childfp.close() # Decouple the socket from the lifetime of the Python socket object. @@ -696,12 +709,13 @@ class Stream(mitogen.core.Stream): return zlib.compress(minimize_source(source), 9) create_child = staticmethod(create_child) + create_child_args = {} name_prefix = 'local' def start_child(self): args = self.get_boot_command() try: - return self.create_child(args) + return self.create_child(args, **self.create_child_args) except OSError: e = sys.exc_info()[1] msg = 'Child start failed: %s. Command was: %s' % (e, Argv(args)) @@ -994,6 +1008,9 @@ class Router(mitogen.core.Router): self._context_by_id[context.context_id] = context return context + def lxc(self, **kwargs): + return self.connect('lxc', **kwargs) + def docker(self, **kwargs): return self.connect('docker', **kwargs)