Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ebe5038

Browse files
authored
mysql_replication: fix crashes caused by deprecated terminology (ansible-collections#71)
* mysql_replication: fix crashes caused by deprecated terminology * Fix unrelated sanity errors * Tests: mysql 8.0.21 -> 8.022 * Adjust integration tests * Add version check to the tests * Add debug statement * Adjust mysql version * Fix tests * Add unit tests * Add changelog fragment * Improve code and coverage * Get rid of extra blank line * Improve coverage * Change suggested
1 parent b7e828a commit ebe5038

File tree

8 files changed

+194
-29
lines changed

8 files changed

+194
-29
lines changed

.github/workflows/ansible-test-plugins.yml

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
matrix:
5656
mysql:
5757
- 5.7.31
58-
- 8.0.21
58+
- 8.0.22
5959
ansible:
6060
- stable-2.9
6161
- stable-2.10
@@ -67,7 +67,7 @@ jobs:
6767
- pymysql==0.9.3
6868
- mysqlclient==2.0.1
6969
exclude:
70-
- mysql: 8.0.21
70+
- mysql: 8.0.22
7171
connector: pymysql==0.7.10
7272
steps:
7373

@@ -101,3 +101,45 @@ jobs:
101101
- uses: codecov/codecov-action@v1
102102
with:
103103
fail_ci_if_error: false
104+
105+
units:
106+
runs-on: ubuntu-latest
107+
name: Units (Ⓐ${{ matrix.ansible }})
108+
strategy:
109+
# As soon as the first unit test fails,
110+
# cancel the others to free up the CI queue
111+
fail-fast: true
112+
matrix:
113+
ansible:
114+
- stable-2.9
115+
- stable-2.10
116+
- devel
117+
118+
steps:
119+
- name: Check out code
120+
uses: actions/checkout@v2
121+
with:
122+
path: ./ansible_collections/community/mysql
123+
124+
- name: Set up Python
125+
uses: actions/setup-python@v2
126+
with:
127+
python-version: 3.8
128+
129+
- name: Install ansible-base (${{matrix.ansible}})
130+
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
131+
132+
# Run the unit tests
133+
- name: Run unit test
134+
run: ansible-test units -v --color --docker --coverage
135+
working-directory: ./ansible_collections/community/mysql
136+
137+
# ansible-test support producing code coverage date
138+
- name: Generate coverage report
139+
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
140+
working-directory: ./ansible_collections/community/mysql
141+
142+
# See the reports at https://codecov.io/gh/GITHUBORG/REPONAME
143+
- uses: codecov/codecov-action@v1
144+
with:
145+
fail_ci_if_error: false
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bugfixes:
2+
- mysql_replication - fix crashes of mariadb >= 10.5.1 and mysql >= 8.0.22 caused by using deprecated terminology (https://github.com/ansible-collections/community.mysql/issues/70).

plugins/modules/mysql_replication.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -242,21 +242,40 @@
242242
from ansible.module_utils.basic import AnsibleModule
243243
from ansible_collections.community.mysql.plugins.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg, mysql_common_argument_spec
244244
from ansible.module_utils._text import to_native
245+
from distutils.version import LooseVersion
245246

246247
executed_queries = []
247248

248249

250+
def uses_replica_terminology(cursor):
251+
"""Checks if REPLICA must be used instead of SLAVE"""
252+
cursor.execute("SELECT VERSION() AS version")
253+
result = cursor.fetchone()
254+
255+
if isinstance(result, dict):
256+
version_str = result['version']
257+
else:
258+
version_str = result[0]
259+
260+
version = LooseVersion(version_str)
261+
262+
if 'mariadb' in version_str.lower():
263+
return version >= LooseVersion('10.5.1')
264+
else:
265+
return version >= LooseVersion('8.0.22')
266+
267+
249268
def get_master_status(cursor):
250269
cursor.execute("SHOW MASTER STATUS")
251270
masterstatus = cursor.fetchone()
252271
return masterstatus
253272

254273

255-
def get_slave_status(cursor, connection_name='', channel=''):
274+
def get_slave_status(cursor, connection_name='', channel='', term='REPLICA'):
256275
if connection_name:
257-
query = "SHOW SLAVE '%s' STATUS" % connection_name
276+
query = "SHOW %s '%s' STATUS" % (term, connection_name)
258277
else:
259-
query = "SHOW SLAVE STATUS"
278+
query = "SHOW %s STATUS" % term
260279

261280
if channel:
262281
query += " FOR CHANNEL '%s'" % channel
@@ -266,11 +285,11 @@ def get_slave_status(cursor, connection_name='', channel=''):
266285
return slavestatus
267286

268287

269-
def stop_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
288+
def stop_slave(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
270289
if connection_name:
271-
query = "STOP SLAVE '%s'" % connection_name
290+
query = "STOP %s '%s'" % (term, connection_name)
272291
else:
273-
query = 'STOP SLAVE'
292+
query = 'STOP %s' % term
274293

275294
if channel:
276295
query += " FOR CHANNEL '%s'" % channel
@@ -288,11 +307,11 @@ def stop_slave(module, cursor, connection_name='', channel='', fail_on_error=Fal
288307
return stopped
289308

290309

291-
def reset_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
310+
def reset_slave(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
292311
if connection_name:
293-
query = "RESET SLAVE '%s'" % connection_name
312+
query = "RESET %s '%s'" % (term, connection_name)
294313
else:
295-
query = 'RESET SLAVE'
314+
query = 'RESET %s' % term
296315

297316
if channel:
298317
query += " FOR CHANNEL '%s'" % channel
@@ -310,11 +329,11 @@ def reset_slave(module, cursor, connection_name='', channel='', fail_on_error=Fa
310329
return reset
311330

312331

313-
def reset_slave_all(module, cursor, connection_name='', channel='', fail_on_error=False):
332+
def reset_slave_all(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
314333
if connection_name:
315-
query = "RESET SLAVE '%s' ALL" % connection_name
334+
query = "RESET %s '%s' ALL" % (term, connection_name)
316335
else:
317-
query = 'RESET SLAVE ALL'
336+
query = 'RESET %s ALL' % term
318337

319338
if channel:
320339
query += " FOR CHANNEL '%s'" % channel
@@ -347,11 +366,11 @@ def reset_master(module, cursor, fail_on_error=False):
347366
return reset
348367

349368

350-
def start_slave(module, cursor, connection_name='', channel='', fail_on_error=False):
369+
def start_slave(module, cursor, connection_name='', channel='', fail_on_error=False, term='REPLICA'):
351370
if connection_name:
352-
query = "START SLAVE '%s'" % connection_name
371+
query = "START %s '%s'" % (term, connection_name)
353372
else:
354-
query = 'START SLAVE'
373+
query = 'START %s' % term
355374

356375
if channel:
357376
query += " FOR CHANNEL '%s'" % channel
@@ -467,6 +486,13 @@ def main():
467486
else:
468487
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e)))
469488

489+
# Since MySQL 8.0.22 and MariaDB 10.5.1,
490+
# "REPLICA" must be used instead of "SLAVE"
491+
if uses_replica_terminology(cursor):
492+
replica_term = 'REPLICA'
493+
else:
494+
replica_term = 'SLAVE'
495+
470496
if mode in "getmaster":
471497
status = get_master_status(cursor)
472498
if not isinstance(status, dict):
@@ -476,7 +502,7 @@ def main():
476502
module.exit_json(queries=executed_queries, **status)
477503

478504
elif mode in "getslave":
479-
status = get_slave_status(cursor, connection_name, channel)
505+
status = get_slave_status(cursor, connection_name, channel, replica_term)
480506
if not isinstance(status, dict):
481507
status = dict(Is_Slave=False, msg="Server is not configured as mysql slave")
482508
else:
@@ -531,13 +557,13 @@ def main():
531557
result['changed'] = True
532558
module.exit_json(queries=executed_queries, **result)
533559
elif mode in "startslave":
534-
started = start_slave(module, cursor, connection_name, channel, fail_on_error)
560+
started = start_slave(module, cursor, connection_name, channel, fail_on_error, replica_term)
535561
if started is True:
536562
module.exit_json(msg="Slave started ", changed=True, queries=executed_queries)
537563
else:
538564
module.exit_json(msg="Slave already started (Or cannot be started)", changed=False, queries=executed_queries)
539565
elif mode in "stopslave":
540-
stopped = stop_slave(module, cursor, connection_name, channel, fail_on_error)
566+
stopped = stop_slave(module, cursor, connection_name, channel, fail_on_error, replica_term)
541567
if stopped is True:
542568
module.exit_json(msg="Slave stopped", changed=True, queries=executed_queries)
543569
else:
@@ -549,13 +575,13 @@ def main():
549575
else:
550576
module.exit_json(msg="Master already reset", changed=False, queries=executed_queries)
551577
elif mode in "resetslave":
552-
reset = reset_slave(module, cursor, connection_name, channel, fail_on_error)
578+
reset = reset_slave(module, cursor, connection_name, channel, fail_on_error, replica_term)
553579
if reset is True:
554580
module.exit_json(msg="Slave reset", changed=True, queries=executed_queries)
555581
else:
556582
module.exit_json(msg="Slave already reset", changed=False, queries=executed_queries)
557583
elif mode in "resetslaveall":
558-
reset = reset_slave_all(module, cursor, connection_name, channel, fail_on_error)
584+
reset = reset_slave_all(module, cursor, connection_name, channel, fail_on_error, replica_term)
559585
if reset is True:
560586
module.exit_json(msg="Slave reset", changed=True, queries=executed_queries)
561587
else:

tests/integration/targets/setup_mysql/defaults/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ percona_client_version: 5.7
77

88
mariadb_install: false
99

10-
mysql_version: 8.0.21
10+
mysql_version: 8.0.22
1111
mariadb_version: 10.5.4
1212

1313
mysql_base_port: 3306

tests/integration/targets/test_mysql_replication/tasks/mysql_replication_channel.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
- assert:
4949
that:
5050
- result is changed
51-
- result.queries == ["START SLAVE FOR CHANNEL '{{ test_channel }}'"]
51+
- result.queries == ["START SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["START REPLICA FOR CHANNEL '{{ test_channel }}'"]
5252

5353
# Test getslave mode:
5454
- name: Get standby status with channel
@@ -69,6 +69,20 @@
6969
- slave_status.Last_IO_Error == ''
7070
- slave_status.Channel_Name == '{{ test_channel }}'
7171
- slave_status is not changed
72+
when: mysql8022_and_higher == false
73+
74+
- assert:
75+
that:
76+
- slave_status.Is_Slave == true
77+
- slave_status.Source_Host == '{{ mysql_host }}'
78+
- slave_status.Exec_Source_Log_Pos == mysql_primary_status.Position
79+
- slave_status.Source_Port == {{ mysql_primary_port }}
80+
- slave_status.Last_IO_Errno == 0
81+
- slave_status.Last_IO_Error == ''
82+
- slave_status.Channel_Name == '{{ test_channel }}'
83+
- slave_status is not changed
84+
when: mysql8022_and_higher == true
85+
7286

7387
# Test stopslave mode:
7488
- name: Stop slave with channel
@@ -82,7 +96,7 @@
8296
- assert:
8397
that:
8498
- result is changed
85-
- result.queries == ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"]
99+
- result.queries == ["STOP SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["STOP REPLICA FOR CHANNEL '{{ test_channel }}'"]
86100

87101
# Test reset
88102
- name: Reset slave with channel
@@ -96,7 +110,7 @@
96110
- assert:
97111
that:
98112
- result is changed
99-
- result.queries == ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"]
113+
- result.queries == ["RESET SLAVE FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["RESET REPLICA FOR CHANNEL '{{ test_channel }}'"]
100114

101115
# Test reset all
102116
- name: Reset slave all with channel
@@ -110,4 +124,4 @@
110124
- assert:
111125
that:
112126
- result is changed
113-
- result.queries == ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"]
127+
- result.queries == ["RESET SLAVE ALL FOR CHANNEL '{{ test_channel }}'"] or result.queries == ["RESET REPLICA ALL FOR CHANNEL '{{ test_channel }}'"]

tests/integration/targets/test_mysql_replication/tasks/mysql_replication_initial.yml

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@
88
login_host: 127.0.0.1
99

1010
block:
11+
- name: find out the database version
12+
mysql_info:
13+
<<: *mysql_params
14+
login_port: '{{ mysql_primary_port }}'
15+
filter: version
16+
register: db
17+
18+
- name: Set mysql8022_and_higher
19+
set_fact:
20+
mysql8022_and_higher: false
21+
22+
- name: Set mysql8022_and_higher
23+
set_fact:
24+
mysql8022_and_higher: true
25+
when:
26+
- db.version.major > 8 or (db.version.major == 8 and db.version.minor > 0) or (db.version.major == 8 and db.version.minor == 0 and db.version.release >= 22)
1127

1228
- name: alias mysql command to include default options
1329
set_fact:
@@ -120,7 +136,7 @@
120136
- assert:
121137
that:
122138
- result is changed
123-
- result.queries == ["START SLAVE"]
139+
- result.queries == ["START SLAVE"] or result.queries == ["START REPLICA"]
124140

125141
# Test getslave mode:
126142
- name: Get standby status
@@ -139,6 +155,18 @@
139155
- slave_status.Last_IO_Errno == 0
140156
- slave_status.Last_IO_Error == ''
141157
- slave_status is not changed
158+
when: mysql8022_and_higher == false
159+
160+
- assert:
161+
that:
162+
- slave_status.Is_Slave == true
163+
- slave_status.Source_Host == '{{ mysql_host }}'
164+
- slave_status.Exec_Source_Log_Pos == mysql_primary_status.Position
165+
- slave_status.Source_Port == {{ mysql_primary_port }}
166+
- slave_status.Last_IO_Errno == 0
167+
- slave_status.Last_IO_Error == ''
168+
- slave_status is not changed
169+
when: mysql8022_and_higher == true
142170

143171
# Create test table and add data to it:
144172
- name: Create test table
@@ -164,6 +192,12 @@
164192
- assert:
165193
that:
166194
- slave_status.Exec_Master_Log_Pos != mysql_primary_status.Position
195+
when: mysql8022_and_higher == false
196+
197+
- assert:
198+
that:
199+
- slave_status.Exec_Source_Log_Pos != mysql_primary_status.Position
200+
when: mysql8022_and_higher == true
167201

168202
- shell: pip show pymysql | awk '/Version/ {print $2}'
169203
register: pymysql_version
@@ -192,7 +226,7 @@
192226
- assert:
193227
that:
194228
- result is changed
195-
- result.queries == ["STOP SLAVE"]
229+
- result.queries == ["STOP SLAVE"] or result.queries == ["STOP REPLICA"]
196230

197231
# Test stopslave mode:
198232
- name: Stop slave that is no longer running

tests/unit/plugins/modules/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)