Skip to content

Commit

Permalink
Merge pull request #11 from mozilla-services/refactoring
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
tarekziade committed Nov 16, 2012
2 parents fc1b979 + a16afdd commit 0592c2c
Show file tree
Hide file tree
Showing 28 changed files with 756 additions and 559 deletions.
16 changes: 8 additions & 8 deletions docs/handlers_ext.py
@@ -1,25 +1,25 @@
import os

from vaurien.handlers import get_handlers
from vaurien.behaviors import get_behaviors


_HEADER = """\
.. _handlers:
.. _behaviors:
Handlers
========
Vaurien provides a collections of handlers.
Vaurien provides a collections of behaviors.
"""


def generate_handlers(app):
path = os.path.join(app.srcdir, 'handlers')
def generate_behaviors(app):
path = os.path.join(app.srcdir, 'behaviors')
ext = app.config['source_suffix']
filename = os.path.join(app.srcdir, "handlers%s" % ext)
items = get_handlers().items()
filename = os.path.join(app.srcdir, "behaviors%s" % ext)
items = get_behaviors().items()
items.sort()

with open(filename, "w") as doc:
Expand Down Expand Up @@ -61,4 +61,4 @@ def generate_handlers(app):


def setup(app):
app.connect('builder-inited', generate_handlers)
app.connect('builder-inited', generate_behaviors)
32 changes: 16 additions & 16 deletions docs/source/index.rst
Expand Up @@ -57,10 +57,10 @@ Let's say you want to add a delay for 20% of the requests done on google.com::


Vaurien will stream all the traffic to google.com but will add delays 20% of the
time. You can pass options to the handler using *--handler-NAME-OPTION* options::
time. You can pass options to the behavior using *--behavior-NAME-OPTION* options::

$ vaurien --proxy localhost:8000 --backend google.com:80 --behavior 20:delay \
--handler-delay-sleep 2
--behavior-delay-sleep 2

Passing all options through the command-line can be tedious, so you can
also create a *ini* file for this::
Expand All @@ -70,14 +70,14 @@ also create a *ini* file for this::
proxy = localhost:8000
behavior = 20:delay

[handler:delay]
[behavior:delay]
sleep = 2


Each behavior applied on the request or response going through Vaurien
is called a **handler**, and the ini file gets one section per handler.
is called a **behavior**, and the ini file gets one section per behavior.

You can find a descriptions of all built-in handlers here: :ref:`handlers`.
You can find a descriptions of all built-in behaviors here: :ref:`behaviors`.

You can also find some examples here: :ref:`examples`.

Expand Down Expand Up @@ -127,7 +127,7 @@ you can write::
client = Client()
options = {'inject': True}

with client.with_handler('error', **options):
with client.with_behavior('error', **options):
# do something...
pass

Expand All @@ -138,29 +138,29 @@ In this test, the proxy is started and stopped before and after the
test, and the Client class will let you drive its behavior.

Within the **with** block, the proxy will error out any call by using
the *errors* handler, so you can verify that your application is
the *errors* behavior, so you can verify that your application is
behaving as expected when it happens.


Extending Vaurien
=================

Vaurien comes with a handful of useful :ref:`handlers`, but you can create your own
handlers and plug them in a configuration file.
Vaurien comes with a handful of useful :ref:`behaviors`, but you can create your own
behaviors and plug them in a configuration file.

In fact that's the best way to create realistic issues. Imagine that you
have a very specific type of error on your LDAP server everytime your
infrastructure is under heavy load. You can reproduce this issue in your
handler and make sure your web application behaves as it should.
behavior and make sure your web application behaves as it should.

Creating new handlers is done by implementing a class with a specific signature.
Creating new behaviors is done by implementing a class with a specific signature.

You just have to write a class with a **__call__** method, and register it with
**Handler.register**::
**Behavior.register**::

from vaurien.handlers import Handler
from vaurien.behaviors import Behavior

class MySuperHandler(object):
class MySuperBehavior(object):

name = 'super'
options = {}
Expand All @@ -169,7 +169,7 @@ You just have to write a class with a **__call__** method, and register it with
# do something here
return True

Handler.register(MySuperHandler)
Behavior.register(MySuperBehavior)


More about this in :ref:`extending`.
Expand All @@ -192,7 +192,7 @@ Contents:
:maxdepth: 2

apis
handlers
behaviors
extending
keepalive
examples
Expand Down
86 changes: 86 additions & 0 deletions vaurien/behaviors/__init__.py
@@ -0,0 +1,86 @@
from abc import ABCMeta, abstractmethod


class Behavior(object):
"""Registry for behaviors.
A behavior is a class that implements two methods:
- on_before_handle
- on_after_handle
"""
__metaclass__ = ABCMeta
_cache = {}

@abstractmethod
def on_before_handle(self, protocol, source, dest, to_backend):
pass

@abstractmethod
def on_after_handle(self, protocol, source, dest, to_backend):
pass

@classmethod
def __subclasshook__(cls, klass):
if cls is Behavior:
for method in cls.__abstractmethods__:
if any(method in base.__dict__ for base in klass.__mro__):
continue
return NotImplemented
return True
return NotImplemented

@classmethod
def register(cls, subclass):
ABCMeta.register(cls, subclass)
if subclass not in cls._abc_registry:
cls._abc_registry.add(subclass)

@classmethod
def _get_instance(cls, klass):
name = klass.name
if name not in cls._cache:
cls._cache[name] = klass()
return cls._cache[name]

