Source code for authnzerver.healthcheck

# -*- coding: utf-8 -*-
# healthcheck.py - Waqas Bhatti (waqas.afzal.bhatti@gmail.com) - Jul 2020
# License: MIT - see the LICENSE file for the full text.

"""These are handlers to respond to health-check requests.

"""

#############
## LOGGING ##
#############

import logging

# get a logger
LOGGER = logging.getLogger(__name__)


#############
## IMPORTS ##
#############

import json
from functools import partial
from time import monotonic

# this replaces the default encoder and makes it so Tornado will do the right
# thing when it converts dicts to JSON when a
# tornado.web.RequestHandler.write(dict) is called.
from .jsonencoder import FrontendEncoder

json._default_encoder = FrontendEncoder()

import tornado.web
import tornado.ioloop

from .ratelimit import RateLimitMixin
from .actions import database_health_check


[docs]class HealthCheckHandler(tornado.web.RequestHandler, RateLimitMixin): """ This handles health check endpoints. """
[docs] def initialize(self, config, executor, cacheobj): """ This sets up some config. """ self.config = config self.executor = executor self.cacheobj = cacheobj self.pii_salt = self.config.piisalt self.nworkers = self.config.workers self.ratelimits = self.config.ratelimits
[docs] def write_error(self, status_code, **kwargs): """ This writes the error as a response. """ self.set_header("content-type", "text/plain; charset=UTF-8") if status_code == 400: self.write( f"HTTP {status_code}: Could not service this request " f"because of invalid request parameters." ) elif status_code == 401: self.write( f"HTTP {status_code}: Could not service this request " f"because of invalid request authentication token or " f"violation of host restriction." ) elif status_code == 429: self.set_header("Retry-After", "180") self.write( f"HTTP {status_code}: Could not service this request " f"because the set rate limit has been exceeded." ) else: self.write(f"HTTP {status_code}: Could not service this request.") if not self._finished: self.finish()
[docs] async def get(self): """This responds to a health-check request. Returns 200 if the server is up and all the background workers report their DB connection is good. """ self.ratelimit_request( 101010, "apikey-healthcheck", {"ip_address": self.request.remote_ip}, ) loop = tornado.ioloop.IOLoop.current() backend_func = partial(database_health_check, config=self.config) health_checks = {} healthcheck_start = monotonic() healthcheck_clock = 0.0 max_healthcheck_time = 5.0 while healthcheck_clock < max_healthcheck_time: # this round-robin schedules the tasks on all the workers health_check = await loop.run_in_executor( self.executor, backend_func ) health_checks[health_check["process"]] = ( "ok" if health_check["success"] else health_check["failure_reason"] ) healthcheck_clock = monotonic() - healthcheck_start if len(health_checks) == self.nworkers: break all_workers_ok = ( all(health_checks[key] for key in health_checks) and len(health_checks) == self.nworkers ) retdict = { "Expected-Workers": self.nworkers, "Workers": health_checks, "Check-Time": healthcheck_clock, "Health-Ok": all_workers_ok, } if all_workers_ok: self.set_status(200) else: self.set_status(500) self.write(retdict) await self.finish()