Skip to content

Commit

Permalink
Containerized controller
Browse files Browse the repository at this point in the history
Adding support (and docs) of running LNST controller
in podman/docker container.
  • Loading branch information
enhaut committed Nov 7, 2024
1 parent 4d76fd0 commit 5837e48
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 6 deletions.
32 changes: 32 additions & 0 deletions container_files/controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# syntax=docker/dockerfile:1
FROM fedora:41

RUN dnf install -y initscripts \
iputils \
python3.9 \
python-pip \
gcc \
python-devel \
libxml2-devel \
libxslt-devel \
libnl3 \
lksctp-tools-devel \
git \
libnl3-devel && \
curl -sSL https://install.python-poetry.org | \
python3 - --version 1.8.3

RUN mkdir -p /root/.lnst
COPY . /lnst
COPY container_files/controller/pool /root/.lnst/pool

RUN cd /lnst && \
/root/.local/bin/poetry config virtualenvs.path /root/lnst_venv && \
/root/.local/bin/poetry config virtualenvs.in-project false && \
/root/.local/bin/poetry install
# setting in-project to false to prevent poetry from
# using in-project .venv which might be present if
# user has mounted host-machine's lnst dir to /lnst

WORKDIR /lnst
CMD ["/lnst/container_files/controller/entrypoint.sh"]
108 changes: 108 additions & 0 deletions container_files/controller/container_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os
import sys
import traceback
from typing import Any, Type, Optional

from lnst.Recipes.ENRT import *
from lnst.Controller.Recipe import BaseRecipe
from lnst.Controller.Controller import Controller
from lnst.Controller.RecipeResults import ResultLevel, ResultType
from lnst.Controller.MachineMapper import ContainerMapper
from lnst.Controller.ContainerPoolManager import ContainerPoolManager

from lnst.Controller.RunSummaryFormatters import *
from lnst.Controller.RunSummaryFormatters.RunSummaryFormatter import RunSummaryFormatter


class ContainerRunner:
"""This class is responsible for running the LNST controller in a container.
Environment variables:
* DEBUG: Set to 1 to enable debug mode
* RECIPE: Name of the recipe class to run
* RECIPE_PARAMS: Parameters to pass to the recipe class
* FORMATTERS: List of formatters to use
* MULTIMATCH: Set to 1 to enable multimatch mode
Agents in containers-specific environment variables:
* PODMAN_URI: URI of the Podman socket
* IMAGE_NAME: Name of the container image
"""

def __init__(self) -> None:
self._controller = Controller(**self._parse_controller_params())
self._recipe_params: dict[str, Any] = self._parse_recipe_params()

if not os.getenv("RECIPE"):
raise ValueError("RECIPE environment variable is not set")
self._recipe_cls: Type[BaseRecipe] = eval(os.getenv("RECIPE", ""))
self._recipe: Optional[BaseRecipe] = None

self._formatters: list[Type[RunSummaryFormatter]] = self._parse_formatters()

def _parse_controller_params(self) -> dict:
params = {
"debug": bool(os.getenv("DEBUG", 0)),
}

if "PODMAN_URI" in os.environ:
return params | {
"podman_uri": os.getenv("PODMAN_URI"),
"image": os.getenv("IMAGE_NAME", "lnst"),
"network_plugin": "cni",
"poolMgr": ContainerPoolManager,
"mapper": ContainerMapper,
}

return params

def _parse_recipe_params(self) -> dict[str, Any]:
params = {}
for param in os.getenv("RECIPE_PARAMS", "").split(";"):
if not param:
continue
key, value = param.split("=")
params[key] = eval(value)

return params

def _parse_formatters(self) -> list[Type[RunSummaryFormatter]]:
return [
eval(formatter)
for formatter in os.getenv("FORMATTERS", "").split(";")
if formatter
]

def run(self) -> ResultType:
"""Initialize recipe class with parameters provided in `RECIPE_PARAMS`
and execute. Function returns overall result.
"""
overall_result = ResultType.PASS

