def test_sendEvent_should_use_kwargs_as_event_items(self): mock_broadcaster = Mock(WampBroadcaster) mock_broadcaster.target = "broadcaster-target" mock_broadcaster.logger = Mock() mock_broadcaster.client = Mock() WampBroadcaster._sendEvent( mock_broadcaster, "event-id", "event-data", tracking_id="tracking-id", target="target", state="foobar", bar="baz", ) actual_call = mock_broadcaster.client.publish.call_args self.assertEqual( call( "target", { "payload": "event-data", "type": "event", "id": "event-id", "tracking_id": "tracking-id", "target": "target", "state": "foobar", "bar": "baz", }, ), actual_call, )
def test_sendFullUpdate_should_forward_tracking_id_to_sendEvent(self): mock_broadcaster = Mock(WampBroadcaster) WampBroadcaster.sendFullUpdate( mock_broadcaster, 'data', tracking_id='tracking-id') self.assertEqual( call('full-update', 'data', 'tracking-id'), mock_broadcaster._sendEvent.call_args)
def test_sendServiceChange_should_forward_tracking_id_to_sendEvent(self): mock_broadcaster = Mock(WampBroadcaster) WampBroadcaster.sendServiceChange( mock_broadcaster, 'data', tracking_id='tracking-id') self.assertEqual( call('service-change', 'data', 'tracking-id'), mock_broadcaster._sendEvent.call_args)
def test_should_queue_messages_when_not_connected(self): self.mock_broadcaster._check_connection.return_value = False WampBroadcaster._publish(self.mock_broadcaster, "any-topic", {"any-key": "any-value"}) WampBroadcaster._publish(self.mock_broadcaster, "other-topic", {"other-key": "other-value"}) self.assertEqual(self.mock_broadcaster.queue.append.call_args_list, [call(('any-topic', {'any-key': 'any-value'})), call(('other-topic', {'other-key': 'other-value'}))])
def test_should_run_session_open_handlers_when_establishing_connection(self): handler_1 = Mock() handler_2 = Mock() self.mock_broadcaster.on_session_open_handlers = [handler_1, handler_2] self.mock_broadcaster.client = Mock() self.mock_broadcaster.queue = [] WampBroadcaster.onSessionOpen(self.mock_broadcaster) handler_1.assert_called_with() handler_2.assert_called_with()
def test_should_flush_messages_after_connecting(self): self.mock_broadcaster.queue = [("topic1", "payload1"), ("topic2", "payload2")] self.mock_broadcaster.client = Mock() self.mock_broadcaster.on_session_open_handlers = [] WampBroadcaster.onSessionOpen(self.mock_broadcaster) self.assertEqual( self.mock_broadcaster._publish.call_args_list, [call('topic1', 'payload1'), call('topic2', 'payload2')])
def test_should_run_session_open_handlers_when_establishing_connection( self): handler_1 = Mock() handler_2 = Mock() self.mock_broadcaster.on_session_open_handlers = [handler_1, handler_2] self.mock_broadcaster.client = Mock() self.mock_broadcaster.queue = [] WampBroadcaster.onSessionOpen(self.mock_broadcaster) handler_1.assert_called_with() handler_2.assert_called_with()
def _connect_broadcaster(self): """ Establishes a connection to the broadcaster as found in the configuration. """ host = self.configuration['broadcaster_host'] port = self.configuration['broadcaster_port'] log.msg('Connecting to broadcaster on %s:%s' % (host, port)) self.broadcaster = WampBroadcaster(host, port, 'yadtreceiver') self.broadcaster.addOnSessionOpenHandler(self.onConnect)
def test_should_flush_messages_after_connecting(self): self.mock_broadcaster.queue = [("topic1", "payload1"), ("topic2", "payload2")] self.mock_broadcaster.client = Mock() self.mock_broadcaster.on_session_open_handlers = [] WampBroadcaster.onSessionOpen(self.mock_broadcaster) self.assertEqual( self.mock_broadcaster._publish.call_args_list, [ call('topic1', 'payload1'), call('topic2', 'payload2')])
def test_should_queue_messages_when_not_connected(self): self.mock_broadcaster._check_connection.return_value = False WampBroadcaster._publish(self.mock_broadcaster, "any-topic", {"any-key": "any-value"}) WampBroadcaster._publish(self.mock_broadcaster, "other-topic", {"other-key": "other-value"}) self.assertEqual(self.mock_broadcaster.queue.append.call_args_list, [ call(('any-topic', { 'any-key': 'any-value' })), call(('other-topic', { 'other-key': 'other-value' })) ])
def test_sendEvent_should_publish_expected_event_on_default_target(self): mock_broadcaster = Mock(WampBroadcaster) mock_broadcaster.target = 'broadcaster-target' mock_broadcaster.logger = Mock() mock_broadcaster.client = Mock() WampBroadcaster._sendEvent( mock_broadcaster, 'event-id', 'event-data', tracking_id='tracking-id') actual_call = mock_broadcaster.client.publish.call_args self.assertEqual(call('broadcaster-target', {'payload': 'event-data', 'type': 'event', 'id': 'event-id', 'tracking_id': 'tracking-id', 'target': 'broadcaster-target'} ), actual_call)
def test_check_connection_should_return_true_when_link_is_up(self): mock_broadcaster = Mock(WampBroadcaster) mock_broadcaster.logger = Mock() mock_broadcaster.url = "ws://broadcaster" mock_broadcaster.client = Mock() self.assertEqual(WampBroadcaster._check_connection(mock_broadcaster), True) self.assertFalse(hasattr(mock_broadcaster, "not_connected_warning_sent"))
def test_check_connection_should_return_true_when_link_is_up(self): mock_broadcaster = Mock(WampBroadcaster) mock_broadcaster.logger = Mock() mock_broadcaster.url = "ws://broadcaster" mock_broadcaster.client = Mock() self.assertEqual( WampBroadcaster._check_connection(mock_broadcaster), True) self.assertFalse( hasattr(mock_broadcaster, "not_connected_warning_sent"))
def test_sendEvent_should_publish_expected_event_on_default_target(self): mock_broadcaster = Mock(WampBroadcaster) mock_broadcaster.target = "broadcaster-target" mock_broadcaster.logger = Mock() mock_broadcaster.client = Mock() WampBroadcaster._sendEvent(mock_broadcaster, "event-id", "event-data", tracking_id="tracking-id") actual_call = mock_broadcaster.client.publish.call_args self.assertEqual( call( "broadcaster-target", { "payload": "event-data", "type": "event", "id": "event-id", "tracking_id": "tracking-id", "target": "broadcaster-target", }, ), actual_call, )
def test_sendEvent_should_use_kwargs_as_event_items(self): mock_broadcaster = Mock(WampBroadcaster) mock_broadcaster.target = 'broadcaster-target' mock_broadcaster.logger = Mock() mock_broadcaster.client = Mock() WampBroadcaster._sendEvent(mock_broadcaster, 'event-id', 'event-data', tracking_id='tracking-id', target='target', state='foobar', bar='baz') actual_call = mock_broadcaster.client.publish.call_args self.assertEqual(call('target', {'payload': 'event-data', 'type': 'event', 'id': 'event-id', 'tracking_id': 'tracking-id', 'target': 'target', 'state': 'foobar', 'bar': 'baz'} ), actual_call)
def test_sendEvent_should_not_drop_data_when_connection(self, check_connection): check_connection.return_value = True ybc = WampBroadcaster("host", 42) ybc.target = "broadcaster-target" ybc.logger = Mock() ybc.client = Mock() WampBroadcaster._sendEvent(ybc, "event-id", "event-data", tracking_id="tracking-id", target="target") self.assertTrue(ybc.client.publish.called)
def test_sendEvent_should_not_drop_data_when_connection(self, check_connection): check_connection.return_value = True ybc = WampBroadcaster('host', 42) ybc.target = 'broadcaster-target' ybc.logger = Mock() ybc.client = Mock() WampBroadcaster._sendEvent(ybc, 'event-id', 'event-data', tracking_id='tracking-id', target='target') self.assertTrue(ybc.client.publish.called)
def test_should_publish_cmd_for_default_target(self): ybc = WampBroadcaster('host', 42) ybc.target = 'broadcaster-target' ybc.logger = Mock() ybc.client = Mock() ybc.publish_cmd('status', 'failed', 'hello', 'nsa-tracking') ybc.client.publish.assert_called_with('broadcaster-target', { 'cmd': 'status', 'state': 'failed', 'payload': None, 'tracking_id': 'nsa-tracking', 'message': 'hello', 'type': 'event', 'target': 'broadcaster-target', 'id': 'cmd'})
def test_should_publish_request_for_target(self): ybc = WampBroadcaster('host', 42) ybc.target = 'broadcaster-target' ybc.logger = Mock() ybc.client = Mock() ybc.publish_request_for_target('target', 'cmd', 'args', 'nsa-tracker') ybc.client.publish.assert_called_with('target', { 'args': 'args', 'cmd': 'cmd', 'type': 'event', 'id': 'request', 'payload': None, 'target': 'target', 'tracking_id': 'nsa-tracker'})
def test_should_publish_request_for_target(self): ybc = WampBroadcaster("host", 42) ybc.target = "broadcaster-target" ybc.logger = Mock() ybc.client = Mock() ybc.publish_request_for_target("target", "cmd", "args", "nsa-tracker") ybc.client.publish.assert_called_with( "target", { "args": "args", "cmd": "cmd", "type": "event", "id": "request", "payload": None, "target": "target", "tracking_id": "nsa-tracker", }, )
def test_should_publish_cmd_for_target(self): ybc = WampBroadcaster("host", 42) ybc.target = "broadcaster-target" ybc.logger = Mock() ybc.client = Mock() ybc.publish_cmd_for_target("target", "status", "failed", "hello", "nsa-tracking") ybc.client.publish.assert_called_with( "target", { "cmd": "status", "state": "failed", "payload": None, "tracking_id": "nsa-tracking", "message": "hello", "type": "event", "target": "target", "id": "cmd", }, )
class Receiver(service.Service): """ The receiver connects to the broadcaster and receives events for the targets that it subscribed to. """ def subscribeTarget(self, targetname): self.configuration.reload_targets() if targetname in self.configuration['allowed_targets']: log.msg('subscribing to target "%s".' % targetname) self.broadcaster.client.subscribe(self.onEvent, unicode(targetname)) else: log.msg( "Can't subscribe to target %s. Target not in allowed targets." % targetname) def unsubscribeTarget(self, targetname): log.msg('unsubscribing from target "%s".' % targetname) self.broadcaster.client.unsubscribe(targetname) def get_target_directory(self, target): """ Appends the given target name to the targets_directory. @raise ReceiverException: if the target directory does not exist. """ hostname = self.configuration['hostname'] targets_directory = self.configuration['targets_directory'] target_directory = os.path.join(targets_directory, target) if not os.path.exists(target_directory): raise ReceiverException('(%s) target[%s] request failed: target directory "%s" does not exist.' % (hostname, target, target_directory)) return target_directory def handle_request(self, event): tracking_id = _determine_tracking_id(event.arguments) vote = str(random_uuid()) def broadcast_vote(_): log.msg('Voting %r for request with tracking-id %r' % (vote, tracking_id)) self.broadcaster._sendEvent('vote', data=vote, tracking_id=tracking_id, target=event.target) def cleanup_fsm(_): del self.states[tracking_id] log.msg('Cleaned up fsm for %s, %d left in memory' % (event.target, len(self.states))) def fold(_): METRICS['voting_folds'] += 1 self.states[tracking_id] = create_voting_fsm(tracking_id, vote, broadcast_vote, functools.partial( self.perform_request, event), fold, cleanup_fsm) reactor.callLater(10, self.states[tracking_id].showdown) def perform_request(self, event, _): """ Handles a request for the given target by executing the given command (using the python_command and script_to_execute from the configuration). """ log.msg('I have won the vote for %r, starting it now..' % (event.target)) METRICS['voting_wins'] += 1 try: hostname = str(self.configuration['hostname']) python_command = str(self.configuration['python_command']) script_to_execute = str(self.configuration['script_to_execute']) command_and_arguments_list = [ python_command, script_to_execute] + event.arguments command_with_arguments = ' '.join(command_and_arguments_list) event.tracking_id = _determine_tracking_id(command_and_arguments_list) self.publish_start(event) if event.tracking_id in self.states: self.states[event.tracking_id].spawned() else: log.err('Tracking ID %r not registered with my FSM, but handling it anyway.' % event.tracking_id) process_protocol = ProcessProtocol( hostname, self.broadcaster, event.target, command_with_arguments, tracking_id=event.tracking_id) target_dir = self.get_target_directory(event.target) # we pulled the arguments out of the event, so they are unicode, not string yet command_and_arguments_list = map(lambda possible_unicode: str(possible_unicode), command_and_arguments_list) reactor.spawnProcess(process_protocol, python_command, command_and_arguments_list, env={}, path=target_dir) except Exception as e: self.publish_failed(event, "%s : %s" % (type(e), e.message)) def publish_failed(self, event, message): """ Publishes a event to signal that the command on the target failed. """ log.err(_stuff=Exception(message), _why=message) METRICS['commands_failed.%s' % (event.target)] += 1 self.broadcaster.publish_cmd_for_target( event.target, event.command, events.FAILED, message, tracking_id=event.tracking_id) def publish_start(self, event): """ Publishes a event to signal that the command on the target started. """ hostname = self.configuration['hostname'] message = '(%s) target[%s] request: command="%s", arguments=%s' % ( hostname, event.target, event.command, event.arguments) log.msg(message) METRICS['commands_started.%s' % (event.target)] += 1 self.broadcaster.publish_cmd_for_target( event.target, event.command, events.STARTED, message, tracking_id=event.tracking_id) def onConnect(self): """ Subscribes to the targets from the configuration. The receiver is useless when no targets are configured, therefore it will exit with error code 1 when no targets are configured. """ self.states = {} self.broadcaster.client.connectionLost = self.onConnectionLost host = self.configuration['broadcaster_host'] port = self.configuration['broadcaster_port'] log.msg('Successfully connected to broadcaster on %s:%s' % (host, port)) self.configuration.reload_targets() targets = sorted(self.configuration['allowed_targets']) if not targets: log.err('No targets configured or no targets in allowed targets.') exit(1) for targetname in targets: log.msg('subscribing to target "%s".' % targetname) self.broadcaster.client.subscribe(self.onEvent, unicode(targetname)) def _should_refresh_connection(self): if not hasattr(self, 'broadcaster') or not self.broadcaster.client: log.msg('Not connected, cannot refresh connection') return False # no connection, cannot refresh current_hour = datetime.now().hour if not current_hour == 2: # only refresh at 2:xx a.m. log.msg("It's %d:xx, not 2:xx a.m., no connection-refresh now." % current_hour) return False return True def _refresh_connection(self, delay=60 * 60, first_call=False): """ When connected, closes connection to force a clean reconnect, except on first_call """ reactor.callLater(delay, self._refresh_connection) log.msg('Might want to refresh connection now.') if not first_call and self._should_refresh_connection(): log.msg( 'Closing connection to broadcaster. This should force a connection-refresh.') self.broadcaster.client.sendClose() def onConnectionLost(self, reason): """ Allows for clean reconnect behaviour, because it 'none'ifies the client explicitly """ log.err('connection lost: %s' % reason) self.broadcaster.client = None def onEvent(self, *args): """ Will be called when receiving an event from the broadcaster. See onConnect which subscribes to the targets. """ try: # Wamp v1: onEvent is callbacked with topic and event target, event_data = args except ValueError: # Wamp v2: onEvent is callbacked with event event_data, = args target = None event = events.Event(target, event_data) if event.is_a_vote: voting_fsm = self.states.get(event.tracking_id) if not voting_fsm: log.msg( 'Ignoring vote %r because I have already lost' % event.vote) return own_vote = voting_fsm.vote is_a_fold = (own_vote < event.vote) if is_a_fold: log.msg( 'Folding due to vote %r being higher than own vote %r' % (event.vote, own_vote)) voting_fsm.fold() else: log.msg( 'Calling due to vote %r being lower than own vote %r' % (event.vote, own_vote)) voting_fsm.call() elif event.is_a_request: try: self.handle_request(event) except Exception as e: log.err(e.message) for line in traceback.format_exc().splitlines(): log.err(line) self.publish_failed(event, e.message) else: log.msg(str(event)) def set_configuration(self, configuration): """ Assigns a configuration to this receiver instance. """ self.configuration = configuration def initialize_twisted_logging(self): twenty_megabytes = 20000000 log_file = LogFile.fromFullPath(self.configuration['log_filename'], maxRotatedFiles=10, rotateLength=twenty_megabytes) log.startLogging(log_file) def startService(self): """ Initializes logging and establishes connection to broadcaster. """ self.initialize_twisted_logging() log.msg('yadtreceiver version %s' % __version__) self._connect_broadcaster() self._refresh_connection(first_call=True) self.schedule_write_metrics(first_call=True) self.reset_metrics_at_midnight(first_call=True) def stopService(self): """ Writes 'shutting down service' to the log. """ log.msg('shutting down service') def _connect_broadcaster(self): """ Establishes a connection to the broadcaster as found in the configuration. """ host = self.configuration['broadcaster_host'] port = self.configuration['broadcaster_port'] log.msg('Connecting to broadcaster on %s:%s' % (host, port)) self.broadcaster = WampBroadcaster(host, port, 'yadtreceiver') self.broadcaster.addOnSessionOpenHandler(self.onConnect) def write_metrics_to_file(self): metrics_directory = self.configuration['metrics_directory'] if not metrics_directory: return if not os.path.isdir(metrics_directory): try: os.makedirs(metrics_directory) except Exception as e: log.err("Cannot create metrics directory : {0}".format(e)) return metrics_file_name = self.configuration['metrics_file'] with open(metrics_file_name, 'w') as metrics_file: _write_metrics(METRICS, metrics_file) def schedule_write_metrics(self, delay=30, first_call=False): reactor.callLater(delay, self.schedule_write_metrics) if not first_call: start = time() self.write_metrics_to_file() write_duration = time() - start log.msg("Wrote metrics to file in {0} seconds".format(write_duration)) METRICS["last_write_duration"] = write_duration def reset_metrics_at_midnight(cls, first_call=False): reactor.callLater(seconds_to_midnight(), cls.reset_metrics_at_midnight) if not first_call: log.msg("Resetting metrics") _reset_metrics(METRICS)
from __future__ import print_function import sys from twisted.internet import reactor import logging logging.basicConfig() logger = logging.getLogger() logger.setLevel(logging.DEBUG) try: host = sys.argv[1] message = sys.argv[2] except IndexError: print("Usage: {0} hostname message".format(sys.argv[0])) sys.exit(1) sys.path.insert(0, "src/main/python") from yadtbroadcastclient import WampBroadcaster def flood(message): wamp._publish("topic", {"id": message}) reactor.callLater(1, flood, message) wamp = WampBroadcaster(host, "8080", "topic") flood(message) reactor.callLater(5, reactor.stop) reactor.run()
try: verify_if_is_expected_event(event) except Exception as e: logger.error(e) def verify_if_is_expected_event(event): if expected_event == event: logger.info("Success: found target event %s" % expected_event) global exit_code exit_code = 0 reactor.stop() else: logger.info("Expected message not yet found") def timeout(timeout_in_seconds): logger.error("Timed out after %d seconds" % timeout_in_seconds) reactor.stop() wamp = WampBroadcaster(host, "8080", "topic") wamp.onEvent = onEvent logger.info("Waiting %d seconds for target event %s" % (timeout_in_seconds, expected_event)) reactor.callLater(timeout_in_seconds, timeout, timeout_in_seconds) reactor.run() sys.exit(exit_code)
]] try: host = sys.argv[1] except IndexError: print("Usage: {0} hostname".format(sys.argv[0])) sys.exit(1) sys.path.insert(0, "src/main/python") from yadtbroadcastclient import WampBroadcaster from time import time test_id = str(int(time())) w = WampBroadcaster(host, "8080", test_id) w.onEvent = partial(print, "Got event ") def connected(): reactor.connected = True def send_full_update_and_service_change(): w.sendServiceChange([{'uri': "service://foo/bar", 'state': "UP"}]) w.sendFullUpdate(dummy_data) reactor.callLater(5, reactor.stop) def ensure_test_data_is_present(): tc = unittest.TestCase('__init__')
def test_sendServiceChange_should_forward_tracking_id_to_sendEvent(self): mock_broadcaster = Mock(WampBroadcaster) WampBroadcaster.sendServiceChange(mock_broadcaster, "data", tracking_id="tracking-id") self.assertEqual(call("service-change", "data", "tracking-id"), mock_broadcaster._sendEvent.call_args)
class Receiver(service.Service): """ The receiver connects to the broadcaster and receives events for the targets that it subscribed to. """ def subscribeTarget(self, targetname): self.configuration.reload_targets() if targetname in self.configuration['allowed_targets']: log.msg('subscribing to target "%s".' % targetname) self.broadcaster.client.subscribe(self.onEvent, unicode(targetname)) else: log.msg( "Can't subscribe to target %s. Target not in allowed targets." % targetname) def unsubscribeTarget(self, targetname): log.msg('unsubscribing from target "%s".' % targetname) self.broadcaster.client.unsubscribe(targetname) def get_target_directory(self, target): """ Appends the given target name to the targets_directory. @raise ReceiverException: if the target directory does not exist. """ hostname = self.configuration['hostname'] targets_directory = self.configuration['targets_directory'] target_directory = os.path.join(targets_directory, target) if not os.path.exists(target_directory): raise ReceiverException( '(%s) target[%s] request failed: target directory "%s" does not exist.' % (hostname, target, target_directory)) return target_directory def handle_request(self, event): tracking_id = _determine_tracking_id(event.arguments) vote = str(random_uuid()) def broadcast_vote(_): log.msg('Voting %r for request with tracking-id %r' % (vote, tracking_id)) self.broadcaster._sendEvent('vote', data=vote, tracking_id=tracking_id, target=event.target) def cleanup_fsm(_): del self.states[tracking_id] log.msg('Cleaned up fsm for %s, %d left in memory' % (event.target, len(self.states))) def fold(_): METRICS['voting_folds'] += 1 self.states[tracking_id] = create_voting_fsm( tracking_id, vote, broadcast_vote, functools.partial(self.perform_request, event), fold, cleanup_fsm) reactor.callLater(10, self.states[tracking_id].showdown) def perform_request(self, event, _): """ Handles a request for the given target by executing the given command (using the python_command and script_to_execute from the configuration). """ log.msg('I have won the vote for %r, starting it now..' % (event.target)) METRICS['voting_wins'] += 1 try: hostname = str(self.configuration['hostname']) python_command = str(self.configuration['python_command']) script_to_execute = str(self.configuration['script_to_execute']) command_and_arguments_list = [python_command, script_to_execute ] + event.arguments command_with_arguments = ' '.join(command_and_arguments_list) event.tracking_id = _determine_tracking_id( command_and_arguments_list) self.publish_start(event) if event.tracking_id in self.states: self.states[event.tracking_id].spawned() else: log.err( 'Tracking ID %r not registered with my FSM, but handling it anyway.' % event.tracking_id) process_protocol = ProcessProtocol(hostname, self.broadcaster, event.target, command_with_arguments, tracking_id=event.tracking_id) target_dir = self.get_target_directory(event.target) # we pulled the arguments out of the event, so they are unicode, not string yet command_and_arguments_list = map( lambda possible_unicode: str(possible_unicode), command_and_arguments_list) reactor.spawnProcess(process_protocol, python_command, command_and_arguments_list, env={}, path=target_dir) except Exception as e: self.publish_failed(event, "%s : %s" % (type(e), e.message)) def publish_failed(self, event, message): """ Publishes a event to signal that the command on the target failed. """ log.err(_stuff=Exception(message), _why=message) METRICS['commands_failed.%s' % (event.target)] += 1 self.broadcaster.publish_cmd_for_target(event.target, event.command, events.FAILED, message, tracking_id=event.tracking_id) def publish_start(self, event): """ Publishes a event to signal that the command on the target started. """ hostname = self.configuration['hostname'] message = '(%s) target[%s] request: command="%s", arguments=%s' % ( hostname, event.target, event.command, event.arguments) log.msg(message) METRICS['commands_started.%s' % (event.target)] += 1 self.broadcaster.publish_cmd_for_target(event.target, event.command, events.STARTED, message, tracking_id=event.tracking_id) def onConnect(self): """ Subscribes to the targets from the configuration. The receiver is useless when no targets are configured, therefore it will exit with error code 1 when no targets are configured. """ self.states = {} self.broadcaster.client.connectionLost = self.onConnectionLost host = self.configuration['broadcaster_host'] port = self.configuration['broadcaster_port'] log.msg('Successfully connected to broadcaster on %s:%s' % (host, port)) self.configuration.reload_targets() targets = sorted(self.configuration['allowed_targets']) if not targets: log.err('No targets configured or no targets in allowed targets.') exit(1) for targetname in targets: log.msg('subscribing to target "%s".' % targetname) self.broadcaster.client.subscribe(self.onEvent, unicode(targetname)) def _should_refresh_connection(self): if not hasattr(self, 'broadcaster') or not self.broadcaster.client: log.msg('Not connected, cannot refresh connection') return False # no connection, cannot refresh current_hour = datetime.now().hour if not current_hour == 2: # only refresh at 2:xx a.m. log.msg("It's %d:xx, not 2:xx a.m., no connection-refresh now." % current_hour) return False return True def _refresh_connection(self, delay=60 * 60, first_call=False): """ When connected, closes connection to force a clean reconnect, except on first_call """ reactor.callLater(delay, self._refresh_connection) log.msg('Might want to refresh connection now.') if not first_call and self._should_refresh_connection(): log.msg( 'Closing connection to broadcaster. This should force a connection-refresh.' ) self.broadcaster.client.sendClose() def onConnectionLost(self, reason): """ Allows for clean reconnect behaviour, because it 'none'ifies the client explicitly """ log.err('connection lost: %s' % reason) self.broadcaster.client = None def onEvent(self, *args): """ Will be called when receiving an event from the broadcaster. See onConnect which subscribes to the targets. """ try: # Wamp v1: onEvent is callbacked with topic and event target, event_data = args except ValueError: # Wamp v2: onEvent is callbacked with event event_data, = args target = None event = events.Event(target, event_data) if event.is_a_vote: voting_fsm = self.states.get(event.tracking_id) if not voting_fsm: log.msg('Ignoring vote %r because I have already lost' % event.vote) return own_vote = voting_fsm.vote is_a_fold = (own_vote < event.vote) if is_a_fold: log.msg( 'Folding due to vote %r being higher than own vote %r' % (event.vote, own_vote)) voting_fsm.fold() else: log.msg('Calling due to vote %r being lower than own vote %r' % (event.vote, own_vote)) voting_fsm.call() elif event.is_a_request: try: self.handle_request(event) except Exception as e: log.err(e.message) for line in traceback.format_exc().splitlines(): log.err(line) self.publish_failed(event, e.message) else: log.msg(str(event)) def set_configuration(self, configuration): """ Assigns a configuration to this receiver instance. """ self.configuration = configuration def initialize_twisted_logging(self): twenty_megabytes = 20000000 log_file = LogFile.fromFullPath(self.configuration['log_filename'], maxRotatedFiles=10, rotateLength=twenty_megabytes) log.startLogging(log_file) def startService(self): """ Initializes logging and establishes connection to broadcaster. """ self.initialize_twisted_logging() log.msg('yadtreceiver version %s' % __version__) self._connect_broadcaster() self._refresh_connection(first_call=True) self.schedule_write_metrics(first_call=True) self.reset_metrics_at_midnight(first_call=True) def stopService(self): """ Writes 'shutting down service' to the log. """ log.msg('shutting down service') def _connect_broadcaster(self): """ Establishes a connection to the broadcaster as found in the configuration. """ host = self.configuration['broadcaster_host'] port = self.configuration['broadcaster_port'] log.msg('Connecting to broadcaster on %s:%s' % (host, port)) self.broadcaster = WampBroadcaster(host, port, 'yadtreceiver') self.broadcaster.addOnSessionOpenHandler(self.onConnect) def write_metrics_to_file(self): metrics_directory = self.configuration['metrics_directory'] if not metrics_directory: return if not os.path.isdir(metrics_directory): try: os.makedirs(metrics_directory) except Exception as e: log.err("Cannot create metrics directory : {0}".format(e)) return metrics_file_name = self.configuration['metrics_file'] with open(metrics_file_name, 'w') as metrics_file: _write_metrics(METRICS, metrics_file) def schedule_write_metrics(self, delay=30, first_call=False): reactor.callLater(delay, self.schedule_write_metrics) if not first_call: start = time() self.write_metrics_to_file() write_duration = time() - start log.msg( "Wrote metrics to file in {0} seconds".format(write_duration)) METRICS["last_write_duration"] = write_duration def reset_metrics_at_midnight(cls, first_call=False): reactor.callLater(seconds_to_midnight(), cls.reset_metrics_at_midnight) if not first_call: log.msg("Resetting metrics") _reset_metrics(METRICS)
def test_sendFullUpdate_should_forward_tracking_id_to_sendEvent(self): mock_broadcaster = Mock(WampBroadcaster) WampBroadcaster.sendFullUpdate(mock_broadcaster, "data", tracking_id="tracking-id") self.assertEqual(call("full-update", "data", "tracking-id"), mock_broadcaster._sendEvent.call_args)