]> git.proxmox.com Git - mirror_ifupdown2.git/commitdiff
Add default link parameter support for ethtool module
authorSam Tannous <stannous@cumulusnetworks.com>
Sun, 10 May 2015 19:42:47 +0000 (15:42 -0400)
committerSam Tannous <stannous@cumulusnetworks.com>
Thu, 4 Jun 2015 19:27:48 +0000 (15:27 -0400)
Ticket: CM-5254
Reviewed By: roopa
Testing Done: tested master and 2.5_br images with testifupdown2 suite and hand tested

This patch creates a json defaults file upon bootup
(which can be overridden by customer configs in /etc)
which the ethtool module in ifupdown2 will consult
when "link-x" configs are removed in order to restore
them to the initial settings used by the switch.
(cherry picked from commit 8388664f5a5a85f2a813cafbf40ac92d7b86f4bf)

Conflicts:
packages/cl-utilities/usrlib/update-ports
(cherry picked from commit 21c9c10ab2fccaf60be9accb337e82541d497cc4)

addons/ethtool.py
config/addons.conf
ifupdown/ifupdownmain.py
ifupdown/policymanager.py [new file with mode: 0644]
setup.py

index 579fdfae7c4a58b445219cf89f46ab6c4b53d262..f578128cd69cb9277425fdc70b5b43aa3ff15222 100644 (file)
 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
 #
+import json
+import ifupdown.policymanager as policymanager
 
 try:
     from ipaddr import IPNetwork
     from sets import Set
     from ifupdown.iface import *
+    from ifupdownaddons.utilsbase 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):
+class ethtool(moduleBase,utilsBase):
     """  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']},
+                             'example' : ['link-speed 1000'],
+                             'default' : 'varies by platform and port'},
                       'link-duplex' :
                             {'help': 'set link duplex',
                              'example' : ['link-duplex full'],
                              'validvals' : ['half', 'full'],
-                             'default' : 'half'},
+                             'default' : 'full'},
                       'link-autoneg' :
                             {'help': 'set autonegotiation',
                              'example' : ['link-autoneg on'],
                              'validvals' : ['on', 'off'],
-                             'default' : 'off'}}}
+                             'default' : 'varies by platform and port'}}}
 
     def __init__(self, *args, **kargs):
         moduleBase.__init__(self, *args, **kargs)
         self.ipcmd = None
 
-    def _post_up(self, ifaceobj):
+    def _post_up(self, ifaceobj, operation='post_up'):
+        """
+        _post_up and _pre_down will reset the layer 2 attributes to default policy
+        settings.
+        """
         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
+        for attr in ['speed', 'duplex', 'autoneg']:
+            # attribute existed before but we must reset to default
+            config_val = ifaceobj.get_attr_value_first('link-%s'%attr)
+            default_val = policymanager.policymanager_api.get_iface_default(
+                module_name='ethtool',
+                ifname=ifaceobj.name,
+                attr='link-%s'%attr)
+
+            # check running values
+            running_val = None
+            if attr == 'autoneg':
+                # we can only get autoneg from ethtool
+                output = self.exec_commandl(['ethtool', ifaceobj.name])
+                running_val = self.get_autoneg(ethtool_output=output)
+            else:
+                running_val = self.read_file_oneline('/sys/class/net/%s/%s' % \
+                                                     (ifaceobj.name, attr))
+            if config_val and config_val == running_val:
+                # running value is what is configured, do nothing
+                continue
+            if not config_val and default_val and default_val == running_val:
+                # nothing configured but the default is running
+                continue
+            # if we got this far, we need to change it
+            if config_val and (config_val != running_val):
+                # if the configured value is not set, set it
+                cmd += ' %s %s' % (attr, config_val)
+            elif default_val and (default_val != running_val):
+                # or if it has a default not equal to running value, set it
+                cmd += ' %s %s' % (attr, default_val)
+            else:
+                # no value set nor default, leave it alone
+                pass
         if cmd:
+            self.logger.debug('ethtool %s: iface %s cmd is %s' % \
+                              (operation, ifaceobj.name, cmd))
             try:
+                # we should only be calling ethtool if there
+                # is a speed set or we can find a default speed
+                # because we should only be calling ethtool on swp ports
                 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)))
+        else:
+            pass
+
+    def _pre_down(self, ifaceobj):
+        pass #self._post_up(ifaceobj,operation="_pre_down")
 
     def _query_check(self, ifaceobj, ifaceobjcurr):
         """
