Compare commits

...

9 Commits

Author SHA1 Message Date
Pea Tyczynska
517688c5f4 Add setter dao methods for requested and allowed broadcast areas 2021-04-14 10:59:00 +01:00
Pea Tyczynska
1cae8a620e Add columns for requested and allowed broadcast areas for a service
We want to restrict where broadcast services can broadcast to.

Hence we are creating columns on service_broadcast_settings table
to store the lists of allowed_broadcast_areas and
requested_allowed_broadcast_areas.
2021-04-14 10:58:25 +01:00
Leo Hemsted
4a2e47b118 Merge pull request #3202 from alphagov/env-fix
fix environment var checking
2021-04-08 13:44:28 +01:00
Leo Hemsted
4a5b1c23bd only send zendesk P1 for alerts
we don't need to be re-notified when someone clicks cancel
2021-04-08 12:22:18 +01:00
Leo Hemsted
9bd8c0239c look for 'live', not 'production'
config['NOTIFY_ENVIRONMENT'] is hardcoded to `'live'` in the Live config
class. The values as seen on the environment which we send real messages
from:

```
>>> json.loads(os.environ['VCAP_APPLICATION'])['space_name']  # what cloudfoundry sets
'production'
>>> os.environ['NOTIFY_ENVIRONMENT']  # we set this from cloudfoundry
'production'
>>> current_app.config['NOTIFY_ENVIRONMENT']  # hardcoded in the Live config
'live'
>>> current_app.config['NOTIFICATION_QUEUE_PREFIX']  # pulled from env var of same name
'live'
>>> current_app.config['ENV']  # this is an unrelated flask variable
'production'
```
2021-04-08 12:17:22 +01:00
David McDonald
dea5828d0e Merge pull request #3198 from alphagov/training-mode-preview
Don't send real broadcasts for preview training mode
2021-04-07 11:40:11 +01:00
David McDonald
42b3f13538 Don't send real broadcasts for preview training mode
We previously allowed MNOs to approve a broadcast themselves in training
mode and have it go out to their integration environment as per
https://github.com/alphagov/notifications-api/pull/3114

However, we want to remove this use case as it means we have to support
configuration for training mode services to do things like pick a
channel and send out alerts which we definteily don't want to do in
production.

By making this change, we reduce the chance of a single bug meaning an
alert will go out in prod that shouldn't.

Note, will also make it harder for development environment testing but I
think it is still worth it as https://www.pivotaltracker.com/story/show/177584959
will make it much harder in our code to allow some environments to send
alerts whilst in training mode.
2021-04-06 14:21:57 +01:00
Leo Hemsted
df97b28c57 Merge pull request #3191 from alphagov/p1-broadcast
send a p1 when a broadcast goes out
2021-04-06 13:40:04 +01:00
Leo Hemsted
df393e36c5 send a p1 when a broadcast goes out on production
it's important to keep tabs on when these things leave our system.
Sending a zendesk ticket that triggers a P1 is probably our simplest way
of notifying the team when this happens (it's what we do with out of
hours emergencies on the admin app too). We don't have any direct
pagerduty integrations from the api app, but we already have the zendesk
client hooked up.

