def test_socket_manager_raises_exception_when_retrieving_socket_with_bad_name():
    ("SocketManager().get_by_name() should raise AgentZeroSocketError when "
     "a given socket does not exist")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a socket manager
    manager = SocketManager(zmq, context)

    # And a couple of sockets
    manager.create('foo', zmq.REQ)
    manager.create('bar', zmq.PUB)

    # When I call get_by_name with an unexpected name
    when_called = manager.get_by_name.when.called_with('boom')

    # Then it should have
    when_called.should.have.raised(
        SocketNotFound,
        "SocketManager(sockets=['foo', 'bar']) has no sockets named 'boom'."
    )
def test_socket_manager_raises_exception_when_retrieving_socket_with_bad_name(
):
    ("SocketManager().get_by_name() should raise AgentZeroSocketError when "
     "a given socket does not exist")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a socket manager
    manager = SocketManager(zmq, context)

    # And a couple of sockets
    manager.create('foo', zmq.REQ)
    manager.create('bar', zmq.PUB)

    # When I call get_by_name with an unexpected name
    when_called = manager.get_by_name.when.called_with('boom')

    # Then it should have
    when_called.should.have.raised(
        SocketNotFound,
        "SocketManager(sockets=['foo', 'bar']) has no sockets named 'boom'.")
def test_socket_manager_raises_exception_when_creating_existing_socket():
    ("SocketManager().create() should raise AgentZeroSocketError when "
     "a given socket already exists")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a socket manager
    manager = SocketManager(zmq, context)

    # And a couple of sockets
    manager.create('foo', zmq.REQ)
    manager.create('bar', zmq.PUB)

    # When I call create on an existing socket name
    when_called = manager.create.when.called_with('foo', zmq.PUB)

    # Then it should have
    when_called.should.have.raised(
        SocketAlreadyExists,
        "SocketManager(sockets=['foo', 'bar']) already has a socket named 'foo'"
    )
def test_socket_manager_raises_exception_when_creating_existing_socket():
    ("SocketManager().create() should raise AgentZeroSocketError when "
     "a given socket already exists")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a socket manager
    manager = SocketManager(zmq, context)

    # And a couple of sockets
    manager.create('foo', zmq.REQ)
    manager.create('bar', zmq.PUB)

    # When I call create on an existing socket name
    when_called = manager.create.when.called_with('foo', zmq.PUB)

    # Then it should have
    when_called.should.have.raised(
        SocketAlreadyExists,
        "SocketManager(sockets=['foo', 'bar']) already has a socket named 'foo'"
    )
Example #5
0
class GraphClient(object):
    def __init__(self, request_connect_addr='tcp://127.0.0.1:6000'):
        self.context = zmq.Context()
        self.sockets = SocketManager(zmq, self.context)
        self.sockets.create('query', zmq.REQ)

    def request(self, data):
        self.sockets.send_safe('query', data)
        return self.sockets.recv_safe('query')
class Pipeline(object):
    steps = []

    def __init__(self):
        self.context = zmq.Context()
        self.sockets = SocketManager(zmq, self.context)
        self.sockets.create("pipe-sub", zmq.SUB)
        self.sockets.create("pipe-in", zmq.PULL)
        self.sockets.create("pipe-out", zmq.PUSH)
        self.children = []
Example #7
0
def test_socket_manager_send_safe_not_ready():
    ("SocketManager.send_safe should return False when the socket is not ready")

    # Given a manager
    manager = SocketManager(zmq, context)

    # And a couple of sockets
    manager.create('foo', zmq.REP)

    # When I call .send_safe()
    result = manager.send_safe('foo', {'some': 'value'})

    # Then it should be false
    result.should.be.false
def test_socket_manager_recv_safe(wait_until_ready):
    ("SocketManager().recv_safe() should deserialize "
     "after receiving, using the configured backend")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I call .recv_safe()
    result = manager.recv_safe('foobar')

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_called_once_with(socket.recv.return_value)

    # And the result should be the unpacked value
    result.should.equal(serializer.unpack.return_value)
