From 213d8a409d7b00107621f1ad646481f106feb7b3 Mon Sep 17 00:00:00 2001 From: Alexandre Derumier Date: Sun, 16 Feb 2020 14:54:09 +0100 Subject: [PATCH] add openvswitch addons This is a reimplementation of ifupdown1 script https://github.com/openvswitch/ovs/blob/master/debian/openvswitch-switch.README.Debian --- etc/network/ifupdown2/addons.conf | 5 + ifupdown2/addons/openvswitch.py | 224 ++++++++++++++++++++++ ifupdown2/addons/openvswitch_port.py | 270 +++++++++++++++++++++++++++ 3 files changed, 499 insertions(+) create mode 100644 ifupdown2/addons/openvswitch.py create mode 100644 ifupdown2/addons/openvswitch_port.py diff --git a/etc/network/ifupdown2/addons.conf b/etc/network/ifupdown2/addons.conf index 7975a80..726d63a 100644 --- a/etc/network/ifupdown2/addons.conf +++ b/etc/network/ifupdown2/addons.conf @@ -1,3 +1,5 @@ +pre-up,openvswitch +pre-up,openvswitch_port pre-up,xfrm pre-up,link pre-up,ppp @@ -42,3 +44,6 @@ post-down,usercmds post-down,link post-down,tunnel post-down,xfrm +post-down,openvswitch_port +post-down,openvswitch + diff --git a/ifupdown2/addons/openvswitch.py b/ifupdown2/addons/openvswitch.py new file mode 100644 index 0000000..5f26b03 --- /dev/null +++ b/ifupdown2/addons/openvswitch.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 Alexandre Derumier +# Author: Alexandre Derumier, aderumier@odiso.com +# + +try: + from ifupdown2.lib.addon import Addon + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + from ifupdown2.ifupdown.exceptions import moduleNotSupported + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + +except: + from lib.addon import Addon + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdownaddons.modulebase import moduleBase + from ifupdown.exceptions import moduleNotSupported + import ifupdown.ifupdownflags as ifupdownflags + +import logging +import re +import subprocess +import os + +class openvswitch(Addon, moduleBase): + """ ifupdown2 addon module to configure Openvswitch bridge """ + + _modinfo = { + 'mhelp': 'openvswitch module configure openvswitch bridges', + 'attrs': { + 'ovs-ports': { + 'help': 'Interfaces to be part of this ovs bridge.', + 'validvals': [''], + 'required': False, + }, + 'ovs-type': { + 'help': 'ovs interface type', + 'validvals': ['OVSBridge'], + 'required': True, + }, + 'ovs-mtu': { + 'help': 'Interface MTU (maximum transmission unit)', + 'validrange': ['552', '9216'], + 'example': ['ovs-mtu 1600'], + 'default': '1500' + }, + 'ovs-options': { + 'help': 'This option lets you add extra arguments to a ovs-vsctl command', + 'required': False, + }, + 'ovs-extra': { + 'help': 'This option lets you run additional ovs-vsctl commands,' + + 'separated by "--" (double dash). Variables can be part of the "ovs_extra"' + + 'option. You can provide all the standard environmental variables' + + 'described in the interfaces(5) man page. You can also pass shell' + + 'commands.extra args', + 'required': False, + 'example': ['ovs_extra set bridge ${IFACE} other-config:hwaddr=00:59:cf:9c:84:3a -- br-set-external-id ${IFACE} bridge-id ${IFACE}'] + + }, + } + } + + def __init__ (self, *args, **kargs): + moduleBase.__init__ (self, *args, **kargs) + Addon.__init__(self) + if not os.path.exists('/usr/bin/ovs-vsctl'): + raise moduleNotSupported('module init failed: no /usr/bin/ovs-vsctl found') + + def _is_ovs_bridge (self, ifaceobj): + ovstype = ifaceobj.get_attr_value_first('ovs-type') + if ovstype: + if ovstype == 'OVSBridge': + return True + else: + return False + return False + + def _get_ovs_ports (self, ifaceobj): + ovs_ports = ifaceobj.get_attr_value_first('ovs-ports') + if ovs_ports: + return sorted (ovs_ports.split ()) + return None + + def _get_running_ovs_ports (self, iface): + output = utils.exec_command("/usr/bin/ovs-vsctl list-ports %s" %iface) + if output: + ovs_ports = sorted(output.splitlines()) + return ovs_ports + return None + + def _ovs_vsctl(self, ifaceobj, cmdlist): + + if cmdlist: + + os.environ['IFACE'] = ifaceobj.name if ifaceobj.name else '' + os.environ['LOGICAL'] = ifaceobj.name if ifaceobj.name else '' + os.environ['METHOD'] = ifaceobj.addr_method if ifaceobj.addr_method else '' + os.environ['ADDRFAM'] = ','.join(ifaceobj.addr_family) if ifaceobj.addr_family else '' + + finalcmd = "/usr/bin/ovs-vsctl" + + for cmd in cmdlist: + finalcmd = finalcmd + " -- " + cmd + + try: + self.logger.debug ("Running %s" % (finalcmd)) + utils.exec_user_command(finalcmd) + except subprocess.CalledProcessError as c: + raise Exception ("Command \"%s failed: %s" % (finalcmd, c.output)) + except Exception as e: + raise Exception ("%s" % e) + + def _addbridge (self, ifaceobj): + + iface = ifaceobj.name + ovsoptions = ifaceobj.get_attr_value_first ('ovs-options') + ovsextra = ifaceobj.get_attr_value('ovs-extra') + ovsmtu = ifaceobj.get_attr_value_first ('ovs-mtu') + + cmd_list = [] + + cmd = "--may-exist add-br %s"%(iface) + cmd_list.append(cmd) + + if ovsoptions: + cmd = "set bridge %s %s" %(iface, ovsoptions) + cmd_list.append(cmd) + + #update + if self.cache.link_exists (iface): + # on update, delete active ports not in the new port list + ovs_ports = self._get_ovs_ports(ifaceobj) + running_ovs_ports = self._get_running_ovs_ports(iface) + if running_ovs_ports is not None and ovs_ports is not None: + missingports = list(set(running_ovs_ports) - set(ovs_ports)) + + if missingports is not None: + for port in missingports: + cmd = "--if-exists del-port %s %s"%(iface, port) + cmd_list.append(cmd) + + #clear old bridge options + cmd = "--if-exists clear bridge %s auto_attach controller external-ids fail_mode flood_vlans ipfix mirrors netflow other_config protocols sflow"%(iface) + + cmd_list.append(cmd) + + #clear old interface options + cmd = "--if-exists clear interface %s mtu_request external-ids other_config options"%(iface) + cmd_list.append(cmd) + + if ovsextra is not None: + cmd_list.extend(ovsextra) + + if ovsmtu is not None: + cmd = "set Interface %s mtu_request=%s"%(iface, ovsmtu) + cmd_list.append(cmd) + + self._ovs_vsctl(ifaceobj, cmd_list) + + def _delbridge (self, ifaceobj): + + cmd = "del-br %s"%(ifaceobj.name) + self._ovs_vsctl(ifaceobj, [cmd]) + + def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None): + return None + + def _up (self, ifaceobj): + self._addbridge (ifaceobj) + + def _down (self, ifaceobj): + if not ifupdownflags.flags.PERFMODE and not self.cache.link_exists (ifaceobj.name): + return + + self._delbridge (ifaceobj) + + def _query_check (self, ifaceobj, ifaceobjcurr): + if not self.cache.link_exists (ifaceobj.name): + return + return + + _run_ops = { + 'pre-up': _up, + 'post-down': _down, + 'query-checkcurr': _query_check + } + + def get_ops (self): + """ returns list of ops supported by this module """ + return self._run_ops.keys () + + def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args): + """ run openvswitch configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get (operation) + if not op_handler: + return + + if (operation != 'query-running' and not self._is_ovs_bridge (ifaceobj)): + return + + if operation == 'query-checkcurr': + op_handler (self, ifaceobj, query_ifaceobj) + else: + op_handler (self, ifaceobj) diff --git a/ifupdown2/addons/openvswitch_port.py b/ifupdown2/addons/openvswitch_port.py new file mode 100644 index 0000000..394ed66 --- /dev/null +++ b/ifupdown2/addons/openvswitch_port.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 Alexandre Derumier +# Author: Alexandre Derumier, aderumier@odiso.com +# + +try: + from ifupdown2.lib.addon import Addon + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + from ifupdown2.ifupdown.exceptions import moduleNotSupported + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + +except: + from lib.addon import Addon + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdownaddons.modulebase import moduleBase + from ifupdown.exceptions import moduleNotSupported + import ifupdown.ifupdownflags as ifupdownflags + +import logging +import re +import subprocess +import os + +class openvswitch_port(Addon, moduleBase): + """ ifupdown2 addon module to configure openvswitch ports """ + + _modinfo = { + 'mhelp': 'openvswitch module configure openvswitch ports', + 'attrs': { + 'ovs-bridge': { + 'help': 'Interfaces to be part of this ovs bridge', + 'required': True, + }, + 'ovs-type': { + 'help': 'ovs interface type', + 'validvals': ['OVSPort', 'OVSIntPort', 'OVSBond', 'OVSTunnel', 'OVSPatchPort'], + 'required': True, + 'example': ['ovs-type OVSPort'], + }, + 'ovs-options': { + 'help': 'This option lets you add extra arguments to a ovs-vsctl command', + 'required': False, + 'example': ['ovs_options bond_mode=balance-tcp lacp=active tag=100'] + }, + 'ovs-extra': { + 'help': 'This option lets you run additional ovs-vsctl commands,' + + 'separated by "--" (double dash). Variables can be part of the "ovs_extra"' + + 'option. You can provide all the standard environmental variables' + + 'described in the interfaces(5) man page. You can also pass shell' + + 'commands.extra args', + 'required': False, + 'example': ['ovs_extra set interface ${IFACE} external-ids:iface-id=$(hostname -s)'] + }, + 'ovs-bonds': { + 'help': 'Interfaces to be part of this ovs bond', + 'validvals': [''], + 'required': False, + }, + 'ovs-tunnel-type': { + 'help': 'For "OVSTunnel" interfaces, the type of the tunnel', + 'required': False, + 'example': ['ovs-tunnel-type gre'], + }, + 'ovs-tunnel-options': { + 'help': 'For "OVSTunnel" interfaces, this field should be ' + + 'used to specify the tunnel options like remote_ip, key, etc.', + 'required': False, + 'example': ['ovs-tunnel-options options:remote_ip=182.168.1.2 options:key=1'], + }, + 'ovs-patch-peer': { + 'help': 'ovs patch peer', + 'required': False, + 'example': ['ovs-patch-peer patch0'], + }, + 'ovs-mtu': { + 'help': 'mtu of the ovs interface', + 'required': False, + 'example': ['ovs-mtu 9000'], + }, + } + } + + def __init__ (self, *args, **kargs): + moduleBase.__init__ (self, *args, **kargs) + Addon.__init__(self) + if not os.path.exists('/usr/bin/ovs-vsctl'): + raise moduleNotSupported('module init failed: no /usr/bin/ovs-vsctl found') + + def _is_ovs_port (self, ifaceobj): + ovstype = ifaceobj.get_attr_value_first ('ovs-type') + ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + if ovstype and ovsbridge: + return True + return False + + def _get_bond_ifaces (self, ifaceobj): + ovs_bonds = ifaceobj.get_attr_value_first ('ovs-bonds') + if ovs_bonds: + return sorted (ovs_bonds.split ()) + return None + + def _ovs_vsctl(self, ifaceobj, cmdlist): + + if cmdlist: + + os.environ['IFACE'] = ifaceobj.name if ifaceobj.name else '' + os.environ['LOGICAL'] = ifaceobj.name if ifaceobj.name else '' + os.environ['METHOD'] = ifaceobj.addr_method if ifaceobj.addr_method else '' + os.environ['ADDRFAM'] = ','.join(ifaceobj.addr_family) if ifaceobj.addr_family else '' + + finalcmd = "/usr/bin/ovs-vsctl" + + for cmd in cmdlist: + finalcmd = finalcmd + " -- " + cmd + + try: + self.logger.debug ("Running %s" % (finalcmd)) + utils.exec_user_command(finalcmd) + except subprocess.CalledProcessError as c: + raise Exception ("Command \"%s failed: %s" % (finalcmd, c.output)) + except Exception as e: + raise Exception ("%s" % e) + + def _addport (self, ifaceobj): + iface = ifaceobj.name + ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + ovsoptions = ifaceobj.get_attr_value_first ('ovs-options') + ovstype = ifaceobj.get_attr_value_first ('ovs-type') + ovsbonds = ifaceobj.get_attr_value_first ('ovs-bonds') + ovsextra = ifaceobj.get_attr_value('ovs-extra') + + cmd_list = [] + + if ovstype == 'OVSBond': + if ovsbonds is None: + raise Exception ("missing ovs-bonds option") + cmd = "--may-exist --fake-iface add-bond %s %s %s"%(ovsbridge, iface, ovsbonds) + cmd_list.append(cmd) + else: + cmd = "--may-exist add-port %s %s"%(ovsbridge, iface) + cmd_list.append(cmd) + + + #clear old ports options + cmd = "--if-exists clear port %s bond_active_slave bond_mode cvlans external_ids lacp mac other_config qos tag trunks vlan_mode"%(iface) + cmd_list.append(cmd) + + #clear old interface options + cmd = "--if-exists clear interface %s mtu_request external-ids other_config options"%(iface) + cmd_list.append(cmd) + + if ovsoptions: + cmd = "set Port %s %s" %(iface, ovsoptions) + cmd_list.append(cmd) + + + if ovstype == 'OVSIntPort': + cmd = "set Interface %s type=internal"%(iface) + cmd_list.append(cmd) + + if ovstype == 'OVSTunnel': + ovstunneltype = ifaceobj.get_attr_value_first ('ovs-tunnel-type') + if ovstunneltype is None: + raise Exception ("missing ovs-tunnel-type option") + ovstunneloptions = ifaceobj.get_attr_value_first('ovs-tunnel-options') + if ovstunneloptions is None: + raise Exception ("missing ovs-tunnel-options option") + cmd = "set Interface %s type=%s %s"%(iface, ovstunneltype, ovstunneloptions) + cmd_list.append(cmd) + + if ovstype == 'OVSPatchPort': + ovspatchpeer = ifaceobj.get_attr_value_first ('ovs-patch-peer') + if ovspatchpeer is None: + raise Exception ("missing ovs-patch-peer") + cmd = "set Interface %s type=patch options:peer=%s"%(iface, ovspatchpeer) + cmd_list.append(cmd) + + #mtu + ovsmtu = ifaceobj.get_attr_value_first ('ovs-mtu') + ovsbonds_list = self._get_bond_ifaces(ifaceobj) + if ovsmtu is not None: + #we can't set mtu on bond fake interface, we apply it on slaves interfaces + if ovstype == 'OVSBond' and ovsbonds_list is not None: + for slave in ovsbonds_list: + cmd = "set Interface %s mtu_request=%s"%(slave,ovsmtu) + cmd_list.append(cmd) + + else: + cmd = "set Interface %s mtu_request=%s"%(iface,ovsmtu) + cmd_list.append(cmd) + + #extra + if ovsextra is not None: + cmd_list.extend(ovsextra) + + self._ovs_vsctl(ifaceobj, cmd_list) + + def _delport (self, ifaceobj): + iface = ifaceobj.name + ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + cmd = "--if-exists del-port %s %s"%(ovsbridge, iface) + + self._ovs_vsctl(ifaceobj, [cmd]) + + def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None): + + if not self._is_ovs_port (ifaceobj): + return None + + ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + return [ovsbridge] + + def _up (self, ifaceobj): + + self._addport (ifaceobj) + + def _down (self, ifaceobj): + if not ifupdownflags.flags.PERFMODE and not self.cache.link_exists (ifaceobj.name): + return + + self._delport (ifaceobj) + + def _query_check (self, ifaceobj, ifaceobjcurr): + if not self.cache.link_exists (ifaceobj.name): + return + return + + _run_ops = { + 'pre-up': _up, + 'post-down': _down, + 'query-checkcurr': _query_check + } + + def get_ops (self): + """ returns list of ops supported by this module """ + return self._run_ops.keys () + + def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args): + """ run Openvswitch port configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get (operation) + if not op_handler: + return + + if (operation != 'query-running' and not self._is_ovs_port (ifaceobj)): + return + + if operation == 'query-checkcurr': + op_handler (self, ifaceobj, query_ifaceobj) + else: + op_handler (self, ifaceobj)