diff --git a/kubernetes/tests/integration/helpers_ha.py b/kubernetes/tests/integration/helpers_ha.py index 1484a65976..9c3548b802 100644 --- a/kubernetes/tests/integration/helpers_ha.py +++ b/kubernetes/tests/integration/helpers_ha.py @@ -717,3 +717,57 @@ def continuous_writes_ctx(juju: Juju, app_name: str) -> Generator: logging.info("Clearing continuous writes") juju.run(test_app_leader, "clear-continuous-writes") + + +def refresh_mysql_server(juju: Juju, app_name: str, charm_path: str) -> None: + """Refresh the mysql server charm and drive the refresh workflow to completion.""" + mysql_leader = get_app_leader(juju, app_name) + mysql_units = get_app_units(juju, app_name) + mysql_units.sort() + + juju.refresh( + app=app_name, + path=charm_path, + resources={"mysql-image": CHARM_METADATA["resources"]["mysql-image"]["upstream-source"]}, + ) + + logging.info("Wait for refresh to start") + juju.wait( + ready=wait_for_apps_status(jubilant.any_blocked, app_name), + timeout=5 * MINUTE_SECS, + ) + juju.wait( + ready=wait_for_unit_status(app_name, mysql_units[-1], "blocked"), + timeout=5 * MINUTE_SECS, + ) + + mysql_status = juju.status().apps[app_name] + upgrade_unit_message = mysql_status.units[mysql_units[-1]].workload_status.message + + if "Refresh incompatible" in upgrade_unit_message: + logging.info("Application refresh is blocked due to incompatibility") + juju.run( + unit=mysql_units[-1], + action="force-refresh-start", + params={"check-compatibility": False}, + wait=5 * MINUTE_SECS, + ) + + logging.info("Wait for refresh to finish on first unit") + juju.wait( + ready=jubilant.all_agents_idle, + timeout=5 * MINUTE_SECS, + ) + + logging.info("Resume refresh") + juju.run( + unit=mysql_leader, + action="resume-refresh", + wait=5 * MINUTE_SECS, + ) + + logging.info("Wait for refresh to complete") + juju.wait( + ready=wait_for_apps_status(jubilant.all_active, app_name), + timeout=20 * MINUTE_SECS, + ) diff --git a/kubernetes/tests/integration/integration/high_availability/test_upgrade.py b/kubernetes/tests/integration/integration/high_availability/test_upgrade.py index c9a34a81c8..be551bca82 100644 --- a/kubernetes/tests/integration/integration/high_availability/test_upgrade.py +++ b/kubernetes/tests/integration/integration/high_availability/test_upgrade.py @@ -14,7 +14,6 @@ from ... import architecture from ...helpers_ha import ( - CHARM_METADATA, check_mysql_units_writes_increment, continuous_writes_ctx, get_app_leader, @@ -22,6 +21,7 @@ get_mysql_primary_unit, get_mysql_variable_value, load_mysql_test_data, + refresh_mysql_server, wait_for_apps_status, wait_for_unit_status, ) @@ -96,58 +96,7 @@ def test_refresh_from_edge(juju: Juju, charm: str, continuous_writes) -> None: logging.info("Ensure continuous writes are incrementing") check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) - mysql_leader = get_app_leader(juju, MYSQL_APP_NAME) - mysql_units = get_app_units(juju, MYSQL_APP_NAME) - mysql_units.sort() - - logging.info("Refresh the charm") - juju.refresh( - app=MYSQL_APP_NAME, - path=charm, - resources={"mysql-image": CHARM_METADATA["resources"]["mysql-image"]["upstream-source"]}, - ) - - logging.info("Wait for refresh to start") - juju.wait( - ready=wait_for_apps_status(jubilant.any_blocked, MYSQL_APP_NAME), - timeout=5 * MINUTE_SECS, - ) - juju.wait( - ready=wait_for_unit_status(MYSQL_APP_NAME, mysql_units[-1], "blocked"), - timeout=5 * MINUTE_SECS, - ) - - mysql_status = juju.status().apps[MYSQL_APP_NAME] - upgrade_unit_status = mysql_status.units[mysql_units[-1]] - upgrade_unit_message = upgrade_unit_status.workload_status.message - - if "Refresh incompatible" in upgrade_unit_message: - logging.info("Application refresh is blocked due to incompatibility") - juju.run( - unit=mysql_units[-1], - action="force-refresh-start", - params={"check-compatibility": False}, - wait=5 * MINUTE_SECS, - ) - - logging.info("Wait for refresh to finish on first unit") - juju.wait( - ready=jubilant.all_agents_idle, - timeout=5 * MINUTE_SECS, - ) - - logging.info("Resume refresh") - juju.run( - unit=mysql_leader, - action="resume-refresh", - wait=5 * MINUTE_SECS, - ) - - logging.info("Wait for refresh to complete") - juju.wait( - ready=wait_for_apps_status(jubilant.all_active, MYSQL_APP_NAME), - timeout=20 * MINUTE_SECS, - ) + refresh_mysql_server(juju, MYSQL_APP_NAME, charm) logging.info("Ensure continuous writes are incrementing") check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) diff --git a/kubernetes/tests/integration/integration/high_availability/test_upgrade_router_first.py b/kubernetes/tests/integration/integration/high_availability/test_upgrade_router_first.py new file mode 100644 index 0000000000..fea7942aac --- /dev/null +++ b/kubernetes/tests/integration/integration/high_availability/test_upgrade_router_first.py @@ -0,0 +1,124 @@ +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +import logging + +import jubilant +from jubilant import Juju + +from ... import architecture +from ...helpers_ha import ( + check_mysql_units_writes_increment, + get_app_leader, + get_mysql_primary_unit, + refresh_mysql_server, + wait_for_apps_status, +) + +MYSQL_APP_NAME = "mysql-k8s" +MYSQL_TEST_APP_NAME = "mysql-test-app" +MYSQL_ROUTER_APP_NAME = "mysql-router-k8s" + +MINUTE_SECS = 60 + + +def test_deploy_latest(juju: Juju) -> None: + """Deploy MySQL, mysql-router-k8s, and mysql-test-app with a router-mediated relation.""" + logging.info("Deploying MySQL cluster") + juju.deploy( + charm=MYSQL_APP_NAME, + app=MYSQL_APP_NAME, + base="ubuntu@26.04", + channel="8.4/edge", + config={"profile": "testing"}, + storage={"data": "500M", "archive": "250M", "logs": "250M", "temp": "250M"}, + num_units=3, + trust=True, + ) + juju.deploy( + charm=MYSQL_ROUTER_APP_NAME, + app=MYSQL_ROUTER_APP_NAME, + base="ubuntu@26.04", + channel="8.4/stable", + num_units=1, + trust=True, + ) + constraints = {"arch": architecture.architecture} + juju.deploy( + charm=MYSQL_TEST_APP_NAME, + app=MYSQL_TEST_APP_NAME, + base="ubuntu@26.04", + channel="latest/edge", + num_units=1, + trust=False, + constraints=constraints, + ) + + logging.info("Relating applications through the router") + juju.integrate( + f"{MYSQL_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:backend-database", + ) + juju.integrate( + f"{MYSQL_TEST_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:database", + ) + + logging.info("Wait for applications to become active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + + +def test_pre_refresh_check(juju: Juju) -> None: + """Test that the pre-refresh-check action runs successfully.""" + mysql_leader = get_app_leader(juju, MYSQL_APP_NAME) + + logging.info("Run pre-refresh-check action") + juju.run(unit=mysql_leader, action="pre-refresh-check") + + logging.info("Assert primary is set to leader") + mysql_primary = get_mysql_primary_unit(juju, MYSQL_APP_NAME) + assert mysql_primary == mysql_leader, "Primary unit not set to leader" + + +def test_refresh_router_first(juju: Juju, charm: str, continuous_writes) -> None: + """Refresh mysql-router-k8s first, then mysql-k8s, asserting writes increment after each.""" + logging.info("Ensure continuous writes are incrementing") + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql-router-k8s (candidate -> edge)") + juju.refresh(app=MYSQL_ROUTER_APP_NAME, channel="8.4/edge") + + logging.info("Wait for refresh to complete") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql-k8s") + refresh_mysql_server(juju, MYSQL_APP_NAME, charm) + + logging.info("Wait for all apps to be active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=5 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) diff --git a/kubernetes/tests/integration/integration/high_availability/test_upgrade_router_second.py b/kubernetes/tests/integration/integration/high_availability/test_upgrade_router_second.py new file mode 100644 index 0000000000..38516fa446 --- /dev/null +++ b/kubernetes/tests/integration/integration/high_availability/test_upgrade_router_second.py @@ -0,0 +1,124 @@ +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +import logging + +import jubilant +from jubilant import Juju + +from ... import architecture +from ...helpers_ha import ( + check_mysql_units_writes_increment, + get_app_leader, + get_mysql_primary_unit, + refresh_mysql_server, + wait_for_apps_status, +) + +MYSQL_APP_NAME = "mysql-k8s" +MYSQL_TEST_APP_NAME = "mysql-test-app" +MYSQL_ROUTER_APP_NAME = "mysql-router-k8s" + +MINUTE_SECS = 60 + + +def test_deploy_latest(juju: Juju) -> None: + """Deploy MySQL, mysql-router-k8s, and mysql-test-app with a router-mediated relation.""" + logging.info("Deploying MySQL cluster") + juju.deploy( + charm=MYSQL_APP_NAME, + app=MYSQL_APP_NAME, + base="ubuntu@26.04", + channel="8.4/edge", + config={"profile": "testing"}, + storage={"data": "500M", "archive": "250M", "logs": "250M", "temp": "250M"}, + num_units=3, + trust=True, + ) + juju.deploy( + charm=MYSQL_ROUTER_APP_NAME, + app=MYSQL_ROUTER_APP_NAME, + base="ubuntu@26.04", + channel="8.4/stable", + num_units=1, + trust=True, + ) + constraints = {"arch": architecture.architecture} + juju.deploy( + charm=MYSQL_TEST_APP_NAME, + app=MYSQL_TEST_APP_NAME, + base="ubuntu@26.04", + channel="latest/edge", + num_units=1, + trust=False, + constraints=constraints, + ) + + logging.info("Relating applications through the router") + juju.integrate( + f"{MYSQL_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:backend-database", + ) + juju.integrate( + f"{MYSQL_TEST_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:database", + ) + + logging.info("Wait for applications to become active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + + +def test_pre_refresh_check(juju: Juju) -> None: + """Test that the pre-refresh-check action runs successfully.""" + mysql_leader = get_app_leader(juju, MYSQL_APP_NAME) + + logging.info("Run pre-refresh-check action") + juju.run(unit=mysql_leader, action="pre-refresh-check") + + logging.info("Assert primary is set to leader") + mysql_primary = get_mysql_primary_unit(juju, MYSQL_APP_NAME) + assert mysql_primary == mysql_leader, "Primary unit not set to leader" + + +def test_refresh_router_second(juju: Juju, charm: str, continuous_writes) -> None: + """Refresh mysql-k8s first, then mysql-router-k8s, asserting writes increment after each.""" + logging.info("Ensure continuous writes are incrementing") + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql-k8s") + refresh_mysql_server(juju, MYSQL_APP_NAME, charm) + + logging.info("Wait for all apps to be active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=5 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql-router-k8s (candidate -> edge)") + juju.refresh(app=MYSQL_ROUTER_APP_NAME, channel="8.4/edge") + + logging.info("Wait for refresh to complete") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) diff --git a/kubernetes/tests/spread/integration/test_upgrade_router_first.py/task.yaml b/kubernetes/tests/spread/integration/test_upgrade_router_first.py/task.yaml new file mode 100644 index 0000000000..f240950a90 --- /dev/null +++ b/kubernetes/tests/spread/integration/test_upgrade_router_first.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade_router_first.py +environment: + TEST_MODULE: high_availability/test_upgrade_router_first.py +execute: | + tox run -e integration -- "tests/integration/integration/$TEST_MODULE" --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/kubernetes/tests/spread/integration/test_upgrade_router_second.py/task.yaml b/kubernetes/tests/spread/integration/test_upgrade_router_second.py/task.yaml new file mode 100644 index 0000000000..33be128e75 --- /dev/null +++ b/kubernetes/tests/spread/integration/test_upgrade_router_second.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade_router_second.py +environment: + TEST_MODULE: high_availability/test_upgrade_router_second.py +execute: | + tox run -e integration -- "tests/integration/integration/$TEST_MODULE" --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/machines/tests/integration/helpers_ha.py b/machines/tests/integration/helpers_ha.py index db7b5c36d9..a74bb28aa6 100644 --- a/machines/tests/integration/helpers_ha.py +++ b/machines/tests/integration/helpers_ha.py @@ -664,3 +664,48 @@ def continuous_writes_ctx(juju: Juju, app_name: str) -> Generator: logging.info("Clearing continuous writes") juju.run(test_app_leader, "clear-continuous-writes") + + +def refresh_mysql_server(juju: Juju, app_name: str, charm_path: str) -> None: + """Refresh the mysql server charm and drive the refresh workflow to completion.""" + mysql_units = get_app_units(juju, app_name) + mysql_units.sort() + + juju.refresh(app=app_name, path=charm_path) + + try: + logging.info("Wait for refresh to start") + juju.wait( + ready=wait_for_apps_status(jubilant.all_blocked, app_name), + timeout=5 * MINUTE_SECS, + ) + + if "Refresh incompatible" in juju.status().apps[app_name].app_status.message: + logging.info("Application refresh is blocked due to incompatibility") + juju.run( + unit=mysql_units[-1], + action="force-refresh-start", + params={"check-compatibility": False}, + wait=5 * MINUTE_SECS, + ) + except TimeoutError: + logging.info("Refresh completed without snap refresh (Python code only)") + else: + logging.info("Wait for refresh to finish on first unit") + juju.wait( + ready=jubilant.all_agents_idle, + timeout=5 * MINUTE_SECS, + ) + + logging.info("Resume refresh") + juju.run( + unit=mysql_units[-2], + action="resume-refresh", + wait=5 * MINUTE_SECS, + ) + + logging.info("Wait for refresh to complete") + juju.wait( + ready=wait_for_apps_status(jubilant.all_active, app_name), + timeout=20 * MINUTE_SECS, + ) diff --git a/machines/tests/integration/integration/high_availability/test_upgrade.py b/machines/tests/integration/integration/high_availability/test_upgrade.py index fd5fcd548e..87198857b6 100644 --- a/machines/tests/integration/integration/high_availability/test_upgrade.py +++ b/machines/tests/integration/integration/high_availability/test_upgrade.py @@ -21,6 +21,7 @@ get_mysql_primary_unit, get_mysql_variable_value, load_mysql_test_data, + refresh_mysql_server, wait_for_apps_status, ) @@ -89,48 +90,7 @@ def test_refresh_from_edge(juju: Juju, charm: str, continuous_writes) -> None: logging.info("Ensure continuous writes are incrementing") check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) - mysql_units = get_app_units(juju, MYSQL_APP_NAME) - mysql_units.sort() - - logging.info("Refresh the charm") - juju.refresh(app=MYSQL_APP_NAME, path=charm) - - try: - logging.info("Wait for refresh to start") - juju.wait( - ready=wait_for_apps_status(jubilant.all_blocked, MYSQL_APP_NAME), - timeout=5 * MINUTE_SECS, - ) - - if "Refresh incompatible" in juju.status().apps[MYSQL_APP_NAME].app_status.message: - logging.info("Application refresh is blocked due to incompatibility") - juju.run( - unit=mysql_units[-1], - action="force-refresh-start", - params={"check-compatibility": False}, - wait=5 * MINUTE_SECS, - ) - except TimeoutError: - logging.info("Refresh completed without snap refresh (Python code only)") - else: - logging.info("Wait for refresh to finish on first unit") - juju.wait( - ready=jubilant.all_agents_idle, - timeout=5 * MINUTE_SECS, - ) - - logging.info("Resume refresh") - juju.run( - unit=mysql_units[-2], - action="resume-refresh", - wait=5 * MINUTE_SECS, - ) - - logging.info("Wait for refresh to complete") - juju.wait( - ready=wait_for_apps_status(jubilant.all_active, MYSQL_APP_NAME), - timeout=20 * MINUTE_SECS, - ) + refresh_mysql_server(juju, MYSQL_APP_NAME, charm) logging.info("Ensure continuous writes are incrementing") check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) diff --git a/machines/tests/integration/integration/high_availability/test_upgrade_router_first.py b/machines/tests/integration/integration/high_availability/test_upgrade_router_first.py new file mode 100644 index 0000000000..f14b72ee4e --- /dev/null +++ b/machines/tests/integration/integration/high_availability/test_upgrade_router_first.py @@ -0,0 +1,117 @@ +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +import logging + +import jubilant +from jubilant import Juju + +from ...helpers_ha import ( + check_mysql_units_writes_increment, + get_app_leader, + get_mysql_primary_unit, + refresh_mysql_server, + wait_for_apps_status, +) + +MYSQL_APP_NAME = "mysql" +MYSQL_TEST_APP_NAME = "mysql-test-app" +MYSQL_ROUTER_APP_NAME = "mysql-router" + +MINUTE_SECS = 60 + + +def test_deploy_latest(juju: Juju) -> None: + """Deploy MySQL, mysql-router, and mysql-test-app with a router-mediated relation.""" + logging.info("Deploying MySQL cluster") + juju.deploy( + charm=MYSQL_APP_NAME, + app=MYSQL_APP_NAME, + base="ubuntu@26.04", + channel="8.4/edge", + config={"profile": "testing"}, + num_units=3, + ) + juju.deploy( + charm=MYSQL_ROUTER_APP_NAME, + app=MYSQL_ROUTER_APP_NAME, + base="ubuntu@26.04", + channel="8.4/stable", + num_units=1, + ) + juju.deploy( + charm=MYSQL_TEST_APP_NAME, + app=MYSQL_TEST_APP_NAME, + base="ubuntu@26.04", + channel="latest/edge", + num_units=1, + ) + + logging.info("Relating applications through the router") + juju.integrate( + f"{MYSQL_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:backend-database", + ) + juju.integrate( + f"{MYSQL_TEST_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:database", + ) + + logging.info("Wait for applications to become active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + + +def test_pre_refresh_check(juju: Juju) -> None: + """Test that the pre-refresh-check action runs successfully.""" + mysql_leader = get_app_leader(juju, MYSQL_APP_NAME) + + logging.info("Run pre-refresh-check action") + juju.run(unit=mysql_leader, action="pre-refresh-check") + + logging.info("Assert primary is set to leader") + mysql_primary = get_mysql_primary_unit(juju, MYSQL_APP_NAME) + assert mysql_primary == mysql_leader, "Primary unit not set to leader" + + +def test_refresh_router_first(juju: Juju, charm: str, continuous_writes) -> None: + """Refresh mysql-router first, then mysql, asserting writes increment after each.""" + logging.info("Ensure continuous writes are incrementing") + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql-router (candidate -> edge)") + juju.refresh(app=MYSQL_ROUTER_APP_NAME, channel="8.4/edge") + + logging.info("Wait for refresh to complete") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql") + refresh_mysql_server(juju, MYSQL_APP_NAME, charm) + + logging.info("Wait for all apps to be active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=5 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) diff --git a/machines/tests/integration/integration/high_availability/test_upgrade_router_second.py b/machines/tests/integration/integration/high_availability/test_upgrade_router_second.py new file mode 100644 index 0000000000..9e64f98a68 --- /dev/null +++ b/machines/tests/integration/integration/high_availability/test_upgrade_router_second.py @@ -0,0 +1,117 @@ +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +import logging + +import jubilant +from jubilant import Juju + +from ...helpers_ha import ( + check_mysql_units_writes_increment, + get_app_leader, + get_mysql_primary_unit, + refresh_mysql_server, + wait_for_apps_status, +) + +MYSQL_APP_NAME = "mysql" +MYSQL_TEST_APP_NAME = "mysql-test-app" +MYSQL_ROUTER_APP_NAME = "mysql-router" + +MINUTE_SECS = 60 + + +def test_deploy_latest(juju: Juju) -> None: + """Deploy MySQL, mysql-router, and mysql-test-app with a router-mediated relation.""" + logging.info("Deploying MySQL cluster") + juju.deploy( + charm=MYSQL_APP_NAME, + app=MYSQL_APP_NAME, + base="ubuntu@26.04", + channel="8.4/edge", + config={"profile": "testing"}, + num_units=3, + ) + juju.deploy( + charm=MYSQL_ROUTER_APP_NAME, + app=MYSQL_ROUTER_APP_NAME, + base="ubuntu@26.04", + channel="8.4/stable", + num_units=1, + ) + juju.deploy( + charm=MYSQL_TEST_APP_NAME, + app=MYSQL_TEST_APP_NAME, + base="ubuntu@26.04", + channel="latest/edge", + num_units=1, + ) + + logging.info("Relating applications through the router") + juju.integrate( + f"{MYSQL_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:backend-database", + ) + juju.integrate( + f"{MYSQL_TEST_APP_NAME}:database", + f"{MYSQL_ROUTER_APP_NAME}:database", + ) + + logging.info("Wait for applications to become active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + + +def test_pre_refresh_check(juju: Juju) -> None: + """Test that the pre-refresh-check action runs successfully.""" + mysql_leader = get_app_leader(juju, MYSQL_APP_NAME) + + logging.info("Run pre-refresh-check action") + juju.run(unit=mysql_leader, action="pre-refresh-check") + + logging.info("Assert primary is set to leader") + mysql_primary = get_mysql_primary_unit(juju, MYSQL_APP_NAME) + assert mysql_primary == mysql_leader, "Primary unit not set to leader" + + +def test_refresh_router_second(juju: Juju, charm: str, continuous_writes) -> None: + """Refresh mysql first, then mysql-router, asserting writes increment after each.""" + logging.info("Ensure continuous writes are incrementing") + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql") + refresh_mysql_server(juju, MYSQL_APP_NAME, charm) + + logging.info("Wait for all apps to be active") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=5 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) + + logging.info("Refreshing mysql-router (candidate -> edge)") + juju.refresh(app=MYSQL_ROUTER_APP_NAME, channel="8.4/edge") + + logging.info("Wait for refresh to complete") + juju.wait( + ready=wait_for_apps_status( + jubilant.all_active, + MYSQL_APP_NAME, + MYSQL_ROUTER_APP_NAME, + MYSQL_TEST_APP_NAME, + ), + timeout=20 * MINUTE_SECS, + ) + check_mysql_units_writes_increment(juju, MYSQL_APP_NAME) diff --git a/machines/tests/spread/integration/test_upgrade_router_first.py/task.yaml b/machines/tests/spread/integration/test_upgrade_router_first.py/task.yaml new file mode 100644 index 0000000000..f240950a90 --- /dev/null +++ b/machines/tests/spread/integration/test_upgrade_router_first.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade_router_first.py +environment: + TEST_MODULE: high_availability/test_upgrade_router_first.py +execute: | + tox run -e integration -- "tests/integration/integration/$TEST_MODULE" --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/machines/tests/spread/integration/test_upgrade_router_second.py/task.yaml b/machines/tests/spread/integration/test_upgrade_router_second.py/task.yaml new file mode 100644 index 0000000000..33be128e75 --- /dev/null +++ b/machines/tests/spread/integration/test_upgrade_router_second.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade_router_second.py +environment: + TEST_MODULE: high_availability/test_upgrade_router_second.py +execute: | + tox run -e integration -- "tests/integration/integration/$TEST_MODULE" --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results