def test_socket_manager_publish_safe():
    ("SocketManager().publish_safe() should serialize "
     "before sending, using the configured backend")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I call .publish_safe()
    manager.publish_safe('foobar', 'topic', 'PAYLOAD')

    # Then it should have packed the payload before sending
    serializer.pack.assert_called_once_with('PAYLOAD')
    packed = serializer.pack.return_value

    socket.send_multipart.assert_called_once_with(['topic', packed])
def test_socket_manager_subscribe_invalid_callable(get_by_name):
    (
        "SocketManager().subscribe() should raise TypeError "
        "if the keep_polling callable is not callable"
    )

    # Background: wait_until_ready is mocked to return the socket
    socket = get_by_name.return_value
    socket.recv_multipart.return_value = ["metrics:whatevs", "the-data"]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name="serializer")

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create("foobar", zmq.REP)

    # When I perform one iteration in the subscriber
    when_called = list.when.called_with(
        manager.subscribe("foobar", keep_polling="not a callable")
    )
    when_called.should.have.raised(
        TypeError,
        "SocketManager.subscribe parameter keep_polling must be a function or callable that returns a boolean",
    )
def test_socket_manager_publish_safe():
    (
        "SocketManager().publish_safe() should serialize "
        "before sending, using the configured backend"
    )

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name="serializer")
    serializer.pack.side_effect = lambda x: "<pac({})ked>".format(repr(x))
    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create("foobar", zmq.REP)

    # When I call .publish_safe()
    manager.publish_safe("foobar", "topic", "PAYLOAD")

    # Then it should have packed the payload before sending
    serializer.pack.assert_called_once_with("PAYLOAD")
    serializer.pack.return_value = "packed"

    socket.send_multipart.assert_called_once_with(
        [cast_bytes("topic"), cast_bytes("<pac('PAYLOAD')ked>")]
    )
def test_socket_manager_recv_event_safe_no_topic(wait_until_ready, set_topic):
    ("SocketManager().recv_event_safe() should raise an exeption when the topic is not a string"
     )

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)
    socket.recv_multipart.return_value = b'a fake topic', 'the-raw-payload'

    # When I call .recv_event_safe()
    when_called = manager.recv_event_safe.when.called_with('foobar',
                                                           topic={'boom'})

    # Then it should have raised and exception
    when_called.should.have.raised(
        TypeError, "recv_event_safe() takes a string, None "
        "or False as argument, received "
        "set(['boom'])(<type 'set'>) instead")
def test_socket_manager_recv_event_safe_missing_socket(wait_until_ready,
                                                       set_topic):
    ("SocketManager().recv_event_safe() should return None when the socket is not ready"
     )

    # Background: wait_until_ready is mocked to return None
    wait_until_ready.return_value = None

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)
    socket.recv_multipart.return_value = b'a fake topic', 'the-raw-payload'

    # When I call .recv_event_safe()
    result = manager.recv_event_safe('foobar', topic=b'helloworld')

    # And the result should be None
    result.should.be.none
def test_socket_manager_recv_event_safe(wait_until_ready, set_topic):
    ("SocketManager().recv_event_safe() should set the topic "
     "on the given socket, wait for data to become available "
     "and return it")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)
    socket.recv_multipart.return_value = b'a fake topic', 'the-raw-payload'

    # When I call .recv_event_safe()
    result = manager.recv_event_safe('foobar', topic=b'helloworld')

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_called_once_with('the-raw-payload')

    # And the result should be the unpacked value
    result.should.be.an(Event)
def test_socket_manager_send_safe(wait_until_ready):
    ("SocketManager().send_safe() should serialize "
     "before sending, using the configured backend")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I call .send_safe()
    sent = manager.send_safe('foobar', 'PAYLOAD')

    # Then it should have sent successfully
    sent.should.be.true

    # And it should have packed the payload before sending
    serializer.pack.assert_called_once_with('PAYLOAD')
    packed = serializer.pack.return_value

    socket.send.assert_called_once_with(packed)
