Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(healthz): overhaul health-checks for them to be less intrusive #2070

Merged
merged 1 commit into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 4 additions & 251 deletions caluma/caluma_core/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
import sys

import pytest
import requests
from django.conf import settings
from django.core import management
from django.db import connection
from minio import Minio
from psycopg2 import OperationalError
from requests.models import Response
from watchman.decorators import check as watchman_check_decorator

from caluma.caluma_form import storage_clients
Expand Down Expand Up @@ -83,179 +80,12 @@ def db_broken_connection(transactional_db):
connection.connect()


def switch_user(test=False):
"""Re-connect to database with either user 'tester' or 'caluma'."""
# set username and password
user = "tester" if test else "caluma"
password = "test" if test else "caluma"

# connect new user to database
connection.close()
settings.DATABASES["default"]["USER"] = user
settings.DATABASES["default"]["PASSWORD"] = password
connection.connect()


@pytest.fixture
def db_user_tester(transactional_db):
"""Create new user 'tester' with privileges necessary for health checks.

User is removed after use.
"""
# get db connection
cursor = connection.cursor()

# create test user (with all necessary privileges)
cursor.execute("CREATE USER tester WITH PASSWORD 'test';")
cursor.execute("GRANT ALL ON caluma_logging_accesslog TO tester;")
cursor.execute("GRANT ALL ON caluma_form_file TO tester;")
cursor.execute(
"GRANT USAGE, SELECT ON SEQUENCE caluma_logging_accesslog_id_seq TO tester;"
)
cursor.execute("GRANT ALL ON django_migrations TO tester;")

yield

# get db connection
cursor = connection.cursor()

# revoke privileges and remove user
cursor.execute("REVOKE ALL ON caluma_logging_accesslog FROM tester;")
cursor.execute("REVOKE ALL ON caluma_form_file FROM tester;")
cursor.execute(
"REVOKE USAGE, SELECT ON SEQUENCE caluma_logging_accesslog_id_seq FROM tester;"
)
cursor.execute("REVOKE ALL ON django_migrations FROM tester;")
cursor.execute("DROP USER tester;")


@pytest.fixture
def db_privileges_working(db_user_tester):
"""DB fixture with all necessary privileges for health checks.

Create new test user with necessary privileges and connect to database.
Remove test user and reconnect with original database user after use.
"""
# switch to new test user
switch_user(test=True)

yield

# switch back to caluma user
switch_user()


@pytest.fixture
def db_read_error(db_user_tester):
"""DB fixture with revoked read privileges on caluma_logging_accesslog.

Create new test user without necessary read privileges for table used by
health check and connect to database.
Remove test user and reconnect with original database user after use.
"""
# revoke select rights on accesslog table
cursor = connection.cursor()
cursor.execute("REVOKE SELECT ON caluma_logging_accesslog FROM tester;")

# switch to new test user
switch_user(test=True)

yield

# switch back to caluma user
switch_user()

# grant select rights on accesslog table such that user can be removed
cursor = connection.cursor()
cursor.execute("GRANT SELECT ON caluma_logging_accesslog TO tester;")


@pytest.fixture
def db_write_error(db_user_tester):
"""DB fixture with revoked write privileges on caluma_logging_accesslog.

Create new test user without necessary write privileges for table used by
health check and connect to database.
Remove test user and reconnect with original database user after use.
"""
# revoke insert, update and delete privileges on accesslog table
cursor = connection.cursor()
cursor.execute(
"REVOKE INSERT,UPDATE,DELETE ON caluma_logging_accesslog FROM tester;"
)

# switch to new test user
switch_user(test=True)

yield

# switch back to caluma user
switch_user()

# grant insert, update, delete rights on accesslog table
# such that user can be removed
cursor = connection.cursor()
cursor.execute("GRANT INSERT,UPDATE,DELETE ON caluma_logging_accesslog TO tester;")


@pytest.fixture
def minio_mock_working(minio_mock, mocker):
"""Provide working minio mock for health checks.

Based on existing minio_mock.
Patches requests.get and requests.put to provide necessary responses for
health check.
"""
# determine fixed UUID to be used during tests
uuid = "7c5ad424-e351-4db8-ba37-bbace45d0e0f"
mocker.patch("uuid.uuid4", return_value=uuid)

