From 7542709455a811e4026824e4bf2606315f858e47 Mon Sep 17 00:00:00 2001 From: Toby Lorne Date: Tue, 27 Oct 2020 14:44:04 +0000 Subject: [PATCH 1/2] clients: cbc_proxy sends message_type When we ask the CBC Proxy to send a message, we should specify that we want to send a real message, when we want a real message We will do this by specifying the message_type which can have 4 types, 3 of which represent a real message: | Name | Effect | | ------ | ------------------------ | | alert | Create an alert | | update | Update an existing alert | | cancel | Cancel an existing alert | | test | Send a link test | We will use message_type to represent the table above Signed-off-by: Toby Lorne Co-authored-by: Richard Co-authored-by: Pea --- app/clients/cbc_proxy.py | 28 ++++++++++ tests/app/clients/test_cbc_proxy.py | 87 +++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/app/clients/cbc_proxy.py b/app/clients/cbc_proxy.py index 57296fa03..d6f2716c9 100644 --- a/app/clients/cbc_proxy.py +++ b/app/clients/cbc_proxy.py @@ -32,6 +32,12 @@ class CBCProxyNoopClient: ): pass + def send_link_test( + self, + identifier, + ): + pass + def create_and_send_broadcast( self, identifier, headline, description, areas @@ -83,11 +89,33 @@ class CBCProxyClient: if 'FunctionError' in result: raise Exception('Function exited with unhandled exception') + def send_link_test( + self, + identifier, + ): + payload_bytes = bytes(json.dumps({ + 'message_type': 'test', + 'identifier': identifier, + }), encoding='utf8') + + result = self._lambda_client.invoke( + FunctionName='bt-ee-1-proxy', + InvocationType='RequestResponse', + Payload=payload_bytes, + ) + + if result['StatusCode'] > 299: + raise Exception('Could not invoke lambda') + + if 'FunctionError' in result: + raise Exception('Function exited with unhandled exception') + def create_and_send_broadcast( self, identifier, headline, description, areas, ): payload_bytes = bytes(json.dumps({ + 'message_type': 'alert', 'identifier': identifier, 'headline': headline, 'description': description, diff --git a/tests/app/clients/test_cbc_proxy.py b/tests/app/clients/test_cbc_proxy.py index 8368180c7..981c50ceb 100644 --- a/tests/app/clients/test_cbc_proxy.py +++ b/tests/app/clients/test_cbc_proxy.py @@ -74,6 +74,7 @@ def test_cbc_proxy_create_and_send_invokes_function(mocker, cbc_proxy): payload = json.loads(payload_bytes) assert payload['identifier'] == identifier + assert payload['message_type'] == 'alert' assert payload['headline'] == headline assert payload['description'] == description assert payload['areas'] == areas @@ -251,3 +252,89 @@ def test_cbc_proxy_send_canary_handles_function_error(mocker, cbc_proxy): InvocationType='RequestResponse', Payload=mocker.ANY, ) + + +def test_cbc_proxy_send_link_test_invokes_function(mocker, cbc_proxy): + identifier = str(uuid.uuid4()) + + ld_client_mock = mocker.patch.object( + cbc_proxy, + '_lambda_client', + create=True, + ) + + ld_client_mock.invoke.return_value = { + 'StatusCode': 200, + } + + cbc_proxy.send_link_test( + identifier=identifier, + ) + + ld_client_mock.invoke.assert_called_once_with( + FunctionName='bt-ee-1-proxy', + InvocationType='RequestResponse', + Payload=mocker.ANY, + ) + + kwargs = ld_client_mock.invoke.mock_calls[0][-1] + payload_bytes = kwargs['Payload'] + payload = json.loads(payload_bytes) + + assert payload['identifier'] == identifier + assert payload['message_type'] == 'test' + + +def test_cbc_proxy_send_link_test_handles_invoke_error(mocker, cbc_proxy): + identifier = str(uuid.uuid4()) + + ld_client_mock = mocker.patch.object( + cbc_proxy, + '_lambda_client', + create=True, + ) + + ld_client_mock.invoke.return_value = { + 'StatusCode': 400, + } + + with pytest.raises(Exception) as e: + cbc_proxy.send_link_test( + identifier=identifier, + ) + + assert e.match('Function exited with unhandled exception') + + ld_client_mock.invoke.assert_called_once_with( + FunctionName='bt-ee-1-proxy', + InvocationType='RequestResponse', + Payload=mocker.ANY, + ) + + +def test_cbc_proxy_send_link_test_handles_function_error(mocker, cbc_proxy): + identifier = str(uuid.uuid4()) + + ld_client_mock = mocker.patch.object( + cbc_proxy, + '_lambda_client', + create=True, + ) + + ld_client_mock.invoke.return_value = { + 'StatusCode': 200, + 'FunctionError': 'something', + } + + with pytest.raises(Exception) as e: + cbc_proxy.send_link_test( + identifier=identifier, + ) + + assert e.match('Could not invoke lambda') + + ld_client_mock.invoke.assert_called_once_with( + FunctionName='bt-ee-1-proxy', + InvocationType='RequestResponse', + Payload=mocker.ANY, + ) From dda71bf6853342660049d24f7ff791f1ccf11542 Mon Sep 17 00:00:00 2001 From: Toby Lorne Date: Tue, 27 Oct 2020 15:39:28 +0000 Subject: [PATCH 2/2] celery: add task for triggering link tests We want to periodically kick off some link tests, so that: - we are periodically communicating with the CBC Proxies - each CBC Proxy is periodically communicating with its CBC This simulation of traffic to the CBC will give us advance warning if something is going to break, or is broken, before someone tries to send a real live message Signed-off-by: Toby Lorne Co-authored-by: Richard Co-authored-by: Pea --- app/celery/scheduled_tasks.py | 22 ++++++++++++++++++++++ app/config.py | 5 +++++ tests/app/celery/test_scheduled_tasks.py | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index ccac190ac..eaf898ddf 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -301,3 +301,25 @@ def send_canary_to_cbc_proxy(): message = f"Sending a canary message to CBC proxy with ID {identifier}" current_app.logger.info(message) cbc_proxy_client.send_canary(identifier) + + +@notify_celery.task(name='trigger-link-tests') +def trigger_link_tests(): + """ + Currently we only have one hardcoded CBC Proxy, which corresponds to one + CBC, and so currently we do not specify the CBC Proxy name + + In future we will have multiple CBC proxies, each proxy corresponding to + one MNO's CBC + + This task should invoke other tasks which do the actual link tests, eg: + for cbc_name in app.config.ENABLED_CBCS: + send_link_test_for_cbc(cbc_name) + + Alternatively this task could be configured to be a Celery group + """ + for _ in range(1): + identifier = str(uuid.uuid4()) + message = f"Sending a link test to CBC proxy with ID {identifier}" + current_app.logger.info(message) + cbc_proxy_client.send_link_test(identifier) diff --git a/app/config.py b/app/config.py index 1ad9bcea7..f689e0201 100644 --- a/app/config.py +++ b/app/config.py @@ -308,6 +308,11 @@ class Config(object): 'schedule': timedelta(minutes=5), 'options': {'queue': QueueNames.PERIODIC} }, + 'trigger-link-tests': { + 'task': 'trigger-link-tests', + 'schedule': timedelta(minutes=5), + 'options': {'queue': QueueNames.PERIODIC} + }, } CELERY_QUEUES = [] diff --git a/tests/app/celery/test_scheduled_tasks.py b/tests/app/celery/test_scheduled_tasks.py index 2c6bf3750..c76632c2e 100644 --- a/tests/app/celery/test_scheduled_tasks.py +++ b/tests/app/celery/test_scheduled_tasks.py @@ -575,3 +575,22 @@ def test_send_canary_to_cbc_proxy_invokes_cbc_proxy_client( uuid.UUID(identifier) except BaseException: pytest.fail(f"{identifier} is not a valid uuid") + + +def test_trigger_link_tests_invokes_cbc_proxy_client( + mocker, +): + mock_send_link_test = mocker.patch( + 'app.cbc_proxy_client.send_link_test', + ) + + scheduled_tasks.trigger_link_tests() + + mock_send_link_test.assert_called + # the 0th argument of the call to send_link_test + identifier = mock_send_link_test.mock_calls[0][1][0] + + try: + uuid.UUID(identifier) + except BaseException: + pytest.fail(f"{identifier} is not a valid uuid")