From f98d88b2624ada0eceaedf16dd7f80f754487b08 Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Tue, 5 Oct 2021 20:35:34 -0700 Subject: [PATCH 1/3] Allow additional dpdk runtime libraries to be installed Only some dpdk runtime libraries are installed by default, but some environments may need additional libraries installed. This adds an option to allow the user to specify additional dpdk runtime libraries via a config option `dpdk-runtime-libraries`. Closes-Bug: #1936850 --- config.yaml | 15 +++++++++++++++ lib/charms/ovn_charm.py | 16 ++++++++++++++++ unit_tests/test_lib_charms_ovn_charm.py | 22 ++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/config.yaml b/config.yaml index 6684b1b..8a64074 100644 --- a/config.yaml +++ b/config.yaml @@ -124,6 +124,21 @@ options: uio_pci_generic . Only used when DPDK is enabled. + dpdk-runtime-libraries: + type: string + default: + description: | + Space delimited list of additional DPDK runtime libraries that should + be installed when DPDK is enabled. + . + By default, only the runtime libraries that are recommended with the + dpdk libraries are installed. Environments that need additional libraries + installed should include those library packages. For example, to enable + the pmd-bnx2x runtime library specify the name of the software package + `librte-pmd-bnx2x20.0`. To enable the pmd-bnx2x and the pmd-ice runtime + libraries, specify the value as `librte-pmd-bnx2x20.0 librte-pmd-ice20.0`. + . + Only used when DPDK is enabled. enable-hardware-offload: type: boolean default: false diff --git a/lib/charms/ovn_charm.py b/lib/charms/ovn_charm.py index b24cb68..0eb7c67 100644 --- a/lib/charms/ovn_charm.py +++ b/lib/charms/ovn_charm.py @@ -300,6 +300,21 @@ def enable_openstack(self): """ return reactive.is_flag_set('charm.ovn-chassis.enable-openstack') + @property + def additional_dpdk_libraries(self): + """A list of additional runtime libraries to be installed for dpdk. + + :returns: list of additional packages to install + :rtype: List[str] + """ + if self.options.enable_dpdk and self.options.dpdk_runtime_libraries: + # dpdk_runtime_libraries is a space delimited list of strings. + # some options are disabled by passing 'None' so filter out a + # specifying of a 'None' value + return list(filter(lambda x: x and x.lower() != 'none', + self.options.dpdk_runtime_libraries.split())) + return [] + @property def packages(self): """Full list of packages to be installed. @@ -310,6 +325,7 @@ def packages(self): _packages = ['ovn-host'] if self.options.enable_dpdk: _packages.extend(['openvswitch-switch-dpdk']) + _packages.extend(self.additional_dpdk_libraries) if self.options.enable_hardware_offload or self.options.enable_sriov: # The ``sriov-netplan-shim`` package does boot-time # configuration of Virtual Functions (VFs) in the system. diff --git a/unit_tests/test_lib_charms_ovn_charm.py b/unit_tests/test_lib_charms_ovn_charm.py index 714d08d..d704687 100644 --- a/unit_tests/test_lib_charms_ovn_charm.py +++ b/unit_tests/test_lib_charms_ovn_charm.py @@ -442,6 +442,27 @@ def test_optional_openstack_metadata_wallaby(self): 'neutron-ovn-metadata-agent']) +class TestDPDKOVNChassisCharmExtraLibs(Helper): + + def setUp(self): + super().setUp(config={ + 'enable-hardware-offload': False, + 'enable-sriov': False, + 'enable-dpdk': True, + 'dpdk-bond-mappings': ('dpdk-bond0:a0:36:9f:dd:37:a4 ' + 'dpdk-bond0:a0:36:9f:dd:3e:9c'), + 'bridge-interface-mappings': 'br-ex:eth0 br-data:dpdk-bond0', + 'ovn-bridge-mappings': ( + 'provider:br-ex other:br-data'), + 'prefer-chassis-as-gw': False, + 'dpdk-runtime-libraries': 'librte-pmd-hinic20.0 None', + }) + + def test__init__(self): + self.assertEquals(self.target.packages, [ + 'ovn-host', 'openvswitch-switch-dpdk', 'librte-pmd-hinic20.0']) + + class TestDPDKOVNChassisCharm(Helper): def setUp(self): @@ -455,6 +476,7 @@ def setUp(self): 'ovn-bridge-mappings': ( 'provider:br-ex other:br-data'), 'prefer-chassis-as-gw': False, + 'dpdk-runtime-libraries': '', }) def test__init__(self): From b2fbde22f7f6e2ac8f691d9567da84f56869831c Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Thu, 7 Oct 2021 12:57:59 -0700 Subject: [PATCH 2/3] Allow for simple package naming for librte libraries Allow for users to specify logical names for the dpdk libraries rather than the full library name. This is done by searching the apt-cache for all libraries which match the `librte-**` search string and selecting those packages. Users can still specify a full package name to slect just that package. Signed-off-by: Billy Olsen --- config.yaml | 9 ++- lib/charms/ovn_charm.py | 38 ++++++++- unit_tests/test_lib_charms_ovn_charm.py | 103 ++++++++++++++++++++++-- 3 files changed, 138 insertions(+), 12 deletions(-) diff --git a/config.yaml b/config.yaml index 8a64074..81702a3 100644 --- a/config.yaml +++ b/config.yaml @@ -133,10 +133,11 @@ options: . By default, only the runtime libraries that are recommended with the dpdk libraries are installed. Environments that need additional libraries - installed should include those library packages. For example, to enable - the pmd-bnx2x runtime library specify the name of the software package - `librte-pmd-bnx2x20.0`. To enable the pmd-bnx2x and the pmd-ice runtime - libraries, specify the value as `librte-pmd-bnx2x20.0 librte-pmd-ice20.0`. + installed should include those library packages. The runtime libraries can + be defined either by the full package name (e.g. librte-pmd-bnx2x20.0) + or by the simple name (e.g. bnx2x). When providing the simple name, a + search is done of the apt-cache for a name matching `librte-**` + for installation and will install all matching packages that are found. . Only used when DPDK is enabled. enable-hardware-offload: diff --git a/lib/charms/ovn_charm.py b/lib/charms/ovn_charm.py index 0eb7c67..b9d03d6 100644 --- a/lib/charms/ovn_charm.py +++ b/lib/charms/ovn_charm.py @@ -14,6 +14,7 @@ import collections import ipaddress import os +import re import subprocess import charms.reactive as reactive @@ -307,13 +308,45 @@ def additional_dpdk_libraries(self): :returns: list of additional packages to install :rtype: List[str] """ + packages = [] if self.options.enable_dpdk and self.options.dpdk_runtime_libraries: # dpdk_runtime_libraries is a space delimited list of strings. # some options are disabled by passing 'None' so filter out a # specifying of a 'None' value - return list(filter(lambda x: x and x.lower() != 'none', + pkgs = list(filter(lambda x: x and x.lower() != 'none', self.options.dpdk_runtime_libraries.split())) - return [] + # Attempt to be nice and resolve the package names the user has + # provided in order to allow for users to specify 'hinic' etc for + # packages. To do this, we will search for all packages using the + # format of `librte-** and return all matching packages. + for name in pkgs: + regex = re.compile(r'(^\S.*)', re.MULTILINE) + if name.lower().startswith('librte'): + packages.append(name) + continue + + cp = self.run('apt-cache', 'policy', + 'librte-*{}*'.format(name)) + # The apt-cache search does not return an error code if the + # package could not be found and the return code is 0. The + # stdout will be empty in this case and the regex won't + # produce a match. Log a warning message and use the provided + # package name anyways. This may cause a failure to install, + # but the user should have an idea of why. + results = re.findall(regex, cp.stdout) + if not results: + ch_core.hookenv.log(('Unable to find candidate librte ' + 'package for {}. Using raw name ' + 'provided.').format(name), + ch_core.hookenv.WARN) + packages.append(name) + continue + + # The regex doesn't remove the trailing ':' so strip it out + # before adding it to the list of packages + packages.extend([p[:-1] for p in results]) + + return packages @property def packages(self): @@ -564,6 +597,7 @@ def run(self, *args): args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True, universal_newlines=True) ch_core.hookenv.log(cp, level=ch_core.hookenv.INFO) + return cp def get_certificate_requests(self): """Override default certificate request handler. diff --git a/unit_tests/test_lib_charms_ovn_charm.py b/unit_tests/test_lib_charms_ovn_charm.py index d704687..451ef4d 100644 --- a/unit_tests/test_lib_charms_ovn_charm.py +++ b/unit_tests/test_lib_charms_ovn_charm.py @@ -14,6 +14,7 @@ import collections import io +import textwrap import unittest.mock as mock import charms_openstack.charm.core as chm_core @@ -445,7 +446,7 @@ def test_optional_openstack_metadata_wallaby(self): class TestDPDKOVNChassisCharmExtraLibs(Helper): def setUp(self): - super().setUp(config={ + self.local_config = { 'enable-hardware-offload': False, 'enable-sriov': False, 'enable-dpdk': True, @@ -455,12 +456,102 @@ def setUp(self): 'ovn-bridge-mappings': ( 'provider:br-ex other:br-data'), 'prefer-chassis-as-gw': False, - 'dpdk-runtime-libraries': 'librte-pmd-hinic20.0 None', - }) + 'dpdk-runtime-libraries': '', + } + super().setUp(config=self.local_config) + + self.run = mock.Mock() + self.patch('charms.ovn_charm.BaseOVNChassisCharm.run', + new_callable=self.run) + self.run.start() + self.called_process = self.run.return_value + self.called_process.returncode = 0 + + def test_single_match(self): + apt_cache_output = textwrap.dedent( + """ + librte-net-hinic21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/universe amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/universe amd64 Packages + """ # noqa + ) + self.called_process.stdout = apt_cache_output + + self.local_config['dpdk-runtime-libraries'] = 'hinic' + target = ovn_charm.BaseUssuriOVNChassisCharm() + self.assertEquals(target.additional_dpdk_libraries, [ + 'librte-net-hinic21']) + self.assertEquals(target.packages, [ + 'ovn-host', 'openvswitch-switch-dpdk', 'librte-net-hinic21']) + + def test_multiple_matches(self): + apt_cache_output = textwrap.dedent( + """ + librte-net-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/main amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/main amd64 Packages + librte-regex-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/universe amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/universe amd64 Packages + librte-common-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/main amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/main amd64 Packages + librte-vdpa-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/universe amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/universe amd64 Packages + """ # noqa + ) + self.called_process.stdout = apt_cache_output + + self.local_config['dpdk-runtime-libraries'] = 'mlx5' + target = ovn_charm.BaseUssuriOVNChassisCharm() + self.assertEquals(target.additional_dpdk_libraries, [ + 'librte-net-mlx5-21', 'librte-regex-mlx5-21', + 'librte-common-mlx5-21', 'librte-vdpa-mlx5-21']) + + def test_specific_package(self): + self.local_config['dpdk-runtime-libraries'] = 'librte-net-mlx5-21' + target = ovn_charm.BaseUssuriOVNChassisCharm() + self.assertEquals(target.additional_dpdk_libraries, [ + 'librte-net-mlx5-21']) + self.run.assert_not_called() - def test__init__(self): - self.assertEquals(self.target.packages, [ - 'ovn-host', 'openvswitch-switch-dpdk', 'librte-pmd-hinic20.0']) + def test_package_not_found(self): + # Missing packages don't have output via this command + self.called_process.stdout = '' + self.local_config['dpdk-runtime-libraries'] = 'missing' + target = ovn_charm.BaseUssuriOVNChassisCharm() + self.assertEquals(target.additional_dpdk_libraries, ['missing']) class TestDPDKOVNChassisCharm(Helper): From 645e4aed5aeb9c1d53a721cbbb068af042af89c6 Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Wed, 9 Feb 2022 16:34:41 -0700 Subject: [PATCH 3/3] Add additionally requested unit tests Add unit tests to cover the case of input being none and having multiple package searches. Signed-off-by: Billy Olsen --- unit_tests/test_lib_charms_ovn_charm.py | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/unit_tests/test_lib_charms_ovn_charm.py b/unit_tests/test_lib_charms_ovn_charm.py index 451ef4d..6698ed1 100644 --- a/unit_tests/test_lib_charms_ovn_charm.py +++ b/unit_tests/test_lib_charms_ovn_charm.py @@ -546,6 +546,77 @@ def test_specific_package(self): 'librte-net-mlx5-21']) self.run.assert_not_called() + def test_none_package(self): + self.local_config['dpdk-runtime-libraries'] = 'None' + target = ovn_charm.BaseUssuriOVNChassisCharm() + self.assertEquals(target.additional_dpdk_libraries, []) + self.run.assert_not_called() + + def test_multiple_packages(self): + process1 = mock.Mock() + process1.stdout = textwrap.dedent( + """ + librte-net-hinic21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/universe amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/universe amd64 Packages + """ # noqa + ) + process2 = mock.Mock() + process2.stdout = textwrap.dedent( + """ + librte-net-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/main amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/main amd64 Packages + librte-regex-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/universe amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/universe amd64 Packages + librte-common-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/main amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/main amd64 Packages + librte-vdpa-mlx5-21: + Installed: 20.11.3-0ubuntu0.21.04.2 + Candidate: 20.11.3-0ubuntu0.21.04.2 + Version table: + *** 20.11.3-0ubuntu0.21.04.2 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute-updates/universe amd64 Packages + 100 /var/lib/dpkg/status + 20.11.1-1 500 + 500 http://us.archive.ubuntu.com/ubuntu hirsute/universe amd64 Packages + """ # noqa + ) + + self.run.side_effect = [process1, process2] + + self.local_config['dpdk-runtime-libraries'] = 'hinic mlx' + target = ovn_charm.BaseUssuriOVNChassisCharm() + self.assertEquals(target.additional_dpdk_libraries, [ + 'librte-net-hinic21', 'librte-net-mlx5-21', 'librte-regex-mlx5-21', + 'librte-common-mlx5-21', 'librte-vdpa-mlx5-21']) + def test_package_not_found(self): # Missing packages don't have output via this command self.called_process.stdout = ''