-
Notifications
You must be signed in to change notification settings - Fork 18
[DPE-8923] Migrate tests/integration/integration/{relations,roles} to Jubilant #714
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
base: main
Are you sure you want to change the base?
Changes from all commits
97906c9
85f9de0
ae03920
cbbaedc
4758b80
1a85fac
c4a06aa
49939ee
6e2cafb
e244621
1344b5f
f38d80b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,245 @@ | ||
| #!/usr/bin/env python3 | ||
| # Copyright 2022 Canonical Ltd. | ||
| # See LICENSE file for licensing details. | ||
|
|
||
| import logging | ||
|
|
||
| import jubilant_backports | ||
| import pytest | ||
| from jubilant_backports import Juju | ||
|
|
||
| from constants import DB_RELATION_NAME, PASSWORD_LENGTH, ROOT_USERNAME | ||
| from utils import generate_random_password | ||
|
|
||
| from ... import markers | ||
| from ...helpers_ha import ( | ||
| CHARM_METADATA, | ||
| MINUTE_SECS, | ||
| check_read_only_endpoints, | ||
| get_app_units, | ||
| get_mysql_primary_unit, | ||
| get_mysql_server_credentials, | ||
| get_relation_data, | ||
| get_unit_ip, | ||
| remove_leader_unit, | ||
| rotate_mysql_server_credentials, | ||
| scale_app_units, | ||
| wait_for_apps_status, | ||
| ) | ||
| from ...helpers_ha import ( | ||
| execute_queries_on_unit_sync as execute_queries_on_unit, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| DATABASE_APP_NAME = CHARM_METADATA["name"] | ||
| CLUSTER_NAME = "test_cluster" | ||
|
|
||
| APPLICATION_APP_NAME = "mysql-test-app" | ||
|
|
||
| APPS = [DATABASE_APP_NAME, APPLICATION_APP_NAME] | ||
|
|
||
| ENDPOINT = "database" | ||
| TIMEOUT = 15 * MINUTE_SECS | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| @pytest.mark.skip_if_deployed | ||
| def test_build_and_deploy(juju: Juju, charm): | ||
| """Build the charm and deploy 3 units to ensure a cluster is formed.""" | ||
| juju.deploy( | ||
| charm, | ||
| DATABASE_APP_NAME, | ||
| config={"cluster-name": CLUSTER_NAME, "profile": "testing"}, | ||
| num_units=3, | ||
| base="[email protected]", | ||
| ) | ||
| juju.deploy( | ||
| APPLICATION_APP_NAME, | ||
| num_units=2, | ||
| channel="latest/edge", | ||
| base="[email protected]", | ||
| ) | ||
|
|
||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_active, DATABASE_APP_NAME), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_waiting, APPLICATION_APP_NAME), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| def test_password_rotation(juju: Juju): | ||
| """Rotate password and confirm changes.""" | ||
| random_unit = get_app_units(juju, DATABASE_APP_NAME)[-1] | ||
|
|
||
| old_credentials = get_mysql_server_credentials(juju, random_unit) | ||
|
|
||
| # get primary unit first, need that to invoke set-password action | ||
| primary_unit_name = get_mysql_primary_unit(juju, DATABASE_APP_NAME, random_unit) | ||
| primary_unit_address = get_unit_ip(juju, DATABASE_APP_NAME, primary_unit_name) | ||
| logger.debug("Primary unit detected before password rotation is %s", primary_unit_address) | ||
|
|
||
| new_password = generate_random_password(PASSWORD_LENGTH) | ||
|
|
||
| rotate_mysql_server_credentials(juju, primary_unit_name, password=new_password) | ||
|
|
||
| updated_credentials = get_mysql_server_credentials(juju, random_unit) | ||
| assert updated_credentials["password"] != old_credentials["password"] | ||
| assert updated_credentials["password"] == new_password | ||
|
|
||
| # verify that the new password actually works by querying the db | ||
| show_tables_sql = ["SHOW DATABASES"] | ||
| output = execute_queries_on_unit( | ||
| primary_unit_address, | ||
| updated_credentials["username"], | ||
| updated_credentials["password"], | ||
| show_tables_sql, | ||
| ) | ||
| assert len(output) > 0, "query with new password failed, no databases found" | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| def test_password_rotation_silent(juju: Juju): | ||
| """Rotate password and confirm changes.""" | ||
| random_unit = get_app_units(juju, DATABASE_APP_NAME)[-1] | ||
|
|
||
| old_credentials = get_mysql_server_credentials(juju, random_unit) | ||
|
|
||
| # get primary unit first, need that to invoke set-password action | ||
| primary_unit = get_mysql_primary_unit(juju, DATABASE_APP_NAME, random_unit) | ||
| primary_unit_address = get_unit_ip(juju, DATABASE_APP_NAME, primary_unit) | ||
| logger.debug("Primary unit detected before password rotation is %s", primary_unit_address) | ||
|
|
||
| rotate_mysql_server_credentials(juju, primary_unit) | ||
|
|
||
| updated_credentials = get_mysql_server_credentials(juju, random_unit) | ||
| assert updated_credentials["password"] != old_credentials["password"] | ||
|
|
||
| # verify that the new password actually works by querying the db | ||
| show_tables_sql = ["SHOW DATABASES"] | ||
| output = execute_queries_on_unit( | ||
| primary_unit_address, | ||
| updated_credentials["username"], | ||
| updated_credentials["password"], | ||
| show_tables_sql, | ||
| ) | ||
| assert len(output) > 0, "query with new password failed, no databases found" | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| def test_password_rotation_root_user(juju: Juju): | ||
| """Rotate password for root user and confirm changes.""" | ||
| random_unit = get_app_units(juju, DATABASE_APP_NAME)[-1] | ||
|
|
||
| old_credentials = get_mysql_server_credentials(juju, random_unit, ROOT_USERNAME) | ||
|
|
||
| # get primary unit first, need that to invoke set-password action | ||
| primary_unit = get_mysql_primary_unit(juju, DATABASE_APP_NAME, random_unit) | ||
| primary_unit_address = get_unit_ip(juju, DATABASE_APP_NAME, primary_unit) | ||
| logger.debug("Primary unit detected before password rotation is %s", primary_unit_address) | ||
|
|
||
| rotate_mysql_server_credentials(juju, primary_unit, ROOT_USERNAME) | ||
|
|
||
| updated_credentials = get_mysql_server_credentials(juju, random_unit, ROOT_USERNAME) | ||
| assert updated_credentials["password"] != old_credentials["password"] | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| @markers.only_without_juju_secrets | ||
| def test_relation_creation_databag(juju: Juju): | ||
| """Relate charms and wait for the expected changes in status.""" | ||
| juju.integrate(f"{APPLICATION_APP_NAME}:{ENDPOINT}", f"{DATABASE_APP_NAME}:{ENDPOINT}") | ||
|
|
||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_active, DATABASE_APP_NAME), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_active, APPLICATION_APP_NAME), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
|
|
||
| relation_data = get_relation_data(juju, APPLICATION_APP_NAME, DB_RELATION_NAME) | ||
| assert {"password", "username"} <= set(relation_data[0]["application-data"]) | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| @markers.only_with_juju_secrets | ||
| def test_relation_creation(juju: Juju): | ||
| """Relate charms and wait for the expected changes in status (using juju secrets).""" | ||
| juju.integrate(f"{APPLICATION_APP_NAME}:{ENDPOINT}", f"{DATABASE_APP_NAME}:{ENDPOINT}") | ||
|
|
||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_active, *APPS), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
|
|
||
| relation_data = get_relation_data(juju, APPLICATION_APP_NAME, DB_RELATION_NAME) | ||
| assert not {"password", "username"} <= set(relation_data[0]["application-data"]) | ||
| assert "secret-user" in relation_data[0]["application-data"] | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| def test_read_only_endpoints(juju: Juju): | ||
| """Check read-only-endpoints are correctly updated.""" | ||
| relation_data = get_relation_data(juju, DATABASE_APP_NAME, DB_RELATION_NAME) | ||
| assert len(relation_data) == 1 | ||
|
|
||
| check_read_only_endpoints(juju, app_name=DATABASE_APP_NAME, relation_name=DB_RELATION_NAME) | ||
|
|
||
| # increase the number of units | ||
| scale_app_units(juju, DATABASE_APP_NAME, 4) | ||
| check_read_only_endpoints(juju, app_name=DATABASE_APP_NAME, relation_name=DB_RELATION_NAME) | ||
|
|
||
| # decrease the number of units | ||
| scale_app_units(juju, DATABASE_APP_NAME, 2) | ||
|
|
||
| # wait for the update of the endpoints | ||
| juju.wait( | ||
| ready=lambda status: check_read_only_endpoints( | ||
| juju, app_name=DATABASE_APP_NAME, relation_name=DB_RELATION_NAME | ||
| ) | ||
| or True, | ||
|
Comment on lines
+208
to
+211
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The last part looks suspicious. Why do we need it? |
||
| timeout=5 * MINUTE_SECS, | ||
| ) | ||
|
|
||
| # increase the number of units | ||
| scale_app_units(juju, DATABASE_APP_NAME, 3) | ||
|
|
||
| # remove the leader unit | ||
| remove_leader_unit(juju, app_name=DATABASE_APP_NAME) | ||
|
|
||
| # wait for the update of the endpoints | ||
| juju.wait( | ||
| ready=lambda status: check_read_only_endpoints( | ||
| juju, app_name=DATABASE_APP_NAME, relation_name=DB_RELATION_NAME | ||
| ) | ||
| or True, | ||
|
Comment on lines
+223
to
+226
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The last part looks suspicious. Why do we need it? |
||
| timeout=5 * MINUTE_SECS, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.abort_on_fail | ||
| def test_relation_broken(juju: Juju): | ||
| """Remove relation and wait for the expected changes in status.""" | ||
| juju.remove_relation(f"{APPLICATION_APP_NAME}:{ENDPOINT}", f"{DATABASE_APP_NAME}:{ENDPOINT}") | ||
|
|
||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_active, DATABASE_APP_NAME), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
| juju.wait( | ||
| ready=wait_for_apps_status(jubilant_backports.all_waiting, APPLICATION_APP_NAME), | ||
| error=jubilant_backports.any_blocked, | ||
| timeout=TIMEOUT, | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment will only be left once, but it applies to all occurrences of these lines.
I think we can simplify the fetching of a random unit by executing the password rotation in the primary itself. To do so, just leave the last argument of the
get_mysql_primary_unitfunction empty, so the helper will use the leader to get who the primary is.