-        Advertised auto-negotiation: No
-        Speed: 1000Mb/s
-        Duplex: Full"""
-        ethtool_attrs = self.dict_key_subset(ifaceobj.config,
-                                          self.get_mod_attrs())
-        if not ethtool_attrs:
+        _query_check() needs to compare the configured (or running)
+        attribute with the running attribute.
+
+        If there is nothing configured, we compare the default attribute with
+        the running attribute and FAIL if they are different.
+        This is because a reboot will lose their running attribute
+        (the default will get set).
+        """
+        for attr in ['speed', 'duplex', 'autoneg']:
+            # autoneg comes from ethtool whereas speed and duplex from /sys/class
+            if attr == 'autoneg':
+                output = self.exec_commandl(['ethtool', ifaceobj.name])
+                running_attr = self.get_autoneg(ethtool_output=output)
+            else:
+                running_attr = self.read_file_oneline('/sys/class/net/%s/%s' % \
+                                                      (ifaceobj.name, attr))
+
+            configured = ifaceobj.get_attr_value_first('link-%s'%attr)
+            default = policymanager.policymanager_api.get_iface_default(
+                module_name='ethtool',
+                ifname=ifaceobj.name,
+                attr='link-%s'%attr)
+
+            # there is a case where there is no running config or
+            # (there is no default and it is not configured).
+            # In this case, we do nothing (e.g. eth0 has only a
+            # default duplex, lo has nothing)
+            if (not running_attr or (not configured and not default)):
+                continue
+
+            # we make sure we can get a running value first
+            if (running_attr and configured and running_attr == configured):
+                # PASS since running is what is configured 
+                ifaceobjcurr.update_config_with_status('link-%s'%attr,
+                                                       running_attr, 0)
+            elif (running_attr and configured and running_attr != configured):
+                # We show a FAIL since it is not the configured or default
+                ifaceobjcurr.update_config_with_status('link-%s'%attr,
+                                                       running_attr, 1)
+            elif (running_attr and default and running_attr == default):
+                # PASS since running is default
+                ifaceobjcurr.update_config_with_status('link-%s'%attr,
+                                                       running_attr, 0)
+            elif (default or configured):
+                # We show a FAIL since it is not the configured or default
+                ifaceobjcurr.update_config_with_status('link-%s'%attr,
+                                                       running_attr, 1)
+        return
+
+    def get_autoneg(self,ethtool_output=None):
+        """
+        get_autoneg simply calls the ethtool command and parses out
+        the autoneg value.
+        """
+        ethtool_attrs = ethtool_output.split()
+        if ('Auto-negotiation:' in ethtool_attrs):
+            return(ethtool_attrs[ethtool_attrs.index('Auto-negotiation:')+1])
+        else:
+            return(None)
+
+    def _query_running(self, ifaceobj, ifaceobj_getfunc=None):
+        """
+        _query_running looks at the speed and duplex from /sys/class
+        and retreives autoneg from ethtool.  We do not report autoneg
+        if speed is not available because this usually means the link is
+        down and the autoneg value is not reliable when the link is down.
+        """
+        # do not bother showing swp ifaces that are not up for the speed
+        # duplex and autoneg are not reliable.
+        if not self.ipcmd.is_link_up(ifaceobj.name):
             return
-        try:
-            speed = ifaceobj.get_attr_value_first('link-speed')
-            if speed:
-                running_speed = self.read_file_oneline(
-                                    '/sys/class/net/%s/speed' %ifaceobj.name)
-                if running_speed and running_speed != speed:
-                     ifaceobjcurr.update_config_with_status('link-speed',
-                             running_speed, 1)
-                else:
-                     ifaceobjcurr.update_config_with_status('link-speed',
-                             running_speed, 0)
-            duplex = ifaceobj.get_attr_value_first('link-duplex')
-            if duplex:
-                running_duplex = self.read_file_oneline(
-                                    '/sys/class/net/%s/duplex' %ifaceobj.name)
-                if running_duplex and running_duplex != duplex:
-                     ifaceobjcurr.update_config_with_status('link-duplex',
-                             running_duplex, 1)
+        for attr in ['speed', 'duplex', 'autoneg']:
+            # autoneg comes from ethtool whereas speed and duplex from /sys/class
+            running_attr = None
+            try:
+                if attr == 'autoneg':
+                    output=self.exec_commandl(['ethtool', ifaceobj.name])
+                    running_attr = self.get_autoneg(ethtool_output=output)
                 else:
