diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 3dbe6f6..5a7f796 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -10,6 +10,7 @@ jobs: matrix: tox-environments: - integration-charm + - integration-scaling - integration-upgrades runs-on: ubuntu-latest steps: diff --git a/src/charm.py b/src/charm.py index b0e114f..198d358 100755 --- a/src/charm.py +++ b/src/charm.py @@ -9,11 +9,16 @@ import ops from charms.data_platform_libs.v0.data_interfaces import DatabaseRequires from charms.nginx_ingress_integrator.v0.nginx_route import require_nginx_route -from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.model import ( + ActiveStatus, + BlockedStatus, + MaintenanceStatus, + WaitingStatus, +) from ops.pebble import CheckStatus from groups import RangerGroupManager -from literals import APPLICATION_PORT +from literals import APP_NAME, APPLICATION_PORT from relations.postgres import PostgresRelationHandler from relations.provider import RangerProvider from state import State @@ -52,6 +57,9 @@ def __init__(self, *args): self.framework.observe(self.on.config_changed, self._on_config_changed) self.framework.observe(self.on.update_status, self._on_update_status) self.framework.observe(self.on.restart_action, self._on_restart) + self.framework.observe( + self.on.peer_relation_changed, self._on_peer_relation_changed + ) self.postgres_relation = DatabaseRequires( self, @@ -104,6 +112,19 @@ def _on_config_changed(self, event: ops.ConfigChangedEvent): """ self.update(event) + @log_event_handler(logger) + def _on_peer_relation_changed(self, event): + """Handle peer relation changes. + + Args: + event: The event triggered when the peer relation changed. + """ + if self.unit.is_leader(): + return + + self.unit.status = WaitingStatus(f"configuring {APP_NAME}") + self.update(event) + @log_event_handler(logger) def _on_update_status(self, event): """Handle `update-status` events. diff --git a/src/literals.py b/src/literals.py index e797401..8a0b4fd 100644 --- a/src/literals.py +++ b/src/literals.py @@ -22,3 +22,4 @@ "group": "groups", "membership": "groupusers", } +APP_NAME = "ranger-k8s" diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index c836561..f5a8ab0 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -65,6 +65,43 @@ async def get_unit_url( return f"{protocol}://{address}:{port}" +async def get_application_url(ops_test: OpsTest, application, port): + """Returns application URL from the model. + + Args: + ops_test: PyTest object. + application: Name of the application. + port: Port number of the URL. + + Returns: + Application URL of the form http://{address}:{port} + """ + status = await ops_test.model.get_status() # noqa: F821 + address = status["applications"][application].public_address + return f"http://{address}:{port}" + + +async def scale(ops_test: OpsTest, app, units): + """Scale the application to the provided number and wait for idle. + + Args: + ops_test: PyTest object. + app: Application to be scaled. + units: Number of units required. + """ + await ops_test.model.applications[app].scale(scale=units) + + # Wait for model to settle + await ops_test.model.wait_for_idle( + apps=[app], + status="active", + idle_period=30, + raise_on_blocked=True, + timeout=600, + wait_for_exact_units=units, + ) + + async def get_memberships(ops_test: OpsTest, url): """Return membership from Ranger. @@ -89,6 +126,7 @@ async def get_memberships(ops_test: OpsTest, url): ) raise data = json.loads(response.text) + logger.info(data) group = data["vXGroupUsers"][0].get("name") user_id = data["vXGroupUsers"][0].get("userId") membership = (group, user_id) diff --git a/tests/integration/test_scaling.py b/tests/integration/test_scaling.py new file mode 100644 index 0000000..fca6946 --- /dev/null +++ b/tests/integration/test_scaling.py @@ -0,0 +1,42 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Charm scaling integration test.""" + +import logging + +import pytest +from helpers import APP_NAME, get_application_url, get_memberships, scale +from pytest_operator.plugin import OpsTest + +logger = logging.getLogger(__name__) + + +@pytest.mark.abort_on_fail +@pytest.mark.usefixtures("deploy") +class TestScaling: + """Integration tests for scaling Ranger charm.""" + + async def test_scaling_up(self, ops_test: OpsTest): + """Scale Ranger charm up to 2 units.""" + await scale(ops_test, app=APP_NAME, units=2) + assert len(ops_test.model.applications[APP_NAME].units) == 2 + + url = await get_application_url( + ops_test, application=APP_NAME, port=6080 + ) + membership = await get_memberships(ops_test, url) + logger.info(f"Ranger memberships: {membership}") + assert membership == ("commercial-systems", 8) + + async def test_scaling_down(self, ops_test: OpsTest): + """Scale Superset charm down to 1 unit.""" + await scale(ops_test, app=APP_NAME, units=1) + assert len(ops_test.model.applications[APP_NAME].units) == 1 + + url = await get_application_url( + ops_test, application=APP_NAME, port=6080 + ) + membership = await get_memberships(ops_test, url) + logger.info(f"Ranger memberships: {membership}") + assert membership == ("commercial-systems", 8) diff --git a/tests/integration/test_upgrades.py b/tests/integration/test_upgrades.py index 97147bc..30c4a51 100644 --- a/tests/integration/test_upgrades.py +++ b/tests/integration/test_upgrades.py @@ -25,9 +25,7 @@ async def deploy(ops_test: OpsTest): ) ranger_config = {"ranger-admin-password": SECURE_PWD} - await ops_test.model.deploy( - APP_NAME, channel="edge", config=ranger_config - ) + await ops_test.model.deploy(APP_NAME, channel="edge", config=ranger_config) async with ops_test.fast_forward(): await ops_test.model.wait_for_idle( diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index c19434c..de32037 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -146,7 +146,7 @@ def test_ready(self): }, } got_plan = harness.get_container_pebble_plan("ranger").to_dict() - self.assertEqual(got_plan, want_plan) + self.assertEqual(got_plan["services"], want_plan["services"]) # The service was started. service = harness.model.unit.get_container("ranger").get_service( diff --git a/tox.ini b/tox.ini index 0224f71..fb27a06 100644 --- a/tox.ini +++ b/tox.ini @@ -40,12 +40,24 @@ description = Run integration tests deps = ipdb==0.13.9 juju==3.1.0.1 - pytest==7.4.0 - pytest-operator==0.29.0 + pytest==7.1.3 + pytest-operator==0.22.0 + pytest-asyncio==0.21 + -r{toxinidir}/requirements.txt +commands = + pytest {[vars]tst_path}integration/test_charm.py -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} + +[testenv:integration-scaling] +description = Run integration tests +deps = + ipdb==0.13.9 + juju==3.1.0.1 + pytest==7.1.3 + pytest-operator==0.22.0 pytest-asyncio==0.21 -r{toxinidir}/requirements.txt commands = - pytest {[vars]tst_path}integration/test_charm.py -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} + pytest {[vars]tst_path}integration/test_scaling.py -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} [testenv:integration-upgrades] description = Run integration tests