Health Checking
===============

GRPC provides `Health Checking Protocol`_ to implement health checks. You can
see it's latest definition here: `grpc/health/v1/health.proto`_.

As you can see from the service definition,
:py:class:`~grpclib.health.service.Health` service should implement one or two
methods: simple unary-unary ``Check`` method for synchronous checks and more
sophisticated unary-stream ``Watch`` method to asynchronously wait for status
changes. `grpclib` implements both of them.

`grpclib` also provides additional functionality to help write health checks, so
users don't have to write a lot of code on their own. It is possible to
implement health check in two ways (you can use both ways simultaneously):

  - use :py:class:`~grpclib.health.check.ServiceCheck` class by providing
    a callable object which can be called asynchronously to determine
    check's status
  - use :py:class:`~grpclib.health.check.ServiceStatus` class and change
    it's status by using :py:class:`~grpclib.health.check.ServiceStatus.set`
    method

:py:class:`~grpclib.health.check.ServiceCheck` is a simplest and most generic
way to implement periodic checks.

:py:class:`~grpclib.health.check.ServiceStatus` is for a more advanced usage,
when you are able to detect and change check's status proactively (e.g. by
detecting lost connection). And this way is more efficient and robust.

User Guide
~~~~~~~~~~

.. note:: To test server's health we will use `grpc_health_probe`_ command.

Overall Server Health
---------------------

The most simplest health checks:

.. code-block:: python3

  from grpclib.health.service import Health

  health = Health()

  server = Server(handlers + [health])

Testing:

.. code-block:: shell

  $ grpc_health_probe -addr=localhost:50051
  healthy: SERVING

Overall server status is always ``SERVING``.

If you want to add real checks:

.. code-block:: python3

  from grpclib.health.service import Health, OVERALL

  health = Health({OVERALL: [db_check, cache_check]})

Overall server status is ``SERVING`` if all checks are passing.

Detailed Services Health
------------------------

If you want to provide different checks for different services:

.. code-block:: python3

  foo = FooService()
  bar = BarService()

  health = Health({
      foo: [a_check, b_check],
      bar: [b_check, c_check],
  })

Testing:

.. code-block:: shell

  $ grpc_health_probe -addr=localhost:50051 -service acme.FooService
  healthy: SERVING
  $ grpc_health_probe -addr=localhost:50051 -service acme.BarService
  healthy: NOT_SERVING
  $ grpc_health_probe -addr=localhost:50051
  healthy: NOT_SERVING

- ``acme.FooService`` is healthy if ``a_check`` and ``b_check`` are passing
- ``acme.BarService`` is healthy if ``b_check`` and ``c_check`` are passing
- Overall health status depends on all checks

You can also override checks list for overall server's health status:

.. code-block:: python3

  foo = FooService()
  bar = BarService()

  health = Health({
      foo: [a_check, b_check],
      bar: [b_check, c_check],
      OVERALL: [a_check, c_check],
  })

Reference
~~~~~~~~~

.. automodule:: grpclib.health.service
    :members: Health, OVERALL

.. automodule:: grpclib.health.check
    :members: ServiceCheck, ServiceStatus

.. _Health Checking Protocol: https://github.com/grpc/grpc/blob/master/doc/health-checking.md
.. _grpc/health/v1/health.proto: https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
.. _grpc_health_probe: https://github.com/grpc-ecosystem/grpc-health-probe
