diff options
author | shadi@chromium.org <shadi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-14 05:40:52 +0000 |
---|---|---|
committer | shadi@chromium.org <shadi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-14 05:40:52 +0000 |
commit | 1e81311680250af51dcc56161231cd22f5debe9d (patch) | |
tree | 812ab67ba90f6204ef486fec0e6a6245d1f892f4 /media | |
parent | c43f3ed89b9518a8fdb8d525cd1d216123a0d179 (diff) | |
download | chromium_src-1e81311680250af51dcc56161231cd22f5debe9d.zip chromium_src-1e81311680250af51dcc56161231cd22f5debe9d.tar.gz chromium_src-1e81311680250af51dcc56161231cd22f5debe9d.tar.bz2 |
Constrained network scripts that configure a local network upload bandwidth,
latency, and packet loss on a specific port.
These scripts will be used by the constrained network server (Issue 8528049)
to serve files on a configured network for testing purposes.
BUG=104254
TEST=Ran locally. Ran unit tests (more unittests will be added)
Review URL: http://codereview.chromium.org/8566029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114355 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
4 files changed, 743 insertions, 0 deletions
diff --git a/media/tools/constrained_network_server/cn.py b/media/tools/constrained_network_server/cn.py new file mode 100755 index 0000000..c2fd877 --- /dev/null +++ b/media/tools/constrained_network_server/cn.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""A script for configuring constraint networks. + +Sets up a constrained network configuration on a specific port. Traffic on this +port will be redirected to another local server port. + +The configuration includes bandwidth, latency, and packet loss. +""" + +import collections +import logging +import optparse +import traffic_control + +# Default logging is ERROR. Use --verbose to enable DEBUG logging. +_DEFAULT_LOG_LEVEL = logging.ERROR + +Dispatcher = collections.namedtuple('Dispatcher', ['dispatch', 'requires_ports', + 'desc']) + +# Map of command names to traffic_control functions. +COMMANDS = { + # Adds a new constrained network configuration. + 'add': Dispatcher(traffic_control.CreateConstrainedPort, + requires_ports=True, desc='Add a new constrained port.'), + + # Deletes an existing constrained network configuration. + 'del': Dispatcher(traffic_control.DeleteConstrainedPort, + requires_ports=True, desc='Delete a constrained port.'), + + # Deletes all constrained network configurations. + 'teardown': Dispatcher(traffic_control.TearDown, + requires_ports=False, + desc='Teardown all constrained ports.') +} + + +def _ParseArgs(): + """Define and parse command-line arguments. + + Returns: + tuple as (command, configuration): + command: one of the possible commands to setup, delete or teardown the + constrained network. + configuration: a map of constrained network properties to their values. + """ + parser = optparse.OptionParser() + + indent_first = parser.formatter.indent_increment + opt_width = parser.formatter.help_position - indent_first + + cmd_usage = [] + for s in COMMANDS: + cmd_usage.append('%*s%-*s%s' % + (indent_first, '', opt_width, s, COMMANDS[s].desc)) + + parser.usage = ('usage: %%prog {%s} [options]\n\n%s' % + ('|'.join(COMMANDS.keys()), '\n'.join(cmd_usage))) + + parser.add_option('--port', type='int', + help='The port to apply traffic control constraints to.') + parser.add_option('--server-port', type='int', + help='Port to forward traffic on --port to.') + parser.add_option('--bandwidth', type='int', + help=('Bandwidth of the network in kbps. Default: ' + '%defaultkbps.')) + parser.add_option('--latency', type='int', + help=('Latency (delay) added to each outgoing packet in ' + 'ms. Default: %defaultms.')) + parser.add_option('--loss', type='int', + help=('Packet-loss percentage on outgoing packets. ' + 'Default: %default%.')) + parser.add_option('--interface', type='string', + help=('Interface to setup constraints on. Use lo for a ' + 'local client. Default: %default.')) + parser.add_option('-v', '--verbose', action='store_true', dest='verbose', + default=False, help='Turn on verbose output.') + options, args = parser.parse_args() + + _SetLogger(options.verbose) + + # Check a valid command was entered + if not args or args[0].lower() not in COMMANDS: + parser.error('Please specify a command {%s}.' % '|'.join(COMMANDS.keys())) + user_cmd = args[0].lower() + + # Check if required options are available + if COMMANDS[user_cmd].requires_ports: + if not (options.port and options.server_port): + parser.error('Please provide port and server-port values.') + + config = { + 'port': options.port, + 'server_port': options.server_port, + 'interface': options.interface, + 'latency': options.latency, + 'bandwidth': options.bandwidth, + 'loss': options.loss + } + return user_cmd, config + + +def _SetLogger(verbose): + log_level = _DEFAULT_LOG_LEVEL + if verbose: + log_level = logging.DEBUG + logging.basicConfig(level=log_level, format='%(message)s') + + +def Main(): + """Get the command and configuration of the network to set up.""" + user_cmd, config = _ParseArgs() + + try: + COMMANDS[user_cmd].dispatch(config) + except traffic_control.TrafficControlError as e: + logging.error('Error: %s\n\nOutput: %s', e.msg, e.error) + + +if __name__ == '__main__': + Main() diff --git a/media/tools/constrained_network_server/traffic_control.py b/media/tools/constrained_network_server/traffic_control.py new file mode 100755 index 0000000..d6527ff --- /dev/null +++ b/media/tools/constrained_network_server/traffic_control.py @@ -0,0 +1,336 @@ +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Traffic control library for constraining the network configuration on a port. + +The traffic controller sets up a constrained network configuration on a port. +Traffic to the constrained port is forwarded to a specified server port. +""" + +import logging +import re +import subprocess + +# The maximum bandwidth limit. +_DEFAULT_MAX_BANDWIDTH_KBPS = 1000000 + + +class TrafficControlError(BaseException): + """Exception raised for errors in traffic control library. + + Attributes: + msg: User defined error message. + cmd: Command for which the exception was raised. + returncode: Return code of running the command. + stdout: Output of running the command. + stderr: Error output of running the command. + """ + + def __init__(self, msg, cmd=None, returncode=None, output=None, + error=None): + BaseException.__init__(self, msg) + self.msg = msg + self.cmd = cmd + self.returncode = returncode + self.output = output + self.error = error + + +def CreateConstrainedPort(config): + """Creates a new constrained port. + + Imposes packet level constraints such as bandwidth, latency, and packet loss + on a given port using the specified configuration dictionary. Traffic to that + port is forwarded to a specified server port. + + Args: + config: Constraint configuration dictionary, format: + port: Port to constrain (integer 1-65535). + server_port: Port to redirect traffic on [port] to (integer 1-65535). + interface: Network interface name (string). + latency: Delay added on each packet sent (integer in ms). + bandwidth: Maximum allowed upload bandwidth (integer in kbps). + loss: Percentage of packets to drop (integer 0-100). + + Raises: + TrafficControlError: If any operation fails. The message in the exception + describes what failed. + """ + _CheckArgsExist(config, 'interface', 'port', 'server_port') + _AddRootQdisc(config['interface']) + + try: + _ConfigureClass('add', config) + _AddSubQdisc(config) + _AddFilter(config['interface'], config['port']) + _AddIptableRule(config['interface'], config['port'], config['server_port']) + except TrafficControlError as e: + logging.debug('Error creating constrained port %d.\nError: %s\n' + 'Deleting constrained port.', config['port'], e.error) + DeleteConstrainedPort(config) + raise e + + +def DeleteConstrainedPort(config): + """Deletes an existing constrained port. + + Deletes constraints set on a given port and the traffic forwarding rule from + the constrained port to a specified server port. + + The original constrained network configuration used to create the constrained + port must be passed in. + + Args: + config: Constraint configuration dictionary, format: + port: Port to constrain (integer 1-65535). + server_port: Port to redirect traffic on [port] to (integer 1-65535). + interface: Network interface name (string). + bandwidth: Maximum allowed upload bandwidth (integer in kbps). + + Raises: + TrafficControlError: If any operation fails. The message in the exception + describes what failed. + """ + _CheckArgsExist(config, 'interface', 'port', 'server_port') + try: + # Delete filters first so it frees the class. + _DeleteFilter(config['interface'], config['port']) + finally: + try: + # Deleting the class deletes attached qdisc as well. + _ConfigureClass('del', config) + finally: + _DeleteIptableRule(config['interface'], config['port'], + config['server_port']) + + +def TearDown(config): + """Deletes the root qdisc and all iptables rules. + + Args: + config: Constraint configuration dictionary, format: + interface: Network interface name (string). + + Raises: + TrafficControlError: If any operation fails. The message in the exception + describes what failed. + """ + _CheckArgsExist(config, 'interface') + + command = ['tc', 'qdisc', 'del', 'dev', config['interface'], 'root'] + try: + _Exec(command, msg='Could not delete root qdisc.') + finally: + _DeleteAllIpTableRules() + + +def _CheckArgsExist(config, *args): + """Check that the args exist in config dictionary and are not None. + + Args: + config: Any dictionary. + *args: The list of key names to check. + + Raises: + TrafficControlError: If any key name does not exist in config or is None. + """ + for key in args: + if key not in config.keys() or config[key] is None: + raise TrafficControlError('Missing "%s" parameter.' % key) + + +def _AddRootQdisc(interface): + """Sets up the default root qdisc. + + Args: + interface: Network interface name. + + Raises: + TrafficControlError: If adding the root qdisc fails for a reason other than + it already exists. + """ + command = ['tc', 'qdisc', 'add', 'dev', interface, 'root', 'handle', '1:', + 'htb'] + try: + _Exec(command, msg=('Error creating root qdisc. ' + 'Make sure you have root access')) + except TrafficControlError as e: + # Ignore the error if root already exists. + if not 'File exists' in e.error: + raise e + + +def _ConfigureClass(option, config): + """Adds or deletes a class and qdisc attached to the root. + + The class specifies bandwidth, and qdisc specifies delay and packet loss. The + class ID is based on the config port. + + Args: + option: Adds or deletes a class option [add|del]. + config: Constraint configuration dictionary, format: + port: Port to constrain (integer 1-65535). + interface: Network interface name (string). + bandwidth: Maximum allowed upload bandwidth (integer in kbps). + """ + # Use constrained port as class ID so we can attach the qdisc and filter to + # it, as well as delete the class, using only the port number. + class_id = '1:%x' % config['port'] + if 'bandwidth' not in config.keys() or not config['bandwidth']: + bandwidth = _DEFAULT_MAX_BANDWIDTH_KBPS + else: + bandwidth = config['bandwidth'] + + bandwidth = '%dkbps' % bandwidth + command = ['tc', 'class', option, 'dev', config['interface'], 'parent', '1:', + 'classid', class_id, 'htb', 'rate', bandwidth, 'ceil', bandwidth] + _Exec(command, msg=('Error configuring class ID %s using "%s" command.' % + (class_id, option))) + + +def _AddSubQdisc(config): + """Adds a qdisc attached to the class identified by the config port. + + Args: + config: Constraint configuration dictionary, format: + port: Port to constrain (integer 1-65535). + interface: Network interface name (string). + latency: Delay added on each packet sent (integer in ms). + loss: Percentage of packets to drop (integer 0-100). + """ + port_hex = '%x' % config['port'] + class_id = '1:%x' % config['port'] + command = ['tc', 'qdisc', 'add', 'dev', config['interface'], 'parent', + class_id, 'handle', port_hex + ':0', 'netem'] + + # Check if packet-loss is set in the configuration. + if 'loss' in config.keys() and config['loss']: + loss = '%d%%' % config['loss'] + command.extend(['loss', loss]) + # Check if latency is set in the configuration. + if 'latency' in config.keys() and config['latency']: + latency = '%dms' % config['latency'] + command.extend(['delay', latency]) + + _Exec(command, msg='Could not attach qdisc to class ID %s.' % class_id) + + +def _AddFilter(interface, port): + """Redirects packets coming to a specified port into the constrained class. + + Args: + interface: Interface name to attach the filter to (string). + port: Port number to filter packets with (integer 1-65535). + """ + class_id = '1:%x' % port + + command = ['tc', 'filter', 'add', 'dev', interface, 'protocol', 'ip', + 'parent', '1:', 'prio', '1', 'u32', 'match', 'ip', 'sport', port, + '0xffff', 'flowid', class_id] + _Exec(command, msg='Error adding filter on port %d.' % port) + + +def _DeleteFilter(interface, port): + """Deletes the filter attached to the configured port. + + Args: + interface: Interface name the filter is attached to (string). + port: Port number being filtered (integer 1-65535). + """ + handle_id = _GetFilterHandleId(interface, port) + command = ['tc', 'filter', 'del', 'dev', interface, 'protocol', 'ip', + 'parent', '1:0', 'handle', handle_id, 'prio', '1', 'u32'] + _Exec(command, msg='Error deleting filter on port %d.' % port) + + +def _GetFilterHandleId(interface, port): + """Searches for the handle ID of the filter identified by the config port. + + Args: + interface: Interface name the filter is attached to (string). + port: Port number being filtered (integer 1-65535). + + Returns: + The handle ID. + + Raises: + TrafficControlError: If handle ID was not found. + """ + command = ['tc', 'filter', 'list', 'dev', interface, 'parent', '1:'] + output = _Exec(command, msg='Error listing filters.') + + # Search for the filter handle ID associated with class ID '1:port'. + handle_id_re = re.search( + '([0-9a-fA-F]{3}::[0-9a-fA-F]{3}).*(?=flowid 1:%x$)' % port, output) + if handle_id_re: + return handle_id_re.group(1) + + raise TrafficControlError(('Could not find filter handle ID for class ID ' + '1:%x.') % port) + + +def _AddIptableRule(interface, port, server_port): + """Forwards traffic from constrained port to a specified server port. + + Args: + interface: Interface name to attach the filter to (string). + port: Port of incoming packets (integer 1-65535). + server_port: Server port to forward the packets to (integer 1-65535). + """ + # Preroute rules for accessing the port through external connections. + command = ['iptables', '-t', 'nat', '-A', 'PREROUTING', '-i', interface, '-p', + 'tcp', '--dport', port, '-j', 'REDIRECT', '--to-port', server_port] + _Exec(command, msg='Error adding iptables rule for port %d.' % port) + + # Output rules for accessing the rule through localhost or 127.0.0.1 + command = ['iptables', '-t', 'nat', '-A', 'OUTPUT', '-p', 'tcp', '--dport', + port, '-j', 'REDIRECT', '--to-port', server_port] + _Exec(command, msg='Error adding iptables rule for port %d.' % port) + + +def _DeleteIptableRule(interface, port, server_port): + """Deletes the iptable rule associated with specified port number. + + Args: + interface: Interface name to attach the filter to (string). + port: Port of incoming packets (integer 1-65535). + server_port: Server port packets are forwarded to (integer 1-65535). + """ + command = ['iptables', '-t', 'nat', '-D', 'PREROUTING', '-i', interface, '-p', + 'tcp', '--dport', port, '-j', 'REDIRECT', '--to-port', server_port] + _Exec(command, msg='Error deleting iptables rule for port %d.' % port) + + command = ['iptables', '-t', 'nat', '-D', 'OUTPUT', '-p', 'tcp', '--dport', + port, '-j', 'REDIRECT', '--to-port', server_port] + _Exec(command, msg='Error adding iptables rule for port %d.' % port) + + +def _DeleteAllIpTableRules(): + """Deletes all iptables rules.""" + command = ['iptables', '-t', 'nat', '-F'] + _Exec(command, msg='Error deleting all iptables rules.') + + +def _Exec(command, msg=None): + """Executes a command. + + Args: + command: Command list to execute. + msg: Message describing the error in case the command fails. + + Returns: + The standard output from running the command. + + Raises: + TrafficControlError: If command fails. Message is set by the msg parameter. + """ + cmd_list = [str(x) for x in command] + cmd = ' '.join(cmd_list) + logging.debug('Running command: %s', cmd) + + p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, error = p.communicate() + if p.returncode != 0: + raise TrafficControlError(msg, cmd, p.returncode, output, error) + return output.strip() diff --git a/media/tools/constrained_network_server/traffic_control_test.py b/media/tools/constrained_network_server/traffic_control_test.py new file mode 100644 index 0000000..583becb --- /dev/null +++ b/media/tools/constrained_network_server/traffic_control_test.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""End-to-end tests for traffic control library.""" +import os +import re +import sys +import unittest + +import traffic_control + + +class TrafficControlTests(unittest.TestCase): + """System tests for traffic_control functions. + + These tests require root access. + """ + # A dummy interface name to use instead of real interface. + _INTERFACE = 'myeth' + + def setUp(self): + """Setup a dummy interface.""" + # If we update to python version 2.7 or newer we can use setUpClass() or + # unittest.skipIf() + if os.getuid() != 0: + sys.exit('You need root access to run these tests.') + + command = ['ip', 'link', 'add', 'name', self._INTERFACE, 'type', 'dummy'] + traffic_control._Exec(command, 'Error creating dummy interface %s.' % + self._INTERFACE) + + def tearDown(self): + """Teardown the dummy interface and any network constraints on it.""" + # Deleting the dummy interface deletes all associated constraints. + command = ['ip', 'link', 'del', self._INTERFACE] + traffic_control._Exec(command) + + def testExecOutput(self): + output = traffic_control._Exec(['echo', ' Test ']) + self.assertEqual(output, 'Test') + + def testExecException(self): + self.assertRaises(traffic_control.TrafficControlError, + traffic_control._Exec, command=['ls', '!doesntExist!']) + + def testExecErrorCustomMsg(self): + try: + traffic_control._Exec(['ls', '!doesntExist!'], msg='test_msg') + self.fail('No exception raised for invalid command.') + except traffic_control.TrafficControlError as e: + self.assertEqual(e.msg, 'test_msg') + + def testAddRootQdisc(self): + """Checks adding a root qdisc is successful.""" + config = {'interface': self._INTERFACE} + root_detail = 'qdisc htb 1: root' + # Assert no htb root at startup. + command = ['tc', 'qdisc', 'ls', 'dev', config['interface']] + output = traffic_control._Exec(command) + self.assertFalse(root_detail in output) + + traffic_control._AddRootQdisc(config['interface']) + output = traffic_control._Exec(command) + # Assert htb root is added + self.assertTrue(root_detail in output) + + def testConfigureClassAdd(self): + """Checks adding and deleting a class to the root qdisc.""" + config = { + 'interface': self._INTERFACE, + 'port': 12345, + 'server_port': 33333, + 'bandwidth': 2000 + } + # Convert Kbps to Kbit + rate = config['bandwidth'] * 8 + class_detail = ('class htb 1:%x root prio 0 rate %dKbit ceil %dKbit' % + (config['port'], rate, rate)) + + # Add root qdisc. + traffic_control._AddRootQdisc(config['interface']) + + # Assert class does not exist prior to adding it. + command = ['tc', 'class', 'ls', 'dev', config['interface']] + output = traffic_control._Exec(command) + self.assertFalse(class_detail in output) + + # Add class to root. + traffic_control._ConfigureClass('add', config) + + # Assert class is added + command = ['tc', 'class', 'ls', 'dev', config['interface']] + output = traffic_control._Exec(command) + self.assertTrue(class_detail in output) + + # Delete class + traffic_control._ConfigureClass('del', config) + + # Assert class is deleted + command = ['tc', 'class', 'ls', 'dev', config['interface']] + output = traffic_control._Exec(command) + self.assertFalse(class_detail in output) + + def testAddSubQdisc(self): + """Checks adding a sub qdisc to existing class.""" + config = { + 'interface': self._INTERFACE, + 'port': 12345, + 'server_port': 33333, + 'bandwidth': 2000, + 'latency': 250, + 'loss': 5 + } + qdisc_re_detail = ('qdisc netem %x: parent 1:%x .* delay %d.0ms loss %d%%' % + (config['port'], config['port'], config['latency'], + config['loss'])) + # Add root qdisc. + traffic_control._AddRootQdisc(config['interface']) + + # Add class to root. + traffic_control._ConfigureClass('add', config) + + # Assert qdisc does not exist prior to adding it. + command = ['tc', 'qdisc', 'ls', 'dev', config['interface']] + output = traffic_control._Exec(command) + handle_id_re = re.search(qdisc_re_detail, output) + self.assertEqual(handle_id_re, None) + + # Add qdisc to class + traffic_control._AddSubQdisc(config) + + # Assert qdisc is added + command = ['tc', 'qdisc', 'ls', 'dev', config['interface']] + output = traffic_control._Exec(command) + handle_id_re = re.search(qdisc_re_detail, output) + self.assertNotEqual(handle_id_re, None) + + +if __name__ == '__main__': + unittest.main() diff --git a/media/tools/constrained_network_server/traffic_control_unittest.py b/media/tools/constrained_network_server/traffic_control_unittest.py new file mode 100644 index 0000000..29580b4 --- /dev/null +++ b/media/tools/constrained_network_server/traffic_control_unittest.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Tests for traffic control library.""" +import unittest + +import traffic_control + + +class TrafficControlUnitTests(unittest.TestCase): + """Unit tests for traffic control.""" + + # Stores commands called by the traffic control _Exec function. + commands = [] + + def _ExecMock(self, command, **kwargs): + """Mocks traffic_control._Exec and adds the command to commands list.""" + cmd_list = [str(x) for x in command] + self.commands.append(' '.join(cmd_list)) + return '' + + def setUp(self): + """Resets the commands list and set the _Exec mock function.""" + self.commands = [] + self._old_Exec = traffic_control._Exec + traffic_control._Exec = self._ExecMock + + def tearDown(self): + """Resets the _Exec mock function to the original.""" + traffic_control._Exec = self._old_Exec + + def testCreateConstrainedPort(self): + config = { + 'interface': 'fakeeth', + 'port': 12345, + 'server_port': 8888, + 'bandwidth': 256, + 'latency': 100, + 'loss': 2 + } + traffic_control.CreateConstrainedPort(config) + expected = [ + 'tc qdisc add dev fakeeth root handle 1: htb', + 'tc class add dev fakeeth parent 1: classid 1:3039 htb rate 256kbps ' + 'ceil 256kbps', + 'tc qdisc add dev fakeeth parent 1:3039 handle 3039:0 netem loss 2% ' + 'delay 100ms', + 'tc filter add dev fakeeth protocol ip parent 1: prio 1 u32 match ip ' + 'sport 12345 0xffff flowid 1:3039', + 'iptables -t nat -A PREROUTING -i fakeeth -p tcp --dport 12345 -j ' + 'REDIRECT --to-port 8888', + 'iptables -t nat -A OUTPUT -p tcp --dport 12345 -j REDIRECT --to-port ' + '8888' + ] + self.assertEqual(expected, self.commands) + + def testCreateConstrainedPortDefaults(self): + config = { + 'interface': 'fakeeth', + 'port': 12345, + 'server_port': 8888, + 'latency': None + } + traffic_control.CreateConstrainedPort(config) + expected = [ + 'tc qdisc add dev fakeeth root handle 1: htb', + 'tc class add dev fakeeth parent 1: classid 1:3039 htb rate %dkbps ' + 'ceil %dkbps' % (traffic_control._DEFAULT_MAX_BANDWIDTH_KBPS, + traffic_control._DEFAULT_MAX_BANDWIDTH_KBPS), + 'tc qdisc add dev fakeeth parent 1:3039 handle 3039:0 netem', + 'tc filter add dev fakeeth protocol ip parent 1: prio 1 u32 match ip ' + 'sport 12345 0xffff flowid 1:3039', + 'iptables -t nat -A PREROUTING -i fakeeth -p tcp --dport 12345 -j ' + 'REDIRECT --to-port 8888', + 'iptables -t nat -A OUTPUT -p tcp --dport 12345 -j REDIRECT --to-port ' + '8888' + ] + self.assertEqual(expected, self.commands) + + def testDeleteConstrainedPort(self): + config = { + 'interface': 'fakeeth', + 'port': 12345, + 'server_port': 8888, + 'bandwidth': 256, + } + _old_GetFilterHandleId = traffic_control._GetFilterHandleId + traffic_control._GetFilterHandleId = lambda interface, port: '800::800' + + try: + traffic_control.DeleteConstrainedPort(config) + expected = [ + 'tc filter del dev fakeeth protocol ip parent 1:0 handle 800::800 ' + 'prio 1 u32', + 'tc class del dev fakeeth parent 1: classid 1:3039 htb rate 256kbps ' + 'ceil 256kbps', + 'iptables -t nat -D PREROUTING -i fakeeth -p tcp --dport 12345 -j ' + 'REDIRECT --to-port 8888', + 'iptables -t nat -D OUTPUT -p tcp --dport 12345 -j REDIRECT ' + '--to-port 8888'] + self.assertEqual(expected, self.commands) + finally: + traffic_control._GetFilterHandleId = _old_GetFilterHandleId + + def testTearDown(self): + config = {'interface': 'fakeeth'} + + traffic_control.TearDown(config) + expected = [ + 'tc qdisc del dev fakeeth root', + 'iptables -t nat -F' + ] + self.assertEqual(expected, self.commands) + + def testGetFilterHandleID(self): + # Check seach for handle ID command. + self.assertRaises(traffic_control.TrafficControlError, + traffic_control._GetFilterHandleId, 'fakeeth', 1) + self.assertEquals(self.commands, ['tc filter list dev fakeeth parent 1:']) + + # Check with handle ID available. + traffic_control._Exec = (lambda command, msg: + 'filter parent 1: protocol ip'' pref 1 u32 fh 800::800 order 2048 key ht ' + '800 bkt 0 flowid 1:1') + output = traffic_control._GetFilterHandleId('fakeeth', 1) + self.assertEqual(output, '800::800') + + # Check with handle ID not available. + traffic_control._Exec = lambda command, msg: 'NO ID IN HERE' + self.assertRaises(traffic_control.TrafficControlError, + traffic_control._GetFilterHandleId, 'fakeeth', 1) + + +if __name__ == '__main__': + unittest.main() |