Skip to content

Commit 8293c94

Browse files
Add integration test for data integrator and tls operator; for external node connectivity
1 parent 716da08 commit 8293c94

File tree

6 files changed

+237
-11
lines changed

6 files changed

+237
-11
lines changed

tests/integration/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ async def get_inserted_data_by_application(unit: Unit) -> str:
4444
return result.results.get("data")
4545

4646

47-
async def execute_queries_on_unit(
47+
async def execute_queries_against_unit(
4848
unit_address: str,
4949
username: str,
5050
password: str,
5151
queries: List[str],
52+
port: int = 3306,
5253
commit: bool = False,
5354
) -> List:
5455
"""Execute given MySQL queries on a unit.
@@ -67,6 +68,7 @@ async def execute_queries_on_unit(
6768
"user": username,
6869
"password": password,
6970
"host": unit_address,
71+
"port": port,
7072
"raise_on_warnings": False,
7173
}
7274

tests/integration/juju_.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright 2023 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
4+
import importlib.metadata
5+
6+
# libjuju version != juju agent version, but the major version should be identical—which is good
7+
# enough to check for secrets
8+
_libjuju_version = importlib.metadata.version("juju")
9+
has_secrets = int(_libjuju_version.split(".")[0]) >= 3
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Copyright 2024 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
4+
import asyncio
5+
import logging
6+
import time
7+
import typing
8+
9+
import pytest
10+
from pytest_operator.plugin import OpsTest
11+
12+
from . import juju_
13+
from .helpers import execute_queries_against_unit, get_tls_certificate_issuer
14+
15+
logger = logging.getLogger(__name__)
16+
17+
MYSQL_APP_NAME = "mysql"
18+
MYSQL_ROUTER_APP_NAME = "mysqlrouter"
19+
DATA_INTEGRATOR_APP_NAME = "data-integrator"
20+
SLOW_TIMEOUT = 15 * 60
21+
TEST_DATABASE = "testdatabase"
22+
TEST_TABLE = "testtable"
23+
24+
if juju_.has_secrets:
25+
TLS_APP_NAME = "self-signed-certificates"
26+
TLS_CONFIG = {"ca-common-name": "Test CA"}
27+
else:
28+
TLS_APP_NAME = "tls-certificates-operator"
29+
TLS_CONFIG = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"}
30+
31+
32+
async def get_data_integrator_credentials(ops_test: OpsTest) -> typing.Dict:
33+
"""Helper to get the credentials from the deployed data integrator"""
34+
data_integrator_unit = ops_test.model.applications[DATA_INTEGRATOR_APP_NAME].units[0]
35+
action = await data_integrator_unit.run_action(action_name="get-credentials")
36+
result = await action.wait()
37+
assert result.results["return-code"] == 0
38+
assert result.results["ok"] == "True"
39+
return result.results["mysql"]
40+
41+
42+
@pytest.mark.group(1)
43+
@pytest.mark.abort_on_fail
44+
async def test_external_connectivity_with_data_integrator(
45+
ops_test: OpsTest, mysql_router_charm_series: str
46+
) -> None:
47+
"""Test encryption when backend database is using TLS."""
48+
logger.info("Deploy and relate all applications")
49+
async with ops_test.fast_forward():
50+
# deploy mysql first
51+
await ops_test.model.deploy(
52+
MYSQL_APP_NAME, channel="8.0/edge", config={"profile": "testing"}, num_units=1
53+
)
54+
data_integrator_config = {"database-name": TEST_DATABASE}
55+
56+
# ROUTER
57+
mysqlrouter_charm = await ops_test.build_charm(".")
58+
59+
# tls, data-integrator and router
60+
await asyncio.gather(
61+
ops_test.model.deploy(
62+
mysqlrouter_charm,
63+
application_name=MYSQL_ROUTER_APP_NAME,
64+
num_units=None,
65+
series=mysql_router_charm_series,
66+
),
67+
ops_test.model.deploy(
68+
TLS_APP_NAME, application_name=TLS_APP_NAME, channel="stable", config=TLS_CONFIG
69+
),
70+
ops_test.model.deploy(
71+
DATA_INTEGRATOR_APP_NAME,
72+
application_name=DATA_INTEGRATOR_APP_NAME,
73+
channel="latest/edge",
74+
series=mysql_router_charm_series,
75+
config=data_integrator_config,
76+
),
77+
)
78+
79+
await ops_test.model.relate(
80+
f"{MYSQL_ROUTER_APP_NAME}:backend-database", f"{MYSQL_APP_NAME}:database"
81+
)
82+
await ops_test.model.relate(
83+
f"{DATA_INTEGRATOR_APP_NAME}:mysql", f"{MYSQL_ROUTER_APP_NAME}:database"
84+
)
85+
86+
logger.info("Waiting for applications to become active")
87+
# We can safely wait only for test application to be ready, given that it will
88+
# only become active once all the other applications are ready.
89+
await ops_test.model.wait_for_idle(
90+
[DATA_INTEGRATOR_APP_NAME], status="active", timeout=SLOW_TIMEOUT
91+
)
92+
93+
credentials = await get_data_integrator_credentials(ops_test)
94+
databases = await execute_queries_against_unit(
95+
credentials["endpoints"].split(",")[0].split(":")[0],
96+
credentials["username"],
97+
credentials["password"],
98+
["SHOW DATABASES;"],
99+
port=credentials["endpoints"].split(",")[0].split(":")[1],
100+
)
101+
assert TEST_DATABASE in databases
102+
103+
104+
@pytest.mark.group(1)
105+
@pytest.mark.abort_on_fail
106+
async def test_external_connectivity_with_data_integrator_and_tls(ops_test: OpsTest) -> None:
107+
"""Test data integrator along with TLS operator"""
108+
logger.info("Ensuring no data exists in the test database")
109+
110+
credentials = await get_data_integrator_credentials(ops_test)
111+
[database_host, database_port] = credentials["endpoints"].split(",")[0].split(":")
112+
mysqlrouter_unit = ops_test.model.applications[MYSQL_ROUTER_APP_NAME].units[0]
113+
114+
show_tables_sql = [
115+
f"SHOW TABLES IN {TEST_DATABASE};",
116+
]
117+
tables = await execute_queries_against_unit(
118+
database_host,
119+
credentials["username"],
120+
credentials["password"],
121+
show_tables_sql,
122+
port=database_port,
123+
)
124+
assert len(tables) == 0, f"Unexpected tables in the {TEST_DATABASE} database"
125+
126+
issuer = await get_tls_certificate_issuer(
127+
ops_test,
128+
mysqlrouter_unit.name,
129+
host=database_host,
130+
port=database_port,
131+
)
132+
assert (
133+
"Issuer: CN = MySQL_Router_Auto_Generated_CA_Certificate" in issuer
134+
), "Expected mysqlrouter autogenerated certificate"
135+
136+
logger.info(f"Relating mysqlrouter with {TLS_APP_NAME}")
137+
await ops_test.model.relate(
138+
f"{MYSQL_ROUTER_APP_NAME}:certificates", f"{TLS_APP_NAME}:certificates"
139+
)
140+
141+
time.sleep(30)
142+
143+
issuer = await get_tls_certificate_issuer(
144+
ops_test,
145+
mysqlrouter_unit.name,
146+
host=database_host,
147+
port=database_port,
148+
)
149+
assert "CN = Test CA" in issuer, f"Expected mysqlrouter certificate from {TLS_APP_NAME}"
150+
151+
create_table_and_insert_data_sql = [
152+
f"CREATE TABLE {TEST_DATABASE}.{TEST_TABLE} (id int, primary key(id));",
153+
f"INSERT INTO {TEST_DATABASE}.{TEST_TABLE} VALUES (1), (2);",
154+
]
155+
await execute_queries_against_unit(
156+
database_host,
157+
credentials["username"],
158+
credentials["password"],
159+
create_table_and_insert_data_sql,
160+
port=database_port,
161+
commit=True,
162+
)
163+
164+
select_data_sql = [
165+
f"SELECT * FROM {TEST_DATABASE}.{TEST_TABLE};",
166+
]
167+
data = await execute_queries_against_unit(
168+
database_host,
169+
credentials["username"],
170+
credentials["password"],
171+
select_data_sql,
172+
port=database_port,
173+
)
174+
assert data == [1, 2], f"Unexpected data in table {TEST_DATABASE}.{TEST_TABLE}"
175+
176+
logger.info(f"Removing relation between mysqlrouter and {TLS_APP_NAME}")
177+
await ops_test.model.applications[MYSQL_ROUTER_APP_NAME].remove_relation(
178+
f"{MYSQL_ROUTER_APP_NAME}:certificates", f"{TLS_APP_NAME}:certificates"
179+
)
180+
181+
time.sleep(30)
182+
183+
issuer = await get_tls_certificate_issuer(
184+
ops_test,
185+
mysqlrouter_unit.name,
186+
host=database_host,
187+
port=database_port,
188+
)
189+
assert (
190+
"Issuer: CN = MySQL_Router_Auto_Generated_CA_Certificate" in issuer
191+
), "Expected mysqlrouter autogenerated certificate"
192+
193+
select_data_sql = [
194+
f"SELECT * FROM {TEST_DATABASE}.{TEST_TABLE};",
195+
]
196+
data = await execute_queries_against_unit(
197+
database_host,
198+
credentials["username"],
199+
credentials["password"],
200+
select_data_sql,
201+
port=database_port,
202+
)
203+
assert data == [1, 2], f"Unexpected data in table {TEST_DATABASE}.{TEST_TABLE}"

tests/integration/test_database.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pytest_operator.plugin import OpsTest
1010

1111
from .helpers import (
12-
execute_queries_on_unit,
12+
execute_queries_against_unit,
1313
get_inserted_data_by_application,
1414
get_server_config_credentials,
1515
)
@@ -118,7 +118,7 @@ async def test_database_relation(ops_test: OpsTest, mysql_router_charm_series: s
118118
select_inserted_data_sql = (
119119
f"SELECT data FROM `{TEST_DATABASE}`.{TEST_TABLE} WHERE data = '{inserted_data}'",
120120
)
121-
selected_data = await execute_queries_on_unit(
121+
selected_data = await execute_queries_against_unit(
122122
mysql_unit_address,
123123
server_config_credentials["username"],
124124
server_config_credentials["password"],

tests/integration/test_exporter.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@
1010
import urllib3
1111
from pytest_operator.plugin import OpsTest
1212

13+
from . import juju_
14+
1315
logger = logging.getLogger(__name__)
1416

1517
MYSQL_APP_NAME = "mysql"
1618
MYSQL_ROUTER_APP_NAME = "mysql-router"
1719
APPLICATION_APP_NAME = "mysql-test-app"
1820
GRAFANA_AGENT_APP_NAME = "grafana-agent"
19-
TLS_APP_NAME = "tls-certificates-operator"
2021
SLOW_TIMEOUT = 25 * 60
2122

23+
if juju_.has_secrets:
24+
TLS_APP_NAME = "self-signed-certificates"
25+
TLS_CONFIG = {"ca-common-name": "Test CA"}
26+
else:
27+
TLS_APP_NAME = "tls-certificates-operator"
28+
TLS_CONFIG = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"}
29+
2230

2331
@pytest.mark.group(1)
2432
@pytest.mark.abort_on_fail
@@ -165,7 +173,7 @@ async def test_exporter_endpoint_with_tls(ops_test: OpsTest) -> None:
165173
TLS_APP_NAME,
166174
application_name=TLS_APP_NAME,
167175
channel="stable",
168-
config={"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"},
176+
config=TLS_CONFIG,
169177
)
170178

171179
await ops_test.model.wait_for_idle([TLS_APP_NAME], status="active", timeout=SLOW_TIMEOUT)

tests/integration/test_tls.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,34 @@
88
import pytest
99
from pytest_operator.plugin import OpsTest
1010

11+
from . import juju_
1112
from .helpers import get_tls_certificate_issuer
1213

1314
logger = logging.getLogger(__name__)
1415

1516
MYSQL_APP_NAME = "mysql"
1617
MYSQL_ROUTER_APP_NAME = "mysqlrouter"
1718
TEST_APP_NAME = "mysql-test-app"
18-
TLS_APP_NAME = "tls-certificates-operator"
1919
SLOW_TIMEOUT = 15 * 60
20-
MODEL_CONFIG = {"logging-config": "<root>=INFO;unit=DEBUG"}
20+
21+
if juju_.has_secrets:
22+
TLS_APP_NAME = "self-signed-certificates"
23+
TLS_CONFIG = {"ca-common-name": "Test CA"}
24+
else:
25+
TLS_APP_NAME = "tls-certificates-operator"
26+
TLS_CONFIG = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"}
2127

2228

2329
@pytest.mark.group(1)
2430
@pytest.mark.abort_on_fail
2531
async def test_build_deploy_and_relate(ops_test: OpsTest, mysql_router_charm_series: str) -> None:
2632
"""Test encryption when backend database is using TLS."""
27-
await ops_test.model.set_config(MODEL_CONFIG)
2833
logger.info("Deploy and relate all applications")
2934
async with ops_test.fast_forward():
3035
# deploy mysql first
3136
await ops_test.model.deploy(
3237
MYSQL_APP_NAME, channel="8.0/edge", config={"profile": "testing"}, num_units=1
3338
)
34-
tls_config = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"}
3539

3640
# ROUTER
3741
mysqlrouter_charm = await ops_test.build_charm(".")
@@ -45,7 +49,7 @@ async def test_build_deploy_and_relate(ops_test: OpsTest, mysql_router_charm_ser
4549
series=mysql_router_charm_series,
4650
),
4751
ops_test.model.deploy(
48-
TLS_APP_NAME, application_name=TLS_APP_NAME, channel="stable", config=tls_config
52+
TLS_APP_NAME, application_name=TLS_APP_NAME, channel="stable", config=TLS_CONFIG
4953
),
5054
ops_test.model.deploy(
5155
TEST_APP_NAME,
@@ -81,7 +85,7 @@ async def test_connected_encryption(ops_test: OpsTest) -> None:
8185
)
8286
assert (
8387
"Issuer: CN = MySQL_Router_Auto_Generated_CA_Certificate" in issuer
84-
), "Expected mysqlrouter autogenerated CA certificate"
88+
), "Expected mysqlrouter autogenerated certificate"
8589

8690
logger.info("Relating TLS with mysqlrouter")
8791
await ops_test.model.relate(TLS_APP_NAME, MYSQL_ROUTER_APP_NAME)

0 commit comments

Comments
 (0)