From 180b8fe9dc1ba2d0f7f21b129ea75d8662128497 Mon Sep 17 00:00:00 2001 From: Carina Antunes <carina.oliveira.antunes@cern.ch> Date: Thu, 10 Feb 2022 18:58:39 +0100 Subject: [PATCH] Etcd: refresh token --- notifications_routing/auditing.py | 25 ++++++++++++++++----- notifications_routing/preferences.py | 10 ++++++--- notifications_routing/router.py | 11 ++++++++-- tests/unit/test_router.py | 33 +++++++++++++++++++++++++--- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/notifications_routing/auditing.py b/notifications_routing/auditing.py index 1202dfd..ce11218 100644 --- a/notifications_routing/auditing.py +++ b/notifications_routing/auditing.py @@ -5,6 +5,7 @@ import uuid from datetime import datetime from etcd3 import Client +from etcd3.errors import ErrInvalidAuthToken from notifications_routing.config import Config @@ -22,16 +23,23 @@ def audit_notification(notification_id, value, user_id=None, key=None): logging.info("Audit disabled") return - if not key: - key = uuid.uuid4() - try: + def put(): client.put( ( f"/notifications/{notification_id}/{Config.AUDIT_ID}" f"/{'target_users/' + user_id + '/' if user_id else ''}{key}" ), - json.dumps({"date": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), **value}), + json.dumps({"date": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), **value}, default=str), ) + + if not key: + key = uuid.uuid4() + try: + put() + except ErrInvalidAuthToken: + logging.debug("refresh etcd token") + client.auth(username=Config.ETCD_USER, password=Config.ETCD_PASSWORD) + put() except Exception: logging.exception("Error auditing to etcd3:") @@ -42,7 +50,7 @@ def get_audit_notification(notification_id, key, user_id=None): logging.info("Audit disabled") return - try: + def get(): kvs = client.range( ( f"/notifications/{notification_id}/{Config.AUDIT_ID}" @@ -52,6 +60,13 @@ def get_audit_notification(notification_id, key, user_id=None): if kvs: return json.loads(kvs[0].value) return None + + try: + return get() + except ErrInvalidAuthToken: + logging.debug("refresh etcd token") + client.auth(username=Config.ETCD_USER, password=Config.ETCD_PASSWORD) + return get() except Exception: logging.exception("Error getting from etcd") return None diff --git a/notifications_routing/preferences.py b/notifications_routing/preferences.py index c1845c7..0fc4f60 100644 --- a/notifications_routing/preferences.py +++ b/notifications_routing/preferences.py @@ -26,12 +26,13 @@ class TargetPreference: Holds method and devices together. """ - def __init__(self, method, devices, scheduledTime, scheduledDay): + def __init__(self, method, devices, scheduledTime, scheduledDay, id): """Initialize the Target Preference.""" self.method = method self.devices = devices self.scheduledTime = scheduledTime self.scheduledDay = scheduledDay + self.id = id def get_delivery_methods_and_targets( @@ -64,7 +65,9 @@ def get_delivery_methods_and_targets( logging.debug("\tUser Preferences devices: %s", device.name) delivery_methods_and_targets.add( - TargetPreference(preference.type, preference.devices, preference.scheduledTime, preference.scheduledDay) + TargetPreference( + preference.type, preference.devices, preference.scheduledTime, preference.scheduledDay, preference.id + ) ) return delivery_methods_and_targets @@ -121,7 +124,7 @@ def apply_user_preferences( ) audit_notification( message[OutputMessageKeys.ID], - {"event": "Preference added to feed", "method": preference.method}, + {"event": "Preference added to feed", "method": preference.method, "preference": preference.id}, user_id=user[data_source.EMAIL], ) continue @@ -137,6 +140,7 @@ def apply_user_preferences( "device": device.type, "subtype": device.subtype if hasattr(device, "subtype") else "", "token": device.token, + "preference": preference.id, }, user_id=user[data_source.EMAIL], ) diff --git a/notifications_routing/router.py b/notifications_routing/router.py index ff21379..9b3f025 100644 --- a/notifications_routing/router.py +++ b/notifications_routing/router.py @@ -101,10 +101,10 @@ class Router(megabus.Listener): try: system_user = self.data_source.get_system_user(user[self.data_source.USERNAME]) users.append(system_user) - unique_usernames.append(self.data_source.USERNAME) + unique_usernames.append(user[self.data_source.USERNAME]) except NotFoundDataSourceError: users.append(user) - unique_usernames.append(self.data_source.USERNAME) + unique_usernames.append(user[self.data_source.USERNAME]) logging.debug("channel %s final users %s", channel_id, users) @@ -220,6 +220,13 @@ class Router(megabus.Listener): # sending to devices per delivery method extracted from preferences apply_user_preferences(self.publisher, self.data_source, delivery_methods_and_targets, message, user) + if not delivery_methods_and_targets: + audit_notification( + message[OutputMessageKeys.ID], + {"event": "No preferences, skipped user"}, + user_id=user[self.data_source.EMAIL], + ) + # Resource not found in the DB, try to fallback to sending by email and continue for other users # Dont catch all exceptions - could cause the notification to be successfully processed and be lost # and not be retried diff --git a/tests/unit/test_router.py b/tests/unit/test_router.py index ee688d3..efba0b0 100644 --- a/tests/unit/test_router.py +++ b/tests/unit/test_router.py @@ -164,7 +164,9 @@ def device(): @pytest.fixture(scope="function") def target_preference(preference): """Target Preference object.""" - return TargetPreference(preference.type, preference.devices, preference.userId, preference.scheduledDay) + return TargetPreference( + preference.type, preference.devices, preference.userId, preference.scheduledDay, preference.id + ) def test_get_channel_users_one_group_without_no_system_users( @@ -197,16 +199,41 @@ def test_get_channel_users_multiple_groups_with_overlapping_users( router_mock, system_user_1, system_user_2, system_user_3, group_user_1, group_user_2, group_user_3 ): """Test get channel users for multiple groups with overlapping users.""" - router_mock.data_source.get_channel_users.return_value = [system_user_1, system_user_2, system_user_3] + router_mock.data_source.get_channel_users.return_value = [system_user_2, system_user_3] router_mock.data_source.get_channel_groups.return_value = [ {"group_id": "186d8dfc-2774-43a8-91b5-a887fcb6ba4a"}, {"group_id": "ffff6789-2774-43a8-91b5-a887fcb6ba4a"}, ] router_mock.data_source.get_group_users.side_effect = [[group_user_1, group_user_2], [group_user_1, group_user_3]] + router_mock.data_source.get_system_user.side_effect = [system_user_1] + assert router_mock.get_channel_users("c3ccc15b-298f-4dc7-877f-2c8970331caf") == [ - system_user_1, system_user_2, system_user_3, + system_user_1, + ] + + +def test_get_channel_no_users_multiple_groups_with_overlapping_users( + router_mock, system_user_1, group_user_1, group_user_2, group_user_3 +): + """Test get channel users w/out users for multiple groups with overlapping users.""" + router_mock.data_source.get_channel_users.return_value = [] + router_mock.data_source.get_channel_groups.return_value = [ + {"group_id": "186d8dfc-2774-43a8-91b5-a887fcb6ba4a"}, + {"group_id": "ffff6789-2774-43a8-91b5-a887fcb6ba4a"}, + ] + router_mock.data_source.get_group_users.side_effect = [[group_user_1, group_user_2], [group_user_1, group_user_3]] + router_mock.data_source.get_system_user.side_effect = [ + system_user_1, + NotFoundDataSourceError(""), + NotFoundDataSourceError(""), + ] + + assert router_mock.get_channel_users("c3ccc15b-298f-4dc7-877f-2c8970331caf") == [ + system_user_1, + group_user_2, + group_user_3, ] -- GitLab