Skip to content

Commit

Permalink
Fix replication queries for MySQL 8.0.23+ and 8.4+
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent-indermuehle committed Jul 24, 2024
1 parent 4d906e3 commit 8df9c69
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 60 deletions.
85 changes: 85 additions & 0 deletions plugins/module_utils/command_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,91 @@ def resolve_command(self, command):
("mariadb", "default"): "CHANGE MASTER",
("mysql", "8.0.23"): "CHANGE REPLICATION SOURCE",
},
"MASTER_HOST": {
("mysql", "default"): "MASTER_HOST",
("mariadb", "default"): "MASTER_HOST",
("mysql", "8.0.23"): "SOURCE_HOST",
},
"MASTER_USER": {
("mysql", "default"): "MASTER_USER",
("mariadb", "default"): "MASTER_USER",
("mysql", "8.0.23"): "SOURCE_USER",
},
"MASTER_PASSWORD": {
("mysql", "default"): "MASTER_PASSWORD",
("mariadb", "default"): "MASTER_PASSWORD",
("mysql", "8.0.23"): "SOURCE_PASSWORD",
},
"MASTER_PORT": {
("mysql", "default"): "MASTER_PORT",
("mariadb", "default"): "MASTER_PORT",
("mysql", "8.0.23"): "SOURCE_PORT",
},
"MASTER_CONNECT_RETRY": {
("mysql", "default"): "MASTER_CONNECT_RETRY",
("mariadb", "default"): "MASTER_CONNECT_RETRY",
("mysql", "8.0.23"): "SOURCE_CONNECT_RETRY",
},
"MASTER_LOG_FILE": {
("mysql", "default"): "MASTER_LOG_FILE",
("mariadb", "default"): "MASTER_LOG_FILE",
("mysql", "8.0.23"): "SOURCE_LOG_FILE",
},
"MASTER_LOG_POS": {
("mysql", "default"): "MASTER_LOG_POS",
("mariadb", "default"): "MASTER_LOG_POS",
("mysql", "8.0.23"): "SOURCE_LOG_POS",
},
"MASTER_DELAY": {
("mysql", "default"): "MASTER_DELAY",
("mariadb", "default"): "MASTER_DELAY",
("mysql", "8.0.23"): "SOURCE_DELAY",
},
"MASTER_SSL": {
("mysql", "default"): "MASTER_SSL",
("mariadb", "default"): "MASTER_SSL",
("mysql", "8.0.23"): "SOURCE_SSL",
},
"MASTER_SSL_CA": {
("mysql", "default"): "MASTER_SSL_CA",
("mariadb", "default"): "MASTER_SSL_CA",
("mysql", "8.0.23"): "SOURCE_SSL_CA",
},
"MASTER_SSL_CAPATH": {
("mysql", "default"): "MASTER_SSL_CAPATH",
("mariadb", "default"): "MASTER_SSL_CAPATH",
("mysql", "8.0.23"): "SOURCE_SSL_CAPATH",
},
"MASTER_SSL_CERT": {
("mysql", "default"): "MASTER_SSL_CERT",
("mariadb", "default"): "MASTER_SSL_CERT",
("mysql", "8.0.23"): "SOURCE_SSL_CERT",
},
"MASTER_SSL_KEY": {
("mysql", "default"): "MASTER_SSL_KEY",
("mariadb", "default"): "MASTER_SSL_KEY",
("mysql", "8.0.23"): "SOURCE_SSL_KEY",
},
"MASTER_SSL_CIPHER": {
("mysql", "default"): "MASTER_SSL_CIPHER",
("mariadb", "default"): "MASTER_SSL_CIPHER",
("mysql", "8.0.23"): "SOURCE_SSL_CIPHER",
},
"MASTER_SSL_VERIFY_SERVER_CERT": {
("mysql", "default"): "MASTER_SSL_VERIFY_SERVER_CERT",
("mariadb", "default"): "MASTER_SSL_VERIFY_SERVER_CERT",
("mysql", "8.0.23"): "SOURCE_SSL_VERIFY_SERVER_CERT",
},
"MASTER_AUTO_POSITION": {
("mysql", "default"): "MASTER_AUTO_POSITION",
("mariadb", "default"): "MASTER_AUTO_POSITION",
("mysql", "8.0.23"): "SOURCE_AUTO_POSITION",
},
"RESET MASTER": {
("mysql", "default"): "RESET MASTER",
("mariadb", "default"): "RESET MASTER",
("mysql", "8.4.0"): "RESET BINARY LOGS AND GTIDS",
},
# Add more command mappings here
}