def test_socket_manager_subscribe(get_by_name):
    ("SocketManager().subscribe() should yield the topic and ")

    # Background: wait_until_ready is mocked to return the socket
    socket = get_by_name.return_value
    socket.recv_multipart.return_value = ['metrics:whatevs', 'the-data']

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I perform one iteration in the subscriber
    topic, payload = next(manager.subscribe('foobar'))

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_called_once_with('the-data')

    # And the result should be the unpacked value
    payload.should.equal(serializer.unpack.return_value)

    # And the topic should be the expected
    topic.should.equal('metrics:whatevs')
def test_socket_manager_recv_event_safe_no_topic(wait_until_ready, set_topic):
    ("SocketManager().recv_event_safe() should raise an exeption when the topic is not a string")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)
    socket.recv_multipart.return_value = b'a fake topic', 'the-raw-payload'

    # When I call .recv_event_safe()
    when_called = manager.recv_event_safe.when.called_with('foobar', topic={'boom'})

    # Then it should have raised and exception
    when_called.should.have.raised(
        TypeError,
        "recv_event_safe() takes a string, None "
        "or False as argument, received "
        "set(['boom'])(<type 'set'>) instead"
    )
def test_socket_manager_recv_event_safe(wait_until_ready, set_topic):
    ("SocketManager().recv_event_safe() should set the topic "
     "on the given socket, wait for data to become available "
     "and return it")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)
    socket.recv_multipart.return_value = b'a fake topic', 'the-raw-payload'

    # When I call .recv_event_safe()
    result = manager.recv_event_safe('foobar', topic=b'helloworld')

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_called_once_with('the-raw-payload')

    # And the result should be the unpacked value
    result.should.be.an(Event)
def test_socket_manager_recv_event_safe_missing_socket(wait_until_ready, set_topic):
    ("SocketManager().recv_event_safe() should return None when the socket is not ready")

    # Background: wait_until_ready is mocked to return None
    wait_until_ready.return_value = None

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)
    socket.recv_multipart.return_value = b'a fake topic', 'the-raw-payload'

    # When I call .recv_event_safe()
    result = manager.recv_event_safe('foobar', topic=b'helloworld')

    # And the result should be None
    result.should.be.none
def test_socket_manager_subscribe(get_by_name):
    ("SocketManager().subscribe() should yield the topic and ")

    # Background: wait_until_ready is mocked to return the socket
    socket = get_by_name.return_value
    socket.recv_multipart.return_value = ['metrics:whatevs', 'the-data']

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I perform one iteration in the subscriber
    topic, payload = next(manager.subscribe('foobar'))

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_called_once_with('the-data')

    # And the result should be the unpacked value
    payload.should.equal(serializer.unpack.return_value)

    # And the topic should be the expected
    topic.should.equal('metrics:whatevs')
def test_socket_manager_publish_safe():
    ("SocketManager().publish_safe() should serialize "
     "before sending, using the configured backend")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I call .publish_safe()
    manager.publish_safe('foobar', 'topic', 'PAYLOAD')

    # Then it should have packed the payload before sending
    serializer.pack.assert_called_once_with('PAYLOAD')
    packed = serializer.pack.return_value

    socket.send_multipart.assert_called_once_with(['topic', packed])
def test_socket_manager_recv_safe(wait_until_ready):
    ("SocketManager().recv_safe() should deserialize "
     "after receiving, using the configured backend")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I call .recv_safe()
    result = manager.recv_safe('foobar')

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_called_once_with(socket.recv.return_value)

    # And the result should be the unpacked value
    result.should.equal(serializer.unpack.return_value)
