Support LXD; closes #339.

This commit is contained in:
David Wilson 2018-08-10 08:43:13 +01:00
parent 4077182fb2
commit 81c8156965
6 changed files with 141 additions and 19 deletions

View File

@ -569,10 +569,10 @@ additional differences exist that may break existing playbooks.
LXC LXC
~~~ ~~~
Like `lxc <https://docs.ansible.com/ansible/2.6/plugins/connection/lxc.html>`_ Connect to classic LXC containers, like `lxc
and `lxd <https://docs.ansible.com/ansible/2.6/plugins/connection/lxd.html>`_ <https://docs.ansible.com/ansible/2.6/plugins/connection/lxc.html>`_ except
except connection delegation is supported, and ``lxc-attach`` is always used connection delegation is supported, and ``lxc-attach`` is always used rather
rather than the LXC Python bindings, as is usual with ``lxc``. than the LXC Python bindings, as is usual with ``lxc``.
The ``lxc-attach`` command must be available on the host machine. 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). * ``ansible_host``: Name of LXC container (default: inventory hostname).
.. _method-lxd:
LXD
~~~
Connect to modern LXD containers, like `lxd
<https://docs.ansible.com/ansible/2.6/plugins/connection/lxd.html>`_ 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:
Machinectl Machinectl
@ -602,21 +616,23 @@ Setns
~~~~~ ~~~~~
The ``setns`` method connects to Linux containers via `setns(2) The ``setns`` method connects to Linux containers via `setns(2)
<https://linux.die.net/man/2/setns>`_. Unlike :ref:`method-docker` and <https://linux.die.net/man/2/setns>`_. Unlike :ref:`method-docker`,
:ref:`method-lxc` the namespace transition is handled internally, ensuring :ref:`method-lxc`, and :ref:`method-lxd` the namespace transition is handled
optimal throughput to the child. This is necessary for :ref:`machinectl` where internally, ensuring optimal throughput to the child. This is necessary for
only PTY channels are supported. :ref:`machinectl` where only PTY channels are supported.
A utility program must be installed to discover the PID of the container's root A utility program must be installed to discover the PID of the container's root
process. 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 * ``ansible_host``: Name of container as it is known to the corresponding tool
(default: inventory hostname). (default: inventory hostname).
* ``ansible_user``: Name of user within the container to execute as. * ``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_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 * ``mitogen_lxc_path``: path to LXD's ``lxc`` command if not available as
``/usr/bin/lxc-info``. ``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 * ``mitogen_machinectl_path``: path to ``machinectl`` command if not available
as ``/bin/machinectl``. as ``/bin/machinectl``.

View File

@ -590,8 +590,8 @@ Router Class
.. method:: lxc (container, lxc_attach_path=None, \**kwargs) .. method:: lxc (container, lxc_attach_path=None, \**kwargs)
Construct a context on the local machine within an LXC container using Construct a context on the local machine within an LXC classic
the ``lxc-attach`` program. container using the ``lxc-attach`` program.
Accepts all parameters accepted by :py:meth:`local`, in addition to: 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 will be searched if given as a filename. Defaults to
``lxc-attach``. ``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) .. 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 Construct a context in the style of :meth:`local`, but change the
@ -609,7 +622,8 @@ Router Class
executing Python. executing Python.
The namespaces to use, and the active root file system are taken from 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 A program is required only to find the root PID, after which management
of the child Python interpreter is handled directly. of the child Python interpreter is handled directly.
@ -617,14 +631,16 @@ Router Class
:param str container: :param str container:
Container to connect to. Container to connect to.
:param str kind: :param str kind:
One of ``docker``, ``lxc`` or ``machinectl``. One of ``docker``, ``lxc``, ``lxd`` or ``machinectl``.
:param str docker_path: :param str docker_path:
Filename or complete path to the Docker binary. ``PATH`` will be Filename or complete path to the Docker binary. ``PATH`` will be
searched if given as a filename. Defaults to ``docker``. 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: :param str lxc_info_path:
Filename or complete path to the ``lxc-info`` binary. ``PATH`` Filename or complete path to the LXC ``lxc-info`` binary. ``PATH``
will be searched if given as a filename. Defaults to will be searched if given as a filename. Defaults to ``lxc-info``.
``lxc-info``.
:param str machinectl_path: :param str machinectl_path:
Filename or complete path to the ``machinectl`` binary. ``PATH`` Filename or complete path to the ``machinectl`` binary. ``PATH``
will be searched if given as a filename. Defaults to will be searched if given as a filename. Defaults to

View File

@ -614,6 +614,7 @@ class Importer(object):
'fork', 'fork',
'jail', 'jail',
'lxc', 'lxc',
'lxd',
'master', 'master',
'minify', 'minify',
'parent', 'parent',

70
mitogen/lxd.py Normal file
View File

@ -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()

View File

@ -1288,6 +1288,9 @@ class Router(mitogen.core.Router):
def lxc(self, **kwargs): def lxc(self, **kwargs):
return self.connect(u'lxc', **kwargs) return self.connect(u'lxc', **kwargs)
def lxd(self, **kwargs):
return self.connect(u'lxd', **kwargs)
def setns(self, **kwargs): def setns(self, **kwargs):
return self.connect(u'setns', **kwargs) return self.connect(u'setns', **kwargs)

View File

@ -94,6 +94,16 @@ def get_lxc_pid(path, name):
raise Error("could not find PID from lxc-info output.\n%s", output) 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): def get_machinectl_pid(path, name):
output = _run_command([path, 'status', name]) output = _run_command([path, 'status', name])
for line in output.splitlines(): for line in output.splitlines():
@ -110,18 +120,22 @@ class Stream(mitogen.parent.Stream):
container = None container = None
username = None username = None
kind = None kind = None
python_path = 'python'
docker_path = 'docker' docker_path = 'docker'
lxc_path = 'lxc'
lxc_info_path = 'lxc-info' lxc_info_path = 'lxc-info'
machinectl_path = 'machinectl' machinectl_path = 'machinectl'
GET_LEADER_BY_KIND = { GET_LEADER_BY_KIND = {
'docker': ('docker_path', get_docker_pid), 'docker': ('docker_path', get_docker_pid),
'lxc': ('lxc_info_path', get_lxc_pid), 'lxc': ('lxc_info_path', get_lxc_pid),
'lxd': ('lxc_path', get_lxd_pid),
'machinectl': ('machinectl_path', get_machinectl_pid), 'machinectl': ('machinectl_path', get_machinectl_pid),
} }
def construct(self, container, kind, username=None, docker_path=None, 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) super(Stream, self).construct(**kwargs)
if kind not in self.GET_LEADER_BY_KIND: if kind not in self.GET_LEADER_BY_KIND:
raise Error('unsupported container kind: %r', kind) raise Error('unsupported container kind: %r', kind)
@ -132,6 +146,8 @@ class Stream(mitogen.parent.Stream):
self.username = username self.username = username
if docker_path: if docker_path:
self.docker_path = docker_path self.docker_path = docker_path
if lxc_path:
self.lxc_path = lxc_path
if lxc_info_path: if lxc_info_path:
self.lxc_info_path = lxc_info_path self.lxc_info_path = lxc_info_path
if machinectl_path: if machinectl_path: