diff --git a/autopush/router/fcm_v1.py b/autopush/router/fcm_v1.py index 32e98767..75f37942 100644 --- a/autopush/router/fcm_v1.py +++ b/autopush/router/fcm_v1.py @@ -8,7 +8,11 @@ from autopush.metrics import make_tags from autopush.router.interface import RouterResponse from autopush.router.fcm import FCMRouter -from autopush.router.fcmv1client import (FCMv1, FCMAuthenticationError) +from autopush.router.fcmv1client import ( + FCMv1, + FCMAuthenticationError, + FCMNotFoundError +) from autopush.types import JSONDict # noqa @@ -134,6 +138,17 @@ def _process_error(self, failure): raise RouterException("Server error", status_code=502, errno=902, log_exception=False) + if isinstance(err, FCMNotFoundError): + self.log.debug("FCM Recipient not found: %s" % err) + self.metrics.increment("notification.bridge.error", + tags=make_tags( + self._base_tags, + reason="recpient_gone" + )) + raise RouterException("FCM Recipient no longer available", + status_code=410, + errno=106, + log_exception=False) if isinstance(err, RouterException): self.log.warn("FCM Error: {}".format(err)) self.metrics.increment("notification.bridge.error", diff --git a/autopush/router/fcmv1client.py b/autopush/router/fcmv1client.py index b8c435a8..45d7ca65 100644 --- a/autopush/router/fcmv1client.py +++ b/autopush/router/fcmv1client.py @@ -12,6 +12,8 @@ class FCMAuthenticationError(Exception): pass +class FCMNotFoundError(Exception): + pass class Result(object): @@ -52,11 +54,13 @@ def parse_response(self, content): return self try: data = json.loads(content) - if self.code in (400, 403, 404) or data.get('error'): + if self.code in (400, 403, 404, 410) or data.get('error'): # Having a hard time finding information about how some # things are handled in FCM, e.g. retransmit requests. # For now, catalog them as errors and provide back-pressure. err = data.get("error") + if err.get("status") == "NOT_FOUND": + raise FCMNotFoundError("FCM recipient no longer available") raise RouterException("{}: {}".format(err.get("status"), err.get("message"))) if "name" in data: @@ -116,7 +120,8 @@ def process(self, response, payload=None): def error(self, failure): if isinstance(failure.value, - (FCMAuthenticationError, TimeoutError, ConnectError)): + (FCMAuthenticationError, FCMNotFoundError, + TimeoutError, ConnectError)): raise failure.value self.logger.error("FCMv1Client failure: {}".format(failure.value)) raise RouterException("Server error: {}".format(failure.value)) diff --git a/autopush/tests/test_router.py b/autopush/tests/test_router.py index b55e6bf6..76390b33 100644 --- a/autopush/tests/test_router.py +++ b/autopush/tests/test_router.py @@ -957,7 +957,7 @@ def check_results(result): d.addCallback(check_results) return d - def test_router_notification_gcm_auth_error(self): + def test_router_notification_fcm_auth_error(self): self.response.code = 401 self._set_content() d = self.router.route_notification(self.notif, self.router_data) @@ -967,7 +967,24 @@ def check_results(fail): d.addBoth(check_results) return d - def test_router_notification_gcm_other_error(self): + def test_router_notification_fcm_not_found_error(self): + self.response.code = 410 + self._set_content(json.dumps( + {"error": + {"code": 410, + "status": "NOT_FOUND", + "message": "Requested entity was not found." + } + })) + d = self.router.route_notification(self.notif, self.router_data) + + def check_results(fail): + self._check_error_call(fail.value, 410, + "FCM Recipient no longer available", 106) + d.addBoth(check_results) + return d + + def test_router_notification_fcm_other_error(self): self._m_request.errback(Failure(Exception)) d = self.router.route_notification(self.notif, self.router_data) diff --git a/requirements.txt b/requirements.txt index d9de25b9..c8e87978 100644 --- a/requirements.txt +++ b/requirements.txt @@ -70,7 +70,7 @@ service-identity==18.1.0 simplejson==3.16.0 six==1.12.0 # via autobahn, automat, cryptography, firebase-admin, google-api-core, google-api-python-client, google-auth, google-resumable-media, grpcio, marshmallow-polyfield, oauth2client, protobuf, pyhamcrest, pyopenssl, python-dateutil, python-jose, treq, txaio treq==18.6.0 -twisted[tls]==19.2.1 +twisted[tls]==19.7.0 txaio==18.8.1 # via autobahn typing==3.7.4 ua-parser==0.8.0