-                     ifaceobjcurr.update_config_with_status('link-duplex',
-                             running_duplex, 0)
-        except Exception:
-            pass
-        return
+                    running_attr = self.read_file_oneline('/sys/class/net/%s/%s' % \
+                                                          (ifaceobj.name, attr))
+            except:
+                # for nonexistent interfaces, we get an error (rc = 256 or 19200)
+                pass
+
+            # show it
+            if (running_attr):
+                ifaceobj.update_config('link-%s'%attr, running_attr)
 
-    def _query_running(self, ifaceobjrunning):
         return
 
-    _run_ops = {'post-up' : _post_up,
-               'query-checkcurr' : _query_check,
-               'query-running' : _query_running }
+    _run_ops = {'pre-down' : _pre_down,
+                'post-up' : _post_up,
+                'query-checkcurr' : _query_check,
+                'query-running' : _query_running }
 
     def get_ops(self):
         """ returns list of ops supported by this module """
@@ -127,6 +226,14 @@ class ethtool(moduleBase):
         if not op_handler:
             return
         self._init_command_handlers()
+
+        # check to make sure we are only checking/setting interfaces with
+        # no lower interfaces.   No bridges, no vlans, loopbacks.
+        if ifaceobj.lowerifaces != None or \
+           self.ipcmd.link_isloopback(ifaceobj.name) or \
+           self.ipcmd.is_vlan_device_by_name(ifaceobj.name):
+            return
+
         if operation == 'query-checkcurr':
             op_handler(self, ifaceobj, query_ifaceobj)
         else:
index e9aa4879f0c64bc0343abbd0ed679a67ac0c3090..a96b75e4fb65517fb2e0ffb690e8a6febc6356ff 100644 (file)
@@ -14,6 +14,7 @@ post-up,ethtool
 post-up,usercmds
 post-up,clagd
 pre-down,usercmds
+pre-down,ethtool
 down,dhcp
 down,addressvirtual
 down,address
index 562110b5de21d672c5a859e5e0cc57b4078f43bf..41cd3676b9c7240f4579fd00d9eacd373a9b8b18 100644 (file)
@@ -1117,6 +1117,7 @@ class ifupdownMain(ifupdownBase):
             self.ALL = True
             self.WITH_DEPENDS = True
         if new_ifaceobjdict:
+            # and now, ifaceobjdict is back to current config
             self.ifaceobjdict = new_ifaceobjdict
             self.dependency_graph = new_dependency_graph
 
@@ -1231,6 +1232,7 @@ class ifupdownMain(ifupdownBase):
         if auto:
             self.ALL = True
             self.WITH_DEPENDS = True
+        # and now, we are back to the current config in ifaceobjdict
         self.ifaceobjdict = new_ifaceobjdict
         self.dependency_graph = new_dependency_graph
         ifacenames = self.ifaceobjdict.keys()
