From 15ef32ea14bb56a0e694cedd8bca00bc90db4149 Mon Sep 17 00:00:00 2001 From: Roopa Prabhu Date: Thu, 9 Oct 2014 16:02:46 -0700 Subject: [PATCH] Move ifupdown2addons into ifupdown2 pacakge Ticket: CM-3864 Reviewed By: Testing Done: Tested build and install open item: - cleanup stale ifupdown2-addons package files --- TODO.addons | 13 + addons/address.py | 300 ++++++ addons/addressvirtual.py | 169 ++++ addons/bridge.py | 895 +++++++++++++++++ addons/bridgevlanaware.py | 942 ++++++++++++++++++ addons/dhcp.py | 129 +++ addons/ethtool.py | 102 ++ addons/ifenslave.py | 380 ++++++++ addons/loopback.py | 64 ++ addons/mstpctl.py | 710 ++++++++++++++ addons/usercmds.py | 97 ++ addons/vlan.py | 191 ++++ addons/vxlan.py | 87 ++ config/addons.conf | 28 + docs.addons/Makefile | 153 +++ docs.addons/source/addonsapiref.rst | 61 ++ docs.addons/source/addonshelperapiref.rst | 44 + docs.addons/source/conf.py | 247 +++++ docs.addons/source/developmentcorner.rst | 58 ++ docs.addons/source/gettingstarted.rst | 29 + docs.addons/source/index.rst | 25 + docs.addons/source/intro.rst | 21 + ifupdownaddons/__init__.py | 0 ifupdownaddons/bridgeutils.py | 498 ++++++++++ ifupdownaddons/cache.py | 90 ++ ifupdownaddons/dhclient.py | 86 ++ ifupdownaddons/ifenslaveutil.py | 407 ++++++++ ifupdownaddons/iproute2.py | 613 ++++++++++++ ifupdownaddons/modulebase.py | 318 ++++++ ifupdownaddons/mstpctlutil.py | 171 ++++ ifupdownaddons/utilsbase.py | 163 ++++ man.rst/ifupdown-addons-interfaces.5.rst | 1079 +++++++++++++++++++++ sbin/ifaddon | 350 +++++++ setup.py | 15 +- 34 files changed, 8532 insertions(+), 3 deletions(-) create mode 100644 TODO.addons create mode 100644 addons/address.py create mode 100644 addons/addressvirtual.py create mode 100644 addons/bridge.py create mode 100644 addons/bridgevlanaware.py create mode 100644 addons/dhcp.py create mode 100644 addons/ethtool.py create mode 100644 addons/ifenslave.py create mode 100644 addons/loopback.py create mode 100644 addons/mstpctl.py create mode 100644 addons/usercmds.py create mode 100644 addons/vlan.py create mode 100644 addons/vxlan.py create mode 100644 config/addons.conf create mode 100644 docs.addons/Makefile create mode 100644 docs.addons/source/addonsapiref.rst create mode 100644 docs.addons/source/addonshelperapiref.rst create mode 100644 docs.addons/source/conf.py create mode 100644 docs.addons/source/developmentcorner.rst create mode 100644 docs.addons/source/gettingstarted.rst create mode 100644 docs.addons/source/index.rst create mode 100644 docs.addons/source/intro.rst create mode 100644 ifupdownaddons/__init__.py create mode 100644 ifupdownaddons/bridgeutils.py create mode 100644 ifupdownaddons/cache.py create mode 100644 ifupdownaddons/dhclient.py create mode 100644 ifupdownaddons/ifenslaveutil.py create mode 100644 ifupdownaddons/iproute2.py create mode 100644 ifupdownaddons/modulebase.py create mode 100644 ifupdownaddons/mstpctlutil.py create mode 100644 ifupdownaddons/utilsbase.py create mode 100644 man.rst/ifupdown-addons-interfaces.5.rst create mode 100755 sbin/ifaddon diff --git a/TODO.addons b/TODO.addons new file mode 100644 index 0000000..5436284 --- /dev/null +++ b/TODO.addons @@ -0,0 +1,13 @@ +TODO: +==== +- run python code guideline checker +- more code documentation +- move all cache handling to decorators in ifupdownaddons package +- input validation (present in some cases not all) +- support the vlan0004 style vlans +- ifquery coverage. currently it is at 80%. +- vxlan module +- fix and release ifaddon utility to manage module priorities +- Deep compare in query for address module (does not compare address attributes like scope) +- Maybe a pure netlink backend +- improve caching diff --git a/addons/address.py b/addons/address.py new file mode 100644 index 0000000..adf8b4f --- /dev/null +++ b/addons/address.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.iproute2 import iproute2 + from ifupdownaddons.dhclient import dhclient +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class address(moduleBase): + """ ifupdown2 addon module to configure address, mtu, hwaddress, alias + (description) on an interface """ + + _modinfo = {'mhelp' : 'address configuration module for interfaces', + 'attrs': { + 'address' : + {'help' : 'ipv4 or ipv6 addresses', + 'example' : ['address 10.0.12.3/24', + 'address 2000:1000:1000:1000:3::5/128']}, + 'netmask' : + {'help': 'netmask', + 'example' : ['netmask 255.255.255.0'], + 'compat' : True}, + 'broadcast' : + {'help': 'broadcast address', + 'example' : ['broadcast 10.0.1.255']}, + 'scope' : + {'help': 'scope', + 'example' : ['scope host']}, + 'preferred-lifetime' : + {'help': 'preferred lifetime', + 'example' : ['preferred-lifetime forever', + 'preferred-lifetime 10']}, + 'gateway' : + {'help': 'default gateway', + 'example' : ['gateway 255.255.255.0']}, + 'mtu' : + { 'help': 'interface mtu', + 'example' : ['mtu 1600'], + 'default' : '1500'}, + 'hwaddress' : + {'help' : 'hw address', + 'example': ['hwaddress 44:38:39:00:27:b8']}, + 'alias' : + { 'help': 'description/alias', + 'example' : ['alias testnetwork']}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _inet_address_config(self, ifaceobj): + newaddrs = [] + addrs = ifaceobj.get_attr_value('address') + if addrs: + # If user address is not in CIDR notation, convert them to CIDR + for addr_index in range(0, len(addrs)): + addr = addrs[addr_index] + if '/' in addr: + newaddrs.append(addr) + continue + netmask = ifaceobj.get_attr_value_n('netmask', addr_index) + if netmask: + prefixlen = IPNetwork('%s' %addr + + '/%s' %netmask).prefixlen + newaddrs.append(addr + '/%s' %prefixlen) + else: + newaddrs.append(addr) + + if not self.PERFMODE and not (ifaceobj.flags & iface.HAS_SIBLINGS): + # if perfmode is not set and also if iface has no sibling + # objects, purge addresses that are not present in the new + # config + runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False) + if newaddrs == runningaddrs: + return + try: + # if primary address is not same, there is no need to keep any. + # reset all addresses + if (newaddrs and runningaddrs and + (newaddrs[0] != runningaddrs[0])): + self.ipcmd.del_addr_all(ifaceobj.name) + else: + self.ipcmd.del_addr_all(ifaceobj.name, newaddrs) + except Exception, e: + self.log_warn(str(e)) + if not newaddrs: + return + for addr_index in range(0, len(newaddrs)): + try: + self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], + ifaceobj.get_attr_value_n('broadcast', addr_index), + ifaceobj.get_attr_value_n('pointopoint',addr_index), + ifaceobj.get_attr_value_n('scope', addr_index), + ifaceobj.get_attr_value_n('preferred-lifetime', addr_index)) + except Exception, e: + self.log_error(str(e)) + + def _up(self, ifaceobj): + if not self.ipcmd.link_exists(ifaceobj.name): + return + try: + # release any stale dhcp addresses if present + if (not self.PERFMODE and + not (ifaceobj.flags & iface.HAS_SIBLINGS)): + # if not running in perf mode and ifaceobj does not have + # any sibling iface objects, kill any stale dhclient + # processes + dhclientcmd = self.dhclient() + if dhclient.is_running(ifaceobj.name): + # release any dhcp leases + dhclientcmd.release(ifaceobj.name) + elif dhclient.is_running6(ifaceobj.name): + dhclientcmd.release6(ifaceobj.name) + except: + pass + self.ipcmd.batch_start() + self._inet_address_config(ifaceobj) + mtu = ifaceobj.get_attr_value_first('mtu') + if mtu: + self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) + hwaddress = ifaceobj.get_attr_value_first('hwaddress') + if hwaddress: + self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress) + alias = ifaceobj.get_attr_value_first('alias') + if alias: + self.ipcmd.link_set_alias(ifaceobj.name, alias) + self.ipcmd.batch_commit() + self.ipcmd.route_add_gateway(ifaceobj.name, + ifaceobj.get_attr_value_first('gateway')) + + def _down(self, ifaceobj): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + return + self.ipcmd.route_del_gateway(ifaceobj.name, + ifaceobj.get_attr_value_first('gateway'), + ifaceobj.get_attr_value_first('metric')) + self.ipcmd.del_addr_all(ifaceobj.name) + mtu = ifaceobj.get_attr_value_first('mtu') + if mtu: + self.ipcmd.link_set(ifaceobj.name, 'mtu', + self.get_mod_subattr('mtu', 'default')) + alias = ifaceobj.get_attr_value_first('alias') + if alias: + self.ipcmd.link_set(ifaceobj.name, 'alias', "\'\'") + except Exception, e: + self.log_warn(str(e)) + + def _get_iface_addresses(self, ifaceobj): + addrlist = ifaceobj.get_attr_value('address') + outaddrlist = [] + + if not addrlist: return None + for addrindex in range(0, len(addrlist)): + addr = addrlist[addrindex] + netmask = ifaceobj.get_attr_value_n('netmask', addrindex) + if netmask: + prefixlen = IPNetwork('%s' %addr + + '/%s' %netmask).prefixlen + addr = addr + '/%s' %prefixlen + outaddrlist.append(addr) + return outaddrlist + + def _query_check(self, ifaceobj, ifaceobjcurr): + runningaddrsdict = None + if not self.ipcmd.link_exists(ifaceobj.name): + self.logger.debug('iface %s not found' %ifaceobj.name) + return + self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, + 'mtu', self.ipcmd.link_get_mtu) + self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, + 'hwaddress', self.ipcmd.link_get_hwaddress) + self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, + 'alias', self.ipcmd.link_get_alias) + # compare addresses + addrs = self._get_iface_addresses(ifaceobj) + runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name) + + # Set ifaceobjcurr method and family + ifaceobjcurr.addr_method = ifaceobj.addr_method + ifaceobjcurr.addr_family = ifaceobj.addr_family + if not runningaddrsdict and not addrs: + return + runningaddrs = runningaddrsdict.keys() if runningaddrsdict else [] + if runningaddrs != addrs: + runningaddrsset = set(runningaddrs) if runningaddrs else set([]) + addrsset = set(addrs) if addrs else set([]) + if (ifaceobj.flags & iface.HAS_SIBLINGS): + if not addrsset: + return + # only check for addresses present in running config + addrsdiff = addrsset.difference(runningaddrsset) + for addr in addrs: + if addr in addrsdiff: + ifaceobjcurr.update_config_with_status('address', + addr, 1) + else: + ifaceobjcurr.update_config_with_status('address', + addr, 0) + else: + addrsdiff = addrsset.symmetric_difference(runningaddrsset) + for addr in addrsset.union(runningaddrsset): + if addr in addrsdiff: + ifaceobjcurr.update_config_with_status('address', + addr, 1) + else: + ifaceobjcurr.update_config_with_status('address', + addr, 0) + elif addrs: + [ifaceobjcurr.update_config_with_status('address', + addr, 0) for addr in addrs] + #XXXX Check broadcast address, scope, etc + return + + def _query_running(self, ifaceobjrunning): + if not self.ipcmd.link_exists(ifaceobjrunning.name): + self.logger.debug('iface %s not found' %ifaceobjrunning.name) + ifaceobjrunning.status = ifaceStatus.NOTFOUND + return + dhclientcmd = dhclient() + if (dhclientcmd.is_running(ifaceobjrunning.name) or + dhclientcmd.is_running6(ifaceobjrunning.name)): + # If dhcp is configured on the interface, we skip it + return + isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name) + if isloopback: + default_addrs = ['127.0.0.1/8', '::1/128'] + ifaceobjrunning.addr_family = 'inet' + ifaceobjrunning.addr_method = 'loopback' + else: + default_addrs = [] + runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name) + if runningaddrsdict: + [ifaceobjrunning.update_config('address', addr) + for addr, addrattrs in runningaddrsdict.items() + if addr not in default_addrs] + mtu = self.ipcmd.link_get_mtu(ifaceobjrunning.name) + if (mtu and + (ifaceobjrunning.name == 'lo' and mtu != '16436') or + (ifaceobjrunning.name != 'lo' and + mtu != self.get_mod_subattr('mtu', 'default'))): + ifaceobjrunning.update_config('mtu', mtu) + alias = self.ipcmd.link_get_alias(ifaceobjrunning.name) + if alias: + ifaceobjrunning.update_config('alias', alias) + + _run_ops = {'up' : _up, + 'down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run address configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'up', '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 ifaceobj.addr_family and + ifaceobj.addr_family != 'inet' and + ifaceobj.addr_family != 'inet6'): + return + if (operation != 'query-running' and ifaceobj.addr_method and + ifaceobj.addr_method != 'static' and + ifaceobj.addr_method != 'loopback'): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/addressvirtual.py b/addons/addressvirtual.py new file mode 100644 index 0000000..7cd8a06 --- /dev/null +++ b/addons/addressvirtual.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import logging +import os +import glob + +class addressvirtual(moduleBase): + """ ifupdown2 addon module to configure virtual addresses """ + + _modinfo = {'mhelp' : 'address module configures virtual addresses for ' + + 'interfaces. It creates a macvlan interface for ' + + 'every mac ip address-virtual line', + 'attrs' : { + 'address-virtual' : + { 'help' : 'bridge router virtual mac and ip', + 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.254/24 11.0.1.254/24']} + }} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_supported(self, ifaceobj): + if ifaceobj.get_attr_value_first('address-virtual'): + return True + return False + + def _apply_address_config(self, ifaceobj, realifacename, address_virtual_list): + purge_existing = False if self.PERFMODE else True + + self.ipcmd.batch_start() + av_idx = 0 + macvlan_prefix = '%s-virt' %ifaceobj.name.replace('.', '-') + for av in address_virtual_list: + av_attrs = av.split() + if len(av_attrs) < 2: + self.logger.warn("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, av)) + av_idx += 1 + continue + + # Create a macvlan device on this device and set the virtual + # router mac and ip on it + macvlan_ifacename = '%s-%d' %(macvlan_prefix, av_idx) + self.ipcmd.link_create_macvlan(macvlan_ifacename, realifacename) + self.ipcmd.link_set_hwaddress(macvlan_ifacename, av_attrs[0]) + self.ipcmd.addr_add_multiple(macvlan_ifacename, av_attrs[1:], + purge_existing) + av_idx += 1 + self.ipcmd.batch_commit() + + def _remove_address_config(self, ifaceobj, ifacename): + if not self.ipcmd.link_exists(ifacename): + return + self.ipcmd.batch_start() + macvlan_prefix = '%s-virt' %ifacename.replace('.', '-') + for macvlan_ifacename in glob.glob("/sys/class/net/%s-*" %macvlan_prefix): + self.ipcmd.link_delete(os.path.basename(macvlan_ifacename)) + self.ipcmd.batch_commit() + + def _get_real_ifacename(self, ifaceobj): + realifacename = ifaceobj.name + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + bridgename = ifaceobj.get_attr_value_first('bridge') + if bridgename: + realifacename = '%s.%s' %(bridgename, ifaceobj.priv_data) + return realifacename + + def _up(self, ifaceobj): + realifacename = self._get_real_ifacename(ifaceobj) + address_virtual_list = ifaceobj.get_attr_value('address-virtual') + if not address_virtual_list: + # XXX: address virtual is not present. In which case, + # delete stale any macvlan devices. + self._remove_address_config(ifaceobj, realifacename) + return + + if not self.ipcmd.link_exists(realifacename): + self.log_warn('%s: target link %s does not exist' + %(ifaceobj.name, realifacename)) + return + self._apply_address_config(ifaceobj, realifacename, address_virtual_list) + + def _down(self, ifaceobj): + realifacename = self._get_real_ifacename(ifaceobj) + try: + self._remove_address_config(ifaceobj, realifacename) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + address_virtual_list = ifaceobj.get_attr_value('address-virtual') + if not address_virtual_list: + return + realifacename = self._get_real_ifacename(ifaceobj) + av_idx = 0 + macvlan_prefix = '%s-virt' %realifacename.replace('.', '-') + for address_virtual in address_virtual_list: + av_attrs = address_virtual.split() + if len(av_attrs) < 2: + self.logger.warn("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, address_virtual)) + av_idx += 1 + continue + + # Check if the macvlan device on this interface + macvlan_ifacename = '%s-%d' %(macvlan_prefix, av_idx) + if self.ipcmd.link_exists(macvlan_ifacename): + # XXX Check mac and ip address + rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename) + raddrs = self.ipcmd.addr_get(macvlan_ifacename) + if rhwaddress == av_attrs[0] and raddrs == av_attrs[1:]: + ifaceobjcurr.update_config_with_status('address-virtual', + address_virtual, 0) + else: + raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs)) + ifaceobjcurr.update_config_with_status('address-virtual', + raddress_virtual, 1) + av_idx += 1 + return + + def _query_running(self, ifaceobjrunning): + # Not implemented + return + + _run_ops = {'up' : _up, + 'down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run vlan 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 + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/bridge.py b/addons/bridge.py new file mode 100644 index 0000000..92c2d99 --- /dev/null +++ b/addons/bridge.py @@ -0,0 +1,895 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bridgeutils import brctl +from ifupdownaddons.iproute2 import iproute2 +import itertools +import re + +class bridge(moduleBase): + """ ifupdown2 addon module to configure linux bridges """ + + _modinfo = { 'mhelp' : 'bridge configuration module', + 'attrs' : { + 'bridge-ports' : + {'help' : 'bridge ports', + 'required' : True, + 'example' : ['bridge-ports swp1.100 swp2.100 swp3.100', + 'bridge-ports glob swp1-3.100', + 'bridge-ports regex (swp[1|2|3].100)']}, + 'bridge-stp' : + {'help': 'bridge-stp yes/no', + 'example' : ['bridge-stp no'], + 'validvals' : ['yes', 'on', 'off', 'no'], + 'default' : 'no'}, + 'bridge-bridgeprio' : + {'help': 'bridge priority', + 'example' : ['bridge-bridgeprio 32768'], + 'default' : '32768'}, + 'bridge-ageing' : + {'help': 'bridge ageing', + 'example' : ['bridge-ageing 300'], + 'default' : '300'}, + 'bridge-fd' : + { 'help' : 'bridge forward delay', + 'example' : ['bridge-fd 15'], + 'default' : '15'}, + 'bridge-gcint' : + # XXX: recheck values + { 'help' : 'bridge garbage collection interval in secs', + 'example' : ['bridge-gcint 4'], + 'default' : '4'}, + 'bridge-hello' : + { 'help' : 'bridge set hello time', + 'example' : ['bridge-hello 2'], + 'default' : '2'}, + 'bridge-maxage' : + { 'help' : 'bridge set maxage', + 'example' : ['bridge-maxage 20'], + 'default' : '20'}, + 'bridge-pathcosts' : + { 'help' : 'bridge set port path costs', + 'example' : ['bridge-pathcosts swp1=100 swp2=100'], + 'default' : '100'}, + 'bridge-portprios' : + { 'help' : 'bridge port prios', + 'example' : ['bridge-portprios swp1=32 swp2=32'], + 'default' : '32'}, + 'bridge-mclmc' : + { 'help' : 'set multicast last member count', + 'example' : ['bridge-mclmc 2'], + 'default' : '2'}, + 'bridge-mcrouter' : + { 'help' : 'set multicast router', + 'default' : '1', + 'example' : ['bridge-mcrouter 1']}, + 'bridge-mcsnoop' : + { 'help' : 'set multicast snooping', + 'default' : '1', + 'example' : ['bridge-mcsnoop 1']}, + 'bridge-mcsqc' : + { 'help' : 'set multicast startup query count', + 'default' : '2', + 'example' : ['bridge-mcsqc 2']}, + 'bridge-mcqifaddr' : + { 'help' : 'set multicast query to use ifaddr', + 'default' : '0', + 'example' : ['bridge-mcqifaddr 0']}, + 'bridge-mcquerier' : + { 'help' : 'set multicast querier', + 'default' : '0', + 'example' : ['bridge-mcquerier 0']}, + 'bridge-hashel' : + { 'help' : 'set hash elasticity', + 'default' : '4096', + 'example' : ['bridge-hashel 4096']}, + 'bridge-hashmax' : + { 'help' : 'set hash max', + 'default' : '4096', + 'example' : ['bridge-hashmax 4096']}, + 'bridge-mclmi' : + { 'help' : 'set multicast last member interval (in secs)', + 'default' : '1', + 'example' : ['bridge-mclmi 1']}, + 'bridge-mcmi' : + { 'help' : 'set multicast membership interval (in secs)', + 'default' : '260', + 'example' : ['bridge-mcmi 260']}, + 'bridge-mcqpi' : + { 'help' : 'set multicast querier interval (in secs)', + 'default' : '255', + 'example' : ['bridge-mcqpi 255']}, + 'bridge-mcqi' : + { 'help' : 'set multicast query interval (in secs)', + 'default' : '125', + 'example' : ['bridge-mcqi 125']}, + 'bridge-mcqri' : + { 'help' : 'set multicast query response interval (in secs)', + 'default' : '10', + 'example' : ['bridge-mcqri 10']}, + 'bridge-mcsqi' : + { 'help' : 'set multicast startup query interval (in secs)', + 'default' : '31', + 'example' : ['bridge-mcsqi 31']}, + 'bridge-mcqv4src' : + { 'help' : 'set per VLAN v4 multicast querier source address', + 'example' : ['bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1']}, + 'bridge-portmcrouter' : + { 'help' : 'set port multicast routers', + 'default' : '1', + 'example' : ['bridge-portmcrouter swp1=1 swp2=1']}, + 'bridge-portmcfl' : + { 'help' : 'port multicast fast leave', + 'default' : '0', + 'example' : ['bridge-portmcfl swp1=0 swp2=0']}, + 'bridge-waitport' : + { 'help' : 'wait for a max of time secs for the' + + ' specified ports to become available,' + + 'if no ports are specified then those' + + ' specified on bridge-ports will be' + + ' used here. Specifying no ports here ' + + 'should not be used if we are using ' + + 'regex or \"all\" on bridge_ports,' + + 'as it wouldnt work.', + 'default' : '0', + 'example' : ['bridge-waitport 4 swp1 swp2']}, + 'bridge-maxwait' : + { 'help' : 'forces to time seconds the maximum time ' + + 'that the Debian bridge setup scripts will ' + + 'wait for the bridge ports to get to the ' + + 'forwarding status, doesn\'t allow factional ' + + 'part. If it is equal to 0 then no waiting' + + ' is done', + 'default' : '0', + 'example' : ['bridge-maxwait 3']}, + 'bridge-vids' : + { 'help' : 'bridge vlans', + 'example' : ['bridge-vids 4000']}, + 'bridge-port-vids' : + { 'help' : 'bridge vlans', + 'example' : ['bridge-port-vids bond0=1-1000,1010-1020']}, + 'bridge-port-pvids' : + { 'help' : 'bridge port vlans', + 'example' : ['bridge-port-pvids bond0=100 bond1=200']}, + }} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.brctlcmd = None + + def _is_bridge(self, ifaceobj): + if ifaceobj.get_attr_value_first('bridge-ports'): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + if not self._is_bridge(ifaceobj): + return None + return self.parse_port_list(ifaceobj.get_attr_value_first( + 'bridge-ports'), ifacenames_all) + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + if not self.brctlcmd.bridge_exists(ifaceobj.name): + return None + return self.brctlcmd.get_bridge_ports(ifaceobj.name) + + def _get_bridge_port_list(self, ifaceobj): + + # port list is also available in the previously + # parsed dependent list. Use that if available, instead + # of parsing port expr again + port_list = ifaceobj.lowerifaces + if port_list: + return port_list + ports = ifaceobj.get_attr_value_first('bridge-ports') + if ports: + return self.parse_port_list(ports) + else: + return None + + def _process_bridge_waitport(self, ifaceobj, portlist): + waitport_value = ifaceobj.get_attr_value_first('bridge-waitport') + if not waitport_value: return + try: + waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1) + if not waitportvals: return + try: + waitporttime = int(waitportvals[0]) + except: + self.log_warn('%s: invalid waitport value \'%s\'' + %(ifaceobj.name, waitporttime)) + return + if waitporttime <= 0: return + try: + waitportlist = self.parse_port_list(waitportvals[1]) + except IndexError, e: + # ignore error and use all bridge ports + waitportlist = portlist + pass + if not waitportlist: return + self.logger.info('%s: waiting for ports %s to exist ...' + %(ifaceobj.name, str(waitportlist))) + starttime = time.time() + while ((time.time() - starttime) < waitporttime): + if all([False for p in waitportlist + if not self.ipcmd.link_exists(p)]): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process waitport: %s' + %(ifaceobj.name, str(e))) + + def _add_ports(self, ifaceobj): + bridgeports = self._get_bridge_port_list(ifaceobj) + runningbridgeports = [] + + self._process_bridge_waitport(ifaceobj, bridgeports) + # Delete active ports not in the new port list + if not self.PERFMODE: + runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if runningbridgeports: + [self.ipcmd.link_set(bport, 'nomaster') + for bport in runningbridgeports + if not bridgeports or bport not in bridgeports] + else: + runningbridgeports = [] + if not bridgeports: + return + err = 0 + for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): + try: + if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport): + self.log_warn('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport)) + err += 1 + continue + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) + self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport + + '/disable_ipv6', '1') + self.ipcmd.addr_flush(bridgeport) + except Exception, e: + self.log_error(str(e)) + if err: + self.log_error('bridge configuration failed (missing ports)') + + def _process_bridge_maxwait(self, ifaceobj, portlist): + maxwait = ifaceobj.get_attr_value_first('bridge-maxwait') + if not maxwait: return + try: + maxwait = int(maxwait) + except: + self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name, + maxwait)) + return + if not maxwait: return + self.logger.info('%s: waiting for ports to go to fowarding state ..' + %ifaceobj.name) + try: + starttime = time.time() + while ((time.time() - starttime) < maxwait): + if all([False for p in portlist + if self.read_file_oneline( + '/sys/class/net/%s/brif/%s/state' + %(ifaceobj.name, p)) != '3']): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process maxwait: %s' + %(ifaceobj.name, str(e))) + + def _ints_to_ranges(self, ints): + for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x): + b = list(b) + yield b[0][1], b[-1][1] + + def _ranges_to_ints(self, rangelist): + """ returns expanded list of integers given set of string ranges + example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6] + """ + result = [] + for part in rangelist: + if '-' in part: + a, b = part.split('-') + a, b = int(a), int(b) + result.extend(range(a, b + 1)) + else: + a = int(part) + result.append(a) + return result + + def _diff_vids(self, vids1, vids2): + vids_to_add = None + vids_to_del = None + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + vids1_diff = Set(vids1_ints).difference(vids2_ints) + vids2_diff = Set(vids2_ints).difference(vids1_ints) + if vids1_diff: + vids_to_add = ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids1_diff)] + if vids2_diff: + vids_to_del = ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids2_diff)] + return (vids_to_del, vids_to_add) + + def _compare_vids(self, vids1, vids2): + """ Returns true if the vids are same else return false """ + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + if Set(vids1_ints).symmetric_difference(vids2_ints): + return False + else: + return True + + def _set_bridge_mcqv4src(self, ifaceobj): + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + running_mcqv4src = {} + if not self.PERFMODE: + running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name) + mcqs = {} + srclist = attrval.split() + for s in srclist: + k, v = s.split('=') + mcqs[k] = v + + k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys()) + for v in k_to_del: + self.brctlcmd.del_mcqv4src(ifaceobj.name, v) + for v in mcqs.keys(): + self.brctlcmd.set_mcqv4src(ifaceobj.name, v, mcqs[v]) + + def _set_bridge_vidinfo(self, ifaceobj): + # Handle bridge vlan attrs + running_vidinfo = {} + if not self.PERFMODE: + running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + attrval = ifaceobj.get_attr_value_first('bridge-port-vids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + if running_vidinfo.get(port): + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids, + running_vidinfo.get(port).get('vlan')) + if vids_to_del: + self.ipcmd.bridge_port_vids_del(port, vids_to_del) + if vids_to_add: + self.ipcmd.bridge_port_vids_add(port, vids_to_add) + else: + self.ipcmd.bridge_port_vids_add(port, vids) + except Exception, e: + self.log_warn('%s: failed to set vid `%s` (%s)' + %(ifaceobj.name, p, str(e))) + + # Install pvids + attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + for p in portlist: + try: + (port, pvid) = p.split('=') + running_pvid = running_vidinfo.get(port, {}).get('pvid') + if running_pvid: + if running_pvid == pvid: + continue + else: + self.ipcmd.bridge_port_pvid_del(port, running_pvid) + self.ipcmd.bridge_port_pvid_add(port, pvid) + except Exception, e: + self.log_warn('%s: failed to set pvid `%s` (%s)' + %(ifaceobj.name, p, str(e))) + + attrval = ifaceobj.get_attr_value_first('bridge-vids') + if attrval: + vids = re.split(r'[\s\t]\s*', attrval) + if running_vidinfo.get(ifaceobj.name): + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids, + running_vidinfo.get(ifaceobj.name).get('vlan')) + if vids_to_del: + self.ipcmd.bridge_vids_del(ifaceobj.name, vids_to_del) + if vids_to_add: + self.ipcmd.bridge_vids_add(ifaceobj.name, vids_to_add) + else: + self.ipcmd.bridge_vids_add(ifaceobj.name, vids) + else: + running_vids = running_vidinfo.get(ifaceobj.name) + if running_vids: + self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids) + + def _apply_bridge_settings(self, ifaceobj): + try: + stp = ifaceobj.get_attr_value_first('bridge-stp') + if stp: + self.brctlcmd.set_stp(ifaceobj.name, stp) + # Use the brctlcmd bulk set method: first build a dictionary + # and then call set + bridgeattrs = { k:v for k,v in + {'ageing' : + ifaceobj.get_attr_value_first('bridge-ageing'), + 'bridgeprio' : + ifaceobj.get_attr_value_first( + 'bridge-bridgeprio'), + 'fd' : + ifaceobj.get_attr_value_first('bridge-fd'), + 'gcint' : + ifaceobj.get_attr_value_first('bridge-gcint'), + 'hello' : + ifaceobj.get_attr_value_first('bridge-hello'), + 'maxage' : + ifaceobj.get_attr_value_first('bridge-maxage'), + 'mclmc' : + ifaceobj.get_attr_value_first('bridge-mclmc'), + 'mcrouter' : + ifaceobj.get_attr_value_first( + 'bridge-mcrouter'), + 'mcsnoop' : + ifaceobj.get_attr_value_first('bridge-mcsnoop'), + 'mcsqc' : + ifaceobj.get_attr_value_first('bridge-mcsqc'), + 'mcqifaddr' : + ifaceobj.get_attr_value_first( + 'bridge-mcqifaddr'), + 'mcquerier' : + ifaceobj.get_attr_value_first( + 'bridge-mcquerier'), + 'hashel' : + ifaceobj.get_attr_value_first('bridge-hashel'), + 'hashmax' : + ifaceobj.get_attr_value_first('bridge-hashmax'), + 'mclmi' : + ifaceobj.get_attr_value_first('bridge-mclmi'), + 'mcmi' : + ifaceobj.get_attr_value_first('bridge-mcmi'), + 'mcqpi' : + ifaceobj.get_attr_value_first('bridge-mcqpi'), + 'mcqi' : + ifaceobj.get_attr_value_first('bridge-mcqi'), + 'mcqri' : + ifaceobj.get_attr_value_first('bridge-mcqri'), + 'mcsqi' : + ifaceobj.get_attr_value_first('bridge-mcsqi') + }.items() + if v } + if bridgeattrs: + self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs) + portattrs = {} + for attrname, dstattrname in {'bridge-pathcosts' : 'pathcost', + 'bridge-portprios' : 'portprio', + 'bridge-portmcrouter' : 'portmcrouter', + 'bridge-portmcfl' : 'portmcfl'}.items(): + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + continue + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + continue + for p in portlist: + try: + (port, val) = p.split('=') + if not portattrs.get(port): + portattrs[port] = {} + portattrs[port].update({dstattrname : val}) + except Exception, e: + self.log_warn('%s: could not parse %s (%s)' + %(ifaceobj.name, attrname, str(e))) + for port, attrdict in portattrs.iteritems(): + self.brctlcmd.set_bridgeport_attrs(ifaceobj.name, port, + attrdict) + self._set_bridge_vidinfo(ifaceobj) + + self._set_bridge_mcqv4src(ifaceobj) + + self._process_bridge_maxwait(ifaceobj, + self._get_bridge_port_list(ifaceobj)) + except Exception, e: + self.log_warn(str(e)) + + def _up(self, ifaceobj): + try: + porterr = False + porterrstr = '' + self.ipcmd.batch_start() + if not self.PERFMODE: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'bridge') + else: + self.ipcmd.link_create(ifaceobj.name, 'bridge') + try: + self._add_ports(ifaceobj) + except Exception, e: + porterr = True + porterrstr = str(e) + pass + finally: + self.ipcmd.batch_commit() + self._apply_bridge_settings(ifaceobj) + except Exception, e: + self.log_error(str(e)) + if porterr: + raise Exception(porterrstr) + + def _down(self, ifaceobj): + try: + if ifaceobj.get_attr_value_first('bridge-ports'): + ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if ports: + for p in ports: + proc_file = ('/proc/sys/net/ipv6/conf/%s' %p + + '/disable_ipv6') + self.write_file(proc_file, '0') + self.brctlcmd.delete_bridge(ifaceobj.name) + except Exception, e: + self.log_error(str(e)) + + def _query_running_vidinfo(self, ifaceobjrunning, ports): + running_attrs = {} + running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + + if ports: + running_bridge_port_vids = '' + for p in ports: + try: + running_vids = running_vidinfo.get(p, {}).get('vlan') + if running_vids: + running_bridge_port_vids += ' %s=%s' %(p, + ','.join(running_vids)) + except Exception: + pass + running_attrs['bridge-port-vids'] = running_bridge_port_vids + + running_bridge_port_pvids = '' + for p in ports: + try: + running_pvids = running_vidinfo.get(p, {}).get('pvid') + if running_pvids: + running_bridge_port_pvids += ' %s=%s' %(p, + running_pvids) + except Exception: + pass + running_attrs['bridge-port-pvids'] = running_bridge_port_pvids + + running_bridge_vids = running_vidinfo.get(ifaceobjrunning.name, {}).get('vlan') + if running_bridge_vids: + running_attrs['bridge-vids'] = ','.join(running_bridge_vids) + return running_attrs + + def _query_running_mcqv4src(self, ifaceobjrunning): + running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobjrunning.name) + mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()] + mcqs.sort() + mcq = ' '.join(mcqs) + return mcq + + def _query_running_attrs(self, ifaceobjrunning): + bridgeattrdict = {} + userspace_stp = 0 + ports = None + skip_kernel_stp_attrs = 0 + + if self.sysctl_get('net.bridge.bridge-stp-user-space') == '1': + userspace_stp = 1 + + tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name) + if not tmpbridgeattrdict: + self.logger.warn('%s: unable to get bridge attrs' + %ifaceobjrunning.name) + return bridgeattrdict + + # Fill bridge_ports and bridge stp attributes first + ports = tmpbridgeattrdict.get('ports') + if ports: + bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())] + stp = tmpbridgeattrdict.get('stp', 'no') + if stp != self.get_mod_subattr('bridge-stp', 'default'): + bridgeattrdict['bridge-stp'] = [stp] + + if stp == 'yes' and userspace_stp: + skip_kernel_stp_attrs = 1 + + # pick all other attributes + for k,v in tmpbridgeattrdict.items(): + if not v: + continue + if k == 'ports' or k == 'stp': + continue + + if skip_kernel_stp_attrs and k[:2] != 'mc': + # only include igmp attributes if kernel stp is off + continue + attrname = 'bridge-' + k + if v != self.get_mod_subattr(attrname, 'default'): + bridgeattrdict[attrname] = [v] + + bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, ports) + if bridgevidinfo: + bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items() + if v}) + + mcq = self._query_running_mcqv4src(ifaceobjrunning) + if mcq: + bridgeattrdict['bridge-mcqv4src'] = [mcq] + + if skip_kernel_stp_attrs: + return bridgeattrdict + + if ports: + portconfig = {'bridge-pathcosts' : '', + 'bridge-portprios' : ''} + for p, v in ports.items(): + v = self.brctlcmd.get_pathcost(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-pathcosts', + 'default'): + portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v) + + v = self.brctlcmd.get_portprio(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-portprios', + 'default'): + portconfig['bridge-portprios'] += ' %s=%s' %(p, v) + + bridgeattrdict.update({k : [v] for k, v in portconfig.items() + if v}) + + return bridgeattrdict + + def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr): + running_mcqs = self._query_running_mcqv4src(ifaceobj) + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + mcqs = attrval.split() + mcqs.sort() + mcqsout = ' '.join(mcqs) + ifaceobjcurr.update_config_with_status('bridge-mcqv4src', + running_mcqs, 1 if running_mcqs != mcqsout else 0) + + def _query_check_vidinfo(self, ifaceobj, ifaceobjcurr): + + err = 0 + running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + attrval = ifaceobj.get_attr_value_first('bridge-port-vids') + if attrval: + running_bridge_port_vids = '' + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + err = 0 + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + running_vids = running_vidinfo.get(port, {}).get('vlan') + if running_vids: + if not self._compare_vids(vids, running_vids): + err += 1 + running_bridge_port_vids += ' %s=%s' %(port, + ','.join(running_vids)) + else: + running_bridge_port_vids += ' %s' %p + else: + err += 1 + except Exception, e: + self.log_warn('%s: failure checking vid %s (%s)' + %(ifaceobj.name, p, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + running_bridge_port_vids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + attrval, 0) + + # Install pvids + attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + running_bridge_port_pvids = '' + err = 0 + for p in portlist: + try: + (port, pvid) = p.split('=') + running_pvid = running_vidinfo.get(port, {}).get('pvid') + if running_pvid and running_pvid == pvid: + running_bridge_port_pvids += ' %s' %p + else: + err += 1 + running_bridge_port_pvids += ' %s=%s' %(port, + running_pvid) + except Exception, e: + self.log_warn('%s: failure checking pvid %s (%s)' + %(ifaceobj.name, pvid, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 0) + + attrval = ifaceobj.get_attr_value_first('bridge-vids') + if attrval: + vids = re.split(r'[\s\t]\s*', attrval) + running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan') + if running_vids: + if self._compare_vids(vids, running_vids): + ifaceobjcurr.update_config_with_status('bridge-vids', + attrval, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-vids', + ','.join(running_vids), 1) + else: + ifaceobjcurr.update_config_with_status('bridge-vids', attrval, + 1) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.brctlcmd.bridge_exists(ifaceobj.name): + self.logger.info('%s: bridge: does not exist' %(ifaceobj.name)) + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: + return + try: + runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + self.logger.debug('%s: bridge: unable to get bridge attrs' + %ifaceobj.name) + runningattrs = {} + except Exception, e: + self.logger.warn(str(e)) + runningattrs = {} + filterattrs = ['bridge-vids', 'bridge-port-vids', + 'bridge-port-pvids'] + for k in Set(ifaceattrs).difference(filterattrs): + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + rv = runningattrs.get(k[7:]) + if k == 'bridge-mcqv4src': + continue + if k == 'bridge-stp': + # special case stp compare because it may + # contain more than one valid values + stp_on_vals = ['on', 'yes'] + stp_off_vals = ['off'] + if ((v in stp_on_vals and rv in stp_on_vals) or + (v in stp_off_vals and rv in stp_off_vals)): + ifaceobjcurr.update_config_with_status('bridge-stp', + v, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-stp', + v, 1) + elif k == 'bridge-ports': + # special case ports because it can contain regex or glob + running_port_list = rv.keys() if rv else [] + bridge_port_list = self._get_bridge_port_list(ifaceobj) + if not running_port_list and not bridge_port_list: + continue + portliststatus = 1 + if running_port_list and bridge_port_list: + difference = set(running_port_list + ).symmetric_difference(bridge_port_list) + if not difference: + portliststatus = 0 + ifaceobjcurr.update_config_with_status('bridge-ports', + ' '.join(running_port_list) + if running_port_list else '', portliststatus) + elif (k == 'bridge-pathcosts' or + k == 'bridge-portprios' or k == 'bridge-portmcrouter' + or k == 'bridge-portmcfl'): + brctlcmdattrname = k[11:].rstrip('s') + # for port attributes, the attributes are in a list + # = + status = 0 + currstr = '' + vlist = self.parse_port_list(v) + if not vlist: + continue + for vlistitem in vlist: + try: + (p, v) = vlistitem.split('=') + currv = self.brctlcmd.get_bridgeport_attr( + ifaceobj.name, p, + brctlcmdattrname) + if currv: + currstr += ' %s=%s' %(p, currv) + else: + currstr += ' %s=%s' %(p, 'None') + if currv != v: + status = 1 + except Exception, e: + self.log_warn(str(e)) + pass + ifaceobjcurr.update_config_with_status(k, currstr, status) + elif not rv: + ifaceobjcurr.update_config_with_status(k, 'notfound', 1) + continue + elif v != rv: + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + + self._query_check_vidinfo(ifaceobj, ifaceobjcurr) + + self._query_check_mcqv4src(ifaceobj, ifaceobjcurr) + + def _query_running(self, ifaceobjrunning): + if not self.brctlcmd.bridge_exists(ifaceobjrunning.name): + return + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning)) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.brctlcmd: + self.brctlcmd = brctl(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run bridge configuration on the interface object passed as + argument. Can create bridge interfaces if they dont exist already + + 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_bridge(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/bridgevlanaware.py b/addons/bridgevlanaware.py new file mode 100644 index 0000000..74076e1 --- /dev/null +++ b/addons/bridgevlanaware.py @@ -0,0 +1,942 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bridgeutils import brctl +from ifupdownaddons.iproute2 import iproute2 +import itertools +import re +import os + +class bridgevlanaware(moduleBase): + """ ifupdown2 addon module to configure linux bridges """ + + _modinfo = { 'mhelp' : 'bridge configuration module', + 'attrs' : { + 'bridge' : + {'help': 'bridge this interface is part of', + 'example' : ['bridge br0']}, + 'bridge-vlan-aware' : + {'help': 'Is this a vlan aware bridge ?', + 'default' : 'no', + 'required' : False, + 'example' : ['bridge-vlan-aware yes']}, + 'type' : + {'help': 'type of interface this module supports', + 'example' : ['type bridge']}, + 'bridge-stp' : + {'help': 'bridge-stp yes/no', + 'example' : ['bridge-stp no'], + 'validvals' : ['yes', 'on', 'off', 'no'], + 'default' : 'no'}, + 'bridge-bridgeprio' : + {'help': 'bridge priority', + 'example' : ['bridge-bridgeprio 32768'], + 'default' : '32768'}, + 'bridge-ageing' : + {'help': 'bridge ageing', + 'example' : ['bridge-ageing 300'], + 'default' : '300'}, + 'bridge-fd' : + { 'help' : 'bridge forward delay', + 'example' : ['bridge-fd 15'], + 'default' : '15'}, + 'bridge-gcint' : + # XXX: recheck values + { 'help' : 'bridge garbage collection interval in secs', + 'example' : ['bridge-gcint 4'], + 'default' : '4'}, + 'bridge-hello' : + { 'help' : 'bridge set hello time', + 'example' : ['bridge-hello 2'], + 'default' : '2'}, + 'bridge-maxage' : + { 'help' : 'bridge set maxage', + 'example' : ['bridge-maxage 20'], + 'default' : '20'}, + 'bridge-pathcosts' : + { 'help' : 'bridge set port path costs', + 'example' : ['bridge-pathcosts swp1=100 swp2=100'], + 'default' : '100'}, + 'bridge-priority' : + { 'help' : 'bridge port priority', + 'example' : ['bridge-priority 32'], + 'default' : '32'}, + 'bridge-mclmc' : + { 'help' : 'set multicast last member count', + 'example' : ['bridge-mclmc 2'], + 'default' : '2'}, + 'bridge-mcrouter' : + { 'help' : 'set multicast router', + 'default' : '1', + 'example' : ['bridge-mcrouter 1']}, + 'bridge-mcsnoop' : + { 'help' : 'set multicast snooping', + 'default' : '1', + 'example' : ['bridge-mcsnoop 1']}, + 'bridge-mcsqc' : + { 'help' : 'set multicast startup query count', + 'default' : '2', + 'example' : ['bridge-mcsqc 2']}, + 'bridge-mcqifaddr' : + { 'help' : 'set multicast query to use ifaddr', + 'default' : '0', + 'example' : ['bridge-mcqifaddr 0']}, + 'bridge-mcquerier' : + { 'help' : 'set multicast querier', + 'default' : '0', + 'example' : ['bridge-mcquerier 0']}, + 'bridge-hashel' : + { 'help' : 'set hash elasticity', + 'default' : '4096', + 'example' : ['bridge-hashel 4096']}, + 'bridge-hashmax' : + { 'help' : 'set hash max', + 'default' : '4096', + 'example' : ['bridge-hashmax 4096']}, + 'bridge-mclmi' : + { 'help' : 'set multicast last member interval (in secs)', + 'default' : '1', + 'example' : ['bridge-mclmi 1']}, + 'bridge-mcmi' : + { 'help' : 'set multicast membership interval (in secs)', + 'default' : '260', + 'example' : ['bridge-mcmi 260']}, + 'bridge-mcqpi' : + { 'help' : 'set multicast querier interval (in secs)', + 'default' : '255', + 'example' : ['bridge-mcqpi 255']}, + 'bridge-mcqi' : + { 'help' : 'set multicast query interval (in secs)', + 'default' : '125', + 'example' : ['bridge-mcqi 125']}, + 'bridge-mcqri' : + { 'help' : 'set multicast query response interval (in secs)', + 'default' : '10', + 'example' : ['bridge-mcqri 10']}, + 'bridge-mcsqi' : + { 'help' : 'set multicast startup query interval (in secs)', + 'default' : '31', + 'example' : ['bridge-mcsqi 31']}, + 'bridge-mcqv4src' : + { 'help' : 'set per VLAN v4 multicast querier source address', + 'compat' : True, + 'example' : ['bridge-mcqv4src 172.16.100.1']}, + 'bridge-igmp-querier-src' : + { 'help' : 'set per VLAN v4 multicast querier source address', + 'example' : ['bridge-igmp-querier-src 172.16.100.1']}, + 'bridge-mcfl' : + { 'help' : 'port multicast fast leave', + 'default' : '0', + 'example' : ['bridge-mcfl 0']}, + 'bridge-waitport' : + { 'help' : 'wait for a max of time secs for the' + + ' specified ports to become available,' + + 'if no ports are specified then those' + + ' specified on bridge-ports will be' + + ' used here. Specifying no ports here ' + + 'should not be used if we are using ' + + 'regex or \"all\" on bridge_ports,' + + 'as it wouldnt work.', + 'default' : '0', + 'example' : ['bridge-waitport 4 swp1 swp2']}, + 'bridge-maxwait' : + { 'help' : 'forces to time seconds the maximum time ' + + 'that the Debian bridge setup scripts will ' + + 'wait for the bridge ports to get to the ' + + 'forwarding status, doesn\'t allow factional ' + + 'part. If it is equal to 0 then no waiting' + + ' is done', + 'default' : '0', + 'example' : ['bridge-maxwait 3']}, + 'bridge-vlan' : + { 'help' : 'bridge vlans', + 'example' : ['bridge-vlan 4000']}, + 'bridge-vlan-native' : + { 'compat' : True, + 'help' : 'bridge port vlan', + 'example' : ['bridge-vlan-native 1']}, + }} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.brctlcmd = None + + def _is_bridge_port(self, ifaceobj): + if ifaceobj.get_attr_value_first('bridge'): + return True + return False + + def _is_bridge(self, ifaceobj): + if ifaceobj.get_attr_value_first('type') == 'bridge': + return True + return False + + def _is_bridge_vlan(self, ifaceobj): + if ifaceobj.type & ifaceType.BRIDGE_VLAN: + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + bridge = ifaceobj.get_attr_value_first('bridge') + if bridge: + match = re.match("^%s-([\d]+)" %bridge, ifaceobj.name) + if match: + ifaceobj.priv_data = int(match.groups()[0], 10) + # XXX: mark this iface as a bridge_vlan iface + ifaceobj.type = ifaceType.BRIDGE_VLAN + return [bridge] + + return None + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + if not self.brctlcmd.bridge_exists(ifaceobj.name): + return None + return self.brctlcmd.get_bridge_ports(ifaceobj.name) + + def _process_bridge_waitport(self, ifaceobj, portlist): + waitport_value = ifaceobj.get_attr_value_first('bridge-waitport') + if not waitport_value: return + try: + waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1) + if not waitportvals: return + try: + waitporttime = int(waitportvals[0]) + except: + self.log_warn('%s: invalid waitport value \'%s\'' + %(ifaceobj.name, waitporttime)) + return + if waitporttime <= 0: return + try: + waitportlist = self.parse_port_list(waitportvals[1]) + except IndexError, e: + # ignore error and use all bridge ports + waitportlist = portlist + pass + if not waitportlist: return + self.logger.info('%s: waiting for ports %s to exist ...' + %(ifaceobj.name, str(waitportlist))) + starttime = time.time() + while ((time.time() - starttime) < waitporttime): + if all([False for p in waitportlist + if not self.ipcmd.link_exists(p)]): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process waitport: %s' + %(ifaceobj.name, str(e))) + + def _add_ports(self, ifaceobj): + bridgeports = self._get_bridge_port_list(ifaceobj) + runningbridgeports = [] + + self._process_bridge_waitport(ifaceobj, bridgeports) + # Delete active ports not in the new port list + if not self.PERFMODE: + runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if runningbridgeports: + [self.ipcmd.link_set(bport, 'nomaster') + for bport in runningbridgeports + if not bridgeports or bport not in bridgeports] + else: + runningbridgeports = [] + if not bridgeports: + return + err = 0 + for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): + try: + if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport): + self.log_warn('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport)) + err += 1 + continue + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) + self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport + + '/disable_ipv6', '1') + self.ipcmd.addr_flush(bridgeport) + except Exception, e: + self.log_error(str(e)) + if err: + self.log_error('bridge configuration failed (missing ports)') + + def _process_bridge_maxwait(self, ifaceobj, portlist): + maxwait = ifaceobj.get_attr_value_first('bridge-maxwait') + if not maxwait: return + try: + maxwait = int(maxwait) + except: + self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name, + maxwait)) + return + if not maxwait: return + self.logger.info('%s: waiting for ports to go to fowarding state ..' + %ifaceobj.name) + try: + starttime = time.time() + while ((time.time() - starttime) < maxwait): + if all([False for p in portlist + if self.read_file_oneline( + '/sys/class/net/%s/brif/%s/state' + %(ifaceobj.name, p)) != '3']): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process maxwait: %s' + %(ifaceobj.name, str(e))) + + def _ints_to_ranges(self, ints): + for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x): + b = list(b) + yield b[0][1], b[-1][1] + + def _ranges_to_ints(self, rangelist): + """ returns expanded list of integers given set of string ranges + example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6] + """ + result = [] + for part in rangelist: + if '-' in part: + a, b = part.split('-') + a, b = int(a), int(b) + result.extend(range(a, b + 1)) + else: + a = int(part) + result.append(a) + return result + + def _diff_vids(self, vids1, vids2): + vids_to_add = None + vids_to_del = None + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + vids1_diff = Set(vids1_ints).difference(vids2_ints) + vids2_diff = Set(vids2_ints).difference(vids1_ints) + if vids1_diff: + vids_to_add = ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids1_diff)] + if vids2_diff: + vids_to_del = ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids2_diff)] + return (vids_to_del, vids_to_add) + + def _compare_vids(self, vids1, vids2): + """ Returns true if the vids are same else return false """ + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + if Set(vids1_ints).symmetric_difference(vids2_ints): + return False + else: + return True + + def _set_bridge_mcqv4src(self, ifaceobj): + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + running_mcqv4src = {} + if not self.PERFMODE: + running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name) + mcqs = {} + srclist = attrval.split() + for s in srclist: + k, v = s.split('=') + mcqs[k] = v + + k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys()) + for v in k_to_del: + self.brctlcmd.del_mcqv4src(ifaceobj.name, int(v, 10)) + for v in mcqs.keys(): + self.brctlcmd.set_mcqv4src(ifaceobj.name, int(v, 10), mcqs[v]) + + def _set_bridge_vidinfo(self, ifaceobj, isbridge=True): + # Handle bridge vlan attrs + running_vidinfo = {} + if not self.PERFMODE: + running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + attrval = ifaceobj.get_attr_value_first('bridge-vlan') + if attrval: + vids = re.split(r'[\s\t]\s*', attrval) + #vids = attrval.split(',') + try: + if running_vidinfo.get(ifaceobj.name): + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids, + running_vidinfo.get(ifaceobj.name, {}).get('vlan')) + if vids_to_del: + self.ipcmd.bridge_vids_del(ifaceobj.name, + vids_to_del, isbridge) + if vids_to_add: + self.ipcmd.bridge_vids_add(ifaceobj.name, + vids_to_add, isbridge) + else: + self.ipcmd.bridge_vids_add(ifaceobj.name, vids, + isbridge) + except Exception, e: + self.log_warn('%s: failed to set vid `%s` (%s)' + %(ifaceobj.name, str(vids), str(e))) + else: + running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan') + if running_vids: + self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids) + + # Install pvids + pvid = ifaceobj.get_attr_value_first('bridge-vlan-native') + if pvid: + try: + running_pvid = running_vidinfo.get(ifaceobj.name, + {}).get('pvid') + if running_pvid: + if running_pvid != pvid: + self.ipcmd.bridge_port_pvid_del(ifaceobj.name, + running_pvid) + else: + self.ipcmd.bridge_port_pvid_add(ifaceobj.name, pvid) + else: + self.ipcmd.bridge_port_pvid_add(ifaceobj.name, pvid) + except Exception, e: + self.log_warn('%s: failed to set pvid `%s` (%s)' + %(ifaceobj.name, pvid, str(e))) + + def _apply_bridge_settings(self, ifaceobj): + try: + stp = ifaceobj.get_attr_value_first('bridge-stp') + if stp: + self.brctlcmd.set_stp(ifaceobj.name, stp) + # Use the brctlcmd bulk set method: first build a dictionary + # and then call set + bridgeattrs = { k:v for k,v in + {'ageing' : + ifaceobj.get_attr_value_first('bridge-ageing'), + 'bridgeprio' : + ifaceobj.get_attr_value_first( + 'bridge-bridgeprio'), + 'fd' : + ifaceobj.get_attr_value_first('bridge-fd'), + 'gcint' : + ifaceobj.get_attr_value_first('bridge-gcint'), + 'hello' : + ifaceobj.get_attr_value_first('bridge-hello'), + 'maxage' : + ifaceobj.get_attr_value_first('bridge-maxage'), + 'mclmc' : + ifaceobj.get_attr_value_first('bridge-mclmc'), + 'mcrouter' : + ifaceobj.get_attr_value_first( + 'bridge-mcrouter'), + 'mcsnoop' : + ifaceobj.get_attr_value_first('bridge-mcsnoop'), + 'mcsqc' : + ifaceobj.get_attr_value_first('bridge-mcsqc'), + 'mcqifaddr' : + ifaceobj.get_attr_value_first( + 'bridge-mcqifaddr'), + 'mcquerier' : + ifaceobj.get_attr_value_first( + 'bridge-mcquerier'), + 'hashel' : + ifaceobj.get_attr_value_first('bridge-hashel'), + 'hashmax' : + ifaceobj.get_attr_value_first('bridge-hashmax'), + 'mclmi' : + ifaceobj.get_attr_value_first('bridge-mclmi'), + 'mcmi' : + ifaceobj.get_attr_value_first('bridge-mcmi'), + 'mcqpi' : + ifaceobj.get_attr_value_first('bridge-mcqpi'), + 'mcqi' : + ifaceobj.get_attr_value_first('bridge-mcqi'), + 'mcqri' : + ifaceobj.get_attr_value_first('bridge-mcqri'), + 'mcsqi' : + ifaceobj.get_attr_value_first('bridge-mcsqi') + }.items() + if v } + if bridgeattrs: + self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs) + self._set_bridge_vidinfo(ifaceobj) + + self._set_bridge_mcqv4src(ifaceobj) + + #self._process_bridge_maxwait(ifaceobj, + # self._get_bridge_port_list(ifaceobj)) + except Exception, e: + self.log_warn(str(e)) + + def _apply_bridge_port_settings(self, ifaceobj, bridge): + try: + # Use the brctlcmd bulk set method: first build a dictionary + # and then call set + portattrs = {} + for attrname, dstattrname in {'bridge-pathcost' : 'pathcost', + 'bridge-prio' : 'portprio', + 'bridge-mcrouter' : 'portmcrouter', + 'bridge-mcfl' : 'portmcfl'}.items(): + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + continue + portattrs[ifaceobj.name] = attrval + self.brctlcmd.set_bridgeport_attrs(bridge, ifaceobj.name, portattrs) + self._set_bridge_vidinfo(ifaceobj, isbridge=False) + except Exception, e: + self.log_warn(str(e)) + + def _apply_bridge_vlan_settings(self, ifaceobj, bridge): + mcq = ifaceobj.get_attrs_value_first(['bridge-mcqv4src', + 'bridge-igmp-querier-src']) + if mcq: + running_mcq = None + if not self.PERFMODE: + running_mcq = self.brctlcmd.get_mcqv4src(bridge, + vlan=ifaceobj.priv_data) + if running_mcq != mcq: + self.brctlcmd.set_mcqv4src(bridge, ifaceobj.priv_data, mcq) + + self.ipcmd.bridge_vids_add(bridge, [ifaceobj.priv_data], True) + + def _delete_bridge_vlan_settings(self, ifaceobj, bridge): + # delete vlan from bridge + self.ipcmd.bridge_vids_del(bridge, [ifaceobj.priv_data], True) + + mcq = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if mcq: + self.brctlcmd.del_mcqv4src(bridge, ifaceobj.name) + + def _up_bridge(self, ifaceobj): + try: + if not self.PERFMODE: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'bridge') + else: + self.ipcmd.link_create(ifaceobj.name, 'bridge') + self._apply_bridge_settings(ifaceobj) + except Exception, e: + self.log_error(str(e)) + + def _up_bridge_port(self, ifaceobj, bridge): + try: + self.ipcmd.link_set(ifaceobj.name, 'master', bridge) + self._apply_bridge_port_settings(ifaceobj, bridge) + except Exception, e: + self.log_error(str(e)) + + def _up_bridge_vlan(self, ifaceobj, bridge): + purge_existing = False if self.PERFMODE else True + try: + address = ifaceobj.get_attr_value('address') + if address: + # Create a vlan device, + ifacename = '%s.%s' %(bridge, ifaceobj.priv_data) + if not self.ipcmd.link_exists(ifacename): + self.ipcmd.link_create_vlan(ifacename, bridge, + ifaceobj.priv_data) + purge_existing = False + hwaddress = ifaceobj.get_attr_value_first('hwaddress') + if hwaddress: + self.ipcmd.link_set_hwaddress(ifacename, hwaddress) + self.ipcmd.addr_add_multiple(ifacename, address, purge_existing) + self._apply_bridge_vlan_settings(ifaceobj, bridge) + except Exception, e: + self.log_error(str(e)) + + def _up(self, ifaceobj): + bridge = ifaceobj.get_attr_value_first('bridge') + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + self._up_bridge_vlan(ifaceobj, bridge) + elif bridge: + self._up_bridge_port(ifaceobj, bridge) + elif self._is_bridge(ifaceobj): + self._up_bridge(ifaceobj) + else: + # Was this interface part of the bridge at some point and now + # got removed ?. If we attached it to the bridge last time + # we should release it + if os.path.exists('/sys/class/net/%s/brport' %ifaceobj.name): + bridgelink = os.readlink('/sys/class/net/%s/brport/bridge' + %ifaceobj.name) + if bridgelink: + bridge = os.path.basename(bridgelink) + if (not ifaceobj.upperifaces or + bridge not in ifaceobj.upperifaces): + # set nomaster + self.ipcmd.link_set(ifaceobj.name, 'nomaster') + + def _down_bridge(self, ifaceobj): + try: + self.brctlcmd.delete_bridge(ifaceobj.name) + except Exception, e: + self.log_error(str(e)) + + def _down_bridge_port(self, ifaceobj): + self.ipcmd.link_set(ifaceobj.name, 'nomaster') + + def _down_bridge_vlan(self, ifaceobj, bridge): + try: + address = ifaceobj.get_attr_value('address') + if address: + # Create a vlan device, + ifacename = '%s.%s' %(bridge, ifaceobj.priv_data) + self.ipcmd.link_delete(ifacename) + self._delete_bridge_vlan_settings(ifaceobj, bridge) + except Exception, e: + self.log_error(str(e)) + + def _down(self, ifaceobj): + bridge = ifaceobj.get_attr_value_first('bridge') + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + self._down_bridge_vlan(ifaceobj, bridge) + elif bridge: + self._down_bridge_port(ifaceobj) + elif self._is_bridge(ifaceobj): + self._down_bridge(ifaceobj) + + def _query_running_vidinfo(self, ifaceobjrunning, ports): + running_attrs = {} + running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + + if ports: + running_bridge_port_vids = '' + for p in ports: + try: + running_vids = running_vidinfo.get(p, {}).get('vlan') + if running_vids: + running_bridge_port_vids += ' %s=%s' %(p, + ','.join(running_vids)) + except Exception: + pass + running_attrs['bridge-port-vids'] = running_bridge_port_vids + + running_bridge_port_pvids = '' + for p in ports: + try: + running_pvids = running_vidinfo.get(p, {}).get('pvid') + if running_pvids: + running_bridge_port_pvids += ' %s=%s' %(p, + running_pvids) + except Exception: + pass + running_attrs['bridge-port-pvids'] = running_bridge_port_pvids + + running_bridge_vids = running_vidinfo.get(ifaceobjrunning.name, {}).get('vlan') + if running_bridge_vids: + running_attrs['bridge-vids'] = ','.join(running_bridge_vids) + return running_attrs + + def _query_running_mcqv4src(self, ifaceobjrunning): + running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobjrunning.name) + mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()] + mcqs.sort() + mcq = ' '.join(mcqs) + return mcq + + def _query_running_attrs(self, ifaceobjrunning): + bridgeattrdict = {} + userspace_stp = 0 + ports = None + skip_kernel_stp_attrs = 0 + + if self.sysctl_get('net.bridge.bridge-stp-user-space') == '1': + userspace_stp = 1 + + tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name) + if not tmpbridgeattrdict: + self.logger.warn('%s: unable to get bridge attrs' + %ifaceobjrunning.name) + return bridgeattrdict + + # Fill bridge_ports and bridge stp attributes first + ports = tmpbridgeattrdict.get('ports') + if ports: + bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())] + stp = tmpbridgeattrdict.get('stp', 'no') + if stp != self.get_mod_subattr('bridge-stp', 'default'): + bridgeattrdict['bridge-stp'] = [stp] + + if stp == 'yes' and userspace_stp: + skip_kernel_stp_attrs = 1 + + # pick all other attributes + for k,v in tmpbridgeattrdict.items(): + if not v: + continue + if k == 'ports' or k == 'stp': + continue + + if skip_kernel_stp_attrs and k[:2] != 'mc': + # only include igmp attributes if kernel stp is off + continue + attrname = 'bridge-' + k + if v != self.get_mod_subattr(attrname, 'default'): + bridgeattrdict[attrname] = [v] + + bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, ports) + if bridgevidinfo: + bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items() + if v}) + + mcq = self._query_running_mcqv4src(ifaceobjrunning) + if mcq: + bridgeattrdict['bridge-mcqv4src'] = [mcq] + + if skip_kernel_stp_attrs: + return bridgeattrdict + + if ports: + portconfig = {'bridge-pathcosts' : '', + 'bridge-portprios' : ''} + for p, v in ports.items(): + v = self.brctlcmd.get_pathcost(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-pathcosts', + 'default'): + portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v) + + v = self.brctlcmd.get_portprio(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-portprios', + 'default'): + portconfig['bridge-portprios'] += ' %s=%s' %(p, v) + + bridgeattrdict.update({k : [v] for k, v in portconfig.items() + if v}) + + return bridgeattrdict + + def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr): + running_mcqs = self._query_running_mcqv4src(ifaceobj) + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + mcqs = attrval.split() + mcqs.sort() + mcqsout = ' '.join(mcqs) + ifaceobjcurr.update_config_with_status('bridge-mcqv4src', + running_mcqs, 1 if running_mcqs != mcqsout else 0) + + def _query_check_vidinfo(self, ifaceobj, ifaceobjcurr): + + err = 0 + running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + attrval = ifaceobj.get_attr_value_first('bridge-port-vids') + if attrval: + running_bridge_port_vids = '' + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + err = 0 + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + running_vids = running_vidinfo.get(port, {}).get('vlan') + if running_vids: + if not self._compare_vids(vids, running_vids): + err += 1 + running_bridge_port_vids += ' %s=%s' %(port, + ','.join(running_vids)) + else: + running_bridge_port_vids += ' %s' %p + else: + err += 1 + except Exception, e: + self.log_warn('%s: failure checking vid %s (%s)' + %(ifaceobj.name, p, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + running_bridge_port_vids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + attrval, 0) + + # Install pvids + attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + running_bridge_port_pvids = '' + err = 0 + for p in portlist: + try: + (port, pvid) = p.split('=') + running_pvid = running_vidinfo.get(port, {}).get('pvid') + if running_pvid and running_pvid == pvid: + running_bridge_port_pvids += ' %s' %p + else: + err += 1 + running_bridge_port_pvids += ' %s=%s' %(port, + running_pvid) + except Exception, e: + self.log_warn('%s: failure checking pvid %s (%s)' + %(ifaceobj.name, pvid, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 0) + + attrval = ifaceobj.get_attr_value_first('bridge-vids') + if attrval: + vids = re.split(r'[\s\t]\s*', attrval) + running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan') + if running_vids: + if self._compare_vids(vids, running_vids): + ifaceobjcurr.update_config_with_status('bridge-vids', + attrval, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-vids', + ','.join(running_vids), 1) + else: + ifaceobjcurr.update_config_with_status('bridge-vids', attrval, + 1) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.brctlcmd.bridge_exists(ifaceobj.name): + self.logger.info('%s: bridge: does not exist' %(ifaceobj.name)) + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: + return + try: + runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + self.logger.debug('%s: bridge: unable to get bridge attrs' + %ifaceobj.name) + runningattrs = {} + except Exception, e: + self.logger.warn(str(e)) + runningattrs = {} + filterattrs = ['bridge-vids', 'bridge-port-vids', + 'bridge-port-pvids'] + for k in Set(ifaceattrs).difference(filterattrs): + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + rv = runningattrs.get(k[7:]) + if k == 'bridge-mcqv4src': + continue + if k == 'bridge-stp': + # special case stp compare because it may + # contain more than one valid values + stp_on_vals = ['on', 'yes'] + stp_off_vals = ['off'] + if ((v in stp_on_vals and rv in stp_on_vals) or + (v in stp_off_vals and rv in stp_off_vals)): + ifaceobjcurr.update_config_with_status('bridge-stp', + v, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-stp', + v, 1) + elif k == 'bridge-ports': + # special case ports because it can contain regex or glob + running_port_list = rv.keys() if rv else [] + bridge_port_list = self._get_bridge_port_list(ifaceobj) + if not running_port_list and not bridge_port_list: + continue + portliststatus = 1 + if running_port_list and bridge_port_list: + difference = set(running_port_list + ).symmetric_difference(bridge_port_list) + if not difference: + portliststatus = 0 + ifaceobjcurr.update_config_with_status('bridge-ports', + ' '.join(running_port_list) + if running_port_list else '', portliststatus) + elif (k == 'bridge-pathcosts' or + k == 'bridge-portprios' or k == 'bridge-portmcrouter' + or k == 'bridge-portmcfl'): + brctlcmdattrname = k[11:].rstrip('s') + # for port attributes, the attributes are in a list + # = + status = 0 + currstr = '' + vlist = self.parse_port_list(v) + if not vlist: + continue + for vlistitem in vlist: + try: + (p, v) = vlistitem.split('=') + currv = self.brctlcmd.get_bridgeport_attr( + ifaceobj.name, p, + brctlcmdattrname) + if currv: + currstr += ' %s=%s' %(p, currv) + else: + currstr += ' %s=%s' %(p, 'None') + if currv != v: + status = 1 + except Exception, e: + self.log_warn(str(e)) + pass + ifaceobjcurr.update_config_with_status(k, currstr, status) + elif not rv: + ifaceobjcurr.update_config_with_status(k, 'notfound', 1) + continue + elif v != rv: + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + + self._query_check_vidinfo(ifaceobj, ifaceobjcurr) + + self._query_check_mcqv4src(ifaceobj, ifaceobjcurr) + + def _query_running(self, ifaceobjrunning): + if not self.brctlcmd.bridge_exists(ifaceobjrunning.name): + return + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning)) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.brctlcmd: + self.brctlcmd = brctl(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run bridge configuration on the interface object passed as + argument. Can create bridge interfaces if they dont exist already + + 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 + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/dhcp.py b/addons/dhcp.py new file mode 100644 index 0000000..d16ec4b --- /dev/null +++ b/addons/dhcp.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.dhclient import dhclient + from ifupdownaddons.iproute2 import iproute2 +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class dhcp(moduleBase): + """ ifupdown2 addon module to configure dhcp on interface """ + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.dhclientcmd = dhclient(**kargs) + self.ipcmd = None + + def _up(self, ifaceobj): + try: + if ifaceobj.addr_family == 'inet': + # First release any existing dhclient processes + try: + if not self.PERFMODE: + self.dhclientcmd.stop(ifaceobj.name) + except: + pass + self.dhclientcmd.start(ifaceobj.name) + elif ifaceobj.addr_family == 'inet6': + accept_ra = ifaceobj.get_attr_value_first('accept_ra') + if accept_ra: + # XXX: Validate value + self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name + + '.accept_ra', accept_ra) + autoconf = ifaceobj.get_attr_value_first('autoconf') + if autoconf: + # XXX: Validate value + self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name + + '.autoconf', autoconf) + try: + self.dhclientcmd.stop6(ifaceobj.name) + except: + pass + self.dhclientcmd.start6(ifaceobj.name) + except Exception, e: + self.log_error(str(e)) + + def _down(self, ifaceobj): + self.dhclientcmd.release(ifaceobj.name) + self.ipcmd.link_down(ifaceobj.name) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if self.dhclientcmd.is_running(ifaceobjcurr.name): + ifaceobjcurr.addr_family = 'inet' + if ifaceobj.addr_family != 'inet': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.addr_method = 'dhcp' + elif self.dhclientcmd.is_running6(ifaceobjcurr.name): + ifaceobjcurr.addr_family = 'inet6' + if ifaceobj.addr_family != 'inet6': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.addr_method = 'dhcp' + else: + ifaceobjcurr.addr_family = None + ifaceobjcurr.status = ifaceStatus.ERROR + + def _query_running(self, ifaceobjrunning): + if not self.ipcmd.link_exists(ifaceobjrunning.name): + self.logger.debug('iface %s not found' %ifaceobjrunning.name) + ifaceobjrunning.status = ifaceStatus.NOTFOUND + return + if self.dhclientcmd.is_running(ifaceobjrunning.name): + ifaceobjrunning.addr_family = 'inet' + ifaceobjrunning.addr_method = 'dhcp' + elif self.dhclientcmd.is_running6(ifaceobjrunning.name): + ifaceobjrunning.addr_family = 'inet6' + ifaceobjrunning.addr_method = 'dhcp6' + + _run_ops = {'up' : _up, + 'down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run dhcp configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'up', '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 + try: + if (operation != 'query-running' and + (ifaceobj.addr_method != 'dhcp' and + ifaceobj.addr_method != 'dhcp6')): + return + except: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/ethtool.py b/addons/ethtool.py new file mode 100644 index 0000000..53999ce --- /dev/null +++ b/addons/ethtool.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.iproute2 import iproute2 +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class ethtool(moduleBase): + """ ifupdown2 addon module to configure ethtool attributes """ + + _modinfo = {'mhelp' : 'ethtool configuration module for interfaces', + 'attrs': { + 'link-speed' : + {'help' : 'set link speed', + 'example' : ['link-speed 1000']}, + 'link-duplex' : + {'help': 'set link duplex', + 'example' : ['link-duplex full'], + 'validvals' : ['half', 'full'], + 'default' : 'half'}, + 'link-autoneg' : + {'help': 'set autonegotiation', + 'example' : ['link-autoneg on'], + 'validvals' : ['on', 'off'], + 'default' : 'off'}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _post_up(self, ifaceobj): + if not self.ipcmd.link_exists(ifaceobj.name): + return + cmd = '' + attrval = ifaceobj.get_attr_value_first('link-speed') + if attrval: + cmd += ' speed %s' %attrval + attrval = ifaceobj.get_attr_value_first('link-duplex') + if attrval: + cmd += ' duplex %s' %attrval + attrval = ifaceobj.get_attr_value_first('link-autoneg') + if attrval: + cmd += ' autoneg %s' %attrval + if cmd: + try: + cmd = 'ethtool -s %s %s' %(ifaceobj.name, cmd) + self.exec_command(cmd) + except Exception, e: + ifaceobj.status = ifaceStatus.ERROR + self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + + def _query_check(self, ifaceobj, ifaceobjcurr): + return + + def _query_running(self, ifaceobjrunning): + return + + _run_ops = {'post-up' : _post_up, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run ethtool configuration on the interface object passed as + argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'post-up', '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 + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/ifenslave.py b/addons/ifenslave.py new file mode 100644 index 0000000..7a36020 --- /dev/null +++ b/addons/ifenslave.py @@ -0,0 +1,380 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +import ifupdownaddons +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.ifenslaveutil import ifenslaveutil +from ifupdownaddons.iproute2 import iproute2 + +class ifenslave(moduleBase): + """ ifupdown2 addon module to configure bond interfaces """ + _modinfo = { 'mhelp' : 'bond configuration module', + 'attrs' : { + 'bond-use-carrier': + {'help' : 'bond use carrier', + 'validvals' : ['0', '1'], + 'default' : '1', + 'example': ['bond-use-carrier 1']}, + 'bond-num-grat-arp': + {'help' : 'bond use carrier', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bond-num-grat-arp 1']}, + 'bond-num-unsol-na' : + {'help' : 'bond slave devices', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bond-num-unsol-na 1']}, + 'bond-xmit-hash-policy' : + {'help' : 'bond slave devices', + 'validvals' : ['layer2', 'layer3+4', 'layer2+3'], + 'default' : 'layer2', + 'example' : ['bond-xmit-hash-policy layer2']}, + 'bond-miimon' : + {'help' : 'bond miimon', + 'validrange' : ['0', '255'], + 'default' : '0', + 'example' : ['bond-miimon 0']}, + 'bond-mode' : + {'help' : 'bond mode', + 'validvals' : ['balance-rr', 'active-backup', + 'balance-xor', 'broadcast', '802.3ad', + 'balance-tlb', 'balance-alb'], + 'default' : 'balance-rr', + 'example' : ['bond-mode 802.3ad']}, + 'bond-lacp-rate': + {'help' : 'bond use carrier', + 'validvals' : ['0', '1'], + 'default' : '0', + 'example' : ['bond-lacp-rate 0']}, + 'bond-min-links': + {'help' : 'bond min links', + 'default' : '0', + 'example' : ['bond-min-links 0']}, + 'bond-ad-sys-priority': + {'help' : '802.3ad system priority', + 'default' : '65535', + 'example' : ['bond-ad-sys-priority 65535']}, + 'bond-ad-sys-mac-addr': + {'help' : '802.3ad system mac address', + 'default' : '00:00:00:00:00:00', + 'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00']}, + 'bond-lacp-fallback-allow': + {'help' : 'allow lacp fall back', + 'validvals' : ['0', '1'], + 'default' : '0', + 'example' : ['bond-lacp-fallback-allow 0']}, + 'bond-lacp-fallback-period': + {'help' : 'grace period (seconds) for lacp fall back', + 'validrange' : ['0', '100'], + 'default' : '90', + 'example' : ['bond-lacp-fallback-period 100']}, + 'bond-lacp-fallback-priority': + {'help' : 'slave priority for lacp fall back', + 'example' : ['bond-lacp-fallback-priority swp1=1 swp2=1 swp3=2']}, + 'bond-slaves' : + {'help' : 'bond slaves', + 'required' : True, + 'example' : ['bond-slaves swp1 swp2', + 'bond-slaves glob swp1-2', + 'bond-slaves regex (swp[1|2)']}}} + + def __init__(self, *args, **kargs): + ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.ifenslavecmd = None + + def _is_bond(self, ifaceobj): + if ifaceobj.get_attr_value_first('bond-slaves'): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + """ Returns list of interfaces dependent on ifaceobj """ + + if not self._is_bond(ifaceobj): + return None + slave_list = self.parse_port_list(ifaceobj.get_attr_value_first( + 'bond-slaves'), ifacenames_all) + + # Also save a copy for future use + ifaceobj.priv_data = list(slave_list) + return slave_list + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + return self.ifenslavecmd.get_slaves(ifaceobj.name) + + def _get_slave_list(self, ifaceobj): + """ Returns slave list present in ifaceobj config """ + + # If priv data already has slave list use that first. + if ifaceobj.priv_data: + return ifaceobj.priv_data + slaves = ifaceobj.get_attr_value_first('bond-slaves') + if slaves: + return self.parse_port_list(slaves) + else: + return None + + def fetch_attr(self, ifaceobj, attrname): + attrval = ifaceobj.get_attr_value_first(attrname) + if attrval: + msg = ('%s: invalid value %s for attr %s.' + %(ifaceobj.name, attrval, attrname)) + optiondict = self.get_mod_attr(attrname) + if not optiondict: + return None + validvals = optiondict.get('validvals') + if validvals and attrval not in validvals: + raise Exception(msg + ' Valid values are %s' %str(validvals)) + validrange = optiondict.get('validrange') + if validrange: + if (int(attrval) < int(validrange[0]) or + int(attrval) > int(validrange[1])): + raise Exception(msg + ' Valid range is [%s,%s]' + %(validrange[0], validrange[1])) + return attrval + + def _apply_master_settings(self, ifaceobj): + have_attrs_to_set = 0 + ifenslavecmd_attrmap = OrderedDict([('bond-mode' , 'mode'), + ('bond-miimon' , 'miimon'), + ('bond-use-carrier', 'use_carrier'), + ('bond-lacp-rate' , 'lacp_rate'), + ('bond-xmit-hash-policy' , 'xmit_hash_policy'), + ('bond-min-links' , 'min_links'), + ('bond-num-grat-arp' , 'num_grat_arp'), + ('bond-num-unsol-na' , 'num_unsol_na'), + ('bond-ad-sys-mac-addr' , 'ad_sys_mac_addr'), + ('bond-ad-sys-priority' , 'ad_sys_priority'), + ('bond-lacp-fallback-allow', 'lacp_fallback_allow'), + ('bond-lacp-fallback-period', 'lacp_fallback_period')]) + linkstatus = self.ipcmd.link_get_status(ifaceobj.name) + if not linkstatus: + # assume link status is 'UP' + linkstatus = 'UP' + try: + # order of attributes set matters for bond, so + # construct the list sequentially + attrstoset = OrderedDict() + for k, dstk in ifenslavecmd_attrmap.items(): + v = self.fetch_attr(ifaceobj, k) + if v: + attrstoset[dstk] = v + if not attrstoset: + return + have_attrs_to_set = 1 + self.ifenslavecmd.set_attrs(ifaceobj.name, attrstoset, + self.ipcmd.link_down if linkstatus == 'UP' else None) + except: + raise + finally: + if have_attrs_to_set and linkstatus == 'UP': + self.ipcmd.link_up(ifaceobj.name) + + def _add_slaves(self, ifaceobj): + runningslaves = [] + + slaves = self._get_slave_list(ifaceobj) + if not slaves: + self.logger.debug('%s: no slaves found' %ifaceobj.name) + return + + if not self.PERFMODE: + runningslaves = self.ifenslavecmd.get_slaves(ifaceobj.name); + if runningslaves: + # Delete active slaves not in the new slave list + [ self.ifenslavecmd.remove_slave(ifaceobj.name, s) + for s in runningslaves if s not in slaves ] + + for slave in Set(slaves).difference(Set(runningslaves)): + if (not self.PERFMODE and + not self.ipcmd.link_exists(slave)): + self.log_warn('%s: skipping slave %s, does not exist' + %(ifaceobj.name, slave)) + continue + self.ifenslavecmd.enslave_slave(ifaceobj.name, slave, + prehook=self.ipcmd.link_down, + posthook=self.ipcmd.link_up) + + def _apply_slaves_lacp_fallback_prio(self, ifaceobj): + slaves = self.ifenslavecmd.get_slaves(ifaceobj.name) + attrval = ifaceobj.get_attr_value_first('bond-lacp-fallback-priority') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + + for p in portlist: + try: + (port, val) = p.split('=') + if port not in slaves: + self.log_warn('%s: skipping slave %s, does not exist' + %(ifaceobj.name, port)) + continue + slaves.remove(port) + self.ifenslavecmd.set_lacp_fallback_priority(ifaceobj.name, port, val) + except Exception, e: + self.log_warn('%s: failed to set lacp_fallback_priority %s (%s)' + %(ifaceobj.name, port, str(e))) + + for p in slaves: + try: + self.ifenslavecmd.set_lacp_fallback_priority(ifaceobj.name, p, '0') + except Exception, e: + self.log_warn('%s: failed to clear lacp_fallback_priority %s (%s)' + %(ifaceobj.name, p, str(e))) + + + def _up(self, ifaceobj): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ifenslavecmd.create_bond(ifaceobj.name) + self._apply_master_settings(ifaceobj) + self._add_slaves(ifaceobj) + self._apply_slaves_lacp_fallback_prio(ifaceobj) + except Exception, e: + self.log_error(str(e)) + + def _down(self, ifaceobj): + try: + self.ifenslavecmd.delete_bond(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + slaves = None + + if not self.ifenslavecmd.bond_exists(ifaceobj.name): + self.logger.debug('bond iface %s' %ifaceobj.name + + ' does not exist') + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: return + runningattrs = self._query_running_attrs(ifaceobj.name) + for k in ifaceattrs: + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + if k == 'bond-slaves': + slaves = v.split() + continue + rv = runningattrs.get(k) + if not rv: + ifaceobjcurr.update_config_with_status(k, 'None', 1) + else: + if k == 'bond-lacp-fallback-priority': + prios = v.split() + prios.sort() + prio_str = ' '.join(prios) + ifaceobjcurr.update_config_with_status(k, rv, + 1 if prio_str != rv else 0) + continue + + ifaceobjcurr.update_config_with_status(k, rv, + 1 if v != rv else 0) + runningslaves = runningattrs.get('bond-slaves') + if not slaves and not runningslaves: + return + retslave = 1 + if slaves and runningslaves: + if slaves and runningslaves: + difference = set(slaves).symmetric_difference(runningslaves) + if not difference: + retslave = 0 + ifaceobjcurr.update_config_with_status('bond-slaves', + ' '.join(runningslaves) + if runningslaves else 'None', retslave) + + def _query_running_attrs(self, bondname): + bondattrs = {'bond-mode' : + self.ifenslavecmd.get_mode(bondname), + 'bond-miimon' : + self.ifenslavecmd.get_miimon(bondname), + 'bond-use-carrier' : + self.ifenslavecmd.get_use_carrier(bondname), + 'bond-lacp-rate' : + self.ifenslavecmd.get_lacp_rate(bondname), + 'bond-min-links' : + self.ifenslavecmd.get_min_links(bondname), + 'bond-ad-sys-mac-addr' : + self.ifenslavecmd.get_ad_sys_mac_addr(bondname), + 'bond-ad-sys-priority' : + self.ifenslavecmd.get_ad_sys_priority(bondname), + 'bond-xmit-hash-policy' : + self.ifenslavecmd.get_xmit_hash_policy(bondname), + 'bond-lacp-fallback-allow' : + self.ifenslavecmd.get_lacp_fallback_allow(bondname), + 'bond-lacp-fallback-period' : + self.ifenslavecmd.get_lacp_fallback_period(bondname), + 'bond-lacp-fallback-priority' : + self.ifenslavecmd.get_lacp_fallback_priority(bondname)} + slaves = self.ifenslavecmd.get_slaves(bondname) + if slaves: + bondattrs['bond-slaves'] = slaves + return bondattrs + + def _query_running(self, ifaceobjrunning): + if not self.ifenslavecmd.bond_exists(ifaceobjrunning.name): + return + bondattrs = self._query_running_attrs(ifaceobjrunning.name) + if bondattrs.get('bond-slaves'): + bondattrs['bond-slaves'] = ' '.join(bondattrs.get('bond-slaves')) + [ifaceobjrunning.update_config(k, v) + for k, v in bondattrs.items() + if v and v != self.get_mod_subattr(k, 'default')] + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-running' : _query_running, + 'query-checkcurr' : _query_check} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.ifenslavecmd: + self.ifenslavecmd = ifenslaveutil(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run bond 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_bond(ifaceobj): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/loopback.py b/addons/loopback.py new file mode 100644 index 0000000..78ebe0c --- /dev/null +++ b/addons/loopback.py @@ -0,0 +1,64 @@ +#!/usr/bin/python + +# This should be pretty simple and might not really even need to exist. +# The key is that we need to call link_create with a type of "dummy" +# since that will translate to 'ip link add loopbackX type dummy' +# The config file should probably just indicate that the type is +# loopback or dummy. + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import logging + +class loopback(moduleBase): + _modinfo = {'mhelp' : 'configure extra loopback module based on ' + + 'dummy device' } + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_loopback_by_name(self, ifacename): + return 'loop' in ifacename + + def _up(self, ifaceobj): + if self._is_loopback_by_name(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'dummy') + + def _down(self, ifaceobj): + if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name): + return + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check} + + def get_ops(self): + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_loopback_by_name(ifaceobj.name)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/mstpctl.py b/addons/mstpctl.py new file mode 100644 index 0000000..559e3f5 --- /dev/null +++ b/addons/mstpctl.py @@ -0,0 +1,710 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bridgeutils import brctl +from ifupdownaddons.iproute2 import iproute2 +from ifupdownaddons.mstpctlutil import mstpctlutil +import traceback + +class mstpctl(moduleBase): + """ ifupdown2 addon module to configure mstp attributes """ + + _modinfo = {'mhelp' : 'mstp configuration module for bridges', + 'attrs' : { + 'mstpctl-ports' : + {'help' : 'mstp ports', + 'compat' : True}, + 'mstpctl-stp' : + {'help': 'bridge stp yes/no', + 'compat' : True, + 'default' : 'no'}, + 'mstpctl-treeprio' : + {'help': 'tree priority', + 'default' : '32768', + 'validrange' : ['0', '65535'], + 'required' : False, + 'example' : ['mstpctl-treeprio 32768']}, + 'mstpctl-ageing' : + {'help': 'ageing time', + 'default' : '300', + 'required' : False, + 'example' : ['mstpctl-ageing 300']}, + 'mstpctl-maxage' : + { 'help' : 'max message age', + 'default' : '20', + 'required' : False, + 'example' : ['mstpctl-maxage 20']}, + 'mstpctl-fdelay' : + { 'help' : 'set forwarding delay', + 'default' : '15', + 'required' : False, + 'example' : ['mstpctl-fdelay 15']}, + 'mstpctl-maxhops' : + { 'help' : 'bridge max hops', + 'default' : '15', + 'required' : False, + 'example' : ['mstpctl-maxhops 15']}, + 'mstpctl-txholdcount' : + { 'help' : 'bridge transmit holdcount', + 'default' : '6', + 'required' : False, + 'example' : ['mstpctl-txholdcount 6']}, + 'mstpctl-forcevers' : + { 'help' : 'bridge force stp version', + 'default' : 'rstp', + 'required' : False, + 'example' : ['mstpctl-forcevers rstp']}, + 'mstpctl-portpathcost' : + { 'help' : 'bridge port path cost', + 'default' : '0', + 'required' : False, + 'example' : ['mstpctl-portpathcost swp1=0 swp2=1']}, + 'mstpctl-portadminage' : + { 'help' : 'bridge port admin age', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-portadminage swp1=no swp2=no']}, + 'mstpctl-portp2p' : + { 'help' : 'bridge port p2p detection mode', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-portp2p swp1=no swp2=no']}, + 'mstpctl-portrestrrole' : + { 'help' : + 'enable/disable port ability to take root role of the port', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-portrestrrole swp1=no swp2=no']}, + 'mstpctl-portrestrtcn' : + { 'help' : + 'enable/disable port ability to propagate received topology change notification of the port', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-portrestrtcn swp1=no swp2=no']}, + 'mstpctl-bpduguard' : + { 'help' : + 'enable/disable bpduguard', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-bpduguard swp1=no swp2=no']}, + 'mstpctl-treeportprio' : + { 'help' : + 'port priority for MSTI instance', + 'default' : '128', + 'validrange' : ['0', '240'], + 'required' : False, + 'example' : ['mstpctl-treeportprio swp1=128 swp2=128']}, + 'mstpctl-hello' : + { 'help' : 'set hello time', + 'default' : '2', + 'required' : False, + 'example' : ['mstpctl-hello 2']}, + 'mstpctl-portnetwork' : + { 'help' : 'enable/disable bridge assurance capability for a port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portnetwork swp1=no swp2=no']}, + 'mstpctl-portadminedge' : + { 'help' : 'enable/disable initial edge state of the port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portadminedge swp1=no swp2=no']}, + 'mstpctl-portautoedge' : + { 'help' : 'enable/disable auto transition to/from edge state of the port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portautoedge swp1=yes swp2=yes']}, + 'mstpctl-treeportcost' : + { 'help' : 'port tree cost', + 'required' : False}, + 'mstpctl-portbpdufilter' : + { 'help' : 'enable/disable bpdu filter on a port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portbpdufilter swp1=no swp2=no']}, + 'mstpctl-pathcost' : + { 'help' : 'port path cost', + 'default' : '0', + 'required' : False, + 'example' : ['mstpctl-pathcost 1']}, + 'mstpctl-adminage' : + { 'help' : 'bridge port admin age', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-adminage no']}, + 'mstpctl-p2p' : + { 'help' : 'bridge port p2p detection mode', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-p2p yes']}, + 'mstpctl-restrrole' : + { 'help' : + 'enable/disable port ability to take root role of the port', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-restrrole yes']}, + 'mstpctl-restrtcn' : + { 'help' : + 'enable/disable port ability to propagate received topology change notification of the port', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-restrtcn yes']}, + 'mstpctl-treeprio' : + { 'help' : + 'port priority for MSTI instance', + 'default' : '128', + 'validrange' : ['0', '240'], + 'required' : False, + 'example' : ['mstpctl-treeprio 128']}, + 'mstpctl-network' : + { 'help' : 'enable/disable bridge assurance capability for a port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-network no']}, + 'mstpctl-adminedge' : + { 'help' : 'enable/disable initial edge state of the port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-adminedge no']}, + 'mstpctl-autoedge' : + { 'help' : 'enable/disable auto transition to/from edge state of the port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-autoedge yes']}, + 'mstpctl-treecost' : + { 'help' : 'port tree cost', + 'required' : False}, + 'mstpctl-bpdufilter' : + { 'help' : 'enable/disable bpdu filter on a port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-bpdufilter yes']}, + }} + + _port_attrs_map = {'mstpctl-pathcost' : 'portpathcost', + 'mstpctl-adminedge' : 'portadminedge', + 'mstpctl-p2p' : 'portp2p', + 'mstpctl-restrrole' : 'portrestrrole', + 'mstpctl-restrtcn' : 'portrestrtcn', + 'mstpctl-bpduguard' : 'bpduguard', + 'mstpctl-treeprio' : 'treeportprio', + 'mstpctl-treecost' : 'treeportcost', + 'mstpctl-network' : 'portnetwork', + 'mstpctl-bpdufilter' : 'portbpdufilter'} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.brctlcmd = None + self.mstpctlcmd = None + + def _is_bridge(self, ifaceobj): + if (ifaceobj.get_attr_value_first('mstpctl-ports') or + ifaceobj.get_attr_value_first('bridge-ports')): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + if not self._is_bridge(ifaceobj): + return None + return self.parse_port_list(ifaceobj.get_attr_value_first( + 'mstpctl-ports'), ifacenames_all) + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + if (self.brctlcmd.bridge_exists(ifaceobj.name) and + not self.mstpctlcmd.mstpbridge_exists(ifaceobj.name)): + return None + return self.brctlcmd.get_bridge_ports(ifaceobj.name) + + def _get_bridge_port_list(self, ifaceobj): + + # port list is also available in the previously + # parsed dependent list. Use that if available, instead + # of parsing port expr again + port_list = ifaceobj.lowerifaces + if port_list: + return port_list + ports = ifaceobj.get_attr_value_first('mstpctl-ports') + if ports: + return self.parse_port_list(ports) + else: + return None + + def _add_ports(self, ifaceobj): + bridgeports = self._get_bridge_port_list(ifaceobj) + + runningbridgeports = [] + # Delete active ports not in the new port list + if not self.PERFMODE: + runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if runningbridgeports: + [self.ipcmd.link_set(bport, 'nomaster') + for bport in runningbridgeports + if not bridgeports or bport not in bridgeports] + else: + runningbridgeports = [] + if not bridgeports: + return + err = 0 + for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): + try: + if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport): + self.log_warn('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport)) + err += 1 + continue + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) + self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport + + '/disable_ipv6', '1') + self.ipcmd.addr_flush(bridgeport) + except Exception, e: + self.log_error(str(e)) + if err: + self.log_error('error configuring bridge (missing ports)') + + def _apply_bridge_settings(self, ifaceobj): + check = False if self.PERFMODE else True + try: + bridgeattrs = {k:v for k,v in + {'treeprio' : + ifaceobj.get_attr_value_first('mstpctl-treeprio'), + 'ageing' : + ifaceobj.get_attr_value_first('mstpctl-ageing'), + 'maxage' : + ifaceobj.get_attr_value_first('mstpctl-maxage'), + 'fdelay' : + ifaceobj.get_attr_value_first('mstpctl-fdelay'), + 'maxhops' : + ifaceobj.get_attr_value_first('mstpctl-maxhops'), + 'txholdcount' : + ifaceobj.get_attr_value_first('mstpctl-txholdcount'), + 'forcevers' : + ifaceobj.get_attr_value_first('mstpctl-forcevers'), + 'hello' : + ifaceobj.get_attr_value_first('mstpctl-hello') + }.items() if v} + + if bridgeattrs: + # set bridge attributes + for k,v in bridgeattrs.items(): + if k == 'treeprio': + continue + try: + if v: + self.mstpctlcmd.set_bridge_attr(ifaceobj.name, k, + v, check) + except Exception, e: + self.logger.warn('%s' %str(e)) + pass + if bridgeattrs.get('treeprio'): + try: + self.mstpctlcmd.set_bridge_treeprio(ifaceobj.name, + bridgeattrs['treeprio'], check) + except Exception, e: + self.logger.warn('%s' %str(e)) + pass + + # set bridge port attributes + for attrname in ['mstpctl-portpathcost', 'mstpctl-portadminedge', + 'mstpctl-portp2p', 'mstpctl-portrestrrole', + 'mstpctl-portrestrtcn', 'mstpctl-bpduguard', + 'mstpctl-treeportprio', 'mstpctl-treeportcost', + 'mstpctl-portnetwork', 'mstpctl-portbpdufilter']: + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + continue + dstattrname = attrname.split('-')[1] + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: error parsing \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + continue + for p in portlist: + try: + (port, val) = p.split('=') + self.mstpctlcmd.set_bridgeport_attr(ifaceobj.name, + port, dstattrname, val, check) + except Exception, e: + self.log_warn('%s: error setting %s (%s)' + %(ifaceobj.name, attrname, str(e))) + except Exception, e: + self.log_warn(str(e)) + pass + + def _apply_bridge_port_settings(self, ifaceobj, bridge): + check = False if self.PERFMODE else True + try: + # set bridge port attributes + for attrname, dstattrname in self._port_attrs_map.items(): + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + continue + try: + self.mstpctlcmd.set_bridgeport_attr(bridge, + ifaceobj.name, dstattrname, attrval, check) + except Exception, e: + self.log_warn('%s: error setting %s (%s)' + %(ifaceobj.name, attrname, str(e))) + except Exception, e: + self.log_warn(str(e)) + pass + + def _up(self, ifaceobj): + # Check if bridge port + bridge = ifaceobj.get_attr_value_first('bridge') + if bridge: + if self.mstpctlcmd.is_mstpd_running(): + self._apply_bridge_port_settings(ifaceobj, bridge) + return + + stp = None + try: + porterr = False + porterrstr = '' + if ifaceobj.get_attr_value_first('mstpctl-ports'): + # If bridge ports specified with mstpctl attr, create the + # bridge and also add its ports + self.ipcmd.batch_start() + if not self.PERFMODE: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'bridge') + else: + self.ipcmd.link_create(ifaceobj.name, 'bridge') + try: + self._add_ports(ifaceobj) + except Exception, e: + porterr = True + porterrstr = str(e) + pass + finally: + self.ipcmd.batch_commit() + stp = ifaceobj.get_attr_value_first('mstpctl-stp') + if stp: + self.set_iface_attr(ifaceobj, 'mstpctl-stp', + self.brctlcmd.set_stp) + else: + stp = self.brctlcmd.get_stp(ifaceobj.name) + if (self.mstpctlcmd.is_mstpd_running() and + (stp == 'yes' or stp == 'on')): + self._apply_bridge_settings(ifaceobj) + except Exception, e: + self.log_error(str(e)) + if porterr: + raise Exception(porterrstr) + + def _down(self, ifaceobj): + try: + if ifaceobj.get_attr_value_first('mstpctl-ports'): + # If bridge ports specified with mstpctl attr, delete the + # bridge + ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if ports: + for p in ports: + proc_file = ('/proc/sys/net/ipv6/conf/%s' %p + + '/disable_ipv6') + self.write_file(proc_file, '0') + self.brctlcmd.delete_bridge(ifaceobj.name) + except Exception, e: + self.log_error(str(e)) + + def _query_running_attrs(self, ifaceobjrunning): + bridgeattrdict = {} + + tmpbridgeattrdict = self.mstpctlcmd.get_bridge_attrs(ifaceobjrunning.name) + if not tmpbridgeattrdict: + return bridgeattrdict + + for k,v in tmpbridgeattrdict.items(): + if k == 'stp' or not v: + continue + if k == 'ports': + ports = v.keys() + continue + attrname = 'mstpctl-' + k + if v and v != self.get_mod_subattr(attrname, 'default'): + bridgeattrdict[attrname] = [v] + + ports = self.brctlcmd.get_bridge_ports(ifaceobjrunning.name) + if ports: + portconfig = {'mstpctl-portnetwork' : '', + 'mstpctl-portpathcost' : '', + 'mstpctl-portadminedge' : '', + 'mstpctl-portautoedge' : '', + 'mstpctl-portp2p' : '', + 'mstpctl-portrestrrole' : '', + 'mstpctl-portrestrtcn' : '', + 'mstpctl-bpduguard' : '', + 'mstpctl-treeportprio' : '', + 'mstpctl-treeportcost' : ''} + + for p in ports: + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portnetwork') + if v and v != 'no': + portconfig['mstpctl-portnetwork'] += ' %s=%s' %(p, v) + + # XXX: Can we really get path cost of a port ??? + #v = self.mstpctlcmd.get_portpathcost(ifaceobjrunning.name, p) + #if v and v != self.get_mod_subattr('mstpctl-portpathcost', + # 'default'): + # portconfig['mstpctl-portpathcost'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portadminedge') + if v and v != 'no': + portconfig['mstpctl-portadminedge'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portp2p') + if v and v != 'no': + portconfig['mstpctl-portp2p'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portrestrrole') + if v and v != 'no': + portconfig['mstpctl-portrestrrole'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portrestrtcn') + if v and v != 'no': + portconfig['mstpctl-portrestrtcn'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'bpduguard') + if v and v != 'no': + portconfig['mstpctl-bpduguard'] += ' %s=%s' %(p, v) + + # XXX: Can we really get path cost of a port ??? + #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + # p, 'treeprio') + #if v and v != self.get_mod_subattr('mstpctl-treeportprio', + # 'default'): + # portconfig['mstpctl-treeportprio'] += ' %s=%s' %(p, v) + + #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + # p, 'treecost') + #if v and v != self.get_mod_subattr('mstpctl-treeportcost', + # 'default'): + # portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v) + + bridgeattrdict.update({k : [v] for k, v in portconfig.items() + if v}) + self.logger.debug(bridgeattrdict) + return bridgeattrdict + + def _query_check_bridge(self, ifaceobj, ifaceobjcurr): + # list of attributes that are not supported currently + blacklistedattrs = ['mstpctl-portpathcost', + 'mstpctl-treeportprio', 'mstpctl-treeportcost'] + if not self.brctlcmd.bridge_exists(ifaceobj.name): + self.logger.debug('bridge %s does not exist' %ifaceobj.name) + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: + return + runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + runningattrs = {} + for k in ifaceattrs: + # for all mstpctl options + if k in blacklistedattrs: + continue + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + + # Get the running attribute + rv = runningattrs.get(k[8:]) + if k == 'mstpctl-stp': + # special case stp compare because it may + # contain more than one valid values + stp_on_vals = ['on', 'yes'] + stp_off_vals = ['off'] + rv = self.brctlcmd.get_stp(ifaceobj.name) + if ((v in stp_on_vals and rv in stp_on_vals) or + (v in stp_off_vals and rv in stp_off_vals)): + ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 0) + else: + ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 1) + continue + + if k == 'mstpctl-ports': + # special case ports because it can contain regex or glob + # XXX: We get all info from mstputils, which means if + # mstpd is down, we will not be returning any bridge bridgeports + running_port_list = self.brctlcmd.get_bridge_ports(ifaceobj.name) + bridge_port_list = self._get_bridge_port_list(ifaceobj) + if not running_port_list and not bridge_port_list: + continue + portliststatus = 1 + if running_port_list and bridge_port_list: + difference = Set(running_port_list).symmetric_difference( + Set(bridge_port_list)) + if not difference: + portliststatus = 0 + ifaceobjcurr.update_config_with_status('mstpctl-ports', + ' '.join(running_port_list) + if running_port_list else '', portliststatus) + elif k[:12] == 'mstpctl-port' or k == 'mstpctl-bpduguard': + # Now, look at port attributes + # derive the mstpctlcmd attr name + #mstpctlcmdattrname = k[12:] if k[:12] == 'mstpctl-port' else k[8:] + mstpctlcmdattrname = k[8:] + + # for port attributes, the attributes are in a list + # = + status = 0 + currstr = '' + vlist = self.parse_port_list(v) + if not vlist: + continue + for vlistitem in vlist: + try: + (p, v) = vlistitem.split('=') + currv = self.mstpctlcmd.get_bridgeport_attr( + ifaceobj.name, p, mstpctlcmdattrname) + if currv: + currstr += ' %s=%s' %(p, currv) + else: + currstr += ' %s=%s' %(p, 'None') + if currv != v: + status = 1 + except Exception, e: + self.log_warn(str(e)) + pass + ifaceobjcurr.update_config_with_status(k, currstr, status) + elif not rv: + ifaceobjcurr.update_config_with_status(k, '', 1) + elif v != rv: + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + + def _query_check_bridge_port(self, ifaceobj, ifaceobjcurr, bridge): + # list of attributes that are not supported currently + blacklistedattrs = ['mstpctl-pathcost', + 'mstpctl-treeprio', 'mstpctl-treecost'] + if not self.ipcmd.link_exists(): + self.logger.debug('bridge port %s does not exist' %ifaceobj.name) + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self._port_attrs_map.keys()) + if not ifaceattrs: + return + runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + runningattrs = {} + for k in ifaceattrs: + # for all mstpctl options + if k in blacklistedattrs: + continue + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + + currv = self.mstpctlcmd.get_bridgeport_attr(bridge, + ifaceobj.name, self._port_attrs_map.get(k)) + if currv: + if currv != v: + ifaceobjcurr.update_config_with_status(k, currv, 1) + else: + ifaceobjcurr.update_config_with_status(k, currv, 0) + else: + ifaceobjcurr.update_config_with_status(k, None, 1) + + def _query_check(self, ifaceobj, ifaceobjcurr): + # Check if bridge port + bridge = ifaceobj.get_attr_value_first('bridge') + if bridge: + self._query_check_bridge_port(ifaceobj, ifaceobjcurr, bridge) + return + self._query_check_bridge(ifaceobj, ifaceobjcurr) + + def _query_running(self, ifaceobjrunning): + if not self.brctlcmd.bridge_exists(ifaceobjrunning.name): + return + if self.brctlcmd.get_stp(ifaceobjrunning.name) == 'no': + # This bridge does not run stp, return + return + # if userspace stp not set, return + if self.sysctl_get('net.bridge.bridge-stp-user-space') != '1': + return + # Check if mstp really knows about this bridge + if not self.mstpctlcmd.mstpbridge_exists(ifaceobjrunning.name): + return + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning)) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.brctlcmd: + self.brctlcmd = brctl(**flags) + if not self.mstpctlcmd: + self.mstpctlcmd = mstpctlutil(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run mstp 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_bridge(ifaceobj): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/usercmds.py b/addons/usercmds.py new file mode 100644 index 0000000..c0713f2 --- /dev/null +++ b/addons/usercmds.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import subprocess +import ifupdownaddons + +class usercmds(ifupdownaddons.modulebase.moduleBase): + """ ifupdown2 addon module to configure user specified commands """ + + _modinfo = {'mhelp' : 'user commands for interfaces', + 'attrs' : { + 'pre-up' : + {'help' : 'run command before bringing the interface up'}, + 'up' : + {'help' : 'run command at interface bring up'}, + 'post-up' : + {'help' : 'run command after interface bring up'}, + 'pre-down' : + {'help' : 'run command before bringing the interface down'}, + 'down' : + {'help' : 'run command at interface down'}, + 'post-down' : + {'help' : 'run command after bringing interface down'}}} + + def _exec_user_cmd(self, cmd): + """ exec's commands using subprocess Popen + + special wrapper using use closefds=True and shell=True + for user commands + """ + + cmd_returncode = 0 + try: + self.logger.info('executing %s' %cmd) + if self.DRYRUN: + return + ch = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + shell=True, + stderr=subprocess.STDOUT, + close_fds=True) + cmd_returncode = ch.wait() + cmdout = ch.communicate()[0] + except Exception, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(cmd, str(e))) + if cmd_returncode != 0: + raise Exception(cmdout) + return cmdout + + def _run_command(self, ifaceobj, op): + cmd_list = ifaceobj.get_attr_value(op) + if cmd_list: + for cmd in cmd_list: + self.logger.info('executing cmd \'%s\'' %cmd) + try: + self._exec_user_cmd(cmd) + except Exception, e: + if not self.ignore_error(str(e)): + self.logger.warn('%s: %s cmd \'%s\' failed (%s)' + %(ifaceobj.name, op, cmd, str(e).strip('\n'))) + pass + + _run_ops = {'pre-up' : _run_command, + 'pre-down' : _run_command, + 'up' : _run_command, + 'post-up' : _run_command, + 'down' : _run_command, + 'post-down' : _run_command} + + 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): + """ run user commands + + Args: + **ifaceobj** (object): iface object + + **operation** (str): list of ops + + 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 + op_handler(self, ifaceobj, operation) diff --git a/addons/vlan.py b/addons/vlan.py new file mode 100644 index 0000000..4d6aecc --- /dev/null +++ b/addons/vlan.py @@ -0,0 +1,191 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import logging + +class vlan(moduleBase): + """ ifupdown2 addon module to configure vlans """ + + _modinfo = {'mhelp' : 'vlan module configures vlan interfaces.' + + 'This module understands vlan interfaces with dot ' + + 'notations. eg swp1.100. Vlan interfaces with any ' + + 'other names need to have raw device and vlan id ' + + 'attributes', + 'attrs' : { + 'vlan-raw-device' : + {'help' : 'vlan raw device'}, + 'vlan-id' : + {'help' : 'vlan id'}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_vlan_device(self, ifaceobj): + vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') + if vlan_raw_device: + return True + elif '.' in ifaceobj.name: + return True + return False + + def _get_vlan_id(self, ifaceobj): + """ Derives vlanid from iface name + + Example: + Returns 1 for ifname vlan0001 returns 1 + Returns 1 for ifname vlan1 + Returns 1 for ifname eth0.1 + + Returns -1 if vlan id cannot be determined + """ + vid_str = ifaceobj.get_attr_value_first('vlan-id') + try: + if vid_str: return int(vid_str) + except: + return -1 + + if ifaceobj.name.startswith('vlan'): + vid_str = ifaceobj.name[4:] + elif '.' in ifaceobj.name: + vid_str = ifaceobj.name.split('.', 1)[1] + else: + return -1 + try: + vid = int(vid_str) + except: + return -1 + return vid + + def _is_vlan_by_name(self, ifacename): + return '.' in ifacename + + def _get_vlan_raw_device_from_ifacename(self, ifacename): + """ Returns vlan raw device from ifname + Example: + Returns eth0 for ifname eth0.100 + + Returns None if vlan raw device name cannot + be determined + """ + vlist = ifacename.split('.', 1) + if len(vlist) == 2: + return vlist[0] + return None + + def _get_vlan_raw_device(self, ifaceobj): + vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') + if vlan_raw_device: + return vlan_raw_device + return self._get_vlan_raw_device_from_ifacename(ifaceobj.name) + + def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None): + if not self._is_vlan_device(ifaceobj): + return None + return [self._get_vlan_raw_device(ifaceobj)] + + def _up(self, ifaceobj): + vlanid = self._get_vlan_id(ifaceobj) + if vlanid == -1: + raise Exception('could not determine vlanid') + vlanrawdevice = self._get_vlan_raw_device(ifaceobj) + if not vlanrawdevice: + raise Exception('could not determine vlan raw device') + self.ipcmd.link_create_vlan(ifaceobj.name, + vlanrawdevice, vlanid) + + def _down(self, ifaceobj): + vlanid = self._get_vlan_id(ifaceobj) + if vlanid == -1: + raise Exception('could not determine vlanid') + vlan_raw_device = self._get_vlan_raw_device(ifaceobj) + if not vlan_raw_device: + raise Exception('could not determine vlan raw device') + if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name): + return + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + if not '.' in ifaceobj.name: + # if vlan name is not in the dot format, check its running state + (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobj.name) + if vlanrawdev != ifaceobj.get_attr_value_first('vlan-raw-device'): + ifaceobjcurr.update_config_with_status('vlan-raw-device', + vlanrawdev, 1) + else: + ifaceobjcurr.update_config_with_status('vlan-raw-device', + vlanrawdev, 0) + if vlanid != ifaceobj.get_attr_value_first('vlan-id'): + ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 1) + else: + ifaceobjcurr.update_config_with_status('vlan-id', + vlanid, 0) + + def _query_running(self, ifaceobjrunning): + if not self.ipcmd.link_exists(ifaceobjrunning.name): + if self._is_vlan_by_name(ifaceobjrunning.name): + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + if not self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name): + return + # If vlan name is not in the dot format, get the + # vlan dev and vlan id + if not '.' in ifaceobjrunning.name: + (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name) + ifaceobjrunning.update_config_dict({(k, v) for k, v in + {'vlan-raw-device' : vlanrawdev, + 'vlan-id' : vlanid}.items() + if v}) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + """ run vlan 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_vlan_device(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/vxlan.py b/addons/vxlan.py new file mode 100644 index 0000000..6422329 --- /dev/null +++ b/addons/vxlan.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import logging + +class vxlan(moduleBase): + _modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.', + 'attrs' : { + 'vxlan-id' : + {'help' : 'vxlan id', + 'required' : True, + 'example': ['vxlan-id 100']}, + 'vxlan-local-tunnelip' : + {'help' : 'vxlan local tunnel ip', + 'example': ['vxlan-local-tunnelip 172.16.20.103']}, + 'vxlan-svcnodeip' : + {'help' : 'vxlan id', + 'example': ['vxlan-svcnodeip 172.16.22.125']}, + 'vxlan-peernodeip' : + {'help' : 'vxlan peer node ip', + 'example': ['vxlan-peernodeip 172.16.22.127']}, + 'vxlan-learning' : + {'help' : 'vxlan learning on/off', + 'example': ['vxlan-learning on']}, + }} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_vxlan_device(self, ifaceobj): + if ifaceobj.get_attr_value_first('vxlan-id'): + return True + return False + + def _up(self, ifaceobj): + vxlanid = ifaceobj.get_attr_value_first('vxlan-id') + if vxlanid: + self.ipcmd.link_create_vxlan(ifaceobj.name, vxlanid, + localtunnelip=ifaceobj.get_attr_value_first('vxlan-local-tunnelip'), + svcnodeips=ifaceobj.get_attr_value('vxlan-svcnodeip'), + peernodeips=ifaceobj.get_attr_value('vxlan-peernodeip'), + learning=ifaceobj.get_attr_value_first('vxlan-learning')) + + def _down(self, ifaceobj): + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + + # Update vxlan object + + def _query_running(self, ifaceobjrunning): + # Not implemented + return + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None): + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_vxlan_device(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/config/addons.conf b/config/addons.conf new file mode 100644 index 0000000..2b1bd0a --- /dev/null +++ b/config/addons.conf @@ -0,0 +1,28 @@ +pre-up,bridge +pre-up,mstpctl +pre-up,vlan +pre-up,vxlan +pre-up,ifenslave +pre-up,bridgevlanaware +pre-up,usercmds +pre-up,loopback +up,dhcp +up,address +up,addressvirtual +up,usercmds +up,loopback +post-up,ethtool +post-up,usercmds +pre-down,usercmds +down,dhcp +down,addressvirtual +down,address +down,usercmds +post-down,bridgevlanaware +post-down,bridge +post-down,mstpctl +post-down,vxlan +post-down,vlan +post-down,ifenslave +post-down,usercmds +post-down,loopback diff --git a/docs.addons/Makefile b/docs.addons/Makefile new file mode 100644 index 0000000..9b5efd7 --- /dev/null +++ b/docs.addons/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ifupdown2.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ifupdown2.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ifupdown2" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ifupdown2" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs.addons/source/addonsapiref.rst b/docs.addons/source/addonsapiref.rst new file mode 100644 index 0000000..0954d27 --- /dev/null +++ b/docs.addons/source/addonsapiref.rst @@ -0,0 +1,61 @@ +Documentation for the ifupdownaddons default addons modules +*********************************************************** + +address +======= + +.. automodule:: address + +.. autoclass:: address + :members: run, get_ops + + +bridge +====== + +.. automodule:: bridge + +.. autoclass:: bridge + :members: run, get_ops + +dhcp +==== + +.. automodule:: dhcp + +.. autoclass:: dhcp + +ethtool +======= + +.. automodule:: ethtool + +.. autoclass:: ethtool + +ifenslave +========= + +.. automodule:: ifenslave + +.. autoclass:: ifenslave + +mstpctl +======= + +.. automodule:: mstpctl + +.. autoclass:: mstpctl + +usercmds +======== + +.. automodule:: usercmds + +.. autoclass:: usercmds + +vlan +==== + +.. automodule:: vlan + +.. autoclass:: vlan diff --git a/docs.addons/source/addonshelperapiref.rst b/docs.addons/source/addonshelperapiref.rst new file mode 100644 index 0000000..01d9a41 --- /dev/null +++ b/docs.addons/source/addonshelperapiref.rst @@ -0,0 +1,44 @@ +Documentation for the ifupdownaddons package helper modules +*********************************************************** + +This package contains modules that provide helper methods +for ifupdown2 addon modules to interact directly with tools +like iproute2, brctl etc. + + +bridgeutils +=========== + +Helper module to work with bridgeutil commands + +.. automodule:: bridgeutils + +.. autoclass:: brctl + +ifenslaveutil +============= + +Helper module to interact with linux api to create bonds. +Currently this is via sysfs. + +.. automodule:: ifenslaveutil + +.. autoclass:: ifenslaveutil + +dhclient +======== + +Helper module to interact with dhclient tools. + +.. automodule:: dhclient + +.. autoclass:: dhclient + +iproute2 +======== + +Helper module to interact with iproute2 tools. + +.. automodule:: iproute2 + +.. autoclass:: iproute2 diff --git a/docs.addons/source/conf.py b/docs.addons/source/conf.py new file mode 100644 index 0000000..e9ddb8d --- /dev/null +++ b/docs.addons/source/conf.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# +# ifupdown2-addons documentation build configuration file, created by +# sphinx-quickstart on Mon Jul 21 11:17:17 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +sys.path.insert(0, os.path.abspath('../../addons')) +sys.path.append(os.path.abspath('../../')) +sys.path.append(os.path.abspath('../../ifupdownaddons')) +sys.path.append(os.path.abspath('../../../ifupdown2')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'ifupdown2-addons' +copyright = u'2014, Roopa Prabhu' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ifupdown2-addonsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ifupdown2-addons.tex', u'ifupdown2-addons Documentation', + u'Roopa Prabhu', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'ifupdown2-addons', u'ifupdown2-addons Documentation', + [u'Roopa Prabhu'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ifupdown2-addons', u'ifupdown2-addons Documentation', + u'Roopa Prabhu', 'ifupdown2-addons', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/docs.addons/source/developmentcorner.rst b/docs.addons/source/developmentcorner.rst new file mode 100644 index 0000000..be5c563 --- /dev/null +++ b/docs.addons/source/developmentcorner.rst @@ -0,0 +1,58 @@ +Development Corner +================== + +Writing a ifupdown2 addon module +-------------------------------- +ifupdown2 addon modules are part of the python-ifupdown2-addons package. +They are installed under /usr/share/ifupdownaddons directory on the target +system. + +The center of the universe for an addon module is the 'class iface' object +exported by the python-ifupdown2 package. + +The iface object is modeled after an iface entry in the user provided network +configuration file (eg. /etc/network/interfaces). For more details see +the api reference for the iface class. + +ifupdown2 dynamically loads a python addon module. It expects the addon module +to implement a few methods. + +* all addon modules must inherit from moduleBase class +* the module must implement a class by the same name +* the network interface object (class iface) and the operation to be performed + is passed to the modules. Operation can be any of 'pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down', 'query-check', 'query-running'. + The module can choose to support a subset or all operations. + In cases when the operation is query-check, the module must compare between + the given and running state and return the checked state of the object in + queryobjcur passed as argument to the run menthod. +* the python addon class must provide a few methods: + * run() : method to configure the interface. + * get_ops() : must return a list of operations it supports. + eg: 'pre-up', 'post-down' + * get_dependent_ifacenames() : must return a list of interfaces the + supported interface is dependent on. This is used to build the + dependency list for sorting and executing interfaces in dependency order. + * if the module supports -r option to ifquery, ie ability to construct the + ifaceobj from running state, it can optionally implement the + get_dependent_ifacenames_running() method, to return the list of + dependent interfaces derived from running state of the interface. + This is different from get_dependent_ifacenames() where the dependent + interfaces are derived from the interfaces config file (provided by the + user). + * provide a dictionary of all supported attributes in the _modinfo + attribute. This is useful for syntax help and man page generation. + +python-ifupdown2-addons package also installs ifupdownaddons python package +that contains helper modules for all addon modules. Its optional for the addon +module to use this package. + +see example address handling module /usr/share/ifupdownaddons/address.py + +API reference +------------- +.. toctree:: + :maxdepth: 2 + + addonsapiref.rst + addonshelperapiref.rst diff --git a/docs.addons/source/gettingstarted.rst b/docs.addons/source/gettingstarted.rst new file mode 100644 index 0000000..e4345ca --- /dev/null +++ b/docs.addons/source/gettingstarted.rst @@ -0,0 +1,29 @@ +Getting Started +=============== + +Prerequisites +------------- +* python-ifupdown2-addons is currently only tested on debian wheezy +* python-ifupdown2-addons needs python version 2.6 or greater +* build depends on: python-stdeb (for deb builds), python-docutils (for rst2man) +* depends on python-gvgen package for printing interface graphs (this will be made optional in the future) +* optional dependency for template engine: python-mako +* python-ifupdown2-addons has an install dependency on python-ifupdown2 + +Building +-------- +$git clone ifupdown2 + +$cd ifupdown2/ifupdown2-addons + +$./build.sh + +Installing +---------- +install generated python-ifupdown2-addons-.deb using dpkg + +$dpkg -i .deb + + + + diff --git a/docs.addons/source/index.rst b/docs.addons/source/index.rst new file mode 100644 index 0000000..27b6fd6 --- /dev/null +++ b/docs.addons/source/index.rst @@ -0,0 +1,25 @@ +.. ifupdown2 documentation master file, created by + sphinx-quickstart on Sun Jul 6 23:49:20 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to ifupdown2-addons documentation! +========================================== + +Contents: +========= + +.. toctree:: + :maxdepth: 2 + + intro.rst + gettingstarted.rst + developmentcorner.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs.addons/source/intro.rst b/docs.addons/source/intro.rst new file mode 100644 index 0000000..35f187e --- /dev/null +++ b/docs.addons/source/intro.rst @@ -0,0 +1,21 @@ +python-ifupdown2-addons +----------------------- + +The python-ifupdown2-addons package contains ifupdown2 addon modules. + +addon modules are responsible for applying interface configuration. +The modules are installed under /usr/share/ifupdownmodules. + +Each module can declare its own set of supported attributes. Each module +is passed the iface object (which is a representation of /etc/network/interfaces +iface entry). Each module is also passed the operation to be performed. + +Example modules are /usr/share/ifupdownmodules/address.py, +/usr/share/ifupdownmodules/bridge.py etc + +The order in which these modules are invoked is listed in +/var/lib/ifupdownaddons/addons.conf. There is a ifaddon utility in the works +to better manage the module ordering. + +For details on how to add a module, see the api reference and development +documentation. diff --git a/ifupdownaddons/__init__.py b/ifupdownaddons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifupdownaddons/bridgeutils.py b/ifupdownaddons/bridgeutils.py new file mode 100644 index 0000000..2aa43e2 --- /dev/null +++ b/ifupdownaddons/bridgeutils.py @@ -0,0 +1,498 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from utilsbase import * +import os +import re +import logging +from cache import * + +class brctl(utilsBase): + """ This class contains helper functions to interact with the bridgeutils + commands """ + + _cache_fill_done = False + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + if self.CACHE and not brctl._cache_fill_done: + self._bridge_fill() + brctl._cache_fill_done = True + + + def _bridge_get_mcattrs_from_sysfs(self, bridgename): + mcattrs = {} + mcattrmap = {'mclmc': 'multicast_last_member_count', + 'mcrouter': 'multicast_router', + 'mcsnoop' : 'multicast_snooping', + 'mcsqc' : 'multicast_startup_query_count', + 'mcqifaddr' : 'multicast_query_use_ifaddr', + 'mcquerier' : 'multicast_querier', + 'hashel' : 'hash_elasticity', + 'hashmax' : 'hash_max', + 'mclmi' : 'multicast_last_member_interval', + 'mcmi' : 'multicast_membership_interval', + 'mcqpi' : 'multicast_querier_interval', + 'mcqi' : 'multicast_query_interval', + 'mcqri' : 'multicast_query_response_interval', + 'mcsqi' : 'multicast_startup_query_interval'} + + mcattrsdivby100 = ['mclmi', 'mcmi', 'mcqpi', 'mcqi', 'mcqri', 'mcsqi'] + + for m, s in mcattrmap.items(): + n = self.read_file_oneline('/sys/class/net/%s/bridge/%s' + %(bridgename, s)) + if m in mcattrsdivby100: + try: + v = int(n) / 100 + mcattrs[m] = str(v) + except Exception, e: + self.logger.warn('error getting mc attr %s (%s)' + %(m, str(e))) + pass + else: + mcattrs[m] = n + return mcattrs + + def _bridge_attrs_fill(self, bridgename): + battrs = {} + bports = {} + + brout = self.exec_command('/sbin/brctl showstp %s' %bridgename) + chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE) + + try: + # Get all bridge attributes + broutlines = chunks[0].splitlines() + #battrs['pathcost'] = broutlines[3].split('path cost')[1].strip() + battrs['maxage'] = broutlines[4].split( + 'bridge max age')[1].strip().replace('.00', '') + battrs['hello'] = broutlines[5].split( + 'bridge hello time')[1].strip().replace('.00', + '') + battrs['fd'] = broutlines[6].split( + 'bridge forward delay')[1].strip( + ).replace('.00', '') + battrs.update(self._bridge_get_mcattrs_from_sysfs(bridgename)) + + # XXX: comment this out until mc attributes become available + # with brctl again + #battrs['hashel'] = broutlines[10].split('hash elasticity')[1].split()[0].strip() + #battrs['hashmax'] = broutlines[10].split('hash max')[1].strip() + #battrs['mclmc'] = broutlines[11].split('mc last member count')[1].split()[0].strip() + #battrs['mciqc'] = broutlines[11].split('mc init query count')[1].strip() + #battrs['mcrouter'] = broutlines[12].split('mc router')[1].split()[0].strip() + ##battrs['mcsnoop'] = broutlines[12].split('mc snooping')[1].strip() + #battrs['mclmt'] = broutlines[13].split('mc last member timer')[1].split()[0].strip() + except Exception, e: + self.logger.warn(str(e)) + pass + + linkCache.update_attrdict([bridgename, 'linkinfo'], battrs) + + for cidx in range(1, len(chunks)): + bpout = chunks[cidx].lstrip('\n') + if not bpout or bpout[0] == ' ': + continue + bplines = bpout.splitlines() + pname = bplines[0].split()[0] + bportattrs = {} + try: + bportattrs['pathcost'] = bplines[2].split( + 'path cost')[1].strip() + bportattrs['fdelay'] = bplines[4].split( + 'forward delay timer')[1].strip() + bportattrs['mcrouter'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_router' %pname) + bportattrs['mcfl'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_fast_leave' %pname) + + #bportattrs['mcrouters'] = bplines[6].split('mc router')[1].split()[0].strip() + #bportattrs['mc fast leave'] = bplines[6].split('mc fast leave')[1].strip() + except Exception, e: + self.logger.warn(str(e)) + pass + bports[pname] = bportattrs + linkCache.update_attrdict([bridgename, 'linkinfo', 'ports'], bports) + + def _bridge_fill(self, bridgename=None, refresh=False): + try: + # if cache is already filled, return + linkCache.get_attr([bridgename, 'linkinfo', 'fd']) + return + except: + pass + if not bridgename: + brctlout = self.exec_command('/sbin/brctl show') + else: + brctlout = self.exec_command('/sbin/brctl show ' + bridgename) + if not brctlout: + return + + for bline in brctlout.splitlines()[1:]: + bitems = bline.split() + if len(bitems) < 2: + continue + try: + linkCache.update_attrdict([bitems[0], 'linkinfo'], + {'stp' : bitems[2]}) + except KeyError: + linkCache.update_attrdict([bitems[0]], + {'linkinfo' : {'stp' : bitems[2]}}) + self._bridge_attrs_fill(bitems[0]) + + def _cache_get(self, attrlist, refresh=False): + try: + if self.DRYRUN: + return None + if self.CACHE: + if not self._cache_fill_done: + self._bridge_fill() + self._cache_fill_done = True + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + self._bridge_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return None + + def _cache_check(self, attrlist, value, refresh=False): + try: + attrvalue = self._cache_get(attrlist, refresh) + if attrvalue and attrvalue == value: + return True + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return False + + def _cache_update(self, attrlist, value): + if self.DRYRUN: return + try: + linkCache.add_attr(attrlist, value) + except: + pass + + def _cache_delete(self, attrlist): + if self.DRYRUN: return + try: + linkCache.del_attr(attrlist) + except: + pass + + def _cache_invalidate(self): + if self.DRYRUN: return + linkCache.invalidate() + + def create_bridge(self, bridgename): + if self.bridge_exists(bridgename): + return + self.exec_command('/sbin/brctl addbr %s' %bridgename) + self._cache_update([bridgename], {}) + + def delete_bridge(self, bridgename): + if not self.bridge_exists(bridgename): + return + self.exec_command('/sbin/brctl delbr %s' %bridgename) + self._cache_invalidate() + + def add_bridge_port(self, bridgename, bridgeportname): + """ Add port to bridge """ + ports = self._cache_get([bridgename, 'linkinfo', 'ports']) + if ports and ports.get(bridgeportname): + return + self.exec_command('/sbin/brctl addif ' + bridgename + ' ' + + bridgeportname) + self._cache_update([bridgename, 'linkinfo', 'ports', + bridgeportname], {}) + + def delete_bridge_port(self, bridgename, bridgeportname): + """ Delete port from bridge """ + ports = self._cache_get([bridgename, 'linkinfo', 'ports']) + if not ports or not ports.get(bridgeportname): + return + self.exec_command('/sbin/brctl delif ' + bridgename + ' ' + + bridgeportname) + self._cache_delete([bridgename, 'linkinfo', 'ports', + 'bridgeportname']) + + def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict): + portattrs = self._cache_get([bridgename, 'linkinfo', + 'ports', bridgeportname]) + if portattrs == None: portattrs = {} + for k, v in attrdict.iteritems(): + if self.CACHE: + curval = portattrs.get(k) + if curval and curval == v: + continue + self.exec_command('/sbin/brctl set%s %s %s %s' + %(k, bridgename, bridgeportname, v)) + + def set_bridgeport_attr(self, bridgename, bridgeportname, + attrname, attrval): + if self._cache_check([bridgename, 'linkinfo', 'ports', + bridgeportname, attrname], attrval): + return + self.exec_command('/sbin/brctl set%s %s %s %s' %(attrname, bridgename, + bridgeportname, attrval)) + + def set_bridge_attrs(self, bridgename, attrdict): + for k, v in attrdict.iteritems(): + if not v: + continue + if self._cache_check([bridgename, 'linkinfo', k], v): + continue + try: + self.exec_command('/sbin/brctl set%s %s %s' + %(k, bridgename, v)) + except Exception, e: + self.logger.warn('%s: %s' %(bridgename, str(e))) + pass + + def set_bridge_attr(self, bridgename, attrname, attrval): + if self._cache_check([bridgename, 'linkinfo', attrname], attrval): + return + self.exec_command('/sbin/brctl set%s %s %s' + %(attrname, bridgename, attrval)) + + def get_bridge_attrs(self, bridgename): + return self._cache_get([bridgename, 'linkinfo']) + + def get_bridgeport_attrs(self, bridgename, bridgeportname): + return self._cache_get([bridgename, 'linkinfo', 'ports', + bridgeportname]) + + def get_bridgeport_attr(self, bridgename, bridgeportname, attrname): + return self._cache_get([bridgename, 'linkinfo', 'ports', + bridgeportname, attrname]) + + def set_stp(self, bridge, stp_state): + self.exec_command('/sbin/brctl stp ' + bridge + ' ' + stp_state) + + def get_stp(self, bridge): + sysfs_stpstate = '/sys/class/net/%s/bridge/stp_state' %bridge + if not os.path.exists(sysfs_stpstate): + return 'error' + stpstate = self.read_file_oneline(sysfs_stpstate) + if not stpstate: + return 'error' + try: + if int(stpstate) > 0: + return 'yes' + elif int(stpstate) == 0: + return 'no' + except: + return 'unknown' + + def conv_value_to_user(self, str): + try: + ret = int(str) / 100 + except: + return None + finally: + return '%d' %ret + + def read_value_from_sysfs(self, filename, preprocess_func): + value = self.read_file_oneline(filename) + if not value: + return None + return preprocess_func(value) + + def set_ageing(self, bridge, ageing): + self.exec_command('/sbin/brctl setageing ' + bridge + ' ' + ageing) + + def get_ageing(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/ageing_time' + %bridge, self.conv_value_to_user) + + def set_bridgeprio(self, bridge, bridgeprio): + self.exec_command('/sbin/brctl setbridgeprio ' + bridge + ' ' + + bridgeprio) + + def get_bridgeprio(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/priority' %bridge) + + def set_fd(self, bridge, fd): + self.exec_command('/sbin/brctl setfd ' + bridge + ' ' + fd) + + def get_fd(self, bridge): + return self.read_value_from_sysfs( + '/sys/class/net/%s/bridge/forward_delay' + %bridge, self.conv_value_to_user) + + def set_gcint(self, bridge, gcint): + #cmd = '/sbin/brctl setgcint ' + bridge + ' ' + gcint + raise Exception('set_gcint not implemented') + + def set_hello(self, bridge, hello): + self.exec_command('/sbin/brctl sethello ' + bridge + ' ' + hello) + + def get_hello(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/hello_time' + %bridge, self.conv_value_to_user) + + def set_maxage(self, bridge, maxage): + self.exec_command('/sbin/brctl setmaxage ' + bridge + ' ' + maxage) + + def get_maxage(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/max_age' + %bridge, self.conv_value_to_user) + + def set_pathcost(self, bridge, port, pathcost): + self.exec_command('/sbin/brctl setpathcost %s' %bridge + ' %s' %port + + ' %s' %pathcost) + + def get_pathcost(self, bridge, port): + return self.read_file_oneline('/sys/class/net/%s/brport/path_cost' + %port) + + def set_portprio(self, bridge, port, prio): + self.exec_command('/sbin/brctl setportprio %s' %bridge + ' %s' %port + + ' %s' %prio) + + def get_portprio(self, bridge, port): + return self.read_file_oneline('/sys/class/net/%s/brport/priority' + %port) + + def set_hashmax(self, bridge, hashmax): + self.exec_command('/sbin/brctl sethashmax %s' %bridge + ' %s' %hashmax) + + def get_hashmax(self, bridge): + return self.read_file_oneline('/sys/class/net/%s/bridge/hash_max' + %bridge) + + def set_hashel(self, bridge, hashel): + self.exec_command('/sbin/brctl sethashel %s' %bridge + ' %s' %hashel) + + def get_hashel(self, bridge): + return self.read_file_oneline('/sys/class/net/%s/bridge/hash_elasticity' + %bridge) + + def set_mclmc(self, bridge, mclmc): + self.exec_command('/sbin/brctl setmclmc %s' %bridge + ' %s' %mclmc) + + def get_mclmc(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_last_member_count' + %bridge) + + def set_mcrouter(self, bridge, mcrouter): + self.exec_command('/sbin/brctl setmcrouter %s' %bridge + + ' %s' %mcrouter) + + def get_mcrouter(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_router' %bridge) + + def set_mcsnoop(self, bridge, mcsnoop): + self.exec_command('/sbin/brctl setmcsnoop %s' %bridge + + ' %s' %mcsnoop) + + def get_mcsnoop(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_snooping' %bridge) + + def set_mcsqc(self, bridge, mcsqc): + self.exec_command('/sbin/brctl setmcsqc %s' %bridge + + ' %s' %mcsqc) + + def get_mcsqc(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_startup_query_count' + %bridge) + + def set_mcqifaddr(self, bridge, mcqifaddr): + self.exec_command('/sbin/brctl setmcqifaddr %s' %bridge + + ' %s' %mcqifaddr) + + def get_mcqifaddr(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_startup_query_use_ifaddr' + %bridge) + + def set_mcquerier(self, bridge, mcquerier): + self.exec_command('/sbin/brctl setmcquerier %s' %bridge + + ' %s' %mcquerier) + + def get_mcquerier(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_querier' %bridge) + + def set_mcqv4src(self, bridge, vlan, mcquerier): + if vlan == 0 or vlan > 4095: + self.logger.warn('mcqv4src vlan \'%d\' invalid range' %vlan) + return + + ip = mcquerier.split('.') + if len(ip) != 4: + self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) + return + for k in ip: + if not k.isdigit() or int(k, 10) < 0 or int(k, 10) > 255: + self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) + return + + self.exec_command('/sbin/brctl setmcqv4src %s' %bridge + + ' %d %s' %(vlan, mcquerier)) + + def del_mcqv4src(self, bridge, vlan): + self.exec_command('/sbin/brctl delmcqv4src %s %d' %(bridge, vlan)) + + def get_mcqv4src(self, bridge, vlan=None): + mcqv4src = {} + mcqout = self.exec_command('/sbin/brctl showmcqv4src %s' %bridge) + if not mcqout: return None + mcqlines = mcqout.splitlines() + for l in mcqlines[1:]: + l=l.strip() + k, d, v = l.split('\t') + if not k or not v: + continue + mcqv4src[k] = v + if vlan: + return mcqv4src.get(vlan) + return mcqv4src + + def set_mclmi(self, bridge, mclmi): + self.exec_command('/sbin/brctl setmclmi %s' %bridge + + ' %s' %mclmi) + + def get_mclmi(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_last_member_interval' + %bridge) + + def set_mcmi(self, bridge, mcmi): + self.exec_command('/sbin/brctl setmcmi %s' %bridge + + ' %s' %mcmi) + + def get_mcmi(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_membership_interval' + %bridge) + + def bridge_exists(self, bridge): + return os.path.exists('/sys/class/net/%s/bridge' %bridge) + + def bridge_port_exists(self, bridge, bridgeportname): + try: + return bridgeportname in os.listdir('/sys/class/net/%s/brif' + %bridge) + except Exception: + return False + + def get_bridge_ports(self, bridgename): + try: + return os.listdir('/sys/class/net/%s/brif/' %bridgename) + except: + return {} diff --git a/ifupdownaddons/cache.py b/ifupdownaddons/cache.py new file mode 100644 index 0000000..61773b5 --- /dev/null +++ b/ifupdownaddons/cache.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import pprint +from collections import OrderedDict + +class linkCache(): + """ This class contains methods and instance variables to cache + link info """ + + _shared_state = {} + + """ { : { 'ifindex': , + 'mtu': , + 'state' : ', + 'flags' : , + 'kind' : , + 'linkinfo' : { : , + : , + : { + } """ + links = {} + @classmethod + def get_attr(cls, mapList): + return reduce(lambda d, k: d[k], mapList, linkCache.links) + + @classmethod + def set_attr(cls, mapList, value): + cls.get_attr(mapList[:-1])[mapList[-1]] = value + + @classmethod + def del_attr(cls, mapList): + try: + del cls.get_attr(mapList[:-1])[mapList[-1]] + except: + pass + + @classmethod + def update_attrdict(cls, mapList, valuedict): + try: + cls.get_attr(mapList[:-1])[mapList[-1]].update(valuedict) + except: + cls.get_attr(mapList[:-1])[mapList[-1]] = valuedict + pass + + @classmethod + def append_to_attrlist(cls, mapList, value): + cls.get_attr(mapList[:-1])[mapList[-1]].append(value) + + @classmethod + def remove_from_attrlist(cls, mapList, value): + try: + cls.get_attr(mapList[:-1])[mapList[-1]].remove(value) + except: + pass + + @classmethod + def check_attr(cls, attrlist, value=None): + try: + cachedvalue = cls.get_attr(attrlist) + if value: + if cachedvalue == value: + return True + else: + return False + elif cachedvalue: + return True + else: + return False + except: + return False + + @classmethod + def invalidate(cls): + cls.links = {} + + @classmethod + def dump(cls): + print 'Dumping link cache' + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(cls.links) + + @classmethod + def dump_link(cls, linkname): + print 'Dumping link %s' %linkname + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(cls.links.get(linkname)) diff --git a/ifupdownaddons/dhclient.py b/ifupdownaddons/dhclient.py new file mode 100644 index 0000000..811ff40 --- /dev/null +++ b/ifupdownaddons/dhclient.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from utilsbase import * +import subprocess +import os + +FNULL = open(os.devnull, 'w') + +class dhclient(utilsBase): + """ This class contains helper methods to interact with the dhclient + utility """ + + def _pid_exists(self, pidfilename): + if os.path.exists(pidfilename): + pid = self.read_file_oneline(pidfilename) + if not os.path.exists('/proc/%s' %pid): + return False + else: + return False + return True + + def is_running(self, ifacename): + return self._pid_exists('/run/dhclient.%s.pid' %ifacename) + + def is_running6(self, ifacename): + return self._pid_exists('/run/dhclient6.%s.pid' %ifacename) + + def stop(self, ifacename): + if os.path.exists('/sbin/dhclient3'): + cmd = ['/sbin/dhclient3', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp3/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + else: + cmd = ['/sbin/dhclient', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + self.subprocess_check_call(cmd) + + def start(self, ifacename): + if os.path.exists('/sbin/dhclient3'): + cmd = ['/sbin/dhclient3', '-pf', + '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp3/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + else: + cmd = ['/sbin/dhclient', '-pf', '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + self.subprocess_check_call(cmd) + + def release(self, ifacename): + if os.path.exists('/sbin/dhclient3'): + cmd = ['/sbin/dhclient3', '-r', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp3/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + else: + cmd = ['/sbin/dhclient', '-r', '-pf', + '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + self.subprocess_check_call(cmd) + + def start6(self, ifacename): + self.subprocess_check_call(['dhclient', '-6', '-pf', + '/run/dhclient6.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient.%s.leases ' %ifacename, + '%s' %ifacename]) + + def stop6(self, ifacename): + self.subprocess_check_call(['dhclient', '-6', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient.%s.leases ' %ifacename, + '%s' %ifacename]) + + def release6(self, ifacename): + self.subprocess_check_call(['dhclient', '-6', '-r', '-pf', + '/run/dhclient6.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient6.%s.leases' %ifacename, + '%s' %ifacename]) diff --git a/ifupdownaddons/ifenslaveutil.py b/ifupdownaddons/ifenslaveutil.py new file mode 100644 index 0000000..27851d3 --- /dev/null +++ b/ifupdownaddons/ifenslaveutil.py @@ -0,0 +1,407 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +from ifupdown.iface import * +from utilsbase import * +from iproute2 import * +from cache import * + +class ifenslaveutil(utilsBase): + """ This class contains methods to interact with linux kernel bond + related interfaces """ + + _cache_fill_done = False + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + if self.CACHE and not self._cache_fill_done: + self._bond_linkinfo_fill_all() + self._cache_fill_done = True + + def _bond_linkinfo_fill_attrs(self, bondname): + try: + linkCache.links[bondname]['linkinfo'] = {} + except: + linkCache.links[bondname] = {'linkinfo': {}} + + try: + linkCache.set_attr([bondname, 'linkinfo', 'slaves'], + self.read_file_oneline('/sys/class/net/%s/bonding/slaves' + %bondname).split()) + linkCache.set_attr([bondname, 'linkinfo', 'mode'], + self.read_file_oneline('/sys/class/net/%s/bonding/mode' + %bondname).split()[0]) + linkCache.set_attr([bondname, 'linkinfo', 'xmit_hash_policy'], + self.read_file_oneline( + '/sys/class/net/%s/bonding/xmit_hash_policy' + %bondname).split()[0]) + linkCache.set_attr([bondname, 'linkinfo', 'lacp_rate'], + self.read_file_oneline('/sys/class/net/%s/bonding/lacp_rate' + %bondname).split()[1]) + linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_priority'], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_priority' + %bondname)) + linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_mac_addr'], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_mac_addr' + %bondname)) + map(lambda x: linkCache.set_attr([bondname, 'linkinfo', x], + self.read_file_oneline('/sys/class/net/%s/bonding/%s' + %(bondname, x))), + ['use_carrier', 'miimon', 'min_links', 'num_unsol_na', + 'num_grat_arp']) + except Exception, e: + pass + + def _bond_linkinfo_fill_all(self): + bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') + if not bondstr: + return + [self._bond_linkinfo_fill_attrs(b) for b in bondstr.split()] + + def _bond_linkinfo_fill(self, bondname, refresh=False): + try: + linkCache.get_attr([bondname, 'linkinfo', 'slaves']) + return + except: + pass + bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') + if (not bondstr or bondname not in bondstr.split()): + raise Exception('bond %s not found' %bondname) + self._bond_linkinfo_fill_attrs(bondname) + + def _cache_get(self, attrlist, refresh=False): + try: + if self.DRYRUN: + return None + if self.CACHE: + if not ifenslaveutil._cache_fill_done: + self._bond_linkinfo_fill_all() + ifenslaveutil._cache_fill_done = True + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + self._bond_linkinfo_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return None + + def _cache_check(self, attrlist, value, refresh=False): + try: + attrvalue = self._cache_get(attrlist, refresh) + if attrvalue and attrvalue == value: + return True + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return False + + def _cache_update(self, attrlist, value): + if self.DRYRUN: return + try: + if attrlist[-1] == 'slaves': + linkCache.add_to_attrlist(attrlist, value) + return + linkCache.add_attr(attrlist, value) + except: + pass + + def _cache_delete(self, attrlist, value=None): + if self.DRYRUN: return + try: + if attrlist[-1] == 'slaves': + linkCache.remove_from_attrlist(attrlist, value) + return + linkCache.del_attr(attrlist) + except: + pass + + def _cache_invalidate(self): + if self.DRYRUN: return + linkCache.invalidate() + + def set_attrs(self, bondname, attrdict, prehook): + for attrname, attrval in attrdict.items(): + if (self._cache_check([bondname, 'linkinfo', + attrname], attrval)): + continue + if (attrname == 'mode' or attrname == 'xmit_hash_policy' or + attrname == 'lacp_rate' or attrname == 'min_links'): + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s/bonding/%s' + %(bondname, attrname), attrval) + except Exception, e: + if self.FORCE: + self.logger.warn(str(e)) + pass + else: + raise + + def set_use_carrier(self, bondname, use_carrier): + if not use_carrier or (use_carrier != '0' and use_carrier != '1'): + return + if (self._cache_check([bondname, 'linkinfo', 'use_carrier'], + use_carrier)): + return + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/use_carrier', use_carrier) + self._cache_update([bondname, 'linkinfo', + 'use_carrier'], use_carrier) + + def get_use_carrier(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'use_carrier']) + + def set_xmit_hash_policy(self, bondname, hash_policy, prehook=None): + valid_values = ['layer2', 'layer3+4', 'layer2+3'] + if not hash_policy: + return + if hash_policy not in valid_values: + raise Exception('invalid hash policy value %s' %hash_policy) + if (self._cache_check([bondname, 'linkinfo', 'xmit_hash_policy'], + hash_policy)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/xmit_hash_policy', hash_policy) + self._cache_update([bondname, 'linkinfo', 'xmit_hash_policy'], + hash_policy) + + def get_xmit_hash_policy(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'xmit_hash_policy']) + + def set_miimon(self, bondname, miimon): + if (self._cache_check([bondname, 'linkinfo', 'miimon'], + miimon)): + return + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/miimon', miimon) + self._cache_update([bondname, 'linkinfo', 'miimon'], miimon) + + def get_miimon(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'miimon']) + + def set_mode(self, bondname, mode, prehook=None): + valid_modes = ['balance-rr', 'active-backup', 'balance-xor', + 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb'] + if not mode: + return + if mode not in valid_modes: + raise Exception('invalid mode %s' %mode) + if (self._cache_check([bondname, 'linkinfo', 'mode'], + mode)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s' %bondname + '/bonding/mode', mode) + self._cache_update([bondname, 'linkinfo', 'mode'], mode) + + def get_mode(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'mode']) + + def set_lacp_rate(self, bondname, lacp_rate, prehook=None, posthook=None): + if not lacp_rate or (lacp_rate != '0' and lacp_rate != '1'): + return + if (self._cache_check([bondname, 'linkinfo', 'lacp_rate'], + lacp_rate)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_rate', lacp_rate) + except: + raise + finally: + if posthook: + prehook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_rate'], lacp_rate) + + def get_lacp_rate(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_rate']) + + def set_lacp_fallback_allow(self, bondname, allow, prehook=None, posthook=None): + if (self._cache_check([bondname, 'linkinfo', 'lacp_fallback_allow'], + lacp_fallback_allow)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_fallback_allow', allow) + except: + raise + finally: + if posthook: + posthook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_fallback_allow'], allow) + + def get_lacp_fallback_allow(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_fallback_allow']) + + def set_lacp_fallback_period(self, bondname, period, prehook=None, posthook=None): + if (self._cache_check([bondname, 'linkinfo', 'lacp_fallback_period'], + lacp_fallback_period)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_fallback_period', period) + except: + raise + finally: + if posthook: + posthook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_fallback_period'], period) + + def get_lacp_fallback_period(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_fallback_period']) + + def set_min_links(self, bondname, min_links, prehook=None): + if (self._cache_check([bondname, 'linkinfo', 'min_links'], + min_links)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s/bonding/min_links' %bondname, + min_links) + self._cache_update([bondname, 'linkinfo', 'min_links'], min_links) + + def get_min_links(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'min_links']) + + def set_lacp_fallback_priority(self, bondname, port, val): + slavefile = '/sys/class/net/%s/bonding_slave/lacp_fallback_priority' %port + if os.path.exists(slavefile): + self.write_file(slavefile, val) + + def get_lacp_fallback_priority(self, bondname): + slaves = self.get_slaves(bondname) + if not slaves: + return slaves + prios = [] + for slave in slaves: + priofile = '/sys/class/net/%s/bonding_slave/lacp_fallback_priority' %slave + if os.path.exists(priofile): + val = self.read_file_oneline(priofile) + if val and val != '0': + prio = slave + '=' + val + prios.append(prio) + prios.sort() + prio_str = ' '.join(prios) + return prio_str + + def get_ad_sys_mac_addr(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'ad_sys_mac_addr']) + + def get_ad_sys_priority(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'ad_sys_priority']) + + def enslave_slave(self, bondname, slave, prehook=None, posthook=None): + slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) + if slaves and slave in slaves: return + if prehook: + prehook(slave) + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/slaves', '+' + slave) + if posthook: + posthook(slave) + self._cache_update([bondname, 'linkinfo', 'slaves'], slave) + + def remove_slave(self, bondname, slave): + slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) + if slave not in slaves: + return + sysfs_bond_path = ('/sys/class/net/%s' %bondname + + '/bonding/slaves') + if not os.path.exists(sysfs_bond_path): + return + self.write_file(sysfs_bond_path, '-' + slave) + self._cache_delete([bondname, 'linkinfo', 'slaves'], slave) + + def remove_slaves_all(self, bondname): + if not _self._cache_get([bondname, 'linkinfo', 'slaves']): + return + slaves = None + sysfs_bond_path = ('/sys/class/net/%s' %bondname + + '/bonding/slaves') + ipcmd = iproute2() + try: + f = open(sysfs_bond_path, 'r') + slaves = f.readline().strip().split() + f.close() + except IOError, e: + raise Exception('error reading slaves of bond %s' %bondname + + '(' + str(e) + ')') + for slave in slaves: + ipcmd.ip_link_down(slave) + try: + self.remove_slave(bondname, slave) + except Exception, e: + if not self.FORCE: + raise Exception('error removing slave %s' + %slave + ' from bond %s' %bondname + + '(%s)' %str(e)) + else: + pass + self._cache_del([bondname, 'linkinfo', 'slaves']) + + def load_bonding_module(self): + return self.exec_command('modprobe -q bonding') + + def create_bond(self, bondname): + if self.bond_exists(bondname): + return + sysfs_net = '/sys/class/net/' + sysfs_bonding_masters = sysfs_net + 'bonding_masters' + if not os.path.exists(sysfs_bonding_masters): + self.logger.debug('loading bonding driver') + self.load_bonding_module() + return True + self.write_file(sysfs_bonding_masters, '+' + bondname) + self._cache_update([bondname], {}) + + def delete_bond(self, bondname): + if not os.path.exists('/sys/class/net/%s' %bondname): + return + self.write_file('/sys/class/net/bonding_masters', '-' + bondname) + self._cache_delete([bondname]) + + def unset_master(self, bondname): + print 'Do nothing yet' + return 0 + + def get_slaves(self, bondname): + slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) + if slaves: + return list(slaves) + slavefile = '/sys/class/net/%s/bonding/slaves' %bondname + if os.path.exists(slavefile): + buf = self.read_file_oneline(slavefile) + if buf: + slaves = buf.split() + if not slaves: + return slaves + self._cache_update([bondname, 'linkinfo', 'slaves'], slaves) + return list(slaves) + + def bond_slave_exists(self, bond, slave): + slaves = self.get_slaves(bond) + if not slaves: return False + return slave in slaves + + def bond_exists(self, bondname): + return os.path.exists('/sys/class/net/%s/bonding' %bondname) diff --git a/ifupdownaddons/iproute2.py b/ifupdownaddons/iproute2.py new file mode 100644 index 0000000..c3a0128 --- /dev/null +++ b/ifupdownaddons/iproute2.py @@ -0,0 +1,613 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +from collections import OrderedDict +from utilsbase import * +from cache import * + +class iproute2(utilsBase): + """ This class contains helper methods to cache and interact with the + commands in the iproute2 package """ + + _cache_fill_done = False + ipbatchbuf = '' + ipbatch = False + ipbatch_pause = False + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + if self.CACHE and not iproute2._cache_fill_done: + self._link_fill() + self._addr_fill() + iproute2._cache_fill_done = True + + def _link_fill(self, ifacename=None, refresh=False): + """ fills cache with link information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + + linkout = {} + if iproute2._cache_fill_done and not refresh: return + try: + # if ifacename already present, return + if (ifacename and not refresh and + linkCache.get_attr([ifacename, 'ifflag'])): + return + except: + pass + cmdout = self.link_show(ifacename=ifacename) + if not cmdout: + return + for c in cmdout.splitlines(): + citems = c.split() + ifnamenlink = citems[1].split('@') + if len(ifnamenlink) > 1: + ifname = ifnamenlink[0] + iflink = ifnamenlink[1].strip(':') + else: + ifname = ifnamenlink[0].strip(':') + iflink = None + linkattrs = {} + linkattrs['link'] = iflink + linkattrs['ifindex'] = citems[0].strip(':') + flags = citems[2].strip('<>').split(',') + linkattrs['flags'] = flags + linkattrs['ifflag'] = 'UP' if 'UP' in flags else 'DOWN' + for i in range(0, len(citems)): + if citems[i] == 'mtu': linkattrs['mtu'] = citems[i+1] + elif citems[i] == 'state': linkattrs['state'] = citems[i+1] + elif citems[i] == 'link/ether': linkattrs['hwaddress'] = citems[i+1] + elif citems[i] == 'vlan' and citems[i+1] == 'id': + linkattrs['linkinfo'] = {'vlanid' : citems[i+2]} + elif citems[i] == 'vxlan' and citems[i+1] == 'id': + vattrs = {'vxlanid' : citems[i+2], + 'svcnode' : []} + for j in range(i+2, len(citems)): + if citems[j] == 'local': + vattrs['local'] = citems[j+1] + elif citems[j] == 'svcnode': + vattrs['svcnode'].append(citems[j+1]) + elif citems[j] == 'peernode': + vattrs['peernode'].append(citems[j+1]) + linkattrs['linkinfo'] = vattrs + break + #linkattrs['alias'] = self.read_file_oneline( + # '/sys/class/net/%s/ifalias' %ifname) + linkout[ifname] = linkattrs + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in linkout.items()] + + def _addr_filter(self, addr, scope=None): + default_addrs = ['127.0.0.1/8', '::1/128' , '0.0.0.0'] + if addr in default_addrs: + return True + if scope and scope == 'link': + return True + return False + + def _addr_fill(self, ifacename=None, refresh=False): + """ fills cache with address information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + + linkout = {} + if iproute2._cache_fill_done: return + try: + # Check if ifacename is already full, in which case, return + if ifacename: + linkCache.get_attr([ifacename, 'addrs']) + return + except: + pass + cmdout = self.addr_show(ifacename=ifacename) + if not cmdout: + return + for c in cmdout.splitlines(): + citems = c.split() + ifnamenlink = citems[1].split('@') + if len(ifnamenlink) > 1: + ifname = ifnamenlink[0] + else: + ifname = ifnamenlink[0].strip(':') + if citems[2] == 'inet': + if self._addr_filter(citems[3], scope=citems[5]): + continue + addrattrs = {} + addrattrs['scope'] = citems[5] + addrattrs['type'] = 'inet' + linkout[ifname]['addrs'][citems[3]] = addrattrs + elif citems[2] == 'inet6': + if self._addr_filter(citems[3], scope=citems[5]): + continue + if citems[5] == 'link': continue #skip 'link' addresses + addrattrs = {} + addrattrs['scope'] = citems[5] + addrattrs['type'] = 'inet6' + linkout[ifname]['addrs'][citems[3]] = addrattrs + else: + linkattrs = {} + linkattrs['addrs'] = OrderedDict({}) + try: + linkout[ifname].update(linkattrs) + except KeyError: + linkout[ifname] = linkattrs + + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in linkout.items()] + + def _cache_get(self, type, attrlist, refresh=False): + try: + if self.DRYRUN: + return False + if self.CACHE: + if not iproute2._cache_fill_done: + self._link_fill() + self._addr_fill() + iproute2._cache_fill_done = True + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + if type == 'link': + self._link_fill(attrlist[0], refresh) + elif type == 'addr': + self._addr_fill(attrlist[0], refresh) + else: + self._link_fill(attrlist[0], refresh) + self._addr_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return None + + def _cache_check(self, type, attrlist, value, refresh=False): + try: + attrvalue = self._cache_get(type, attrlist, refresh) + if attrvalue and attrvalue == value: + return True + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return False + + def _cache_update(self, attrlist, value): + if self.DRYRUN: return + try: + linkCache.add_attr(attrlist, value) + except: + pass + + def _cache_delete(self, attrlist): + if self.DRYRUN: return + try: + linkCache.del_attr(attrlist) + except: + pass + + def _cache_invalidate(self): + linkCache.invalidate() + + def batch_start(self): + self.ipbatcbuf = '' + self.ipbatch = True + self.ipbatch_pause = False + + def add_to_batch(self, cmd): + self.ipbatchbuf += cmd + '\n' + + def batch_pause(self): + self.ipbatch_pause = True + + def batch_resume(self): + self.ipbatch_pause = False + + def batch_commit(self): + if not self.ipbatchbuf: + return + try: + self.exec_command_talk_stdin('ip -force -batch -', + stdinbuf=self.ipbatchbuf) + except Exception: + raise + finally: + self.ipbatchbuf = '' + self.ipbatch = False + self.ipbatch_pause = False + + def addr_show(self, ifacename=None): + if ifacename: + if not self.link_exists(ifacename): + return + return self.exec_commandl(['ip','-o', 'addr', 'show', 'dev', + '%s' %ifacename]) + else: + return self.exec_commandl(['ip', '-o', 'addr', 'show']) + + def link_show(self, ifacename=None): + if ifacename: + return self.exec_commandl(['ip', '-o', '-d', 'link', + 'show', 'dev', '%s' %ifacename]) + else: + return self.exec_commandl(['ip', '-o', '-d', 'link', 'show']) + + def addr_add(self, ifacename, address, broadcast=None, + peer=None, scope=None, preferred_lifetime=None): + if not address: + return + cmd = 'addr add %s' %address + if broadcast: + cmd += ' broadcast %s' %broadcast + if peer: + cmd += ' peer %s' %peer + if scope: + cmd += ' scope %s' %scope + if preferred_lifetime: + cmd += ' preferred_lft %s' %preferred_lifetime + cmd += ' dev %s' %ifacename + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + self._cache_update([ifacename, 'addrs', address], {}) + + def addr_del(self, ifacename, address, broadcast=None, + peer=None, scope=None): + """ Delete ipv4 address """ + if not address: + return + if not self._cache_get('addr', [ifacename, 'addrs', address]): + return + cmd = 'addr del %s' %address + if broadcast: + cmd += 'broadcast %s' %broadcast + if peer: + cmd += 'peer %s' %peer + if scope: + cmd += 'scope %s' %scope + cmd += ' dev %s' %ifacename + self.exec_command('ip ' + cmd) + self._cache_delete([ifacename, 'addrs', address]) + + def addr_flush(self, ifacename): + cmd = 'addr flush dev %s' %ifacename + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + self._cache_delete([ifacename, 'addrs']) + + def del_addr_all(self, ifacename, skip_addrs=[]): + if not skip_addrs: skip_addrs = [] + runningaddrsdict = self.addr_get(ifacename) + try: + # XXX: ignore errors. Fix this to delete secondary addresses + # first + [self.addr_del(ifacename, a) for a in + set(runningaddrsdict.keys()).difference(skip_addrs)] + except: + # ignore errors + pass + + def addr_get(self, ifacename, details=True): + addrs = self._cache_get('addr', [ifacename, 'addrs']) + if not addrs: + return None + if details: + return addrs + return addrs.keys() + + def addr_add_multiple(self, ifacename, addrs, purge_existing=False): + # purges address + if purge_existing: + # if perfmode is not set and also if iface has no sibling + # objects, purge addresses that are not present in the new + # config + runningaddrs = self.addr_get(ifacename, details=False) + if addrs == runningaddrs: + return + try: + # if primary address is not same, there is no need to keep any. + # reset all addresses + if (addrs and runningaddrs and + (addrs[0] != runningaddrs[0])): + self.del_addr_all(ifacename) + else: + self.del_addr_all(ifacename, addrs) + except Exception, e: + self.log_warn(str(e)) + for a in addrs: + try: + self.addr_add(ifacename, a) + except Exception, e: + self.logger.error(str(e)) + + def _link_set_ifflag(self, ifacename, value): + # Dont look at the cache, the cache may have stale value + # because link status can be changed by external + # entity (One such entity is ifupdown main program) + cmd = 'link set dev %s %s' %(ifacename, value.lower()) + if self.ipbatch: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + + def link_up(self, ifacename): + self._link_set_ifflag(ifacename, 'UP') + + def link_down(self, ifacename): + self._link_set_ifflag(ifacename, 'DOWN') + + def link_set(self, ifacename, key, value=None, force=False): + if not force: + if (key not in ['master', 'nomaster'] and + self._cache_check('link', [ifacename, key], value)): + return + cmd = 'link set dev %s %s' %(ifacename, key) + if value: + cmd += ' %s' %value + if self.ipbatch: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + if key not in ['master', 'nomaster']: + self._cache_update([ifacename, key], value) + + def link_set_hwaddress(self, ifacename, hwaddress, force=False): + if not force: + if self._cache_check('link', [ifacename, 'hwaddress'], hwaddress): + return + self.link_down(ifacename) + cmd = 'link set dev %s address %s' %(ifacename, hwaddress) + if self.ipbatch: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + self.link_up(ifacename) + self._cache_update([ifacename, 'hwaddress'], hwaddress) + + def link_set_alias(self, ifacename, alias): + self.exec_commandl(['ip', 'link', 'set', 'dev', + ifacename, 'alias', alias]) + + def link_get_alias(self, ifacename): + return self.read_file_oneline('/sys/class/net/%s/ifalias' + %ifacename) + + def link_isloopback(self, ifacename): + flags = self._cache_get('link', [ifacename, 'flags']) + if not flags: + return + if 'LOOPBACK' in flags: + return True + return False + + def link_get_status(self, ifacename): + return self._cache_get('link', [ifacename, 'ifflag'], refresh=True) + + def route_add_gateway(self, ifacename, gateway, metric=None): + if not gateway: + return + cmd = 'ip route add default via %s' %gateway + # Add metric + if metric: + cmd += 'metric %s' %metric + cmd += ' dev %s' %ifacename + self.exec_command(cmd) + + def route_del_gateway(self, ifacename, gateway, metric=None): + # delete default gw + if not gateway: + return + cmd = 'ip route del default via %s' %gateway + if metric: + cmd += ' metric %s' %metric + cmd += ' dev %s' %ifacename + self.exec_command(cmd) + + def route6_add_gateway(self, ifacename, gateway): + if not gateway: + return + return self.exec_command('ip -6 route add default via %s' %gateway + + ' dev %s' %ifacename) + + def route6_del_gateway(self, ifacename, gateway): + if not gateway: + return + return self.exec_command('ip -6 route del default via %s' %gateway + + 'dev %s' %ifacename) + + def link_create_vlan(self, vlan_device_name, vlan_raw_device, vlanid): + if self.link_exists(vlan_device_name): + return + self.exec_command('ip link add link %s' %vlan_raw_device + + ' name %s' %vlan_device_name + + ' type vlan id %d' %vlanid) + self._cache_update([vlan_device_name], {}) + + def link_create_vlan_from_name(self, vlan_device_name): + v = vlan_device_name.split('.') + if len(v) != 2: + self.logger.warn('invalid vlan device name %s' %vlan_device_name) + return + self.link_create_vlan(vlan_device_name, v[0], v[1]) + + def link_create_macvlan(self, name, linkdev, mode='private'): + if self.link_exists(name): + return + cmd = ('link add link %s' %linkdev + + ' name %s' %name + + ' type macvlan mode %s' %mode) + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + self._cache_update([name], {}) + + def link_create_vxlan(self, name, vxlanid, + localtunnelip=None, + svcnodeips=None, + peernodeips=None, + learning='off'): + if svcnodeips and peernodeips: + raise Exception("svcnodeip and peernodeip is mutually exclusive") + args = '' + if localtunnelip: + args += ' local %s' %localtunnelip + if svcnodeips: + for s in svcnodeips: + args += ' svcnode %s' %s + if peernodeips: + for s in peernodeips: + args += ' peernode %s' %s + if learning == 'on': + args += ' learning' + + if self.link_exists(name): + cmd = 'link set dev %s type vxlan ' %(name) + else: + cmd = 'link add dev %s type vxlan id %s' %(name, vxlanid) + cmd += args + + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + # XXX: update linkinfo correctly + self._cache_update([name], {}) + + def link_exists(self, ifacename): + return os.path.exists('/sys/class/net/%s' %ifacename) + + def is_vlan_device_by_name(self, ifacename): + if re.search(r'\.', ifacename): + return True + return False + + def route_add(self, route): + self.exec_command('ip route add ' + route) + + def route6_add(self, route): + self.exec_command('ip -6 route add ' + route) + + def get_vlandev_attrs(self, ifacename): + return (self._cache_get('link', [ifacename, 'linkinfo', 'link']), + self._cache_get('link', [ifacename, 'linkinfo', 'vlanid'])) + + def link_get_mtu(self, ifacename): + return self._cache_get('link', [ifacename, 'mtu']) + + def link_get_hwaddress(self, ifacename): + return self._cache_get('link', [ifacename, 'hwaddress']) + + def link_create(self, ifacename, type, link=None): + if self.link_exists(ifacename): + return + cmd = 'link add' + if link: + cmd += ' link %s' %link + cmd += ' name %s type %s' %(ifacename, type) + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + self._cache_update([ifacename], {}) + + def link_delete(self, ifacename): + if not self.link_exists(ifacename): + return + cmd = 'link del %s' %ifacename + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + self._cache_invalidate() + + def bridge_port_vids_add(self, bridgeportname, vids): + [self.exec_command('bridge vlan add vid %s dev %s' + %(v, bridgeportname)) for v in vids] + + def bridge_port_vids_del(self, bridgeportname, vids): + if not vids: + return + [self.exec_command('bridge vlan del vid %s dev %s' + %(v, bridgeportname)) for v in vids] + + def bridge_port_vids_flush(self, bridgeportname): + self.exec_command('bridge vlan del vid %s dev %s' + %(vid, bridgeportname)) + + def bridge_port_vids_get(self, bridgeportname): + self.exec_command('/bin/bridge vlan show %s' %bridgeportname) + bridgeout = self.exec_command('/bin/bridge vlan show dev %s' + %bridgeportname) + if not bridgeout: return [] + brvlanlines = bridgeout.readlines()[2:] + vids = [l.strip() for l in brvlanlines] + return [vid for v in vids if vid] + + def bridge_port_vids_get_all(self): + brvlaninfo = {} + bridgeout = self.exec_command('/bin/bridge vlan show') + if not bridgeout: return brvlaninfo + brvlanlines = bridgeout.splitlines() + brportname=None + for l in brvlanlines[1:]: + if l and l[0] not in [' ', '\t']: + brportname = None + l=l.strip() + if not l: + brportname=None + continue + if 'PVID' in l: + attrs = l.split() + brportname = attrs[0] + brvlaninfo[brportname] = {'pvid' : attrs[1], + 'vlan' : []} + elif brportname: + if 'Egress Untagged' not in l: + brvlaninfo[brportname]['vlan'].append(l) + elif not brportname: + attrs = l.split() + if attrs[1] == 'None' or 'Egress Untagged' in attrs[1]: + continue + brportname = attrs[0] + brvlaninfo[brportname] = {'vlan' : [attrs[1]]} + return brvlaninfo + + def bridge_port_pvid_add(self, bridgeportname, pvid): + self.exec_command('bridge vlan add vid %s untagged pvid dev %s' + %(pvid, bridgeportname)) + + def bridge_port_pvid_del(self, bridgeportname, pvid): + self.exec_command('bridge vlan del vid %s untagged pvid dev %s' + %(pvid, bridgeportname)) + + def bridge_port_pvids_get(self, bridgeportname): + return self.read_file_oneline('/sys/class/net/%s/brport/pvid' + %bridgeportname) + + def bridge_vids_add(self, bridgeportname, vids, bridge=True): + target = 'self' if bridge else '' + [self.exec_command('bridge vlan add vid %s dev %s %s' + %(v, bridgeportname, target)) for v in vids] + + def bridge_vids_del(self, bridgeportname, vids, bridge=True): + target = 'self' if bridge else '' + [self.exec_command('bridge vlan del vid %s dev %s %s' + %(v, bridgeportname, target)) for v in vids] + + def bridge_is_vlan_aware(self, bridgename): + filename = '/sys/class/net/%s/bridge/vlan_filtering' %bridgename + if os.path.exists(filename) and self.read_file_oneline(filename) == '1': + return True + return False diff --git a/ifupdownaddons/modulebase.py b/ifupdownaddons/modulebase.py new file mode 100644 index 0000000..46880a6 --- /dev/null +++ b/ifupdownaddons/modulebase.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import re +import io +import logging +import subprocess +import traceback +from ifupdown.iface import * +#from ifupdownaddons.iproute2 import * +#from ifupdownaddons.dhclient import * +#from ifupdownaddons.bridgeutils import * +#from ifupdownaddons.mstpctlutil import * +#from ifupdownaddons.ifenslaveutil import * + +class moduleBase(object): + """ Base class for ifupdown addon modules + + Provides common infrastructure methods for all addon modules """ + + def __init__(self, *args, **kargs): + modulename = self.__class__.__name__ + self.logger = logging.getLogger('ifupdown.' + modulename) + self.FORCE = kargs.get('force', False) + """force interface configuration""" + self.DRYRUN = kargs.get('dryrun', False) + """only predend you are applying configuration, dont really do it""" + self.NOWAIT = kargs.get('nowait', False) + self.PERFMODE = kargs.get('perfmode', False) + self.CACHE = kargs.get('cache', False) + self.CACHE_FLAGS = kargs.get('cacheflags', 0x0) + + def log_warn(self, str): + """ log a warning if err str is not one of which we should ignore """ + if not self.ignore_error(str): + if self.logger.getEffectiveLevel() == logging.DEBUG: + traceback.print_stack() + self.logger.warn(str) + pass + + def log_error(self, str): + """ log an err if err str is not one of which we should ignore and raise an exception """ + if not self.ignore_error(str): + if self.logger.getEffectiveLevel() == logging.DEBUG: + traceback.print_stack() + raise Exception(str) + else: + pass + + def exec_command(self, cmd, cmdenv=None): + """ execute command passed as argument. + + Args: + cmd (str): command to execute + + Kwargs: + cmdenv (dict): environment variable name value pairs + """ + cmd_returncode = 0 + cmdout = '' + + try: + self.logger.info('Executing ' + cmd) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmd.split(), + stdout=subprocess.PIPE, + shell=False, env=cmdenv, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate()[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('could not execute ' + cmd + + '(' + str(e) + ')') + if cmd_returncode != 0: + raise Exception('error executing cmd \'%s\'' %cmd + + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def exec_command_talk_stdin(self, cmd, stdinbuf): + """ execute command passed as argument and write contents of stdinbuf + into stdin of the cmd + + Args: + cmd (str): command to execute + stdinbuf (str): string to write to stdin of the cmd process + """ + cmd_returncode = 0 + cmdout = '' + + try: + self.logger.info('Executing %s (stdin=%s)' %(cmd, stdinbuf)) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmd.split(), + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + shell=False, env=cmdenv, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate(input=stdinbuf)[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('could not execute ' + cmd + + '(' + str(e) + ')') + if cmd_returncode != 0: + raise Exception('error executing cmd \'%s (%s)\'' + %(cmd, stdinbuf) + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def get_ifaces_from_proc(self): + ifacenames = [] + with open('/proc/net/dev') as f: + try: + lines = f.readlines() + for line in lines: + ifacenames.append(line.split()[0].strip(': ')) + except: + raise + return ifacenames + + def parse_regex(self, expr, ifacenames=None): + try: + proc_ifacenames = self.get_ifaces_from_proc() + except: + self.logger.warn('error reading ifaces from proc') + for proc_ifacename in proc_ifacenames: + if re.search(expr + '$', proc_ifacename): + yield proc_ifacename + if not ifacenames: + return + for ifacename in ifacenames: + if re.search(expr + '$', ifacename): + yield ifacename + + def parse_glob(self, expr): + errmsg = ('error parsing glob expression \'%s\'' %expr + + ' (supported glob syntax: swp1-10 or swp[1-10])') + start_index = 0 + end_index = 0 + try: + regexs = [re.compile(r"([A-Za-z0-9]+[A-Za-z])(\d+)\-(\d+)(.*)"), + re.compile(r"([A-Za-z0-9]+)\[(\d+)\-(\d+)\](.*)")] + for r in regexs: + m = r.match(expr) + if not m: + continue + mlist = m.groups() + if len(mlist) != 4: + raise Exception(errmsg + '(unexpected len)') + prefix = mlist[0] + suffix = mlist[3] + start_index = int(mlist[1]) + end_index = int(mlist[2]) + except: + self.logger.warn(errmsg) + pass + if not start_index and not end_index: + self.logger.warn(errmsg) + yield expr + else: + for i in range(start_index, end_index + 1): + yield prefix + '%d' %i + suffix + + def parse_port_list(self, port_expr, ifacenames=None): + """ parse port list containing glob and regex + + Args: + port_expr (str): expression + ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression + """ + regex = 0 + glob = 0 + portlist = [] + + if not port_expr: + return None + for expr in re.split(r'[\s\t]\s*', port_expr): + if expr == 'regex': + regex = 1 + elif expr == 'glob': + glob = 1 + elif regex: + for port in self.parse_regex(expr, ifacenames): + if port not in portlist: + portlist.append(port) + regex = 0 + elif glob: + for port in self.parse_glob(expr): + portlist.append(port) + glob = 0 + else: + portlist.append(expr) + if not portlist: + return None + return portlist + + def ignore_error(self, errmsg): + if (self.FORCE or re.search(r'exists', errmsg, + re.IGNORECASE | re.MULTILINE)): + return True + return False + + def write_file(self, filename, strexpr): + """ writes string to a file """ + try: + self.logger.info('writing \'%s\'' %strexpr + + ' to file %s' %filename) + if self.DRYRUN: + return 0 + with open(filename, 'w') as f: + f.write(strexpr) + except IOError, e: + self.logger.warn('error writing to file %s' + %filename + '(' + str(e) + ')') + return -1 + return 0 + + def read_file(self, filename): + """ read file and return lines from the file """ + try: + self.logger.info('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readlines() + except: + return None + return None + + def read_file_oneline(self, filename): + """ reads and returns first line from the file """ + try: + self.logger.info('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readline().strip('\n') + except: + return None + return None + + def sysctl_set(self, variable, value): + """ set sysctl variable to value passed as argument """ + self.exec_command('sysctl %s=' %variable + '%s' %value) + + def sysctl_get(self, variable): + """ get value of sysctl variable """ + return self.exec_command('sysctl %s' %variable).split('=')[1].strip() + + def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc, + prehook=None, prehookargs=None): + ifacename = ifaceobj.name + attrvalue = ifaceobj.get_attr_value_first(attr_name) + if attrvalue: + if prehook: + if prehookargs: + prehook(prehookargs) + else: + prehook(ifacename) + attr_valsetfunc(ifacename, attrvalue) + + def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr, + attr_name, attr_valgetfunc, + attr_valgetextraarg=None): + attrvalue = ifaceobj.get_attr_value_first(attr_name) + if not attrvalue: + return + if attr_valgetextraarg: + runningattrvalue = attr_valgetfunc(ifaceobj.name, + attr_valgetextraarg) + else: + runningattrvalue = attr_valgetfunc(ifaceobj.name) + if (not runningattrvalue or + (runningattrvalue != attrvalue)): + ifaceobjcurr.update_config_with_status(attr_name, + runningattrvalue, 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, + runningattrvalue, 0) + + def dict_key_subset(self, a, b): + """ returns a list of differing keys """ + return [x for x in a if x in b] + + def get_mod_attrs(self): + """ returns list of all module attrs defined in the module _modinfo dict""" + try: + return self._modinfo.get('attrs').keys() + except: + return None + + def get_mod_attr(self, attrname): + """ returns module attr info """ + try: + return self._modinfo.get('attrs', {}).get(attrname) + except: + return None + + def get_mod_subattr(self, attrname, subattrname): + """ returns module attrs defined in the module _modinfo dict""" + try: + return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname], + self._modinfo) + except: + return None + + def get_modinfo(self): + """ return module info """ + try: + return self._modinfo + except: + return None + + def get_flags(self): + return dict(force=self.FORCE, dryrun=self.DRYRUN, nowait=self.NOWAIT, + perfmode=self.PERFMODE, cache=self.CACHE, + cacheflags=self.CACHE_FLAGS) diff --git a/ifupdownaddons/mstpctlutil.py b/ifupdownaddons/mstpctlutil.py new file mode 100644 index 0000000..c178d96 --- /dev/null +++ b/ifupdownaddons/mstpctlutil.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from utilsbase import * +from ifupdown.iface import * +from cache import * +import re + +class mstpctlutil(utilsBase): + """ This class contains helper methods to interact with mstpd using + mstputils commands """ + + _cache_fill_done = False + + _bridgeattrmap = {'bridgeid' : 'bridge-id', + 'maxage' : 'max-age', + 'fdelay' : 'forward-delay', + 'txholdcount' : 'tx-hold-count', + 'maxhops' : 'max-hops', + 'ageing' : 'ageing-time', + 'hello' : 'hello-time', + 'forcevers' : 'force-protocol-version'} + + _bridgeportattrmap = {'portadminedge' : 'admin-edge-port', + 'portp2p' : 'point-to-point', + 'portrestrrole' : 'restricted-role', + 'portrestrtcn' : 'restricted-TCN', + 'bpduguard' : 'bpdu-guard-port', + 'portautoedge' : 'auto-edge-port', + 'portnetwork' : 'network-port', + 'portbpdufilter' : 'bpdufilter-port'} + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + + def is_mstpd_running(self): + try: + self.exec_command('/bin/pidof mstpd') + except: + return False + else: + return True + + def get_bridgeport_attr(self, bridgename, portname, attrname): + try: + return self.subprocess_check_output(['/sbin/mstpctl', + 'showportdetail', '%s' %bridgename, '%s' %portname, + self._bridgeportattrmap[attrname]]).strip('\n') + except Exception, e: + pass + return None + + def get_bridgeport_attrs(self, bridgename, portname): + bridgeattrs = {} + try: + bridgeattrs = dict((k, self.get_bridgeport_attr(bridgename, v)) + for k, v in self._bridgeattrmap.items()) + bridgeattrs['treeprio'] = int(bridgeattrs.get('bridgeid', + '').split('.')[0], base=16) * 4096 + except Exception, e: + self.logger.warn(str(e)) + pass + return bridgeattrs + + def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict, + check=True): + for k, v in attrdict.iteritems(): + if not v: + continue + try: + self.set_bridgeport_attr(self, bridgename, bridgeportname, + k, v, check) + except Exception, e: + self.logger.warn(str(e)) + + def set_bridgeport_attr(self, bridgename, bridgeportname, attrname, + attrvalue, check=True): + if check: + attrvalue_curr = self.get_bridgeport_attr(bridgename, + bridgeportname, attrname) + if attrvalue_curr and attrvalue_curr == attrvalue: + return + if attrname == 'treeportcost' or attrname == 'treeportprio': + self.subprocess_check_output(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '%s' %bridgeportname, '0', '%s' %attrvalue]) + else: + self.subprocess_check_output(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '%s' %bridgeportname, '%s' %attrvalue]) + + def get_bridge_attrs(self, bridgename): + bridgeattrs = {} + try: + bridgeattrs = dict((k, self.get_bridge_attr(bridgename, k)) + for k in self._bridgeattrmap.keys()) + bridgeattrs['treeprio'] = '%d' %(int(bridgeattrs.get('bridgeid', + '').split('.')[0], base=16) * 4096) + del bridgeattrs['bridgeid'] + except Exception, e: + self.logger.info(bridgeattrs) + self.logger.warn(str(e)) + pass + return bridgeattrs + + def get_bridge_attr(self, bridgename, attrname): + try: + return self.subprocess_check_output(['/sbin/mstpctl', + 'showbridge', '%s' %bridgename, + self._bridgeattrmap[attrname]]).strip('\n') + except Exception, e: + pass + return None + + def set_bridge_attr(self, bridgename, attrname, attrvalue, check=True): + + if check: + attrvalue_curr = self.get_bridge_attr(bridgename, attrname) + if attrvalue_curr and attrvalue_curr == attrvalue: + return + if attrname == 'treeprio': + self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '0', '%s' %attrvalue]) + else: + self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '%s' %attrvalue]) + + def set_bridge_attrs(self, bridgename, attrdict, check=True): + for k, v in attrdict.iteritems(): + if not v: + continue + try: + self.set_bridge_attr(bridgename, k, v, check) + except Exception, e: + self.logger.warn('%s: %s' %(bridgename, str(e))) + pass + + def get_bridge_treeprio(self, bridgename): + try: + bridgeid = subprocess.check_output(['/sbin/mstpctl', + 'showbridge', '%s' %bridgename, + self._bridgeattrmap['bridgeid']]).strip('\n') + return '%d' %(int(bridgeid.split('.')[0], base=16) * 4096) + except: + pass + return None + + def set_bridge_treeprio(self, bridgename, attrvalue, check=True): + if check: + attrvalue_curr = self.get_bridge_treeprio(bridgename) + if attrvalue_curr and attrvalue_curr == attrvalue: + return + self.subprocess_check_output(['/sbin/mstpctl', 'settreeprio', + '%s' %bridgename, '0', '%s' %attrvalue]) + + def showbridge(self, bridgename=None): + if bridgename: + return self.exec_command('/sbin/mstpctl showbridge %s' %bridgename) + else: + return self.exec_command('/sbin/mstpctl showbridge') + + def showportdetail(self, bridgename): + return self.exec_command('/sbin/mstpctl showportdetail %s' %bridgename) + + def mstpbridge_exists(self, bridgename): + try: + subprocess.check_call('mstpctl showbridge %s' %bridgename) + return True + except: + return False diff --git a/ifupdownaddons/utilsbase.py b/ifupdownaddons/utilsbase.py new file mode 100644 index 0000000..73fdfd5 --- /dev/null +++ b/ifupdownaddons/utilsbase.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import logging +import subprocess +import re +import io +from ifupdown.iface import * +from cache import * + +#import timeit +import time +import logging + +def profile(func): + def wrap(*args, **kwargs): + started_at = time.time() + result = func(*args, **kwargs) + print str(func) + print (time.time() - started_at) + return result + return wrap + +class utilsBase(object): + """ Base class for ifupdown addon utilities """ + + def __init__(self, *args, **kargs): + modulename = self.__class__.__name__ + self.logger = logging.getLogger('ifupdown.' + modulename) + self.FORCE = kargs.get('force', False) + self.DRYRUN = kargs.get('dryrun', False) + self.NOWAIT = kargs.get('nowait', False) + self.PERFMODE = kargs.get('perfmode', False) + self.CACHE = kargs.get('cache', False) + + def exec_commandl(self, cmdl, cmdenv=None): + """ Executes command """ + + cmd_returncode = 0 + cmdout = '' + try: + self.logger.info('executing ' + ' '.join(cmdl)) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmdl, + stdout=subprocess.PIPE, + shell=False, env=cmdenv, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate()[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(' '.join(cmdl), str(e))) + if cmd_returncode != 0: + raise Exception('failed to execute cmd \'%s\'' + %' '.join(cmdl) + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def exec_command(self, cmd, cmdenv=None): + """ Executes command given as string in the argument cmd """ + + return self.exec_commandl(cmd.split(), cmdenv) + + def exec_command_talk_stdin(self, cmd, stdinbuf): + """ Executes command and writes to stdin of the process """ + cmd_returncode = 0 + cmdout = '' + try: + self.logger.info('executing %s [%s]' %(cmd, stdinbuf)) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmd.split(), + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + shell=False, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate(input=stdinbuf)[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(cmd, str(e))) + if cmd_returncode != 0: + raise Exception('failed to execute cmd \'%s [%s]\'' + %(cmd, stdinbuf) + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def subprocess_check_output(self, cmdl): + self.logger.info('executing ' + ' '.join(cmdl)) + if self.DRYRUN: + return + try: + return subprocess.check_output(cmdl, stderr=subprocess.STDOUT) + except Exception, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(' '.join(cmdl), e.output)) + + def subprocess_check_call(self, cmdl): + """ subprocess check_call implementation using popen + + Uses popen because it needs the close_fds argument + """ + + cmd_returncode = 0 + try: + self.logger.info('executing ' + ' '.join(cmdl)) + if self.DRYRUN: + return + ch = subprocess.Popen(cmdl, + stdout=None, + shell=False, + stderr=None, + close_fds=True) + cmd_returncode = ch.wait() + except Exception, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(' '.join(cmdl), str(e))) + if cmd_returncode != 0: + raise Exception('failed to execute cmd \'%s\'' + %' '.join(cmdl)) + return + + def write_file(self, filename, strexpr): + try: + self.logger.info('writing \'%s\'' %strexpr + + ' to file %s' %filename) + if self.DRYRUN: + return 0 + with open(filename, 'w') as f: + f.write(strexpr) + except IOError, e: + self.logger.warn('error writing to file %s' + %filename + '(' + str(e) + ')') + return -1 + return 0 + + def read_file(self, filename): + try: + self.logger.debug('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readlines() + except: + return None + return None + + def read_file_oneline(self, filename): + try: + self.logger.debug('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readline().strip('\n') + except: + return None + return None + + def sysctl_set(self, variable, value): + self.exec_command('sysctl %s=' %variable + '%s' %value) + + def sysctl_get(self, variable): + return self.exec_command('sysctl %s' %variable).split('=')[1].strip() diff --git a/man.rst/ifupdown-addons-interfaces.5.rst b/man.rst/ifupdown-addons-interfaces.5.rst new file mode 100644 index 0000000..24d18c7 --- /dev/null +++ b/man.rst/ifupdown-addons-interfaces.5.rst @@ -0,0 +1,1079 @@ +========================== +ifupdown-addons-interfaces +========================== +--------------------------------------------------------- +ifupdown2 addon modules interface configuration +--------------------------------------------------------- +:Author: roopa@cumulusnetworks.com +:Date: 2013-09-25 +:Copyright: Copyright 2013 Cumulus Networks, Inc. All rights reserved. +:Version: 0.1 +:Manual section: 5 + + +DESCRIPTION +=========== + ifupdown2 addon modules add incremental functionality to + core ifupdown2 tool. + + All installed addon modules are executed on every interface + listed in the interfaces file. Addon modules are installed under + /usr/share/ifupdownaddons. To see the list of active addon + modules, see ifaddon(8). + + Addon modules add new attributes to the interfaces(5) file. + Below is a list of attribute options provided by each module. + These can be listed under each iface section in the interfaces(5) + file. + + +EXAMPLES +======== + Listed below are addon modules and their supported attributes. + The attributes if applicable go under the iface section in the + interfaces(5) file. + + **ethtool**: ethtool configuration module for interfaces + + + **link-duplex** + + **help**: set link duplex + + + **required**: False + + **default**: half + + **validvals**: half,full + + **example**: + link-duplex full + + + **link-autoneg** + + **help**: set autonegotiation + + + **required**: False + + **default**: off + + **validvals**: on,off + + **example**: + link-autoneg on + + + **link-speed** + + **help**: set link speed + + + **required**: False + + **example**: + link-speed 1000 + + + + **bridge**: bridge configuration module + + + **bridge-mcqifaddr** + + **help**: set multicast query to use ifaddr + + + **required**: False + + **default**: 0 + + **example**: + bridge-mcqifaddr 0 + + + **bridge-gcint** + + **help**: bridge garbage collection interval in secs + + + **required**: False + + **default**: 4 + + **example**: + bridge-gcint 4 + + + **bridge-mcsqc** + + **help**: set multicast startup query count + + + **required**: False + + **default**: 2 + + **example**: + bridge-mcsqc 2 + + + **bridge-stp** + + **help**: bridge-stp yes/no + + + **required**: False + + **default**: no + + **validvals**: yes,on,off,no + + **example**: + bridge-stp no + + + **bridge-mcsqi** + + **help**: set multicast startup query interval (in secs) + + + **required**: False + + **default**: 31 + + **example**: + bridge-mcsqi 31 + + + **bridge-mcmi** + + **help**: set multicast membership interval (in secs) + + + **required**: False + + **default**: 260 + + **example**: + bridge-mcmi 260 + + + **bridge-ports** + + **help**: bridge ports + + + **required**: True + + **example**: + bridge-ports swp1.100 swp2.100 swp3.100 + + bridge-ports glob swp1-3.100 + + bridge-ports regex (swp[1|2|3].100) + + + **bridge-mcsnoop** + + **help**: set multicast snooping + + + **required**: False + + **default**: 1 + + **example**: + bridge-mcsnoop 1 + + + **bridge-maxwait** + + **help**: forces to time seconds the maximum time that the Deb + ian bridge setup scripts will wait for the bridge ports to ge + t to the forwarding status, doesn't allow factional part. If i + t is equal to 0 then no waiting is done + + + **required**: False + + **default**: 0 + + **example**: + bridge-maxwait 3 + + + **bridge-pathcosts** + + **help**: bridge set port path costs + + + **required**: False + + **default**: 100 + + **example**: + bridge-pathcosts swp1=100 swp2=100 + + + **bridge-portprios** + + **help**: bridge port prios + + + **required**: False + + **default**: 32 + + **example**: + bridge-portprios swp1=32 swp2=32 + + + **bridge-fd** + + **help**: bridge forward delay + + + **required**: False + + **default**: 15 + + **example**: + bridge-fd 15 + + + **bridge-ageing** + + **help**: bridge ageing + + + **required**: False + + **default**: 300 + + **example**: + bridge-ageing 300 + + + **bridge-hello** + + **help**: bridge set hello time + + + **required**: False + + **default**: 2 + + **example**: + bridge-hello 2 + + + **bridge-mcquerier** + + **help**: set multicast querier + + + **required**: False + + **default**: 0 + + **example**: + bridge-mcquerier 0 + + + **bridge-mclmc** + + **help**: set multicast last member count + + + **required**: False + + **default**: 2 + + **example**: + bridge-mclmc 2 + + + **bridge-mcrouter** + + **help**: set multicast router + + + **required**: False + + **default**: 1 + + **example**: + bridge-mcrouter 1 + + + **bridge-portmcrouter** + + **help**: set port multicast routers + + + **required**: False + + **default**: 1 + + **example**: + bridge-portmcrouter swp1=1 swp2=1 + + + **bridge-mclmi** + + **help**: set multicast last member interval (in secs) + + + **required**: False + + **default**: 1 + + **example**: + bridge-mclmi 1 + + + **bridge-hashmax** + + **help**: set hash max + + + **required**: False + + **default**: 4096 + + **example**: + bridge-hashmax 4096 + + + **bridge-waitport** + + **help**: wait for a max of time secs for the specified ports + to become available,if no ports are specified then those speci + fied on bridge-ports will be used here. Specifying no ports he + re should not be used if we are using regex or "all" on bridge + _ports,as it wouldnt work. + + + **required**: False + + **default**: 0 + + **example**: + bridge-waitport 4 + + + **bridge-mcqri** + + **help**: set multicast query response interval (in secs) + + + **required**: False + + **default**: 10 + + **example**: + bridge-mcqri 10 + + + **bridge-hashel** + + **help**: set hash elasticity + + + **required**: False + + **default**: 4096 + + **example**: + bridge-hashel 4096 + + + **bridge-mcqpi** + + **help**: set multicast querier interval (in secs) + + + **required**: False + + **default**: 255 + + **example**: + bridge-mcqpi 255 + + + **bridge-bridgeprio** + + **help**: bridge priority + + + **required**: False + + **default**: 32768 + + **example**: + bridge-bridgeprio 32768 + + + **bridge-maxage** + + **help**: bridge set maxage + + + **required**: False + + **default**: 20 + + **example**: + bridge-maxage 20 + + + **bridge-portmcfl** + + **help**: port multicast fast leave + + + **required**: False + + **default**: 0 + + **example**: + bridge-portmcfl swp1=0 swp2=0 + + + **bridge-mcqi** + + **help**: set multicast query interval (in secs) + + + **required**: False + + **default**: 125 + + **example**: + bridge-mcqi 125 + + + + **usercmds**: user commands for interfaces + + + **down** + + **help**: run command at interface down + + + **required**: False + + **post-up** + + **help**: run command after interface bring up + + + **required**: False + + **up** + + **help**: run command at interface bring up + + + **required**: False + + **pre-down** + + **help**: run command before bringing the interface down + + + **required**: False + + **pre-up** + + **help**: run command before bringing the interface up + + + **required**: False + + **post-down** + + **help**: run command after bringing interface down + + + **required**: False + + + **mstpctl**: mstp configuration module for bridges + + + **mstpctl-fdelay** + + **help**: set forwarding delay + + + **required**: False + + **default**: 15 + + **example**: + mstpctl-fdelay 15 + + + **mstpctl-txholdcount** + + **help**: bridge transmit holdcount + + + **required**: False + + **default**: 6 + + **example**: + mstpctl-txholdcount 6 + + + **mstpctl-portautoedge** + + **help**: enable/disable auto transition to/from edge state of + the port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portautoedge swp1=yes swp2=yes + + + **mstpctl-portrestrrole** + + **help**: enable/disable port ability to take root role of the + port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portrestrrole swp1=no swp2=no + + + **mstpctl-portnetwork** + + **help**: enable/disable bridge assurance capability for a por + t + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portnetwork swp1=no swp2=no + + + **mstpctl-portp2p** + + **help**: bridge port p2p detection mode + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portp2p swp1=no swp2=no + + + **mstpctl-treeprio** + + **help**: tree priority + + + **required**: False + + **default**: 32768 + + validrange: 0-65535 + + **example**: + mstpctl-treeprio 32768 + + + **mstpctl-treeportprio** + + **help**: port priority for MSTI instance + + + **required**: False + + **default**: 128 + + validrange: 0-240 + + **example**: + mstpctl-treeportprio swp1=128 swp2=128 + + + **mstpctl-hello** + + **help**: set hello time + + + **required**: False + + **default**: 2 + + **example**: + mstpctl-hello 2 + + + **mstpctl-ageing** + + **help**: ageing time + + + **required**: False + + **default**: 300 + + **example**: + mstpctl-ageing 300 + + + **mstpctl-portadminedge** + + **help**: enable/disable initial edge state of the port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portadminedge swp1=no swp2=no + + + **mstpctl-maxage** + + **help**: max message age + + + **required**: False + + **default**: 20 + + **example**: + mstpctl-maxage 20 + + + **mstpctl-maxhops** + + **help**: bridge max hops + + + **required**: False + + **default**: 15 + + **example**: + mstpctl-maxhops 15 + + + **mstpctl-portrestrtcn** + + **help**: enable/disable port ability to propagate received to + pology change notification of the port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portrestrtcn swp1=no swp2=no + + + **mstpctl-portpathcost** + + **help**: bridge port path cost + + + **required**: False + + **default**: 0 + + **example**: + mstpctl-portpathcost swp1=0 swp2=1 + + + **mstpctl-portadminage** + + **help**: bridge port admin age + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portadminage swp1=no swp2=no + + + **mstpctl-portbpdufilter** + + **help**: enable/disable bpdu filter on a port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portbpdufilter swp1=no swp2=no + + + **mstpctl-forcevers** + + **help**: bridge force stp version + + + **required**: False + + **default**: rstp + + **example**: + mstpctl-forcevers rstp + + + **mstpctl-treeportcost** + + **help**: port tree cost + + + **required**: False + + **mstpctl-bpduguard** + + **help**: enable/disable bpduguard + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-bpduguard swp1=no swp2=no + + + + **vlan**: vlan module configures vlan interfaces.This module under + stands vlan interfaces with dot notations. eg swp1.100. Vlan inter + faces with any other names need to have raw device and vlan id att + ributes + + + **vlan-id** + + **help**: vlan id + + + **required**: False + + **vlan-raw-device** + + **help**: vlan raw device + + + **required**: False + + + **ifenslave**: bond configuration module + + + **bond-miimon** + + **help**: bond miimon + + + **required**: False + + **default**: 0 + + validrange: 0-255 + + **example**: + bond-miimon 0 + + + **bond-slaves** + + **help**: bond slaves + + + **required**: True + + **example**: + bond-slaves swp1 swp2 + + bond-slaves glob swp1-2 + + bond-slaves regex (swp[1|2) + + + **bond-mode** + + **help**: bond mode + + + **required**: False + + **default**: balance-rr + + **validvals**: balance-rr,active-backup,balance-xor,broadcast,802.3ad,balance-tlb,balance-alb + + **example**: + bond-mode 802.3ad + + + **bond-num-grat-arp** + + **help**: bond use carrier + + + **required**: False + + **default**: 1 + + validrange: 0-255 + + **example**: + bond-num-grat-arp 1 + + + **bond-ad-sys-mac-addr** + + **help**: 802.3ad system mac address + + + **required**: False + + **default**: 00:00:00:00:00:00 + + **example**: + bond-ad-sys-mac-addr 00:00:00:00:00:00 + + + **bond-use-carrier** + + **help**: bond use carrier + + + **required**: False + + **default**: 1 + + **validvals**: 0,1 + + **example**: + bond-use-carrier 1 + + + **bond-lacp-rate** + + **help**: bond use carrier + + + **required**: False + + **default**: 0 + + **validvals**: 0,1 + + **example**: + bond-lacp-rate 0 + + + **bond-min-links** + + **help**: bond min links + + + **required**: False + + **default**: 0 + + **example**: + bond-min-links 0 + + + **bond-num-unsol-na** + + **help**: bond slave devices + + + **required**: False + + **default**: 1 + + validrange: 0-255 + + **example**: + bond-num-unsol-na 1 + + + **bond-ad-sys-priority** + + **help**: 802.3ad system priority + + + **required**: False + + **default**: 65535 + + **example**: + bond-ad-sys-priority 65535 + + + **bond-xmit-hash-policy** + + **help**: bond slave devices + + + **required**: False + + **default**: layer2 + + **validvals**: layer2,layer3+4,layer2+3 + + **example**: + bond-xmit-hash-policy layer2 + + + + **address**: address configuration module for interfaces + + + **broadcast** + + **help**: broadcast address + + + **required**: False + + **example**: + broadcast 10.0.1.255 + + + **hwaddress** + + **help**: hw address + + + **required**: False + + **example**: + hwaddress 44:38:39:00:27:b8 + + + **alias** + + **help**: description/alias + + + **required**: False + + **example**: + alias testnetwork + + + **address** + + **help**: ipv4 or ipv6 addresses + + + **required**: False + + **example**: + address 10.0.12.3/24 + + address 2000:1000:1000:1000:3::5/128 + + + **scope** + + **help**: scope + + + **required**: False + + **example**: + scope host + + + **preferred-lifetime** + + **help**: preferred lifetime + + + **required**: False + + **example**: + preferred-lifetime forever + + preferred-lifetime 10 + + + **gateway** + + **help**: default gateway + + + **required**: False + + **example**: + gateway 255.255.255.0 + + + **mtu** + + **help**: interface mtu + + + **required**: False + + **default**: 1500 + + **example**: + mtu 1600 + + + +SEE ALSO +======== + interfaces(5), + ifup(8), + ip(8), + mstpctl(8), + brctl(8), + ethtool(8) diff --git a/sbin/ifaddon b/sbin/ifaddon new file mode 100755 index 0000000..145bc3d --- /dev/null +++ b/sbin/ifaddon @@ -0,0 +1,350 @@ +#!/usr/bin/python + +import sys +import os +import re +import argparse +from collections import OrderedDict + +lockfile="/run/network/.lock" +modules_configfile='/var/lib/ifupdownaddons/addons.conf' +modules_dir='/usr/share/ifupdownaddons' + +addon_config = OrderedDict([('pre-up', []), + ('up', []), + ('post-up', []), + ('pre-down', []), + ('down', []), + ('post-down', [])]) + +def read_modules_config(): + with open(modules_configfile, 'r') as f: + lines = f.readlines() + for l in lines: + litems = l.rstrip(' \n').split(',') + operation = litems[0] + mname = litems[1] + addon_config[operation].append(mname) + +def man_rst_header(): + print '==========================' + print 'ifupdown-addons-interfaces' + print '==========================' + + print '---------------------------------------------------------' + print 'ifupdown2 addon modules interface configuration' + print '---------------------------------------------------------' + + print ':Author: roopa@cumulusnetworks.com' + print ':Date: 2013-09-25' + print ':Copyright: Copyright 2013 Cumulus Networks, Inc. All rights reserved.' + print ':Version: 0.1' + print ':Manual section: 5' + print '\n' + +def man_rst_body(): + + print 'DESCRIPTION' + print '===========' + + print (''' ifupdown2 addon modules add incremental functionality to + core ifupdown2 tool. + + All installed addon modules are executed on every interface + listed in the interfaces file. Addon modules are installed under + /usr/share/ifupdownaddons. To see the list of active addon + modules, see ifaddon(8). + + Addon modules add new attributes to the interfaces(5) file. + Below is a list of attribute options provided by each module. + These can be listed under each iface section in the interfaces(5) + file. ''') + + print '\n' + +def get_addon_modinfo(modules_dir): + """ load python modules from modules_dir + + Default modules_dir is /usr/share/ifupdownmodules + + """ + if not modules_dir in sys.path: + sys.path.append(modules_dir) + read_modules_config() + modinfo = {} + try: + for op, mlist in addon_config.items(): + for mname in mlist: + if mname in modinfo.keys(): continue + mpath = modules_dir + '/' + mname + '.py' + if os.path.exists(mpath): + try: + m = __import__(mname) + mclass = getattr(m, mname) + except: + pass + continue + minstance = mclass() + if hasattr(minstance, 'get_modinfo'): + modinfo[mname] = minstance.get_modinfo() + except: + raise + + return modinfo + +def print_long_string(indent, strarg): + slen = 70 - len(indent) + tmphelpstr = strarg + l = len(strarg) + while l > 0: + rem = slen if l >= slen else l + print('%s%s' %(indent, tmphelpstr[:rem])) + tmphelpstr = tmphelpstr[rem:].strip() + l -= rem + +def man_rst_examples(): + print 'EXAMPLES' + print '========' + print ''' Listed below are addon modules and their supported attributes. + The attributes if applicable go under the iface section in the + interfaces(5) file.\n''' + + indent = ' ' + modinfo = get_addon_modinfo(modules_dir) + for m, mdict in modinfo.items(): + aindent = indent + ' ' + aindentplus = aindent + ' ' + if not mdict: + continue + print_long_string(indent, '**%s**: %s' %(m, mdict.get('mhelp', ''))) + attrdict = mdict.get('attrs') + if not attrdict: + continue + print '\n' + try: + for attrname, attrvaldict in attrdict.items(): + if attrvaldict.get('compat', False): + continue + print('%s**%s**\n' %(aindent, attrname)) + print_long_string(aindentplus, '**help**: %s' + %(attrvaldict.get('help', ''))) + print '\n' + print('%s**required**: %s\n' %(aindentplus, + attrvaldict.get('required', False))) + default = attrvaldict.get('default') + if default: + print('%s**default**: %s\n' %(aindentplus, default)) + validrange = attrvaldict.get('validrange') + if validrange: + print('%svalidrange: %s\n' + %(aindentplus, '-'.join(validrange))) + validvals = attrvaldict.get('validvals') + if validvals: + print('%s**validvals**: %s\n' + %(aindentplus, ','.join(validvals))) + examples = attrvaldict.get('example') + if not examples: + continue + print '%s**example**:' %(aindentplus) + for e in examples: + print '%s%s\n' %(aindentplus + indent, e) + print '' + except Exception, e: + print "Roopa: m = %s, str(e) = %s\n" %(m, str(e)) + pass + print '' + +def man_rst_see_also(): + print 'SEE ALSO' + print '========' + print ''' interfaces(5), + ifup(8), + ip(8), + mstpctl(8), + brctl(8), + ethtool(8)''' + +def show_man_rst(): + man_rst_header() + man_rst_body() + man_rst_examples() + man_rst_see_also() + +def show(): + for operation, mlist in addon_config.items(): + postion = 1 + for m in mlist: + print '%d. %s' %(postion, m) + postion += 1 + +def write_modules_config(): + with open(modules_configfile, 'w') as f: + for op, mlist in addon_config.items(): + [f.write('%s,%s\n' %(op, m)) for m in mlist] + +def process_add_cmd(args): + op = args.operation + module = args.module + position = args.position + if not op: + for k, vlist in addon_config.items(): + if module not in vlist: + addon_config[k].append(module) + else: + print '%s: module %s already present' %(k, module) + return + if module in addon_config.get(op): + print 'module already present' + return + if position: + try: + addon_config[op].insert(position, module) + except Exception, e: + print ('error inserting module %s at postion %s (%s)' + %(module, position, str(e))) + raise + else: + addon_config[op].append(module) + + +def process_del_cmd(args): + op = args.operation + module = args.module + + if op: + del addon_config[op] + else: + try: + [addon_config[op].remove(module) for op in addon_config.keys()] + except ValueError: + pass + +def process_move_cmd(args): + op = args.operation + module = args.module + pos = 0 + + try: + pos = int(args.position) + if pos < 0 or pos > len(addon_config.get(op)): + raise Exception('invalid value for position') + except: + raise + + if addon_config[op].index(module) == pos: + print '%s module %s already at location %d' %(op, module, pos) + return + + addon_config[op].remove(module) + addon_config[op].insert(pos, module) + +def print_mlist(mlist, indent): + for idx, val in enumerate(mlist): + print '%s%d. %s' %(indent, idx, val) + +def process_show_cmd(args): + indent = ' ' + op = args.operation + + if args.man: + show_man_rst() + return + + if op: + mlist = addon_config[op] + print '%s:' %op + print_mlist(mlist, indent) + else: + for op, mlist in addon_config.items(): + print '%s:' %op + print_mlist(mlist, indent) + print '' + +cmdhandlers = {'add' : process_add_cmd, + 'del' : process_del_cmd, + 'move' : process_move_cmd, + 'show' : process_show_cmd} + +def update_subparser_add(subparser): + subparser.add_argument('module', metavar='MODULE', help='module name') + subparser.add_argument('operation', metavar='OPERATION', + choices=['pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down'], + help='operations', nargs='?') + subparser.add_argument('position', metavar='POSITION', nargs='?', + help='position') + subparser.set_defaults(func=process_add_cmd) + +def update_subparser_del(subparser): + subparser.add_argument('module', metavar='MODULE', help='module name') + subparser.add_argument('operation', metavar='OPERATION', + choices=['pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down'], + help='operations', nargs='?') + subparser.add_argument('position', metavar='POSITION', nargs='?', + help='position') + subparser.set_defaults(func=process_del_cmd) + +def update_subparser_move(subparser): + subparser.add_argument('module', metavar='MODULE', help='module name') + subparser.add_argument('operation', metavar='OPERATION', + choices=['pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down'], + help='operations') + subparser.add_argument('position', metavar='POSITION', + help='position') + subparser.set_defaults(func=process_move_cmd) + + +def update_subparser_show(subparser): + subparser.add_argument('--man', action='store_true', + help=argparse.SUPPRESS) + subparser.add_argument('operation', metavar='OPERATION', + choices=addon_config.keys(), + help='operations %s' %str(addon_config.keys()), + nargs='?') + subparser.set_defaults(func=process_show_cmd) + +def update_argparser(argparser): + subparsers = argparser.add_subparsers(help='sub-command help') + + parser_add = subparsers.add_parser('add') + update_subparser_add(parser_add) + + parser_del = subparsers.add_parser('del', help='del help') + update_subparser_del(parser_del) + + parser_move = subparsers.add_parser('move', help='move help') + update_subparser_move(parser_move) + + parser_show = subparsers.add_parser('show', help='show help') + update_subparser_show(parser_show) + +def parse_args(argsv): + descr = 'ifupdown addon modules management command.\n \ + This command helps add/del/display/reorder modules \n \ + in all ifupdown module categories' + + argparser = argparse.ArgumentParser(description=descr) + update_argparser(argparser) + + args = argparser.parse_args(argsv) + return args + +def main(argv): + """ main function """ + try: + # Command line arg parser + args = parse_args(argv[1:]) + read_modules_config() + args.func(args) + write_modules_config() + except Exception, e: + print 'error processing command (%s)' %str(e) + +if __name__ == "__main__": + if not os.geteuid() == 0: + print 'Error: Must be root to run this command' + exit(1) + + main(sys.argv) diff --git a/setup.py b/setup.py index b6ca032..afbf830 100755 --- a/setup.py +++ b/setup.py @@ -6,13 +6,13 @@ setup(name='ifupdown2', author='Roopa Prabhu', author_email='roopa@cumulusnetworks.com', url='cumulusnetworks.com', - packages=['ifupdown'], + packages=['ifupdown', 'ifupdownaddons'], scripts = ['sbin/ifupdown'], install_requires = ['python-gvgen'], data_files=[('share/man/man8/', ['man/ifup.8', 'man/ifquery.8', 'man/ifreload.8']), ('share/man/man5/', - ['man/interfaces.5']), + ['man/interfaces.5', 'man/ifupdown-addons-interfaces.5']), ('/etc/init.d/', ['init.d/networking']), ('/sbin/', ['sbin/ifupdown']), @@ -24,5 +24,14 @@ setup(name='ifupdown2', ['docs/examples/interfaces', 'docs/examples/interfaces_bridge_template_func', 'docs/examples/interfaces_with_template']), - ('/etc/bash_completion.d/', ['completion/ifup'])] + ('/etc/bash_completion.d/', ['completion/ifup']), + ('/usr/share/ifupdownaddons/', ['addons/bridge.py', + 'addons/ifenslave.py', 'addons/vlan.py', + 'addons/mstpctl.py', 'addons/address.py', + 'addons/dhcp.py', 'addons/usercmds.py', + 'addons/ethtool.py', 'addons/loopback.py', + 'addons/addressvirtual.py', 'addons/vxlan.py', + 'addons/bridgevlanaware.py']), + ('/var/lib/ifupdownaddons/', ['config/addons.conf']) + ] ) -- 2.39.5