Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

659 Add Postgres and Redis to health endpoint #690

Merged
merged 10 commits into from
Jun 22, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The types of changes are:
* Adds Fideslog integration [#541](https://github.com/ethyca/fidesops/pull/541)
* Adds endpoint analytics events [#622](https://github.com/ethyca/fidesops/pull/622)
* Sample dataset and access configuration for Zendesk (ticket endpoints) [#677](https://github.com/ethyca/fidesops/pull/677)
* Adds Postgres and Redis health checks to health endpoint [#690](https://github.com/ethyca/fidesops/pull/690)

### Changed

Expand Down
4 changes: 0 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ services:
dockerfile: Dockerfile
command: celery -A fidesops.tasks beat -l info --logfile=/var/log/celery.log
depends_on:
fidesops:
condition: service_healthy
db:
condition: service_started
redis:
condition: service_started
restart: always
Expand Down
2 changes: 1 addition & 1 deletion docs/fidesops/docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,6 @@ Note that there's no need for a persistent volume mount for the web server, it's

### Test the Web Server

To test that your server is running, visit `http://{server_url}/health` in your browser (e.g. http://0.0.0.0:8080/health) and you should see `{ "healthy": true }`.
To test that your server is running, visit `http://{server_url}/health` in your browser (e.g. http://0.0.0.0:8080/health) and you should see `{"healthy": True, "database": "healthy", "cache": "healthy"}`.

You now have a functional `fidesops` server running! Now you can use the API to set up your OAuth clients, connect to databases, configure policies, execute privacy requests, etc. To learn more, head to the [How-To Guides](guides/oauth.md) for details.
4 changes: 2 additions & 2 deletions docs/fidesops/docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Ensure nothing is running on ports `8080`, `5432`, or `6379` prior to these step

2. Run `docker compose up` from the root of the Fidesops project directory. The provided `docker-compose.yml` will create the necessary databases and spin up the server.

3. Visit `http://0.0.0.0:8080/health` in your browser. A response of `{ "healthy": true }` indicates a successful deployment.
3. Visit `http://0.0.0.0:8080/health` in your browser. A response of `{"healthy": True, "database": "healthy", "cache": "healthy"}` indicates a successful deployment.

## Build From Your Project

Expand Down Expand Up @@ -86,7 +86,7 @@ Ensure nothing is running on ports `8080`, `5432`, or `6379` prior to these step

2. Ensure Docker is running, and run `docker compose up` from the project's root directory. This will pull the latest Fidesops Docker image, create the sample databases, and start the server.

3. Visit `http://0.0.0.0:8080/health` in your browser. A response of `{ "healthy": true }` indicates a successful deployment.
3. Visit `http://0.0.0.0:8080/health` in your browser. A response of `{"healthy": True, "database": "healthy", "cache": "healthy"}` indicates a successful deployment.

## Next Steps
You now have a working test installation of Fidesops! From here, use the available [How-To Guides](guides/oauth.md) to view examples of authenticating, connecting to databases, configuring policies, and more.
54 changes: 51 additions & 3 deletions src/fidesops/api/v1/endpoints/health_endpoints.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,62 @@
from typing import Dict
import logging
from typing import Dict, Optional, Union

from alembic import migration, script
from fastapi import APIRouter
from redis.exceptions import ResponseError
from sqlalchemy import create_engine

from fidesops.api.v1.urn_registry import HEALTH
from fidesops.common_exceptions import RedisConnectionError
from fidesops.core.config import config
from fidesops.db.database import get_alembic_config
from fidesops.util.cache import get_cache

router = APIRouter(tags=["Public"])

logger = logging.getLogger(__name__)
# stops polluting logs with sqlalchemy / alembic info-level logs
logging.getLogger("sqlalchemy.engine").setLevel(logging.ERROR)
logging.getLogger("alembic").setLevel(logging.WARNING)

@router.get(HEALTH, response_model=Dict[str, bool])
def health_check() -> Dict[str, bool]:

@router.get(HEALTH, response_model=Dict[str, Union[bool, str]])
def health_check() -> Dict[str, Union[bool, str]]:
return {
"healthy": True,
Copy link
Contributor

@seanpreston seanpreston Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be great if we could change this response to "webserver": "healthy" to standardise the format across the services. Or alternatively something like:

{
  "healthy": True,
  "db": {
    "healthy": True
  },
  "cache": {
    "healthy": True
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, I've updated to "webserver": "healthy"

"database": get_db_health(config.database.SQLALCHEMY_DATABASE_URI),
"cache": get_cache_health(),
}


def get_db_health(database_url: Optional[str]) -> str:
"""Checks if the db is reachable and up to date in alembic migrations"""
if not database_url or not config.database.ENABLED:
return "no db configured"
try:
engine = create_engine(database_url)
alembic_config = get_alembic_config(database_url)
alembic_script_directory = script.ScriptDirectory.from_config(alembic_config)
with engine.begin() as conn:
context = migration.MigrationContext.configure(conn)
if (
context.get_current_revision()
!= alembic_script_directory.get_current_head()
):
return "needs migration"
return "healthy"
except Exception as error: # pylint: disable=broad-except
logger.error(f"Unable to reach the database: {error}")
return "unhealthy"


def get_cache_health() -> str:
"""Checks if the cache is reachable"""
if not config.redis.ENABLED:
return "no cache configured"
try:
get_cache()
return "healthy"
except (RedisConnectionError, ResponseError) as e:
logger.error(f"Unable to reach cache: {e}")
return "unhealthy"
6 changes: 5 additions & 1 deletion tests/api/v1/endpoints/test_health_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@

def test_health(api_client: TestClient) -> None:
response = api_client.get(HEALTH)
assert response.json() == {"healthy": True}
assert response.json() == {
"healthy": True,
"database": "healthy",
"cache": "healthy",
}
12 changes: 10 additions & 2 deletions tests/api/v1/test_exception_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ def test_db_disabled(self, api_client: TestClient, generate_auth_header):
assert expected_response == response_body

# health endpoint should still work
expected_response = {"healthy": True}
expected_response = {
"healthy": True,
"database": "no db configured",
"cache": "healthy",
}
response = api_client.get(HEALTH)
response_body = json.loads(response.text)
assert 200 == response.status_code
Expand Down Expand Up @@ -66,7 +70,11 @@ def test_redis_disabled(self, api_client: TestClient, generate_auth_header):
assert expected_response == response_body

# health endpoint should still work
expected_response = {"healthy": True}
expected_response = {
"healthy": True,
"database": "healthy",
"cache": "no cache configured",
}
response = api_client.get(HEALTH)
response_body = json.loads(response.text)
assert 200 == response.status_code
Expand Down