launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04481
[Merge] lp:~abentley/launchpad/induce-latency into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/induce-latency into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #821038 in Launchpad itself: "It should be easy to test Launchpad with high latency"
https://bugs.launchpad.net/launchpad/+bug/821038
For more details, see:
https://code.launchpad.net/~abentley/launchpad/induce-latency/+merge/70471
= Summary =
Add a script to induce latency on localhost:443
== Proposed fix ==
See above
== Pre-implementation notes ==
None
== Implementation details ==
I adapted the underlying commands from http://doc.bazaar.canonical.com/bzr.dev/developers/testing.html#simulating-slow-networks
I went for the port-specific version because Postgres also uses tcp on localhost, and I didn't want to introduce latency there.
== Tests ==
None
== Demo and Q/A ==
None
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
utilities/local-latency
utilities/script_commands.py
--
https://code.launchpad.net/~abentley/launchpad/induce-latency/+merge/70471
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/induce-latency into lp:launchpad.
=== added file 'utilities/local-latency'
--- utilities/local-latency 1970-01-01 00:00:00 +0000
+++ utilities/local-latency 2011-08-04 17:39:52 +0000
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+__metaclass__ = type
+
+import sys
+import subprocess
+
+from script_commands import (
+ Command,
+ OptionParser,
+ UserError,
+ )
+
+
+def tc(command):
+ subprocess.call('sudo tc ' + command, shell=True)
+
+
+class StartCommand(Command):
+
+ @classmethod
+ def get_parser(cls):
+ parser = OptionParser()
+ parser.add_option(
+ '-d', '--delay', dest='delay', type='int',
+ help='Length of delay in miliseconds (each way).')
+ return parser
+
+ @staticmethod
+ def run(delay=500, port=443):
+ tc('qdisc add dev lo root handle 1: prio')
+ tc('qdisc add dev lo parent 1:3 handle 30: netem delay %dms' % delay)
+ tc('filter add dev lo protocol ip parent 1:0 prio 3 u32 match ip'
+ ' dport %d 0xffff flowid 1:3' % port)
+ tc('filter add dev lo protocol ip parent 1:0 prio 3 u32 match ip'
+ ' sport %d 0xffff flowid 1:3' % port)
+
+
+Command.commands['start'] = StartCommand
+
+
+class StopCommand(Command):
+
+ @staticmethod
+ def get_parser():
+ parser = OptionParser()
+ return parser
+
+ @staticmethod
+ def run():
+ tc('qdisc del dev lo root')
+
+
+Command.commands['stop'] = StopCommand
+
+
+
+if __name__ == "__main__":
+ try:
+ Command.run_subcommand(sys.argv[1:])
+ except UserError as e:
+ sys.stderr.write(str(e)+'\n')
=== added file 'utilities/script_commands.py'
--- utilities/script_commands.py 1970-01-01 00:00:00 +0000
+++ utilities/script_commands.py 2011-08-04 17:39:52 +0000
@@ -0,0 +1,56 @@
+import optparse
+
+
+class UserError(Exception):
+ pass
+
+
+class OptionParser(optparse.OptionParser):
+
+ UNSPECIFIED = object()
+
+ def add_option(self, *args, **kwargs):
+ """Add an option, with the default that it not be included."""
+ kwargs['default'] = kwargs.get('default', self.UNSPECIFIED)
+ optparse.OptionParser.add_option(self, *args, **kwargs)
+
+ def parse_args_dict(self, cmd_args):
+ """Return a dict of specified options.
+
+ Unspecified options with no explicit default are not included in the
+ dict."""
+ options, args = self.parse_args(cmd_args)
+ option_dict = dict(
+ item for item in options.__dict__.items()
+ if item[1] is not self.UNSPECIFIED)
+ return args, option_dict
+
+
+class Command:
+ """Base class for subcommands."""
+
+ commands = {}
+
+ @classmethod
+ def parse_args(cls, args):
+ if len(args) != 0:
+ raise UserError('Too many arguments.')
+ return {}
+
+ @classmethod
+ def run_from_args(cls, cmd_args):
+ args, kwargs = cls.get_parser().parse_args_dict(cmd_args)
+ kwargs.update(cls.parse_args(args))
+ cls.run(**kwargs)
+
+ @classmethod
+ def run_subcommand(cls, argv):
+ if len(argv) < 1:
+ raise UserError('Must supply a command: %s.' %
+ ', '.join(cls.commands.keys()))
+ try:
+ command = cls.commands[argv[0]]
+ except KeyError:
+ raise UserError('%s invalid. Valid commands: %s.' %
+ (argv[0], ', '.join(cls.commands.keys())))
+ command.run_from_args(argv[1:])