def test_socket_manager_send_safe(wait_until_ready):
    ("SocketManager().send_safe() should serialize "
     "before sending, using the configured backend")

    # Background: wait_until_ready is mocked to return the socket
    wait_until_ready.side_effect = lambda name, *args: manager.sockets[name]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name='serializer')

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create('foobar', zmq.REP)

    # When I call .send_safe()
    sent = manager.send_safe('foobar', 'PAYLOAD')

    # Then it should have sent successfully
    sent.should.be.true

    # And it should have packed the payload before sending
    serializer.pack.assert_called_once_with('PAYLOAD')
    packed = serializer.pack.return_value

    socket.send.assert_called_once_with(packed)
Example #24
0
def test_socket_manager_subscribe():
    ("SocketManager.subscribe should subscribe from a topic")

    # Given a manager
    manager = SocketManager(zmq, context)

    # And a couple of sockets
    manager.ensure_and_bind('foo', zmq.PUB, 'inproc://test.publisher.2', zmq.POLLOUT)
    manager.create('bar', zmq.SUB)
    manager.connect('bar', 'inproc://test.publisher.2', zmq.POLLIN)

    gevent.spawn(manager.publish_safe, 'foo', 'some-topic', {'some': 'value'})

    # Then it should have received
    topic, result = next(manager.subscribe('bar'))
    topic.should.equal('some-topic')
    result.should.equal({'some': 'value'})
def test_socket_create():
    ("SocketManager().create() should create a socket")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a socket manager
    manager = SocketManager(zmq, context)

    # When I create a socket
    manager.create('foobar', 'SOME_SOCKET_TYPE')

    # Then it should have been added to the socket pool
    manager.sockets.should.have.key("foobar")
    manager.sockets["foobar"].should.equal(context.socket.return_value)

    # And the socket should have been created with the given type
    context.socket.assert_called_once_with('SOME_SOCKET_TYPE')
def test_socket_create():
    ("SocketManager().create() should create a socket")

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a socket manager
    manager = SocketManager(zmq, context)

    # When I create a socket
    manager.create('foobar', 'SOME_SOCKET_TYPE')

    # Then it should have been added to the socket pool
    manager.sockets.should.have.key("foobar")
    manager.sockets["foobar"].should.equal(context.socket.return_value)

    # And the socket should have been created with the given type
    context.socket.assert_called_once_with('SOME_SOCKET_TYPE')
def test_socket_manager_subscribe(get_by_name):
    ("SocketManager().subscribe() should yield the topic and yield an Event")

    # Background: wait_until_ready is mocked to return the socket
    socket = Mock(name='<socket(name="foobar")>')
    get_by_name.side_effect = [socket, socket, socket, socket, socket, None]

    socket.recv_multipart.side_effect = [
        ["metrics:whatevs", "the-data"],
        ["action", "test"],
    ]

    # Given a zmq mock
    zmq = Mock()

    # And a context
    context = Mock()

    # And a serializer
    serializer = Mock(name="serializer")

    # And a socket manager
    manager = SocketManager(zmq, context, serialization_backend=serializer)

    # And a socket
    socket = manager.create("foobar", zmq.REP)

    # When I perform one iteration in the subscriber
    events = list(manager.subscribe("foobar"))
    events.should.have.length_of(2)
    event1, event2 = events
    topic1, payload1 = event1.topic, event1.data

    # Then it should have unpacked the payload after receiving
    serializer.unpack.assert_has_calls([call("the-data"), call("test")])

    # And the result should be the unpacked value
    payload1.should.equal(serializer.unpack.return_value)

    # And the topic should be the expected
    topic1.should.equal("metrics:whatevs")

    event2.should.be.an(Event)