After broadcasts go live, we may want to change this to a P2 (but even
then, there's arguments for keeping it P1 to start with I think).

Don't cause a P1 if it goes out on staging as that might be MNOs testing.
2021-04-06 11:32:19 +01:00
6 changed files with 140 additions and 2 deletions

View File

@@ -214,7 +214,7 @@ def _create_broadcast_event(broadcast_message):
dao_save_object(event)
if not broadcast_message.stubbed or current_app.config['NOTIFY_ENVIRONMENT'] in ['preview', 'development']:
if not broadcast_message.stubbed:
send_broadcast_event.apply_async(
kwargs={'broadcast_event_id': str(event.id)},
queue=QueueNames.BROADCASTS

View File

@@ -5,7 +5,7 @@ from flask import current_app
from notifications_utils.statsd_decorators import statsd
from sqlalchemy.schema import Sequence
from app import cbc_proxy_client, db, notify_celery
from app import cbc_proxy_client, db, notify_celery, zendesk_client
from app.clients.cbc_proxy import (
CBCProxyFatalException,
CBCProxyRetryableException,
@@ -111,6 +111,33 @@ def send_broadcast_event(broadcast_event_id):
return
broadcast_event = dao_get_broadcast_event_by_id(broadcast_event_id)
if (
current_app.config['NOTIFY_ENVIRONMENT'] == 'live' and
broadcast_event.message_type == BroadcastEventMessageType.ALERT
):
broadcast_message = broadcast_event.broadcast_message
# raise a P1 to alert team that broadcast is going out.
message = '\n'.join([
'Broadcast Sent',
'',
f'https://www.notifications.service.gov.uk/services/{broadcast_message.service_id}/current-alerts/{broadcast_message.id}', # noqa
'',
f'This broacast has been sent on channel {broadcast_message.service.broadcast_channel}.',
f'This broadcast is targeted at areas {broadcast_message.areas.get("areas")}.',
''
f'This broadcast\'s content starts "{broadcast_message.content[:100]}"'
'',
'If this alert is not expected refer to the runbook for instructions.',
'https://docs.google.com/document/d/1J99yOlfp4nQz6et0w5oJVqi-KywtIXkxrEIyq_g2XUs',
])
zendesk_client.create_ticket(
subject="Live broadcast sent",
message=message,
ticket_type=zendesk_client.TYPE_INCIDENT,
p1=True,
)
for provider in broadcast_event.service.get_available_broadcast_providers():
send_broadcast_provider_message.apply_async(
kwargs={'broadcast_event_id': broadcast_event_id, 'provider': provider},

View File

@@ -75,3 +75,13 @@ def insert_or_update_service_broadcast_settings(service, channel, provider_restr
service.service_broadcast_settings.channel = channel
service.service_broadcast_settings.provider = provider_restriction
db.session.add(service.service_broadcast_settings)
def insert_or_update_requested_allowed_broadcast_areas(service, requested_allowed_broadcast_areas):
service.service_broadcast_settings.requested_allowed_broadcast_areas = requested_allowed_broadcast_areas
db.session.add(service.service_broadcast_settings)
def insert_or_update_allowed_broadcast_areas(service):
service.service_broadcast_settings.allowed_broadcast_areas = service.service_broadcast_settings.requested_allowed_broadcast_areas # noqa
db.session.add(service.service_broadcast_settings)

View File

@@ -2557,6 +2557,9 @@ class ServiceBroadcastSettings(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
allowed_broadcast_areas = db.Column(JSONB(none_as_null=True), nullable=False, default=list)
requested_allowed_broadcast_areas = db.Column(JSONB(none_as_null=True), nullable=True)
class BroadcastChannelTypes(db.Model):
__tablename__ = 'broadcast_channel_types'

View File

@@ -0,0 +1,31 @@
"""
Revision ID: 0351_allowed_broadcast_areas
Revises: 0350_update_rates
Create Date: 2021-04-09 12:51:17.158043
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = '0351_allowed_broadcast_areas'
down_revision = '0350_update_rates'
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('service_broadcast_settings', sa.Column(
'allowed_broadcast_areas', postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=True)
)
op.add_column('service_broadcast_settings', sa.Column(
'requested_allowed_broadcast_areas', postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), nullable=True)
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('service_broadcast_settings', 'requested_allowed_broadcast_areas')
op.drop_column('service_broadcast_settings', 'allowed_broadcast_areas')
# ### end Alembic commands ###

View File

@@ -36,6 +36,7 @@ def test_send_broadcast_event_queues_up_for_active_providers(mocker, notify_api,
template = create_template(sample_broadcast_service, BROADCAST_TYPE)
broadcast_message = create_broadcast_message(template, status=BroadcastStatusType.BROADCASTING)
event = create_broadcast_event(broadcast_message)
mock_create_ticket = mocker.patch("app.celery.broadcast_message_tasks.zendesk_client.create_ticket")
mock_send_broadcast_provider_message = mocker.patch(
'app.celery.broadcast_message_tasks.send_broadcast_provider_message',
@@ -49,6 +50,9 @@ def test_send_broadcast_event_queues_up_for_active_providers(mocker, notify_api,
call(kwargs={'broadcast_event_id': event.id, 'provider': 'vodafone'}, queue='broadcast-tasks')
]
# we're on test env so this isn't called
assert mock_create_ticket.called is False
def test_send_broadcast_event_only_sends_to_one_provider_if_set_on_service(
mocker,
@@ -106,6 +110,69 @@ def test_send_broadcast_event_does_nothing_if_cbc_proxy_disabled(mocker, notify_
assert mock_send_broadcast_provider_message.apply_async.called is False
def test_send_broadcast_event_creates_zendesk_p1(mocker, notify_api, sample_broadcast_service):
template = create_template(sample_broadcast_service, BROADCAST_TYPE)
broadcast_message = create_broadcast_message(
template,
status=BroadcastStatusType.BROADCASTING,
areas={'areas': ['wd20-S13002775', 'wd20-S13002773'], 'simple_polygons': []},
)
event = create_broadcast_event(broadcast_message)
mock_create_ticket = mocker.patch("app.celery.broadcast_message_tasks.zendesk_client.create_ticket")
mocker.patch('app.celery.broadcast_message_tasks.send_broadcast_provider_message')
with set_config(notify_api, 'NOTIFY_ENVIRONMENT', 'live'):
send_broadcast_event(event.id)
assert mock_create_ticket.call_count == 1
zendesk_args = mock_create_ticket.call_args[1]
assert zendesk_args['p1'] is True
assert zendesk_args['ticket_type'] == 'incident'
assert str(broadcast_message.id) in zendesk_args['message']
assert 'channel severe' in zendesk_args['message']
assert "areas ['wd20-S13002775', 'wd20-S13002773']" in zendesk_args['message']
# the start of the content from the broadcast template
assert "Dear Sir/Madam" in zendesk_args['message']
def test_send_broadcast_event_doesnt_p1_when_cancelling(mocker, notify_api, sample_broadcast_service):
template = create_template(sample_broadcast_service, BROADCAST_TYPE)
broadcast_message = create_broadcast_message(
template,
status=BroadcastStatusType.BROADCASTING,
areas={'areas': ['wd20-S13002775', 'wd20-S13002773'], 'simple_polygons': []},
)
create_broadcast_event(broadcast_message, message_type=BroadcastEventMessageType.ALERT)
cancel_event = create_broadcast_event(broadcast_message, message_type=BroadcastEventMessageType.CANCEL)
mock_create_ticket = mocker.patch("app.celery.broadcast_message_tasks.zendesk_client.create_ticket")
mocker.patch('app.celery.broadcast_message_tasks.send_broadcast_provider_message')
with set_config(notify_api, 'NOTIFY_ENVIRONMENT', 'live'):
send_broadcast_event(cancel_event.id)
assert mock_create_ticket.called is False
def test_send_broadcast_event_doesnt_create_zendesk_on_staging(mocker, notify_api, sample_broadcast_service):
template = create_template(sample_broadcast_service, BROADCAST_TYPE)
broadcast_message = create_broadcast_message(template, status=BroadcastStatusType.BROADCASTING)
event = create_broadcast_event(broadcast_message)
mock_create_ticket = mocker.patch("app.celery.broadcast_message_tasks.zendesk_client.create_ticket")
mock_send_broadcast_provider_message = mocker.patch(
'app.celery.broadcast_message_tasks.send_broadcast_provider_message',
)
with set_config(notify_api, 'NOTIFY_ENVIRONMENT', 'staging'):
send_broadcast_event(event.id)
assert mock_send_broadcast_provider_message.apply_async.called is True
assert mock_create_ticket.called is False
@freeze_time('2020-08-01 12:00')
@pytest.mark.parametrize('provider,provider_capitalised', [
['ee', 'EE'],