diff --git a/docs/ansible.rst b/docs/ansible.rst index 5bec89e7..a6130873 100644 --- a/docs/ansible.rst +++ b/docs/ansible.rst @@ -569,10 +569,10 @@ additional differences exist that may break existing playbooks. LXC ~~~ -Like `lxc `_ -and `lxd `_ -except connection delegation is supported, and ``lxc-attach`` is always used -rather than the LXC Python bindings, as is usual with ``lxc``. +Connect to classic LXC containers, like `lxc +`_ except +connection delegation is supported, and ``lxc-attach`` is always used rather +than the LXC Python bindings, as is usual with ``lxc``. The ``lxc-attach`` command must be available on the host machine. @@ -580,6 +580,20 @@ The ``lxc-attach`` command must be available on the host machine. * ``ansible_host``: Name of LXC container (default: inventory hostname). +.. _method-lxd: + +LXD +~~~ + +Connect to modern LXD containers, like `lxd +`_ except +connection delegation is supported. The ``lxc`` command must be available on +the host machine. + +* ``ansible_python_interpreter`` +* ``ansible_host``: Name of LXC container (default: inventory hostname). + + .. _machinectl: Machinectl @@ -602,21 +616,23 @@ Setns ~~~~~ The ``setns`` method connects to Linux containers via `setns(2) -`_. Unlike :ref:`method-docker` and -:ref:`method-lxc` the namespace transition is handled internally, ensuring -optimal throughput to the child. This is necessary for :ref:`machinectl` where -only PTY channels are supported. +`_. Unlike :ref:`method-docker`, +:ref:`method-lxc`, and :ref:`method-lxd` the namespace transition is handled +internally, ensuring optimal throughput to the child. This is necessary for +:ref:`machinectl` where only PTY channels are supported. A utility program must be installed to discover the PID of the container's root process. -* ``mitogen_kind``: one of ``docker``, ``lxc`` or ``machinectl``. +* ``mitogen_kind``: one of ``docker``, ``lxc``, ``lxd`` or ``machinectl``. * ``ansible_host``: Name of container as it is known to the corresponding tool (default: inventory hostname). * ``ansible_user``: Name of user within the container to execute as. * ``mitogen_docker_path``: path to Docker if not available on the system path. -* ``mitogen_lxc_info_path``: path to ``lxc-info`` command if not available as - ``/usr/bin/lxc-info``. +* ``mitogen_lxc_path``: path to LXD's ``lxc`` command if not available as + ``lxc-info``. +* ``mitogen_lxc_info_path``: path to LXC classic's ``lxc-info`` command if not + available as ``lxc-info``. * ``mitogen_machinectl_path``: path to ``machinectl`` command if not available as ``/bin/machinectl``. diff --git a/docs/api.rst b/docs/api.rst index 92abe1ec..ed9509fc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -590,8 +590,8 @@ Router Class .. method:: lxc (container, lxc_attach_path=None, \**kwargs) - Construct a context on the local machine within an LXC container using - the ``lxc-attach`` program. + Construct a context on the local machine within an LXC classic + container using the ``lxc-attach`` program. Accepts all parameters accepted by :py:meth:`local`, in addition to: @@ -602,6 +602,19 @@ Router Class will be searched if given as a filename. Defaults to ``lxc-attach``. + .. method:: lxc (container, lxc_attach_path=None, \**kwargs) + + Construct a context on the local machine within a LXD container using + the ``lxc`` program. + + 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_path: + Filename or complete path to the ``lxc`` binary. ``PATH`` will be + searched if given as a filename. Defaults to ``lxc``. + .. method:: setns (container, kind, docker_path=None, lxc_info_path=None, machinectl_path=None, \**kwargs) Construct a context in the style of :meth:`local`, but change the @@ -609,7 +622,8 @@ Router Class executing Python. The namespaces to use, and the active root file system are taken from - the root PID of a running Docker, LXC, or systemd-nspawn container. + the root PID of a running Docker, LXC, LXD, or systemd-nspawn + container. A program is required only to find the root PID, after which management of the child Python interpreter is handled directly. @@ -617,14 +631,16 @@ Router Class :param str container: Container to connect to. :param str kind: - One of ``docker``, ``lxc`` or ``machinectl``. + One of ``docker``, ``lxc``, ``lxd`` or ``machinectl``. :param str docker_path: Filename or complete path to the Docker binary. ``PATH`` will be searched if given as a filename. Defaults to ``docker``. + :param str lxc_path: + Filename or complete path to the LXD ``lxc`` binary. ``PATH`` will + be searched if given as a filename. Defaults to ``lxc``. :param str lxc_info_path: - Filename or complete path to the ``lxc-info`` binary. ``PATH`` - will be searched if given as a filename. Defaults to - ``lxc-info``. + Filename or complete path to the LXC ``lxc-info`` binary. ``PATH`` + will be searched if given as a filename. Defaults to ``lxc-info``. :param str machinectl_path: Filename or complete path to the ``machinectl`` binary. ``PATH`` will be searched if given as a filename. Defaults to diff --git a/mitogen/core.py b/mitogen/core.py index cfcd70d2..261f2621 100644 --- a/mitogen/core.py +++ b/mitogen/core.py @@ -614,6 +614,7 @@ class Importer(object): 'fork', 'jail', 'lxc', + 'lxd', 'master', 'minify', 'parent', diff --git a/mitogen/lxd.py b/mitogen/lxd.py new file mode 100644 index 00000000..6e8e8b18 --- /dev/null +++ b/mitogen/lxd.py @@ -0,0 +1,70 @@ +# 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): + child_is_immediate_subprocess = False + create_child_args = { + # If lxc 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_path = 'lxc' + python_path = 'python' + + def construct(self, container, lxc_path=None, **kwargs): + super(Stream, self).construct(**kwargs) + self.container = container + if lxc_path: + self.lxc_path = lxc_path + + def connect(self): + super(Stream, self).connect() + self.name = u'lxd.' + self.container + + def get_boot_command(self): + bits = [ + self.lxc_path, + 'exec', + '--force-noninteractive', + self.container, + '--', + ] + return bits + super(Stream, self).get_boot_command() diff --git a/mitogen/parent.py b/mitogen/parent.py index 4299d3cd..14e0ef6d 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -1288,6 +1288,9 @@ class Router(mitogen.core.Router): def lxc(self, **kwargs): return self.connect(u'lxc', **kwargs) + def lxd(self, **kwargs): + return self.connect(u'lxd', **kwargs) + def setns(self, **kwargs): return self.connect(u'setns', **kwargs) diff --git a/mitogen/setns.py b/mitogen/setns.py index 1779ca77..224550ce 100644 --- a/mitogen/setns.py +++ b/mitogen/setns.py @@ -94,6 +94,16 @@ def get_lxc_pid(path, name): raise Error("could not find PID from lxc-info output.\n%s", output) +def get_lxd_pid(path, name): + output = _run_command([path, 'info', name]) + for line in output.splitlines(): + bits = line.split() + if bits and bits[0] == 'Pid:': + return int(bits[1]) + + raise Error("could not find PID from lxc output.\n%s", output) + + def get_machinectl_pid(path, name): output = _run_command([path, 'status', name]) for line in output.splitlines(): @@ -110,18 +120,22 @@ class Stream(mitogen.parent.Stream): container = None username = None kind = None + python_path = 'python' docker_path = 'docker' + lxc_path = 'lxc' lxc_info_path = 'lxc-info' machinectl_path = 'machinectl' GET_LEADER_BY_KIND = { 'docker': ('docker_path', get_docker_pid), 'lxc': ('lxc_info_path', get_lxc_pid), + 'lxd': ('lxc_path', get_lxd_pid), 'machinectl': ('machinectl_path', get_machinectl_pid), } def construct(self, container, kind, username=None, docker_path=None, - lxc_info_path=None, machinectl_path=None, **kwargs): + lxc_path=None, lxc_info_path=None, machinectl_path=None, + **kwargs): super(Stream, self).construct(**kwargs) if kind not in self.GET_LEADER_BY_KIND: raise Error('unsupported container kind: %r', kind) @@ -132,6 +146,8 @@ class Stream(mitogen.parent.Stream): self.username = username if docker_path: self.docker_path = docker_path + if lxc_path: + self.lxc_path = lxc_path if lxc_info_path: self.lxc_info_path = lxc_info_path if machinectl_path: