Skip to content

Commit

Permalink
Merge pull request #204 from uktrade/update-healthcheck-separate-p1-p2
Browse files Browse the repository at this point in the history
Update healthcheck separate p1 p2
  • Loading branch information
kevincarrogan authored Aug 3, 2023
2 parents 84ebf34 + 2f45eae commit ac9319a
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 168 deletions.
1 change: 1 addition & 0 deletions conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"django.contrib.staticfiles",
"background_task",
"mail.apps.MailConfig",
"healthcheck",
]

MIDDLEWARE = [
Expand Down
6 changes: 2 additions & 4 deletions conf/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@
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:
if settings.ENABLE_MOCK_HMRC_SERVICE: # pragma: no cover
urlpatterns += [path("mock-hmrc/", include("mock_hmrc.urls"))]
133 changes: 0 additions & 133 deletions conf/views.py

This file was deleted.

Empty file added healthcheck/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions healthcheck/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class HealthcheckConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "healthcheck"
80 changes: 80 additions & 0 deletions healthcheck/checks.py
Original file line number Diff line number Diff line change
@@ -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()
Empty file added healthcheck/tests/__init__.py
Empty file.
74 changes: 43 additions & 31 deletions conf/tests/test_healthcheck.py → healthcheck/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
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


class TestHealthcheck(testcases.TestCase):
class TestHealthCheckP1(testcases.TestCase):
MAILSERVERS_TO_PATCH = [
"get_hmrc_to_dit_mailserver",
"get_spire_to_dit_mailserver",
Expand All @@ -27,10 +27,10 @@ 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.checks.{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()
Expand All @@ -43,38 +43,13 @@ 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)
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):
Expand All @@ -83,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)
Expand All @@ -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)
8 changes: 8 additions & 0 deletions healthcheck/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import path

from . import views

urlpatterns = [
path("", views.HealthCheckP1.as_view(), name="healthcheck_p1"),
path("p2/", views.HealthCheckP2.as_view(), name="healthcheck_p2"),
]
Loading

0 comments on commit ac9319a

Please sign in to comment.