@classmethod
def get_behaviors(cls):
return dict([(klass.name, cls._get_instance(klass))
for klass in cls._abc_registry])

@classmethod
def get_behavior(cls, name):
for klass in cls._abc_registry:
if klass.name == name:
return cls._get_instance(klass)
raise KeyError(name)


def get_behaviors():
"""Return a mapping of all behaviors.
The keys are the behavior names and the values the classes.
"""
return Behavior.get_behaviors()


def get_behavior(name):
"""Returns an instance of the given behavior."""
return Behavior.get_behaviors(name)()


# manually register built-in behaviors
from vaurien.behaviors.dummy import Dummy
Behavior.register(Dummy)

from vaurien.behaviors.error import Error
Behavior.register(Error)

from vaurien.behaviors.blackout import Blackout
Behavior.register(Blackout)

from vaurien.behaviors.delay import Delay
Behavior.register(Delay)

from vaurien.behaviors.hang import Hang
Behavior.register(Hang)
16 changes: 16 additions & 0 deletions vaurien/behaviors/blackout.py
@@ -0,0 +1,16 @@
from vaurien.behaviors.dummy import Dummy


class Blackout(Dummy):
"""Reads the packets that have been sent then hangs.
Acts like a *pdb.set_trace()* you'd forgot in your code ;)
"""
name = 'blackout'
options = {}

def on_before_handle(self, protocol, source, dest, to_backend):
# consume the socket and hang
source.close()
source._closed = True
return False
16 changes: 12 additions & 4 deletions vaurien/handlers/delay.py → vaurien/behaviors/delay.py
@@ -1,9 +1,10 @@
import gevent
from vaurien.handlers.dummy import Dummy

from vaurien.behaviors.dummy import Dummy


class Delay(Dummy):
"""Adds a delay before the backend is called.
"""Adds a delay before or after the backend is called.
The delay can happen *after* or *before* the backend is called.
"""
Expand All @@ -14,10 +15,17 @@ class Delay(Dummy):
" after", bool, True)}
options.update(Dummy.options)

def on_before_handler(self):
def __init__(self):
self.settings = {}

def update_settings(self, settings):
self.settings.update(settings)

def on_before_handle(self, protocol, source, dest, to_backend):
if self.option('before'):
gevent.sleep(self.option('sleep'))
return True

def on_after_handler(self):
def on_after_handle(self, protocol, source, dest, to_backend):
if not self.option('before'):
gevent.sleep(self.option('sleep'))
33 changes: 33 additions & 0 deletions vaurien/behaviors/dummy.py
@@ -0,0 +1,33 @@
import re


class Dummy(object):
"""Transparent behavior. Nothing's done.
"""
name = 'dummy'
options = {}

def __init__(self):
self.settings = {}

def update_settings(self, settings):
self.settings.update(settings)

def _convert(self, value, type_):
if isinstance(value, type_):
return value
if type_ == bool:
value = value.lower()
return value in ('y', 'yes', '1', 'on')
return type_(value)

def option(self, name):
type_, default = self.options[name][1:3]
value = self.settings.get(name, default)
return self._convert(value, type_)

def on_before_handle(self, protocol, source, dest, to_backend):
return True

def on_after_handle(self, protocol, source, dest, to_backend):
return True
22 changes: 14 additions & 8 deletions vaurien/handlers/error.py → vaurien/behaviors/error.py
@@ -1,7 +1,9 @@
import os
import random

from vaurien.handlers.dummy import Dummy
from vaurien.behaviors.dummy import Dummy
from vaurien.util import get_data


_ERRORS = {
500: ("Internal Server Error",
Expand Down Expand Up @@ -61,24 +63,28 @@ class Error(Dummy):
'warmup': ("Number of calls before erroring out", int, 0)}
options.update(Dummy.options)

def __init__(self, settings=None, proxy=None):
super(Error, self).__init__(settings, proxy)
def __init__(self):
super(Error, self).__init__()
self.current = 0

def __call__(self, source, dest, to_backend):

def on_before_handle(self, protocol, source, dest, to_backend):
if self.current < self.option('warmup'):
self.current += 1
return super(Error, self).__call__(source, dest, to_backend)

data = self._get_data(source)
# read the data
data = get_data(source)
if not data:
return False

if self.option('http') and to_backend:
# error out
if protocol.name == 'http' and to_backend:
# we'll just send back a random error
source.sendall(random_http_error())
return
source.close()
source._closed = True

return False

if self.option('inject'):
if not to_backend: # back to the client
Expand Down
12 changes: 7 additions & 5 deletions vaurien/handlers/hang.py → vaurien/behaviors/hang.py
@@ -1,20 +1,22 @@
import gevent
from vaurien.handlers.base import BaseHandler

from vaurien.behaviors.dummy import Dummy
from vaurien.util import get_data

class Hang(BaseHandler):

class Hang(Dummy):
"""Reads the packets that have been sent then hangs.
Acts like a *pdb.set_trace()* you'd forgot in your code ;)
"""
name = 'hang'
options = {}

def ___call__(self, source, dest, to_backend):
def on_before_handle(self, protocol, source, dest, to_backend):
# consume the socket and hang
data = self._get_data(source, dest, to_backend)
data = get_data(source)
while data:
data = self._get_data(source, dest, to_backend)
data = get_data(source)

while True:
gevent.sleep(1.)

0 comments on commit 0592c2c

Please sign in to comment.