Merge ~danilogondolfo/netplan/+git/ubuntu:jammy_sriov_sru into ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu-jammy
- Git
- lp:~danilogondolfo/netplan/+git/ubuntu
- jammy_sriov_sru
- Merge into ubuntu-jammy
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) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lukas Märdian | Approve | ||
Ubuntu Core Development Team | Pending | ||
Review via email: mp+474659@code.launchpad.net |
Commit message
Description of the change
This MR incorporates the PRs below into Jammy
https:/
https:/
It was made necessary after a recent request https:/
A PPA for Jammy is available here https:/
Danilo Egea Gondolfo (danilogondolfo) : | # |
Danilo Egea Gondolfo (danilogondolfo) : | # |
Lukas Märdian (slyon) wrote : | # |
Danilo Egea Gondolfo (danilogondolfo) wrote : | # |
PR#454 was cherry picked into netplan 1.0.1, which is in Noble :)
https:/
https:/
Danilo Egea Gondolfo (danilogondolfo) wrote : | # |
I split the SRU paperwork. Each bug has its own description and test cases:
https:/
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.
Lukas Märdian (slyon) : | # |
Lukas Märdian (slyon) wrote : | # |
thanks for the .symbol version string fixes.
See https:/
Preview Diff
1 | diff --git a/debian/changelog b/debian/changelog |
2 | index 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): |
21 | diff --git a/debian/libnetplan0.symbols b/debian/libnetplan0.symbols |
22 | index 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 |
33 | diff --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 |
34 | new file mode 100644 |
35 | index 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) |
154 | diff --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 |
155 | new file mode 100644 |
156 | index 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; |
323 | diff --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 |
324 | new file mode 100644 |
325 | index 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(): |
429 | diff --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 |
430 | new file mode 100644 |
431 | index 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() |
607 | diff --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 |
608 | new file mode 100644 |
609 | index 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) |
726 | diff --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 |
727 | new file mode 100644 |
728 | index 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): |
1286 | diff --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 |
1287 | new file mode 100644 |
1288 | index 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 |
1375 | diff --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 |
1376 | new file mode 100644 |
1377 | index 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): |
1529 | diff --git a/debian/patches/lp2020409/0026-cli-sriov-refactoring.patch b/debian/patches/lp2020409/0026-cli-sriov-refactoring.patch |
1530 | new file mode 100644 |
1531 | index 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'), |
2303 | diff --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 |
2304 | new file mode 100644 |
2305 | index 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') |
2441 | diff --git a/debian/patches/series b/debian/patches/series |
2442 | index 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 |
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