Example #28
0
class Step(object):
    def __init__(self, pull_bind_address='tcp://127.0.0.1', subscriber_connect_address='tcp://127.0.0.1:6000', concurrency=100, timeout=1):
        self.context = zmq.Context()
        self.sockets = SocketManager(zmq, self.context)
        self.sockets.create('pull-in', zmq.PULL)
        # self.sockets.set_socket_option('pull-in', zmq.RCVHWM, concurrency)
        self.sockets.create('events', zmq.PUB)
        self.name = self.__class__.__name__
        self.subscriber_connect_address = subscriber_connect_address
        self._allowed_to_run = True
        self.pool = gevent.pool.Pool(concurrency + 1)
        self.timeout = timeout
        self.pull_bind_address = pull_bind_address
        self.id = str(uuid.uuid4())
        self.logger = self.sockets.get_logger('events', 'logs', 'logs')

    def listen(self):
        _, self.address = self.sockets.bind_to_random_port('pull-in', zmq.POLLIN, local_address=self.pull_bind_address)
        gevent.sleep(1)

    def connect(self):
        self.sockets.connect('events', self.subscriber_connect_address, zmq.POLLOUT)
        gevent.sleep(1)
        self.notify_available()

    def notify_available(self):
        self.send_event('available', self.to_dict())

    def should_run(self):
        gevent.sleep(0.1)
        return self._allowed_to_run

    def send_event(self, name, data):
        self.sockets.publish_safe('events', name, data)

    def dispatch(self, job):
        try:
            start = time.time()
            job['job_started_at'] = start
            self.send_event('job:started', job)
            job['instructions'] = self.execute(job['instructions'])
            job['job_finished_at'] = time.time()
            self.logger.info("done processing %s", job)

        except Exception as e:
            job['job_finished_at'] = time.time()
            job['error'] = serialized_exception(e)
            self.send_event('job:error', job)
            job['success'] = False
            self.send_event('job:failed', job)
            self.logger.exception('failed to execute job {id}'.format(**dict(job)))
        finally:
            end = time.time()
            job['end'] = end

        if job.get('instructions'):
            self.send_event('job:success', job)

        else:
            self.send_event('job:failed', job)

    def to_dict(self):
        return dict(
            id=self.id,
            address=self.address,
            job_type=self.job_type,
            step_name=self.name,
        )

    def loop(self):
        self.listen()
        self.connect()
        self.logger.info('listening for jobs on %s', self.address)
        while self.should_run():
            if self.pool.free_count() == 0:
                self.logger.info('waiting for an execution slot')
                self.pool.wait_available()

            job = self.sockets.recv_safe('pull-in')

            if job:
                self.logger.info('received job')
                self.pool.spawn(self.dispatch, job)
            else:
                self.notify_available()
                gevent.sleep(1)
