From 79a2fb6ee2f96d99781a0e4cf173fc193cd562af Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Wed, 2 Aug 2023 15:19:00 +0100 Subject: [PATCH 1/4] Move out healthcheck into its own django app --- conf/settings.py | 1 + conf/urls.py | 4 +--- healthcheck/__init__.py | 0 healthcheck/apps.py | 6 ++++++ healthcheck/tests/__init__.py | 0 .../test_healthcheck.py => healthcheck/tests/test_views.py | 4 ++-- healthcheck/urls.py | 7 +++++++ {conf => healthcheck}/views.py | 0 8 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 healthcheck/__init__.py create mode 100644 healthcheck/apps.py create mode 100644 healthcheck/tests/__init__.py rename conf/tests/test_healthcheck.py => healthcheck/tests/test_views.py (96%) create mode 100644 healthcheck/urls.py rename {conf => healthcheck}/views.py (100%) diff --git a/conf/settings.py b/conf/settings.py index 96db6469..6c568913 100644 --- a/conf/settings.py +++ b/conf/settings.py @@ -37,6 +37,7 @@ "django.contrib.staticfiles", "background_task", "mail.apps.MailConfig", + "healthcheck", ] MIDDLEWARE = [ diff --git a/conf/urls.py b/conf/urls.py index ec6f86ca..04a70e47 100644 --- a/conf/urls.py +++ b/conf/urls.py @@ -17,12 +17,10 @@ from django.contrib import admin from django.urls import include, path -from conf.views import HealthCheck - urlpatterns = [ path("admin/", admin.site.urls), path("mail/", include("mail.urls")), - path("healthcheck/", HealthCheck.as_view(), name="healthcheck"), + path("healthcheck/", include("healthcheck.urls")), ] if settings.ENABLE_MOCK_HMRC_SERVICE: diff --git a/healthcheck/__init__.py b/healthcheck/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/healthcheck/apps.py b/healthcheck/apps.py new file mode 100644 index 00000000..b4ccc784 --- /dev/null +++ b/healthcheck/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HealthcheckConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "healthcheck" diff --git a/healthcheck/tests/__init__.py b/healthcheck/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/conf/tests/test_healthcheck.py b/healthcheck/tests/test_views.py similarity index 96% rename from conf/tests/test_healthcheck.py rename to healthcheck/tests/test_views.py index 066a2a02..c0e8c7b0 100644 --- a/conf/tests/test_healthcheck.py +++ b/healthcheck/tests/test_views.py @@ -11,7 +11,7 @@ from parameterized import parameterized from rest_framework import status -from mail.enums import LicenceActionEnum, ReceptionStatusEnum, ReplyStatusEnum +from mail.enums import LicenceActionEnum, ReplyStatusEnum from mail.models import LicencePayload, Mail from mail.tasks import LICENCE_DATA_TASK_QUEUE, MANAGE_INBOX_TASK_QUEUE @@ -27,7 +27,7 @@ def setUp(self): self.mocked_mailservers = {} for mailserver_to_patch in self.MAILSERVERS_TO_PATCH: - patched_mailserver = patch(f"conf.views.{mailserver_to_patch}").start() + patched_mailserver = patch(f"healthcheck.views.{mailserver_to_patch}").start() self.mocked_mailservers[mailserver_to_patch] = patched_mailserver self.url = reverse("healthcheck") diff --git a/healthcheck/urls.py b/healthcheck/urls.py new file mode 100644 index 00000000..6b0d5093 --- /dev/null +++ b/healthcheck/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.HealthCheck.as_view(), name="healthcheck"), +] diff --git a/conf/views.py b/healthcheck/views.py similarity index 100% rename from conf/views.py rename to healthcheck/views.py From 278696ad42f8b65de0559e28b9e24b7e527e7bce Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Wed, 2 Aug 2023 15:32:41 +0100 Subject: [PATCH 2/4] Split out healthcheck views into two priority levels --- healthcheck/tests/test_views.py | 66 ++++++++++++--------- healthcheck/urls.py | 3 +- healthcheck/views.py | 102 ++++++++++++++++++++------------ 3 files changed, 105 insertions(+), 66 deletions(-) diff --git a/healthcheck/tests/test_views.py b/healthcheck/tests/test_views.py index c0e8c7b0..bffd85ea 100644 --- a/healthcheck/tests/test_views.py +++ b/healthcheck/tests/test_views.py @@ -16,7 +16,7 @@ from mail.tasks import LICENCE_DATA_TASK_QUEUE, MANAGE_INBOX_TASK_QUEUE -class TestHealthcheck(testcases.TestCase): +class TestHealthCheckP1(testcases.TestCase): MAILSERVERS_TO_PATCH = [ "get_hmrc_to_dit_mailserver", "get_spire_to_dit_mailserver", @@ -30,7 +30,7 @@ def setUp(self): patched_mailserver = patch(f"healthcheck.views.{mailserver_to_patch}").start() self.mocked_mailservers[mailserver_to_patch] = patched_mailserver - self.url = reverse("healthcheck") + self.url = reverse("healthcheck_p1") def tearDown(self) -> None: super().tearDown() @@ -43,31 +43,6 @@ def test_healthcheck_return_ok(self): self.assertEqual(response.context["message"], "OK") self.assertEqual(response.context["status"], status.HTTP_200_OK) - def test_healthcheck_service_unavailable_pending_mail(self): - sent_at = timezone.now() - timedelta(seconds=settings.EMAIL_AWAITING_REPLY_TIME) - Mail.objects.create( - edi_filename="filename", - edi_data="1\\fileHeader\\CHIEF\\SPIRE\\", - status=ReplyStatusEnum.PENDING, - sent_at=sent_at, - ) - response = self.client.get(self.url) - self.assertEqual(response.context["message"], "Pending mail error") - self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) - - def test_healthcheck_service_unavailable_pending_payload(self): - received_at = timezone.now() - timedelta(seconds=settings.LICENSE_POLL_INTERVAL) - LicencePayload.objects.create( - lite_id=uuid.uuid4(), - reference="ABC12345", - action=LicenceActionEnum.INSERT, - is_processed=False, - received_at=received_at, - ) - response = self.client.get(self.url) - self.assertEqual(response.context["message"], "Payload objects error") - self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) - def test_healthcheck_service_unavailable_inbox_task_not_responsive(self): run_at = timezone.now() + timedelta(minutes=settings.INBOX_POLL_INTERVAL) task, _ = Task.objects.get_or_create(queue=MANAGE_INBOX_TASK_QUEUE) @@ -94,3 +69,40 @@ def test_healthcheck_service_mailbox_authentication_failure(self, mailserver_fac response = self.client.get(self.url) self.assertEqual(response.context["message"], "Mailbox authentication error") self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) + + +class TestHealthCheckP2(testcases.TestCase): + def setUp(self): + super().setUp() + + self.url = reverse("healthcheck_p2") + + def test_healthcheck_return_ok(self): + response = self.client.get(self.url) + self.assertEqual(response.context["message"], "OK") + self.assertEqual(response.context["status"], status.HTTP_200_OK) + + def test_healthcheck_service_unavailable_pending_mail(self): + sent_at = timezone.now() - timedelta(seconds=settings.EMAIL_AWAITING_REPLY_TIME) + Mail.objects.create( + edi_filename="filename", + edi_data="1\\fileHeader\\CHIEF\\SPIRE\\", + status=ReplyStatusEnum.PENDING, + sent_at=sent_at, + ) + response = self.client.get(self.url) + self.assertEqual(response.context["message"], "Pending mail error") + self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) + + def test_healthcheck_service_unavailable_pending_payload(self): + received_at = timezone.now() - timedelta(seconds=settings.LICENSE_POLL_INTERVAL) + LicencePayload.objects.create( + lite_id=uuid.uuid4(), + reference="ABC12345", + action=LicenceActionEnum.INSERT, + is_processed=False, + received_at=received_at, + ) + response = self.client.get(self.url) + self.assertEqual(response.context["message"], "Payload objects error") + self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) diff --git a/healthcheck/urls.py b/healthcheck/urls.py index 6b0d5093..858ed805 100644 --- a/healthcheck/urls.py +++ b/healthcheck/urls.py @@ -3,5 +3,6 @@ from . import views urlpatterns = [ - path("", views.HealthCheck.as_view(), name="healthcheck"), + path("", views.HealthCheckP1.as_view(), name="healthcheck_p1"), + path("p2/", views.HealthCheckP2.as_view(), name="healthcheck_p2"), ] diff --git a/healthcheck/views.py b/healthcheck/views.py index 5d83d01a..f4400a6d 100644 --- a/healthcheck/views.py +++ b/healthcheck/views.py @@ -16,7 +16,7 @@ from mail.tasks import LICENCE_DATA_TASK_QUEUE, MANAGE_INBOX_TASK_QUEUE -class HealthCheck(APIView): +class HealthCheckP1(APIView): ERROR_LICENCE_DATA_TASK_QUEUE = "licences_updates_queue error" ERROR_MANAGE_INBOX_TASK_QUEUE = "manage_inbox_queue error" ERROR_PENDING_MAIL = "Pending mail error" @@ -42,6 +42,69 @@ def get(self, request): logging.error("%s is not responsive", MANAGE_INBOX_TASK_QUEUE) return self._build_response(HTTP_503_SERVICE_UNAVAILABLE, self.ERROR_MANAGE_INBOX_TASK_QUEUE, start_time) + logging.info("All services are responsive") + return self._build_response(HTTP_200_OK, "OK", start_time) + + @staticmethod + def _is_lite_licence_update_task_responsive() -> bool: + dt = timezone.now() + datetime.timedelta(seconds=settings.LITE_LICENCE_DATA_POLL_INTERVAL) + + return Task.objects.filter(queue=LICENCE_DATA_TASK_QUEUE, run_at__lte=dt).exists() + + @staticmethod + def _is_inbox_polling_task_responsive() -> bool: + dt = timezone.now() + datetime.timedelta(seconds=settings.INBOX_POLL_INTERVAL) + + return Task.objects.filter(queue=MANAGE_INBOX_TASK_QUEUE, run_at__lte=dt).exists() + + def _build_response(self, status, message, start_time): + duration_ms = (time.time() - start_time) * 1000 + response_time = "{:.3f}".format(duration_ms) + context = {"message": message, "response_time": response_time, "status": status} + + return render(self.request, "healthcheck.xml", context, content_type="application/xml", status=status) + + def _can_authenticate_mailboxes(self) -> bool: + mailserver_factories = ( + get_hmrc_to_dit_mailserver, + get_spire_to_dit_mailserver, + ) + mailbox_results = [] + for mailserver_factory in mailserver_factories: + mailserver = mailserver_factory() + try: + mailserver.connect_to_pop3() + except poplib.error_proto as e: + response, *_ = e.args + logging.error( + "Failed to connect to mailbox: %s (%s)", + mailserver.hostname, + response, + ) + mailbox_results.append(False) + else: + mailbox_results.append(True) + finally: + mailserver.quit_pop3_connection() + + return all(mailbox_results) + + +class HealthCheckP2(APIView): + ERROR_LICENCE_DATA_TASK_QUEUE = "licences_updates_queue error" + ERROR_MANAGE_INBOX_TASK_QUEUE = "manage_inbox_queue error" + ERROR_PENDING_MAIL = "Pending mail error" + ERROR_REJECTED_MAIL = "Rejected mail error" + ERROR_PAYLOAD_OBJECTS = "Payload objects error" + ERROR_MAILBOX_AUTHENTICATION = "Mailbox authentication error" + + def get(self, request): + """ + Provides a health check endpoint as per [https://man.uktrade.io/docs/howtos/healthcheck.html#pingdom] + """ + + start_time = time.time() + payload_object_pending = self._get_license_payload_object_pending() if payload_object_pending: logging.error( @@ -63,18 +126,6 @@ def get(self, request): logging.info("All services are responsive") return self._build_response(HTTP_200_OK, "OK", start_time) - @staticmethod - def _is_lite_licence_update_task_responsive() -> bool: - dt = timezone.now() + datetime.timedelta(seconds=settings.LITE_LICENCE_DATA_POLL_INTERVAL) - - return Task.objects.filter(queue=LICENCE_DATA_TASK_QUEUE, run_at__lte=dt).exists() - - @staticmethod - def _is_inbox_polling_task_responsive() -> bool: - dt = timezone.now() + datetime.timedelta(seconds=settings.INBOX_POLL_INTERVAL) - - return Task.objects.filter(queue=MANAGE_INBOX_TASK_QUEUE, run_at__lte=dt).exists() - @staticmethod def _get_pending_mail() -> []: dt = timezone.now() - datetime.timedelta(seconds=settings.EMAIL_AWAITING_REPLY_TIME) @@ -106,28 +157,3 @@ def _get_license_payload_object_pending() -> bool: dt = timezone.now() + datetime.timedelta(seconds=settings.LICENSE_POLL_INTERVAL) return LicencePayload.objects.filter(is_processed=False, received_at__lte=dt).first() - - def _can_authenticate_mailboxes(self) -> bool: - mailserver_factories = ( - get_hmrc_to_dit_mailserver, - get_spire_to_dit_mailserver, - ) - mailbox_results = [] - for mailserver_factory in mailserver_factories: - mailserver = mailserver_factory() - try: - mailserver.connect_to_pop3() - except poplib.error_proto as e: - response, *_ = e.args - logging.error( - "Failed to connect to mailbox: %s (%s)", - mailserver.hostname, - response, - ) - mailbox_results.append(False) - else: - mailbox_results.append(True) - finally: - mailserver.quit_pop3_connection() - - return all(mailbox_results) From 3caf69814726785840157a032d0273963781cf92 Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Wed, 2 Aug 2023 16:32:45 +0100 Subject: [PATCH 3/4] Make a common base view for healthchecks --- healthcheck/checks.py | 80 ++++++++++++++++ healthcheck/tests/test_views.py | 6 +- healthcheck/views.py | 158 ++++++-------------------------- 3 files changed, 112 insertions(+), 132 deletions(-) create mode 100644 healthcheck/checks.py diff --git a/healthcheck/checks.py b/healthcheck/checks.py new file mode 100644 index 00000000..8ebc7928 --- /dev/null +++ b/healthcheck/checks.py @@ -0,0 +1,80 @@ +import datetime +import logging +import poplib + +from background_task.models import Task +from django.conf import settings +from django.utils import timezone + +from mail.enums import ReceptionStatusEnum +from mail.libraries.routing_controller import get_hmrc_to_dit_mailserver, get_spire_to_dit_mailserver +from mail.models import LicencePayload, Mail +from mail.tasks import LICENCE_DATA_TASK_QUEUE, MANAGE_INBOX_TASK_QUEUE + +logger = logging.getLogger(__name__) + + +def can_authenticate_mailboxes(): + mailserver_factories = ( + get_hmrc_to_dit_mailserver, + get_spire_to_dit_mailserver, + ) + mailbox_results = [] + for mailserver_factory in mailserver_factories: + mailserver = mailserver_factory() + try: + mailserver.connect_to_pop3() + except poplib.error_proto as e: + response, *_ = e.args + logger.error( + "Failed to connect to mailbox: %s (%s)", + mailserver.hostname, + response, + ) + mailbox_results.append(False) + else: + mailbox_results.append(True) + finally: + mailserver.quit_pop3_connection() + + return all(mailbox_results) + + +def is_licence_payloads_processing(): + dt = timezone.now() + datetime.timedelta(seconds=settings.LICENSE_POLL_INTERVAL) + + unprocessed_payloads = LicencePayload.objects.filter(is_processed=False, received_at__lte=dt) + for unprocessed_payload in unprocessed_payloads: + logger.error( + "Payload object has been unprocessed for over %s seconds: %s", + settings.LICENSE_POLL_INTERVAL, + unprocessed_payload, + ) + + return not unprocessed_payloads.exists() + + +def is_lite_licence_update_task_responsive(): + dt = timezone.now() + datetime.timedelta(seconds=settings.LITE_LICENCE_DATA_POLL_INTERVAL) + + return Task.objects.filter(queue=LICENCE_DATA_TASK_QUEUE, run_at__lte=dt).exists() + + +def is_manage_inbox_task_responsive(): + dt = timezone.now() + datetime.timedelta(seconds=settings.INBOX_POLL_INTERVAL) + + return Task.objects.filter(queue=MANAGE_INBOX_TASK_QUEUE, run_at__lte=dt).exists() + + +def is_pending_mail_processing(): + dt = timezone.now() - datetime.timedelta(seconds=settings.EMAIL_AWAITING_REPLY_TIME) + + pending_mails = Mail.objects.exclude(status=ReceptionStatusEnum.REPLY_SENT).filter(sent_at__lte=dt) + for pending_mail in pending_mails: + logger.error( + "The following Mail has been pending for over %s seconds: %s", + settings.EMAIL_AWAITING_REPLY_TIME, + pending_mail, + ) + + return not pending_mails.exists() diff --git a/healthcheck/tests/test_views.py b/healthcheck/tests/test_views.py index bffd85ea..300b9869 100644 --- a/healthcheck/tests/test_views.py +++ b/healthcheck/tests/test_views.py @@ -27,7 +27,7 @@ def setUp(self): self.mocked_mailservers = {} for mailserver_to_patch in self.MAILSERVERS_TO_PATCH: - patched_mailserver = patch(f"healthcheck.views.{mailserver_to_patch}").start() + patched_mailserver = patch(f"healthcheck.checks.{mailserver_to_patch}").start() self.mocked_mailservers[mailserver_to_patch] = patched_mailserver self.url = reverse("healthcheck_p1") @@ -49,7 +49,7 @@ def test_healthcheck_service_unavailable_inbox_task_not_responsive(self): task.run_at = run_at task.save() response = self.client.get(self.url) - self.assertEqual(response.context["message"], "manage_inbox_queue error") + self.assertEqual(response.context["message"], "Manage inbox queue error") self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) def test_healthcheck_service_unavailable_licence_update_task_not_responsive(self): @@ -58,7 +58,7 @@ def test_healthcheck_service_unavailable_licence_update_task_not_responsive(self task.run_at = run_at task.save() response = self.client.get(self.url) - self.assertEqual(response.context["message"], "licences_updates_queue error") + self.assertEqual(response.context["message"], "Licences updates queue error") self.assertEqual(response.context["status"], status.HTTP_503_SERVICE_UNAVAILABLE) @parameterized.expand(MAILSERVERS_TO_PATCH) diff --git a/healthcheck/views.py b/healthcheck/views.py index f4400a6d..b1733e23 100644 --- a/healthcheck/views.py +++ b/healthcheck/views.py @@ -1,29 +1,22 @@ -import datetime import logging -import poplib import time -from background_task.models import Task -from django.conf import settings from django.shortcuts import render -from django.utils import timezone from rest_framework.status import HTTP_200_OK, HTTP_503_SERVICE_UNAVAILABLE from rest_framework.views import APIView -from mail.enums import ChiefSystemEnum, ReceptionStatusEnum, ReplyStatusEnum -from mail.libraries.routing_controller import get_hmrc_to_dit_mailserver, get_spire_to_dit_mailserver -from mail.models import LicencePayload, Mail -from mail.tasks import LICENCE_DATA_TASK_QUEUE, MANAGE_INBOX_TASK_QUEUE +from .checks import ( + can_authenticate_mailboxes, + is_licence_payloads_processing, + is_lite_licence_update_task_responsive, + is_manage_inbox_task_responsive, + is_pending_mail_processing, +) +logger = logging.getLogger(__name__) -class HealthCheckP1(APIView): - ERROR_LICENCE_DATA_TASK_QUEUE = "licences_updates_queue error" - ERROR_MANAGE_INBOX_TASK_QUEUE = "manage_inbox_queue error" - ERROR_PENDING_MAIL = "Pending mail error" - ERROR_REJECTED_MAIL = "Rejected mail error" - ERROR_PAYLOAD_OBJECTS = "Payload objects error" - ERROR_MAILBOX_AUTHENTICATION = "Mailbox authentication error" +class BaseHealthCheckView(APIView): def get(self, request): """ Provides a health check endpoint as per [https://man.uktrade.io/docs/howtos/healthcheck.html#pingdom] @@ -31,32 +24,18 @@ def get(self, request): start_time = time.time() - if not self._can_authenticate_mailboxes(): - return self._build_response(HTTP_503_SERVICE_UNAVAILABLE, self.ERROR_MAILBOX_AUTHENTICATION, start_time) - - if not self._is_lite_licence_update_task_responsive(): - logging.error("%s is not responsive", LICENCE_DATA_TASK_QUEUE) - return self._build_response(HTTP_503_SERVICE_UNAVAILABLE, self.ERROR_LICENCE_DATA_TASK_QUEUE, start_time) - - if settings.CHIEF_SOURCE_SYSTEM == ChiefSystemEnum.SPIRE and not self._is_inbox_polling_task_responsive(): - logging.error("%s is not responsive", MANAGE_INBOX_TASK_QUEUE) - return self._build_response(HTTP_503_SERVICE_UNAVAILABLE, self.ERROR_MANAGE_INBOX_TASK_QUEUE, start_time) + for check, message in self.checks: + if not check(): + logger.error("%s", message) + return self._build_response( + HTTP_503_SERVICE_UNAVAILABLE, + message, + start_time, + ) - logging.info("All services are responsive") + logger.info("All services are responsive") return self._build_response(HTTP_200_OK, "OK", start_time) - @staticmethod - def _is_lite_licence_update_task_responsive() -> bool: - dt = timezone.now() + datetime.timedelta(seconds=settings.LITE_LICENCE_DATA_POLL_INTERVAL) - - return Task.objects.filter(queue=LICENCE_DATA_TASK_QUEUE, run_at__lte=dt).exists() - - @staticmethod - def _is_inbox_polling_task_responsive() -> bool: - dt = timezone.now() + datetime.timedelta(seconds=settings.INBOX_POLL_INTERVAL) - - return Task.objects.filter(queue=MANAGE_INBOX_TASK_QUEUE, run_at__lte=dt).exists() - def _build_response(self, status, message, start_time): duration_ms = (time.time() - start_time) * 1000 response_time = "{:.3f}".format(duration_ms) @@ -64,96 +43,17 @@ def _build_response(self, status, message, start_time): return render(self.request, "healthcheck.xml", context, content_type="application/xml", status=status) - def _can_authenticate_mailboxes(self) -> bool: - mailserver_factories = ( - get_hmrc_to_dit_mailserver, - get_spire_to_dit_mailserver, - ) - mailbox_results = [] - for mailserver_factory in mailserver_factories: - mailserver = mailserver_factory() - try: - mailserver.connect_to_pop3() - except poplib.error_proto as e: - response, *_ = e.args - logging.error( - "Failed to connect to mailbox: %s (%s)", - mailserver.hostname, - response, - ) - mailbox_results.append(False) - else: - mailbox_results.append(True) - finally: - mailserver.quit_pop3_connection() - - return all(mailbox_results) - - -class HealthCheckP2(APIView): - ERROR_LICENCE_DATA_TASK_QUEUE = "licences_updates_queue error" - ERROR_MANAGE_INBOX_TASK_QUEUE = "manage_inbox_queue error" - ERROR_PENDING_MAIL = "Pending mail error" - ERROR_REJECTED_MAIL = "Rejected mail error" - ERROR_PAYLOAD_OBJECTS = "Payload objects error" - ERROR_MAILBOX_AUTHENTICATION = "Mailbox authentication error" - - def get(self, request): - """ - Provides a health check endpoint as per [https://man.uktrade.io/docs/howtos/healthcheck.html#pingdom] - """ - - start_time = time.time() - - payload_object_pending = self._get_license_payload_object_pending() - if payload_object_pending: - logging.error( - "Payload object has been unprocessed for over %s seconds: %s", - settings.LICENSE_POLL_INTERVAL, - payload_object_pending, - ) - return self._build_response(HTTP_503_SERVICE_UNAVAILABLE, self.ERROR_PAYLOAD_OBJECTS, start_time) - - pending_mail = self._get_pending_mail() - if pending_mail: - logging.error( - "The following Mail has been pending for over %s seconds: %s", - settings.EMAIL_AWAITING_REPLY_TIME, - pending_mail, - ) - return self._build_response(HTTP_503_SERVICE_UNAVAILABLE, self.ERROR_PENDING_MAIL, start_time) - - logging.info("All services are responsive") - return self._build_response(HTTP_200_OK, "OK", start_time) - - @staticmethod - def _get_pending_mail() -> []: - dt = timezone.now() - datetime.timedelta(seconds=settings.EMAIL_AWAITING_REPLY_TIME) - qs = Mail.objects.exclude(status=ReceptionStatusEnum.REPLY_SENT).filter(sent_at__lte=dt) - - return list(qs.values_list("id", flat=True)) - @staticmethod - def _get_rejected_mail() -> []: - dt = timezone.now() - datetime.timedelta(seconds=settings.EMAIL_AWAITING_CORRECTIONS_TIME) - - return list( - Mail.objects.filter( - status=ReceptionStatusEnum.REPLY_SENT, - response_data__icontains=ReplyStatusEnum.REJECTED, - sent_at__lte=dt, - ).values_list("id", flat=True) - ) - - def _build_response(self, status, message, start_time): - duration_ms = (time.time() - start_time) * 1000 - response_time = "{:.3f}".format(duration_ms) - context = {"message": message, "response_time": response_time, "status": status} - - return render(self.request, "healthcheck.xml", context, content_type="application/xml", status=status) +class HealthCheckP1(BaseHealthCheckView): + checks = [ + (can_authenticate_mailboxes, "Mailbox authentication error"), + (is_lite_licence_update_task_responsive, "Licences updates queue error"), + (is_manage_inbox_task_responsive, "Manage inbox queue error"), + ] - @staticmethod - def _get_license_payload_object_pending() -> bool: - dt = timezone.now() + datetime.timedelta(seconds=settings.LICENSE_POLL_INTERVAL) - return LicencePayload.objects.filter(is_processed=False, received_at__lte=dt).first() +class HealthCheckP2(BaseHealthCheckView): + checks = [ + (is_licence_payloads_processing, "Payload objects error"), + (is_pending_mail_processing, "Pending mail error"), + ] From 2f45eae1697d01d72e30a827a1fbcfe3c4df8c10 Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Wed, 2 Aug 2023 16:40:27 +0100 Subject: [PATCH 4/4] Force ignore test URL --- conf/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/urls.py b/conf/urls.py index 04a70e47..b93d4932 100644 --- a/conf/urls.py +++ b/conf/urls.py @@ -23,5 +23,5 @@ path("healthcheck/", include("healthcheck.urls")), ] -if settings.ENABLE_MOCK_HMRC_SERVICE: +if settings.ENABLE_MOCK_HMRC_SERVICE: # pragma: no cover urlpatterns += [path("mock-hmrc/", include("mock_hmrc.urls"))]