Skip to content

Commit 335674c

Browse files
Attempt to stabilize failing integration tests (#496)
* Attempt to stabilize failing integration tests * Run format * Address PR feedback + move log rotation test to separate file
1 parent 1abc450 commit 335674c

File tree

4 files changed

+156
-108
lines changed

4 files changed

+156
-108
lines changed

tests/integration/helpers.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def is_connection_possible(credentials: Dict, **extra_opts) -> bool:
347347

348348

349349
async def get_process_pid(
350-
ops_test: OpsTest, unit_name: str, container_name: str, process: str
350+
ops_test: OpsTest, unit_name: str, container_name: str, process: str, full_match: bool = False
351351
) -> Optional[int]:
352352
"""Return the pid of a process running in a given unit.
353353
@@ -356,6 +356,7 @@ async def get_process_pid(
356356
unit_name: The name of the unit
357357
container_name: The name of the container in the unit
358358
process: The process name to search for
359+
full_match: Whether to fully match the process name
359360
360361
Returns:
361362
A integer for the process id
@@ -366,7 +367,7 @@ async def get_process_pid(
366367
container_name,
367368
unit_name,
368369
"pgrep",
369-
"-x",
370+
"-f" if full_match else "-x",
370371
process,
371372
]
372373
return_code, pid, _ = await ops_test.juju(*get_pid_commands)
@@ -621,8 +622,12 @@ async def read_contents_from_file_in_unit(
621622
return contents
622623

623624

624-
async def ls_la_in_unit(
625-
ops_test: OpsTest, unit_name: str, directory: str, container_name: str = CONTAINER_NAME
625+
async def ls_in_unit(
626+
ops_test: OpsTest,
627+
unit_name: str,
628+
directory: str,
629+
container_name: str = CONTAINER_NAME,
630+
exclude_files: list[str] = [],
626631
) -> list[str]:
627632
"""Returns the output of ls -la in unit.
628633
@@ -631,21 +636,22 @@ async def ls_la_in_unit(
631636
unit_name: The name of unit in which to run ls -la
632637
directory: The directory from which to run ls -la
633638
container_name: The container where to run ls -la
639+
exclude_files: Files to exclude from the output of ls -la
634640
635641
Returns:
636642
a list of files returned by ls -la
637643
"""
638644
return_code, output, _ = await ops_test.juju(
639-
"ssh", "--container", container_name, unit_name, "ls", "-la", directory
645+
"ssh", "--container", container_name, unit_name, "ls", "-1", directory
640646
)
641647
assert return_code == 0
642648

643-
ls_output = output.split("\n")[1:]
649+
ls_output = output.split("\n")
644650

645651
return [
646652
line.strip("\r")
647653
for line in ls_output
648-
if len(line.strip()) > 0 and line.split()[-1] not in [".", ".."]
654+
if len(line.strip()) > 0 and line.strip() not in exclude_files
649655
]
650656

651657

@@ -666,6 +672,21 @@ async def stop_running_log_rotate_dispatcher(ops_test: OpsTest, unit_name: str):
666672
"/usr/bin/python3 scripts/log_rotate_dispatcher.py",
667673
)
668674

675+
# hold execution until process is stopped
676+
try:
677+
for attempt in Retrying(stop=stop_after_attempt(45), wait=wait_fixed(2)):
678+
with attempt:
679+
if await get_process_pid(
680+
ops_test,
681+
unit_name,
682+
"charm",
683+
"/usr/bin/python3 scripts/log_rotate_dispatcher.py",
684+
full_match=True,
685+
):
686+
raise Exception
687+
except RetryError:
688+
raise Exception("Failed to stop the log_rotate_dispatcher process")
689+
669690

670691
async def stop_running_flush_mysql_job(
671692
ops_test: OpsTest, unit_name: str, container_name: str = CONTAINER_NAME
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024 Canonical Ltd.
3+
# See LICENSE file for licensing details.
4+
5+
import logging
6+
from pathlib import Path
7+
8+
import pytest
9+
import yaml
10+
from pytest_operator.plugin import OpsTest
11+
12+
from ..helpers import (
13+
delete_file_or_directory_in_unit,
14+
dispatch_custom_event_for_logrotate,
15+
ls_in_unit,
16+
read_contents_from_file_in_unit,
17+
stop_running_flush_mysql_job,
18+
stop_running_log_rotate_dispatcher,
19+
write_content_to_file_in_unit,
20+
)
21+
22+
logger = logging.getLogger(__name__)
23+
24+
METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
25+
APP_NAME = METADATA["name"]
26+
27+
28+
@pytest.mark.group(1)
29+
@pytest.mark.abort_on_fail
30+
async def test_log_rotation(
31+
ops_test: OpsTest, highly_available_cluster, continuous_writes
32+
) -> None:
33+
"""Test the log rotation of text files.
34+
35+
Run continuous writes to ensure that audit log plugin is loaded and active
36+
when mysql-test-app runs start-continuous-writes (by logging into mysql).
37+
"""
38+
unit = ops_test.model.applications[APP_NAME].units[0]
39+
40+
logger.info("Extending update-status-hook-interval to 60m")
41+
await ops_test.model.set_config({"update-status-hook-interval": "60m"})
42+
43+
# Exclude slowquery log files as slowquery logs are not enabled by default
44+
log_types = ["error", "general", "audit"]
45+
log_files = ["error.log", "general.log", "audit.log"]
46+
archive_directories = [
47+
"archive_error",
48+
"archive_general",
49+
"archive_slowquery",
50+
"archive_audit",
51+
]
52+
53+
logger.info("Overwriting the log rotate dispatcher script")
54+
unit_label = unit.name.replace("/", "-")
55+
await write_content_to_file_in_unit(
56+
ops_test,
57+
unit,
58+
f"/var/lib/juju/agents/unit-{unit_label}/charm/scripts/log_rotate_dispatcher.py",
59+
"exit(0)\n",
60+
container_name="charm",
61+
)
62+
63+
logger.info("Stopping the log rotate dispatcher")
64+
await stop_running_log_rotate_dispatcher(ops_test, unit.name)
65+
66+
logger.info("Stopping any running logrotate jobs")
67+
await stop_running_flush_mysql_job(ops_test, unit.name)
68+
69+
logger.info("Removing existing archive directories")
70+
for archive_directory in archive_directories:
71+
await delete_file_or_directory_in_unit(
72+
ops_test,
73+
unit.name,
74+
f"/var/log/mysql/{archive_directory}/",
75+
)
76+
77+
logger.info("Writing some data to the text log files")
78+
for log in log_types:
79+
log_path = f"/var/log/mysql/{log}.log"
80+
await write_content_to_file_in_unit(ops_test, unit, log_path, f"test {log} content\n")
81+
82+
logger.info("Ensuring only log files exist")
83+
# Exclude archive directories, as handling any event would restart the
84+
# log_rotate_dispatcher (by the log_rotate_manager)
85+
ls_output = await ls_in_unit(
86+
ops_test, unit.name, "/var/log/mysql/", exclude_files=archive_directories
87+
)
88+
89+
for file in log_files:
90+
# audit.log can be rotated and new file not created until access to db
91+
assert (
92+
file in ls_output or file == "audit.log"
93+
), f"❌ files other than log files exist {ls_output}"
94+
95+
logger.info("Dispatching custom event to rotate logs")
96+
await dispatch_custom_event_for_logrotate(ops_test, unit.name)
97+
98+
logger.info("Ensuring log files and archive directories exist")
99+
ls_output = await ls_in_unit(ops_test, unit.name, "/var/log/mysql/")
100+
101+
for file in log_files + archive_directories:
102+
# audit.log can be rotated and new file not created until access to db
103+
assert (
104+
file in ls_output or file == "audit.log"
105+
), f"❌ unexpected files/directories in log directory: {ls_output}"
106+
107+
logger.info("Ensuring log files were rotated")
108+
# Exclude checking slowquery log rotation as slowquery logs are disabled by default
109+
for log in set(log_types):
110+
file_contents = await read_contents_from_file_in_unit(
111+
ops_test, unit, f"/var/log/mysql/{log}.log"
112+
)
113+
assert f"test {log} content" not in file_contents, f"❌ log file {log}.log not rotated"
114+
115+
ls_output = await ls_in_unit(ops_test, unit.name, f"/var/log/mysql/archive_{log}/")
116+
assert len(ls_output) != 0, f"❌ archive directory is empty: {ls_output}"
117+
118+
rotated_file_content_exists = False
119+
for filename in ls_output:
120+
file_contents = await read_contents_from_file_in_unit(
121+
ops_test,
122+
unit,
123+
f"/var/log/mysql/archive_{log}/{filename}",
124+
)
125+
if f"test {log} content" in file_contents:
126+
rotated_file_content_exists = True
127+
assert rotated_file_content_exists, f"❌ log file {log}.log not rotated"

tests/integration/juju_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
juju_major_version = int(_libjuju_version.split(".")[0])
1515

1616

17-
async def run_action(unit: juju.unit.Unit, action_name, **params):
17+
async def run_action(unit: juju.unit.Unit, action_name: str, **params):
1818
action = await unit.run_action(action_name=action_name, **params)
1919
result = await action.wait()
2020
# Syntax changed across libjuju major versions

tests/integration/test_charm.py

Lines changed: 0 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,17 @@
1515
from utils import generate_random_password
1616

1717
from .helpers import (
18-
delete_file_or_directory_in_unit,
19-
dispatch_custom_event_for_logrotate,
2018
execute_queries_on_unit,
2119
fetch_credentials,
2220
generate_random_string,
2321
get_cluster_status,
2422
get_primary_unit,
2523
get_server_config_credentials,
2624
get_unit_address,
27-
ls_la_in_unit,
28-
read_contents_from_file_in_unit,
2925
retrieve_database_variable_value,
3026
rotate_credentials,
3127
scale_application,
3228
start_mysqld_exporter,
33-
stop_running_flush_mysql_job,
34-
stop_running_log_rotate_dispatcher,
35-
write_content_to_file_in_unit,
3629
)
3730

3831
logger = logging.getLogger(__name__)
@@ -336,96 +329,3 @@ async def test_custom_variables(ops_test: OpsTest) -> None:
336329
logger.info(f"Checking that {k} is set to {v} on {unit.name}")
337330
value = await retrieve_database_variable_value(ops_test, unit, k)
338331
assert int(value) == v, f"Variable {k} is not set to {v}"
339-
340-
341-
@pytest.mark.group(1)
342-
@pytest.mark.abort_on_fail
343-
async def test_log_rotation(ops_test: OpsTest) -> None:
344-
"""Test the log rotation of text files."""
345-
unit = ops_test.model.applications[APP_NAME].units[0]
346-
347-
logger.info("Extending update-status-hook-interval to 60m")
348-
await ops_test.model.set_config({"update-status-hook-interval": "60m"})
349-
350-
# Exclude slowquery log files as slowquery logs are not enabled by default
351-
log_types = ["error", "general", "audit"]
352-
log_files = ["error.log", "general.log", "audit.log"]
353-
archive_directories = [
354-
"archive_error",
355-
"archive_general",
356-
"archive_slowquery",
357-
"archive_audit",
358-
]
359-
360-
logger.info("Overwriting the log rotate dispatcher script")
361-
unit_label = unit.name.replace("/", "-")
362-
await write_content_to_file_in_unit(
363-
ops_test,
364-
unit,
365-
f"/var/lib/juju/agents/unit-{unit_label}/charm/scripts/log_rotate_dispatcher.py",
366-
"exit(0)\n",
367-
container_name="charm",
368-
)
369-
370-
logger.info("Stopping the log rotate dispatcher")
371-
await stop_running_log_rotate_dispatcher(ops_test, unit.name)
372-
373-
logger.info("Stopping any running logrotate jobs")
374-
await stop_running_flush_mysql_job(ops_test, unit.name)
375-
376-
logger.info("Removing existing archive directories")
377-
for archive_directory in archive_directories:
378-
await delete_file_or_directory_in_unit(
379-
ops_test,
380-
unit.name,
381-
f"/var/log/mysql/{archive_directory}/",
382-
)
383-
384-
logger.info("Writing some data to the text log files")
385-
for log in log_types:
386-
log_path = f"/var/log/mysql/{log}.log"
387-
await write_content_to_file_in_unit(ops_test, unit, log_path, f"test {log} content\n")
388-
389-
logger.info("Ensuring only log files exist")
390-
ls_la_output = await ls_la_in_unit(ops_test, unit.name, "/var/log/mysql/")
391-
392-
assert len(ls_la_output) == len(
393-
log_files
394-
), f"❌ files other than log files exist {ls_la_output}"
395-
directories = [line.split()[-1] for line in ls_la_output]
396-
assert sorted(directories) == sorted(
397-
log_files
398-
), f"❌ file other than logs files exist: {ls_la_output}"
399-
400-
logger.info("Dispatching custom event to rotate logs")
401-
await dispatch_custom_event_for_logrotate(ops_test, unit.name)
402-
403-
logger.info("Ensuring log files and archive directories exist")
404-
ls_la_output = await ls_la_in_unit(ops_test, unit.name, "/var/log/mysql/")
405-
406-
assert len(ls_la_output) == len(
407-
log_files + archive_directories
408-
), f"❌ unexpected files/directories in log directory: {ls_la_output}"
409-
directories = [line.split()[-1] for line in ls_la_output]
410-
assert sorted(directories) == sorted(
411-
log_files + archive_directories
412-
), f"❌ unexpected files/directories in log directory: {ls_la_output}"
413-
414-
logger.info("Ensuring log files were rotated")
415-
# Exclude checking slowquery log rotation as slowquery logs are disabled by default
416-
for log in set(log_types):
417-
file_contents = await read_contents_from_file_in_unit(
418-
ops_test, unit, f"/var/log/mysql/{log}.log"
419-
)
420-
assert f"test {log} content" not in file_contents, f"❌ log file {log}.log not rotated"
421-
422-
ls_la_output = await ls_la_in_unit(ops_test, unit.name, f"/var/log/mysql/archive_{log}/")
423-
assert len(ls_la_output) == 1, f"❌ more than 1 file in archive directory: {ls_la_output}"
424-
425-
filename = ls_la_output[0].split()[-1]
426-
file_contents = await read_contents_from_file_in_unit(
427-
ops_test,
428-
unit,
429-
f"/var/log/mysql/archive_{log}/{filename}",
430-
)
431-
assert f"test {log} content" in file_contents, f"❌ log file {log}.log not rotated"

0 commit comments

Comments
 (0)