This patch series implements L2TP support for the LNST recipes.
Due to the nature of the L2TP kernel subsystem the LNST L2TP API is split into two parts.
First one is the L2TPManager, which provides an API to create and delete the L2TP tunnels. The implementation is simply a wrapper fo the pyroute's L2tp API.
The second one is the L2TPSessionDevice class that represents a session within a previously configured L2TP tunnel. The class inherits from Device class and thus provides all of the API that is available for the other devices such as device link state operation, mtu setting, etc.
v2: - L2TPManager.cleanup(): fixed list modification during iteration issue
Jan Tluka (4): Devices.SoftDevice: remove unused _init_generic attribute RecipeCommon: add L2TPManager Devices: add L2TPSessionDevice docs: add L2TP device API documentation
.../device_classes/l2tpsessiondevice.rst | 7 + docs/source/devices.rst | 8 + docs/source/l2tp_manager.rst | 5 + docs/source/supported_devices.rst | 9 + docs/source/tester_api.rst | 1 + lnst/Devices/L2TPSessionDevice.py | 118 +++++++++++++ lnst/Devices/SoftDevice.py | 3 - lnst/Devices/__init__.py | 4 +- lnst/RecipeCommon/L2TPManager.py | 156 ++++++++++++++++++ 9 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 docs/source/device_classes/l2tpsessiondevice.rst create mode 100644 docs/source/devices.rst create mode 100644 docs/source/l2tp_manager.rst create mode 100644 docs/source/supported_devices.rst create mode 100644 lnst/Devices/L2TPSessionDevice.py create mode 100644 lnst/RecipeCommon/L2TPManager.py
This attribute is not used anywhere in the code so I'm removing it to avoid confusion.
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/SoftDevice.py | 3 --- 1 file changed, 3 deletions(-)
diff --git a/lnst/Devices/SoftDevice.py b/lnst/Devices/SoftDevice.py index 3373f1de..36d5e9cf 100644 --- a/lnst/Devices/SoftDevice.py +++ b/lnst/Devices/SoftDevice.py @@ -28,9 +28,6 @@ class SoftDevice(Device):
self._bulk_enabled = True
- self._init_generic = {"IFLA_LINKINFO": { - "attrs": [("IFLA_INFO_KIND", self._link_type)]}} - if "name" not in kwargs: kwargs["name"] = ifmanager.assign_name(self._name_template)
This adds L2TPManager class that can be used to create L2TP tunnels. Due to the nature of the L2TP subsystem this can't be implemented as a SoftDevice class. Instead user should use the init_class() method of the Host API to create a machine specific instance of the class. Then the instance methods create_tunnel(), delete_tunnel() can be used to manage the L2TP tunnels.
Support for the L2TP session devices will be added in a followup patch.
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/RecipeCommon/L2TPManager.py | 156 +++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 lnst/RecipeCommon/L2TPManager.py
diff --git a/lnst/RecipeCommon/L2TPManager.py b/lnst/RecipeCommon/L2TPManager.py new file mode 100644 index 00000000..dc5c6995 --- /dev/null +++ b/lnst/RecipeCommon/L2TPManager.py @@ -0,0 +1,156 @@ +""" +This module defines the L2TPManager class that provides an API for +creating and deleting L2TP tunnels. It uses pyroute2 API for the tunnel +management. + +Copyright 2021 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +jtluka@redhat.com (Jan Tluka) +""" + +import logging +from pyroute2.netlink import NetlinkError +from pyroute2.netlink.generic.l2tp import L2tp +from lnst.Common.LnstError import LnstError + + +class L2tpConfigurationError(LnstError): + pass + + +class L2tpDeconfigurationError(LnstError): + pass + + +class L2TPManager: + """ + This class serves as an LNST interface to create the L2TP tunnels in an + LNST recipe. + + Users should use the :meth:`Host.init_class` method to create a host + specific instance of the class as shown in the following example: + + .. code-block:: python + + from lnst.Controller import BaseRecipe + from lnst.Controller.Requirements import HostReq + from lnst.RecipeCommon.L2TPManager import L2TPManager + + class L2TPRecipe(BaseRecipe): + m1 = HostReq() + + def test(self): + m1 = self.matched.m1 + m1.l2tp = m1.init_class(L2TPManager) + + This class provides only the API to create and destroy the L2TP tunnels. + To create a session for an L2TP tunnel you have to use the + :any:`L2TPSessionDevice`. + + LNST will not cleanup any of the tunnels created in a recipe, so it is + user's responsibility to delete all previously created tunnels. This + applies also to situation when an exception is raised during the recipe + execution. + + This can be handled by a code similar to the following: + + .. code-block:: python + + class L2TPRecipe(BaseRecipe): + def test(self): + m1 = self.matched.m1 + m1.l2tp = m1.init_class(L2TPManager) + + try: + self._test() + finally: + m1.l2tp.cleanup() + + def _test(self): + m1 = self.matched.m1 + + m1.l2tp.create_tunnel( + tunnel_id=1000, + peer_tunnel_id=1000, + encap="udp", + local="192.168.200.1", + remote="192.168.200.2", + udp_sport=5000, + udp_dport=5000 + ) + """ + def __init__(self): + self._tunnels = [] + + try: + self._l2tp_api = L2tp() + except NetlinkError: + raise L2tpConfigurationError( + "Could not initialize pyroute's L2TP API. Please check if l2tp_eth module is loaded." + ) + + @property + def l2tp_api(self): + """ + This is a handle for the pyroute2's netlink l2tp API. + """ + return self._l2tp_api + + def create_tunnel(self, **kwargs): + """ + This method creates an L2TP tunnel based on the keyword arguments. + These arguments should match the pyroute2's :meth:`L2tp.create_tunnel` + arguments. + """ + logging.info(f"Creating L2TP tunnel: {kwargs}") + tunnel_id = kwargs["tunnel_id"] + if tunnel_id in self._tunnels: + raise L2tpConfigurationError(f"Tunnel with id {tunnel_id} already exists") + + response = self.l2tp_api.create_tunnel(**kwargs) + if self._response_errors(response) is not None: + raise L2tpConfigurationError( + "Could not create L2TP tunnel {tunnel_id} through pyroute API" + ) + + self._tunnels.append(tunnel_id) + return tunnel_id + + def cleanup(self): + """ + This method deletes all tunnels created previously through the instance + of this object. The method serves as a convenient way to cleanup at the + end of a recipe. + """ + for tunnel_id in self._tunnels: + self.delete_tunnel(tunnel_id) + + self._tunnels.clear() + + def delete_tunnel(self, tunnel_id): + """ + This method deletes the tunnel with the specified tunnel_id. + """ + logging.info(f"Deleting L2TP tunnel: {tunnel_id}") + response = self.l2tp_api.delete_tunnel(tunnel_id) + if self._response_errors(response) is not None: + raise L2tpDeconfigurationError( + f"Could not delete L2TP tunnel {tunnel_id} through pyroute API" + ) + + return True + + def _response_errors(self, response): + """ + delete response: ({'header': {'length': 36, 'type': 2, 'flags': 256, 'sequence_number': 258, 'pid': 4586, 'error': None, 'target': 'localhost', 'stats': Stats(qsize=0, delta=0, delay=0)}, 'event': 'NLMSG_ERROR'},) + create response: ({'header': {'length': 36, 'type': 2, 'flags': 256, 'sequence_number': 255, 'pid': 4586, 'error': None, 'target': 'localhost', 'stats': Stats(qsize=0, delta=0, delay=0)}, 'event': 'NLMSG_ERROR'},) + """ + for item in response: + if item["event"] == "NLMSG_ERROR" and item["header"]["error"] is not None: + return item["header"]["error"] + + return None
This adds support for the L2TP sessions implemented as LNST Device class.
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/L2TPSessionDevice.py | 118 ++++++++++++++++++++++++++++++ lnst/Devices/__init__.py | 4 +- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 lnst/Devices/L2TPSessionDevice.py
diff --git a/lnst/Devices/L2TPSessionDevice.py b/lnst/Devices/L2TPSessionDevice.py new file mode 100644 index 00000000..2cda9c96 --- /dev/null +++ b/lnst/Devices/L2TPSessionDevice.py @@ -0,0 +1,118 @@ +""" +Defines the L2TPSessionDevice class. + +Copyright 2021 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +jtluka@redhat.com (Jan Tluka) +""" + +from pyroute2.netlink import NetlinkError +from pyroute2.netlink.generic.l2tp import L2tp +from lnst.Common.DeviceError import DeviceError, DeviceConfigError +from lnst.Devices.Device import Device + + +class L2TPSessionDevice(Device): + """ + This device class allows user to create L2TP sessions for tunnels + previously created using the :any:`L2TPManager`. + + .. code-block:: python + + from lnst.Controller import BaseRecipe + from lnst.Controller.Requirements import HostReq + from lnst.RecipeCommon.L2TPManager import L2TPManager + from lnst.Devices import L2TPSessionDevice + + class L2TPRecipe(BaseRecipe): + m1 = HostReq() + + def test(self): + m1 = self.matched.m1 + m1.l2tp = m1.init_class(L2TPManager) + m1.l2tp.create_tunnel( + tunnel_id=1000, + peer_tunnel_id=1000, + encap="udp", + local="192.168.200.1", + remote="192.168.200.2", + udp_sport=5000, + udp_dport=5000 + + m1.session1 = L2TPSessionDevice( + tunnel_id=1000, + session_id=2000, + peer_session_id=2000 + ) + m1.session1.up() + """ + _name_template = "t_l2tp" + #: mandatory options for the device + _mandatory_opts = ["tunnel_id", "session_id", "peer_session_id"] + + def __init__(self, ifmanager, *args, **kwargs): + self._name = None + for i in self._mandatory_opts: + if i not in kwargs: + raise DeviceConfigError( + "Option {} is mandatory for type {}".format( + i, self.__class__.__name__ + ) + ) + + self._tunnel_id = tunnel_id = kwargs["tunnel_id"] + self._session_id = kwargs["session_id"] + self._peer_session_id = kwargs["peer_session_id"] + self._ifmanager = ifmanager + + super(L2TPSessionDevice, self).__init__(ifmanager) + + def _create(self): + try: + self._l2tp_api = L2tp() + except NetlinkError: + raise DeviceError( + "Could not initialize pyroute's L2TP API. Please check if l2tp_eth module is loaded." + ) + + session = { + "tunnel_id": self._tunnel_id, + "session_id": self._session_id, + "peer_session_id": self._peer_session_id, + } + if self._name is None: + self._name = session["ifname"] = self._ifmanager.assign_name( + self._name_template + ) + + try: + self._l2tp_api.create_session(**session) + except NetlinkError as e: + raise DeviceError(f"Could not create an L2TP session: {e}") + + def destroy(self): + self._l2tp_api.delete_session(self._tunnel_id, self._session_id) + return True + + @Device.name.getter + def name(self): + try: + return super(L2TPSessionDevice, self).name + except: + return self._name + + @property + def session_id(self): + return self._session_id + + @property + def peer_session_id(self): + return self._peer_session_id + + @property + def tunnel_id(self): + return self._tunnel_id diff --git a/lnst/Devices/__init__.py b/lnst/Devices/__init__.py index 4f68cb50..5ffed58e 100644 --- a/lnst/Devices/__init__.py +++ b/lnst/Devices/__init__.py @@ -14,6 +14,7 @@ from lnst.Devices.VtiDevice import VtiDevice, Vti6Device from lnst.Devices.VethDevice import VethDevice, PairedVethDevice from lnst.Devices.VethPair import VethPair from lnst.Devices.MacsecDevice import MacsecDevice +from lnst.Devices.L2TPSessionDevice import L2TPSessionDevice from lnst.Devices.RemoteDevice import RemoteDevice, remotedev_decorator
device_classes = [ @@ -32,7 +33,8 @@ device_classes = [ ("Vti6Device", Vti6Device), ("BondDevice", BondDevice), ("TeamDevice", TeamDevice), - ("MacsecDevice", MacsecDevice)] + ("MacsecDevice", MacsecDevice), + ("L2TPSessionDevice", L2TPSessionDevice)]
for name, cls in device_classes: globals()[name] = remotedev_decorator(cls)
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/device_classes/l2tpsessiondevice.rst | 7 +++++++ docs/source/devices.rst | 8 ++++++++ docs/source/l2tp_manager.rst | 5 +++++ docs/source/supported_devices.rst | 9 +++++++++ docs/source/tester_api.rst | 1 + 5 files changed, 30 insertions(+) create mode 100644 docs/source/device_classes/l2tpsessiondevice.rst create mode 100644 docs/source/devices.rst create mode 100644 docs/source/l2tp_manager.rst create mode 100644 docs/source/supported_devices.rst
diff --git a/docs/source/device_classes/l2tpsessiondevice.rst b/docs/source/device_classes/l2tpsessiondevice.rst new file mode 100644 index 00000000..d2b23e2a --- /dev/null +++ b/docs/source/device_classes/l2tpsessiondevice.rst @@ -0,0 +1,7 @@ +L2TPSessionDevice +================= + +.. autoclass:: lnst.Devices.L2TPSessionDevice.L2TPSessionDevice + :members: + :show-inheritance: + :private-members: _mandatory_opts diff --git a/docs/source/devices.rst b/docs/source/devices.rst new file mode 100644 index 00000000..f48f85d5 --- /dev/null +++ b/docs/source/devices.rst @@ -0,0 +1,8 @@ +Devices +^^^^^^^ + +.. toctree:: + :maxdepth: 2 + :caption: Devices + + device_classes/l2tpsessiondevice diff --git a/docs/source/l2tp_manager.rst b/docs/source/l2tp_manager.rst new file mode 100644 index 00000000..721d06e2 --- /dev/null +++ b/docs/source/l2tp_manager.rst @@ -0,0 +1,5 @@ +L2TPManager +^^^^^^^^^^^ + +.. autoclass:: lnst.RecipeCommon.L2TPManager.L2TPManager + :members: diff --git a/docs/source/supported_devices.rst b/docs/source/supported_devices.rst new file mode 100644 index 00000000..de272c9c --- /dev/null +++ b/docs/source/supported_devices.rst @@ -0,0 +1,9 @@ +Supported Devices +^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 3 + :caption: Supported devices + + devices + l2tp_manager diff --git a/docs/source/tester_api.rst b/docs/source/tester_api.rst index 7a21b0cf..78e22f49 100644 --- a/docs/source/tester_api.rst +++ b/docs/source/tester_api.rst @@ -8,5 +8,6 @@ Test developer API recipe_api controller_api parameters + supported_devices recipe_run_export implementing_new_device
pushed, thanks.
-Ondrej
On Mon, May 10, 2021 at 05:23:45PM +0200, Jan Tluka wrote:
This patch series implements L2TP support for the LNST recipes.
Due to the nature of the L2TP kernel subsystem the LNST L2TP API is split into two parts.
First one is the L2TPManager, which provides an API to create and delete the L2TP tunnels. The implementation is simply a wrapper fo the pyroute's L2tp API.
The second one is the L2TPSessionDevice class that represents a session within a previously configured L2TP tunnel. The class inherits from Device class and thus provides all of the API that is available for the other devices such as device link state operation, mtu setting, etc.
v2:
- L2TPManager.cleanup(): fixed list modification during iteration issue
Jan Tluka (4): Devices.SoftDevice: remove unused _init_generic attribute RecipeCommon: add L2TPManager Devices: add L2TPSessionDevice docs: add L2TP device API documentation
.../device_classes/l2tpsessiondevice.rst | 7 + docs/source/devices.rst | 8 + docs/source/l2tp_manager.rst | 5 + docs/source/supported_devices.rst | 9 + docs/source/tester_api.rst | 1 + lnst/Devices/L2TPSessionDevice.py | 118 +++++++++++++ lnst/Devices/SoftDevice.py | 3 - lnst/Devices/__init__.py | 4 +- lnst/RecipeCommon/L2TPManager.py | 156 ++++++++++++++++++ 9 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 docs/source/device_classes/l2tpsessiondevice.rst create mode 100644 docs/source/devices.rst create mode 100644 docs/source/l2tp_manager.rst create mode 100644 docs/source/supported_devices.rst create mode 100644 lnst/Devices/L2TPSessionDevice.py create mode 100644 lnst/RecipeCommon/L2TPManager.py
-- 2.26.3 _______________________________________________ LNST-developers mailing list -- lnst-developers@lists.fedorahosted.org To unsubscribe send an email to lnst-developers-leave@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/lnst-developers@lists.fedorahos... Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure
lnst-developers@lists.fedorahosted.org