1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Adding support for ProxySQL (#19872)

* Adding support for ProxySQL
This commit is contained in:
bmildren 2017-04-04 21:47:19 +01:00 committed by Toshio Kuratomi
parent 00231c29ba
commit 21ab4343e3
8 changed files with 3202 additions and 0 deletions

View file

@ -0,0 +1,554 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_backend_servers
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes mysql hosts from proxysql admin interface.
description:
- The M(proxysql_backend_servers) module adds or removes mysql hosts using
the proxysql admin interface.
options:
hostgroup_id:
description:
- The hostgroup in which this mysqld instance is included. An instance
can be part of one or more hostgroups.
default: 0
hostname:
description:
- The ip address at which the mysqld instance can be contacted.
required: True
port:
description:
- The port at which the mysqld instance can be contacted.
default: 3306
status:
description:
- ONLINE - Backend server is fully operational.
OFFLINE_SOFT - When a server is put into C(OFFLINE_SOFT) mode,
connections are kept in use until the current
transaction is completed. This allows to gracefully
detach a backend.
OFFLINE_HARD - When a server is put into C(OFFLINE_HARD) mode, the
existing connections are dropped, while new incoming
connections aren't accepted either.
If omitted the proxysql database default for I(status) is C(ONLINE).
choices: [ "ONLINE", "OFFLINE_SOFT", "OFFLINE_HARD"]
weight:
description:
- The bigger the weight of a server relative to other weights, the higher
the probability of the server being chosen from the hostgroup. If
omitted the proxysql database default for I(weight) is 1.
compression:
description:
- If the value of I(compression) is greater than 0, new connections to
that server will use compression. If omitted the proxysql database
default for I(compression) is 0.
max_connections:
description:
- The maximum number of connections ProxySQL will open to this backend
server. If omitted the proxysql database default for I(max_connections)
is 1000.
max_replication_lag:
description:
- If greater than 0, ProxySQL will reguarly monitor replication lag. If
replication lag goes above I(max_replication_lag), proxysql will
temporarily shun the server until replication catches up. If omitted
the proxysql database default for I(max_replication_lag) is 0.
use_ssl:
description:
- If I(use_ssl) is set to C(True), connections to this server will be
made using SSL connections. If omitted the proxysql database default
for I(use_ssl) is C(False).
max_latency_ms:
description:
- Ping time is monitored regularly. If a host has a ping time greater
than I(max_latency_ms) it is excluded from the connection pool
(although the server stays ONLINE). If omitted the proxysql database
default for I(max_latency_ms) is 0.
comment:
description:
- Text field that can be used for any purposed defined by the user.
Could be a description of what the host stores, a reminder of when the
host was added or disabled, or a JSON processed by some checker script.
default: ''
state:
description:
- When C(present) - adds the host, when C(absent) - removes the host.
choices: [ "present", "absent" ]
default: present
save_to_disk:
description:
- Save mysql host config to sqlite db on disk to persist the
configuration.
default: True
load_to_runtime:
description:
- Dynamically load mysql host config to runtime memory.
default: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example adds a server, it saves the mysql server config to disk, but
# avoids loading the mysql server config to runtime (this might be because
# several servers are being added and the user wants to push the config to
# runtime in a single batch using the M(proxysql_manage_config) module). It
# uses supplied credentials to connect to the proxysql admin interface.
- proxysql_backend_servers:
login_user: 'admin'
login_password: 'admin'
hostname: 'mysql01'
state: present
load_to_runtime: False
# This example removes a server, saves the mysql server config to disk, and
# dynamically loads the mysql server config to runtime. It uses credentials
# in a supplied config file to connect to the proxysql admin interface.
- proxysql_backend_servers:
config_file: '~/proxysql.cnf'
hostname: 'mysql02'
state: absent
'''
RETURN = '''
stdout:
description: The mysql host modified or removed from proxysql
returned: On create/update will return the newly modified host, on delete
it will return the deleted record.
type: dict
"sample": {
"changed": true,
"hostname": "192.168.52.1",
"msg": "Added server to mysql_hosts",
"server": {
"comment": "",
"compression": "0",
"hostgroup_id": "1",
"hostname": "192.168.52.1",
"max_connections": "1000",
"max_latency_ms": "0",
"max_replication_lag": "0",
"port": "3306",
"status": "ONLINE",
"use_ssl": "0",
"weight": "1"
},
"state": "present"
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
from ansible.module_utils.six import iteritems
try:
import MySQLdb
import MySQLdb.cursors
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if module.params["port"] < 0 \
or module.params["port"] > 65535:
module.fail_json(
msg="port must be a valid unix port number (0-65535)"
)
if module.params["compression"]:
if module.params["compression"] < 0 \
or module.params["compression"] > 102400:
module.fail_json(
msg="compression must be set between 0 and 102400"
)
if module.params["max_replication_lag"]:
if module.params["max_replication_lag"] < 0 \
or module.params["max_replication_lag"] > 126144000:
module.fail_json(
msg="max_replication_lag must be set between 0 and 102400"
)
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def save_config_to_disk(cursor):
cursor.execute("SAVE MYSQL SERVERS TO DISK")
return True
def load_config_to_runtime(cursor):
cursor.execute("LOAD MYSQL SERVERS TO RUNTIME")
return True
class ProxySQLServer(object):
def __init__(self, module):
self.state = module.params["state"]
self.save_to_disk = module.params["save_to_disk"]
self.load_to_runtime = module.params["load_to_runtime"]
self.hostgroup_id = module.params["hostgroup_id"]
self.hostname = module.params["hostname"]
self.port = module.params["port"]
config_data_keys = ["status",
"weight",
"compression",
"max_connections",
"max_replication_lag",
"use_ssl",
"max_latency_ms",
"comment"]
self.config_data = dict((k, module.params[k])
for k in config_data_keys)
def check_server_config_exists(self, cursor):
query_string = \
"""SELECT count(*) AS `host_count`
FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['host_count']) > 0)
def check_server_config(self, cursor):
query_string = \
"""SELECT count(*) AS `host_count`
FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += "\n AND " + col + " = %s"
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['host_count']) > 0)
def get_server_config(self, cursor):
query_string = \
"""SELECT *
FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
cursor.execute(query_string, query_data)
server = cursor.fetchone()
return server
def create_server_config(self, cursor):
query_string = \
"""INSERT INTO mysql_servers (
hostgroup_id,
hostname,
port"""
cols = 3
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
query_string += ",\n" + col
query_string += \
(")\n" +
"VALUES (" +
"%s ," * cols)
query_string = query_string[:-2]
query_string += ")"
cursor.execute(query_string, query_data)
return True
def update_server_config(self, cursor):
query_string = """UPDATE mysql_servers"""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\nSET " + col + "= %s,"
else:
query_string += "\n " + col + " = %s,"
query_string = query_string[:-1]
query_string += ("\nWHERE hostgroup_id = %s\n AND hostname = %s" +
"\n AND port = %s")
query_data.append(self.hostgroup_id)
query_data.append(self.hostname)
query_data.append(self.port)
cursor.execute(query_string, query_data)
return True
def delete_server_config(self, cursor):
query_string = \
"""DELETE FROM mysql_servers
WHERE hostgroup_id = %s
AND hostname = %s
AND port = %s"""
query_data = \
[self.hostgroup_id,
self.hostname,
self.port]
cursor.execute(query_string, query_data)
return True
def manage_config(self, cursor, state):
if state:
if self.save_to_disk:
save_config_to_disk(cursor)
if self.load_to_runtime:
load_config_to_runtime(cursor)
def create_server(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.create_server_config(cursor)
result['msg'] = "Added server to mysql_hosts"
result['server'] = \
self.get_server_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Server would have been added to" +
" mysql_hosts, however check_mode" +
" is enabled.")
def update_server(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.update_server_config(cursor)
result['msg'] = "Updated server in mysql_hosts"
result['server'] = \
self.get_server_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Server would have been updated in" +
" mysql_hosts, however check_mode" +
" is enabled.")
def delete_server(self, check_mode, result, cursor):
if not check_mode:
result['server'] = \
self.get_server_config(cursor)
result['changed'] = \
self.delete_server_config(cursor)
result['msg'] = "Deleted server from mysql_hosts"
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Server would have been deleted from" +
" mysql_hosts, however check_mode is" +
" enabled.")
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default='127.0.0.1'),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default='', type='path'),
hostgroup_id=dict(default=0, type='int'),
hostname=dict(required=True, type='str'),
port=dict(default=3306, type='int'),
status=dict(choices=['ONLINE',
'OFFLINE_SOFT',
'OFFLINE_HARD']),
weight=dict(type='int'),
compression=dict(type='int'),
max_connections=dict(type='int'),
max_replication_lag=dict(type='int'),
use_ssl=dict(type='bool'),
max_latency_ms=dict(type='int'),
comment=dict(default='', type='str'),
state=dict(default='present', choices=['present',
'absent']),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class=MySQLdb.cursors.DictCursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
proxysql_server = ProxySQLServer(module)
result = {}
result['state'] = proxysql_server.state
if proxysql_server.hostname:
result['hostname'] = proxysql_server.hostname
if proxysql_server.state == "present":
try:
if not proxysql_server.check_server_config(cursor):
if not proxysql_server.check_server_config_exists(cursor):
proxysql_server.create_server(module.check_mode,
result,
cursor)
else:
proxysql_server.update_server(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The server already exists in mysql_hosts" +
" and doesn't need to be updated.")
result['server'] = \
proxysql_server.get_server_config(cursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to modify server.. %s" % e
)
elif proxysql_server.state == "absent":
try:
if proxysql_server.check_server_config_exists(cursor):
proxysql_server.delete_server(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The server is already absent from the" +
" mysql_hosts memory configuration")
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to remove server.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,316 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_global_variables
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Gets or sets the proxysql global variables.
description:
- The M(proxysql_global_variables) module gets or sets the proxysql global
variables.
options:
variable:
description:
- Defines which variable should be returned, or if I(value) is specified
which variable should be updated.
required: True
value:
description:
- Defines a value the variable specified using I(variable) should be set
to.
save_to_disk:
description:
- Save mysql host config to sqlite db on disk to persist the
configuration.
default: True
load_to_runtime:
description:
- Dynamically load mysql host config to runtime memory.
default: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example sets the value of a variable, saves the mysql admin variables
# config to disk, and dynamically loads the mysql admin variables config to
# runtime. It uses supplied credentials to connect to the proxysql admin
# interface.
- proxysql_global_variables:
login_user: 'admin'
login_password: 'admin'
variable: 'mysql-max_connections'
value: 4096
# This example gets the value of a variable. It uses credentials in a
# supplied config file to connect to the proxysql admin interface.
- proxysql_global_variables:
config_file: '~/proxysql.cnf'
variable: 'mysql-default_query_delay'
'''
RETURN = '''
stdout:
description: Returns the mysql variable supplied with it's associted value.
returned: Returns the current variable and value, or the newly set value
for the variable supplied..
type: dict
"sample": {
"changed": false,
"msg": "The variable is already been set to the supplied value",
"var": {
"variable_name": "mysql-poll_timeout",
"variable_value": "3000"
}
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
try:
import MySQLdb
import MySQLdb.cursors
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def save_config_to_disk(variable, cursor):
if variable.startswith("admin"):
cursor.execute("SAVE ADMIN VARIABLES TO DISK")
else:
cursor.execute("SAVE MYSQL VARIABLES TO DISK")
return True
def load_config_to_runtime(variable, cursor):
if variable.startswith("admin"):
cursor.execute("LOAD ADMIN VARIABLES TO RUNTIME")
else:
cursor.execute("LOAD MYSQL VARIABLES TO RUNTIME")
return True
def check_config(variable, value, cursor):
query_string = \
"""SELECT count(*) AS `variable_count`
FROM global_variables
WHERE variable_name = %s and variable_value = %s"""
query_data = \
[variable, value]
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['variable_count']) > 0)
def get_config(variable, cursor):
query_string = \
"""SELECT *
FROM global_variables
WHERE variable_name = %s"""
query_data = \
[variable, ]
cursor.execute(query_string, query_data)
row_count = cursor.rowcount
resultset = cursor.fetchone()
if row_count > 0:
return resultset
else:
return False
def set_config(variable, value, cursor):
query_string = \
"""UPDATE global_variables
SET variable_value = %s
WHERE variable_name = %s"""
query_data = \
[value, variable]
cursor.execute(query_string, query_data)
return True
def manage_config(variable, save_to_disk, load_to_runtime, cursor, state):
if state:
if save_to_disk:
save_config_to_disk(variable, cursor)
if load_to_runtime:
load_config_to_runtime(variable, cursor)
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default="", type='path'),
variable=dict(required=True, type='str'),
value=dict(),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
variable = module.params["variable"]
value = module.params["value"]
save_to_disk = module.params["save_to_disk"]
load_to_runtime = module.params["load_to_runtime"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class=MySQLdb.cursors.DictCursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
result = {}
if not value:
try:
if get_config(variable, cursor):
result['changed'] = False
result['msg'] = \
"Returned the variable and it's current value"
result['var'] = get_config(variable, cursor)
else:
module.fail_json(
msg="The variable \"%s\" was not found" % variable
)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to get config.. %s" % e
)
else:
try:
if get_config(variable, cursor):
if not check_config(variable, value, cursor):
if not module.check_mode:
result['changed'] = set_config(variable, value, cursor)
result['msg'] = \
"Set the variable to the supplied value"
result['var'] = get_config(variable, cursor)
manage_config(variable,
save_to_disk,
load_to_runtime,
cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Variable would have been set to" +
" the supplied value, however" +
" check_mode is enabled.")
else:
result['changed'] = False
result['msg'] = ("The variable is already been set to" +
" the supplied value")
result['var'] = get_config(variable, cursor)
else:
module.fail_json(
msg="The variable \"%s\" was not found" % variable
)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to set config.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,257 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_manage_config
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Writes the proxysql configuration settings between layers.
description:
- The M(proxysql_global_variables) module writes the proxysql configuration
settings between layers. Currently this module will always report a
changed state, so should typically be used with WHEN however this will
change in a future version when the CHECKSUM table commands are available
for all tables in proxysql.
options:
action:
description:
- The supplied I(action) combines with the supplied I(direction) to
provide the semantics of how we want to move the I(config_settings)
between the I(config_layers).
choices: [ "LOAD", "SAVE" ]
required: True
config_settings:
description:
- The I(config_settings) specifies which configuration we're writing.
choices: [ "MYSQL USERS", "MYSQL SERVERS", "MYSQL QUERY RULES",
"MYSQL VARIABLES", "ADMIN VARIABLES", "SCHEDULER" ]
required: True
direction:
description:
- FROM - denotes we're reading values FROM the supplied I(config_layer)
and writing to the next layer.
TO - denotes we're reading from the previous layer and writing TO the
supplied I(config_layer)."
choices: [ "FROM", "TO" ]
required: True
config_layer:
description:
- RUNTIME - represents the in-memory data structures of ProxySQL used by
the threads that are handling the requests.
MEMORY - (sometimes also referred as main) represents the in-memory
SQLite3 database.
DISK - represents the on-disk SQLite3 database.
CONFIG - is the classical config file. You can only LOAD FROM the
config file.
choices: [ "MEMORY", "DISK", "RUNTIME", "CONFIG" ]
required: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example saves the mysql users config from memory to disk. It uses
# supplied credentials to connect to the proxysql admin interface.
- proxysql_global_variables:
login_user: 'admin'
login_password: 'admin'
action: "SAVE"
config_settings: "MYSQL USERS"
direction: "FROM"
config_layer: "MEMORY"
# This example loads the mysql query rules config from memory to to runtime. It
# uses supplied credentials to connect to the proxysql admin interface.
- proxysql_global_variables:
config_file: '~/proxysql.cnf'
action: "LOAD"
config_settings: "MYSQL QUERY RULES"
direction: "TO"
config_layer: "RUNTIME"
'''
RETURN = '''
stdout:
description: Simply reports whether the action reported a change.
returned: Currently the returned value with always be changed=True.
type: dict
"sample": {
"changed": true
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
try:
import MySQLdb
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if module.params["config_layer"] == 'CONFIG' and \
(module.params["action"] != 'LOAD' or
module.params["direction"] != 'FROM'):
if (module.params["action"] != 'LOAD' and
module.params["direction"] != 'FROM'):
msg_string = ("Neither the action \"%s\" nor the direction" +
" \"%s\" are valid combination with the CONFIG" +
" config_layer")
module.fail_json(msg=msg_string % (module.params["action"],
module.params["direction"]))
elif module.params["action"] != 'LOAD':
msg_string = ("The action \"%s\" is not a valid combination" +
" with the CONFIG config_layer")
module.fail_json(msg=msg_string % module.params["action"])
else:
msg_string = ("The direction \"%s\" is not a valid combination" +
" with the CONFIG config_layer")
module.fail_json(msg=msg_string % module.params["direction"])
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def manage_config(manage_config_settings, cursor):
query_string = "%s" % ' '.join(manage_config_settings)
cursor.execute(query_string)
return True
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default="", type='path'),
action=dict(required=True, choices=['LOAD',
'SAVE']),
config_settings=dict(required=True, choices=['MYSQL USERS',
'MYSQL SERVERS',
'MYSQL QUERY RULES',
'MYSQL VARIABLES',
'ADMIN VARIABLES',
'SCHEDULER']),
direction=dict(required=True, choices=['FROM',
'TO']),
config_layer=dict(required=True, choices=['MEMORY',
'DISK',
'RUNTIME',
'CONFIG'])
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
action = module.params["action"]
config_settings = module.params["config_settings"]
direction = module.params["direction"]
config_layer = module.params["config_layer"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
result = {}
manage_config_settings = \
[action, config_settings, direction, config_layer]
try:
result['changed'] = manage_config(manage_config_settings,
cursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to manage config.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,523 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_mysql_users
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes mysql users from proxysql admin interface.
description:
- The M(proxysql_mysql_users) module adds or removes mysql users using the
proxysql admin interface.
options:
username:
description:
- Name of the user connecting to the mysqld or ProxySQL instance.
required: True
password:
description:
- Password of the user connecting to the mysqld or ProxySQL instance.
active:
description:
- A user with I(active) set to C(False) will be tracked in the database,
but will be never loaded in the in-memory data structures. If omitted
the proxysql database default for I(active) is C(True).
use_ssl:
description:
- If I(use_ssl) is set to C(True), connections by this user will be made
using SSL connections. If omitted the proxysql database default for
I(use_ssl) is C(False).
default_hostgroup:
description:
- If there is no matching rule for the queries sent by this user, the
traffic it generates is sent to the specified hostgroup.
If omitted the proxysql database default for I(use_ssl) is 0.
default_schema:
description:
- The schema to which the connection should change to by default.
transaction_persistent:
description:
- If this is set for the user with which the MySQL client is connecting
to ProxySQL (thus a "frontend" user), transactions started within a
hostgroup will remain within that hostgroup regardless of any other
rules.
If omitted the proxysql database default for I(transaction_persistent) is
C(False).
fast_forward:
description:
- If I(fast_forward) is set to C(True), I(fast_forward) will bypass the
query processing layer (rewriting, caching) and pass through the query
directly as is to the backend server. If omitted the proxysql database
default for I(fast_forward) is C(False).
backend:
description:
- If I(backend) is set to C(True), this (username, password) pair is
used for authenticating to the ProxySQL instance.
default: True
frontend:
description:
- If I(frontend) is set to C(True), this (username, password) pair is
used for authenticating to the mysqld servers against any hostgroup.
default: True
max_connections:
description:
- The maximum number of connections ProxySQL will open to the backend for
this user. If omitted the proxysql database default for
I(max_connections) is 10000.
state:
description:
- When C(present) - adds the user, when C(absent) - removes the user.
choices: [ "present", "absent" ]
default: present
save_to_disk:
description:
- Save mysql host config to sqlite db on disk to persist the
configuration.
default: True
load_to_runtime:
description:
- Dynamically load mysql host config to runtime memory.
default: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example adds a user, it saves the mysql user config to disk, but
# avoids loading the mysql user config to runtime (this might be because
# several users are being added and the user wants to push the config to
# runtime in a single batch using the M(proxysql_manage_config) module). It
# uses supplied credentials to connect to the proxysql admin interface.
- proxysql_mysql_users:
login_user: 'admin'
login_password: 'admin'
username: 'productiondba'
state: present
load_to_runtime: False
# This example removes a user, saves the mysql user config to disk, and
# dynamically loads the mysql user config to runtime. It uses credentials
# in a supplied config file to connect to the proxysql admin interface.
- proxysql_mysql_users:
config_file: '~/proxysql.cnf'
username: 'mysqlboy'
state: absent
'''
RETURN = '''
stdout:
description: The mysql user modified or removed from proxysql
returned: On create/update will return the newly modified user, on delete
it will return the deleted record.
type: dict
sample": {
"changed": true,
"msg": "Added user to mysql_users",
"state": "present",
"user": {
"active": "1",
"backend": "1",
"default_hostgroup": "1",
"default_schema": null,
"fast_forward": "0",
"frontend": "1",
"max_connections": "10000",
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"schema_locked": "0",
"transaction_persistent": "0",
"use_ssl": "0",
"username": "guest_ro"
},
"username": "guest_ro"
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
from ansible.module_utils.six import iteritems
try:
import MySQLdb
import MySQLdb.cursors
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def save_config_to_disk(cursor):
cursor.execute("SAVE MYSQL USERS TO DISK")
return True
def load_config_to_runtime(cursor):
cursor.execute("LOAD MYSQL USERS TO RUNTIME")
return True
class ProxySQLUser(object):
def __init__(self, module):
self.state = module.params["state"]
self.save_to_disk = module.params["save_to_disk"]
self.load_to_runtime = module.params["load_to_runtime"]
self.username = module.params["username"]
self.backend = module.params["backend"]
self.frontend = module.params["frontend"]
config_data_keys = ["password",
"active",
"use_ssl",
"default_hostgroup",
"default_schema",
"transaction_persistent",
"fast_forward",
"max_connections"]
self.config_data = dict((k, module.params[k])
for k in config_data_keys)
def check_user_config_exists(self, cursor):
query_string = \
"""SELECT count(*) AS `user_count`
FROM mysql_users
WHERE username = %s
AND backend = %s
AND frontend = %s"""
query_data = \
[self.username,
self.backend,
self.frontend]
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['user_count']) > 0)
def check_user_privs(self, cursor):
query_string = \
"""SELECT count(*) AS `user_count`
FROM mysql_users
WHERE username = %s
AND backend = %s
AND frontend = %s"""
query_data = \
[self.username,
self.backend,
self.frontend]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += "\n AND " + col + " = %s"
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['user_count']) > 0)
def get_user_config(self, cursor):
query_string = \
"""SELECT *
FROM mysql_users
WHERE username = %s
AND backend = %s
AND frontend = %s"""
query_data = \
[self.username,
self.backend,
self.frontend]
cursor.execute(query_string, query_data)
user = cursor.fetchone()
return user
def create_user_config(self, cursor):
query_string = \
"""INSERT INTO mysql_users (
username,
backend,
frontend"""
cols = 3
query_data = \
[self.username,
self.backend,
self.frontend]
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
query_string += ",\n" + col
query_string += \
(")\n" +
"VALUES (" +
"%s ," * cols)
query_string = query_string[:-2]
query_string += ")"
cursor.execute(query_string, query_data)
return True
def update_user_config(self, cursor):
query_string = """UPDATE mysql_users"""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\nSET " + col + "= %s,"
else:
query_string += "\n " + col + " = %s,"
query_string = query_string[:-1]
query_string += ("\nWHERE username = %s\n AND backend = %s" +
"\n AND frontend = %s")
query_data.append(self.username)
query_data.append(self.backend)
query_data.append(self.frontend)
cursor.execute(query_string, query_data)
return True
def delete_user_config(self, cursor):
query_string = \
"""DELETE FROM mysql_users
WHERE username = %s
AND backend = %s
AND frontend = %s"""
query_data = \
[self.username,
self.backend,
self.frontend]
cursor.execute(query_string, query_data)
return True
def manage_config(self, cursor, state):
if state:
if self.save_to_disk:
save_config_to_disk(cursor)
if self.load_to_runtime:
load_config_to_runtime(cursor)
def create_user(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.create_user_config(cursor)
result['msg'] = "Added user to mysql_users"
result['user'] = \
self.get_user_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("User would have been added to" +
" mysql_users, however check_mode" +
" is enabled.")
def update_user(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.update_user_config(cursor)
result['msg'] = "Updated user in mysql_users"
result['user'] = \
self.get_user_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("User would have been updated in" +
" mysql_users, however check_mode" +
" is enabled.")
def delete_user(self, check_mode, result, cursor):
if not check_mode:
result['user'] = \
self.get_user_config(cursor)
result['changed'] = \
self.delete_user_config(cursor)
result['msg'] = "Deleted user from mysql_users"
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("User would have been deleted from" +
" mysql_users, however check_mode is" +
" enabled.")
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default='', type='path'),
username=dict(required=True, type='str'),
password=dict(no_log=True, type='str'),
active=dict(type='bool'),
use_ssl=dict(type='bool'),
default_hostgroup=dict(type='int'),
default_schema=dict(type='str'),
transaction_persistent=dict(type='bool'),
fast_forward=dict(type='bool'),
backend=dict(default=True, type='bool'),
frontend=dict(default=True, type='bool'),
max_connections=dict(type='int'),
state=dict(default='present', choices=['present',
'absent']),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class=MySQLdb.cursors.DictCursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
proxysql_user = ProxySQLUser(module)
result = {}
result['state'] = proxysql_user.state
if proxysql_user.username:
result['username'] = proxysql_user.username
if proxysql_user.state == "present":
try:
if not proxysql_user.check_user_privs(cursor):
if not proxysql_user.check_user_config_exists(cursor):
proxysql_user.create_user(module.check_mode,
result,
cursor)
else:
proxysql_user.update_user(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The user already exists in mysql_users" +
" and doesn't need to be updated.")
result['user'] = \
proxysql_user.get_user_config(cursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to modify user.. %s" % e
)
elif proxysql_user.state == "absent":
try:
if proxysql_user.check_user_config_exists(cursor):
proxysql_user.delete_user(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The user is already absent from the" +
" mysql_users memory configuration")
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to remove user.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,657 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_query_rules
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Modifies query rules using the proxysql admin interface.
description:
- The M(proxysql_query_rules) module modifies query rules using the
proxysql admin interface.
options:
rule_id:
description:
- The unique id of the rule. Rules are processed in rule_id order.
active:
description:
- A rule with I(active) set to C(False) will be tracked in the database,
but will be never loaded in the in-memory data structures.
username:
description:
- Filtering criteria matching username. If I(username) is non-NULL, a
query will match only if the connection is made with the correct
username.
schemaname:
description:
- Filtering criteria matching schemaname. If I(schemaname) is non-NULL, a
query will match only if the connection uses schemaname as its default
schema.
flagIN:
description:
- Used in combination with I(flagOUT) and I(apply) to create chains of
rules.
client_addr:
description:
- Match traffic from a specific source.
proxy_addr:
description:
- Match incoming traffic on a specific local IP.
proxy_port:
description:
- Match incoming traffic on a specific local port.
digest:
description:
- Match queries with a specific digest, as returned by
stats_mysql_query_digest.digest.
match_digest:
description:
- Regular expression that matches the query digest. The dialect of
regular expressions used is that of re2 - https://github.com/google/re2
match_pattern:
description:
- Regular expression that matches the query text. The dialect of regular
expressions used is that of re2 - https://github.com/google/re2
negate_match_pattern:
description:
- If I(negate_match_pattern) is set to C(True), only queries not matching
the query text will be considered as a match. This acts as a NOT
operator in front of the regular expression matching against
match_pattern.
flagOUT:
description:
- Used in combination with I(flagIN) and apply to create chains of rules.
When set, I(flagOUT) signifies the I(flagIN) to be used in the next
chain of rules.
replace_pattern:
description:
- This is the pattern with which to replace the matched pattern. Note
that this is optional, and when omitted, the query processor will only
cache, route, or set other parameters without rewriting.
destination_hostgroup:
description:
- Route matched queries to this hostgroup. This happens unless there is a
started transaction and the logged in user has
I(transaction_persistent) set to C(True) (see M(proxysql_mysql_users)).
cache_ttl:
description:
- The number of milliseconds for which to cache the result of the query.
Note in ProxySQL 1.1 I(cache_ttl) was in seconds.
timeout:
description:
- The maximum timeout in milliseconds with which the matched or rewritten
query should be executed. If a query run for longer than the specific
threshold, the query is automatically killed. If timeout is not
specified, the global variable mysql-default_query_timeout applies.
retries:
description:
- The maximum number of times a query needs to be re-executed in case of
detected failure during the execution of the query. If retries is not
specified, the global variable mysql-query_retries_on_failure applies.
delay:
description:
- Number of milliseconds to delay the execution of the query. This is
essentially a throttling mechanism and QoS, and allows a way to give
priority to queries over others. This value is added to the
mysql-default_query_delay global variable that applies to all queries.
mirror_flagOUT:
description:
- Enables query mirroring. If set I(mirror_flagOUT) can be used to
evaluates the mirrored query against the specified chain of rules.
mirror_hostgroup:
description:
- Enables query mirroring. If set I(mirror_hostgroup) can be used to
mirror queries to the same or different hostgroup.
error_msg:
description:
- Query will be blocked, and the specified error_msg will be returned to
the client.
log:
description:
- Query will be logged.
apply:
description:
- Used in combination with I(flagIN) and I(flagOUT) to create chains of
rules. Setting apply to True signifies the last rule to be applied.
comment:
description:
- Free form text field, usable for a descriptive comment of the query
rule.
state:
description:
- When C(present) - adds the rule, when C(absent) - removes the rule.
choices: [ "present", "absent" ]
default: present
force_delete:
description:
- By default we avoid deleting more than one schedule in a single batch,
however if you need this behaviour and you're not concerned about the
schedules deleted, you can set I(force_delete) to C(True).
default: False
save_to_disk:
description:
- Save mysql host config to sqlite db on disk to persist the
configuration.
default: True
load_to_runtime:
description:
- Dynamically load mysql host config to runtime memory.
default: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example adds a rule to redirect queries from a specific user to another
# hostgroup, it saves the mysql query rule config to disk, but avoids loading
# the mysql query config config to runtime (this might be because several
# rules are being added and the user wants to push the config to runtime in a
# single batch using the M(proxysql_manage_config) module). It uses supplied
# credentials to connect to the proxysql admin interface.
- proxysql_backend_servers:
login_user: admin
login_password: admin
username: 'guest_ro'
destination_hostgroup: 1
active: 1
retries: 3
state: present
load_to_runtime: False
# This example removes all rules that use the username 'guest_ro', saves the
# mysql query rule config to disk, and dynamically loads the mysql query rule
# config to runtime. It uses credentials in a supplied config file to connect
# to the proxysql admin interface.
- proxysql_backend_servers:
config_file: '~/proxysql.cnf'
username: 'guest_ro'
state: absent
force_delete: true
'''
RETURN = '''
stdout:
description: The mysql user modified or removed from proxysql
returned: On create/update will return the newly modified rule, in all
other cases will return a list of rules that match the supplied
criteria.
type: dict
"sample": {
"changed": true,
"msg": "Added rule to mysql_query_rules",
"rules": [
{
"active": "0",
"apply": "0",
"cache_ttl": null,
"client_addr": null,
"comment": null,
"delay": null,
"destination_hostgroup": 1,
"digest": null,
"error_msg": null,
"flagIN": "0",
"flagOUT": null,
"log": null,
"match_digest": null,
"match_pattern": null,
"mirror_flagOUT": null,
"mirror_hostgroup": null,
"negate_match_pattern": "0",
"proxy_addr": null,
"proxy_port": null,
"reconnect": null,
"replace_pattern": null,
"retries": null,
"rule_id": "1",
"schemaname": null,
"timeout": null,
"username": "guest_ro"
}
],
"state": "present"
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
from ansible.module_utils.six import iteritems
try:
import MySQLdb
import MySQLdb.cursors
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def save_config_to_disk(cursor):
cursor.execute("SAVE MYSQL QUERY RULES TO DISK")
return True
def load_config_to_runtime(cursor):
cursor.execute("LOAD MYSQL QUERY RULES TO RUNTIME")
return True
class ProxyQueryRule(object):
def __init__(self, module):
self.state = module.params["state"]
self.force_delete = module.params["force_delete"]
self.save_to_disk = module.params["save_to_disk"]
self.load_to_runtime = module.params["load_to_runtime"]
config_data_keys = ["rule_id",
"active",
"username",
"schemaname",
"flagIN",
"client_addr",
"proxy_addr",
"proxy_port",
"digest",
"match_digest",
"match_pattern",
"negate_match_pattern",
"flagOUT",
"replace_pattern",
"destination_hostgroup",
"cache_ttl",
"timeout",
"retries",
"delay",
"mirror_flagOUT",
"mirror_hostgroup",
"error_msg",
"log",
"apply",
"comment"]
self.config_data = dict((k, module.params[k])
for k in config_data_keys)
def check_rule_pk_exists(self, cursor):
query_string = \
"""SELECT count(*) AS `rule_count`
FROM mysql_query_rules
WHERE rule_id = %s"""
query_data = \
[self.config_data["rule_id"]]
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['rule_count']) > 0)
def check_rule_cfg_exists(self, cursor):
query_string = \
"""SELECT count(*) AS `rule_count`
FROM mysql_query_rules"""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\n WHERE " + col + " = %s"
else:
query_string += "\n AND " + col + " = %s"
if cols > 0:
cursor.execute(query_string, query_data)
else:
cursor.execute(query_string)
check_count = cursor.fetchone()
return int(check_count['rule_count'])
def get_rule_config(self, cursor, created_rule_id=None):
query_string = \
"""SELECT *
FROM mysql_query_rules"""
if created_rule_id:
query_data = [created_rule_id, ]
query_string += "\nWHERE rule_id = %s"
cursor.execute(query_string, query_data)
rule = cursor.fetchone()
else:
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\n WHERE " + col + " = %s"
else:
query_string += "\n AND " + col + " = %s"
if cols > 0:
cursor.execute(query_string, query_data)
else:
cursor.execute(query_string)
rule = cursor.fetchall()
return rule
def create_rule_config(self, cursor):
query_string = \
"""INSERT INTO mysql_query_rules ("""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
query_string += "\n" + col + ","
query_string = query_string[:-1]
query_string += \
(")\n" +
"VALUES (" +
"%s ," * cols)
query_string = query_string[:-2]
query_string += ")"
cursor.execute(query_string, query_data)
new_rule_id = cursor.lastrowid
return True, new_rule_id
def update_rule_config(self, cursor):
query_string = """UPDATE mysql_query_rules"""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None and col != "rule_id":
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\nSET " + col + "= %s,"
else:
query_string += "\n " + col + " = %s,"
query_string = query_string[:-1]
query_string += "\nWHERE rule_id = %s"
query_data.append(self.config_data["rule_id"])
cursor.execute(query_string, query_data)
return True
def delete_rule_config(self, cursor):
query_string = \
"""DELETE FROM mysql_query_rules"""
cols = 0
query_data = []
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
if cols == 1:
query_string += "\n WHERE " + col + " = %s"
else:
query_string += "\n AND " + col + " = %s"
if cols > 0:
cursor.execute(query_string, query_data)
else:
cursor.execute(query_string)
check_count = cursor.rowcount
return True, int(check_count)
def manage_config(self, cursor, state):
if state:
if self.save_to_disk:
save_config_to_disk(cursor)
if self.load_to_runtime:
load_config_to_runtime(cursor)
def create_rule(self, check_mode, result, cursor):
if not check_mode:
result['changed'], new_rule_id = \
self.create_rule_config(cursor)
result['msg'] = "Added rule to mysql_query_rules"
self.manage_config(cursor,
result['changed'])
result['rules'] = \
self.get_rule_config(cursor, new_rule_id)
else:
result['changed'] = True
result['msg'] = ("Rule would have been added to" +
" mysql_query_rules, however" +
" check_mode is enabled.")
def update_rule(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.update_rule_config(cursor)
result['msg'] = "Updated rule in mysql_query_rules"
self.manage_config(cursor,
result['changed'])
result['rules'] = \
self.get_rule_config(cursor)
else:
result['changed'] = True
result['msg'] = ("Rule would have been updated in" +
" mysql_query_rules, however" +
" check_mode is enabled.")
def delete_rule(self, check_mode, result, cursor):
if not check_mode:
result['rules'] = \
self.get_rule_config(cursor)
result['changed'], result['rows_affected'] = \
self.delete_rule_config(cursor)
result['msg'] = "Deleted rule from mysql_query_rules"
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Rule would have been deleted from" +
" mysql_query_rules, however" +
" check_mode is enabled.")
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default="", type='path'),
rule_id=dict(type='int'),
active=dict(type='bool'),
username=dict(type='str'),
schemaname=dict(type='str'),
flagIN=dict(type='int'),
client_addr=dict(type='str'),
proxy_addr=dict(type='str'),
proxy_port=dict(type='int'),
digest=dict(type='str'),
match_digest=dict(type='str'),
match_pattern=dict(type='str'),
negate_match_pattern=dict(type='bool'),
flagOUT=dict(type='int'),
replace_pattern=dict(type='str'),
destination_hostgroup=dict(type='int'),
cache_ttl=dict(type='int'),
timeout=dict(type='int'),
retries=dict(type='int'),
delay=dict(type='int'),
mirror_flagOUT=dict(type='int'),
mirror_hostgroup=dict(type='int'),
error_msg=dict(type='str'),
log=dict(type='bool'),
apply=dict(type='bool'),
comment=dict(type='str'),
state=dict(default='present', choices=['present',
'absent']),
force_delete=dict(default=False, type='bool'),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class=MySQLdb.cursors.DictCursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
proxysql_query_rule = ProxyQueryRule(module)
result = {}
result['state'] = proxysql_query_rule.state
if proxysql_query_rule.state == "present":
try:
if not proxysql_query_rule.check_rule_cfg_exists(cursor):
if proxysql_query_rule.config_data["rule_id"] and \
proxysql_query_rule.check_rule_pk_exists(cursor):
proxysql_query_rule.update_rule(module.check_mode,
result,
cursor)
else:
proxysql_query_rule.create_rule(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The rule already exists in" +
" mysql_query_rules and doesn't need to be" +
" updated.")
result['rules'] = \
proxysql_query_rule.get_rule_config(cursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to modify rule.. %s" % e
)
elif proxysql_query_rule.state == "absent":
try:
existing_rules = proxysql_query_rule.check_rule_cfg_exists(cursor)
if existing_rules > 0:
if existing_rules == 1 or \
proxysql_query_rule.force_delete:
proxysql_query_rule.delete_rule(module.check_mode,
result,
cursor)
else:
module.fail_json(
msg=("Operation would delete multiple rules" +
" use force_delete to override this")
)
else:
result['changed'] = False
result['msg'] = ("The rule is already absent from the" +
" mysql_query_rules memory configuration")
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to remove rule.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,430 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_replication_hostgroups
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Manages replication hostgroups using the proxysql admin
interface.
description:
- Each row in mysql_replication_hostgroups represent a pair of
writer_hostgroup and reader_hostgroup. ProxySQL will monitor the value of
read_only for all the servers in specified hostgroups, and based on the
value of read_only will assign the server to the writer or reader
hostgroups.
options:
writer_hostgroup:
description:
- Id of the writer hostgroup.
required: True
reader_hostgroup:
description:
- Id of the reader hostgroup.
required: True
comment:
description:
- Text field that can be used for any purposed defined by the user.
state:
description:
- When C(present) - adds the replication hostgroup, when C(absent) -
removes the replication hostgroup.
choices: [ "present", "absent" ]
default: present
save_to_disk:
description:
- Save mysql host config to sqlite db on disk to persist the
configuration.
default: True
load_to_runtime:
description:
- Dynamically load mysql host config to runtime memory.
default: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example adds a replication hostgroup, it saves the mysql server config
# to disk, but avoids loading the mysql server config to runtime (this might be
# because several replication hostgroup are being added and the user wants to
# push the config to runtime in a single batch using the
# M(proxysql_manage_config) module). It uses supplied credentials to connect
# to the proxysql admin interface.
- proxysql_replication_hostgroups:
login_user: 'admin'
login_password: 'admin'
writer_hostgroup: 1
reader_hostgroup: 2
state: present
load_to_runtime: False
# This example removes a replication hostgroup, saves the mysql server config
# to disk, and dynamically loads the mysql server config to runtime. It uses
# credentials in a supplied config file to connect to the proxysql admin
# interface.
- proxysql_replication_hostgroups:
config_file: '~/proxysql.cnf'
writer_hostgroup: 3
reader_hostgroup: 4
state: absent
'''
RETURN = '''
stdout:
description: The replication hostgroup modified or removed from proxysql
returned: On create/update will return the newly modified group, on delete
it will return the deleted record.
type: dict
"sample": {
"changed": true,
"msg": "Added server to mysql_hosts",
"repl_group": {
"comment": "",
"reader_hostgroup": "1",
"writer_hostgroup": "2"
},
"state": "present"
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
try:
import MySQLdb
import MySQLdb.cursors
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if not module.params["writer_hostgroup"] >= 0:
module.fail_json(
msg="writer_hostgroup must be a integer greater than or equal to 0"
)
if not module.params["reader_hostgroup"] == \
module.params["writer_hostgroup"]:
if not module.params["reader_hostgroup"] > 0:
module.fail_json(
msg=("writer_hostgroup must be a integer greater than" +
" or equal to 0")
)
else:
module.fail_json(
msg="reader_hostgroup cannot equal writer_hostgroup"
)
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def save_config_to_disk(cursor):
cursor.execute("SAVE MYSQL SERVERS TO DISK")
return True
def load_config_to_runtime(cursor):
cursor.execute("LOAD MYSQL SERVERS TO RUNTIME")
return True
class ProxySQLReplicationHostgroup(object):
def __init__(self, module):
self.state = module.params["state"]
self.save_to_disk = module.params["save_to_disk"]
self.load_to_runtime = module.params["load_to_runtime"]
self.writer_hostgroup = module.params["writer_hostgroup"]
self.reader_hostgroup = module.params["reader_hostgroup"]
self.comment = module.params["comment"]
def check_repl_group_config(self, cursor, keys):
query_string = \
"""SELECT count(*) AS `repl_groups`
FROM mysql_replication_hostgroups
WHERE writer_hostgroup = %s
AND reader_hostgroup = %s"""
query_data = \
[self.writer_hostgroup,
self.reader_hostgroup]
if self.comment and not keys:
query_string += "\n AND comment = %s"
query_data.append(self.comment)
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return (int(check_count['repl_groups']) > 0)
def get_repl_group_config(self, cursor):
query_string = \
"""SELECT *
FROM mysql_replication_hostgroups
WHERE writer_hostgroup = %s
AND reader_hostgroup = %s"""
query_data = \
[self.writer_hostgroup,
self.reader_hostgroup]
cursor.execute(query_string, query_data)
repl_group = cursor.fetchone()
return repl_group
def create_repl_group_config(self, cursor):
query_string = \
"""INSERT INTO mysql_replication_hostgroups (
writer_hostgroup,
reader_hostgroup,
comment)
VALUES (%s, %s, %s)"""
query_data = \
[self.writer_hostgroup,
self.reader_hostgroup,
self.comment or '']
cursor.execute(query_string, query_data)
return True
def update_repl_group_config(self, cursor):
query_string = \
"""UPDATE mysql_replication_hostgroups
SET comment = %s
WHERE writer_hostgroup = %s
AND reader_hostgroup = %s"""
query_data = \
[self.comment,
self.writer_hostgroup,
self.reader_hostgroup]
cursor.execute(query_string, query_data)
return True
def delete_repl_group_config(self, cursor):
query_string = \
"""DELETE FROM mysql_replication_hostgroups
WHERE writer_hostgroup = %s
AND reader_hostgroup = %s"""
query_data = \
[self.writer_hostgroup,
self.reader_hostgroup]
cursor.execute(query_string, query_data)
return True
def manage_config(self, cursor, state):
if state:
if self.save_to_disk:
save_config_to_disk(cursor)
if self.load_to_runtime:
load_config_to_runtime(cursor)
def create_repl_group(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.create_repl_group_config(cursor)
result['msg'] = "Added server to mysql_hosts"
result['repl_group'] = \
self.get_repl_group_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Repl group would have been added to" +
" mysql_replication_hostgroups, however" +
" check_mode is enabled.")
def update_repl_group(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.update_repl_group_config(cursor)
result['msg'] = "Updated server in mysql_hosts"
result['repl_group'] = \
self.get_repl_group_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Repl group would have been updated in" +
" mysql_replication_hostgroups, however" +
" check_mode is enabled.")
def delete_repl_group(self, check_mode, result, cursor):
if not check_mode:
result['repl_group'] = \
self.get_repl_group_config(cursor)
result['changed'] = \
self.delete_repl_group_config(cursor)
result['msg'] = "Deleted server from mysql_hosts"
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Repl group would have been deleted from" +
" mysql_replication_hostgroups, however" +
" check_mode is enabled.")
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default="", type='path'),
writer_hostgroup=dict(required=True, type='int'),
reader_hostgroup=dict(required=True, type='int'),
comment=dict(type='str'),
state=dict(default='present', choices=['present',
'absent']),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class=MySQLdb.cursors.DictCursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
proxysql_repl_group = ProxySQLReplicationHostgroup(module)
result = {}
result['state'] = proxysql_repl_group.state
if proxysql_repl_group.state == "present":
try:
if not proxysql_repl_group.check_repl_group_config(cursor,
keys=True):
proxysql_repl_group.create_repl_group(module.check_mode,
result,
cursor)
else:
if not proxysql_repl_group.check_repl_group_config(cursor,
keys=False):
proxysql_repl_group.update_repl_group(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The repl group already exists in" +
" mysql_replication_hostgroups and" +
" doesn't need to be updated.")
result['repl_group'] = \
proxysql_repl_group.get_repl_group_config(cursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to modify replication hostgroup.. %s" % e
)
elif proxysql_repl_group.state == "absent":
try:
if proxysql_repl_group.check_repl_group_config(cursor,
keys=True):
proxysql_repl_group.delete_repl_group(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The repl group is already absent from the" +
" mysql_replication_hostgroups memory" +
" configuration")
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to delete replication hostgroup.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,465 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: proxysql_scheduler
version_added: "2.3"
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes schedules from proxysql admin interface.
description:
- The M(proxysql_scheduler) module adds or removes schedules using the
proxysql admin interface.
options:
active:
description:
- A schedule with I(active) set to C(False) will be tracked in the
database, but will be never loaded in the in-memory data structures.
default: True
interval_ms:
description:
- How often (in millisecond) the job will be started. The minimum value
for I(interval_ms) is 100 milliseconds.
default: 10000
filename:
description:
- Full path of the executable to be executed.
required: True
arg1:
description:
- Argument that can be passed to the job.
arg2:
description:
- Argument that can be passed to the job.
arg3:
description:
- Argument that can be passed to the job.
arg4:
description:
- Argument that can be passed to the job.
arg5:
description:
- Argument that can be passed to the job.
comment:
description:
- Text field that can be used for any purposed defined by the user.
state:
description:
- When C(present) - adds the schedule, when C(absent) - removes the
schedule.
choices: [ "present", "absent" ]
default: present
force_delete:
description:
- By default we avoid deleting more than one schedule in a single batch,
however if you need this behaviour and you're not concerned about the
schedules deleted, you can set I(force_delete) to C(True).
default: False
save_to_disk:
description:
- Save mysql host config to sqlite db on disk to persist the
configuration.
default: True
load_to_runtime:
description:
- Dynamically load mysql host config to runtime memory.
default: True
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
default: None
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
default: None
login_host:
description:
- The host used to connect to ProxySQL admin interface.
default: '127.0.0.1'
login_port:
description:
- The port used to connect to ProxySQL admin interface.
default: 6032
config_file:
description:
- Specify a config file from which login_user and login_password are to
be read.
default: ''
'''
EXAMPLES = '''
---
# This example adds a schedule, it saves the scheduler config to disk, but
# avoids loading the scheduler config to runtime (this might be because
# several servers are being added and the user wants to push the config to
# runtime in a single batch using the M(proxysql_manage_config) module). It
# uses supplied credentials to connect to the proxysql admin interface.
- proxysql_scheduler:
login_user: 'admin'
login_password: 'admin'
interval_ms: 1000
filename: "/opt/maintenance.py"
state: present
load_to_runtime: False
# This example removes a schedule, saves the scheduler config to disk, and
# dynamically loads the scheduler config to runtime. It uses credentials
# in a supplied config file to connect to the proxysql admin interface.
- proxysql_scheduler:
config_file: '~/proxysql.cnf'
filename: "/opt/old_script.py"
state: absent
'''
RETURN = '''
stdout:
description: The schedule modified or removed from proxysql
returned: On create/update will return the newly modified schedule, on
delete it will return the deleted record.
type: dict
"sample": {
"changed": true,
"filename": "/opt/test.py",
"msg": "Added schedule to scheduler",
"schedules": [
{
"active": "1",
"arg1": null,
"arg2": null,
"arg3": null,
"arg4": null,
"arg5": null,
"comment": "",
"filename": "/opt/test.py",
"id": "1",
"interval_ms": "10000"
}
],
"state": "present"
}
'''
ANSIBLE_METADATA = {
'version': '1.0',
'supported_by': 'community',
'status': ['stableinterface']
}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.mysql import mysql_connect
from ansible.module_utils.pycompat24 import get_exception
from ansible.module_utils.six import iteritems
try:
import MySQLdb
import MySQLdb.cursors
except ImportError:
MYSQLDB_FOUND = False
else:
MYSQLDB_FOUND = True
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["login_port"] < 0 \
or module.params["login_port"] > 65535:
module.fail_json(
msg="login_port must be a valid unix port number (0-65535)"
)
if module.params["interval_ms"] < 100 \
or module.params["interval_ms"] > 100000000:
module.fail_json(
msg="interval_ms must between 100ms & 100000000ms"
)
if not MYSQLDB_FOUND:
module.fail_json(
msg="the python mysqldb module is required"
)
def save_config_to_disk(cursor):
cursor.execute("SAVE SCHEDULER TO DISK")
return True
def load_config_to_runtime(cursor):
cursor.execute("LOAD SCHEDULER TO RUNTIME")
return True
class ProxySQLSchedule(object):
def __init__(self, module):
self.state = module.params["state"]
self.force_delete = module.params["force_delete"]
self.save_to_disk = module.params["save_to_disk"]
self.load_to_runtime = module.params["load_to_runtime"]
self.active = module.params["active"]
self.interval_ms = module.params["interval_ms"]
self.filename = module.params["filename"]
config_data_keys = ["arg1",
"arg2",
"arg3",
"arg4",
"arg5",
"comment"]
self.config_data = dict((k, module.params[k])
for k in config_data_keys)
def check_schedule_config(self, cursor):
query_string = \
"""SELECT count(*) AS `schedule_count`
FROM scheduler
WHERE active = %s
AND interval_ms = %s
AND filename = %s"""
query_data = \
[self.active,
self.interval_ms,
self.filename]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += "\n AND " + col + " = %s"
cursor.execute(query_string, query_data)
check_count = cursor.fetchone()
return int(check_count['schedule_count'])
def get_schedule_config(self, cursor):
query_string = \
"""SELECT *
FROM scheduler
WHERE active = %s
AND interval_ms = %s
AND filename = %s"""
query_data = \
[self.active,
self.interval_ms,
self.filename]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += "\n AND " + col + " = %s"
cursor.execute(query_string, query_data)
schedule = cursor.fetchall()
return schedule
def create_schedule_config(self, cursor):
query_string = \
"""INSERT INTO scheduler (
active,
interval_ms,
filename"""
cols = 0
query_data = \
[self.active,
self.interval_ms,
self.filename]
for col, val in iteritems(self.config_data):
if val is not None:
cols += 1
query_data.append(val)
query_string += ",\n" + col
query_string += \
(")\n" +
"VALUES (%s, %s, %s" +
", %s" * cols +
")")
cursor.execute(query_string, query_data)
return True
def delete_schedule_config(self, cursor):
query_string = \
"""DELETE FROM scheduler
WHERE active = %s
AND interval_ms = %s
AND filename = %s"""
query_data = \
[self.active,
self.interval_ms,
self.filename]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += "\n AND " + col + " = %s"
cursor.execute(query_string, query_data)
check_count = cursor.rowcount
return True, int(check_count)
def manage_config(self, cursor, state):
if state:
if self.save_to_disk:
save_config_to_disk(cursor)
if self.load_to_runtime:
load_config_to_runtime(cursor)
def create_schedule(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = \
self.create_schedule_config(cursor)
result['msg'] = "Added schedule to scheduler"
result['schedules'] = \
self.get_schedule_config(cursor)
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Schedule would have been added to" +
" scheduler, however check_mode" +
" is enabled.")
def delete_schedule(self, check_mode, result, cursor):
if not check_mode:
result['schedules'] = \
self.get_schedule_config(cursor)
result['changed'] = \
self.delete_schedule_config(cursor)
result['msg'] = "Deleted schedule from scheduler"
self.manage_config(cursor,
result['changed'])
else:
result['changed'] = True
result['msg'] = ("Schedule would have been deleted from" +
" scheduler, however check_mode is" +
" enabled.")
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default=None, type='str'),
login_password=dict(default=None, no_log=True, type='str'),
login_host=dict(default="127.0.0.1"),
login_unix_socket=dict(default=None),
login_port=dict(default=6032, type='int'),
config_file=dict(default="", type='path'),
active=dict(default=True, type='bool'),
interval_ms=dict(default=10000, type='int'),
filename=dict(required=True, type='str'),
arg1=dict(type='str'),
arg2=dict(type='str'),
arg3=dict(type='str'),
arg4=dict(type='str'),
arg5=dict(type='str'),
comment=dict(type='str'),
state=dict(default='present', choices=['present',
'absent']),
force_delete=dict(default=False, type='bool'),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
),
supports_check_mode=True
)
perform_checks(module)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class=MySQLdb.cursors.DictCursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % e
)
proxysql_schedule = ProxySQLSchedule(module)
result = {}
result['state'] = proxysql_schedule.state
result['filename'] = proxysql_schedule.filename
if proxysql_schedule.state == "present":
try:
if not proxysql_schedule.check_schedule_config(cursor) > 0:
proxysql_schedule.create_schedule(module.check_mode,
result,
cursor)
else:
result['changed'] = False
result['msg'] = ("The schedule already exists and doesn't" +
" need to be updated.")
result['schedules'] = \
proxysql_schedule.get_schedule_config(cursor)
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to modify schedule.. %s" % e
)
elif proxysql_schedule.state == "absent":
try:
existing_schedules = \
proxysql_schedule.check_schedule_config(cursor)
if existing_schedules > 0:
if existing_schedules == 1 or proxysql_schedule.force_delete:
proxysql_schedule.delete_schedule(module.check_mode,
result,
cursor)
else:
module.fail_json(
msg=("Operation would delete multiple records" +
" use force_delete to override this")
)
else:
result['changed'] = False
result['msg'] = ("The schedule is already absent from the" +
" memory configuration")
except MySQLdb.Error:
e = get_exception()
module.fail_json(
msg="unable to remove schedule.. %s" % e
)
module.exit_json(**result)
if __name__ == '__main__':
main()