Files
zulip/zerver/views/health.py
Alex Vandiver 5ee4b642ad views: Add a /health healthcheck endpoint.
This endpoint verifies that the services that Zulip needs to function
are running, and Django can talk to them.  It is designed to be used
as a readiness probe[^1] for Zulip, either by Kubernetes, or some other
reverse-proxy load-balancer in front of Zulip.  Because of this, it
limits access to only localhost and the IP addresses of configured
reverse proxies.

Tests are limited because we cannot stop running services (which would
impact other concurrent tests) and there would be extremely limited
utility to mocking the very specific methods we're calling to raising
the exceptions that we're looking for.

[^1]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
2023-09-20 09:53:59 -07:00

66 lines
2.1 KiB
Python

from django.db.migrations.recorder import MigrationRecorder
from django.http import HttpRequest, HttpResponse
from django.utils.crypto import get_random_string
from django.utils.translation import gettext as _
from pika import BlockingConnection
from zerver.lib.cache import cache_delete, cache_get, cache_set
from zerver.lib.exceptions import ServerNotReadyError
from zerver.lib.queue import get_queue_client
from zerver.lib.redis_utils import get_redis_client
from zerver.lib.response import json_success
def check_database() -> None:
try:
if not MigrationRecorder.Migration.objects.exists():
raise ServerNotReadyError(_("Database is empty")) # nocoverage
except ServerNotReadyError: # nocoverage
raise
except Exception: # nocoverage
raise ServerNotReadyError(_("Cannot query postgresql"))
def check_rabbitmq() -> None: # nocoverage
try:
conn = get_queue_client().connection
if conn is None:
raise ServerNotReadyError(_("Cannot connect to rabbitmq"))
assert isinstance(conn, BlockingConnection)
conn.process_data_events()
except ServerNotReadyError:
raise
except Exception:
raise ServerNotReadyError(_("Cannot query rabbitmq"))
def check_redis() -> None:
try:
get_redis_client().ping()
except Exception: # nocoverage
raise ServerNotReadyError(_("Cannot query redis"))
def check_memcached() -> None:
try:
roundtrip_key = "health_check_" + get_random_string(32)
roundtrip_value = get_random_string(32)
cache_set(roundtrip_key, roundtrip_value)
got_value = cache_get(roundtrip_key)[0]
if got_value != roundtrip_value:
raise ServerNotReadyError(_("Cannot write to memcached")) # nocoverage
cache_delete(roundtrip_key)
except ServerNotReadyError: # nocoverage
raise
except Exception: # nocoverage
raise ServerNotReadyError(_("Cannot query memcached"))
def health(request: HttpRequest) -> HttpResponse:
check_database()
check_rabbitmq()
check_redis()
check_memcached()
return json_success(request)