def setUp(self): settings = AutopushSettings( hostname="localhost", statsd_host=None, ) apns_config = {'cert_file': 'fake.cert', 'key_file': 'fake.key'} self.mock_apns = Mock(spec=apns.APNs) self.router = APNSRouter(settings, apns_config) self.router.apns = self.mock_apns self.notif = Notification(10, "data", dummy_chid, None, 200) self.router_data = dict(router_data=dict(token="connect_data"))
def setUp(self): from twisted.logger import Logger settings = AutopushSettings( hostname="localhost", statsd_host=None, ) apns_config = {'cert_file': 'fake.cert', 'key_file': 'fake.key'} self.mock_apns = Mock(spec=apns.APNs) self.router = APNSRouter(settings, apns_config) self.router.apns = self.mock_apns self.router.log = Mock(spec=Logger) self.headers = {"content-encoding": "aesgcm", "encryption": "test", "encryption-key": "test"} self.notif = Notification(10, "q60d6g", dummy_chid, self.headers, 200) self.router_data = dict(router_data=dict(token="connect_data"))
def setUp(self, mt, mc): from twisted.logger import Logger conf = AutopushConfig( hostname="localhost", statsd_host=None, ) apns_config = { 'firefox': { 'cert': 'fake.cert', 'key': 'fake.key', 'topic': 'com.example.SomeApp', 'max_connections': 2, } } self.mock_connection = mc mc.return_value = mc self.metrics = metrics = Mock(spec=SinkMetrics) self.router = APNSRouter(conf, apns_config, metrics) self.mock_response = Mock() self.mock_response.status = 200 mc.get_response.return_value = self.mock_response # toss the existing connection try: self.router.apns['firefox'].connections.pop() except IndexError: # pragma nocover pass self.router.apns['firefox'].connections.append(self.mock_connection) self.router.apns['firefox'].log = Mock(spec=Logger) self.headers = { "content-encoding": "aesgcm", "encryption": "test", "encryption-key": "test" } self.notif = WebPushNotification( uaid=uuid.UUID(dummy_uaid), channel_id=uuid.UUID(dummy_chid), data="q60d6g", headers=self.headers, ttl=200, message_id=10, ) self.notif.cleanup_headers() self.router_data = dict( router_data=dict(token="connect_data", rel_channel="firefox"))
class APNSRouterTestCase(unittest.TestCase): def setUp(self): settings = AutopushSettings( hostname="localhost", statsd_host=None, ) apns_config = {'cert_file': 'fake.cert', 'key_file': 'fake.key'} self.mock_apns = Mock(spec=apns.APNs) self.router = APNSRouter(settings, apns_config) self.router.apns = self.mock_apns self.notif = Notification(10, "data", dummy_chid, None, 200) self.router_data = dict(router_data=dict(token="connect_data")) def test_register(self): result = self.router.register("uaid", {"token": "connect_data"}) eq_(result, {"token": "connect_data"}) def test_register_bad(self): self.assertRaises(RouterException, self.router.register, "uaid", {}) def test_route_notification(self): d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) d.addCallback(check_results) return d def test_message_pruning(self): now = int(time.time()) self.router.messages = {now: {'token': 'dump', 'payload': {}}, now-60: {'token': 'dump', 'payload': {}}} d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) eq_(len(self.router.messages), 1) d.addCallback(check_results) return d def test_response_listener_with_success(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=0, identifier=1)) eq_(len(self.router.messages), 0) def test_response_listener_with_nonretryable_error(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=2, identifier=1)) eq_(len(self.router.messages), 1) def test_response_listener_with_retryable_existing_message(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} # Mock out the _connect call to be harmless self.router._connect = Mock() self.router._error(dict(status=1, identifier=1)) eq_(len(self.router.messages), 1) assert(self.router.apns.gateway_server.send_notification.called) def test_response_listener_with_retryable_non_existing_message(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=1, identifier=10)) eq_(len(self.router.messages), 1) def test_ammend(self): resp = {"key": "value"} eq_(resp, self.router.amend_msg(resp)) def test_check_token(self): (t, v) = self.router.check_token("") ok_(t)
class APNSRouterTestCase(unittest.TestCase): def _waitfor(self, func): times = 0 while not func(): # pragma: nocover time.sleep(1) times += 1 if times > 9: break @patch('autopush.router.apns2.HTTP20Connection', spec=hyper.HTTP20Connection) @patch('hyper.tls', spec=hyper.tls) def setUp(self, mt, mc): from twisted.logger import Logger conf = AutopushConfig( hostname="localhost", statsd_host=None, ) apns_config = { 'firefox': { 'cert': 'fake.cert', 'key': 'fake.key', 'topic': 'com.example.SomeApp', 'max_connections': 2, } } self.mock_connection = mc mc.return_value = mc self.metrics = metrics = Mock(spec=SinkMetrics) self.router = APNSRouter(conf, apns_config, metrics) self.mock_response = Mock() self.mock_response.status = 200 mc.get_response.return_value = self.mock_response # toss the existing connection try: self.router.apns['firefox'].connections.pop() except IndexError: # pragma nocover pass self.router.apns['firefox'].connections.append(self.mock_connection) self.router.apns['firefox'].log = Mock(spec=Logger) self.headers = { "content-encoding": "aesgcm", "encryption": "test", "encryption-key": "test" } self.notif = WebPushNotification( uaid=uuid.UUID(dummy_uaid), channel_id=uuid.UUID(dummy_chid), data="q60d6g", headers=self.headers, ttl=200, message_id=10, ) self.notif.cleanup_headers() self.router_data = dict( router_data=dict(token="connect_data", rel_channel="firefox")) def test_register(self): router_data = {"token": "connect_data"} self.router.register("uaid", router_data=router_data, app_id="firefox") assert router_data == { "rel_channel": "firefox", "token": "connect_data" } def test_extended_register(self): router_data = { "token": "connect_data", "aps": { "foo": "bar", "gorp": "baz" } } self.router.register("uaid", router_data=router_data, app_id="firefox") assert router_data == { "rel_channel": "firefox", "token": "connect_data", "aps": { "foo": "bar", "gorp": "baz" } } def test_register_bad(self): with pytest.raises(RouterException): self.router.register("uaid", router_data={}, app_id="firefox") def test_register_bad_channel(self): with pytest.raises(RouterException): self.router.register("uaid", router_data={"token": "connect_data"}, app_id="unknown") @inlineCallbacks def test_connection_error(self): from hyper.http20.exceptions import ConnectionError def raiser(*args, **kwargs): raise ConnectionError("oops") self.router.apns['firefox'].connections[1].request = Mock( side_effect=raiser) with pytest.raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) assert ex.value.response_body == ('APNS returned an error ' 'processing request') assert ex.value.status_code == 502 self.flushLoggedErrors() @inlineCallbacks def test_connection_fail_error(self): def raiser(*args, **kwargs): error = socket.error() error.errno = socket.errno.EPIPE raise error self.router.apns['firefox'].connections[1].request = Mock( side_effect=raiser) with pytest.raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) assert ex.value.response_body == "APNS returned an error processing " \ "request" assert ex.value.status_code == 502 self.flushLoggedErrors() @inlineCallbacks def test_route_notification(self): result = yield self.router.route_notification(self.notif, self.router_data) yield self._waitfor( lambda: self.mock_connection.request.called is True) assert isinstance(result, RouterResponse) assert self.mock_connection.request.called body = self.mock_connection.request.call_args[1] body_json = json.loads(body['body']) assert 'chid' in body_json # The ChannelID is a UUID4, and unpredictable. del (body_json['chid']) assert body_json == { "body": "q60d6g", "enc": "test", "ver": 10, "aps": { "mutable-content": 1, "alert": { "loc-key": "SentTab.NoTabArrivingNotification.body", "title-loc-key": "SentTab.NoTabArrivingNotification.title", }, }, "enckey": "test", "con": "aesgcm", } @inlineCallbacks def test_route_notification_complex(self): router_data = dict( router_data=dict(token="connect_data", rel_channel="firefox", aps=dict(string="String", array=['a', 'b', 'c'], number=decimal.Decimal(4)))) result = yield self.router.route_notification(self.notif, router_data) yield self._waitfor( lambda: self.mock_connection.request.called is True) assert isinstance(result, RouterResponse) assert self.mock_connection.request.called body = self.mock_connection.request.call_args[1] body_json = json.loads(body['body']) assert body_json['aps']['number'] == 4 assert body_json['aps']['string'] == 'String' @inlineCallbacks def test_route_low_priority_notification(self): """low priority and empty apns_ids are not yet used, but may feature when priorty work is done.""" apns2 = self.router.apns['firefox'] exp = int(time.time() + 300) yield apns2.send("abcd0123", {}, 'apnsid', priority=False, exp=exp) yield self._waitfor( lambda: self.mock_connection.request.called is True) assert self.mock_connection.request.called body = self.mock_connection.request.call_args[1] headers = body['headers'] assert headers == { 'apns-expiration': str(exp), 'apns-topic': 'com.example.SomeApp', 'apns-priority': '5', 'apns-id': 'apnsid' } @inlineCallbacks def test_bad_send(self): self.mock_response.status = 400 self.mock_response.read.return_value = json.dumps({'reason': 'boo'}) with pytest.raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) assert isinstance(ex.value, RouterException) assert ex.value.status_code == 502 assert ex.value.message == 'APNS Transmit Error 400:boo' assert ex.value.response_body == ( 'APNS could not process your message boo') @inlineCallbacks def test_fail_send(self): def throw(*args, **kwargs): raise HTTP20Error("oops") self.router.apns['firefox'].connections[0].request.side_effect = throw with pytest.raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) assert isinstance(ex.value, RouterException) assert ex.value.status_code == 502 assert ex.value.message == "Server error" assert ex.value.response_body == 'APNS returned an error ' \ 'processing request' assert self.metrics.increment.called assert self.metrics.increment.call_args[0][0] == \ 'notification.bridge.connection.error' self.flushLoggedErrors() @inlineCallbacks def test_fail_send_bad_write_retry(self): def throw(*args, **kwargs): raise ssl.SSLError(ssl.SSL_ERROR_SSL, "[SSL: BAD_WRITE_RETRY] bad write retry") self.router.apns['firefox'].connections[0].request.side_effect = throw with pytest.raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) assert isinstance(ex.value, RouterException) assert ex.value.status_code == 502 assert ex.value.message == "Server error" assert ex.value.response_body == 'APNS returned an error ' \ 'processing request' assert self.metrics.increment.called assert self.metrics.increment.call_args[0][0] == \ 'notification.bridge.connection.error' self.flushLoggedErrors() def test_too_many_connections(self): rr = self.router.apns['firefox'] with pytest.raises(RouterException) as ex: while True: rr._get_connection() assert isinstance(ex.value, RouterException) assert ex.value.status_code == 503 assert ex.value.message == "Too many APNS requests, " \ "increase pool from 2" assert ex.value.response_body == "APNS busy, please retry" def test_amend(self): resp = {"key": "value"} expected = resp.copy() self.router.amend_endpoint_response(resp, {}) assert resp == expected def test_route_crypto_key(self): headers = { "content-encoding": "aesgcm", "encryption": "test", "crypto-key": "test" } self.notif = WebPushNotification( uaid=uuid.UUID(dummy_uaid), channel_id=uuid.UUID(dummy_chid), data="q60d6g", headers=headers, ttl=200, message_id=10, ) self.notif.cleanup_headers() d = self.router.route_notification(self.notif, self.router_data) def check_results(result): assert isinstance(result, RouterResponse) assert result.status_code == 201 assert result.logged_status == 200 assert "TTL" in result.headers assert self.mock_connection.called d.addCallback(check_results) return d
class APNSRouterTestCase(unittest.TestCase): def setUp(self): settings = AutopushSettings( hostname="localhost", statsd_host=None, ) apns_config = {'cert_file': 'fake.cert', 'key_file': 'fake.key'} self.mock_apns = Mock(spec=apns.APNs) self.router = APNSRouter(settings, apns_config) self.router.apns = self.mock_apns self.notif = Notification(10, "data", dummy_chid, None, 200) self.router_data = dict(router_data=dict(token="connect_data")) def test_register(self): result = self.router.register("uaid", {"token": "connect_data"}) eq_(result, {"token": "connect_data"}) def test_register_bad(self): self.assertRaises(RouterException, self.router.register, "uaid", {}) def test_route_notification(self): d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) d.addCallback(check_results) return d def test_message_pruning(self): now = int(time.time()) self.router.messages = {now: {'token': 'dump', 'payload': {}}, now-60: {'token': 'dump', 'payload': {}}} d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) eq_(len(self.router.messages), 1) d.addCallback(check_results) return d def test_response_listener_with_success(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=0, identifier=1)) eq_(len(self.router.messages), 0) def test_response_listener_with_nonretryable_error(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=2, identifier=1)) eq_(len(self.router.messages), 1) def test_response_listener_with_retryable_existing_message(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} # Mock out the _connect call to be harmless self.router._connect = Mock() self.router._error(dict(status=1, identifier=1)) eq_(len(self.router.messages), 1) assert(self.router.apns.gateway_server.send_notification.called) def test_response_listener_with_retryable_non_existing_message(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=1, identifier=10)) eq_(len(self.router.messages), 1) def test_ammend(self): resp = {"key": "value"} eq_(resp, self.router.amend_msg(resp))
class APNSRouterTestCase(unittest.TestCase): def setUp(self): from twisted.logger import Logger settings = AutopushSettings( hostname="localhost", statsd_host=None, ) apns_config = {'cert_file': 'fake.cert', 'key_file': 'fake.key'} self.mock_apns = Mock(spec=apns.APNs) self.router = APNSRouter(settings, apns_config) self.router.apns = self.mock_apns self.router.log = Mock(spec=Logger) self.headers = {"content-encoding": "aesgcm", "encryption": "test", "encryption-key": "test"} self.notif = Notification(10, "q60d6g", dummy_chid, self.headers, 200) self.router_data = dict(router_data=dict(token="connect_data")) def test_register(self): result = self.router.register("uaid", {"token": "connect_data"}) eq_(result, {"token": "connect_data"}) def test_register_bad(self): self.assertRaises(RouterException, self.router.register, "uaid", {}) def test_route_notification(self): d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) d.addCallback(check_results) return d def test_message_pruning(self): now = int(time.time()) self.router.messages = {now: {'token': 'dump', 'payload': {}}, now-60: {'token': 'dump', 'payload': {}}} d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) eq_(len(self.router.messages), 1) payload = self.router.messages[now]['payload'] eq_(payload.alert, 'SimplePush') custom = payload.custom eq_(custom['Msg'], self.notif.data) eq_(custom['Ver'], self.notif.version) eq_(custom['Con'], 'aesgcm') eq_(custom['Enc'], 'test') eq_(custom['Enckey'], 'test') eq_(custom['Chid'], self.notif.channel_id) ok_('Cryptokey' not in custom) d.addCallback(check_results) return d def test_response_listener_with_success(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=0, identifier=1)) eq_(len(self.router.messages), 0) def test_response_listener_with_nonretryable_error(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=2, identifier=1)) eq_(len(self.router.messages), 1) def test_response_listener_with_retryable_existing_message(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} # Mock out the _connect call to be harmless self.router._connect = Mock() self.router._error(dict(status=1, identifier=1)) eq_(len(self.router.messages), 1) assert(self.router.apns.gateway_server.send_notification.called) def test_response_listener_with_retryable_non_existing_message(self): self.router.messages = {1: {'token': 'dump', 'payload': {}}} self.router._error(dict(status=1, identifier=10)) eq_(len(self.router.messages), 1) def test_ammend(self): resp = {"key": "value"} eq_(resp, self.router.amend_msg(resp)) def test_check_token(self): (t, v) = self.router.check_token("") ok_(t) def test_route_crypto_key(self): headers = {"content-encoding": "aesgcm", "encryption": "test", "crypto-key": "test"} self.notif = Notification(10, "q60d6g", dummy_chid, headers, 200) now = int(time.time()) self.router.messages = {now: {'token': 'dump', 'payload': {}}, now-60: {'token': 'dump', 'payload': {}}} d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) assert(self.mock_apns.gateway_server.send_notification.called) eq_(len(self.router.messages), 1) payload = self.router.messages[now]['payload'] eq_(payload.alert, 'SimplePush') custom = payload.custom eq_(custom['Msg'], self.notif.data) eq_(custom['Ver'], self.notif.version) eq_(custom['Con'], 'aesgcm') eq_(custom['Enc'], 'test') eq_(custom['Cryptokey'], 'test') eq_(custom['Chid'], self.notif.channel_id) ok_('Enckey' not in custom) d.addCallback(check_results) return d
def __init__( self, crypto_key=None, datadog_api_key=None, datadog_app_key=None, datadog_flush_interval=None, hostname=None, port=None, router_scheme=None, router_hostname=None, router_port=None, endpoint_scheme=None, endpoint_hostname=None, endpoint_port=None, router_conf={}, router_tablename="router", router_read_throughput=5, router_write_throughput=5, storage_tablename="storage", storage_read_throughput=5, storage_write_throughput=5, message_tablename="message", message_read_throughput=5, message_write_throughput=5, statsd_host="localhost", statsd_port=8125, resolve_hostname=False, max_data=4096, # Reflected up from UDP Router wake_timeout=0, env='development', enable_cors=False, s3_bucket=DEFAULT_BUCKET, senderid_expry=SENDERID_EXPRY, senderid_list={}, hello_timeout=0, ): """Initialize the Settings object Upon creation, the HTTP agent will initialize, all configured routers will be setup and started, logging will be started, and the database will have a preflight check done. """ # Use a persistent connection pool for HTTP requests. pool = HTTPConnectionPool(reactor) self.agent = Agent(reactor, connectTimeout=5, pool=pool) # Metrics setup if datadog_api_key: self.metrics = DatadogMetrics( api_key=datadog_api_key, app_key=datadog_app_key, flush_interval=datadog_flush_interval) elif statsd_host: self.metrics = TwistedMetrics(statsd_host, statsd_port) else: self.metrics = SinkMetrics() if not crypto_key: crypto_key = [Fernet.generate_key()] if not isinstance(crypto_key, list): crypto_key = [crypto_key] self.update(crypto_key=crypto_key) self.crypto_key = crypto_key self.max_data = max_data self.clients = {} # Setup hosts/ports/urls default_hostname = socket.gethostname() self.hostname = hostname or default_hostname if resolve_hostname: self.hostname = resolve_ip(self.hostname) self.port = port self.endpoint_hostname = endpoint_hostname or self.hostname self.router_hostname = router_hostname or self.hostname self.router_conf = router_conf self.router_url = canonical_url(router_scheme or 'http', self.router_hostname, router_port) self.endpoint_url = canonical_url(endpoint_scheme or 'http', self.endpoint_hostname, endpoint_port) # Database objects self.router_table = get_router_table(router_tablename, router_read_throughput, router_write_throughput) self.storage_table = get_storage_table(storage_tablename, storage_read_throughput, storage_write_throughput) self.message_table = get_message_table(message_tablename, message_read_throughput, message_write_throughput) self.storage = Storage(self.storage_table, self.metrics) self.router = Router(self.router_table, self.metrics) self.message = Message(self.message_table, self.metrics) # Run preflight check preflight_check(self.storage, self.router) # CORS self.cors = enable_cors # Force timeout in idle seconds self.wake_timeout = wake_timeout # Setup the routers self.routers = {} self.routers["simplepush"] = SimpleRouter( self, router_conf.get("simplepush")) self.routers["webpush"] = WebPushRouter(self, None) if 'apns' in router_conf: self.routers["apns"] = APNSRouter(self, router_conf["apns"]) if 'gcm' in router_conf: self.routers["gcm"] = GCMRouter(self, router_conf["gcm"]) # Env self.env = env self.hello_timeout = hello_timeout
class APNSRouterTestCase(unittest.TestCase): def _waitfor(self, func): times = 0 while not func(): # pragma: nocover time.sleep(1) times += 1 if times > 9: break @patch('autopush.router.apns2.HTTP20Connection', spec=hyper.HTTP20Connection) @patch('hyper.tls', spec=hyper.tls) def setUp(self, mt, mc): from twisted.logger import Logger settings = AutopushSettings( hostname="localhost", statsd_host=None, ) apns_config = { 'firefox': { 'cert': 'fake.cert', 'key': 'fake.key', 'topic': 'com.example.SomeApp', 'max_connections': 2, } } self.mock_connection = mc mc.return_value = mc self.router = APNSRouter(settings, apns_config) self.mock_response = Mock() self.mock_response.status = 200 mc.get_response.return_value = self.mock_response # toss the existing connection try: self.router.apns['firefox'].connections.pop() except IndexError: # pragma nocover pass self.router.apns['firefox'].connections.append(self.mock_connection) self.router.apns['firefox'].log = Mock(spec=Logger) self.headers = { "content-encoding": "aesgcm", "encryption": "test", "encryption-key": "test" } self.notif = WebPushNotification( uaid=uuid.UUID(dummy_uaid), channel_id=uuid.UUID(dummy_chid), data="q60d6g", headers=self.headers, ttl=200, message_id=10, ) self.notif.cleanup_headers() self.router_data = dict( router_data=dict(token="connect_data", rel_channel="firefox")) def test_register(self): router_data = {"token": "connect_data"} self.router.register("uaid", router_data=router_data, app_id="firefox") eq_(router_data, {"rel_channel": "firefox", "token": "connect_data"}) def test_register_bad(self): with assert_raises(RouterException): self.router.register("uaid", router_data={}, app_id="firefox") def test_register_bad_channel(self): with assert_raises(RouterException): self.router.register("uaid", router_data={"token": "connect_data"}, app_id="unknown") @inlineCallbacks def test_connection_error(self): from hyper.http20.exceptions import ConnectionError def raiser(*args, **kwargs): raise ConnectionError("oops") self.router.apns['firefox'].connections[1].request = Mock( side_effect=raiser) with assert_raises(RouterException) as e: yield self.router.route_notification(self.notif, self.router_data) eq_(e.exception.response_body, 'APNS returned an error ' 'processing request') eq_(e.exception.status_code, 502) self.flushLoggedErrors() @inlineCallbacks def test_route_notification(self): result = yield self.router.route_notification(self.notif, self.router_data) yield self._waitfor( lambda: self.mock_connection.request.called is True) ok_(isinstance(result, RouterResponse)) ok_(self.mock_connection.request.called) body = self.mock_connection.request.call_args[1] body_json = json.loads(body['body']) ok_('chid' in body_json) # The ChannelID is a UUID4, and unpredictable. del (body_json['chid']) eq_( body_json, { "body": "q60d6g", "enc": "test", "ver": 10, "aps": { "content-available": 1, }, "enckey": "test", "con": "aesgcm", }) @inlineCallbacks def test_route_low_priority_notification(self): """low priority and empty apns_ids are not yet used, but may feature when priorty work is done.""" apns2 = self.router.apns['firefox'] exp = int(time.time() + 300) yield apns2.send("abcd0123", {}, 'apnsid', priority=False, exp=exp) yield self._waitfor( lambda: self.mock_connection.request.called is True) ok_(self.mock_connection.request.called) body = self.mock_connection.request.call_args[1] headers = body['headers'] eq_( headers, { 'apns-expiration': str(exp), 'apns-topic': 'com.example.SomeApp', 'apns-priority': '5', 'apns-id': 'apnsid' }) @inlineCallbacks def test_bad_send(self): self.mock_response.status = 400 self.mock_response.read.return_value = json.dumps({'reason': 'boo'}) with assert_raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) ok_(isinstance(ex.exception, RouterException)) eq_(ex.exception.status_code, 502) eq_(ex.exception.message, 'APNS Transmit Error 400:boo') eq_(ex.exception.response_body, 'APNS could not process your ' 'message boo') @inlineCallbacks def test_fail_send(self): def throw(*args, **kwargs): raise HTTP20Error("oops") self.router.apns['firefox'].connections[0].request.side_effect = throw with assert_raises(RouterException) as ex: yield self.router.route_notification(self.notif, self.router_data) ok_(isinstance(ex.exception, RouterException)) eq_(ex.exception.status_code, 502) eq_(ex.exception.message, "Server error") eq_(ex.exception.response_body, 'APNS returned an error processing ' 'request') self.flushLoggedErrors() def test_too_many_connections(self): rr = self.router.apns['firefox'] with assert_raises(RouterException) as ex: while True: rr._get_connection() ok_(isinstance(ex.exception, RouterException)) eq_(ex.exception.status_code, 503) eq_(ex.exception.message, "Too many APNS requests, " "increase pool from 2") eq_(ex.exception.response_body, "APNS busy, please retry") def test_amend(self): resp = {"key": "value"} expected = resp.copy() self.router.amend_endpoint_response(resp, {}) eq_(resp, expected) def test_route_crypto_key(self): headers = { "content-encoding": "aesgcm", "encryption": "test", "crypto-key": "test" } self.notif = WebPushNotification( uaid=uuid.UUID(dummy_uaid), channel_id=uuid.UUID(dummy_chid), data="q60d6g", headers=headers, ttl=200, message_id=10, ) self.notif.cleanup_headers() d = self.router.route_notification(self.notif, self.router_data) def check_results(result): ok_(isinstance(result, RouterResponse)) eq_(result.status_code, 201) eq_(result.logged_status, 200) ok_("TTL" in result.headers) ok_(self.mock_connection.called) d.addCallback(check_results) return d
def __init__( self, crypto_key=None, datadog_api_key=None, datadog_app_key=None, datadog_flush_interval=None, hostname=None, port=None, router_scheme=None, router_hostname=None, router_port=None, endpoint_scheme=None, endpoint_hostname=None, endpoint_port=None, router_conf=None, router_tablename="router", router_read_throughput=5, router_write_throughput=5, storage_tablename="storage", storage_read_throughput=5, storage_write_throughput=5, message_tablename="message", message_read_throughput=5, message_write_throughput=5, statsd_host="localhost", statsd_port=8125, resolve_hostname=False, max_data=4096, # Reflected up from UDP Router wake_timeout=0, env='development', enable_cors=False, hello_timeout=0, bear_hash_key=None, preflight_uaid="deadbeef00000000deadbeef00000000", ami_id=None, client_certs=None, msg_limit=100, debug=False, connect_timeout=0.5, ): """Initialize the Settings object Upon creation, the HTTP agent will initialize, all configured routers will be setup and started, logging will be started, and the database will have a preflight check done. """ # Use a persistent connection pool for HTTP requests. pool = HTTPConnectionPool(reactor) if not debug: pool._factory = QuietClientFactory self.agent = Agent(reactor, connectTimeout=connect_timeout, pool=pool) if not crypto_key: crypto_key = [Fernet.generate_key()] if not isinstance(crypto_key, list): crypto_key = [crypto_key] self.update(crypto_key=crypto_key) self.crypto_key = crypto_key if bear_hash_key is None: bear_hash_key = [] if not isinstance(bear_hash_key, list): bear_hash_key = [bear_hash_key] self.bear_hash_key = bear_hash_key self.max_data = max_data self.clients = {} # Setup hosts/ports/urls default_hostname = socket.gethostname() self.hostname = hostname or default_hostname if resolve_hostname: self.hostname = resolve_ip(self.hostname) # Metrics setup if datadog_api_key: self.metrics = DatadogMetrics( hostname=self.hostname, api_key=datadog_api_key, app_key=datadog_app_key, flush_interval=datadog_flush_interval, ) elif statsd_host: self.metrics = TwistedMetrics(statsd_host, statsd_port) else: self.metrics = SinkMetrics() self.port = port self.endpoint_hostname = endpoint_hostname or self.hostname self.router_hostname = router_hostname or self.hostname if router_conf is None: router_conf = {} self.router_conf = router_conf self.router_url = canonical_url(router_scheme or 'http', self.router_hostname, router_port) self.endpoint_url = canonical_url(endpoint_scheme or 'http', self.endpoint_hostname, endpoint_port) self.enable_tls_auth = client_certs is not None self.client_certs = client_certs # Database objects self.router_table = get_router_table(router_tablename, router_read_throughput, router_write_throughput) self.storage_table = get_storage_table(storage_tablename, storage_read_throughput, storage_write_throughput) self.message_table = get_rotating_message_table( message_tablename, message_read_throughput=message_read_throughput, message_write_throughput=message_write_throughput) self._message_prefix = message_tablename self.message_limit = msg_limit self.storage = Storage(self.storage_table, self.metrics) self.router = Router(self.router_table, self.metrics) # Used to determine whether a connection is out of date with current # db objects. There are three noteworty cases: # 1 "Last Month" the table requires a rollover. # 2 "This Month" the most common case. # 3 "Next Month" where the system will soon be rolling over, but with # timing, some nodes may roll over sooner. Ensuring the next month's # table is present before the switchover is the main reason for this, # just in case some nodes do switch sooner. self.create_initial_message_tables() # Run preflight check preflight_check(self.storage, self.router, preflight_uaid) # CORS self.cors = enable_cors # Force timeout in idle seconds self.wake_timeout = wake_timeout # Setup the routers self.routers = dict() self.routers["simplepush"] = SimpleRouter( self, router_conf.get("simplepush")) self.routers["webpush"] = WebPushRouter(self, None) if 'apns' in router_conf: self.routers["apns"] = APNSRouter(self, router_conf["apns"]) if 'gcm' in router_conf: self.routers["gcm"] = GCMRouter(self, router_conf["gcm"]) # Env self.env = env self.hello_timeout = hello_timeout self.ami_id = ami_id # Generate messages per legacy rules, only used for testing to # generate legacy data. self._notification_legacy = False