mysql_variables: add mode parameter (#63547)

* mysql_variables: add mode parameter

* mysql_variables: add mode parameter, add changelog fragment

* mysql_variables: add mode parameter, fix

* mysql_variables: add mode parameter, fix

* mysql_variables: add mode parameter, fix

* mysql_variables: add mode parameter, fix sanity

* mysql_variables: add mode parameter, remove warns

* mysql_variables: add mode parameter, refactoring

* mysql_variables: add mode parameter, fix logic
This commit is contained in:
Andrey Klychkov 2019-11-02 12:16:28 +03:00 committed by Felix Fontein
parent 42b290b781
commit 73526b9d65
3 changed files with 179 additions and 19 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- mysql_variables - add ``mode`` parameter (https://github.com/ansible/ansible/issues/60119).

View file

@ -19,7 +19,7 @@ module: mysql_variables
short_description: Manage MySQL global variables short_description: Manage MySQL global variables
description: description:
- Query / Set MySQL variables. - Query / Set MySQL variables.
version_added: 1.3 version_added: '1.3'
author: author:
- Balazs Pocze (@banyek) - Balazs Pocze (@banyek)
options: options:
@ -32,6 +32,22 @@ options:
description: description:
- If set, then sets variable value to this - If set, then sets variable value to this
type: str type: str
mode:
description:
- C(global) assigns C(value) to a global system variable which will be changed at runtime
but won't persist across server restarts.
- C(persist) assigns C(value) to a global system variable and persists it to
the mysqld-auto.cnf option file in the data directory
(the variable will survive service restarts).
- C(persist_only) persists C(value) to the mysqld-auto.cnf option file in the data directory
but without setting the global variable runtime value
(the value will be changed after the next service restart).
- Supported by MySQL 8.0 or later.
- For more information see U(https://dev.mysql.com/doc/refman/8.0/en/set-variable.html).
type: str
choices: ['global', 'persist', 'persist_only']
default: global
version_added: '2.10'
seealso: seealso:
- module: mysql_info - module: mysql_info
@ -48,10 +64,11 @@ EXAMPLES = r'''
mysql_variables: mysql_variables:
variable: sync_binlog variable: sync_binlog
- name: Set read_only variable to 1 - name: Set read_only variable to 1 persistently
mysql_variables: mysql_variables:
variable: read_only variable: read_only
value: 1 value: 1
mode: persist
''' '''
RETURN = r''' RETURN = r'''
@ -75,6 +92,24 @@ from ansible.module_utils._text import to_native
executed_queries = [] executed_queries = []
def check_mysqld_auto(module, cursor, mysqlvar):
"""Check variable's value in mysqld-auto.cnf."""
query = ("SELECT VARIABLE_VALUE "
"FROM performance_schema.persisted_variables "
"WHERE VARIABLE_NAME = %s")
try:
cursor.execute(query, (mysqlvar,))
res = cursor.fetchone()
except Exception as e:
if "Table 'performance_schema.persisted_variables' doesn't exist" in str(e):
module.fail_json(msg='Server version must be 8.0 or greater.')
if res:
return res[0]
else:
return None
def typedvalue(value): def typedvalue(value):
""" """
Convert value to number whenever possible, return same value Convert value to number whenever possible, return same value
@ -110,7 +145,7 @@ def getvariable(cursor, mysqlvar):
return None return None
def setvariable(cursor, mysqlvar, value): def setvariable(cursor, mysqlvar, value, mode='global'):
""" Set a global mysql variable to a given value """ Set a global mysql variable to a given value
The DB driver will handle quoting of the given value based on its The DB driver will handle quoting of the given value based on its
@ -118,7 +153,13 @@ def setvariable(cursor, mysqlvar, value):
should be passed as numeric literals. should be passed as numeric literals.
""" """
query = "SET GLOBAL %s = " % mysql_quote_identifier(mysqlvar, 'vars') if mode == 'persist':
query = "SET PERSIST %s = " % mysql_quote_identifier(mysqlvar, 'vars')
elif mode == 'global':
query = "SET GLOBAL %s = " % mysql_quote_identifier(mysqlvar, 'vars')
elif mode == 'persist_only':
query = "SET PERSIST_ONLY %s = " % mysql_quote_identifier(mysqlvar, 'vars')
try: try:
cursor.execute(query + "%s", (value,)) cursor.execute(query + "%s", (value,))
executed_queries.append(query + "%s" % value) executed_queries.append(query + "%s" % value)
@ -126,6 +167,7 @@ def setvariable(cursor, mysqlvar, value):
result = True result = True
except Exception as e: except Exception as e:
result = to_native(e) result = to_native(e)
return result return result
@ -144,6 +186,7 @@ def main():
ca_cert=dict(type='path', aliases=['ssl_ca']), ca_cert=dict(type='path', aliases=['ssl_ca']),
connect_timeout=dict(type='int', default=30), connect_timeout=dict(type='int', default=30),
config_file=dict(type='path', default='~/.my.cnf'), config_file=dict(type='path', default='~/.my.cnf'),
mode=dict(type='str', choices=['global', 'persist', 'persist_only'], default='global'),
), ),
) )
user = module.params["login_user"] user = module.params["login_user"]
@ -157,6 +200,8 @@ def main():
mysqlvar = module.params["variable"] mysqlvar = module.params["variable"]
value = module.params["value"] value = module.params["value"]
mode = module.params["mode"]
if mysqlvar is None: if mysqlvar is None:
module.fail_json(msg="Cannot run without variable to operate with") module.fail_json(msg="Cannot run without variable to operate with")
if match('^[0-9a-z_]+$', mysqlvar) is None: if match('^[0-9a-z_]+$', mysqlvar) is None:
@ -177,27 +222,54 @@ def main():
else: else:
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e))) module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e)))
mysqlvar_val = None
var_in_mysqld_auto_cnf = None
mysqlvar_val = getvariable(cursor, mysqlvar) mysqlvar_val = getvariable(cursor, mysqlvar)
if mysqlvar_val is None: if mysqlvar_val is None:
module.fail_json(msg="Variable not available \"%s\"" % mysqlvar, changed=False) module.fail_json(msg="Variable not available \"%s\"" % mysqlvar, changed=False)
if value is None: if value is None:
module.exit_json(msg=mysqlvar_val) module.exit_json(msg=mysqlvar_val)
else:
# Type values before using them
value_wanted = typedvalue(value)
value_actual = typedvalue(mysqlvar_val)
if value_wanted == value_actual:
module.exit_json(msg="Variable already set to requested value", changed=False)
try:
result = setvariable(cursor, mysqlvar, value_wanted)
except SQLParseError as e:
result = to_native(e)
if result is True: if mode in ('persist', 'persist_only'):
module.exit_json(msg="Variable change succeeded prev_value=%s" % value_actual, var_in_mysqld_auto_cnf = check_mysqld_auto(module, cursor, mysqlvar)
changed=True, queries=executed_queries)
else: if mode == 'persist_only':
module.fail_json(msg=result, changed=False) if var_in_mysqld_auto_cnf is None:
mysqlvar_val = False
else:
mysqlvar_val = var_in_mysqld_auto_cnf
# Type values before using them
value_wanted = typedvalue(value)
value_actual = typedvalue(mysqlvar_val)
value_in_auto_cnf = None
if var_in_mysqld_auto_cnf is not None:
value_in_auto_cnf = typedvalue(var_in_mysqld_auto_cnf)
if value_wanted == value_actual and mode in ('global', 'persist'):
if mode == 'persist' and value_wanted == value_in_auto_cnf:
module.exit_json(msg="Variable is already set to requested value globally"
"and stored into mysqld-auto.cnf file.", changed=False)
elif mode == 'global':
module.exit_json(msg="Variable is already set to requested value.", changed=False)
if mode == 'persist_only' and value_wanted == value_in_auto_cnf:
module.exit_json(msg="Variable is already stored into mysqld-auto.cnf "
"with requested value.", changed=False)
try:
result = setvariable(cursor, mysqlvar, value_wanted, mode)
except SQLParseError as e:
result = to_native(e)
if result is True:
module.exit_json(msg="Variable change succeeded prev_value=%s" % value_actual,
changed=True, queries=executed_queries)
else:
module.fail_json(msg=result, changed=False)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -308,3 +308,89 @@
login_unix_socket: '{{ mysql_socket }}' login_unix_socket: '{{ mysql_socket }}'
login_user: root login_user: root
login_password: '{{ root_pass }}' login_password: '{{ root_pass }}'
#=========================================
# Check mode 'persist' and 'persist_only':
#
- name: update mysql variable value (expect changed=true) in persist mode
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist
register: result
- assert:
that:
- result.queries == ["SET PERSIST `{{ set_name }}` = {{ set_value }}"]
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{set_value}}'
- name: try to update mysql variable value (expect changed=false) in persist mode again
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist
register: result
- include: assert_var.yml changed=false output={{result}} var_name={{set_name}} var_value='{{set_value}}'
- name: set mysql variable to a temp value
mysql_variables:
variable: '{{ set_name }}'
value: '200'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist
- name: update mysql variable value (expect changed=true) in persist_only mode
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist_only
register: result
- assert:
that:
- result is changed
- result.queries == ["SET PERSIST_ONLY `{{ set_name }}` = {{ set_value }}"]
- name: try to update mysql variable value (expect changed=false) in persist_only mode again
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist_only
register: result
- assert:
that:
- result is not changed
- set_fact:
set_name: max_connections
set_value: 105
def_val: 151
- name: update mysql variable value (expect changed=true) in persist_only mode
mysql_variables:
variable: '{{ set_name }}'
value: '{{ set_value }}'
login_unix_socket: '{{ mysql_socket }}'
login_user: root
login_password: '{{ root_pass }}'
mode: persist_only
register: result
- include: assert_var.yml changed=true output={{result}} var_name={{set_name}} var_value='{{def_val}}'