From 6e990342af6a84d1829c5d4bc4c6174f25705aeb Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Wed, 21 Aug 2024 09:56:34 -0300 Subject: [PATCH 1/7] test for multiple relations being scaled in/out together --- tests/integration/test_multi_relations.py | 91 +++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/integration/test_multi_relations.py diff --git a/tests/integration/test_multi_relations.py b/tests/integration/test_multi_relations.py new file mode 100644 index 000000000..dd88bd9aa --- /dev/null +++ b/tests/integration/test_multi_relations.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + + +from pathlib import Path + +import pytest +import yaml +from pytest_operator.plugin import OpsTest + +DB_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) + + +@pytest.mark.group(1) +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest): + """Build the charm and deploy 1 units to ensure a cluster is formed.""" + # Build and deploy charm from local source folder + db_charm = await ops_test.build_charm(".") + + config = {"profile": "testing"} + resources = {"mysql-image": DB_METADATA["resources"]["mysql-image"]["upstream-source"]} + + await ops_test.model.deploy( + db_charm, + application_name="mysql", + config=config, + num_units=1, + resources=resources, + series="jammy", + trust=True, + ) + + for i in range(1, 7): + await ops_test.model.deploy( + "mysql-test-app", + application_name=f"app{i}", + num_units=1, + channel="latest/edge", + ) + await ops_test.model.deploy( + "mysql-router-k8s", + application_name=f"router{i}", + num_units=1, + channel="8.0/edge", + trust=True, + ) + + +@pytest.mark.group(1) +@pytest.mark.abort_on_fail +async def test_relate_all(ops_test: OpsTest): + """Relate all the applications to the database.""" + for i in range(1, 7): + await ops_test.model.integrate("mysql:database", f"router{i}:backend-database") + await ops_test.model.integrate(f"app{i}:database", f"router{i}:database") + + await ops_test.model.block_until( + lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), + timeout=60 * 15, + wait_period=5, + ) + + +@pytest.mark.group(1) +@pytest.mark.abort_on_fail +async def test_scale_out(ops_test: OpsTest): + """Scale database and routers.""" + await ops_test.model.applications["mysql"].scale(3) + for i in range(1, 7): + await ops_test.model.applications[f"router{i}"].scale(3) + await ops_test.model.block_until( + lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), + timeout=60 * 15, + wait_period=5, + ) + + +@pytest.mark.group(1) +@pytest.mark.abort_on_fail +async def test_scale_in(ops_test: OpsTest): + """Scale database and routers.""" + await ops_test.model.applications["mysql"].scale(1) + for i in range(1, 7): + await ops_test.model.applications[f"router{i}"].scale(1) + await ops_test.model.block_until( + lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), + timeout=60 * 15, + wait_period=5, + ) From b3ccde535d15438de3ee02d8cf1d9cef5cc6ab33 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Wed, 21 Aug 2024 22:19:53 -0300 Subject: [PATCH 2/7] fallback for juju 2.x tests --- tests/integration/test_multi_relations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_multi_relations.py b/tests/integration/test_multi_relations.py index dd88bd9aa..d5d9ea57c 100644 --- a/tests/integration/test_multi_relations.py +++ b/tests/integration/test_multi_relations.py @@ -53,8 +53,8 @@ async def test_build_and_deploy(ops_test: OpsTest): async def test_relate_all(ops_test: OpsTest): """Relate all the applications to the database.""" for i in range(1, 7): - await ops_test.model.integrate("mysql:database", f"router{i}:backend-database") - await ops_test.model.integrate(f"app{i}:database", f"router{i}:database") + await ops_test.model.relate("mysql:database", f"router{i}:backend-database") + await ops_test.model.relate(f"app{i}:database", f"router{i}:database") await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), From bf9025f9ee48951d146e78027878df89e68532b3 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Thu, 22 Aug 2024 16:39:01 -0300 Subject: [PATCH 3/7] increased scale --- tests/integration/test_multi_relations.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_multi_relations.py b/tests/integration/test_multi_relations.py index d5d9ea57c..77238068e 100644 --- a/tests/integration/test_multi_relations.py +++ b/tests/integration/test_multi_relations.py @@ -10,6 +10,7 @@ from pytest_operator.plugin import OpsTest DB_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) +SCALE_OUT = 7 @pytest.mark.group(1) @@ -26,13 +27,13 @@ async def test_build_and_deploy(ops_test: OpsTest): db_charm, application_name="mysql", config=config, - num_units=1, + num_units=3, resources=resources, series="jammy", trust=True, ) - for i in range(1, 7): + for i in range(1, SCALE_OUT + 1): await ops_test.model.deploy( "mysql-test-app", application_name=f"app{i}", @@ -52,7 +53,7 @@ async def test_build_and_deploy(ops_test: OpsTest): @pytest.mark.abort_on_fail async def test_relate_all(ops_test: OpsTest): """Relate all the applications to the database.""" - for i in range(1, 7): + for i in range(1, SCALE_OUT + 1): await ops_test.model.relate("mysql:database", f"router{i}:backend-database") await ops_test.model.relate(f"app{i}:database", f"router{i}:database") @@ -67,8 +68,8 @@ async def test_relate_all(ops_test: OpsTest): @pytest.mark.abort_on_fail async def test_scale_out(ops_test: OpsTest): """Scale database and routers.""" - await ops_test.model.applications["mysql"].scale(3) - for i in range(1, 7): + await ops_test.model.applications["mysql"].scale(5) + for i in range(1, SCALE_OUT + 1): await ops_test.model.applications[f"router{i}"].scale(3) await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), @@ -82,7 +83,7 @@ async def test_scale_out(ops_test: OpsTest): async def test_scale_in(ops_test: OpsTest): """Scale database and routers.""" await ops_test.model.applications["mysql"].scale(1) - for i in range(1, 7): + for i in range(1, SCALE_OUT + 1): await ops_test.model.applications[f"router{i}"].scale(1) await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), From cea086204bf2381462a069bc0fdd64b37879d6f0 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 23 Aug 2024 17:05:18 -0300 Subject: [PATCH 4/7] use different dbs --- tests/integration/test_multi_relations.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_multi_relations.py b/tests/integration/test_multi_relations.py index 77238068e..57a4a4f3d 100644 --- a/tests/integration/test_multi_relations.py +++ b/tests/integration/test_multi_relations.py @@ -33,12 +33,14 @@ async def test_build_and_deploy(ops_test: OpsTest): trust=True, ) - for i in range(1, SCALE_OUT + 1): + for i in range(SCALE_OUT): + config = {"database_name": f"database{i}"} await ops_test.model.deploy( "mysql-test-app", application_name=f"app{i}", num_units=1, channel="latest/edge", + config=config, ) await ops_test.model.deploy( "mysql-router-k8s", @@ -53,7 +55,7 @@ async def test_build_and_deploy(ops_test: OpsTest): @pytest.mark.abort_on_fail async def test_relate_all(ops_test: OpsTest): """Relate all the applications to the database.""" - for i in range(1, SCALE_OUT + 1): + for i in range(SCALE_OUT): await ops_test.model.relate("mysql:database", f"router{i}:backend-database") await ops_test.model.relate(f"app{i}:database", f"router{i}:database") @@ -69,7 +71,7 @@ async def test_relate_all(ops_test: OpsTest): async def test_scale_out(ops_test: OpsTest): """Scale database and routers.""" await ops_test.model.applications["mysql"].scale(5) - for i in range(1, SCALE_OUT + 1): + for i in range(SCALE_OUT): await ops_test.model.applications[f"router{i}"].scale(3) await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), @@ -83,7 +85,7 @@ async def test_scale_out(ops_test: OpsTest): async def test_scale_in(ops_test: OpsTest): """Scale database and routers.""" await ops_test.model.applications["mysql"].scale(1) - for i in range(1, SCALE_OUT + 1): + for i in range(SCALE_OUT): await ops_test.model.applications[f"router{i}"].scale(1) await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), From ea2f78f6dfa0c7bb7f880f68a331f9148a9a5de8 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Mon, 9 Sep 2024 10:31:17 -0300 Subject: [PATCH 5/7] fix block until to account for units count --- tests/integration/test_multi_relations.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_multi_relations.py b/tests/integration/test_multi_relations.py index 57a4a4f3d..bb2074487 100644 --- a/tests/integration/test_multi_relations.py +++ b/tests/integration/test_multi_relations.py @@ -27,14 +27,14 @@ async def test_build_and_deploy(ops_test: OpsTest): db_charm, application_name="mysql", config=config, - num_units=3, + num_units=1, resources=resources, series="jammy", trust=True, ) for i in range(SCALE_OUT): - config = {"database_name": f"database{i}"} + config = {"database_name": f"database{i}", "sleep_interval": "2000"} await ops_test.model.deploy( "mysql-test-app", application_name=f"app{i}", @@ -73,8 +73,10 @@ async def test_scale_out(ops_test: OpsTest): await ops_test.model.applications["mysql"].scale(5) for i in range(SCALE_OUT): await ops_test.model.applications[f"router{i}"].scale(3) + expected_unit_sum = 5 + 4 * SCALE_OUT await ops_test.model.block_until( - lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), + lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()) + and len(ops_test.model.units) == expected_unit_sum, timeout=60 * 15, wait_period=5, ) @@ -87,8 +89,10 @@ async def test_scale_in(ops_test: OpsTest): await ops_test.model.applications["mysql"].scale(1) for i in range(SCALE_OUT): await ops_test.model.applications[f"router{i}"].scale(1) + expected_unit_sum = 1 + 2 * SCALE_OUT await ops_test.model.block_until( - lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), + lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()) + and len(ops_test.model.units) == expected_unit_sum, timeout=60 * 15, wait_period=5, ) From 0fe3517aea6ff0a3e9ed7fed035bed3fc2cdaf2f Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Mon, 9 Sep 2024 10:32:02 -0300 Subject: [PATCH 6/7] avoid pod label update for every relation --- src/relations/mysql_provider.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/relations/mysql_provider.py b/src/relations/mysql_provider.py index 219afe8db..0eedfd88b 100644 --- a/src/relations/mysql_provider.py +++ b/src/relations/mysql_provider.py @@ -222,11 +222,13 @@ def _configure_endpoints(self, _) -> None: relation_data = self.database.fetch_relation_data() for relation in relations: - # only update endpoints if on_database_requested has executed - if relation.id not in relation_data: - continue + # only update endpoints if on_database_requested on any + # relation + if relation.id in relation_data: + break + return - self.charm._mysql.update_endpoints() + self.charm._mysql.update_endpoints() def _on_update_status(self, _) -> None: """Handle the update status event. From e2571e433b695bf8fe0ad8fee0ff40d856760cb7 Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Mon, 9 Sep 2024 17:22:11 -0300 Subject: [PATCH 7/7] bigger timeouts required --- tests/integration/test_multi_relations.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/integration/test_multi_relations.py b/tests/integration/test_multi_relations.py index bb2074487..271e96605 100644 --- a/tests/integration/test_multi_relations.py +++ b/tests/integration/test_multi_relations.py @@ -10,7 +10,8 @@ from pytest_operator.plugin import OpsTest DB_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) -SCALE_OUT = 7 +SCALE_APPS = 7 +SCALE_UNITS = 3 @pytest.mark.group(1) @@ -33,7 +34,7 @@ async def test_build_and_deploy(ops_test: OpsTest): trust=True, ) - for i in range(SCALE_OUT): + for i in range(SCALE_APPS): config = {"database_name": f"database{i}", "sleep_interval": "2000"} await ops_test.model.deploy( "mysql-test-app", @@ -55,13 +56,13 @@ async def test_build_and_deploy(ops_test: OpsTest): @pytest.mark.abort_on_fail async def test_relate_all(ops_test: OpsTest): """Relate all the applications to the database.""" - for i in range(SCALE_OUT): + for i in range(SCALE_APPS): await ops_test.model.relate("mysql:database", f"router{i}:backend-database") await ops_test.model.relate(f"app{i}:database", f"router{i}:database") await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()), - timeout=60 * 15, + timeout=60 * 25, wait_period=5, ) @@ -70,14 +71,14 @@ async def test_relate_all(ops_test: OpsTest): @pytest.mark.abort_on_fail async def test_scale_out(ops_test: OpsTest): """Scale database and routers.""" - await ops_test.model.applications["mysql"].scale(5) - for i in range(SCALE_OUT): - await ops_test.model.applications[f"router{i}"].scale(3) - expected_unit_sum = 5 + 4 * SCALE_OUT + await ops_test.model.applications["mysql"].scale(SCALE_UNITS) + for i in range(SCALE_APPS): + await ops_test.model.applications[f"router{i}"].scale(SCALE_UNITS) + expected_unit_sum = SCALE_UNITS + 4 * SCALE_APPS await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()) and len(ops_test.model.units) == expected_unit_sum, - timeout=60 * 15, + timeout=60 * 30, wait_period=5, ) @@ -87,9 +88,9 @@ async def test_scale_out(ops_test: OpsTest): async def test_scale_in(ops_test: OpsTest): """Scale database and routers.""" await ops_test.model.applications["mysql"].scale(1) - for i in range(SCALE_OUT): + for i in range(SCALE_APPS): await ops_test.model.applications[f"router{i}"].scale(1) - expected_unit_sum = 1 + 2 * SCALE_OUT + expected_unit_sum = 1 + 2 * SCALE_APPS await ops_test.model.block_until( lambda: all(unit.workload_status == "active" for unit in ops_test.model.units.values()) and len(ops_test.model.units) == expected_unit_sum,