Files
notifications-api/app/clients/cbc_proxy.py
T

300 lines
9.0 KiB
Python
Raw Normal View History

import json
2021-07-19 14:38:27 +01:00
import uuid
from abc import ABC, abstractmethod
import boto3
2021-02-12 19:48:32 +00:00
import botocore
2020-11-17 12:35:22 +00:00
from flask import current_app
2020-12-24 15:09:41 +00:00
from notifications_utils.template import non_gsm_characters
from sqlalchemy.schema import Sequence
from app.config import BroadcastProvider
2020-12-18 17:39:35 +00:00
from app.utils import DATETIME_FORMAT, format_sequential_number
2020-10-29 11:12:28 +00:00
2020-10-22 12:19:25 +01:00
# The variable names in this file have specific meaning in a CAP message
#
# identifier is a unique field for each CAP message
#
# headline is a field which we are not sure if we will use
#
# description is the body of the message
# areas is a list of dicts, with the following items
# * description is a string which populates the areaDesc field
# * polygon is a list of lat/long pairs
2020-10-22 12:19:25 +01:00
#
2020-11-17 12:35:22 +00:00
# previous_provider_messages is a list of previous events (models.py::BroadcastProviderMessage)
# ie a Cancel message would have a unique event but have the event of
# the preceeding Alert message in the previous_provider_messages field
2020-10-22 12:19:25 +01:00
class CBCProxyRetryableException(Exception):
2020-10-29 10:22:50 +00:00
pass
2020-11-17 12:35:22 +00:00
class CBCProxyClient:
_lambda_client = None
2020-10-20 11:18:46 +01:00
def init_app(self, app):
if app.config.get('CBC_PROXY_ENABLED'):
2020-11-17 12:35:22 +00:00
self._lambda_client = boto3.client(
'lambda',
region_name='eu-west-2',
aws_access_key_id=app.config['CBC_PROXY_AWS_ACCESS_KEY_ID'],
aws_secret_access_key=app.config['CBC_PROXY_AWS_SECRET_ACCESS_KEY'],
)
def get_proxy(self, provider):
proxy_classes = {
BroadcastProvider.EE: CBCProxyEE,
2021-01-14 11:44:46 +00:00
BroadcastProvider.THREE: CBCProxyThree,
2021-01-26 11:11:44 +00:00
BroadcastProvider.O2: CBCProxyO2,
BroadcastProvider.VODAFONE: CBCProxyVodafone,
2020-11-17 12:35:22 +00:00
}
return proxy_classes[provider](self._lambda_client)
class CBCProxyClientBase(ABC):
2021-01-11 13:37:45 +00:00
@property
@abstractmethod
def lambda_name(self):
pass
2020-11-17 12:35:22 +00:00
2021-01-11 13:46:47 +00:00
@property
@abstractmethod
def failover_lambda_name(self):
pass
@property
@abstractmethod
def LANGUAGE_ENGLISH(self):
pass
@property
@abstractmethod
def LANGUAGE_WELSH(self):
pass
2020-12-24 15:09:41 +00:00
2020-11-17 12:35:22 +00:00
def __init__(self, lambda_client):
self._lambda_client = lambda_client
2020-10-20 11:18:46 +01:00
def send_link_test(self):
2021-07-19 14:38:27 +01:00
self._send_link_test(self.lambda_name)
self._send_link_test(self.failover_lambda_name)
def _send_link_test(
self,
lambda_name,
): pass
2020-10-27 14:44:04 +00:00
2020-10-20 11:18:46 +01:00
def create_and_send_broadcast(
2021-01-29 11:54:12 +00:00
self, identifier, headline, description, areas, sent, expires, channel, message_number=None
2020-10-20 11:18:46 +01:00
):
2020-12-09 11:13:50 +00:00
pass
2020-10-20 11:18:46 +01:00
# We have not implementated updating a broadcast
def update_and_send_broadcast(
self,
identifier, previous_provider_messages, headline, description, areas,
2021-01-29 11:54:12 +00:00
sent, expires, channel, message_number=None
2020-10-20 11:18:46 +01:00
):
pass
def cancel_broadcast(
self,
identifier, previous_provider_messages, headline, description, areas,
2020-12-11 18:52:54 +00:00
sent, expires, message_number=None
2020-10-20 11:18:46 +01:00
):
pass
2021-01-11 13:02:14 +00:00
def _invoke_lambda_with_failover(self, payload):
2021-01-14 10:45:49 +00:00
result = self._invoke_lambda(self.lambda_name, payload)
2020-10-26 17:14:08 +00:00
if not result:
2021-01-14 10:45:49 +00:00
failover_result = self._invoke_lambda(self.failover_lambda_name, payload)
if not failover_result:
raise CBCProxyRetryableException(
f'Lambda failed for both {self.lambda_name} and {self.failover_lambda_name}'
)
2021-01-11 13:02:14 +00:00
return result
2021-01-14 10:45:49 +00:00
def _invoke_lambda(self, lambda_name, payload):
payload_bytes = bytes(json.dumps(payload), encoding='utf8')
2021-02-12 19:48:32 +00:00
try:
2021-07-19 14:34:35 +01:00
current_app.logger.info(
2021-07-20 16:06:48 +01:00
f"Calling lambda {lambda_name} with payload {str(payload)[:1000]}"
2021-07-19 14:34:35 +01:00
)
2021-02-12 19:48:32 +00:00
result = self._lambda_client.invoke(
FunctionName=lambda_name,
InvocationType='RequestResponse',
Payload=payload_bytes,
)
2021-02-16 09:03:38 +00:00
except botocore.exceptions.ClientError:
2021-02-12 19:48:32 +00:00
current_app.logger.exception(f'Boto ClientError calling lambda {lambda_name}')
success = False
return success
2020-10-26 17:14:08 +00:00
if result['StatusCode'] > 299:
current_app.logger.info(
2021-01-15 16:48:04 +00:00
f"Error calling lambda {lambda_name} with status code { result['StatusCode']}, {result.get('Payload')}"
)
success = False
2020-10-26 17:14:08 +00:00
elif 'FunctionError' in result:
current_app.logger.info(
2021-02-18 11:51:38 +00:00
f"Error calling lambda {lambda_name} with function error { result['Payload'].read() }"
)
success = False
else:
success = True
return success
2020-10-29 10:22:50 +00:00
2020-12-24 15:09:41 +00:00
def infer_language_from(self, content):
if non_gsm_characters(content):
return self.LANGUAGE_WELSH
return self.LANGUAGE_ENGLISH
2020-11-17 12:35:22 +00:00
2021-02-11 16:56:15 +00:00
class CBCProxyOne2ManyClient(CBCProxyClientBase):
LANGUAGE_ENGLISH = 'en-GB'
LANGUAGE_WELSH = 'cy-GB'
2021-07-19 14:38:27 +01:00
def _send_link_test(
2021-02-11 16:56:15 +00:00
self,
2021-07-19 14:38:27 +01:00
lambda_name,
2021-02-11 16:56:15 +00:00
):
"""
link test - open up a connection to a specific provider, and send them an xml payload with a <msgType> of
test.
"""
payload = {
'message_type': 'test',
2021-07-19 14:38:27 +01:00
'identifier': str(uuid.uuid4()),
2021-02-11 16:56:15 +00:00
'message_format': 'cap'
}
2021-07-19 14:38:27 +01:00
self._invoke_lambda(lambda_name=lambda_name, payload=payload)
2021-02-11 16:56:15 +00:00
def create_and_send_broadcast(
self, identifier, headline, description, areas, sent, expires, channel, message_number=None
):
payload = {
'message_type': 'alert',
'identifier': identifier,
'message_format': 'cap',
'headline': headline,
'description': description,
'areas': areas,
'sent': sent,
'expires': expires,
'language': self.infer_language_from(description),
'channel': channel,
}
self._invoke_lambda_with_failover(payload=payload)
def cancel_broadcast(
self,
identifier, previous_provider_messages,
sent, message_number=None
):
payload = {
'message_type': 'cancel',
'identifier': identifier,
'message_format': 'cap',
"references": [
{
"message_id": str(message.id),
"sent": message.created_at.strftime(DATETIME_FORMAT)
} for message in previous_provider_messages
],
'sent': sent,
}
self._invoke_lambda_with_failover(payload=payload)
class CBCProxyEE(CBCProxyOne2ManyClient):
2021-01-25 14:28:40 +00:00
lambda_name = 'ee-1-proxy'
failover_lambda_name = 'ee-2-proxy'
2020-10-26 17:14:08 +00:00
2021-02-11 16:56:15 +00:00
class CBCProxyThree(CBCProxyOne2ManyClient):
2021-01-14 11:44:46 +00:00
lambda_name = 'three-1-proxy'
failover_lambda_name = 'three-2-proxy'
2021-02-11 16:56:15 +00:00
class CBCProxyO2(CBCProxyOne2ManyClient):
2021-01-26 11:11:44 +00:00
lambda_name = 'o2-1-proxy'
failover_lambda_name = 'o2-2-proxy'
class CBCProxyVodafone(CBCProxyClientBase):
2020-12-04 17:29:12 +00:00
lambda_name = 'vodafone-1-proxy'
2021-01-11 13:46:47 +00:00
failover_lambda_name = 'vodafone-2-proxy'
2020-12-09 11:13:50 +00:00
2020-12-24 15:09:41 +00:00
LANGUAGE_ENGLISH = 'English'
LANGUAGE_WELSH = 'Welsh'
2021-07-19 14:38:27 +01:00
def _send_link_test(
2020-12-09 11:13:50 +00:00
self,
2021-07-19 14:38:27 +01:00
lambda_name,
2020-12-09 11:13:50 +00:00
):
"""
link test - open up a connection to a specific provider, and send them an xml payload with a <msgType> of
test.
"""
from app import db
sequence = Sequence('broadcast_provider_message_number_seq')
sequential_number = db.session.connection().execute(sequence)
formatted_seq_number = format_sequential_number(sequential_number)
2020-12-09 11:13:50 +00:00
payload = {
'message_type': 'test',
2021-07-19 14:38:27 +01:00
'identifier': str(uuid.uuid4()),
'message_number': formatted_seq_number,
2020-12-09 11:13:50 +00:00
'message_format': 'ibag'
}
2021-07-19 14:38:27 +01:00
self._invoke_lambda(lambda_name=lambda_name, payload=payload)
2020-12-09 11:13:50 +00:00
def create_and_send_broadcast(
2021-01-29 11:54:12 +00:00
self, identifier, message_number, headline, description, areas, sent, expires, channel
2020-12-09 11:13:50 +00:00
):
payload = {
'message_type': 'alert',
'identifier': identifier,
'message_number': message_number,
'message_format': 'ibag',
'headline': headline,
'description': description,
'areas': areas,
'sent': sent,
'expires': expires,
2020-12-24 15:09:41 +00:00
'language': self.infer_language_from(description),
2021-01-29 11:54:12 +00:00
'channel': channel,
2020-12-09 11:13:50 +00:00
}
2021-01-11 13:02:14 +00:00
self._invoke_lambda_with_failover(payload=payload)
2020-12-11 18:52:54 +00:00
def cancel_broadcast(
self, identifier, previous_provider_messages, sent, message_number
):
2020-12-14 17:52:08 +00:00
2020-12-11 18:52:54 +00:00
payload = {
'message_type': 'cancel',
'identifier': identifier,
'message_number': message_number,
'message_format': 'ibag',
"references": [
{
"message_id": str(message.id),
2020-12-14 17:52:08 +00:00
"message_number": format_sequential_number(message.message_number),
"sent": message.created_at.strftime(DATETIME_FORMAT)
2020-12-11 18:52:54 +00:00
} for message in previous_provider_messages
],
'sent': sent,
}
2021-01-11 13:02:14 +00:00
self._invoke_lambda_with_failover(payload=payload)