# mock upload response
upload_response = Response()
upload_response.status_code = 200
mocker.patch("requests.put", return_value=upload_response)

# mock download response
download_response = Response()
download_response.status_code = 200
download_response._content = (
b"--ffc537c8d0da305912d6440576ac0d75\r\n"
b'Content-Disposition: form-data; name="file"; '
b'filename="healthz_tmp-file.txt"\r\n\r\n'
b"Test\nTest\n\r\n"
b"--ffc537c8d0da305912d6440576ac0d75--\r\n"
def minio_mock_working(mocker):
"""Provide working minio mock for health checks."""
return mocker.patch.object(
storage_clients.client.client, "bucket_exists", return_value=False
)
mocker.patch("requests.get", return_value=download_response)

# change object name of existing stat response
stat_response = Minio.stat_object.return_value
stat_response._object_name = "healthz_tmp-object_" + uuid

# update responses compatible with media storage service health check
Minio.stat_object.side_effect = [stat_response, None]


@pytest.fixture
def minio_broken_connection():
"""Provide new minio client with faulty connection details.

Change minio settings to generate new client unable to connect to minio
instance.
Restore original settings and client after use.
"""
# store previous client
prev_client = storage_clients.client

# reconnect with new client (with invalid host settings)
prev_settings = settings.MINIO_STORAGE_ENDPOINT
settings.MINIO_STORAGE_ENDPOINT = "not-minio:9000"
storage_clients.client = storage_clients.Minio()

yield

# restore previous client
settings.MINIO_STORAGE_ENDPOINT = prev_settings
storage_clients.client = prev_client


@pytest.fixture
Expand All @@ -276,83 +106,6 @@ def minio_not_configured():
storage_clients.client = prev_client


@pytest.fixture
def minio_mock_read_error(minio_mock, mocker):
"""Produce exception when downloading file or stat-ing object.

This scenario might occur when there is a internal minio issue or if a
previous upload isn't successful (which doesn't issue exception).
"""

# raise exception instead of handling request
def download_side_effect(*args, **kwargs): # pragma: no cover
raise requests.exceptions.RequestException("GET request failed")

def stat_side_effect(*args, **kwargs):
raise Exception("stat_object failed")

# determine fixed UUID to be used during tests
uuid = "7c5ad424-e351-4db8-ba37-bbace45d0e0f"
mocker.patch("uuid.uuid4", return_value=uuid)

# mock upload response
upload_response = Response()
upload_response.status_code = 200
mocker.patch("requests.put", return_value=upload_response)

# mock erroneous download response
download_response = Response()
download_response.status_code = 500
mocker.patch(
"requests.get", return_value=download_response, side_effect=download_side_effect
)

# raise exception
Minio.stat_object.side_effect = stat_side_effect


@pytest.fixture
def minio_mock_write_error(minio_mock, mocker):
"""Produce exception when uploading file.

This scenario might occur when there is a internal minio issue.
"""

# raise exception instead of handling request
def upload_side_effect(*args, **kwargs):
raise requests.exceptions.RequestException("PUT request failed")

# determine fixed UUID to be used during tests
uuid = "7c5ad424-e351-4db8-ba37-bbace45d0e0f"
mocker.patch("uuid.uuid4", return_value=uuid)

# mock upload response
upload_response = Response()
upload_response.status_code = 500
mocker.patch(
"requests.put", return_value=upload_response, side_effect=upload_side_effect
)

# mock download response
download_response = Response()
download_response.status_code = 200
download_response._content = (
b"--ffc537c8d0da305912d6440576ac0d75\r\n"
b'Content-Disposition: form-data; name="file"; '
b'filename="healthz_tmp-file.txt"\r\n\r\n'
b"Test\nTest\n\r\n"
b"--ffc537c8d0da305912d6440576ac0d75--\r\n"
)
mocker.patch("requests.get", return_value=download_response)

# change object name of existing stat response
stat_response = Minio.stat_object.return_value
stat_response._object_name = "healthz_tmp-object_" + uuid

# update responses compatible with media storage service health check
Minio.stat_object.side_effect = [stat_response, None]


@pytest.fixture
def roll_back_migrations():
"""Roll back database migrations to ensure unapplied migrations exist."""
Expand Down
Loading
Loading