Merge ~danilogondolfo/netplan/+git/ubuntu:jammy_sriov_sru into ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu-jammy

Proposed by Danilo Egea Gondolfo
Status: Merged
Merged at revision: 7a7e46e7963eaeffcbd33f0959754fe553cae14e
Proposed branch: ~danilogondolfo/netplan/+git/ubuntu:jammy_sriov_sru
Merge into: ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu-jammy
Diff against target: 2460 lines (+2373/-0)
13 files modified
debian/changelog (+12/-0)
debian/libnetplan0.symbols (+1/-0)
debian/patches/lp1988018/0018-libnetplan-add-a-getter-for-bond-mode.patch (+115/-0)
debian/patches/lp1988018/0019-sriov-move-the-udev-logic-to-a-service-unit.patch (+163/-0)
debian/patches/lp1988018/0020-sriov-check-the-eswitch-mode-before-trying-to-change.patch (+100/-0)
debian/patches/lp1988018/0021-sriov_rebind-cooperate-with-VF-LAG-activation.patch (+172/-0)
debian/patches/lp1988018/0022-sriov_rebind-netplan-rebind-debug-setup.patch (+113/-0)
debian/patches/lp1988018/0023-tests-sriov-adapt-tests-to-the-last-sr-iov-related-c.patch (+554/-0)
debian/patches/lp1988018/0024-sriov_apply-execute-apply-sriov-only-before-network-.patch (+83/-0)
debian/patches/lp2020409/0025-sriov-accept-setting-the-eswitch-mode-without-VFs.patch (+148/-0)
debian/patches/lp2020409/0026-cli-sriov-refactoring.patch (+768/-0)
debian/patches/lp2020409/0027-cli-sriov-set-eswitch-regardless-of-pcidev.vfs.patch (+132/-0)
debian/patches/series (+12/-0)
Reviewer Review Type Date Requested Status
Lukas Märdian Approve
Ubuntu Core Development Team Pending
Review via email: mp+474659@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Danilo Egea Gondolfo (danilogondolfo) :
Revision history for this message
Danilo Egea Gondolfo (danilogondolfo) :
Revision history for this message
Lukas Märdian (slyon) wrote :

This is a preliminary review, as I didn't have time to review all the individual patches, yet. But I left a few inline comments already.

PR#454 is only included in Netplan v1.1 (in Oracular, but not in Noble, yet). So this needs a clear explanation on the SRU bug report. We usually want to ship the features/fixes top-down, so that people upgrading from older releases won't regress. This needs extra paper-work (SRU template) for https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/2020409

How do we avoid issues with phased updates this time around? https://ubuntu-archive-team.ubuntu.com/phased-updates.html

Revision history for this message
Danilo Egea Gondolfo (danilogondolfo) wrote :
Revision history for this message
Danilo Egea Gondolfo (danilogondolfo) wrote :

I split the SRU paperwork. Each bug has its own description and test cases:

https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018

https://bugs.launchpad.net/netplan/+bug/2020409

Revision history for this message
Lukas Märdian (slyon) wrote :

- PR#454: Ah that's good. Indeed, it is already in 1.0.1/Noble. Sorry, I didn't spot this.

- lp1988018: Patches match upstream with unexpectedly little fuzz and any differences are sensible and explained. Great!

- lp2020409: Patches match upstream with unexpectedly little fuzz.

Overall, this is looking pretty good (besides the .symbols file version). Tests look fine, too, with only expected failures. Thanks for providing DEP-3 headers, providing test builds and autopkgtest logs!

We should double-check the phased-updates situation to see if we can do anything about it in this update.

Revision history for this message
Lukas Märdian (slyon) :
Revision history for this message
Lukas Märdian (slyon) wrote :

thanks for the .symbol version string fixes.

