Skip to content

Commit 9c74317

Browse files
alanbchristieAlan Christie
and
Alan Christie
authored
feat: Introduce 'infection' logic (#512)
Co-authored-by: Alan Christie <alan.christie@matildapeak.com>
1 parent f496abe commit 9c74317

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,26 @@ In `settings.py`, this is controlled by setting the value of `FRAGALYSIS_BACKEND
215215
which is also exposed in the developer docker-compose file.
216216
To enable it, you need to set it to a valid Sentry DNS value.
217217

218+
## Deployment mode
219+
The stack can be deployed in one of tweo modes: - `DEVELOPMENT` or `PRODUCTION`.
220+
The mode is controlled by the `DEPLOYMENT_MODE` environment variable and is used
221+
by the backend in order to tailor the behaviour of the application.
222+
223+
In `PRODUCTION` mode the API is typically a little more strict than in `DEVELOPMENT` mode.
224+
225+
## Forced errors ("infections")
226+
In order to allow error paths of various elements of the stack to be tested, the
227+
developer can _inject_ specific errors ("infections"). This is
228+
achieved by setting the environment variable `INFECTIONS` in the `docker-compose.yml` file
229+
or, for kubernetes deployments, using the ansible variable `stack_infections`.
230+
231+
Known errors are documented in the `api/infections.py` module. To induce the error
232+
(at thew appropriate point in the stack) provide the infection name as the value of the
233+
`INFECTIONS` environment variable. You can provide more than one name by separating
234+
them with a comma.
235+
236+
Infections are ignored in `PRODUCTION` mode.
237+
218238
## Compiling the documentation
219239
Because the documentation uses Sphinx and its `autodoc` module, compiling the
220240
documentation needs all the application requirements. As this is often impractical

api/infections.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# infections.py
2+
#
3+
# A utility that provides a list of infections (forced internal errors).
4+
# Infections are injected into the application via the environment variable
5+
# 'INFECTIONS', a comma-separated list of infection names.
6+
7+
import os
8+
from typing import Dict, Set
9+
10+
from api.utils import deployment_mode_is_production
11+
12+
# The built-in set of infections.
13+
# Must be lowercase, but user can use any case in the environment variable.
14+
# Define every name as a constant, and add it and a description to the _CATALOGUE.
15+
INFECTION_STRUCTURE_DOWNLOAD: str = 'structure-download'
16+
17+
# The index is the short-form name of the infection, and the value is the
18+
# description of the infection.
19+
_CATALOGUE: Dict[str, str] = {
20+
INFECTION_STRUCTURE_DOWNLOAD: 'An error in the DownloadStructures view'
21+
}
22+
23+
# What infection have been set?
24+
_INFECTIONS: str = os.environ.get('INFECTIONS', '').lower()
25+
26+
27+
def have_infection(name: str) -> bool:
28+
"""Returns True if we've been given the named infection.
29+
Infections are never present in production mode."""
30+
return False if deployment_mode_is_production() else name in _get_infections()
31+
32+
33+
def _get_infections() -> Set[str]:
34+
if _INFECTIONS == '':
35+
return set()
36+
infections: set[str] = {
37+
infection for infection in _INFECTIONS.split(',') if infection in _CATALOGUE
38+
}
39+
return infections

docker-compose.yml

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ services:
8888
AUTHENTICATE_UPLOAD: ${AUTHENTICATE_UPLOAD:-True}
8989
DEPLOYMENT_MODE: 'development'
9090
POSTGRESQL_USER: postgres
91+
# Comma-separated dforced errors (infections?)
92+
INFECTIONS: ''
9193
# Celery tasks need to run synchronously
9294
CELERY_TASK_ALWAYS_EAGER: 'True'
9395
# Error reporting and default/root log-level

viewer/views.py

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from rest_framework.response import Response
3131
from rest_framework.views import APIView
3232

33+
from api.infections import INFECTION_STRUCTURE_DOWNLOAD, have_infection
3334
from api.security import ISpyBSafeQuerySet
3435
from api.utils import get_highlighted_diffs, get_params, pretty_request
3536
from viewer import filters, models, serializers
@@ -1508,6 +1509,11 @@ def create(self, request):
15081509
}
15091510
return Response(content, status=status.HTTP_404_NOT_FOUND)
15101511

1512+
# Forced errors?
1513+
if have_infection(INFECTION_STRUCTURE_DOWNLOAD):
1514+
content = {'message': f'Download Error! ({INFECTION_STRUCTURE_DOWNLOAD})'}
1515+
return Response(content, status=status.HTTP_404_NOT_FOUND)
1516+
15111517
filename_url = create_or_return_download_link(request, target, site_obvs)
15121518
assert filename_url is not None
15131519
return Response({"file_url": filename_url})

0 commit comments

Comments
 (0)