diff --git a/ifupdown/policymanager.py b/ifupdown/policymanager.py
new file mode 100644 (file)
index 0000000..2f9cbac
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+#
+# Copyright 2015 Cumulus Networks, Inc. All rights reserved.
+#
+#
+'''
+The PolicyManager should be subclassed by addon modules
+to read a JSON policy config file that is later used to
+set defaults:
+
+Initialize: This module defines a list of config file location based
+          on module.  There are defined in the __init__():  All the
+          addon modules need to do is import the policymanager module.
+
+          import ifupdown.policymanager as policymanager
+
+
+Provides: an API to retrieve link attributes based on addon module name,
+          interface name, and attribute.
+
+        The ifupdown.policymanager module provides a global object policymanager_api
+        that can be called like so:
+
+        speed_default = policymanager.policymanager_api.get_default(
+            module_name='ethtool',
+            ifname=ifaceobj.name,
+            attr='link-speed'
+            )
+'''
+
+import json
+import logging
+import glob
+
+class policymanager():
+    def __init__(self):
+        # we should check for these files in order
+        # so that customers can override the /var/lib file settings
+        self.logger = logging.getLogger('ifupdown.' +
+                            self.__class__.__name__)
+
+        # we grab the json files from a known location and make sure that
+        # the defaults_policy is checked first
+        user_files = glob.glob('/etc/network/ifupdown2/policy.d/*.json')
+        # grab the default module files
+        default_files = glob.glob('/var/lib/ifupdownaddons/policy.d/*.json')
+        # keep an array of defaults indexed by module name
+        self.system_policy_array = {}
+        for filename in default_files:
+            system_array  = {}
+            try:
+                fd = open(filename,'r')
+                system_array = json.load(fd)
+                self.logger.debug('reading %s system policy defaults config' \
+                                  % filename)
+            except Exception, e:
+                self.logger.debug('could not read %s system policy defaults config' \
+                                  % filename)
+                self.logger.debug('    exception is %s' % str(e))
+            for module in system_array.keys():
+                if self.system_policy_array.has_key(module):
+                    self.logger.debug('warning: overwriting system module %s from file %s' \
+                                      % (module,filename))
+                self.system_policy_array[module] = system_array[module]
+
+        # take care of user defined policy defaults
+        self.user_policy_array = {}
+        for filename in user_files:
+            user_array  = {}
+            try:
+                fd = open(filename,'r')
+                user_array = json.load(fd)
+                self.logger.debug('reading %s policy user defaults config' \
+                                  % filename)
+            except Exception, e:
+                self.logger.debug('could not read %s user policy defaults config' \
+                                  % filename)
+                self.logger.debug('    exception is %s' % str(e))
+            # customer added module attributes
+            for module in user_array.keys():
+                if self.system_policy_array.has_key(module):
+                    # warn user that we are overriding the system module setting
+                    self.logger.debug('warning: overwriting system with user module %s from file %s' \
+                                      % (module,filename))
+                self.user_policy_array[module] = user_array[module]
+        return
+
+    def get_iface_default(self,module_name=None,ifname=None,attr=None):
+        '''
+        get_iface_default: Addon modules must use one of two types of access methods to
+        the default configs.   In this method, we expect the default to be
+        either in
+            [module]['iface_defaults'][ifname][attr] or
+            [module]['defaults'][attr]
+        We first check the user_policy_array and return that value. But if
+        the user did not specify an override, we use the system_policy_array.
+        '''
+        # make sure we have an index
+        if (not ifname or not attr or not module_name):
+            return None
+
+        val = None
+        # users can specify defaults to override the systemwide settings
+        # look for user specific interface attribute iface_defaults first
+        try:
+            # looks for user specified value
+            val = self.user_policy_array[module_name]['iface_defaults'][ifname][attr]
+            return val
+        except:
+            pass
+        try:
+            # failing that, there may be a user default for all intefaces
+            val = self.user_policy_array[module_name]['defaults'][attr]
+            return val
+        except:
+            pass
+        try:
+            # failing that, look for  system setting for the interface
+            val = self.system_policy_array[module_name]['iface_defaults'][ifname][attr]
+            return val
+        except:
+            pass
+        try:
+            # failing that, look for  system setting for all interfaces
+            val = self.system_policy_array[module_name]['defaults'][attr]
+            return val
+        except:
+            pass
+
+        # could not find any system or user default so return Non
+        return val
+
+    def get_attr_default(self,module_name=None,attr=None):
+        '''
+        get_attr_default: Addon modules must use one of two types of access methods to
+        the default configs.   In this method, we expect the default to be in
+
+        [module][attr] 
+
+        We first check the user_policy_array and return that value. But if
+        the user did not specify an override, we use the system_policy_array.
+        '''
+        if (not attr or not module_name):
+            return None
+        # users can specify defaults to override the systemwide settings
+        # look for user specific interface attribute iface_defaults first
+        val = None
+        if self.user_policy_array.get(module_name):
+            val = self.user_policy_array[module_name].get(attr)
+
+        if not val:
+            if self.system_policy_array.get(module_name):
+                val = self.system_policy_array[module_name].get(attr)
+
+        return val
+
+    def get_module_default(self,module_name=None):
+        '''
+        get_module_default: Addon modules can also access the entire config
+        This method returns indexed by "system" and "user": these are the
+        system-wide and user-defined policy arrays for a specific module.
+        '''
+        if not module_name:
+            return None
+        if self.system_policy_array.get(module_name) and \
+           self.user_policy_array.get(module_name):
+            mod_array = {"system":self.system_policy_array[module_name],
+                         "user":self.user_policy_array[module_name]}
+        else:
+            # the module must not have these defined, return None
+            mod_array = None
+
+        return mod_array
+
+policymanager_api = policymanager()
index 58218e34bc792dfbb22bd45cdf25ac08231b4cec..015a550b90ebb829e8e069d1335573318f7872bb 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -40,6 +40,8 @@ setup(name='ifupdown2',
                       'addons/ethtool.py', 'addons/loopback.py',
                       'addons/addressvirtual.py', 'addons/vxlan.py',
                       'addons/bridgevlan.py']),
-                  ('/var/lib/ifupdownaddons/', ['config/addons.conf'])
+                  ('/var/lib/ifupdownaddons/', ['config/addons.conf']),
+                  ('/var/lib/ifupdownaddons/policy.d/', []),
+                  ('/etc/network/ifupdown2/policy.d/', [])
                   ]
       )