Example #29
0
class Pipeline(object):
    def __init__(self, name, steps=[]):
        self.name = name
        self.actions = Speaker(
            'actions',
            [
                'available',
                'failed',
                'started',
                'success',
                'metric',
                'error',
                'logs',
            ]
        )
        self.steps = [s.job_type for s in steps]
        self.total_steps = len(steps)
        self.context = zmq.Context()
        self.sockets = SocketManager(zmq, self.context)
        self.sockets.create('step-events', zmq.SUB)
        self.sockets.create('jobs-in', zmq.PULL)
        for step in self.steps:
            self.sockets.create(step, zmq.PUSH)

        for action in self.actions.actions.keys():
            self.bind_action(action)

        self.total_actions = len(self.actions.actions)
        self.pool = gevent.pool.Pool(self.total_actions ** (self.total_steps + 1))
        self.greenlets = []
        self._allowed_to_run = True
        self.default_interval = 0.1
        self.backend = StorageBackend()
        self.logger = logging.getLogger('pipeline')

    def on_started(self, event):
        worker = Worker.from_event(event)
        self.logger.info('%s [%s] started to process a job', worker.job_type, worker.id)

    def on_available(self, event):
        worker = Worker.from_event(event)
        if self.backend.register_worker(worker):
            self.sockets.connect(worker.job_type, worker.address, zmq.POLLOUT)
            self.logger.info('connected to worker: [%s]', dict(worker))

    def on_failed(self, event):
        worker = Worker.from_event(event)
        self.logger.warning('%s [%s] failed', worker.job_type, worker.id)

    def on_success(self, event):
        worker = Worker.from_event(event)
        self.logger.info('%s [%s] success', worker.job_type, worker.id)
        self.enqueue_next_job(event.data)

    def on_metric(self, event):
        self.logger.info(' '.join([event.topic, event.data]))

    def on_error(self, event):
        worker = Worker.from_event(event)
        self.logger.warning('%s [%s] errored: %s', worker.job_type, worker.id, event)

    def on_logs(self, event):
        msg = event.data.pop('msg', None)
        if msg:
            self.logger.debug(msg)

    def enqueue_next_job(self, data):
        result = data.pop('instructions')
        job = Job.from_dict(data)
        job['instructions'] = result

        step_index = self.steps.index(job.type)

        try:
            next_job_type = self.steps[step_index + 1]
        except IndexError:
            next_job_type = None

        if next_job_type:
            self.logger.info("enqueuing next job: %s", next_job_type)
            job['job_type'] = next_job_type
            self.backend.enqueue_job(job)

    def bind_action(self, name, method=None):
        action = getattr(self.actions, name, None)
        if not action:
            raise KeyError('undefined action: {0}'.format(name))

        method = method or getattr(self, 'on_{0}'.format(name), None)
        if not method:
            raise TypeError('{0} does not have method {1}(self, topic, data)'.format(self.__class__, name))

        action(lambda _, event: self.spawn(method, event))

    def should_run(self):
        return self._allowed_to_run

    def listen(self, subscriber_bind_address='tcp://127.0.0.1:6000', pull_bind_address='tcp://127.0.0.1:7000'):
        self.sockets.bind('step-events', subscriber_bind_address, zmq.POLLIN)
        self.sockets.bind('jobs-in', pull_bind_address, zmq.POLLIN)
        self.logger.info('listening for events on %s', subscriber_bind_address)
        self.logger.info('listening for instructions on %s', pull_bind_address)

    def route_event(self, event):
        if not event:
            return

        ROUTES = {
            re.compile(r'available'): self.actions.available,
            re.compile(r'failed'): self.actions.failed,
            re.compile(r'success'): self.actions.success,
            re.compile(r'started'): self.actions.started,
            re.compile(r'metric'): self.actions.metric,
            re.compile(r'logs'): self.actions.logs,
            re.compile(r'error'): self.actions.error,
        }
        matched = False
        for regex, action in ROUTES.items():
            if regex.search(event.topic):
                action.shout(event)
                matched = True

        if not matched:
            print 'unmatched event', event.topic, event.data

    def drain_jobs_in(self):
        while self.should_run():
            data = self.sockets.recv_safe('jobs-in')
            if not data:
                gevent.sleep(0)
                continue

            job = Job.new(data)
            self.backend.enqueue_job(job)
            gevent.sleep(0)

    def drain_jobs_out(self):
        iteration = -1
        while self.should_run():
            iteration += 1

            index = iteration % len(self.steps)

            job_type = self.steps[index]
            worker = self.backend.get_next_available_worker_for_type(job_type)

            if not worker:
                gevent.sleep(0)
                continue

            job = self.backend.dequeue_job_of_type(job_type)
            if not job:
                gevent.sleep(0)
                continue

            self.sockets.send_safe(worker.job_type, job.to_dict())
            gevent.sleep(0)

    def spawn(self, *args, **kw):
        self.greenlets.append(
            self.pool.spawn(*args, **kw)
        )

    def idle(self):
        gevent.sleep(0)

    def loop(self):
        self.listen()
        self.spawn(self.drain_events)
        self.spawn(self.drain_jobs_in)
        self.spawn(self.drain_jobs_out)
        while self.should_run():
            gevent.sleep(5)

    def drain_events(self):
        # drain events
        while self.should_run():
            event = self.sockets.recv_event_safe('step-events')
            if event:
                self.route_event(event)
                gevent.sleep(0)
            else:
                self.idle()