← Back to team overview

launchpad-reviewers team mailing list archive

[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:])