Skip to content
This repository has been archived by the owner on Oct 16, 2024. It is now read-only.

Require a database for Django to start #42

Merged
merged 2 commits into from
Sep 11, 2024
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
23 changes: 23 additions & 0 deletions paas_app_charmer/django/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ def get_cos_dir(self) -> str:
"""
return str((pathlib.Path(__file__).parent / "cos").absolute())

def is_ready(self) -> bool:
"""Check if the charm is ready to start the workload application.

For Django, at least one database is needed. Migrations will be run on startup
and without that integration it will fail.

Returns:
True if the charm is ready to start the workload application.
"""
if not super().is_ready():
return False

# At this point all integrations are correctly configured. If there is no database uri,
# it means that there is no integration for databases or they are optional and no one
# is set.
charm_state = self._create_charm_state()
if not charm_state.integrations.databases_uris:
self.update_app_and_unit_status(
ops.BlockedStatus("Django requires a database integration to work")
)
return False
return True

def _on_create_superuser_action(self, event: ops.ActionEvent) -> None:
"""Handle the create-superuser action.

Expand Down
76 changes: 63 additions & 13 deletions tests/unit/django/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import pathlib
import shlex
import textwrap
import typing
import unittest.mock

Expand All @@ -28,7 +29,67 @@ def cwd():
@pytest.fixture(name="harness")
def harness_fixture() -> typing.Generator[Harness, None, None]:
"""Ops testing framework harness fixture."""
harness = Harness(DjangoCharm)
harness = _build_harness()
yield harness
harness.cleanup()


@pytest.fixture(name="harness_no_integrations")
def harness_no_integrations_fixture() -> typing.Generator[Harness, None, None]:
"""Ops testing framework harness fixture without a database."""
meta = textwrap.dedent(
"""
name: django-k8s

bases:
- build-on:
- name: ubuntu
channel: "22.04"
run-on:
- name: ubuntu
channel: "22.04"

summary: An example Django application.

description: An example Django application.

containers:
django-app:
resource: django-app-image

peers:
secret-storage:
interface: secret-storage
provides:
grafana-dashboard:
interface: grafana_dashboard
metrics-endpoint:
interface: prometheus_scrape
requires:
ingress:
interface: ingress
limit: 1
logging:
interface: loki_push_api
"""
)
harness = _build_harness(meta)
yield harness
harness.cleanup()


@pytest.fixture
def database_migration_mock():
"""Create a mock instance for the DatabaseMigration class."""
mock = unittest.mock.MagicMock()
mock.status = DatabaseMigrationStatus.PENDING
mock.script = None
return mock


def _build_harness(meta=None):
"""Create a harness instance with the specified metadata."""
harness = Harness(DjangoCharm, meta=meta)
harness.set_leader()
container = "django-app"
root = harness.get_filesystem_root(container)
Expand All @@ -51,15 +112,4 @@ def check_config_handler(_):
check_config_command,
handler=check_config_handler,
)

yield harness
harness.cleanup()


@pytest.fixture
def database_migration_mock():
"""Create a mock instance for the DatabaseMigration class."""
mock = unittest.mock.MagicMock()
mock.status = DatabaseMigrationStatus.PENDING
mock.script = None
return mock
return harness
22 changes: 20 additions & 2 deletions tests/unit/django/test_charm.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

"""Flask charm unit tests."""
"""Django charm unit tests."""

# this is a unit test file
# pylint: disable=protected-access

import unittest.mock

import ops
import pytest
from ops.testing import ExecArgs, ExecResult, Harness

from examples.django.charm.src.charm import DjangoCharm
from paas_app_charmer._gunicorn.webserver import GunicornWebserver, WebserverConfig
from paas_app_charmer._gunicorn.workload_config import create_workload_config
from paas_app_charmer._gunicorn.wsgi_app import WsgiApp
Expand Down Expand Up @@ -43,7 +45,7 @@ def test_django_config(harness: Harness, config: dict, env: dict) -> None:
"""
arrange: none
act: start the django charm and set django-app container to be ready.
assert: flask charm should submit the correct flaks pebble layer to pebble.
assert: django charm should submit the correct pebble layer to pebble.
"""
harness.begin()
container = harness.charm.unit.get_container("django-app")
Expand Down Expand Up @@ -124,3 +126,19 @@ def handler(args: ExecArgs) -> None | ExecResult:
)
assert "password" in output.results
assert output.results["password"] == password


def test_required_database_integration(harness_no_integrations: Harness):
"""
arrange: Start the Django charm with no integrations specified in the charm.
act: Start the django charm and set django-app container to be ready.
assert: The charm should be blocked, as Django requires a database to work.
"""
harness = harness_no_integrations
container = harness.model.unit.get_container("django-app")
container.add_layer("a_layer", DEFAULT_LAYER)

harness.begin_with_initial_hooks()
assert harness.model.unit.status == ops.BlockedStatus(
"Django requires a database integration to work"
)
Loading