try:
self._recipe = self._recipe_cls(**self._recipe_params)
self._controller.run(
self._recipe, multimatch=bool(os.getenv("MULTIMATCH", False))
)
except Exception:
print("LNST Controller crashed with an exception:", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
exit(ResultType.FAIL)

for formatter in self._formatters:
fmt = formatter(level=ResultLevel.IMPORTANT)
for run in self._recipe.runs:
print(fmt.format_run(run))
overall_result = ResultType.max_severity(
overall_result, run.overall_result
)

return overall_result


if __name__ == "__main__":
runner = ContainerRunner()
exit_code = 0 if runner.run() == ResultType.PASS else 1
exit(exit_code)
3 changes: 3 additions & 0 deletions container_files/controller/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
PYTHON_PATH=$(/root/.local/bin/poetry env info -p)/bin/python
exec "$PYTHON_PATH" /lnst/container_files/controller/container_runner.py
Empty file.
136 changes: 131 additions & 5 deletions docs/source/extensions.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
.. _containerized:

LNST in containers
^^^^^^^^^^^^^^^^^^
LNST supports running agents in containers at the host machine.
==================
LNST supports running both agents and controller in containers at the host machine.
LNST uses custom RPC protocol to communicate between controller and agents which
uses separate network interface to not interfere with test and so, it doesn't matter
where controller and agents are running as long as they can communicate.

With support of running LNST in containers your machine setup might look like this:

1. both controller and agents are running on your baremetal machines

2. controller is running on your baremetal machine and agents are running in containers

3. controller is running in container and agents are running on your baremetal machine

4. both controller and agents are running in containers


This article describes how to run individual parts of LNST in containers. If you want
to run either controller or agents on baremetal see :ref:`installation` section.


Common requirements
-------------------
We recommend to use Podman as this was developed and tested with Podman but should
work with Docker as well.

If you want to use Podman, follow installation steps on
`official Podman installation page <https://podman.io/getting-started/installation>`_.


.. _containerized_agents:

Containerized agents
--------------------

Containers and networks are dynamically created based on recipe requirements.
Containers are also automatically connected to networks.

Requirements
------------
````````````
The first requirement is **Podman**, follow installation steps on
`official Podman installation page <https://podman.io/getting-started/installation>`_.

Expand Down Expand Up @@ -65,7 +100,7 @@ Socket URL could be found at the top of logs generated by this command.
The usual URL is `unix:/run/podman/podman.sock`

Build LNST agent image
----------------------
``````````````````````
Currently, LNST does not support automated building, so build LNST agent
machine image.

Expand Down Expand Up @@ -103,10 +138,101 @@ Only initialization of `Controller()` object has to be changed:
And run the script.

Classes documentation
---------------------
`````````````````````
.. autoclass:: lnst.Controller.MachineMapper.ContainerMapper
:members:


.. automodule:: lnst.Controller.ContainerPoolManager
:members: ContainerPoolManager


Containerized controller
------------------------

Using containerized agents
``````````````````````````

Before proceeding with containerized controller, you need to build LNST
agent image (see :ref:`containerized_agents`) you also need to provide
following parameters as environment variables to controller container:

* `PODMAN_URI` - URI to Podman socket, e.g. `tcp://localhost[:port]`. This needs to be accessible from container.
* `IMAGE_NAME` - name of the image you built for agents.

It expects that you use CNI as network backend for Podman.


Using baremetal agents
``````````````````````
Firstly, you need to prepare machine XMLs if you decide to run agents on baremetal
machines (see :ref:`machines-pool`). Instead of putting them into `~/.lnst/pool`
directory, you need to put them into `container_files/controller/pool` directory.
Machine XMLs are copied to container during build process from
`container_files/controller/pool`.
Podman doesn't support copying files located outside of build context, so you
need to put it to LNST project directory.

.. note::
To avoid having to deal with pool files you can simply mount your `~/.lnst/pool` directory
to `/root/.lnst/pool/` in the container (read-only access is sufficient).


Build and run controller
````````````````````````

Build the controller image:

.. code-block:: bash
cd your_lnst_project_directory
podman build . -t lnst_controller -f container_files/controller/Dockerfile
This will copy pool files to `/root/.lnst/pool/` in container and LNST from
`your_lnst_project_directory` to `/lnst` in container.

.. note::
If you want to avoid rebuilding the image every time you change your LNST project (e.g. during
development), you can mount `your_lnst_project_directory` to `/lnst` in container. The LNST's
virtual environment is located outside of `/lnst/` directory, so if your changes requires
reinstallation fo LNST and/or its dependencies, you need to rebuild the image.


Before running the container, you need to provide environment variables:

* `RECIPE` - name of recipe class, these are loaded from `lnst.Recipes.ENRT` as wildcard import
* `RECIPE_PARAMS` - `;` separated list of parameters for recipe. Each parameter is in format `key=value`
* `FORMATTERS` - `;` separated list of formatters, these are loaded from `lnst.Formatters` as wildcard import
* `DEBUG` - enables/disables LNST's debug mode

.. warning::
`RECIPE`, `RECIPE_PARAMS` and `FORMATTERS` are parsed using Python's `eval` function,
which is a security risk. Make sure you trust the source of these variables.

Now, you can run the controller:

.. code-block:: bash
podman run -e RECIPE=SimpleNetworkRecipe -e RECIPE_PARAMS="perf_iterations=1;perf_duration=10" -e DEBUG=1 --rm --name lnst_controller lnst_controller
.. note::
Podman containers are by default NATed, so you may need to use some other `--network` mode
to make agent machines reachable from controller container. If agent machines are reachable
from your host machine, `--network=host` should do the job. Read
`Podman's documentation <https://github.com/containers/podman/blob/main/docs/tutorials/basic_networking.md#basic-network-setups>`_
first.


Or you can run more complex recipes:

.. code-block:: bash
podman run -e RECIPE=XDPDropRecipe -e RECIPE_PARAMS="perf_iterations=1;perf_tool_cpu=[0,1];multi_dev_interrupt_config={'host1':{'eth0':{'cpus':[0],'policy':'round-robin'}}}" --rm --name lnst_controller lnst_controller
Classes documentation
`````````````````````
.. autoclass:: container_files.controller.container_runner.ContainerRunner
:members:

11 changes: 10 additions & 1 deletion docs/source/installation.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _installation:

Install LNST and Hello world
============================

Expand All @@ -11,7 +13,9 @@ LNST is logically split into two separate application use cases:

Codebases for both use cases are developed in this repository and as we
currently don't have a stable release yet, the recommended method of
installation involves the following steps:
installation is either to install both Agents and Controller on your
local machines or to use docker/podman. Manual installation on your
machine involves the following steps:

.. code-block:: bash
Expand All @@ -20,6 +24,8 @@ installation involves the following steps:
poetry install
For installation of containerized version, see :ref:`containerized`.

This installs both the Controller and the Agent code, and you'll need to run
this on all the test machines that you want to use as well as the machine which
you want to use as the Controller. Optionally a Controller and a Agent CAN run
Expand Down Expand Up @@ -112,6 +118,9 @@ configured pools, since we didn't configure any yet, this is quite expected. But
running this script did take care of creating a default configuration file and
directory where we'll now be able to create our machine pool.


.. _machines-pool:

Creating a simple machine pool
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down

0 comments on commit 5837e48

Please sign in to comment.