Expand Down
49 changes: 25 additions & 24 deletions plugins/modules/mysql_replication.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
- Balazs Pocze (@banyek)
- Andrew Klychkov (@Andersson007)
- Dennis Urtubia (@dennisurtubia)
- Laurent Indermühle (@laurent-indermuehle)
options:
mode:
description:
- Module operating mode. Could be
C(changeprimary) (CHANGE MASTER TO),
C(changeprimary) (CHANGE MASTER TO) - also works for MySQL 8.0.23 and later since community.mysql 3.10.0,
C(changereplication) (CHANGE REPLICATION SOURCE TO) - only supported in MySQL 8.0.23 and later,
C(getprimary) (SHOW MASTER STATUS),
C(getreplica) (SHOW REPLICA STATUS),
Expand Down Expand Up @@ -403,8 +404,8 @@ def reset_replica_all(module, cursor, connection_name='', channel='', fail_on_er
return reset


def reset_primary(module, cursor, fail_on_error=False):
query = 'RESET MASTER'
def reset_primary(module, cursor, command_resolver, fail_on_error=False):
query = command_resolver.resolve_command('RESET MASTER')
try:
executed_queries.append(query)
cursor.execute(query)
Expand All @@ -413,7 +414,7 @@ def reset_primary(module, cursor, fail_on_error=False):
reset = False
except Exception as e:
if fail_on_error:
module.fail_json(msg="RESET MASTER failed: %s" % to_native(e))
module.fail_json(msg="%s failed: %s" % (command_resolver.resolve_command('RESET MASTER'), to_native(e)))
reset = False
return reset

Expand Down Expand Up @@ -607,52 +608,52 @@ def main():
chm = []
result = {}
if primary_host is not None:
chm.append("MASTER_HOST='%s'" % primary_host)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_HOST'), primary_host))
if primary_user is not None:
chm.append("MASTER_USER='%s'" % primary_user)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_USER'), primary_user))
if primary_password is not None:
chm.append("MASTER_PASSWORD='%s'" % primary_password)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_PASSWORD'), primary_password))
if primary_port is not None:
chm.append("MASTER_PORT=%s" % primary_port)
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_PORT'), primary_port))
if primary_connect_retry is not None:
chm.append("MASTER_CONNECT_RETRY=%s" % primary_connect_retry)
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_CONNECT_RETRY'), primary_connect_retry))
if primary_log_file is not None:
chm.append("MASTER_LOG_FILE='%s'" % primary_log_file)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_LOG_FILE'), primary_log_file))
if primary_log_pos is not None:
chm.append("MASTER_LOG_POS=%s" % primary_log_pos)
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_LOG_POS'), primary_log_pos))
if primary_delay is not None:
chm.append("MASTER_DELAY=%s" % primary_delay)
chm.append("%s=%s" % (command_resolver.resolve_command('MASTER_DELAY'), primary_delay))
if relay_log_file is not None:
chm.append("RELAY_LOG_FILE='%s'" % relay_log_file)
if relay_log_pos is not None:
chm.append("RELAY_LOG_POS=%s" % relay_log_pos)
if primary_ssl is not None:
if primary_ssl:
chm.append("MASTER_SSL=1")
chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL'))
else:
chm.append("MASTER_SSL=0")
chm.append("%s=0" % command_resolver.resolve_command('MASTER_SSL'))
if primary_ssl_ca is not None:
chm.append("MASTER_SSL_CA='%s'" % primary_ssl_ca)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CA'), primary_ssl_ca))
if primary_ssl_capath is not None:
chm.append("MASTER_SSL_CAPATH='%s'" % primary_ssl_capath)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CAPATH'), primary_ssl_capath))
if primary_ssl_cert is not None:
chm.append("MASTER_SSL_CERT='%s'" % primary_ssl_cert)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CERT'), primary_ssl_cert))
if primary_ssl_key is not None:
chm.append("MASTER_SSL_KEY='%s'" % primary_ssl_key)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_KEY'), primary_ssl_key))
if primary_ssl_cipher is not None:
chm.append("MASTER_SSL_CIPHER='%s'" % primary_ssl_cipher)
chm.append("%s='%s'" % (command_resolver.resolve_command('MASTER_SSL_CIPHER'), primary_ssl_cipher))
if primary_ssl_verify_server_cert:
chm.append("SOURCE_SSL_VERIFY_SERVER_CERT=1")
chm.append("%s=1" % command_resolver.resolve_command('MASTER_SSL_VERIFY_SERVER_CERT'))
if primary_auto_position:
chm.append("MASTER_AUTO_POSITION=1")
chm.append("%s=1" % command_resolver.resolve_command('MASTER_AUTO_POSITION'))
if primary_use_gtid is not None:
chm.append("MASTER_USE_GTID=%s" % primary_use_gtid)
chm.append("MASTER_USE_GTID=%s" % primary_use_gtid) # MariaDB only
try:
changeprimary(cursor, command_resolver, chm, connection_name, channel)
except mysql_driver.Warning as e:
result['warning'] = to_native(e)
except Exception as e:
module.fail_json(msg='%s. Query == CHANGE MASTER TO %s' % (to_native(e), chm))
module.fail_json(msg='%s. Query == %s TO %s' % (to_native(e), command_resolver.resolve_command('CHANGE MASTER'), chm))
result['changed'] = True
module.exit_json(queries=executed_queries, **result)
elif mode == "startreplica":
Expand All @@ -668,7 +669,7 @@ def main():
else:
module.exit_json(msg="Replica already stopped", changed=False, queries=executed_queries)
elif mode == 'resetprimary':
reset = reset_primary(module, cursor, fail_on_error)
reset = reset_primary(module, cursor, command_resolver, fail_on_error)
if reset is True:
module.exit_json(msg="Primary reset", changed=True, queries=executed_queries)
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
Expand All @@ -18,8 +19,7 @@
# Tests of channel parameter:
- import_tasks: mysql_replication_channel.yml
when:
- db_engine == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7
- mysql8022_and_higher == true # FIXME: mysql 5.7 should work, but our tets fails, why?
- db_engine == 'mysql' # FIXME: mariadb introduces FOR CHANNEL in 10.7

# Tests of resetprimary mode:
- import_tasks: mysql_replication_resetprimary_mode.yml
Expand All @@ -30,3 +30,4 @@
- import_tasks: mysql_replication_changereplication_mode.yml
when:
- db_engine == 'mysql'
- db_version is version('8.0.23', '>=')
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@
when:
- db_engine == 'mysql' and db_version is version('8.0.23', '>=')
vars:
result_query: ["CHANGE REPLICATION SOURCE TO MASTER_HOST='{{ mysql_host }}',\
MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\
MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE=\
'{{ mysql_primary_status.File }}',MASTER_LOG_POS=\
result_query: ["CHANGE REPLICATION SOURCE TO SOURCE_HOST='{{ mysql_host }}',\
SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\
SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\
'{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\
{{ mysql_primary_status.Position }} FOR CHANNEL '{{ test_channel }}'"]

# Test startreplica mode:
Expand Down Expand Up @@ -102,7 +102,10 @@
mysql_host_value: '{{ mysql_host }}'
mysql_primary_port_value: '{{ mysql_primary_port }}'
test_channel_value: '{{ test_channel }}'
when: mysql8022_and_higher == false
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.22', '<'))
- assert:
that:
Expand All @@ -118,7 +121,9 @@
mysql_host_value: '{{ mysql_host }}'
mysql_primary_port_value: '{{ mysql_primary_port }}'
test_channel_value: '{{ test_channel }}'
when: mysql8022_and_higher == true
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')


# Test stopreplica mode:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
login_host: '{{ mysql_host }}'

block:
- name: Set mysql8022_and_higher
set_fact:
mysql8022_and_higher: false

- name: Set mysql8022_and_higher
set_fact:
mysql8022_and_higher: true
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')

# We use iF NOT EXISTS because the GITHUB Action:
# "ansible-community/ansible-test-gh-action" uses "--retry-on-error".
Expand Down Expand Up @@ -136,11 +126,10 @@
that:
- result is not failed

# Test changeprimary mode:
# primary_ssl_ca will be set as '' to check the module's behaviour for #23976,
# must be converted to an empty string
- name: Run replication
mysql_replication:
- name: Test changeprimary mode with empty primary_ssl_ca
community.mysql.mysql_replication:
<<: *mysql_params
login_port: '{{ mysql_replica1_port }}'
mode: changeprimary
Expand All @@ -151,7 +140,7 @@
primary_log_file: '{{ mysql_primary_status.File }}'
primary_log_pos: '{{ mysql_primary_status.Position }}'
primary_ssl_ca: ''
primary_ssl: no
primary_ssl: false
register: result

- name: Assert that changeprimmary is changed and return expected query for MariaDB and MySQL < 8.0.23
Expand All @@ -176,14 +165,15 @@
- result is changed
- result.queries == expected_queries
when:
- db_engine == 'mysql' and db_version is version('8.0.23', '>=')
- db_engine == 'mysql'
- db_version is version('8.0.23', '>=')
vars:
expected_queries: ["CHANGE REPLICATION SOURCE TO \
MASTER_HOST='{{ mysql_host }}',\
MASTER_USER='{{ replication_user }}',MASTER_PASSWORD='********',\
MASTER_PORT={{ mysql_primary_port }},MASTER_LOG_FILE=\
'{{ mysql_primary_status.File }}',MASTER_LOG_POS=\
{{ mysql_primary_status.Position }},MASTER_SSL=0,MASTER_SSL_CA=''"]
SOURCE_HOST='{{ mysql_host }}',\
SOURCE_USER='{{ replication_user }}',SOURCE_PASSWORD='********',\
SOURCE_PORT={{ mysql_primary_port }},SOURCE_LOG_FILE=\
'{{ mysql_primary_status.File }}',SOURCE_LOG_POS=\
{{ mysql_primary_status.Position }},SOURCE_SSL=0,SOURCE_SSL_CA=''"]

# Test startreplica mode:
- name: Start replica
Expand Down Expand Up @@ -220,7 +210,10 @@
vars:
mysql_host_value: "{{ mysql_host }}"
mysql_primary_port_value: "{{ mysql_primary_port }}"
when: mysql8022_and_higher is falsy(convert_bool=True)
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.22', '<'))
- name: Assert that getreplica returns expected values for MySQL newer than 8.0.22
assert:
Expand All @@ -235,7 +228,9 @@
vars:
mysql_host_value: "{{ mysql_host }}"
mysql_primary_port_value: "{{ mysql_primary_port }}"
when: mysql8022_and_higher is truthy(convert_bool=True)
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')

# Create test table and add data to it:
- name: Create test table
Expand All @@ -262,13 +257,18 @@
assert:
that:
- replica_status.Exec_Master_Log_Pos != mysql_primary_status.Position
when: mysql8022_and_higher == false
when:
- >
db_engine == 'mariadb' or
(db_engine == 'mysql' and db_version is version('8.0.22', '<'))
- name: Assert that getreplica Log_Pos is different for MySQL newer than 8.0.22
assert:
that:
- replica_status.Exec_Source_Log_Pos != mysql_primary_status.Position
when: mysql8022_and_higher == true
when:
- db_engine == 'mysql'
- db_version is version('8.0.22', '>=')

- name: Start replica that is already running
mysql_replication:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
ansible.builtin.assert:
that:
- result is changed
- result.queries == ["CHANGE REPLICATION SOURCE TO MASTER_DELAY=60"]
- result.queries == ["CHANGE REPLICATION SOURCE TO SOURCE_DELAY=60"]
when:
- db_engine == 'mysql' and db_version is version('8.0.23', '>=')

Expand Down
Loading

0 comments on commit 8df9c69

Please sign in to comment.