]> git.proxmox.com Git - ifupdown2.git/blame - debian/patches/pve/0008-add-openvswitch-addon.patch
openvswitch: don't remove tap|veth interfaces when adding an internal ovs port
[ifupdown2.git] / debian / patches / pve / 0008-add-openvswitch-addon.patch
CommitLineData
1aa30813 1From 4311f4deb9b95e67694c04ced13782a3608a176b Mon Sep 17 00:00:00 2001
bf9a532c
AD
2From: Alexandre Derumier <aderumier@odiso.com>
3Date: Mon, 17 Feb 2020 13:32:18 +0100
4Subject: [PATCH] add openvswitch addon
5
1aa30813 6Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
bf9a532c
AD
7---
8 etc/network/ifupdown2/addons.conf | 4 +
1aa30813 9 ifupdown2/addons/openvswitch.py | 248 ++++++++++++++++++++++++
bf9a532c
AD
10 ifupdown2/addons/openvswitch_port.py | 274 +++++++++++++++++++++++++++
11 ifupdown2/lib/iproute2.py | 3 +
12 ifupdown2/nlmanager/nlpacket.py | 1 +
1aa30813 13 5 files changed, 530 insertions(+)
bf9a532c
AD
14 create mode 100644 ifupdown2/addons/openvswitch.py
15 create mode 100644 ifupdown2/addons/openvswitch_port.py
16
17diff --git a/etc/network/ifupdown2/addons.conf b/etc/network/ifupdown2/addons.conf
1aa30813 18index c43d377..8811cc2 100644
bf9a532c
AD
19--- a/etc/network/ifupdown2/addons.conf
20+++ b/etc/network/ifupdown2/addons.conf
21@@ -1,3 +1,5 @@
22+pre-up,openvswitch
23+pre-up,openvswitch_port
24 pre-up,xfrm
25 pre-up,link
26 pre-up,ppp
1aa30813 27@@ -43,3 +45,5 @@ post-down,usercmds
bf9a532c
AD
28 post-down,link
29 post-down,tunnel
30 post-down,xfrm
31+post-down,openvswitch_port
32+post-down,openvswitch
33diff --git a/ifupdown2/addons/openvswitch.py b/ifupdown2/addons/openvswitch.py
34new file mode 100644
1aa30813 35index 0000000..1d4c563
bf9a532c
AD
36--- /dev/null
37+++ b/ifupdown2/addons/openvswitch.py
1aa30813 38@@ -0,0 +1,248 @@
bf9a532c
AD
39+#!/usr/bin/python
40+#
41+# Copyright 2020 Alexandre Derumier <aderumier@odiso.com>
42+# Author: Alexandre Derumier, aderumier@odiso.com
43+#
44+
45+try:
46+ from ifupdown2.lib.addon import Addon
47+
48+ from ifupdown2.ifupdown.iface import *
49+ from ifupdown2.ifupdown.utils import utils
50+ from ifupdown2.ifupdownaddons.modulebase import moduleBase
51+ from ifupdown2.ifupdown.exceptions import moduleNotSupported
52+ import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
53+
54+except:
55+ from lib.addon import Addon
56+
57+ from ifupdown.iface import *
58+ from ifupdown.utils import utils
59+ from ifupdownaddons.modulebase import moduleBase
60+ from ifupdown.exceptions import moduleNotSupported
61+ import ifupdown.ifupdownflags as ifupdownflags
62+
63+import logging
64+import re
65+import subprocess
66+import os
67+
68+class openvswitch(Addon, moduleBase):
69+ """ ifupdown2 addon module to configure Openvswitch bridge """
70+
71+ _modinfo = {
72+ 'mhelp': 'openvswitch module configure openvswitch bridges',
73+ 'attrs': {
74+ 'ovs-ports': {
75+ 'help': 'Interfaces to be part of this ovs bridge.',
76+ 'validvals': ['<interface-list>'],
77+ 'required': False,
78+ },
79+ 'ovs-type': {
80+ 'help': 'ovs interface type',
81+ 'validvals': ['OVSBridge'],
82+ 'required': True,
83+ },
84+ 'ovs-mtu': {
85+ 'help': 'Interface MTU (maximum transmission unit)',
86+ 'validrange': ['552', '9216'],
87+ 'example': ['ovs-mtu 1600'],
88+ 'default': '1500'
89+ },
90+ 'ovs-options': {
91+ 'help': 'This option lets you add extra arguments to a ovs-vsctl command',
92+ 'required': False,
93+ },
94+ 'ovs-extra': {
95+ 'help': 'This option lets you run additional ovs-vsctl commands,' +
96+ 'separated by "--" (double dash). Variables can be part of the "ovs_extra"' +
97+ 'option. You can provide all the standard environmental variables' +
98+ 'described in the interfaces(5) man page. You can also pass shell' +
99+ 'commands.extra args',
100+ 'required': False,
101+ 'example': ['ovs_extra set bridge ${IFACE} other-config:hwaddr=00:59:cf:9c:84:3a -- br-set-external-id ${IFACE} bridge-id ${IFACE}']
102+
103+ },
1aa30813
AD
104+ 'ovs-ports-condone-regex': {
105+ "help": "bridge ports to ignore/condone when reloading config / removing interfaces",
106+ "required": False,
107+ "default": "^(tap|veth|fwln)",
108+ "example": ["ovs-ports-condone-regex ^[a-zA-Z0-9]+_v[0-9]{1,4}$"]
109+ },
bf9a532c
AD
110+ }
111+ }
112+
113+ def __init__ (self, *args, **kargs):
114+ moduleBase.__init__ (self, *args, **kargs)
115+ Addon.__init__(self)
116+ if not os.path.exists('/usr/bin/ovs-vsctl'):
117+ raise moduleNotSupported('module init failed: no /usr/bin/ovs-vsctl found')
118+
119+ def _is_ovs_bridge (self, ifaceobj):
120+ ovstype = ifaceobj.get_attr_value_first('ovs-type')
121+ if ovstype:
122+ if ovstype == 'OVSBridge':
123+ return True
124+ else:
125+ return False
126+ return False
127+
128+ def _get_ovs_ports (self, ifaceobj):
129+ ovs_ports = ifaceobj.get_attr_value_first('ovs-ports')
130+ if ovs_ports:
131+ return sorted (ovs_ports.split ())
132+ return None
133+
134+ def _get_running_ovs_ports (self, iface):
135+ output = utils.exec_command("/usr/bin/ovs-vsctl list-ports %s" %iface)
136+ if output:
137+ ovs_ports = sorted(output.splitlines())
138+ return ovs_ports
139+ return None
140+
1aa30813
AD
141+ def _get_ovs_port_condone_regex(self, ifaceobj, get_string = False):
142+ ovs_port_condone_regex = ifaceobj.get_attr_value_first('ovs-ports-condone-regex')
143+ if not ovs_port_condone_regex:
144+ ovs_port_condone_regex = self.get_attr_default_value('ovs-ports-condone-regex')
145+
146+ if ovs_port_condone_regex:
147+ if get_string:
148+ return ovs_port_condone_regex
149+ return re.compile (r"%s" % ovs_port_condone_regex)
150+ return None
151+
bf9a532c
AD
152+ def _ovs_vsctl(self, ifaceobj, cmdlist):
153+
154+ if cmdlist:
155+
156+ os.environ['IFACE'] = ifaceobj.name if ifaceobj.name else ''
157+ os.environ['LOGICAL'] = ifaceobj.name if ifaceobj.name else ''
158+ os.environ['METHOD'] = ifaceobj.addr_method if ifaceobj.addr_method else ''
159+ os.environ['ADDRFAM'] = ','.join(ifaceobj.addr_family) if ifaceobj.addr_family else ''
160+
161+ finalcmd = "/usr/bin/ovs-vsctl"
162+
163+ for cmd in cmdlist:
164+ finalcmd = finalcmd + " -- " + cmd
165+
166+ try:
167+ self.logger.debug ("Running %s" % (finalcmd))
168+ utils.exec_user_command(finalcmd)
169+ except subprocess.CalledProcessError as c:
170+ raise Exception ("Command \"%s failed: %s" % (finalcmd, c.output))
171+ except Exception as e:
172+ raise Exception ("%s" % e)
173+
174+ def _addbridge (self, ifaceobj):
175+
176+ iface = ifaceobj.name
177+ ovsoptions = ifaceobj.get_attr_value_first ('ovs-options')
178+ ovsextra = ifaceobj.get_attr_value('ovs-extra')
179+ ovsmtu = ifaceobj.get_attr_value_first ('ovs-mtu')
180+
181+ cmd_list = []
182+
183+ cmd = "--may-exist add-br %s"%(iface)
184+ cmd_list.append(cmd)
185+
186+ if ovsoptions:
187+ cmd = "set bridge %s %s" %(iface, ovsoptions)
188+ cmd_list.append(cmd)
189+
190+ #update
191+ if self.cache.link_exists (iface):
1aa30813
AD
192+
193+ ovsportscondoneregex = self._get_ovs_port_condone_regex(ifaceobj)
bf9a532c
AD
194+ # on update, delete active ports not in the new port list
195+ ovs_ports = self._get_ovs_ports(ifaceobj)
196+ running_ovs_ports = self._get_running_ovs_ports(iface)
197+ if running_ovs_ports is not None and ovs_ports is not None:
198+ missingports = list(set(running_ovs_ports) - set(ovs_ports))
199+
200+ if missingports is not None:
201+ for port in missingports:
1aa30813
AD
202+ if ovsportscondoneregex and ovsportscondoneregex.match(port):
203+ self.logger.info("%s: port %s will stay enslaved as it matches with ovs-ports-condone-regex" % (ifaceobj.name, port))
204+ continue
bf9a532c
AD
205+ cmd = "--if-exists del-port %s %s"%(iface, port)
206+ cmd_list.append(cmd)
207+
208+ #clear old bridge options
209+ cmd = "--if-exists clear bridge %s auto_attach controller external-ids fail_mode flood_vlans ipfix mirrors netflow other_config protocols sflow"%(iface)
210+
211+ cmd_list.append(cmd)
212+
213+ #clear old interface options
214+ cmd = "--if-exists clear interface %s mtu_request external-ids other_config options"%(iface)
215+ cmd_list.append(cmd)
216+
217+ if ovsextra is not None:
218+ cmd_list.extend(ovsextra)
219+
220+ if ovsmtu is not None:
221+ cmd = "set Interface %s mtu_request=%s"%(iface, ovsmtu)
222+ cmd_list.append(cmd)
223+
224+ self._ovs_vsctl(ifaceobj, cmd_list)
225+ if not self.cache.link_exists(ifaceobj.name):
226+ self.iproute2.link_add_openvswitch(ifaceobj.name, "openvswitch")
227+
228+ def _delbridge (self, ifaceobj):
229+
230+ cmd = "del-br %s"%(ifaceobj.name)
231+ self._ovs_vsctl(ifaceobj, [cmd])
232+
233+ def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None):
234+ return None
235+
236+ def _up (self, ifaceobj):
237+ self._addbridge (ifaceobj)
238+
239+ def _down (self, ifaceobj):
240+ if not ifupdownflags.flags.PERFMODE and not self.cache.link_exists (ifaceobj.name):
241+ return
242+
243+ self._delbridge (ifaceobj)
244+
245+ def _query_check (self, ifaceobj, ifaceobjcurr):
246+ if not self.cache.link_exists (ifaceobj.name):
247+ return
248+ return
249+
250+ _run_ops = {
251+ 'pre-up': _up,
252+ 'post-down': _down,
253+ 'query-checkcurr': _query_check
254+ }
255+
256+ def get_ops (self):
257+ """ returns list of ops supported by this module """
258+ return self._run_ops.keys ()
259+
260+ def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args):
261+ """ run openvswitch configuration on the interface object passed as argument
262+
263+ Args:
264+ **ifaceobj** (object): iface object
265+
266+ **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
267+ 'query-running'
268+ Kwargs:
269+ **query_ifaceobj** (object): query check ifaceobject. This is only
270+ valid when op is 'query-checkcurr'. It is an object same as
271+ ifaceobj, but contains running attribute values and its config
272+ status. The modules can use it to return queried running state
273+ of interfaces. status is success if the running state is same
274+ as user required state in ifaceobj. error otherwise.
275+ """
276+ op_handler = self._run_ops.get (operation)
277+ if not op_handler:
278+ return
279+
280+ if (operation != 'query-running' and not self._is_ovs_bridge (ifaceobj)):
281+ return
282+
283+ if operation == 'query-checkcurr':
284+ op_handler (self, ifaceobj, query_ifaceobj)
285+ else:
286+ op_handler (self, ifaceobj)
287diff --git a/ifupdown2/addons/openvswitch_port.py b/ifupdown2/addons/openvswitch_port.py
288new file mode 100644
289index 0000000..e34cc18
290--- /dev/null
291+++ b/ifupdown2/addons/openvswitch_port.py
292@@ -0,0 +1,274 @@
293+#!/usr/bin/python
294+#
295+# Copyright 2020 Alexandre Derumier <aderumier@odiso.com>
296+# Author: Alexandre Derumier, aderumier@odiso.com
297+#
298+
299+try:
300+ from ifupdown2.lib.addon import Addon
301+
302+ from ifupdown2.ifupdown.iface import *
303+ from ifupdown2.ifupdown.utils import utils
304+ from ifupdown2.ifupdownaddons.modulebase import moduleBase
305+ from ifupdown2.ifupdown.exceptions import moduleNotSupported
306+ import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
307+
308+except:
309+ from lib.addon import Addon
310+
311+ from ifupdown.iface import *
312+ from ifupdown.utils import utils
313+ from ifupdownaddons.modulebase import moduleBase
314+ from ifupdown.exceptions import moduleNotSupported
315+ import ifupdown.ifupdownflags as ifupdownflags
316+
317+import logging
318+import re
319+import subprocess
320+import os
321+
322+class openvswitch_port(Addon, moduleBase):
323+ """ ifupdown2 addon module to configure openvswitch ports """
324+
325+ _modinfo = {
326+ 'mhelp': 'openvswitch module configure openvswitch ports',
327+ 'attrs': {
328+ 'ovs-bridge': {
329+ 'help': 'Interfaces to be part of this ovs bridge',
330+ 'required': True,
331+ },
332+ 'ovs-type': {
333+ 'help': 'ovs interface type',
334+ 'validvals': ['OVSPort', 'OVSIntPort', 'OVSBond', 'OVSTunnel', 'OVSPatchPort'],
335+ 'required': True,
336+ 'example': ['ovs-type OVSPort'],
337+ },
338+ 'ovs-options': {
339+ 'help': 'This option lets you add extra arguments to a ovs-vsctl command',
340+ 'required': False,
341+ 'example': ['ovs_options bond_mode=balance-tcp lacp=active tag=100']
342+ },
343+ 'ovs-extra': {
344+ 'help': 'This option lets you run additional ovs-vsctl commands,' +
345+ 'separated by "--" (double dash). Variables can be part of the "ovs_extra"' +
346+ 'option. You can provide all the standard environmental variables' +
347+ 'described in the interfaces(5) man page. You can also pass shell' +
348+ 'commands.extra args',
349+ 'required': False,
350+ 'example': ['ovs_extra set interface ${IFACE} external-ids:iface-id=$(hostname -s)']
351+ },
352+ 'ovs-bonds': {
353+ 'help': 'Interfaces to be part of this ovs bond',
354+ 'validvals': ['<interface-list>'],
355+ 'required': False,
356+ },
357+ 'ovs-tunnel-type': {
358+ 'help': 'For "OVSTunnel" interfaces, the type of the tunnel',
359+ 'required': False,
360+ 'example': ['ovs-tunnel-type gre'],
361+ },
362+ 'ovs-tunnel-options': {
363+ 'help': 'For "OVSTunnel" interfaces, this field should be ' +
364+ 'used to specify the tunnel options like remote_ip, key, etc.',
365+ 'required': False,
366+ 'example': ['ovs-tunnel-options options:remote_ip=182.168.1.2 options:key=1'],
367+ },
368+ 'ovs-patch-peer': {
369+ 'help': 'ovs patch peer',
370+ 'required': False,
371+ 'example': ['ovs-patch-peer patch0'],
372+ },
373+ 'ovs-mtu': {
374+ 'help': 'mtu of the ovs interface',
375+ 'required': False,
376+ 'example': ['ovs-mtu 9000'],
377+ },
378+ }
379+ }
380+
381+ def __init__ (self, *args, **kargs):
382+ moduleBase.__init__ (self, *args, **kargs)
383+ Addon.__init__(self)
384+ if not os.path.exists('/usr/bin/ovs-vsctl'):
385+ raise moduleNotSupported('module init failed: no /usr/bin/ovs-vsctl found')
386+
387+ def _is_ovs_port (self, ifaceobj):
388+ ovstype = ifaceobj.get_attr_value_first ('ovs-type')
389+ ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge')
390+ if ovstype and ovsbridge:
391+ return True
392+ return False
393+
394+ def _get_bond_ifaces (self, ifaceobj):
395+ ovs_bonds = ifaceobj.get_attr_value_first ('ovs-bonds')
396+ if ovs_bonds:
397+ return sorted (ovs_bonds.split ())
398+ return None
399+
400+ def _ovs_vsctl(self, ifaceobj, cmdlist):
401+
402+ if cmdlist:
403+
404+ os.environ['IFACE'] = ifaceobj.name if ifaceobj.name else ''
405+ os.environ['LOGICAL'] = ifaceobj.name if ifaceobj.name else ''
406+ os.environ['METHOD'] = ifaceobj.addr_method if ifaceobj.addr_method else ''
407+ os.environ['ADDRFAM'] = ','.join(ifaceobj.addr_family) if ifaceobj.addr_family else ''
408+
409+ finalcmd = "/usr/bin/ovs-vsctl"
410+
411+ for cmd in cmdlist:
412+ finalcmd = finalcmd + " -- " + cmd
413+
414+ try:
415+ self.logger.debug ("Running %s" % (finalcmd))
416+ utils.exec_user_command(finalcmd)
417+ except subprocess.CalledProcessError as c:
418+ raise Exception ("Command \"%s failed: %s" % (finalcmd, c.output))
419+ except Exception as e:
420+ raise Exception ("%s" % e)
421+
422+ def _addport (self, ifaceobj):
423+ iface = ifaceobj.name
424+ ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge')
425+ ovsoptions = ifaceobj.get_attr_value_first ('ovs-options')
426+ ovstype = ifaceobj.get_attr_value_first ('ovs-type')
427+ ovsbonds = ifaceobj.get_attr_value_first ('ovs-bonds')
428+ ovsextra = ifaceobj.get_attr_value('ovs-extra')
429+
430+ cmd_list = []
431+
432+ if ovstype == 'OVSBond':
433+ if ovsbonds is None:
434+ raise Exception ("missing ovs-bonds option")
435+ cmd = "--may-exist --fake-iface add-bond %s %s %s"%(ovsbridge, iface, ovsbonds)
436+ cmd_list.append(cmd)
437+ else:
438+ cmd = "--may-exist add-port %s %s"%(ovsbridge, iface)
439+ cmd_list.append(cmd)
440+
441+
442+ #clear old ports options
443+ cmd = "--if-exists clear port %s bond_active_slave bond_mode cvlans external_ids lacp mac other_config qos tag trunks vlan_mode"%(iface)
444+ cmd_list.append(cmd)
445+
446+ #clear old interface options
447+ cmd = "--if-exists clear interface %s mtu_request external-ids other_config options"%(iface)
448+ cmd_list.append(cmd)
449+
450+ if ovsoptions:
451+ cmd = "set Port %s %s" %(iface, ovsoptions)
452+ cmd_list.append(cmd)
453+
454+
455+ if ovstype == 'OVSIntPort':
456+ cmd = "set Interface %s type=internal"%(iface)
457+ cmd_list.append(cmd)
458+
459+ if ovstype == 'OVSTunnel':
460+ ovstunneltype = ifaceobj.get_attr_value_first ('ovs-tunnel-type')
461+ if ovstunneltype is None:
462+ raise Exception ("missing ovs-tunnel-type option")
463+ ovstunneloptions = ifaceobj.get_attr_value_first('ovs-tunnel-options')
464+ if ovstunneloptions is None:
465+ raise Exception ("missing ovs-tunnel-options option")
466+ cmd = "set Interface %s type=%s %s"%(iface, ovstunneltype, ovstunneloptions)
467+ cmd_list.append(cmd)
468+
469+ if ovstype == 'OVSPatchPort':
470+ ovspatchpeer = ifaceobj.get_attr_value_first ('ovs-patch-peer')
471+ if ovspatchpeer is None:
472+ raise Exception ("missing ovs-patch-peer")
473+ cmd = "set Interface %s type=patch options:peer=%s"%(iface, ovspatchpeer)
474+ cmd_list.append(cmd)
475+
476+ #mtu
477+ ovsmtu = ifaceobj.get_attr_value_first ('ovs-mtu')
478+ ovsbonds_list = self._get_bond_ifaces(ifaceobj)
479+ if ovsmtu is not None:
480+ #we can't set mtu on bond fake interface, we apply it on slaves interfaces
481+ if ovstype == 'OVSBond' and ovsbonds_list is not None:
482+ for slave in ovsbonds_list:
483+ cmd = "set Interface %s mtu_request=%s"%(slave,ovsmtu)
484+ cmd_list.append(cmd)
485+
486+ else:
487+ cmd = "set Interface %s mtu_request=%s"%(iface,ovsmtu)
488+ cmd_list.append(cmd)
489+
490+ #extra
491+ if ovsextra is not None:
492+ cmd_list.extend(ovsextra)
493+
494+ self._ovs_vsctl(ifaceobj, cmd_list)
495+
496+ if ovstype != 'OVSTunnel' and ovstype != 'OVSPatchPort':
497+ if not self.cache.link_exists(ifaceobj.name):
498+ self.iproute2.link_add_openvswitch(ifaceobj.name, "openvswitch")
499+
500+ def _delport (self, ifaceobj):
501+ iface = ifaceobj.name
502+ ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge')
503+ cmd = "--if-exists del-port %s %s"%(ovsbridge, iface)
504+
505+ self._ovs_vsctl(ifaceobj, [cmd])
506+
507+ def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None):
508+
509+ if not self._is_ovs_port (ifaceobj):
510+ return None
511+
512+ ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge')
513+ return [ovsbridge]
514+
515+ def _up (self, ifaceobj):
516+
517+ self._addport (ifaceobj)
518+
519+ def _down (self, ifaceobj):
520+ if not ifupdownflags.flags.PERFMODE and not self.cache.link_exists (ifaceobj.name):
521+ return
522+
523+ self._delport (ifaceobj)
524+
525+ def _query_check (self, ifaceobj, ifaceobjcurr):
526+ if not self.cache.link_exists (ifaceobj.name):
527+ return
528+ return
529+
530+ _run_ops = {
531+ 'pre-up': _up,
532+ 'post-down': _down,
533+ 'query-checkcurr': _query_check
534+ }
535+
536+ def get_ops (self):
537+ """ returns list of ops supported by this module """
538+ return self._run_ops.keys ()
539+
540+ def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args):
541+ """ run Openvswitch port configuration on the interface object passed as argument
542+
543+ Args:
544+ **ifaceobj** (object): iface object
545+
546+ **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
547+ 'query-running'
548+ Kwargs:
549+ **query_ifaceobj** (object): query check ifaceobject. This is only
550+ valid when op is 'query-checkcurr'. It is an object same as
551+ ifaceobj, but contains running attribute values and its config
552+ status. The modules can use it to return queried running state
553+ of interfaces. status is success if the running state is same
554+ as user required state in ifaceobj. error otherwise.
555+ """
556+ op_handler = self._run_ops.get (operation)
557+ if not op_handler:
558+ return
559+
560+ if (operation != 'query-running' and not self._is_ovs_port (ifaceobj)):
561+ return
562+
563+ if operation == 'query-checkcurr':
564+ op_handler (self, ifaceobj, query_ifaceobj)
565+ else:
566+ op_handler (self, ifaceobj)
567diff --git a/ifupdown2/lib/iproute2.py b/ifupdown2/lib/iproute2.py
568index 704d120..a1223b9 100644
569--- a/ifupdown2/lib/iproute2.py
570+++ b/ifupdown2/lib/iproute2.py
571@@ -334,6 +334,9 @@ class IPRoute2(Cache, Requirements):
572 def link_add_xfrm(ifname, xfrm_name, xfrm_id):
573 utils.exec_commandl(['ip', 'link', 'add', xfrm_name, 'type', 'xfrm', 'dev', ifname, 'if_id', xfrm_id])
574
575+ def link_add_openvswitch(self, ifname, kind):
576+ self.__update_cache_after_link_creation(ifname, kind)
577+
578 ############################################################################
579 # TUNNEL
580 ############################################################################
581diff --git a/ifupdown2/nlmanager/nlpacket.py b/ifupdown2/nlmanager/nlpacket.py
582index fcb89fb..c8a0697 100644
583--- a/ifupdown2/nlmanager/nlpacket.py
584+++ b/ifupdown2/nlmanager/nlpacket.py
585@@ -2791,6 +2791,7 @@ class AttributeIFLA_LINKINFO(Attribute):
586 "dummy",
587 "bridge",
588 "macvlan",
589+ "openvswitch"
590 ):
591 self.log.debug('Unsupported IFLA_INFO_KIND %s' % kind)
592 return
593--
5942.20.1
595