See https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/2033259/comments/12 for my phased-updates suggestion.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 3249112..067a2ea 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,15 @@
6+netplan.io (0.107.1-3ubuntu0.22.04.2) jammy; urgency=medium
7+
8+ * debian/patches/lp1988018: VF-LAG activation
9+ Fixes the order in which SR-IOV configuration is performed and
10+ cooperates with VF-LAG activation (LP: #1988018).
11+ * debian/patches/lp2020409:
12+ Enables setting the embedded-switch mode without having to define
13+ virtual functions (LP: #2020409).
14+ * debian/libnetplan0.symbols: New symbol _netplan_netdef_get_bond_mode.
15+
16+ -- Danilo Egea Gondolfo <danilo.egea.gondolfo@canonical.com> Mon, 07 Oct 2024 10:57:38 +0100
17+
18 netplan.io (0.107.1-3ubuntu0.22.04.1) jammy; urgency=medium
19
20 * Backport netplan.io 0.107.1-3 to 22.04 (LP: #2058031):
21diff --git a/debian/libnetplan0.symbols b/debian/libnetplan0.symbols
22index 908927f..db07ea6 100644
23--- a/debian/libnetplan0.symbols
24+++ b/debian/libnetplan0.symbols
25@@ -9,6 +9,7 @@ libnetplan.so.0.0 libnetplan0 #MINVER#
26 _netplan_iter_defs_per_devtype_next@Base 0.104
27 _netplan_nameserver_iter_free@Base 0.107
28 _netplan_nameserver_iter_next@Base 0.107
29+ _netplan_netdef_get_bond_mode@Base 0.107.1-3ubuntu0.22.04.2~
30 _netplan_netdef_get_critical@Base 0.105
31 _netplan_netdef_get_delay_vf_rebind@Base 0.106
32 _netplan_netdef_get_embedded_switch_mode@Base 0.106
33diff --git a/debian/patches/lp1988018/0018-libnetplan-add-a-getter-for-bond-mode.patch b/debian/patches/lp1988018/0018-libnetplan-add-a-getter-for-bond-mode.patch
34new file mode 100644
35index 0000000..d891729
36--- /dev/null
37+++ b/debian/patches/lp1988018/0018-libnetplan-add-a-getter-for-bond-mode.patch
38@@ -0,0 +1,115 @@
39+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
40+Date: Wed, 7 Feb 2024 10:53:13 +0000
41+Subject: libnetplan: add a getter for bond mode
42+
43+Add a new internal function to expose the bond mode from
44+netdef.bond_params.mode and a Python binding for it.
45+
46+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
47+Origin: https://github.com/canonical/netplan/pull/439
48+---
49+ python-cffi/netplan/_build_cffi.py | 1 +
50+ python-cffi/netplan/netdef.py | 4 ++++
51+ src/types.c | 11 +++++++++++
52+ src/util-internal.h | 3 +++
53+ tests/test_libnetplan.py | 30 ++++++++++++++++++++++++++++++
54+ 5 files changed, 49 insertions(+)
55+
56+diff --git a/python-cffi/netplan/_build_cffi.py b/python-cffi/netplan/_build_cffi.py
57+index ba0af4e..f9a18b7 100644
58+--- a/python-cffi/netplan/_build_cffi.py
59++++ b/python-cffi/netplan/_build_cffi.py
60+@@ -116,6 +116,7 @@ ffibuilder.cdef("""
61+ gboolean _netplan_netdef_is_trivial_compound_itf(const NetplanNetDefinition* netdef);
62+ int _netplan_state_get_vf_count_for_def(
63+ const NetplanState* np_state, const NetplanNetDefinition* netdef, NetplanError** error);
64++ ssize_t _netplan_netdef_get_bond_mode(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buf_size);
65+
66+ // Iterators (internal)
67+ struct netdef_pertype_iter* _netplan_state_new_netdef_pertype_iter(NetplanState* np_state, const char* def_type);
68+diff --git a/python-cffi/netplan/netdef.py b/python-cffi/netplan/netdef.py
69+index cc23725..a323fff 100644
70+--- a/python-cffi/netplan/netdef.py
71++++ b/python-cffi/netplan/netdef.py
72+@@ -143,6 +143,10 @@ class NetDefinition():
73+ raise NetplanException(msg)
74+ return count
75+
76++ @property
77++ def _bond_mode(self) -> str:
78++ return _string_realloc_call_no_error(lambda b: lib._netplan_netdef_get_bond_mode(self._ptr, b, len(b)))
79++
80+ @property
81+ def _is_trivial_compound_itf(self) -> bool:
82+ '''
83+diff --git a/src/types.c b/src/types.c
84+index b885e6c..2ce6925 100644
85+--- a/src/types.c
86++++ b/src/types.c
87+@@ -619,3 +619,14 @@ _netplan_netdef_is_trivial_compound_itf(const NetplanNetDefinition* netdef)
88+ return !complex_object_is_dirty(netdef, &netdef->bridge_params, sizeof(netdef->bridge_params));
89+ return FALSE;
90+ }
91++
92++ssize_t
93++_netplan_netdef_get_bond_mode(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buf_size)
94++{
95++ g_assert(netdef);
96++
97++ if (netdef->type == NETPLAN_DEF_TYPE_BOND && netdef->bond_params.mode)
98++ return netplan_copy_string(netdef->bond_params.mode, out_buffer, out_buf_size);
99++ else
100++ return FALSE;
101++}
102+diff --git a/src/util-internal.h b/src/util-internal.h
103+index e9c2d13..54f962d 100644
104+--- a/src/util-internal.h
105++++ b/src/util-internal.h
106+@@ -123,6 +123,9 @@ netplan_netdef_get_delay_virtual_functions_rebind(const NetplanNetDefinition* ne
107+ NETPLAN_INTERNAL guint
108+ _netplan_netdef_get_vlan_id(const NetplanNetDefinition* netdef);
109+
110++NETPLAN_INTERNAL ssize_t
111++_netplan_netdef_get_bond_mode(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buf_size);
112++
113+ NETPLAN_INTERNAL gboolean
114+ _netplan_netdef_is_trivial_compound_itf(const NetplanNetDefinition* netdef);
115+
116+diff --git a/tests/test_libnetplan.py b/tests/test_libnetplan.py
117+index 134cfa0..395746a 100644
118+--- a/tests/test_libnetplan.py
119++++ b/tests/test_libnetplan.py
120+@@ -984,3 +984,33 @@ tail:
121+ '''
122+ with self.assertRaises(ValueError):
123+ netplan.NetplanParserException('not the expected file path, line and column', 0, 0)
124++
125++ def test_netdef_get_bond_mode(self):
126++ state = state_from_yaml(self.confdir, '''network:
127++ ethernets:
128++ eth0:
129++ dhcp4: false
130++ bonds:
131++ bond0:
132++ parameters:
133++ mode: active-backup
134++ dhcp4: false
135++ interfaces:
136++ - eth0
137++ ''')
138++
139++ self.assertEqual(state['bond0']._bond_mode, "active-backup")
140++
141++ def test_netdef_get_bond_mode_unset(self):
142++ state = state_from_yaml(self.confdir, '''network:
143++ ethernets:
144++ eth0:
145++ dhcp4: false
146++ bonds:
147++ bond0:
148++ dhcp4: false
149++ interfaces:
150++ - eth0
151++ ''')
152++
153++ self.assertIsNone(state['bond0']._bond_mode)
154diff --git a/debian/patches/lp1988018/0019-sriov-move-the-udev-logic-to-a-service-unit.patch b/debian/patches/lp1988018/0019-sriov-move-the-udev-logic-to-a-service-unit.patch
155new file mode 100644
156index 0000000..99ea02a
157--- /dev/null
158+++ b/debian/patches/lp1988018/0019-sriov-move-the-udev-logic-to-a-service-unit.patch
159@@ -0,0 +1,163 @@
160+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
161+Date: Thu, 25 Jan 2024 12:25:40 +0000
162+Subject: sriov: move the udev logic to a service unit
163+
164+netplan apply will be called multiple times in parallel when the udev
165+rules matches multiple device. That might cause problems during boot.
166+
167+Move the call to "netplan apply --sriov-only" to a service so it will be
168+called just once for all the interfaces. The sriov-rebind service must
169+wait for this new service to finish before start running.
170+
171+This change was tested with real hardware and appears to work fine.
172+
173+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
174+Origin: https://github.com/canonical/netplan/pull/439
175+---
176+ src/sriov.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------
177+ 1 file changed, 81 insertions(+), 12 deletions(-)
178+
179+diff --git a/src/sriov.c b/src/sriov.c
180+index 710f1a8..86881b2 100644
181+--- a/src/sriov.c
182++++ b/src/sriov.c
183+@@ -40,6 +40,7 @@ write_sriov_rebind_systemd_unit(GHashTable* pfs, const char* rootdir, GError** e
184+ GString* s = g_string_new("[Unit]\n");
185+ g_string_append(s, "Description=(Re-)bind SR-IOV Virtual Functions to their driver\n");
186+ g_string_append_printf(s, "After=network.target\n");
187++ g_string_append_printf(s, "After=netplan-sriov-apply.service\n");
188+
189+ /* Run after udev */
190+ g_hash_table_iter_init(&iter, pfs);
191+@@ -51,19 +52,60 @@ write_sriov_rebind_systemd_unit(GHashTable* pfs, const char* rootdir, GError** e
192+
193+ g_string_append(s, "\n[Service]\nType=oneshot\n");
194+ g_string_truncate(interfaces, interfaces->len-1); /* cut trailing whitespace */
195+- g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan rebind %s\n", interfaces->str);
196++ g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan rebind --debug %s\n", interfaces->str);
197+
198+ g_autofree char* new_s = _netplan_scrub_systemd_unit_contents(s->str);
199+ g_string_free(s, TRUE);
200+ s = g_string_new(new_s);
201+- _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640);
202++ mode_t orig_umask = umask(022);
203++ g_string_free_to_file(s, rootdir, path, NULL);
204++ umask(orig_umask);
205+ g_string_free(interfaces, TRUE);
206+
207+ safe_mkdir_p_dir(link);
208+ if (symlink(path, link) < 0 && errno != EEXIST) {
209+ // LCOV_EXCL_START
210+ g_set_error(error, NETPLAN_FILE_ERROR, errno,
211+- "failed to create enablement symlink: %m\n");
212++ "failed to create enablement symlink: %m");
213++ return FALSE;
214++ // LCOV_EXCL_STOP
215++ }
216++ return TRUE;
217++}
218++
219++static gboolean
220++write_sriov_apply_systemd_unit(GHashTable* pfs, const char* rootdir, GError** error)
221++{
222++ g_autofree gchar* id_escaped = NULL;
223++ g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/multi-user.target.wants/netplan-sriov-apply.service", NULL);
224++ g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-sriov-apply.service", NULL);
225++ GHashTableIter iter;
226++ gpointer key;
227++
228++ GString* s = g_string_new("[Unit]\n");
229++ g_string_append(s, "Description=Apply SR-IOV configuration\n");
230++ g_string_append_printf(s, "After=network.target\n");
231++
232++ g_hash_table_iter_init(&iter, pfs);
233++ while (g_hash_table_iter_next (&iter, &key, NULL)) {
234++ g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", (gchar*) key);
235++ }
236++
237++ g_string_append(s, "\n[Service]\nType=oneshot\n");
238++ g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan apply --sriov-only\n");
239++
240++ g_autofree char* new_s = _netplan_scrub_systemd_unit_contents(s->str);
241++ g_string_free(s, TRUE);
242++ s = g_string_new(new_s);
243++ mode_t orig_umask = umask(022);
244++ g_string_free_to_file(s, rootdir, path, NULL);
245++ umask(orig_umask);
246++
247++ safe_mkdir_p_dir(link);
248++ if (symlink(path, link) < 0 && errno != EEXIST) {
249++ // LCOV_EXCL_START
250++ g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
251++ "failed to create enablement symlink: %m");
252+ return FALSE;
253+ // LCOV_EXCL_STOP
254+ }
255+@@ -83,6 +125,8 @@ netplan_state_finish_sriov_write(const NetplanState* np_state, const char* rootd
256+
257+ if (np_state) {
258+ GHashTable* rebind_pfs = g_hash_table_new(g_str_hash, g_str_equal);
259++ GHashTable* apply_pfs = g_hash_table_new(g_str_hash, g_str_equal);
260++
261+ /* Find netdev interface names for SR-IOV PFs*/
262+ for (GList* iterator = np_state->netdefs_ordered; iterator; iterator = iterator->next) {
263+ def = (NetplanNetDefinition*) iterator->data;
264+@@ -93,6 +137,15 @@ netplan_state_finish_sriov_write(const NetplanState* np_state, const char* rootd
265+ pf = def;
266+ else if (def->sriov_link)
267+ pf = def->sriov_link;
268++
269++ if (pf) {
270++ if (pf->set_name)
271++ g_hash_table_add(apply_pfs, pf->set_name);
272++ else if (!pf->has_match) /* netdef_id == interface name */
273++ g_hash_table_add(apply_pfs, pf->id);
274++ else
275++ g_warning("%s: Cannot determine SR-IOV PF interface name.", pf->id);
276++ }
277+ }
278+
279+ if (pf && pf->sriov_delay_virtual_functions_rebind) {
280+@@ -106,17 +159,33 @@ netplan_state_finish_sriov_write(const NetplanState* np_state, const char* rootd
281+ pf->id);
282+ }
283+ }
284+- if (g_hash_table_size(rebind_pfs) > 0) {
285+- ret = write_sriov_rebind_systemd_unit(rebind_pfs, rootdir, NULL);
286++
287++ if (any_sriov) {
288++ ret = write_sriov_apply_systemd_unit(apply_pfs, rootdir, NULL);
289++ if (!ret) {
290++ // LCOV_EXCL_START
291++ g_warning("netplan-sriov-apply.service cannot be created.");
292++ goto error;
293++ // LCOV_EXCL_STOP
294++ }
295++
296++ /*
297++ * The sriov-apply service will always be created (as long as there is any sr-iov configuration)
298++ * and the sriov-rebind MUST only run after apply. As sriov-apply will always be there if sriov-rebind
299++ * is present, using the After= dependency statement is enough (Requires= is not necessary).
300++ */
301++ if (g_hash_table_size(rebind_pfs) > 0) {
302++ ret = write_sriov_rebind_systemd_unit(rebind_pfs, rootdir, NULL);
303++ if (!ret)
304++ // LCOV_EXCL_START
305++ g_warning("netplan-sriov-rebind.service cannot be created.");
306++ // LCOV_EXCL_STOP
307++ }
308+ }
309+- g_hash_table_destroy(rebind_pfs);
310+- }
311+
312+- if (any_sriov) {
313+- /* For now we execute apply --sriov-only everytime there is a new
314+- SR-IOV device appearing, which is fine as it's relatively fast */
315+- GString *udev_rule = g_string_new("ACTION==\"add\", SUBSYSTEM==\"net\", ATTRS{sriov_totalvfs}==\"?*\", RUN+=\"/usr/sbin/netplan apply --sriov-only\"\n");
316+- g_string_free_to_file(udev_rule, rootdir, "run/udev/rules.d/99-sriov-netplan-setup.rules", NULL);
317++error:
318++ g_hash_table_destroy(rebind_pfs);
319++ g_hash_table_destroy(apply_pfs);
320+ }
321+
322+ return ret;
323diff --git a/debian/patches/lp1988018/0020-sriov-check-the-eswitch-mode-before-trying-to-change.patch b/debian/patches/lp1988018/0020-sriov-check-the-eswitch-mode-before-trying-to-change.patch
324new file mode 100644
325index 0000000..b3d1d62
326--- /dev/null
327+++ b/debian/patches/lp1988018/0020-sriov-check-the-eswitch-mode-before-trying-to-change.patch
328@@ -0,0 +1,100 @@
329+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
330+Date: Thu, 25 Jan 2024 14:59:44 +0000
331+Subject: sriov: check the eswitch mode before trying to change it
332+
333+The eswitch mode will be modified with devlink regardless its current
334+mode. It causes issues when VF LAG is enabled in the PF. This particular
335+problem will only happen on Mellanox interfaces (hopefully) due to the
336+VF LAG feature.
337+
338+Retrieving the eswitch mode with devlink seems to not work on all NICs.
339+If it fails to get the information, the new method will return
340+'undetermined' and we set the eswitch mode anyway.
341+
342+In a setup with Mellanox NICs with eswitch mode set to switchdev and VF
343+LAG enabled, netplan apply will break the setup even if there is nothing
344+to change in the SR-IOV NICs. This change will prevent it to happen.
345+
346+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
347+Origin: https://github.com/canonical/netplan/pull/439
348+---
349+ netplan_cli/cli/sriov.py | 52 ++++++++++++++++++++++++++++++++++++++----------
350+ 1 file changed, 42 insertions(+), 10 deletions(-)
351+
352+diff --git a/netplan_cli/cli/sriov.py b/netplan_cli/cli/sriov.py
353+index dbec831..4f3448c 100644
354+--- a/netplan_cli/cli/sriov.py
355++++ b/netplan_cli/cli/sriov.py
356+@@ -16,6 +16,7 @@
357+ # You should have received a copy of the GNU General Public License
358+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
359+
360++import json
361+ import logging
362+ import os
363+ import subprocess
364+@@ -152,6 +153,35 @@ class PCIDevice(object):
365+ ]
366+ )
367+
368++ def devlink_eswitch_mode(self) -> str:
369++ """Query eswitch mode via devlink for the PCI device
370++ :return: the eswitch mode or '__undetermined' if it can't be retrieved
371++ :rtype: str
372++ """
373++ pci = f"pci/{self.pci_addr}"
374++ try:
375++ output = subprocess.check_output(
376++ [
377++ "/sbin/devlink",
378++ "-j",
379++ "dev",
380++ "eswitch",
381++ "show",
382++ pci,
383++ ],
384++ stderr=subprocess.DEVNULL,
385++ )
386++ except subprocess.CalledProcessError:
387++ return '__undetermined'
388++
389++ json_output = json.loads(output)
390++
391++ # The JSON document looks like this when the 'mode' is available:
392++ # {"dev":{"pci/0000:03:00.0":{"mode":"switchdev"}}}
393++ # and like this when it's not available
394++ # {"dev":{}}
395++ return json_output.get("dev", {}).get(pci, {}).get('mode', '__undetermined')
396++
397+ def __str__(self) -> str:
398+ """String represenation of object
399+ :return: PCI address of string
400+@@ -447,16 +477,18 @@ def apply_sriov_config(config_manager, rootdir='/'):
401+ if eswitch_mode in ['switchdev', 'legacy']:
402+ pci_addr = _get_pci_slot_name(iface)
403+ pcidev = PCIDevice(pci_addr)
404+- if pcidev.is_pf:
405+- logging.debug("Found VFs of {}: {}".format(pcidev, pcidev.vf_addrs))
406+- if pcidev.vfs:
407+- rebind_delayed = netdef._delay_virtual_functions_rebind
408+- try:
409+- unbind_vfs(pcidev.vfs, pcidev.driver)
410+- pcidev.devlink_set('eswitch', 'mode', eswitch_mode)
411+- finally:
412+- if not rebind_delayed:
413+- bind_vfs(pcidev.vfs, pcidev.driver)
414++ current_eswitch_mode_system = pcidev.devlink_eswitch_mode()
415++ if eswitch_mode != current_eswitch_mode_system:
416++ if pcidev.is_pf:
417++ logging.debug("Found VFs of {}: {}".format(pcidev, pcidev.vf_addrs))
418++ if pcidev.vfs:
419++ rebind_delayed = netdef._delay_virtual_functions_rebind
420++ try:
421++ unbind_vfs(pcidev.vfs, pcidev.driver)
422++ pcidev.devlink_set('eswitch', 'mode', eswitch_mode)
423++ finally:
424++ if not rebind_delayed:
425++ bind_vfs(pcidev.vfs, pcidev.driver)
426+
427+ filtered_vlans_set = set()
428+ for vlan, netdef in np_state.vlans.items():
429diff --git a/debian/patches/lp1988018/0021-sriov_rebind-cooperate-with-VF-LAG-activation.patch b/debian/patches/lp1988018/0021-sriov_rebind-cooperate-with-VF-LAG-activation.patch
430new file mode 100644
431index 0000000..c61cb4a
432--- /dev/null
433+++ b/debian/patches/lp1988018/0021-sriov_rebind-cooperate-with-VF-LAG-activation.patch
434@@ -0,0 +1,172 @@
435+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
436+Date: Thu, 25 Jan 2024 17:27:40 +0000
437+Subject: sriov_rebind: cooperate with VF LAG activation
438+
439+When a PF is part of a bond interface, the mlx5 driver will try to
440+activate the VF LAG feature. While it happens, VFs can't be bound or the
441+process will fail.
442+
443+This change adds a verification step to netplan rebind so, if the PF is
444+part of a bond, it will wait for a few seconds until the VF LAG feature
445+is reported as active by the driver.
446+
447+This is done through the debugfs interface provided by the mlx5 driver
448+in newer version of the kernel (5.19+).
449+
450+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
451+Origin: https://github.com/canonical/netplan/pull/439
452+---
453+ netplan_cli/cli/commands/sriov_rebind.py | 124 ++++++++++++++++++++++++++++++-
454+ 1 file changed, 122 insertions(+), 2 deletions(-)
455+
456+diff --git a/netplan_cli/cli/commands/sriov_rebind.py b/netplan_cli/cli/commands/sriov_rebind.py
457+index 373d9c8..a8c324f 100644
458+--- a/netplan_cli/cli/commands/sriov_rebind.py
459++++ b/netplan_cli/cli/commands/sriov_rebind.py
460+@@ -18,9 +18,29 @@
461+ '''netplan SR-IOV rebind command line'''
462+
463+ import logging
464++import os
465++from time import sleep
466+
467+ from .. import utils
468+ from ..sriov import PCIDevice, bind_vfs, _get_pci_slot_name
469++import netplan
470++
471++
472++FALLBACK_WAIT_TIME_SEC = 3
473++INTERVAL_SEC = 0.2
474++MAX_WAITING_TIME_SEC = 5
475++
476++
477++class MLX5VFLAGStateNotFound(Exception):
478++ pass
479++
480++
481++class MLX5VFLAGStateCannotBeRead(Exception):
482++ pass
483++
484++
485++class MLX5VFLAGStateDisabled(Exception):
486++ pass
487+
488+
489+ class NetplanSriovRebind(utils.NetplanCommand):
490+@@ -31,6 +51,8 @@ class NetplanSriovRebind(utils.NetplanCommand):
491+ leaf=True)
492+
493+ def run(self):
494++ self.parser.add_argument('--root-dir', default='/',
495++ help='Search for configuration files in this root directory instead of /')
496+ self.parser.add_argument('netdevs', type=str, nargs='*', default=[],
497+ help='Space separated list of PF interface names')
498+ self.func = self.command_rebind
499+@@ -44,7 +66,105 @@ class NetplanSriovRebind(utils.NetplanCommand):
500+ pci_addr = _get_pci_slot_name(iface)
501+ pcidev = PCIDevice(pci_addr)
502+ if not pcidev.is_pf:
503+- logging.warning('{} does not seem to be a SR-IOV physical function'.format(iface))
504++ logging.debug('{} does not seem to be a SR-IOV physical function'.format(iface))
505+ continue
506++
507++ # There are some hardware-specific configuration that must happen *before* the bind
508++ # of VFs to their drivers. Some settings take time to be effective and, when possible,
509++ # we need to wait until the driver reports it's ready.
510++ self._perform_hardware_specific_quirks(iface, pcidev)
511++
512+ bound_vfs = bind_vfs(pcidev.vfs, pcidev.driver)
513+- logging.info('{}: bound {} VFs'.format(pcidev, len(bound_vfs)))
514++ logging.debug('{}: bound {} VFs'.format(pcidev, len(bound_vfs)))
515++
516++ def _perform_hardware_specific_quirks(self, iface: str, pf: PCIDevice):
517++ """
518++ Perform any hardware-specific quirks for the given SR-IOV device to make
519++ sure it's ready before the bind.
520++ """
521++
522++ if pf.driver in ['mlx5_core']:
523++ # Mellanox specific quirks
524++
525++ parser = netplan.Parser()
526++ parser.load_yaml_hierarchy(self.root_dir)
527++ np_state = netplan.State()
528++ np_state.import_parser_results(parser)
529++
530++ for netdef in np_state.ethernets.values():
531++ if (netdef._has_match and netdef.set_name == iface) or netdef.id == iface:
532++ if bond_link := netdef.links.get('bond'):
533++ # VF LAG support. See LP: #1988018
534++ # If the PF is a member of a bond, the user might be trying to enable the
535++ # VF LAG feature.
536++ # Mellanox VF LAG requires that the LAG state reports as 'active'
537++ # *before* VFs can be bound to the driver. Performing the bind operation
538++ # before the device is ready will cause the VF LAG feature to never be enabled.
539++
540++ # Another condition for the VF LAG activation is that the LAG mode
541++ # must be one of 'active-backup', 'balanced-xor' or '802.3ad'.
542++ bond_mode = bond_link._bond_mode
543++ if not self._is_bond_mode_supported(bond_mode):
544++ logging.debug(f'{iface} - LAG mode {bond_mode} is not supported by VF LAG')
545++ continue
546++
547++ logging.debug(f'{iface} - waiting for the LAG state to be \'active\'')
548++ try:
549++ self._wait_for_mlx5_pf_lag_state_active(pf)
550++ except MLX5VFLAGStateCannotBeRead:
551++ logging.debug(f'{iface} - VF LAG state cannot be read')
552++ except MLX5VFLAGStateNotFound:
553++ logging.debug(f'{iface} - VF LAG state debugfs file not found')
554++ except MLX5VFLAGStateDisabled:
555++ logging.debug(f'{iface} - VF LAG state is still \'disabled\' after waiting')
556++ else:
557++ logging.debug(f'{iface} - VF LAG state is \'active\'')
558++
559++ def _wait_for_mlx5_pf_lag_state_active(self, pf: PCIDevice):
560++ """
561++ The mlx5 driver added support for debugfs in https://github.com/torvalds/linux/commit/7f46a0b7327a
562++ It's available since kernel 5.19 https://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.19
563++ """
564++ retries = int(MAX_WAITING_TIME_SEC / INTERVAL_SEC)
565++ pci_addr = pf.pci_addr
566++ path = f'/sys/kernel/debug/mlx5/{pci_addr}/lag/state'
567++
568++ if not os.path.exists(path):
569++ # If the debugfs file doesn't exist, it might be because this version of the mlx5 driver
570++ # still doesn't support it or because the debugfs is not mounted.
571++ # In this case, we probably should still wait for a few seconds to give time for the
572++ # driver to change state.
573++ # Based on tests with a ConnectX-5 NIC, 1 second is enough time, so let's wait a bit more
574++ # just in case. This delay will only be introduced if the PF is part of a bond.
575++ sleep(FALLBACK_WAIT_TIME_SEC)
576++ raise MLX5VFLAGStateNotFound
577++
578++ while retries > 0:
579++ try:
580++ if self._get_mlx5_vf_lag_state(pci_addr) != 'active':
581++ logging.debug(f'{pci_addr} VF LAG state is not active yet, retrying in 1 second...')
582++ # Based on tests with a ConnectX-5 NIC, a single 1-second cycle was enough time to
583++ # allow the interfaces to change state.
584++ sleep(INTERVAL_SEC)
585++ else:
586++ return
587++
588++ except Exception:
589++ raise MLX5VFLAGStateCannotBeRead
590++
591++ retries = retries - 1
592++
593++ raise MLX5VFLAGStateDisabled
594++
595++ def _is_bond_mode_supported(self, mode: str) -> bool:
596++ '''
597++ Return True or False if the bond mode is one of the supported modes
598++ for the VG LAG activation.
599++ '''
600++ return mode in ['active-backup', 'balanced-xor', '802.3ad']
601++
602++ def _get_mlx5_vf_lag_state(self, pci_addr: str) -> str:
603++ path = f'/sys/kernel/debug/mlx5/{pci_addr}/lag/state'
604++
605++ with open(path, 'r') as f:
606++ return f.read().strip()
607diff --git a/debian/patches/lp1988018/0022-sriov_rebind-netplan-rebind-debug-setup.patch b/debian/patches/lp1988018/0022-sriov_rebind-netplan-rebind-debug-setup.patch
608new file mode 100644
609index 0000000..7c15299
610--- /dev/null
611+++ b/debian/patches/lp1988018/0022-sriov_rebind-netplan-rebind-debug-setup.patch
612@@ -0,0 +1,113 @@
613+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
614+Date: Thu, 8 Feb 2024 15:36:30 +0000
615+Subject: sriov_rebind: netplan rebind --debug setup
616+
617+All the subcommands support the parameter --debug but it's not really
618+implemented (only the global netplan --debug is).
619+
620+The main reason for that is because the global --debug also enables glib
621+debugging messages and I'd like to not have all that noise in the output
622+of netplan rebind as --debug will be used by default in the systemd
623+service unit.
624+
625+But the real reason for that is because it's really tricky to get the
626+messages from the root logger from stderr in unit tests. Changing the
627+root logger to log them to stdout will break many tests.
628+
629+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
630+Origin: https://github.com/canonical/netplan/pull/439
631+---
632+ netplan_cli/cli/commands/sriov_rebind.py | 36 ++++++++++++++++++++++++--------
633+ 1 file changed, 27 insertions(+), 9 deletions(-)
634+
635+diff --git a/netplan_cli/cli/commands/sriov_rebind.py b/netplan_cli/cli/commands/sriov_rebind.py
636+index a8c324f..334dcaa 100644
637+--- a/netplan_cli/cli/commands/sriov_rebind.py
638++++ b/netplan_cli/cli/commands/sriov_rebind.py
639+@@ -19,6 +19,7 @@
640+
641+ import logging
642+ import os
643++import sys
644+ from time import sleep
645+
646+ from .. import utils
647+@@ -57,7 +58,24 @@ class NetplanSriovRebind(utils.NetplanCommand):
648+ help='Space separated list of PF interface names')
649+ self.func = self.command_rebind
650+
651++ self.logger = logging.getLogger('sriov_rebind')
652++ self.logger.propagate = False
653++ log_handler = logging.StreamHandler(stream=sys.stdout)
654++
655+ self.parse_args()
656++
657++ # netplan rebind --debug setup
658++ if self.debug:
659++ self.logger.setLevel(logging.DEBUG)
660++ log_handler.setLevel(logging.DEBUG)
661++ log_handler.setFormatter(logging.Formatter('%(levelname)s:%(message)s'))
662++ else:
663++ self.logger.setLevel(logging.INFO)
664++ log_handler.setLevel(logging.INFO)
665++ log_handler.setFormatter(logging.Formatter('%(message)s'))
666++
667++ self.logger.addHandler(log_handler)
668++
669+ self.run_command()
670+
671+ def command_rebind(self):
672+@@ -66,7 +84,7 @@ class NetplanSriovRebind(utils.NetplanCommand):
673+ pci_addr = _get_pci_slot_name(iface)
674+ pcidev = PCIDevice(pci_addr)
675+ if not pcidev.is_pf:
676+- logging.debug('{} does not seem to be a SR-IOV physical function'.format(iface))
677++ self.logger.debug('{} does not seem to be a SR-IOV physical function'.format(iface))
678+ continue
679+
680+ # There are some hardware-specific configuration that must happen *before* the bind
681+@@ -75,7 +93,7 @@ class NetplanSriovRebind(utils.NetplanCommand):
682+ self._perform_hardware_specific_quirks(iface, pcidev)
683+
684+ bound_vfs = bind_vfs(pcidev.vfs, pcidev.driver)
685+- logging.debug('{}: bound {} VFs'.format(pcidev, len(bound_vfs)))
686++ self.logger.debug('{}: bound {} VFs'.format(pcidev, len(bound_vfs)))
687+
688+ def _perform_hardware_specific_quirks(self, iface: str, pf: PCIDevice):
689+ """
690+@@ -105,20 +123,20 @@ class NetplanSriovRebind(utils.NetplanCommand):
691+ # must be one of 'active-backup', 'balanced-xor' or '802.3ad'.
692+ bond_mode = bond_link._bond_mode
693+ if not self._is_bond_mode_supported(bond_mode):
694+- logging.debug(f'{iface} - LAG mode {bond_mode} is not supported by VF LAG')
695++ self.logger.debug(f'{iface} - LAG mode {bond_mode} is not supported by VF LAG')
696+ continue
697+
698+- logging.debug(f'{iface} - waiting for the LAG state to be \'active\'')
699++ self.logger.debug(f'{iface} - waiting for the LAG state to be \'active\'')
700+ try:
701+ self._wait_for_mlx5_pf_lag_state_active(pf)
702+ except MLX5VFLAGStateCannotBeRead:
703+- logging.debug(f'{iface} - VF LAG state cannot be read')
704++ self.logger.debug(f'{iface} - VF LAG state cannot be read')
705+ except MLX5VFLAGStateNotFound:
706+- logging.debug(f'{iface} - VF LAG state debugfs file not found')
707++ self.logger.debug(f'{iface} - VF LAG state debugfs file not found')
708+ except MLX5VFLAGStateDisabled:
709+- logging.debug(f'{iface} - VF LAG state is still \'disabled\' after waiting')
710++ self.logger.debug(f'{iface} - VF LAG state is still \'disabled\' after waiting')
711+ else:
712+- logging.debug(f'{iface} - VF LAG state is \'active\'')
713++ self.logger.debug(f'{iface} - VF LAG state is \'active\'')
714+
715+ def _wait_for_mlx5_pf_lag_state_active(self, pf: PCIDevice):
716+ """
717+@@ -142,7 +160,7 @@ class NetplanSriovRebind(utils.NetplanCommand):
718+ while retries > 0:
719+ try:
720+ if self._get_mlx5_vf_lag_state(pci_addr) != 'active':
721+- logging.debug(f'{pci_addr} VF LAG state is not active yet, retrying in 1 second...')
722++ self.logger.debug(f'{pci_addr} VF LAG state is not active yet, retrying...')
723+ # Based on tests with a ConnectX-5 NIC, a single 1-second cycle was enough time to
724+ # allow the interfaces to change state.
725+ sleep(INTERVAL_SEC)
726diff --git a/debian/patches/lp1988018/0023-tests-sriov-adapt-tests-to-the-last-sr-iov-related-c.patch b/debian/patches/lp1988018/0023-tests-sriov-adapt-tests-to-the-last-sr-iov-related-c.patch
727new file mode 100644
728index 0000000..66eacb6
729--- /dev/null
730+++ b/debian/patches/lp1988018/0023-tests-sriov-adapt-tests-to-the-last-sr-iov-related-c.patch
731@@ -0,0 +1,554 @@
732+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
733+Date: Thu, 25 Jan 2024 17:42:34 +0000
734+Subject: tests/sriov: adapt tests to the last sr-iov related changes
735+
736+And add a few more tests and delete tests that don't make sense anymore.
737+
738+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
739+Origin: https://github.com/canonical/netplan/pull/439
740+---
741+ tests/generator/base.py | 6 -
742+ tests/generator/test_ethernets.py | 109 +-------------
743+ tests/test_sriov.py | 299 +++++++++++++++++++++++++++++++++++++-
744+ 3 files changed, 294 insertions(+), 120 deletions(-)
745+
746+diff --git a/tests/generator/base.py b/tests/generator/base.py
747+index d1298e3..f99fd73 100644
748+--- a/tests/generator/base.py
749++++ b/tests/generator/base.py
750+@@ -378,12 +378,6 @@ class TestBase(unittest.TestCase):
751+ with open(os.path.join(networkd_dir, '10-netplan-' + fname)) as f:
752+ self.assertEqual(f.read(), contents)
753+
754+- def assert_additional_udev(self, file_contents_map):
755+- udev_dir = os.path.join(self.workdir.name, 'run', 'udev', 'rules.d')
756+- for fname, contents in file_contents_map.items():
757+- with open(os.path.join(udev_dir, fname)) as f:
758+- self.assertEqual(f.read(), contents)
759+-
760+ def assert_networkd_udev(self, file_contents_map):
761+ udev_dir = os.path.join(self.workdir.name, 'run', 'udev', 'rules.d')
762+ if not file_contents_map:
763+diff --git a/tests/generator/test_ethernets.py b/tests/generator/test_ethernets.py
764+index 6c1a101..b67b044 100644
765+--- a/tests/generator/test_ethernets.py
766++++ b/tests/generator/test_ethernets.py
767+@@ -18,7 +18,7 @@
768+
769+ import os
770+
771+-from .base import TestBase, ND_DHCP4, UDEV_MAC_RULE, UDEV_NO_MAC_RULE, UDEV_SRIOV_RULE, \
772++from .base import TestBase, ND_DHCP4, UDEV_MAC_RULE, UDEV_NO_MAC_RULE, \
773+ NM_MANAGED, NM_UNMANAGED, NM_MANAGED_MAC, NM_UNMANAGED_MAC, \
774+ NM_MANAGED_DRIVER, NM_UNMANAGED_DRIVER
775+
776+@@ -82,45 +82,6 @@ LinkLocalAddressing=ipv6
777+ '''})
778+ self.assert_networkd_udev(None)
779+
780+- def test_eth_sriov_vlan_filterv_link(self):
781+- self.generate('''network:
782+- version: 2
783+- ethernets:
784+- enp1:
785+- dhcp4: n
786+- enp1s16f1:
787+- dhcp4: n
788+- link: enp1''')
789+-
790+- self.assert_networkd({'enp1.network': '''[Match]
791+-Name=enp1
792+-
793+-[Network]
794+-LinkLocalAddressing=ipv6
795+-''',
796+- 'enp1s16f1.network': '''[Match]
797+-Name=enp1s16f1
798+-
799+-[Network]
800+-LinkLocalAddressing=ipv6
801+-'''})
802+- self.assert_additional_udev({'99-sriov-netplan-setup.rules': UDEV_SRIOV_RULE})
803+-
804+- def test_eth_sriov_virtual_functions(self):
805+- self.generate('''network:
806+- version: 2
807+- ethernets:
808+- enp1:
809+- virtual-function-count: 8''')
810+-
811+- self.assert_networkd({'enp1.network': '''[Match]
812+-Name=enp1
813+-
814+-[Network]
815+-LinkLocalAddressing=ipv6
816+-'''})
817+- self.assert_additional_udev({'99-sriov-netplan-setup.rules': UDEV_SRIOV_RULE})
818+-
819+ def test_eth_match_by_driver_rename(self):
820+ self.generate('''network:
821+ version: 2
822+@@ -384,74 +345,6 @@ method=link-local
823+ method=ignore
824+ '''})
825+
826+- def test_eth_sriov_link(self):
827+- self.generate('''network:
828+- version: 2
829+- renderer: NetworkManager
830+- ethernets:
831+- enp1:
832+- dhcp4: n
833+- enp1s16f1:
834+- dhcp4: n
835+- link: enp1''')
836+-
837+- self.assert_networkd({})
838+- self.assert_nm({'enp1': '''[connection]
839+-id=netplan-enp1
840+-type=ethernet
841+-interface-name=enp1
842+-
843+-[ethernet]
844+-wake-on-lan=0
845+-
846+-[ipv4]
847+-method=link-local
848+-
849+-[ipv6]
850+-method=ignore
851+-''',
852+- 'enp1s16f1': '''[connection]
853+-id=netplan-enp1s16f1
854+-type=ethernet
855+-interface-name=enp1s16f1
856+-
857+-[ethernet]
858+-wake-on-lan=0
859+-
860+-[ipv4]
861+-method=link-local
862+-
863+-[ipv6]
864+-method=ignore
865+-'''})
866+- self.assert_additional_udev({'99-sriov-netplan-setup.rules': UDEV_SRIOV_RULE})
867+-
868+- def test_eth_sriov_virtual_functions(self):
869+- self.generate('''network:
870+- version: 2
871+- renderer: NetworkManager
872+- ethernets:
873+- enp1:
874+- dhcp4: n
875+- virtual-function-count: 8''')
876+-
877+- self.assert_networkd({})
878+- self.assert_nm({'enp1': '''[connection]
879+-id=netplan-enp1
880+-type=ethernet
881+-interface-name=enp1
882+-
883+-[ethernet]
884+-wake-on-lan=0
885+-
886+-[ipv4]
887+-method=link-local
888+-
889+-[ipv6]
890+-method=ignore
891+-'''})
892+- self.assert_additional_udev({'99-sriov-netplan-setup.rules': UDEV_SRIOV_RULE})
893+-
894+ def test_eth_set_mac(self):
895+ self.generate('''network:
896+ version: 2
897+diff --git a/tests/test_sriov.py b/tests/test_sriov.py
898+index 054500c..324a9e3 100644
899+--- a/tests/test_sriov.py
900++++ b/tests/test_sriov.py
901+@@ -16,13 +16,16 @@
902+ # You should have received a copy of the GNU General Public License
903+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
904+
905++from io import StringIO
906+ import os
907++import subprocess
908+ import tempfile
909+ import unittest
910+
911+ from subprocess import CalledProcessError
912+ from collections import defaultdict
913+ from unittest.mock import patch, mock_open, call
914++from netplan_cli.cli.commands.sriov_rebind import INTERVAL_SEC, MAX_WAITING_TIME_SEC, NetplanSriovRebind
915+
916+ import netplan
917+ import netplan_cli.cli.sriov as sriov
918+@@ -737,8 +740,9 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
919+ @patch('subprocess.check_call')
920+ @patch('netplan_cli.cli.sriov.PCIDevice.bound', new_callable=unittest.mock.PropertyMock)
921+ @patch('netplan_cli.cli.sriov.PCIDevice.sys', new_callable=unittest.mock.PropertyMock)
922++ @patch('netplan_cli.cli.sriov.PCIDevice.devlink_eswitch_mode')
923+ @patch('netplan_cli.cli.sriov._get_pci_slot_name')
924+- def test_apply_sriov_config_eswitch_mode(self, gpsn, pcidevice_sys, pcidevice_bound,
925++ def test_apply_sriov_config_eswitch_mode(self, gpsn, pcidevice_devlink, pcidevice_sys, pcidevice_bound,
926+ scc, quirks, set_numvfs, get_counts, netifs):
927+ handle = mock_open()
928+ builtin_open = open # save the unpatched version of open()
929+@@ -765,6 +769,7 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
930+ enp2_pci_addr = '0000:03:00.1'
931+ gpsn.side_effect = lambda iface: enp1_pci_addr if iface == 'enp1' else enp2_pci_addr
932+ sys_path = os.path.join(self.workdir.name, 'sys')
933++ pcidevice_devlink.return_value = '__undetermined'
934+ pcidevice_sys.return_value = sys_path
935+ pcidevice_bound.side_effect = [
936+ True, True, # 2x unbind (enp1 VFs)
937+@@ -851,6 +856,242 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
938+ call('0000:03:00.2'),
939+ call('0000:03:00.3')])
940+
941++ @patch('netplan_cli.cli.sriov.PCIDevice.is_pf', new_callable=unittest.mock.PropertyMock)
942++ @patch('netplan_cli.cli.sriov.PCIDevice.driver', new_callable=unittest.mock.PropertyMock)
943++ @patch('netplan_cli.cli.commands.sriov_rebind._get_pci_slot_name')
944++ @patch('netplan_cli.cli.commands.sriov_rebind.bind_vfs')
945++ def test_cli_rebind_mellanox_with_unsupported_bond_mode(self, bind_mock, gpsn, driver_mock, is_pf_mock):
946++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
947++ print('''network:
948++ version: 2
949++ renderer: networkd
950++ ethernets:
951++ enp1:
952++ embedded-switch-mode: "legacy"
953++ delay-virtual-functions-rebind: true
954++ enp1s16f1:
955++ link: enp1
956++ enp1s16f2:
957++ link: enp1
958++ bonds:
959++ bond0:
960++ parameters:
961++ mode: balance-rr
962++ interfaces:
963++ - enp1
964++''', file=fd)
965++
966++ gpsn.return_value = '0000:03:00.0'
967++ bind_mock.return_value = []
968++ driver_mock.return_value = 'mlx5_core'
969++ is_pf_mock.return_value = True
970++
971++ out = call_cli(['rebind', '--debug', '--root-dir', self.workdir.name, 'enp1'])
972++ self.assertIn('LAG mode balance-rr is not supported by VF LAG', out)
973++
974++ @patch('netplan_cli.cli.commands.sriov_rebind.sleep')
975++ @patch('netplan_cli.cli.sriov.PCIDevice.is_pf', new_callable=unittest.mock.PropertyMock)
976++ @patch('netplan_cli.cli.sriov.PCIDevice.driver', new_callable=unittest.mock.PropertyMock)
977++ @patch('netplan_cli.cli.commands.sriov_rebind._get_pci_slot_name')
978++ @patch('netplan_cli.cli.commands.sriov_rebind.bind_vfs')
979++ @patch('os.path.exists')
980++ def test_cli_rebind_mellanox_state_file_not_found(self, path_mock, bind_mock, gpsn, driver_mock,
981++ is_pf_mock, sleep_mock):
982++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
983++ print('''network:
984++ version: 2
985++ renderer: networkd
986++ ethernets:
987++ enp1:
988++ embedded-switch-mode: "legacy"
989++ delay-virtual-functions-rebind: true
990++ enp1s16f1:
991++ link: enp1
992++ enp1s16f2:
993++ link: enp1
994++ bonds:
995++ bond0:
996++ parameters:
997++ mode: active-backup
998++ interfaces:
999++ - enp1
1000++''', file=fd)
1001++
1002++ sleep_mock.return_value = None
1003++ path_mock.return_value = False
1004++ gpsn.return_value = '0000:03:00.0'
1005++ bind_mock.return_value = []
1006++ driver_mock.return_value = 'mlx5_core'
1007++ is_pf_mock.return_value = True
1008++
1009++ out = call_cli(['rebind', '--debug', '--root-dir', self.workdir.name, 'enp1'])
1010++ self.assertIn('VF LAG state debugfs file not found', out)
1011++
1012++ @patch('netplan_cli.cli.commands.sriov_rebind.NetplanSriovRebind._get_mlx5_vf_lag_state')
1013++ @patch('netplan_cli.cli.commands.sriov_rebind.sleep')
1014++ @patch('netplan_cli.cli.sriov.PCIDevice.is_pf', new_callable=unittest.mock.PropertyMock)
1015++ @patch('netplan_cli.cli.sriov.PCIDevice.driver', new_callable=unittest.mock.PropertyMock)
1016++ @patch('netplan_cli.cli.commands.sriov_rebind._get_pci_slot_name')
1017++ @patch('netplan_cli.cli.commands.sriov_rebind.bind_vfs')
1018++ @patch('os.path.exists')
1019++ def test_cli_rebind_mellanox_state_file_cannot_be_read(self, path_mock, bind_mock, gpsn, driver_mock,
1020++ is_pf_mock, sleep_mock, get_mlx5_mock):
1021++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1022++ print('''network:
1023++ version: 2
1024++ renderer: networkd
1025++ ethernets:
1026++ enp1:
1027++ embedded-switch-mode: "legacy"
1028++ delay-virtual-functions-rebind: true
1029++ enp1s16f1:
1030++ link: enp1
1031++ enp1s16f2:
1032++ link: enp1
1033++ bonds:
1034++ bond0:
1035++ parameters:
1036++ mode: active-backup
1037++ interfaces:
1038++ - enp1
1039++''', file=fd)
1040++
1041++ sleep_mock.return_value = None
1042++ path_mock.return_value = True
1043++ gpsn.return_value = '0000:03:00.0'
1044++ bind_mock.return_value = []
1045++ driver_mock.return_value = 'mlx5_core'
1046++ is_pf_mock.return_value = True
1047++ get_mlx5_mock.side_effect = Exception
1048++
1049++ out = call_cli(['rebind', '--debug', '--root-dir', self.workdir.name, 'enp1'])
1050++ self.assertIn('VF LAG state cannot be read', out)
1051++
1052++ @patch('netplan_cli.cli.commands.sriov_rebind.NetplanSriovRebind._get_mlx5_vf_lag_state')
1053++ @patch('netplan_cli.cli.commands.sriov_rebind.sleep')
1054++ @patch('netplan_cli.cli.sriov.PCIDevice.is_pf', new_callable=unittest.mock.PropertyMock)
1055++ @patch('netplan_cli.cli.sriov.PCIDevice.driver', new_callable=unittest.mock.PropertyMock)
1056++ @patch('netplan_cli.cli.commands.sriov_rebind._get_pci_slot_name')
1057++ @patch('netplan_cli.cli.commands.sriov_rebind.bind_vfs')
1058++ @patch('os.path.exists')
1059++ def test_cli_rebind_mellanox_disabled_after_waiting(self, path_mock, bind_mock, gpsn, driver_mock,
1060++ is_pf_mock, sleep_mock, get_mlx5_mock):
1061++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1062++ print('''network:
1063++ version: 2
1064++ renderer: networkd
1065++ ethernets:
1066++ enp1:
1067++ embedded-switch-mode: "legacy"
1068++ delay-virtual-functions-rebind: true
1069++ enp1s16f1:
1070++ link: enp1
1071++ enp1s16f2:
1072++ link: enp1
1073++ bonds:
1074++ bond0:
1075++ parameters:
1076++ mode: active-backup
1077++ interfaces:
1078++ - enp1
1079++''', file=fd)
1080++
1081++ sleep_mock.return_value = None
1082++ path_mock.return_value = True
1083++ gpsn.return_value = '0000:03:00.0'
1084++ bind_mock.return_value = []
1085++ driver_mock.return_value = 'mlx5_core'
1086++ is_pf_mock.return_value = True
1087++ get_mlx5_mock.return_value = 'disabled'
1088++
1089++ out = call_cli(['rebind', '--debug', '--root-dir', self.workdir.name, 'enp1'])
1090++ self.assertIn('VF LAG state is still \'disabled\' after waiting', out)
1091++
1092++ # check if are we retrying the expected number of times
1093++ retries = int(MAX_WAITING_TIME_SEC / INTERVAL_SEC)
1094++ sleep_calls = [call(INTERVAL_SEC)] * retries
1095++ self.assertEqual(sleep_mock.call_args_list, sleep_calls)
1096++
1097++ @patch('netplan_cli.cli.commands.sriov_rebind.NetplanSriovRebind._get_mlx5_vf_lag_state')
1098++ @patch('netplan_cli.cli.commands.sriov_rebind.sleep')
1099++ @patch('netplan_cli.cli.sriov.PCIDevice.is_pf', new_callable=unittest.mock.PropertyMock)
1100++ @patch('netplan_cli.cli.sriov.PCIDevice.driver', new_callable=unittest.mock.PropertyMock)
1101++ @patch('netplan_cli.cli.commands.sriov_rebind._get_pci_slot_name')
1102++ @patch('netplan_cli.cli.commands.sriov_rebind.bind_vfs')
1103++ @patch('os.path.exists')
1104++ def test_cli_rebind_mellanox_vf_lag_state_is_active(self, path_mock, bind_mock, gpsn, driver_mock,
1105++ is_pf_mock, sleep_mock, get_mlx5_mock):
1106++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1107++ print('''network:
1108++ version: 2
1109++ renderer: networkd
1110++ ethernets:
1111++ enp1:
1112++ embedded-switch-mode: "legacy"
1113++ delay-virtual-functions-rebind: true
1114++ enp1s16f1:
1115++ link: enp1
1116++ enp1s16f2:
1117++ link: enp1
1118++ bonds:
1119++ bond0:
1120++ parameters:
1121++ mode: active-backup
1122++ interfaces:
1123++ - enp1
1124++''', file=fd)
1125++
1126++ sleep_mock.return_value = None
1127++ path_mock.return_value = True
1128++ gpsn.return_value = '0000:03:00.0'
1129++ bind_mock.return_value = []
1130++ driver_mock.return_value = 'mlx5_core'
1131++ is_pf_mock.return_value = True
1132++ get_mlx5_mock.return_value = 'active'
1133++
1134++ out = call_cli(['rebind', '--debug', '--root-dir', self.workdir.name, 'enp1'])
1135++ self.assertIn('VF LAG state is \'active\'', out)
1136++
1137++ def test_get_mlx5_vf_lag_state(self):
1138++ rebind = NetplanSriovRebind()
1139++
1140++ f = StringIO()
1141++ f.write(' active')
1142++ f.seek(0)
1143++
1144++ with patch('builtins.open') as open_mock:
1145++ open_mock.return_value = f
1146++ state = rebind._get_mlx5_vf_lag_state('fake_pci_address')
1147++
1148++ self.assertEqual(state, 'active')
1149++
1150++ @patch('subprocess.check_output')
1151++ def test_PCIDevice_devlink_eswitch_mode_query(self, check_output_mock):
1152++ pcidev = sriov.PCIDevice('0000:03:00.0')
1153++ check_output_mock.return_value = '{"dev":{"pci/0000:03:00.0":{"mode":"switchdev"}}}'
1154++ self.assertEqual(pcidev.devlink_eswitch_mode(), 'switchdev')
1155++ check_output_mock.assert_has_calls([
1156++ call(['/sbin/devlink', '-j', 'dev', 'eswitch', 'show', 'pci/0000:03:00.0'], stderr=-3),
1157++ ])
1158++
1159++ @patch('subprocess.check_output')
1160++ def test_PCIDevice_devlink_eswitch_mode_query_not_supported(self, check_output_mock):
1161++ pcidev = sriov.PCIDevice('0000:03:00.0')
1162++ check_output_mock.return_value = '{"dev":{}}'
1163++ self.assertEqual(pcidev.devlink_eswitch_mode(), '__undetermined')
1164++ check_output_mock.assert_has_calls([
1165++ call(['/sbin/devlink', '-j', 'dev', 'eswitch', 'show', 'pci/0000:03:00.0'], stderr=-3),
1166++ ])
1167++
1168++ @patch('subprocess.check_output')
1169++ def test_PCIDevice_devlink_eswitch_mode_query_failure(self, check_output_mock):
1170++ pcidev = sriov.PCIDevice('0000:03:00.0')
1171++ check_output_mock.side_effect = subprocess.CalledProcessError(1, None)
1172++ self.assertEqual(pcidev.devlink_eswitch_mode(), '__undetermined')
1173++ check_output_mock.assert_has_calls([
1174++ call(['/sbin/devlink', '-j', 'dev', 'eswitch', 'show', 'pci/0000:03:00.0'], stderr=-3),
1175++ ])
1176++
1177+
1178+ class TestParser(TestBase):
1179+ def test_eswitch_mode(self):
1180+@@ -871,12 +1112,22 @@ class TestParser(TestBase):
1181+ self.assert_sriov({'rebind.service': '''[Unit]
1182+ Description=(Re-)bind SR-IOV Virtual Functions to their driver
1183+ After=network.target
1184++After=netplan-sriov-apply.service
1185+ After=sys-subsystem-net-devices-enblue.device
1186+ After=sys-subsystem-net-devices-engreen.device
1187+
1188+ [Service]
1189+ Type=oneshot
1190+-ExecStart=/usr/sbin/netplan rebind enblue engreen
1191++ExecStart=/usr/sbin/netplan rebind --debug enblue engreen
1192++''', 'apply.service': '''[Unit]
1193++Description=Apply SR-IOV configuration
1194++After=network.target
1195++After=sys-subsystem-net-devices-enblue.device
1196++After=sys-subsystem-net-devices-engreen.device
1197++
1198++[Service]
1199++Type=oneshot
1200++ExecStart=/usr/sbin/netplan apply --sriov-only
1201+ '''})
1202+
1203+ def test_rebind_service_generation(self):
1204+@@ -907,12 +1158,22 @@ ExecStart=/usr/sbin/netplan rebind enblue engreen
1205+ self.assert_sriov({'rebind.service': '''[Unit]
1206+ Description=(Re-)bind SR-IOV Virtual Functions to their driver
1207+ After=network.target
1208++After=netplan-sriov-apply.service
1209++After=sys-subsystem-net-devices-enblue.device
1210++After=sys-subsystem-net-devices-engreen.device
1211++
1212++[Service]
1213++Type=oneshot
1214++ExecStart=/usr/sbin/netplan rebind --debug enblue engreen
1215++''', 'apply.service': '''[Unit]
1216++Description=Apply SR-IOV configuration
1217++After=network.target
1218+ After=sys-subsystem-net-devices-enblue.device
1219+ After=sys-subsystem-net-devices-engreen.device
1220+
1221+ [Service]
1222+ Type=oneshot
1223+-ExecStart=/usr/sbin/netplan rebind enblue engreen
1224++ExecStart=/usr/sbin/netplan apply --sriov-only
1225+ '''})
1226+
1227+ def test_escaping_semicolons_from_unit_file(self):
1228+@@ -936,12 +1197,23 @@ ExecStart=/usr/sbin/netplan rebind enblue engreen
1229+ self.assert_sriov({'rebind.service': '''[Unit]
1230+ Description=(Re-)bind SR-IOV Virtual Functions to their driver
1231+ After=network.target
1232++After=netplan-sriov-apply.service
1233++After=sys-subsystem-net-devices-engreen.device
1234+ After=sys-subsystem-net-devices-;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc.device
1235++
1236++[Service]
1237++Type=oneshot
1238++ExecStart=/usr/sbin/netplan rebind --debug engreen ;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc
1239++''', 'apply.service': '''[Unit]
1240++Description=Apply SR-IOV configuration
1241++DefaultDependencies=no
1242++Before=network-pre.target
1243+ After=sys-subsystem-net-devices-engreen.device
1244++After=sys-subsystem-net-devices-;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc.device
1245+
1246+ [Service]
1247+ Type=oneshot
1248+-ExecStart=/usr/sbin/netplan rebind ;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc engreen
1249++ExecStart=/usr/sbin/netplan apply --sriov-only
1250+ '''})
1251+
1252+ def test_rebind_not_delayed(self):
1253+@@ -953,7 +1225,15 @@ ExecStart=/usr/sbin/netplan rebind ;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc eng
1254+ delay-virtual-functions-rebind: false
1255+ sriov_vf:
1256+ link: engreen''')
1257+- self.assert_sriov({})
1258++ self.assert_sriov({'apply.service': '''[Unit]
1259++Description=Apply SR-IOV configuration
1260++After=network.target
1261++After=sys-subsystem-net-devices-engreen.device
1262++
1263++[Service]
1264++Type=oneshot
1265++ExecStart=/usr/sbin/netplan apply --sriov-only
1266++'''})
1267+
1268+ def test_rebind_no_iface(self):
1269+ out = self.generate('''network:
1270+@@ -965,7 +1245,14 @@ ExecStart=/usr/sbin/netplan rebind ;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc eng
1271+ delay-virtual-functions-rebind: true
1272+ sriov_vf:
1273+ link: engreen''')
1274+- self.assert_sriov({})
1275++ self.assert_sriov({'apply.service': '''[Unit]
1276++Description=Apply SR-IOV configuration
1277++After=network.target
1278++
1279++[Service]
1280++Type=oneshot
1281++ExecStart=/usr/sbin/netplan apply --sriov-only
1282++'''})
1283+ self.assertIn('engreen: Cannot rebind SR-IOV virtual functions, unknown interface name.', out)
1284+
1285+ def test_invalid_not_a_pf(self):
1286diff --git a/debian/patches/lp1988018/0024-sriov_apply-execute-apply-sriov-only-before-network-.patch b/debian/patches/lp1988018/0024-sriov_apply-execute-apply-sriov-only-before-network-.patch
1287new file mode 100644
1288index 0000000..62f53b5
1289--- /dev/null
1290+++ b/debian/patches/lp1988018/0024-sriov_apply-execute-apply-sriov-only-before-network-.patch
1291@@ -0,0 +1,83 @@
1292+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
1293+Date: Fri, 23 Feb 2024 10:08:34 +0000
1294+Subject: sriov_apply: execute apply --sriov-only before network-pre.target
1295+
1296+When activating VF LAG and at the same time changing the e-switch mode
1297+to switchdev, the e-switch change MUST be performed before the bonding.
1298+Trying to change it after the bond is created will fail and the driver
1299+will report that the e-switch is busy.
1300+
1301+In fact, all the SR-IOV initial setup must happen before the networking
1302+configuration.
1303+
1304+Change the order in which we call netplan apply --sriov-only to happen
1305+before network-pre.target.
1306+
1307+Also, add DefaultDependencies=no to the service to prevent the creation
1308+of dependency cycles.
1309+
1310+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1988018
1311+Origin: https://github.com/canonical/netplan/pull/439
1312+---
1313+ src/sriov.c | 3 ++-
1314+ tests/test_sriov.py | 12 ++++++++----
1315+ 2 files changed, 10 insertions(+), 5 deletions(-)
1316+
1317+diff --git a/src/sriov.c b/src/sriov.c
1318+index 86881b2..4c54207 100644
1319+--- a/src/sriov.c
1320++++ b/src/sriov.c
1321+@@ -84,7 +84,8 @@ write_sriov_apply_systemd_unit(GHashTable* pfs, const char* rootdir, GError** er
1322+
1323+ GString* s = g_string_new("[Unit]\n");
1324+ g_string_append(s, "Description=Apply SR-IOV configuration\n");
1325+- g_string_append_printf(s, "After=network.target\n");
1326++ g_string_append(s, "DefaultDependencies=no\n");
1327++ g_string_append(s, "Before=network-pre.target\n");
1328+
1329+ g_hash_table_iter_init(&iter, pfs);
1330+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
1331+diff --git a/tests/test_sriov.py b/tests/test_sriov.py
1332+index 324a9e3..ca297cb 100644
1333+--- a/tests/test_sriov.py
1334++++ b/tests/test_sriov.py
1335+@@ -1121,7 +1121,8 @@ Type=oneshot
1336+ ExecStart=/usr/sbin/netplan rebind --debug enblue engreen
1337+ ''', 'apply.service': '''[Unit]
1338+ Description=Apply SR-IOV configuration
1339+-After=network.target
1340++DefaultDependencies=no
1341++Before=network-pre.target
1342+ After=sys-subsystem-net-devices-enblue.device
1343+ After=sys-subsystem-net-devices-engreen.device
1344+
1345+@@ -1167,7 +1168,8 @@ Type=oneshot
1346+ ExecStart=/usr/sbin/netplan rebind --debug enblue engreen
1347+ ''', 'apply.service': '''[Unit]
1348+ Description=Apply SR-IOV configuration
1349+-After=network.target
1350++DefaultDependencies=no
1351++Before=network-pre.target
1352+ After=sys-subsystem-net-devices-enblue.device
1353+ After=sys-subsystem-net-devices-engreen.device
1354+
1355+@@ -1227,7 +1229,8 @@ ExecStart=/usr/sbin/netplan apply --sriov-only
1356+ link: engreen''')
1357+ self.assert_sriov({'apply.service': '''[Unit]
1358+ Description=Apply SR-IOV configuration
1359+-After=network.target
1360++DefaultDependencies=no
1361++Before=network-pre.target
1362+ After=sys-subsystem-net-devices-engreen.device
1363+
1364+ [Service]
1365+@@ -1247,7 +1250,8 @@ ExecStart=/usr/sbin/netplan apply --sriov-only
1366+ link: engreen''')
1367+ self.assert_sriov({'apply.service': '''[Unit]
1368+ Description=Apply SR-IOV configuration
1369+-After=network.target
1370++DefaultDependencies=no
1371++Before=network-pre.target
1372+
1373+ [Service]
1374+ Type=oneshot
1375diff --git a/debian/patches/lp2020409/0025-sriov-accept-setting-the-eswitch-mode-without-VFs.patch b/debian/patches/lp2020409/0025-sriov-accept-setting-the-eswitch-mode-without-VFs.patch
1376new file mode 100644
1377index 0000000..bd2cd6f
1378--- /dev/null
1379+++ b/debian/patches/lp2020409/0025-sriov-accept-setting-the-eswitch-mode-without-VFs.patch
1380@@ -0,0 +1,148 @@
1381+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
1382+Date: Tue, 9 Apr 2024 12:43:31 +0100
1383+Subject: sriov: accept setting the eswitch mode without VFs
1384+
1385+The embedded switch mode can be set even if the interface doesn't have
1386+any virtual functions. This is a requisite to use the PF with only
1387+scalable functions for example.
1388+
1389+With this change, the presence of the 'embedded-switch-mode' will be
1390+enough to identify the interface as a PF and emit the systemd services
1391+required to apply the configuration.
1392+
1393+The changes required by the CLI will be done is the next commit.
1394+
1395+Bug-Ubuntu: https://bugs.launchpad.net/netplan/+bug/2020409
1396+Origin: https://github.com/canonical/netplan/pull/454
1397+---
1398+ src/sriov.c | 11 ++++++++---
1399+ src/validation.c | 7 ++++---
1400+ tests/test_sriov.py | 39 +++++++++++++++++++++++++++++++++------
1401+ 3 files changed, 45 insertions(+), 12 deletions(-)
1402+
1403+diff --git a/src/sriov.c b/src/sriov.c
1404+index 4c54207..0962533 100644
1405+--- a/src/sriov.c
1406++++ b/src/sriov.c
1407+@@ -128,13 +128,18 @@ netplan_state_finish_sriov_write(const NetplanState* np_state, const char* rootd
1408+ GHashTable* rebind_pfs = g_hash_table_new(g_str_hash, g_str_equal);
1409+ GHashTable* apply_pfs = g_hash_table_new(g_str_hash, g_str_equal);
1410+
1411+- /* Find netdev interface names for SR-IOV PFs*/
1412++ /* Find netdev interface names for SR-IOV PFs
1413++ * We consider an interface to be a PF if at least of the conditions below is true:
1414++ * 1) the user explicitly set a desired number of VFs
1415++ * 2) there is at least one interface with a link to it (meaning the interface is a VF of this PF)
1416++ * 3) the user set the embedded-switch-mode (which can be applied regardless if the interface has VFs)
1417++ * */
1418+ for (GList* iterator = np_state->netdefs_ordered; iterator; iterator = iterator->next) {
1419+ def = (NetplanNetDefinition*) iterator->data;
1420+ pf = NULL;
1421+- if (def->sriov_explicit_vf_count < G_MAXUINT || def->sriov_link) {
1422++ if (def->sriov_explicit_vf_count < G_MAXUINT || def->sriov_link || def->embedded_switch_mode) {
1423+ any_sriov = TRUE;
1424+- if (def->sriov_explicit_vf_count < G_MAXUINT)
1425++ if (def->sriov_explicit_vf_count < G_MAXUINT || def->embedded_switch_mode)
1426+ pf = def;
1427+ else if (def->sriov_link)
1428+ pf = def->sriov_link;
1429+diff --git a/src/validation.c b/src/validation.c
1430+index 23b5808..b6d3bf3 100644
1431+--- a/src/validation.c
1432++++ b/src/validation.c
1433+@@ -475,9 +475,10 @@ validate_sriov_rules(const NetplanParser* npp, NetplanNetDefinition* nd, GError*
1434+ }
1435+ }
1436+ }
1437+- gboolean eswitch_mode = (nd->embedded_switch_mode ||
1438+- nd->sriov_delay_virtual_functions_rebind);
1439+- if (eswitch_mode && !is_sriov_pf) {
1440++ /* Does it set the eswitch mode? It can be set regardless if the interface has VFs */
1441++ if (nd->embedded_switch_mode)
1442++ is_sriov_pf = TRUE;
1443++ if (nd->sriov_delay_virtual_functions_rebind && !is_sriov_pf) {
1444+ valid = yaml_error(npp, node, error, "%s: This is not a SR-IOV PF", nd->id);
1445+ goto sriov_rules_error;
1446+ }
1447+diff --git a/tests/test_sriov.py b/tests/test_sriov.py
1448+index ca297cb..670e67e 100644
1449+--- a/tests/test_sriov.py
1450++++ b/tests/test_sriov.py
1451+@@ -1113,17 +1113,44 @@ class TestParser(TestBase):
1452+ Description=(Re-)bind SR-IOV Virtual Functions to their driver
1453+ After=network.target
1454+ After=netplan-sriov-apply.service
1455+-After=sys-subsystem-net-devices-enblue.device
1456+ After=sys-subsystem-net-devices-engreen.device
1457++After=sys-subsystem-net-devices-enblue.device
1458+
1459+ [Service]
1460+ Type=oneshot
1461+-ExecStart=/usr/sbin/netplan rebind --debug enblue engreen
1462++ExecStart=/usr/sbin/netplan rebind --debug engreen enblue
1463+ ''', 'apply.service': '''[Unit]
1464+ Description=Apply SR-IOV configuration
1465+ DefaultDependencies=no
1466+ Before=network-pre.target
1467++After=sys-subsystem-net-devices-engreen.device
1468+ After=sys-subsystem-net-devices-enblue.device
1469++
1470++[Service]
1471++Type=oneshot
1472++ExecStart=/usr/sbin/netplan apply --sriov-only
1473++'''})
1474++
1475++ def test_eswitch_mode_sets_interface_as_pf(self):
1476++ self.generate('''network:
1477++ version: 2
1478++ ethernets:
1479++ engreen:
1480++ embedded-switch-mode: switchdev
1481++ delay-virtual-functions-rebind: true''')
1482++ self.assert_sriov({'rebind.service': '''[Unit]
1483++Description=(Re-)bind SR-IOV Virtual Functions to their driver
1484++After=network.target
1485++After=netplan-sriov-apply.service
1486++After=sys-subsystem-net-devices-engreen.device
1487++
1488++[Service]
1489++Type=oneshot
1490++ExecStart=/usr/sbin/netplan rebind --debug engreen
1491++''', 'apply.service': '''[Unit]
1492++Description=Apply SR-IOV configuration
1493++DefaultDependencies=no
1494++Before=network-pre.target
1495+ After=sys-subsystem-net-devices-engreen.device
1496+
1497+ [Service]
1498+@@ -1160,18 +1187,18 @@ ExecStart=/usr/sbin/netplan apply --sriov-only
1499+ Description=(Re-)bind SR-IOV Virtual Functions to their driver
1500+ After=network.target
1501+ After=netplan-sriov-apply.service
1502+-After=sys-subsystem-net-devices-enblue.device
1503+ After=sys-subsystem-net-devices-engreen.device
1504++After=sys-subsystem-net-devices-enblue.device
1505+
1506+ [Service]
1507+ Type=oneshot
1508+-ExecStart=/usr/sbin/netplan rebind --debug enblue engreen
1509++ExecStart=/usr/sbin/netplan rebind --debug engreen enblue
1510+ ''', 'apply.service': '''[Unit]
1511+ Description=Apply SR-IOV configuration
1512+ DefaultDependencies=no
1513+ Before=network-pre.target
1514+-After=sys-subsystem-net-devices-enblue.device
1515+ After=sys-subsystem-net-devices-engreen.device
1516++After=sys-subsystem-net-devices-enblue.device
1517+
1518+ [Service]
1519+ Type=oneshot
1520+@@ -1264,7 +1291,7 @@ ExecStart=/usr/sbin/netplan apply --sriov-only
1521+ version: 2
1522+ ethernets:
1523+ engreen:
1524+- embedded-switch-mode: legacy''', expect_fail=True)
1525++ delay-virtual-functions-rebind: true''', expect_fail=True)
1526+ self.assertIn("This is not a SR-IOV PF", err)
1527+
1528+ def test_invalid_eswitch_mode(self):
1529diff --git a/debian/patches/lp2020409/0026-cli-sriov-refactoring.patch b/debian/patches/lp2020409/0026-cli-sriov-refactoring.patch
1530new file mode 100644
1531index 0000000..97ea245
1532--- /dev/null
1533+++ b/debian/patches/lp2020409/0026-cli-sriov-refactoring.patch
1534@@ -0,0 +1,768 @@
1535+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
1536+Date: Thu, 11 Apr 2024 15:52:44 +0100
1537+Subject: cli/sriov: refactoring
1538+
1539+Refactor get_vf_count_and_functions() in 3 different ones. It was doing
1540+too many things and was hard to read. Three of its 5 parameters were being
1541+used as output and one of them was being changed by
1542+_get_target_interface() in the same call chain.
1543+
1544+The new _get_physical_functions() will also return interfaces that only
1545+have the property embedded_switch_mode. This is a requirement to enable
1546+netplan to change the eswitch mode even if it doesn't have VFs.
1547+See LP: #2020409
1548+
1549+Adapt the unit tests to the new functions and implement new tests.
1550+
1551+Bug-Ubuntu: https://bugs.launchpad.net/netplan/+bug/2020409
1552+Origin: https://github.com/canonical/netplan/pull/454
1553+---
1554+ netplan_cli/cli/sriov.py | 149 ++++++++++++++--------
1555+ tests/test_sriov.py | 314 ++++++++++++++++++++++++++++++++++++++---------
1556+ 2 files changed, 351 insertions(+), 112 deletions(-)
1557+
1558+diff --git a/netplan_cli/cli/sriov.py b/netplan_cli/cli/sriov.py
1559+index 4f3448c..3e03695 100644
1560+--- a/netplan_cli/cli/sriov.py
1561++++ b/netplan_cli/cli/sriov.py
1562+@@ -21,8 +21,7 @@ import logging
1563+ import os
1564+ import subprocess
1565+ import typing
1566+-
1567+-from collections import defaultdict
1568++from typing import Dict, List, Optional, Set
1569+
1570+ from . import utils
1571+ from ..configmanager import ConfigurationError
1572+@@ -212,37 +211,47 @@ def unbind_vfs(vfs: typing.Iterable[PCIDevice], driver) -> typing.Iterable[PCIDe
1573+ return unbound_vfs
1574+
1575+
1576+-def _get_target_interface(interfaces, np_state, pf_link, pfs):
1577+- if pf_link not in pfs:
1578+- # handle the match: syntax, get the actual device name
1579+- pf_dev = np_state[pf_link]
1580+- if pf_dev._has_match:
1581+- # now here it's a bit tricky
1582+- set_name = pf_dev.set_name
1583+- if set_name and set_name in interfaces:
1584+- # if we had a match: stanza and set-name: this means we should
1585+- # assume that, if found, the interface has already been
1586+- # renamed - use the new name
1587+- pfs[pf_link] = set_name
1588+- else:
1589+- for interface in interfaces:
1590+- if not pf_dev._match_interface(
1591+- iface_name=interface,
1592+- iface_driver=utils.get_interface_driver_name(interface),
1593+- iface_mac=utils.get_interface_macaddress(interface)):
1594+- continue
1595+- # we have a matching PF
1596+- # store the matching interface in the dictionary of
1597+- # active PFs, but error out if we matched more than one
1598+- if pf_link in pfs:
1599+- raise ConfigurationError('matched more than one interface for a PF device: %s' % pf_link)
1600+- pfs[pf_link] = interface
1601+- else:
1602+- # no match field, assume entry name is the interface name
1603+- if pf_link in interfaces:
1604+- pfs[pf_link] = pf_link
1605++def _interface_matches(netdef: netplan.NetDefinition, interface: str) -> bool:
1606++ return netdef._match_interface(
1607++ iface_name=interface,
1608++ iface_driver=utils.get_interface_driver_name(interface),
1609++ iface_mac=utils.get_interface_macaddress(interface))
1610+
1611+- return pfs.get(pf_link, None)
1612++
1613++def _get_interface_name_for_netdef(netdef: netplan.NetDefinition) -> Optional[str]:
1614++ """
1615++ Try to match a netdef with the real system network interface.
1616++ Throws ConfigurationError if there is more than one match.
1617++ """
1618++ interfaces: List[str] = netifaces.interfaces()
1619++ if netdef._has_match:
1620++ # now here it's a bit tricky
1621++ set_name: str = netdef.set_name
1622++ if set_name and set_name in interfaces:
1623++ # if we had a match: stanza and set-name: this means we should
1624++ # assume that, if found, the interface has already been
1625++ # renamed - use the new name
1626++ return set_name
1627++ else:
1628++ matches: Set[str] = set()
1629++ # we walk through all the system interfaces to determine if there is
1630++ # more than one matched interface
1631++ for interface in interfaces:
1632++ if not _interface_matches(netdef, interface):
1633++ continue
1634++ # we have a matching PF
1635++ # error out if we matched more than one
1636++ if len(matches) > 1:
1637++ raise ConfigurationError('matched more than one interface for a PF device: %s' % netdef.id)
1638++ matches.add(interface)
1639++ if matches:
1640++ return list(matches)[0]
1641++ else:
1642++ # no match field, assume entry name is the interface name
1643++ if netdef.id in interfaces:
1644++ return netdef.id
1645++
1646++ return None
1647+
1648+
1649+ def _get_pci_slot_name(netdev):
1650+@@ -262,27 +271,67 @@ def _get_pci_slot_name(netdev):
1651+ raise RuntimeError('failed parsing PCI slot name for %s: %s' % (netdev, str(e)))
1652+
1653+
1654+-def get_vf_count_and_functions(interfaces, np_state,
1655+- vf_counts, vfs, pfs):
1656++def _get_physical_functions(np_state: netplan.State) -> Dict[str, str]:
1657+ """
1658+ Go through the list of netplan ethernet devices and identify which are
1659+- PFs and VFs, matching the former with actual networking interfaces.
1660+- Count how many VFs each PF will need.
1661++ PFs matching them with actual network interfaces.
1662+ """
1663+- for nid, netdef in np_state.ethernets.items():
1664+- if netdef.links.get('sriov') and _get_target_interface(interfaces, np_state, netdef.links.get('sriov').id, pfs):
1665+- vfs[nid] = None
1666++ pfs = {}
1667++ for netdef in np_state.ethernets.values():
1668++ # If the sriov_link is present, the interface is a VF and link is the PF
1669++ if link := netdef.links.get('sriov'):
1670++ if iface := _get_interface_name_for_netdef(np_state[link.id]):
1671++ pfs[link.id] = iface
1672++ else:
1673++ # If a netdef also defines the embedded_switch_mode key we consider it's a PF
1674++ # This enables us to change the eswitch mode even when the PF has no VFs.
1675++ if netdef._embedded_switch_mode:
1676++ if iface := _get_interface_name_for_netdef(netdef):
1677++ pfs[netdef.id] = iface
1678++
1679++ # If the netdef has any (positive) number of VFs that's because it's a PF
1680++ try:
1681++ count = netdef._vf_count
1682++ except netplan.NetplanException as e:
1683++ raise ConfigurationError(str(e))
1684++ if count > 0:
1685++ if iface := _get_interface_name_for_netdef(netdef):
1686++ pfs[netdef.id] = iface
1687++
1688++ return pfs
1689+
1690++
1691++def _get_vf_number_per_pf(np_state: netplan.State) -> Dict[str, int]:
1692++ """
1693++ Go through the list of netplan ethernet devices and identify which ones
1694++ have VFs. netdef._vf_count ultimately calls _netplan_state_get_vf_count_for_def
1695++ from libnetplan which return MAX(sriov_explicit_vf_count, number of VF netdefs).
1696++ """
1697++ vf_counts = {}
1698++ for netdef in np_state.ethernets.values():
1699+ try:
1700+ count = netdef._vf_count
1701+ except netplan.NetplanException as e:
1702+ raise ConfigurationError(str(e))
1703+- if count == 0:
1704+- continue
1705++ if count > 0:
1706++ if iface := _get_interface_name_for_netdef(netdef):
1707++ vf_counts[iface] = count
1708+
1709+- pf = _get_target_interface(interfaces, np_state, nid, pfs)
1710+- if pf:
1711+- vf_counts[pf] = count
1712++ return vf_counts
1713++
1714++
1715++def _get_virtual_functions(np_state: netplan.State) -> Set[str]:
1716++ """
1717++ Go through the list of netplan ethernet devices and identify which ones
1718++ are virtual functions
1719++ """
1720++ vfs = set()
1721++ for netdef in np_state.ethernets.values():
1722++ # If the sriov_link is present and the PF is also present in the system we save the VF
1723++ if link := netdef.links.get('sriov'):
1724++ if _get_interface_name_for_netdef(np_state[link.id]):
1725++ vfs.add(netdef.id)
1726++ return vfs
1727+
1728+
1729+ def set_numvfs_for_pf(pf, vf_count):
1730+@@ -419,14 +468,11 @@ def apply_sriov_config(config_manager, rootdir='/'):
1731+ # pointing to an PF. So let's browse through all ethernet devices,
1732+ # find all that are VFs and count how many of those are linked to
1733+ # particular PFs, as we need to then set the numvfs for each.
1734+- vf_counts = defaultdict(int)
1735++ vf_counts = _get_vf_number_per_pf(np_state)
1736+ # we also store all matches between VF/PF netplan entry names and
1737+ # interface that they're currently matching to
1738+- vfs = {}
1739+- pfs = {}
1740+-
1741+- get_vf_count_and_functions(
1742+- interfaces, np_state, vf_counts, vfs, pfs)
1743++ vfs_set = _get_virtual_functions(np_state)
1744++ pfs = _get_physical_functions(np_state)
1745+
1746+ # setup the required number of VFs per PF
1747+ # at the same time store which PFs got changed in case the NICs
1748+@@ -454,7 +500,8 @@ def apply_sriov_config(config_manager, rootdir='/'):
1749+ # entries to existing interfaces, otherwise we won't be able to set
1750+ # filtered VLANs for those.
1751+ # XXX: does matching those even make sense?
1752+- for vf in vfs:
1753++ vfs = {}
1754++ for vf in vfs_set:
1755+ netdef = np_state[vf]
1756+ if netdef._has_match:
1757+ # right now we only match by name, as I don't think matching per
1758+diff --git a/tests/test_sriov.py b/tests/test_sriov.py
1759+index 670e67e..c4ffd4f 100644
1760+--- a/tests/test_sriov.py
1761++++ b/tests/test_sriov.py
1762+@@ -23,7 +23,6 @@ import tempfile
1763+ import unittest
1764+
1765+ from subprocess import CalledProcessError
1766+-from collections import defaultdict
1767+ from unittest.mock import patch, mock_open, call
1768+ from netplan_cli.cli.commands.sriov_rebind import INTERVAL_SEC, MAX_WAITING_TIME_SEC, NetplanSriovRebind
1769+
1770+@@ -60,15 +59,6 @@ class MockSRIOVOpen():
1771+ self.open.return_value.write.side_effect = sriov_write
1772+
1773+
1774+-def mock_set_counts(interfaces, config_manager, vf_counts, active_vfs, active_pfs):
1775+- counts = {'enp1': 2, 'enp2': 1}
1776+- vfs = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
1777+- pfs = {'enp1': 'enp1', 'enpx': 'enp2'}
1778+- vf_counts.update(counts)
1779+- active_vfs.update(vfs)
1780+- active_pfs.update(pfs)
1781+-
1782+-
1783+ class TestSRIOV(unittest.TestCase):
1784+ def setUp(self):
1785+ self.workdir = tempfile.TemporaryDirectory()
1786+@@ -146,13 +136,15 @@ class TestSRIOV(unittest.TestCase):
1787+ for i in range(len(vfs)):
1788+ os.symlink(os.path.join('../../..', vfs[i][1]), os.path.join(pf_dev_path, 'virtfn'+str(i)))
1789+
1790++ @patch('netifaces.interfaces')
1791+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
1792+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
1793+- def test_get_vf_count_and_functions(self, gim, gidn):
1794++ def test_get_vf_count_vfs_and_pfs(self, gim, gidn, ifaces):
1795+ # we mock-out get_interface_driver_name and get_interface_macaddress
1796+ # to return useful values for the test
1797+ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
1798+ gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar'
1799++ ifaces.return_value = ['enp1', 'enp2', 'enp3', 'enp5', 'enp0', 'enp8', 'enp10']
1800+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1801+ print('''network:
1802+ version: 2
1803+@@ -175,6 +167,8 @@ class TestSRIOV(unittest.TestCase):
1804+ enp8:
1805+ virtual-function-count: 7
1806+ enp9: {}
1807++ enp10:
1808++ embedded-switch-mode: switchdev
1809+ wlp6s0: {}
1810+ enp1s16f1:
1811+ link: enp1
1812+@@ -195,35 +189,213 @@ class TestSRIOV(unittest.TestCase):
1813+ link: enp9
1814+ ''', file=fd)
1815+ self.configmanager.parse()
1816+- interfaces = ['enp1', 'enp2', 'enp3', 'enp5', 'enp0', 'enp8']
1817+- vf_counts = defaultdict(int)
1818+- vfs = {}
1819+- pfs = {}
1820+
1821+- # call the function under test
1822+- sriov.get_vf_count_and_functions(interfaces, self.configmanager.np_state,
1823+- vf_counts, vfs, pfs)
1824++ vf_counts = sriov._get_vf_number_per_pf(self.configmanager.np_state)
1825++ vfs = sriov._get_virtual_functions(self.configmanager.np_state)
1826++ pfs = sriov._get_physical_functions(self.configmanager.np_state)
1827++
1828+ # check if the right vf counts have been recorded in vf_counts
1829+ self.assertDictEqual(
1830+ vf_counts,
1831+ {'enp1': 2, 'enp2': 2, 'enp3': 1, 'enp5': 1, 'enp8': 7})
1832+ # also check if the vfs and pfs dictionaries got properly set
1833+- self.assertDictEqual(
1834++ self.assertSetEqual(
1835+ vfs,
1836+- {'enp1s16f1': None, 'enp1s16f2': None, 'enp2s16f1': None,
1837+- 'enp2s16f2': None, 'enp3s16f1': None, 'enpxs16f1': None})
1838++ {'enp1s16f1', 'enp1s16f2', 'enp2s16f1',
1839++ 'enp2s16f2', 'enp3s16f1', 'enpxs16f1'})
1840++ self.assertDictEqual(
1841++ pfs,
1842++ {'enp1': 'enp1', 'enp2': 'enp2', 'enp3': 'enp3',
1843++ 'enpx': 'enp5', 'enp8': 'enp8', 'enp10': 'enp10'})
1844++
1845++ @patch('netifaces.interfaces')
1846++ @patch('netplan_cli.cli.utils.get_interface_driver_name')
1847++ @patch('netplan_cli.cli.utils.get_interface_macaddress')
1848++ def test_get_physical_functions(self, gim, gidn, ifaces):
1849++ # we mock-out get_interface_driver_name and get_interface_macaddress
1850++ # to return useful values for the test
1851++ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
1852++ gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar'
1853++ ifaces.return_value = ['enp1', 'enp2', 'enp3', 'enp5', 'enp0', 'enp8', 'enp10']
1854++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1855++ print('''network:
1856++ version: 2
1857++ renderer: networkd
1858++ ethernets:
1859++ renderer: networkd
1860++ enp1:
1861++ mtu: 9000
1862++ enp2:
1863++ match:
1864++ driver: foo
1865++ enp3:
1866++ match:
1867++ macaddress: 00:01:02:03:04:05
1868++ enpx:
1869++ match:
1870++ name: enp[4-5]
1871++ enp0:
1872++ mtu: 9000
1873++ enp8:
1874++ virtual-function-count: 7
1875++ enp9: {}
1876++ enp10:
1877++ embedded-switch-mode: switchdev
1878++ wlp6s0: {}
1879++ enp1s16f1:
1880++ link: enp1
1881++ macaddress: 01:02:03:04:05:00
1882++ enp1s16f2:
1883++ link: enp1
1884++ macaddress: 01:02:03:04:05:01
1885++ enp2s16f1:
1886++ link: enp2
1887++ enp2s16f2: {link: enp2}
1888++ enp3s16f1:
1889++ link: enp3
1890++ enpxs16f1:
1891++ match:
1892++ name: enp[4-5]s16f1
1893++ link: enpx
1894++ enp9s16f1:
1895++ link: enp9
1896++''', file=fd)
1897++ self.configmanager.parse()
1898++
1899++ pfs = sriov._get_physical_functions(self.configmanager.np_state)
1900++
1901+ self.assertDictEqual(
1902+ pfs,
1903+ {'enp1': 'enp1', 'enp2': 'enp2', 'enp3': 'enp3',
1904+- 'enpx': 'enp5', 'enp8': 'enp8'})
1905++ 'enpx': 'enp5', 'enp8': 'enp8', 'enp10': 'enp10'})
1906++
1907++ @patch('netifaces.interfaces')
1908++ @patch('netplan_cli.cli.utils.get_interface_driver_name')
1909++ @patch('netplan_cli.cli.utils.get_interface_macaddress')
1910++ def test_get_vf_number_per_pf(self, gim, gidn, ifaces):
1911++ # we mock-out get_interface_driver_name and get_interface_macaddress
1912++ # to return useful values for the test
1913++ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
1914++ gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar'
1915++ ifaces.return_value = ['enp1', 'enp2', 'enp3', 'enp5', 'enp0', 'enp8']
1916++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1917++ print('''network:
1918++ version: 2
1919++ renderer: networkd
1920++ ethernets:
1921++ renderer: networkd
1922++ enp1:
1923++ mtu: 9000
1924++ enp2:
1925++ match:
1926++ driver: foo
1927++ enp3:
1928++ match:
1929++ macaddress: 00:01:02:03:04:05
1930++ enpx:
1931++ match:
1932++ name: enp[4-5]
1933++ enp0:
1934++ mtu: 9000
1935++ enp8:
1936++ virtual-function-count: 7
1937++ enp9: {}
1938++ wlp6s0: {}
1939++ enp1s16f1:
1940++ link: enp1
1941++ macaddress: 01:02:03:04:05:00
1942++ enp1s16f2:
1943++ link: enp1
1944++ macaddress: 01:02:03:04:05:01
1945++ enp2s16f1:
1946++ link: enp2
1947++ enp2s16f2: {link: enp2}
1948++ enp3s16f1:
1949++ link: enp3
1950++ enpxs16f1:
1951++ match:
1952++ name: enp[4-5]s16f1
1953++ link: enpx
1954++ enp9s16f1:
1955++ link: enp9
1956++''', file=fd)
1957++ self.configmanager.parse()
1958++
1959++ vf_counts = sriov._get_vf_number_per_pf(self.configmanager.np_state)
1960++
1961++ # check if the right vf counts have been recorded in vf_counts
1962++ self.assertDictEqual(
1963++ vf_counts,
1964++ {'enp1': 2, 'enp2': 2, 'enp3': 1, 'enp5': 1, 'enp8': 7})
1965++
1966++ @patch('netifaces.interfaces')
1967++ @patch('netplan_cli.cli.utils.get_interface_driver_name')
1968++ @patch('netplan_cli.cli.utils.get_interface_macaddress')
1969++ def test_get_virtual_functions(self, gim, gidn, ifaces):
1970++ # we mock-out get_interface_driver_name and get_interface_macaddress
1971++ # to return useful values for the test
1972++ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
1973++ gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar'
1974++ ifaces.return_value = ['enp1', 'enp2', 'enp3', 'enp5', 'enp0', 'enp8']
1975++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
1976++ print('''network:
1977++ version: 2
1978++ renderer: networkd
1979++ ethernets:
1980++ renderer: networkd
1981++ enp1:
1982++ mtu: 9000
1983++ enp2:
1984++ match:
1985++ driver: foo
1986++ enp3:
1987++ match:
1988++ macaddress: 00:01:02:03:04:05
1989++ enpx:
1990++ match:
1991++ name: enp[4-5]
1992++ enp0:
1993++ mtu: 9000
1994++ enp8:
1995++ virtual-function-count: 7
1996++ enp9: {}
1997++ wlp6s0: {}
1998++ enp1s16f1:
1999++ link: enp1
2000++ macaddress: 01:02:03:04:05:00
2001++ enp1s16f2:
2002++ link: enp1
2003++ macaddress: 01:02:03:04:05:01
2004++ enp2s16f1:
2005++ link: enp2
2006++ enp2s16f2: {link: enp2}
2007++ enp3s16f1:
2008++ link: enp3
2009++ enpxs16f1:
2010++ match:
2011++ name: enp[4-5]s16f1
2012++ link: enpx
2013++ enp9s16f1:
2014++ link: enp9
2015++''', file=fd)
2016++ self.configmanager.parse()
2017++
2018++ vfs = sriov._get_virtual_functions(self.configmanager.np_state)
2019+
2020++ self.assertSetEqual(
2021++ vfs,
2022++ {'enp1s16f1', 'enp1s16f2', 'enp2s16f1',
2023++ 'enp2s16f2', 'enp3s16f1', 'enpxs16f1'})
2024++
2025++ @patch('netifaces.interfaces')
2026+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2027+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2028+- def test_get_vf_count_and_functions_set_name(self, gim, gidn):
2029++ def test_get_vf_count_vfs_and_pfs_set_name(self, gim, gidn, ifaces):
2030+ # we mock-out get_interface_driver_name and get_interface_macaddress
2031+ # to return useful values for the test
2032+ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
2033+ gidn.side_effect = lambda x: 'foo' if x == 'enp1' else 'bar'
2034++ ifaces.return_value = ['pf1', 'enp8', 'enp1s16f1']
2035+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2036+ print('''network:
2037+ version: 2
2038+@@ -244,14 +416,10 @@ class TestSRIOV(unittest.TestCase):
2039+ macaddress: 01:02:03:04:05:00
2040+ ''', file=fd)
2041+ self.configmanager.parse()
2042+- interfaces = ['pf1', 'enp8']
2043+- vf_counts = defaultdict(int)
2044+- vfs = {}
2045+- pfs = {}
2046++ vf_counts = sriov._get_vf_number_per_pf(self.configmanager.np_state)
2047++ vfs = sriov._get_virtual_functions(self.configmanager.np_state)
2048++ pfs = sriov._get_physical_functions(self.configmanager.np_state)
2049+
2050+- # call the function under test
2051+- sriov.get_vf_count_and_functions(interfaces, self.configmanager.np_state,
2052+- vf_counts, vfs, pfs)
2053+ # check if the right vf counts have been recorded in vf_counts -
2054+ # we expect netplan to take into consideration the renamed interface
2055+ # names here
2056+@@ -259,20 +427,22 @@ class TestSRIOV(unittest.TestCase):
2057+ vf_counts,
2058+ {'pf1': 1, 'enp8': 7})
2059+ # also check if the vfs and pfs dictionaries got properly set
2060+- self.assertDictEqual(
2061++ self.assertSetEqual(
2062+ vfs,
2063+- {'enp1s16f1': None})
2064++ {'enp1s16f1'})
2065+ self.assertDictEqual(
2066+ pfs,
2067+ {'enp1': 'pf1', 'enp8': 'enp8'})
2068+
2069++ @patch('netifaces.interfaces')
2070+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2071+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2072+- def test_get_vf_count_and_functions_many_match(self, gim, gidn):
2073++ def test_get_vf_count_vfs_and_pfs_many_match(self, gim, gidn, ifaces):
2074+ # we mock-out get_interface_driver_name and get_interface_macaddress
2075+ # to return useful values for the test
2076+ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
2077+ gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar'
2078++ ifaces.return_value = ['enp1', 'wlp6s0', 'enp2', 'enp3']
2079+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2080+ print('''network:
2081+ version: 2
2082+@@ -287,26 +457,23 @@ class TestSRIOV(unittest.TestCase):
2083+ link: enpx
2084+ ''', file=fd)
2085+ self.configmanager.parse()
2086+- interfaces = ['enp1', 'wlp6s0', 'enp2', 'enp3']
2087+- vf_counts = defaultdict(int)
2088+- vfs = {}
2089+- pfs = {}
2090+
2091+ # call the function under test
2092+ with self.assertRaises(ConfigurationError) as e:
2093+- sriov.get_vf_count_and_functions(interfaces, self.configmanager.np_state,
2094+- vf_counts, vfs, pfs)
2095++ _ = sriov._get_physical_functions(self.configmanager.np_state)
2096+
2097+ self.assertIn('matched more than one interface for a PF device: enpx',
2098+ str(e.exception))
2099+
2100++ @patch('netifaces.interfaces')
2101+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2102+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2103+- def test_get_vf_count_and_functions_not_enough_explicit(self, gim, gidn):
2104++ def test_get_vf_count_vfs_and_pfs_not_enough_explicit(self, gim, gidn, ifaces):
2105+ # we mock-out get_interface_driver_name and get_interface_macaddress
2106+ # to return useful values for the test
2107+ gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00'
2108+ gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar'
2109++ ifaces.return_value = ['enp1', 'wlp6s0']
2110+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2111+ print('''network:
2112+ version: 2
2113+@@ -324,15 +491,17 @@ class TestSRIOV(unittest.TestCase):
2114+ link: enp1
2115+ ''', file=fd)
2116+ self.configmanager.parse()
2117+- interfaces = ['enp1', 'wlp6s0']
2118+- vf_counts = defaultdict(int)
2119+- vfs = {}
2120+- pfs = {}
2121+
2122+ # call the function under test
2123+ with self.assertRaises(ConfigurationError) as e:
2124+- sriov.get_vf_count_and_functions(interfaces, self.configmanager.np_state,
2125+- vf_counts, vfs, pfs)
2126++ _ = sriov._get_vf_number_per_pf(self.configmanager.np_state)
2127++
2128++ self.assertIn('more VFs allocated than the explicit size declared: 3 > 2',
2129++ str(e.exception))
2130++
2131++ # _get_pkysical_functions() also might raise ConfigurationError()
2132++ with self.assertRaises(ConfigurationError) as e:
2133++ _ = sriov._get_physical_functions(self.configmanager.np_state)
2134+
2135+ self.assertIn('more VFs allocated than the explicit size declared: 3 > 2',
2136+ str(e.exception))
2137+@@ -479,14 +648,16 @@ class TestSRIOV(unittest.TestCase):
2138+ str(e.exception))
2139+
2140+ @patch('netifaces.interfaces')
2141+- @patch('netplan_cli.cli.sriov.get_vf_count_and_functions')
2142++ @patch('netplan_cli.cli.sriov._get_vf_number_per_pf')
2143++ @patch('netplan_cli.cli.sriov._get_virtual_functions')
2144++ @patch('netplan_cli.cli.sriov._get_physical_functions')
2145+ @patch('netplan_cli.cli.sriov.set_numvfs_for_pf')
2146+ @patch('netplan_cli.cli.sriov.perform_hardware_specific_quirks')
2147+ @patch('netplan_cli.cli.sriov.apply_vlan_filter_for_vf')
2148+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2149+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2150+ def test_apply_sriov_config(self, gim, gidn, apply_vlan, quirks,
2151+- set_numvfs, get_counts, netifs):
2152++ set_numvfs, get_phys, get_virt, get_num, netifs):
2153+ # set up the environment
2154+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2155+ print('''network:
2156+@@ -516,11 +687,14 @@ class TestSRIOV(unittest.TestCase):
2157+ # set up all the mock objects
2158+ netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0',
2159+ 'enp1s16f1', 'enp1s16f2', 'enp2s16f1']
2160+- get_counts.side_effect = mock_set_counts
2161+ set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True
2162+ gidn.return_value = 'foodriver'
2163+ gim.return_value = '00:01:02:03:04:05'
2164+
2165++ get_num.return_value = {'enp1': 2, 'enp2': 1}
2166++ get_virt.return_value = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
2167++ get_phys.return_value = {'enp1': 'enp1', 'enpx': 'enp2'}
2168++
2169+ # call method under test
2170+ sriov.apply_sriov_config(self.configmanager, rootdir=self.workdir.name)
2171+
2172+@@ -539,14 +713,16 @@ class TestSRIOV(unittest.TestCase):
2173+ apply_vlan.assert_called_once_with('enp2', 'enp2s16f1', 'vf1.15', 15)
2174+
2175+ @patch('netifaces.interfaces')
2176+- @patch('netplan_cli.cli.sriov.get_vf_count_and_functions')
2177++ @patch('netplan_cli.cli.sriov._get_vf_number_per_pf')
2178++ @patch('netplan_cli.cli.sriov._get_virtual_functions')
2179++ @patch('netplan_cli.cli.sriov._get_physical_functions')
2180+ @patch('netplan_cli.cli.sriov.set_numvfs_for_pf')
2181+ @patch('netplan_cli.cli.sriov.perform_hardware_specific_quirks')
2182+ @patch('netplan_cli.cli.sriov.apply_vlan_filter_for_vf')
2183+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2184+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2185+ def test_apply_sriov_config_invalid_vlan(self, gim, gidn, apply_vlan, quirks,
2186+- set_numvfs, get_counts, netifs):
2187++ set_numvfs, get_phys, get_virt, get_num, netifs):
2188+ # set up the environment
2189+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2190+ print('''network:
2191+@@ -575,7 +751,9 @@ class TestSRIOV(unittest.TestCase):
2192+ # set up all the mock objects
2193+ netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0',
2194+ 'enp1s16f1', 'enp1s16f2', 'enp2s16f1']
2195+- get_counts.side_effect = mock_set_counts
2196++ get_num.return_value = {'enp1': 2, 'enp2': 1}
2197++ get_virt.return_value = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
2198++ get_phys.return_value = {'enp1': 'enp1', 'enpx': 'enp2'}
2199+ set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True
2200+ gidn.return_value = 'foodriver'
2201+ gim.return_value = '00:01:02:03:04:05'
2202+@@ -607,14 +785,16 @@ class TestSRIOV(unittest.TestCase):
2203+ logs.output[0])
2204+
2205+ @patch('netifaces.interfaces')
2206+- @patch('netplan_cli.cli.sriov.get_vf_count_and_functions')
2207++ @patch('netplan_cli.cli.sriov._get_vf_number_per_pf')
2208++ @patch('netplan_cli.cli.sriov._get_virtual_functions')
2209++ @patch('netplan_cli.cli.sriov._get_physical_functions')
2210+ @patch('netplan_cli.cli.sriov.set_numvfs_for_pf')
2211+ @patch('netplan_cli.cli.sriov.perform_hardware_specific_quirks')
2212+ @patch('netplan_cli.cli.sriov.apply_vlan_filter_for_vf')
2213+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2214+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2215+ def test_apply_sriov_config_too_many_vlans(self, gim, gidn, apply_vlan, quirks,
2216+- set_numvfs, get_counts, netifs):
2217++ set_numvfs, get_phys, get_virt, get_num, netifs):
2218+ # set up the environment
2219+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2220+ print('''network:
2221+@@ -648,7 +828,9 @@ class TestSRIOV(unittest.TestCase):
2222+ # set up all the mock objects
2223+ netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0',
2224+ 'enp1s16f1', 'enp1s16f2', 'enp2s16f1']
2225+- get_counts.side_effect = mock_set_counts
2226++ get_num.return_value = {'enp1': 2, 'enp2': 1}
2227++ get_virt.return_value = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
2228++ get_phys.return_value = {'enp1': 'enp1', 'enpx': 'enp2'}
2229+ set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True
2230+ gidn.return_value = 'foodriver'
2231+ gim.return_value = '00:01:02:03:04:05'
2232+@@ -662,14 +844,16 @@ class TestSRIOV(unittest.TestCase):
2233+ self.assertEqual(apply_vlan.call_count, 1)
2234+
2235+ @patch('netifaces.interfaces')
2236+- @patch('netplan_cli.cli.sriov.get_vf_count_and_functions')
2237++ @patch('netplan_cli.cli.sriov._get_vf_number_per_pf')
2238++ @patch('netplan_cli.cli.sriov._get_virtual_functions')
2239++ @patch('netplan_cli.cli.sriov._get_physical_functions')
2240+ @patch('netplan_cli.cli.sriov.set_numvfs_for_pf')
2241+ @patch('netplan_cli.cli.sriov.perform_hardware_specific_quirks')
2242+ @patch('netplan_cli.cli.sriov.apply_vlan_filter_for_vf')
2243+ @patch('netplan_cli.cli.utils.get_interface_driver_name')
2244+ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2245+ def test_apply_sriov_config_many_match(self, gim, gidn, apply_vlan, quirks,
2246+- set_numvfs, get_counts, netifs):
2247++ set_numvfs, get_phys, get_virt, get_num, netifs):
2248+ # set up the environment
2249+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2250+ print('''network:
2251+@@ -694,7 +878,9 @@ class TestSRIOV(unittest.TestCase):
2252+ # set up all the mock objects
2253+ netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0',
2254+ 'enp1s16f1', 'enp1s16f2', 'enp2s16f1']
2255+- get_counts.side_effect = mock_set_counts
2256++ get_num.return_value = {'enp1': 2, 'enp2': 1}
2257++ get_virt.return_value = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
2258++ get_phys.return_value = {'enp1': 'enp1', 'enpx': 'enp2'}
2259+ set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True
2260+ gidn.return_value = 'foodriver'
2261+ gim.return_value = '00:01:02:03:04:05'
2262+@@ -733,8 +919,11 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
2263+ open(os.path.join(self.workdir.name, 'sys_mock/bus/pci/devices/0000:00:1f.6/physfn'), 'a').close()
2264+ self.assertTrue(pcidev.is_vf)
2265+
2266++ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2267+ @patch('netifaces.interfaces')
2268+- @patch('netplan_cli.cli.sriov.get_vf_count_and_functions')
2269++ @patch('netplan_cli.cli.sriov._get_vf_number_per_pf')
2270++ @patch('netplan_cli.cli.sriov._get_virtual_functions')
2271++ @patch('netplan_cli.cli.sriov._get_physical_functions')
2272+ @patch('netplan_cli.cli.sriov.set_numvfs_for_pf')
2273+ @patch('netplan_cli.cli.sriov.perform_hardware_specific_quirks')
2274+ @patch('subprocess.check_call')
2275+@@ -743,7 +932,7 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
2276+ @patch('netplan_cli.cli.sriov.PCIDevice.devlink_eswitch_mode')
2277+ @patch('netplan_cli.cli.sriov._get_pci_slot_name')
2278+ def test_apply_sriov_config_eswitch_mode(self, gpsn, pcidevice_devlink, pcidevice_sys, pcidevice_bound,
2279+- scc, quirks, set_numvfs, get_counts, netifs):
2280++ scc, quirks, set_numvfs, get_phys, get_virt, get_num, netifs, gima):
2281+ handle = mock_open()
2282+ builtin_open = open # save the unpatched version of open()
2283+
2284+@@ -775,6 +964,7 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
2285+ True, True, # 2x unbind (enp1 VFs)
2286+ True, True, True, True, # 4x unbind (enpx/enp2 VFs)
2287+ False, False, False, False] # 4x re-bind (enpx/enp2 VFs)
2288++ gima.return_value = '00:11:22:33:44:55'
2289+
2290+ # YAML config
2291+ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2292+@@ -802,7 +992,9 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
2293+ # set up all the mock objects
2294+ netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0',
2295+ 'enp1s16f1', 'enp1s16f2', 'enp2s16f1']
2296+- get_counts.side_effect = mock_set_counts
2297++ get_num.return_value = {'enp1': 2, 'enp2': 1}
2298++ get_virt.return_value = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
2299++ get_phys.return_value = {'enp1': 'enp1', 'enpx': 'enp2'}
2300+ writes = [
2301+ ('/sys/bus/pci/drivers/mlx5_core/unbind', '0000:03:00.2'),
2302+ ('/sys/bus/pci/drivers/mlx5_core/unbind', '0000:03:00.3'),
2303diff --git a/debian/patches/lp2020409/0027-cli-sriov-set-eswitch-regardless-of-pcidev.vfs.patch b/debian/patches/lp2020409/0027-cli-sriov-set-eswitch-regardless-of-pcidev.vfs.patch
2304new file mode 100644
2305index 0000000..6acce65
2306--- /dev/null
2307+++ b/debian/patches/lp2020409/0027-cli-sriov-set-eswitch-regardless-of-pcidev.vfs.patch
2308@@ -0,0 +1,132 @@
2309+From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
2310+Date: Tue, 16 Apr 2024 10:26:22 +0100
2311+Subject: cli/sriov: set eswitch regardless of pcidev.vfs
2312+
2313+The call to pcidev.devlink_set was still inside a check for the
2314+existence of VFs. Move it to outside of it.
2315+
2316+Add a test to cover the case where the unbind_vfs operation fails.
2317+
2318+Bug-Ubuntu: https://bugs.launchpad.net/netplan/+bug/2020409
2319+Origin: https://github.com/canonical/netplan/pull/454
2320+---
2321+ netplan_cli/cli/sriov.py | 14 +++++----
2322+ tests/test_sriov.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
2323+ 2 files changed, 87 insertions(+), 5 deletions(-)
2324+
2325+diff --git a/netplan_cli/cli/sriov.py b/netplan_cli/cli/sriov.py
2326+index 3e03695..0e1c6ef 100644
2327+--- a/netplan_cli/cli/sriov.py
2328++++ b/netplan_cli/cli/sriov.py
2329+@@ -529,13 +529,17 @@ def apply_sriov_config(config_manager, rootdir='/'):
2330+ if pcidev.is_pf:
2331+ logging.debug("Found VFs of {}: {}".format(pcidev, pcidev.vf_addrs))
2332+ if pcidev.vfs:
2333+- rebind_delayed = netdef._delay_virtual_functions_rebind
2334+ try:
2335+ unbind_vfs(pcidev.vfs, pcidev.driver)
2336+- pcidev.devlink_set('eswitch', 'mode', eswitch_mode)
2337+- finally:
2338+- if not rebind_delayed:
2339+- bind_vfs(pcidev.vfs, pcidev.driver)
2340++ except Exception as e:
2341++ logging.warning(f'Unbinding of VFs for {netdef_id} failed: {str(e)}')
2342++
2343++ logging.debug(f'Changing eswitch mode from {current_eswitch_mode_system} to {eswitch_mode} for: {netdef_id}')
2344++ pcidev.devlink_set('eswitch', 'mode', eswitch_mode)
2345++
2346++ if pcidev.vfs:
2347++ if not netdef._delay_virtual_functions_rebind:
2348++ bind_vfs(pcidev.vfs, pcidev.driver)
2349+
2350+ filtered_vlans_set = set()
2351+ for vlan, netdef in np_state.vlans.items():
2352+diff --git a/tests/test_sriov.py b/tests/test_sriov.py
2353+index c4ffd4f..b2577aa 100644
2354+--- a/tests/test_sriov.py
2355++++ b/tests/test_sriov.py
2356+@@ -1021,6 +1021,84 @@ MODALIAS=pci:v00008086d0000156Fsv000017AAsd00002245bc02sc00i00
2357+ call(['/sbin/devlink', 'dev', 'eswitch', 'set', 'pci/0000:03:00.1', 'mode', 'switchdev'])
2358+ ])
2359+
2360++ @patch('netplan_cli.cli.sriov.unbind_vfs')
2361++ @patch('netplan_cli.cli.utils.get_interface_macaddress')
2362++ @patch('netifaces.interfaces')
2363++ @patch('netplan_cli.cli.sriov._get_vf_number_per_pf')
2364++ @patch('netplan_cli.cli.sriov._get_virtual_functions')
2365++ @patch('netplan_cli.cli.sriov._get_physical_functions')
2366++ @patch('netplan_cli.cli.sriov.set_numvfs_for_pf')
2367++ @patch('netplan_cli.cli.sriov.perform_hardware_specific_quirks')
2368++ @patch('subprocess.check_call')
2369++ @patch('netplan_cli.cli.sriov.PCIDevice.bound', new_callable=unittest.mock.PropertyMock)
2370++ @patch('netplan_cli.cli.sriov.PCIDevice.sys', new_callable=unittest.mock.PropertyMock)
2371++ @patch('netplan_cli.cli.sriov.PCIDevice.devlink_eswitch_mode')
2372++ @patch('netplan_cli.cli.sriov._get_pci_slot_name')
2373++ def test_apply_sriov_config_eswitch_mode_unbind_failed(self, gpsn, pcidevice_devlink, pcidevice_sys, pcidevice_bound,
2374++ scc, quirks, set_numvfs, get_phys, get_virt, get_num, netifs,
2375++ gima, ubind):
2376++ # set up the mock sysfs environment
2377++ self._prepare_sysfs_dir_structure(pf=('enp1', '0000:03:00.0'),
2378++ vfs=[('enp1s16f1', '0000:03:00.2'),
2379++ ('enp1s16f2', '0000:03:00.3')],
2380++ pf_driver='mlx5_core')
2381++ self._prepare_sysfs_dir_structure(pf=('enp2', '0000:03:00.1'),
2382++ vfs=[('enp2s14f1', '0000:03:08.2'),
2383++ ('enp2s15f1', '0000:03:08.3'),
2384++ ('enp2s16f1', '0000:03:08.4'),
2385++ ('enp2s17f1', '0000:03:08.5')],
2386++ pf_driver='mlx5_core')
2387++ enp1_pci_addr = '0000:03:00.0'
2388++ enp2_pci_addr = '0000:03:00.1'
2389++ gpsn.side_effect = lambda iface: enp1_pci_addr if iface == 'enp1' else enp2_pci_addr
2390++ sys_path = os.path.join(self.workdir.name, 'sys')
2391++ pcidevice_devlink.return_value = '__undetermined'
2392++ pcidevice_sys.return_value = sys_path
2393++ pcidevice_bound.side_effect = [
2394++ True, True, # 2x unbind (enp1 VFs)
2395++ True, True, True, True, # 4x unbind (enpx/enp2 VFs)
2396++ False, False, False, False] # 4x re-bind (enpx/enp2 VFs)
2397++ gima.return_value = '00:11:22:33:44:55'
2398++
2399++ # YAML config
2400++ with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd:
2401++ print('''network:
2402++ version: 2
2403++ renderer: networkd
2404++ ethernets:
2405++ enp1:
2406++ embedded-switch-mode: "legacy"
2407++ delay-virtual-functions-rebind: true
2408++ enpx:
2409++ match:
2410++ name: enp[2-3]
2411++ embedded-switch-mode: "switchdev"
2412++ enp1s16f1:
2413++ link: enp1
2414++ enp1s16f2:
2415++ link: enp1
2416++ customvf1:
2417++ match:
2418++ name: enp[2-3]s16f[1-4]
2419++ link: enpx
2420++''', file=fd)
2421++
2422++ # set up all the mock objects
2423++ netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0',
2424++ 'enp1s16f1', 'enp1s16f2', 'enp2s16f1']
2425++ get_num.return_value = {'enp1': 2, 'enp2': 1}
2426++ get_virt.return_value = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None}
2427++ get_phys.return_value = {'enp1': 'enp1', 'enpx': 'enp2'}
2428++
2429++ ubind.side_effect = Exception('some IO error related to mlx5_core/unbind')
2430++
2431++ # test success case
2432++ with patch('logging.warning') as log:
2433++ sriov.apply_sriov_config(self.configmanager, rootdir=self.workdir.name)
2434++ log.assert_has_calls([
2435++ call('Unbinding of VFs for enp1 failed: some IO error related to mlx5_core/unbind'),
2436++ call('Unbinding of VFs for enpx failed: some IO error related to mlx5_core/unbind')])
2437++
2438+ @patch('netplan_cli.cli.sriov.PCIDevice.bound', new_callable=unittest.mock.PropertyMock)
2439+ @patch('netplan_cli.cli.sriov.PCIDevice.sys', new_callable=unittest.mock.PropertyMock)
2440+ @patch('netplan_cli.cli.commands.sriov_rebind._get_pci_slot_name')
2441diff --git a/debian/patches/series b/debian/patches/series
2442index 0f33be3..e3cc142 100644
2443--- a/debian/patches/series
2444+++ b/debian/patches/series
2445@@ -15,3 +15,15 @@ lp2066258/0015-backends-escape-file-paths.patch
2446 lp2066258/0016-backends-escape-semicolons-in-service-units.patch
2447 0017-emitter-allow-unicode-characters-in-the-emitter.patch
2448 0018-parse-do-not-escape-all-non-ascii-bytes.patch
2449+
2450+# SR-IOV improvements
2451+lp1988018/0018-libnetplan-add-a-getter-for-bond-mode.patch
2452+lp1988018/0019-sriov-move-the-udev-logic-to-a-service-unit.patch
2453+lp1988018/0020-sriov-check-the-eswitch-mode-before-trying-to-change.patch
2454+lp1988018/0021-sriov_rebind-cooperate-with-VF-LAG-activation.patch
2455+lp1988018/0022-sriov_rebind-netplan-rebind-debug-setup.patch
2456+lp1988018/0023-tests-sriov-adapt-tests-to-the-last-sr-iov-related-c.patch
2457+lp1988018/0024-sriov_apply-execute-apply-sriov-only-before-network-.patch
2458+lp2020409/0025-sriov-accept-setting-the-eswitch-mode-without-VFs.patch
2459+lp2020409/0026-cli-sriov-refactoring.patch
2460+lp2020409/0027-cli-sriov-set-eswitch-regardless-of-pcidev.vfs.patch

Subscribers

People subscribed via source and target branches