diff --git a/tests/integration/targets/test_mysql_replication/defaults/main.yml b/tests/integration/targets/test_mysql_replication/defaults/main.yml index 48fd560..76f66d3 100644 --- a/tests/integration/targets/test_mysql_replication/defaults/main.yml +++ b/tests/integration/targets/test_mysql_replication/defaults/main.yml @@ -15,3 +15,11 @@ test_channel: test_channel-1 user_name_1: 'db_user1' user_password_1: 'gadfFDSdtTU^Sdfuj' + +enable_group_replication: true +group_replication_group_name: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' +group_replication_port: 33061 +group_replication_user: 'group_repl_user' +group_replication_pass: 'group_repl_pass' +group_replication_local_address: "{{ mysql_host }}:{{ group_replication_port }}" +group_replication_group_seeds: "{{ mysql_host }}:{{ group_replication_port }}" diff --git a/tests/integration/targets/test_mysql_replication/tasks/main.yml b/tests/integration/targets/test_mysql_replication/tasks/main.yml index e77af38..e90568d 100644 --- a/tests/integration/targets/test_mysql_replication/tasks/main.yml +++ b/tests/integration/targets/test_mysql_replication/tasks/main.yml @@ -35,3 +35,9 @@ when: - db_engine == 'mysql' - db_version is version('8.0.23', '>=') + +# Tests of group replication: +- import_tasks: mysql_replication_group.yml + when: + - (db_engine == 'mysql' and db_version is version('8.0.0', '>=')) or + (db_engine == 'mariadb' and db_version is version('10.1.0', '>=')) diff --git a/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_group.yml b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_group.yml new file mode 100644 index 0000000..14af793 --- /dev/null +++ b/tests/integration/targets/test_mysql_replication/tasks/mysql_replication_group.yml @@ -0,0 +1,104 @@ +--- +# Copyright: (c) 2025, Sebastian Pfahl (@eryx12o45) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + mysql_params: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: '{{ mysql_host }}' + + block: + # Setup group replication prerequisites + - name: Create group replication user + shell: + "echo \"CREATE USER IF NOT EXISTS \ + '{{ group_replication_user }}'@'{{ mysql_host }}' \ + IDENTIFIED {% if db_engine == 'mysql' %}WITH mysql_native_password {% endif %}BY '{{ group_replication_pass }}'; \ + GRANT REPLICATION SLAVE ON *.* TO \ + '{{ group_replication_user }}'@'{{ mysql_host }}'; \ + GRANT BACKUP_ADMIN ON *.* TO \ + '{{ group_replication_user }}'@'{{ mysql_host }}'; \ + GRANT GROUP_REPLICATION_STREAM ON *.* TO \ + '{{ group_replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}" + when: db_version is version('8.0.0', '>=') + + - name: Create group replication user for MariaDB + shell: + "echo \"CREATE USER IF NOT EXISTS \ + '{{ group_replication_user }}'@'{{ mysql_host }}' \ + IDENTIFIED BY '{{ group_replication_pass }}'; \ + GRANT REPLICATION SLAVE ON *.* TO \ + '{{ group_replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}" + when: db_engine == 'mariadb' + + # Configure group replication settings + - name: Configure group replication settings for MySQL + shell: + "echo \"SET GLOBAL group_replication_group_name='{{ group_replication_group_name }}'; \ + SET GLOBAL group_replication_local_address='{{ mysql_host }}:{{ group_replication_port }}'; \ + SET GLOBAL group_replication_group_seeds='{{ mysql_host }}:{{ group_replication_port }}'; \ + SET GLOBAL group_replication_bootstrap_group=ON;\" | {{ mysql_command }}" + when: db_engine == 'mysql' and db_version is version('8.0.0', '>=') + + - name: Configure group replication settings for MariaDB + shell: + "echo \"SET GLOBAL wsrep_provider='/usr/lib/galera/libgalera_smm.so'; \ + SET GLOBAL wsrep_cluster_name='{{ group_replication_group_name }}'; \ + SET GLOBAL wsrep_cluster_address='gcomm://{{ mysql_host }}:{{ group_replication_port }}'; \ + SET GLOBAL wsrep_node_address='{{ mysql_host }}:{{ group_replication_port }}';\" | {{ mysql_command }}" + when: db_engine == 'mariadb' and db_version is version('10.1.0', '>=') + ignore_errors: true + + # Test startgroupreplication mode + - name: Start group replication + mysql_replication: + <<: *mysql_params + mode: startgroupreplication + group_replication_user: '{{ group_replication_user }}' + group_replication_password: '{{ group_replication_pass }}' + register: result + ignore_errors: true + + - name: Assert that startgroupreplication returns expected values + assert: + that: + - result is changed + - result.queries | length > 0 + - "'START GROUP_REPLICATION' in result.queries[0]" + when: result is not failed + + # Check group replication status + - name: Check group replication status + shell: + "echo \"SHOW STATUS LIKE 'group_replication_status';\" | {{ mysql_command }}" + register: gr_status + ignore_errors: true + + # Test stopgroupreplication mode + - name: Stop group replication + mysql_replication: + <<: *mysql_params + mode: stopgroupreplication + register: result + ignore_errors: true + + - name: Assert that stopgroupreplication returns expected values + assert: + that: + - result is changed + - result.queries | length > 0 + - "'STOP GROUP_REPLICATION' in result.queries[0]" + when: result is not failed + + # Cleanup group replication settings + - name: Reset group replication settings for MySQL + shell: + "echo \"SET GLOBAL group_replication_bootstrap_group=OFF;\" | {{ mysql_command }}" + when: db_engine == 'mysql' and db_version is version('8.0.0', '>=') + ignore_errors: true + + - name: Drop group replication user + shell: + "echo \"DROP USER IF EXISTS '{{ group_replication_user }}'@'{{ mysql_host }}';\" | {{ mysql_command }}" + ignore_errors: true diff --git a/tests/unit/plugins/module_utils/test_mariadb_replication.py b/tests/unit/plugins/module_utils/test_mariadb_replication.py index 513d8cf..7e8940f 100644 --- a/tests/unit/plugins/module_utils/test_mariadb_replication.py +++ b/tests/unit/plugins/module_utils/test_mariadb_replication.py @@ -10,6 +10,20 @@ from ansible_collections.community.mysql.plugins.module_utils.implementations.ma from ..utils import dummy_cursor_class +class MockCursor: + def __init__(self, status="ONLINE"): + self.status = status + self.executed_queries = [] + + def execute(self, query): + self.executed_queries.append(query) + + def fetchone(self): + if "group_replication_status" in self.executed_queries[-1]: + return ["group_replication_status", self.status] + return None + + @pytest.mark.parametrize( 'f_output,c_output,c_ret_type', [ @@ -22,3 +36,86 @@ from ..utils import dummy_cursor_class def test_uses_replica_terminology(f_output, c_output, c_ret_type): cursor = dummy_cursor_class(c_output, c_ret_type) assert uses_replica_terminology(cursor) == f_output + + +@pytest.mark.parametrize( + 'user,password,expected_query', + [ + (None, None, "START GROUP_REPLICATION"), + ("repl_user", None, "START GROUP_REPLICATION USER='repl_user'"), + (None, "repl_pass", "START GROUP_REPLICATION"), + ("repl_user", "repl_pass", "START GROUP_REPLICATION USER='repl_user' PASSWORD='repl_pass'"), + ] +) +def test_start_group_replication(user, password, expected_query): + """Test startgroupreplication function with different parameters.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import startgroupreplication + + cursor = MockCursor() + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + chm = [] + if user: + chm.append(" USER='%s'" % user) + if password: + chm.append(" PASSWORD='%s'" % password) + + result = startgroupreplication(module, cursor, chm, False) + + assert result is True + assert cursor.executed_queries[0] == expected_query + assert cursor.executed_queries[1] == "SHOW STATUS LIKE 'group_replication_status';" + + +def test_stop_group_replication(): + """Test stopgroupreplication function.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import stopgroupreplication + + cursor = MockCursor() + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + result = stopgroupreplication(module, cursor, False) + + assert result is True + assert cursor.executed_queries[0] == "STOP GROUP_REPLICATION" + assert cursor.executed_queries[1] == "SHOW STATUS LIKE 'group_replication_status';" + + +def test_start_group_replication_fail(): + """Test startgroupreplication function with failure.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import startgroupreplication + import pymysql + + cursor = MockCursor(status="ERROR") + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + # Mock the Warning exception + pymysql.Warning = Exception + + result = startgroupreplication(module, cursor, [], True) + + assert result is False + + +def test_stop_group_replication_fail(): + """Test stopgroupreplication function with failure.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import stopgroupreplication + import pymysql + + cursor = MockCursor(status="ERROR") + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + # Mock the Warning exception + pymysql.Warning = Exception + + result = stopgroupreplication(module, cursor, True) + + assert result is False diff --git a/tests/unit/plugins/module_utils/test_mysql_replication.py b/tests/unit/plugins/module_utils/test_mysql_replication.py index c4126a5..cd4a9d7 100644 --- a/tests/unit/plugins/module_utils/test_mysql_replication.py +++ b/tests/unit/plugins/module_utils/test_mysql_replication.py @@ -10,6 +10,20 @@ from ansible_collections.community.mysql.plugins.module_utils.implementations.my from ..utils import dummy_cursor_class +class MockCursor: + def __init__(self, status="ONLINE"): + self.status = status + self.executed_queries = [] + + def execute(self, query): + self.executed_queries.append(query) + + def fetchone(self): + if "group_replication_status" in self.executed_queries[-1]: + return ["group_replication_status", self.status] + return None + + @pytest.mark.parametrize( 'f_output,c_output,c_ret_type', [ @@ -26,3 +40,86 @@ from ..utils import dummy_cursor_class def test_uses_replica_terminology(f_output, c_output, c_ret_type): cursor = dummy_cursor_class(c_output, c_ret_type) assert uses_replica_terminology(cursor) == f_output + + +@pytest.mark.parametrize( + 'user,password,expected_query', + [ + (None, None, "START GROUP_REPLICATION"), + ("repl_user", None, "START GROUP_REPLICATION USER='repl_user'"), + (None, "repl_pass", "START GROUP_REPLICATION"), + ("repl_user", "repl_pass", "START GROUP_REPLICATION USER='repl_user' PASSWORD='repl_pass'"), + ] +) +def test_start_group_replication(user, password, expected_query): + """Test startgroupreplication function with different parameters.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import startgroupreplication + + cursor = MockCursor() + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + chm = [] + if user: + chm.append(" USER='%s'" % user) + if password: + chm.append(" PASSWORD='%s'" % password) + + result = startgroupreplication(module, cursor, chm, False) + + assert result is True + assert cursor.executed_queries[0] == expected_query + assert cursor.executed_queries[1] == "SHOW STATUS LIKE 'group_replication_status';" + + +def test_stop_group_replication(): + """Test stopgroupreplication function.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import stopgroupreplication + + cursor = MockCursor() + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + result = stopgroupreplication(module, cursor, False) + + assert result is True + assert cursor.executed_queries[0] == "STOP GROUP_REPLICATION" + assert cursor.executed_queries[1] == "SHOW STATUS LIKE 'group_replication_status';" + + +def test_start_group_replication_fail(): + """Test startgroupreplication function with failure.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import startgroupreplication + import pymysql + + cursor = MockCursor(status="ERROR") + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + # Mock the Warning exception + pymysql.Warning = Exception + + result = startgroupreplication(module, cursor, [], True) + + assert result is False + + +def test_stop_group_replication_fail(): + """Test stopgroupreplication function with failure.""" + from ansible_collections.community.mysql.plugins.modules.mysql_replication import stopgroupreplication + import pymysql + + cursor = MockCursor(status="ERROR") + module = type('obj', (object,), { + 'fail_json': lambda msg: None, + }) + + # Mock the Warning exception + pymysql.Warning = Exception + + result = stopgroupreplication(module, cursor, True) + + assert result is False