diff --git a/bindep.txt b/bindep.txt new file mode 100644 index 00000000..111bf65b --- /dev/null +++ b/bindep.txt @@ -0,0 +1,5 @@ +# This is a cross-platform list tracking distribution packages needed for +# install and tests; +# see https://docs.openstack.org/infra/bindep/ for additional information. + +ethtool diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index 5e5915ba..235d5396 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -261,7 +261,8 @@ class IfcfgNetConfig(os_net_config.NetConfig): "IPADDR", "NETMASK", "MTU", - "ONBOOT" + "ONBOOT", + "ETHTOOL_OPTS" ] # Check whether any of the changes require restart for change in self.enumerate_ifcfg_changes(file_values, new_values): @@ -331,6 +332,46 @@ class IfcfgNetConfig(os_net_config.NetConfig): commands.append("link set dev %s mtu 1500" % device_name) return commands + def ethtool_apply_command(self, device_name, filename, data): + """Return list of commands needed to implement changes. + + Given ifcfg data for an interface, return commands required to + apply the configuration using 'ethtool' commands. + + :param device_name: The name of the int, bridge, or bond + :type device_name: string + :param filename: The ifcfg- filename. + :type filename: string + :param data: The data for the new ifcfg- file. + :type data: string + :returns: commands (commands to be run) + """ + + previous_cfg = common.get_file_data(filename) + file_values = self.parse_ifcfg(previous_cfg) + data_values = self.parse_ifcfg(data) + logger.debug("File values:\n%s" % file_values) + logger.debug("Data values:\n%s" % data_values) + changes = self.enumerate_ifcfg_changes(file_values, data_values) + commands = [] + + if "ETHTOOL_OPTS" in changes: + if changes["ETHTOOL_OPTS"] == "added" or \ + changes["ETHTOOL_OPTS"] == "modified": + for command_opts in data_values["ETHTOOL_OPTS"].split(';'): + if re.match(r'\s*-+\w+-*\w* ', command_opts): + if device_name or "${DEVICE}" or "$DEVICE" \ + in command_opts: + commands.append("%s" % command_opts) + else: + msg = ("Assigned interface name to \ + ETHTOOL_OPTS is invalid %s" % device_name) + raise utils.InvalidInterfaceException(msg) + else: + commands.append("-s %s %s" % + (device_name, command_opts)) + return commands + def iproute2_route_commands(self, filename, data): """Return a list of commands for 'ip route' to modify routing table. @@ -1311,6 +1352,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): vpp_interfaces = self.vpp_interface_data.values() vpp_bonds = self.vpp_bond_data.values() ipcmd = utils.iproute2_path() + ethtoolcmd = utils.ethtool_path() for interface_name, iface_data in self.interface_data.items(): route_data = self.route_data.get(interface_name, '') @@ -1755,6 +1797,27 @@ class IfcfgNetConfig(os_net_config.NetConfig): self.child_members(interface[0])) break + commands = self.ethtool_apply_command(interface[0], + interface[1], + interface[2]) + if commands is not None: + for command in commands: + try: + args = command.split() + args = [interface[0] + if item in ["${DEVICE}", "$DEVICE"] + else item for item in args] + self.execute('Running ethtool %s' % command, + ethtoolcmd, *args) + except Exception as e: + logger.warning("Error in 'ethtool %s', restarting %s:\ + \n%s)" % + (command, interface[0], str(e))) + restart_interfaces.append(interface[0]) + restart_interfaces.extend( + self.child_members(interface[0])) + break + for bridge in apply_bridges: logger.debug('Running ip commands on bridge: %s' % bridge[0]) diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index 6c861ab8..1a568f03 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -104,6 +104,7 @@ BOOTPROTO="dhcp" ONBOOT="yes" TYPE="Ethernet" """ + _IFCFG_STATIC1 = """# This file is autogenerated by os-net-config DEVICE=eth0 BOOTPROTO=static @@ -115,6 +116,31 @@ ONBOOT=yes _IFCFG_STATIC1_MTU = _IFCFG_STATIC1 + "\nMTU=9000" +_IFCFG_STATIC1_ETHTOOL1 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"speed 100 duplex full autoneg off\"" + +_IFCFG_STATIC1_ETHTOOL2 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"-G ${DEVICE} rx 1024 tx 1024\"" + +_IFCFG_STATIC1_ETHTOOL3 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"--set-ring ${DEVICE} rx 1024 tx 1024\"" + +_IFCFG_STATIC1_ETHTOOL4 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"--pause ${DEVICE} autoneg on\"" + +_IFCFG_STATIC1_ETHTOOL5 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"-G ${DEVICE} rx 1024 tx 1024;\ +-A ${DEVICE} autoneg on;\ +--pause ${DEVICE} autoneg off;\ +--set-ring ${DEVICE} rx 512\"" + +_IFCFG_STATIC1_ETHTOOL6 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"-G $DEVICE rx 1024 tx 1024\"" + +_IFCFG_STATIC1_ETHTOOL7 = _IFCFG_STATIC1 +\ + "\nETHTOOL_OPTS=\"-G eth0 rx 1024 tx 1024\"" + + _IFCFG_STATIC2 = """DEVICE=eth0 BOOTPROTO=static IPADDR=10.0.1.2 @@ -2539,6 +2565,8 @@ class TestIfcfgNetConfigApply(base.TestCase): _IFCFG_ROUTES2) self.assertTrue(commands == command_list1) + shutil.rmtree(tmpdir) + def test_ifcfg_rule_commands(self): tmpdir = tempfile.mkdtemp() @@ -2557,6 +2585,8 @@ class TestIfcfgNetConfigApply(base.TestCase): _IFCFG_RULES2) self.assertTrue(commands == command_list1) + shutil.rmtree(tmpdir) + def test_ifcfg_ipmtu_commands(self): tmpdir = tempfile.mkdtemp() @@ -2590,6 +2620,64 @@ class TestIfcfgNetConfigApply(base.TestCase): _IFCFG_STATIC2_MTU) self.assertTrue(commands == command_list3) + shutil.rmtree(tmpdir) + + def test_ifcfg_ethtool_commands(self): + + tmpdir = tempfile.mkdtemp() + interface = "eth0" + interface_filename = tmpdir + '/ifcfg-' + interface + file = open(interface_filename, 'w') + file.write(_IFCFG_STATIC1) + file.close() + + command_list1 = ['-s eth0 speed 100 duplex full autoneg off'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL1) + self.assertTrue(command == command_list1) + + command_list2 = ['-G ${DEVICE} rx 1024 tx 1024'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL2) + self.assertTrue(command == command_list2) + + command_list3 = ['--set-ring ${DEVICE} rx 1024 tx 1024'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL3) + self.assertTrue(command == command_list3) + + command_list4 = ['--pause ${DEVICE} autoneg on'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL4) + self.assertTrue(command == command_list4) + + command_list5 = ['-G ${DEVICE} rx 1024 tx 1024', + '-A ${DEVICE} autoneg on', + '--pause ${DEVICE} autoneg off', + '--set-ring ${DEVICE} rx 512'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL5) + self.assertTrue(command == command_list5) + + command_list6 = ['-G $DEVICE rx 1024 tx 1024'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL6) + self.assertTrue(command == command_list6) + + command_list7 = ['-G eth0 rx 1024 tx 1024'] + command = self.provider.ethtool_apply_command(interface, + interface_filename, + _IFCFG_STATIC1_ETHTOOL7) + self.assertTrue(command == command_list7) + + shutil.rmtree(tmpdir) + def test_restart_children_on_change(self): # setup and apply a bridge interface = objects.Interface('em1') diff --git a/os_net_config/utils.py b/os_net_config/utils.py index 33665a8d..0e4ef101 100644 --- a/os_net_config/utils.py +++ b/os_net_config/utils.py @@ -851,3 +851,15 @@ def iproute2_path(): logger.warning("Could not execute /sbin/ip or /usr/sbin/ip") return False return ipcmd + + +def ethtool_path(): + """Find 'ethtool' executable.""" + if os.access('/sbin/ethtool', os.X_OK): + ethtoolcmd = '/sbin/ethtool' + elif os.access('/usr/sbin/ethtool', os.X_OK): + ethtoolcmd = '/usr/sbin/ethtool' + else: + logger.warning("Could not execute /sbin/ethtool or /usr/sbin/ethtool") + return False + return ethtoolcmd