From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id CD6094331D; Mon, 13 Nov 2023 18:57:04 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id BC27540A7D; Mon, 13 Nov 2023 18:57:04 +0100 (CET) Received: from mail-oi1-f171.google.com (mail-oi1-f171.google.com [209.85.167.171]) by mails.dpdk.org (Postfix) with ESMTP id 167B84026F for ; Mon, 13 Nov 2023 18:56:43 +0100 (CET) Received: by mail-oi1-f171.google.com with SMTP id 5614622812f47-3b2df2fb611so3229728b6e.0 for ; Mon, 13 Nov 2023 09:56:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1699898202; x=1700503002; darn=dpdk.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=7buxLzZMnfa5ljzadfYtnD443Dnam0hI4iTJ5F0rCoM=; b=GM6Kav7pH5T5/YBkyyu7XDWWohQJ7mI9aSF6eYkpgf8lvjn/okUpzFQsfJOlrEC4Uc nmv3tK4PMcgSXPSs5sshf7AvF3L/LdR4vYkrKB55DVvCGYSOX1ET045Hty5u0rzzMXMC MjBhNAuTG+g+TNyr+UaJnfxRjLzeNq4d4JOt0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1699898202; x=1700503002; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=7buxLzZMnfa5ljzadfYtnD443Dnam0hI4iTJ5F0rCoM=; b=cfyLVg6PgyamAsG6SaySxpvycZVoMq12xGSK0vvx7erfixbKHO/3ki3ZeWAcfPPDsx krmi0X4Np5sD7ybh1thRoMvy/VKNRUDpfYkAyM1oWH/HW9UmUaGMNUDmuRLXPXliQn6h NmOy6ouq5ByRuUSP3ejzizJV9KmW1PjRjnrGPy4ewLg1ogr0Xsjaap0fTEObg7aO7nbe cb97ZIJ65R0PwLRuT0+xROIrHq9w0r8vQ6XmbPp9tRVbQyPEriWVssBddiRCfrQN+EPf Ah4pdAXh6KlV3toM3JzHA4wBpkdVBDyWNTTQ0vJ3WdWdfJyiB7neRePXSkCnt7vPPeZI Nr6w== X-Gm-Message-State: AOJu0YyKMlYCC2zTY9jrHTqNRVp1KIQjs+GYrkY+jWWWJ5EusdvJx814 96t4+iW1ewUSXH+701EIhOJgbFJK+74KM4ZVbaWfbg== X-Google-Smtp-Source: AGHT+IH91sZlpTxMM6C9HTFBfm19ITSV4l4tpHXVj3W87oKLen4rJPTELAZ1iDIXc8PsUVttqLuuiCjTMYA2WuST4g8= X-Received: by 2002:a05:6808:1798:b0:3a4:8251:5f43 with SMTP id bg24-20020a056808179800b003a482515f43mr10921156oib.40.1699898202353; Mon, 13 Nov 2023 09:56:42 -0800 (PST) MIME-Version: 1.0 References: <20231109231707.25400-1-jspewock@iol.unh.edu> <20231109231707.25400-2-jspewock@iol.unh.edu> In-Reply-To: <20231109231707.25400-2-jspewock@iol.unh.edu> From: Patrick Robb Date: Mon, 13 Nov 2023 12:56:31 -0500 Message-ID: Subject: Re: [PATCH v3 1/1] dts: bind to DPDK driver before running test suites To: jspewock@iol.unh.edu Cc: Honnappa.Nagarahalli@arm.com, juraj.linkes@pantheon.tech, thomas@monjalon.net, wathsala.vithanage@arm.com, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com, dev@dpdk.org Content-Type: multipart/alternative; boundary="000000000000818584060a0c64f4" X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org --000000000000818584060a0c64f4 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Thu, Nov 9, 2023 at 6:17=E2=80=AFPM wrote: > From: Jeremy Spewock > > Modifies the current process so that we bind to os_driver_for_dpdk from > the configuration file before running test suites and bind back to the > os_driver afterwards. This allows test suites to assume that the ports > are bound to a DPDK supported driver or bind to either driver as needed. > > Signed-off-by: Jeremy Spewock > --- > dts/framework/testbed_model/sut_node.py | 33 +++++++++++++++++++++++++ > dts/tests/TestSuite_os_udp.py | 4 +++ > dts/tests/TestSuite_smoke_tests.py | 6 ++--- > 3 files changed, 39 insertions(+), 4 deletions(-) > > diff --git a/dts/framework/testbed_model/sut_node.py > b/dts/framework/testbed_model/sut_node.py > index 202aebfd06..4161d3a4d5 100644 > --- a/dts/framework/testbed_model/sut_node.py > +++ b/dts/framework/testbed_model/sut_node.py > @@ -89,6 +89,7 @@ class SutNode(Node): > _dpdk_version: str | None > _node_info: NodeInfo | None > _compiler_version: str | None > + _path_to_devbind_script: PurePath | None > > def __init__(self, node_config: SutNodeConfiguration): > super(SutNode, self).__init__(node_config) > @@ -105,6 +106,7 @@ def __init__(self, node_config: SutNodeConfiguration)= : > self._dpdk_version =3D None > self._node_info =3D None > self._compiler_version =3D None > + self._path_to_devbind_script =3D None > self._logger.info(f"Created node: {self.name}") > > @property > @@ -155,6 +157,14 @@ def compiler_version(self) -> str: > return "" > return self._compiler_version > > + @property > + def path_to_devbind_script(self) -> PurePath: > + if self._path_to_devbind_script is None: > + self._path_to_devbind_script =3D > self.main_session.join_remote_path( > + self._remote_dpdk_dir, "usertools", "dpdk-devbind.py" > + ) > + return self._path_to_devbind_script > + > def get_build_target_info(self) -> BuildTargetInfo: > return BuildTargetInfo( > dpdk_version=3Dself.dpdk_version, > compiler_version=3Dself.compiler_version > @@ -176,6 +186,14 @@ def _set_up_build_target( > self._configure_build_target(build_target_config) > self._copy_dpdk_tarball() > self._build_dpdk() > + self.bind_ports_to_driver() > + > + def _tear_down_build_target(self) -> None: > + """ > + This method exists to be optionally overwritten by derived > classes and > + is not decorated so that the derived class doesn't have to use > the decorator. > + """ > + self.bind_ports_to_driver(for_dpdk=3DFalse) > > def _configure_build_target( > self, build_target_config: BuildTargetConfiguration > @@ -389,3 +407,18 @@ def create_interactive_shell( > return super().create_interactive_shell( > shell_cls, timeout, privileged, str(eal_parameters) > ) > + > + def bind_ports_to_driver(self, for_dpdk: bool =3D True) -> None: > + """Bind all ports on the SUT to a driver. > + > + Args: > + for_dpdk: Boolean that, when True, binds ports to > os_driver_for_dpdk > + or, when False, binds to os_driver. Defaults to True. > + """ > + for port in self.ports: > + driver =3D port.os_driver_for_dpdk if for_dpdk else > port.os_driver > + self.main_session.send_command( > + f"{self.path_to_devbind_script} -b {driver} --force > {port.pci}", > + privileged=3DTrue, > + verify=3DTrue, > + ) > This all looks consistent with the understanding we came to during the CI meeting and on the v2 discussion thread. > diff --git a/dts/tests/TestSuite_os_udp.py b/dts/tests/TestSuite_os_udp.p= y > index 9b5f39711d..bf6b93deb5 100644 > --- a/dts/tests/TestSuite_os_udp.py > +++ b/dts/tests/TestSuite_os_udp.py > @@ -19,6 +19,8 @@ def set_up_suite(self) -> None: > Configure SUT ports and SUT to route traffic from if1 to if2= . > """ > > + # This test uses kernel drivers > + self.sut_node.bind_ports_to_driver(for_dpdk=3DFalse) > self.configure_testbed_ipv4() > > def test_os_udp(self) -> None: > @@ -43,3 +45,5 @@ def tear_down_suite(self) -> None: > Remove the SUT port configuration configured in setup. > """ > self.configure_testbed_ipv4(restore=3DTrue) > + # Assume other suites will likely need dpdk driver > + self.sut_node.bind_ports_to_driver(for_dpdk=3DTrue) > Thanks for refactoring this to make it survive the binding approach change. We'll still want to revisit this for 24.03, as Juraj mentioned that it's really just to demonstrate the TG is performant, and may not be needed in the future. > diff --git a/dts/tests/TestSuite_smoke_tests.py > b/dts/tests/TestSuite_smoke_tests.py > index 4a269df75b..e8016d1b54 100644 > --- a/dts/tests/TestSuite_smoke_tests.py > +++ b/dts/tests/TestSuite_smoke_tests.py > @@ -84,9 +84,7 @@ def test_device_bound_to_driver(self) -> None: > Ensure that all drivers listed in the config are bound to th= e > correct > driver. > """ > - path_to_devbind =3D self.sut_node.main_session.join_remote_path( > - self.sut_node._remote_dpdk_dir, "usertools", "dpdk-devbind.p= y" > - ) > + path_to_devbind =3D self.sut_node.path_to_devbind_script > > all_nics_in_dpdk_devbind =3D > self.sut_node.main_session.send_command( > f"{path_to_devbind} --status | awk '{REGEX_FOR_PCI_ADDRESS}'= ", > @@ -108,7 +106,7 @@ def test_device_bound_to_driver(self) -> None: > # We know this isn't None, but mypy doesn't > assert devbind_info_for_nic is not None > self.verify( > - devbind_info_for_nic.group(1) =3D=3D nic.os_driver, > + devbind_info_for_nic.group(1) =3D=3D nic.os_driver_for_d= pdk, > f"Driver for device {nic.pci} does not match driver > listed in " > f"configuration (bound to > {devbind_info_for_nic.group(1)})", > ) > -- > 2.42.0 > > We discussed this aspect of binding during last week's CI meeting and I understood Juraj to be consenting to returning to DTS running the binding to the dpdk driver (so, what you're doing here), as opposed to relying on the user to do it, and making it a smoke test. As we've discussed, that's how the old DTS framework ran, and I prefer to stick to this approach. One aspect I raised was how in a lab context it's desirable for us to define as much as possible within config files, and have environmental configuration be handled by DTS. So, since there was basically agreement here, I think your changes here are appropriate. Acked-by: Patrick Robb --000000000000818584060a0c64f4 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable

On Thu, Nov 9, 2023 at 6:17=E2=80=AFPM &l= t;jspewock@iol.unh.edu> wrot= e:
From: Jeremy = Spewock <jspew= ock@iol.unh.edu>

Modifies the current process so that we bind to os_driver_for_dpdk from
the configuration file before running test suites and bind back to the
os_driver afterwards. This allows test suites to assume that the ports
are bound to a DPDK supported driver or bind to either driver as needed.
Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
---
=C2=A0dts/framework/testbed_model/sut_node.py | 33 ++++++++++++++++++++++++= +
=C2=A0dts/tests/TestSuite_os_udp.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0|=C2=A0 4 +++
=C2=A0dts/tests/TestSuite_smoke_tests.py=C2=A0 =C2=A0 =C2=A0 |=C2=A0 6 ++--= -
=C2=A03 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbe= d_model/sut_node.py
index 202aebfd06..4161d3a4d5 100644
--- a/dts/framework/testbed_model/sut_node.py
+++ b/dts/framework/testbed_model/sut_node.py
@@ -89,6 +89,7 @@ class SutNode(Node):
=C2=A0 =C2=A0 =C2=A0_dpdk_version: str | None
=C2=A0 =C2=A0 =C2=A0_node_info: NodeInfo | None
=C2=A0 =C2=A0 =C2=A0_compiler_version: str | None
+=C2=A0 =C2=A0 _path_to_devbind_script: PurePath | None

=C2=A0 =C2=A0 =C2=A0def __init__(self, node_config: SutNodeConfiguration):<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super(SutNode, self).__init__(node_config= )
@@ -105,6 +106,7 @@ def __init__(self, node_config: SutNodeConfiguration):<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._dpdk_version =3D None
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._node_info =3D None
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._compiler_version =3D None
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._path_to_devbind_script =3D None
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._logger.info(f"Created node: {self.name}")

=C2=A0 =C2=A0 =C2=A0@property
@@ -155,6 +157,14 @@ def compiler_version(self) -> str:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return "= "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return self._compiler_version

+=C2=A0 =C2=A0 @property
+=C2=A0 =C2=A0 def path_to_devbind_script(self) -> PurePath:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if self._path_to_devbind_script is None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._path_to_devbind_script =3D= self.main_session.join_remote_path(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._remote_dpdk_= dir, "usertools", "dpdk-devbind.py"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self._path_to_devbind_script
+
=C2=A0 =C2=A0 =C2=A0def get_build_target_info(self) -> BuildTargetInfo:<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return BuildTargetInfo(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0dpdk_version=3Dself.dpdk_ve= rsion, compiler_version=3Dself.compiler_version
@@ -176,6 +186,14 @@ def _set_up_build_target(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._configure_build_target(build_target= _config)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._copy_dpdk_tarball()
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._build_dpdk()
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.bind_ports_to_driver()
+
+=C2=A0 =C2=A0 def _tear_down_build_target(self) -> None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 This method exists to be optionally overwritte= n by derived classes and
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 is not decorated so that the derived class doe= sn't have to use the decorator.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.bind_ports_to_driver(for_dpdk=3DFalse)
=C2=A0 =C2=A0 =C2=A0def _configure_build_target(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self, build_target_config: BuildTargetCon= figuration
@@ -389,3 +407,18 @@ def create_interactive_shell(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return super().create_interactive_shell(<= br> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0shell_cls, timeout, privile= ged, str(eal_parameters)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
+
+=C2=A0 =C2=A0 def bind_ports_to_driver(self, for_dpdk: bool =3D True) ->= ; None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Bind all ports on the SUT to= a driver.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Args:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 for_dpdk: Boolean that, when Tru= e, binds ports to os_driver_for_dpdk
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 or, when False, binds to os_driv= er. Defaults to True.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 for port in self.ports:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 driver =3D port.os_driver_for_dp= dk if for_dpdk else port.os_driver
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self.main_session.send_command(<= br> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"{self.path_= to_devbind_script} -b {driver} --force {port.pci}",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 privileged=3DTrue,=
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 verify=3DTrue,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )

This all looks consistent with the understanding we came to during th= e CI meeting and on the v2 discussion thread.=C2=A0
=C2=A0
<= blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-l= eft:1px solid rgb(204,204,204);padding-left:1ex"> diff --git a/dts/tests/TestSuite_os_udp.py b/dts/tests/TestSuite_os_udp.py<= br> index 9b5f39711d..bf6b93deb5 100644
--- a/dts/tests/TestSuite_os_udp.py
+++ b/dts/tests/TestSuite_os_udp.py
@@ -19,6 +19,8 @@ def set_up_suite(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Configure SUT ports and SUT= to route traffic from if1 to if2.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""

+=C2=A0 =C2=A0 =C2=A0 =C2=A0 # This test uses kernel drivers
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.sut_node.bind_ports_to_driver(for_dpdk=3D= False)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.configure_testbed_ipv4()

=C2=A0 =C2=A0 =C2=A0def test_os_udp(self) -> None:
@@ -43,3 +45,5 @@ def tear_down_suite(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Remove the SUT port configu= ration configured in setup.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.configure_testbed_ipv4(restore=3DTru= e)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 # Assume other suites will likely need dpdk dr= iver
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.sut_node.bind_ports_to_driver(for_dpdk=3D= True)
=C2=A0
Thanks for refactoring this to = make it survive the binding approach change. We'll still want to revisi= t this for 24.03, as Juraj mentioned that it's really just to demonstra= te the TG is performant, and may not be needed in the future.=C2=A0
=C2=A0
diff --git a/dts/tests/TestSuite_smoke_tests.py b/dts/tests/TestSuite_smoke= _tests.py
index 4a269df75b..e8016d1b54 100644
--- a/dts/tests/TestSuite_smoke_tests.py
+++ b/dts/tests/TestSuite_smoke_tests.py
@@ -84,9 +84,7 @@ def test_device_bound_to_driver(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Ensure that all drivers lis= ted in the config are bound to the correct
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0driver.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 path_to_devbind =3D self.sut_node.main_session= .join_remote_path(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self.sut_node._remote_dpdk_dir, = "usertools", "dpdk-devbind.py"
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 path_to_devbind =3D self.sut_node.path_to_devb= ind_script

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0all_nics_in_dpdk_devbind =3D self.sut_nod= e.main_session.send_command(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0f"{path_to_devbind} --= status | awk '{REGEX_FOR_PCI_ADDRESS}'",
@@ -108,7 +106,7 @@ def test_device_bound_to_driver(self) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# We know this isn't No= ne, but mypy doesn't
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0assert devbind_info_for_nic= is not None
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.verify(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 devbind_info_for_n= ic.group(1) =3D=3D nic.os_driver,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 devbind_info_for_n= ic.group(1) =3D=3D nic.os_driver_for_dpdk,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0f"Driver= for device {nic.pci} does not match driver listed in "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0f"config= uration (bound to {devbind_info_for_nic.group(1)})",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
--
2.42.0



--000000000000818584060a0c64f4--