Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
# Collections Plugins Directory
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
would contain module utils and modules respectively.
Here is an example directory of the majority of plugins currently supported by Ansible:
```
└── plugins
├── action
├── become
├── cache
├── callback
├── cliconf
├── connection
├── filter
├── httpapi
├── inventory
├── lookup
├── module_utils
├── modules
├── netconf
├── shell
├── strategy
├── terminal
├── test
└── vars
```
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html).

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
# Documentation fragment for ProxySQL connectivity
CONNECTIVITY = r'''
options:
login_user:
description:
- The username used to authenticate to ProxySQL admin interface.
type: str
login_password:
description:
- The password used to authenticate to ProxySQL admin interface.
type: str
login_host:
description:
- The host used to connect to ProxySQL admin interface.
type: str
default: '127.0.0.1'
login_unix_socket:
description:
- The socket used to connect to ProxySQL admin interface.
type: str
login_port:
description:
- The port used to connect to ProxySQL admin interface.
type: int
default: 6032
config_file:
description:
- Specify a config file from which I(login_user) and I(login_password)
are to be read.
type: path
default: ''
requirements:
- PyMySQL
- mysqlclient
'''
# Documentation fragment for managing ProxySQL configuration
MANAGING_CONFIG = r'''
options:
save_to_disk:
description:
- Save config to sqlite db on disk to persist the configuration.
type: bool
default: true
load_to_runtime:
description:
- Dynamically load config to runtime memory.
type: bool
default: true
'''

View File

@@ -0,0 +1,170 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c), Jonathan Mainguy <jon@soh.re>, 2015
# Most of this was originally added by Sven Schliesing @muffl0n in the mysql_user.py module
#
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
from ansible.module_utils.six.moves import configparser
from ansible.module_utils.basic import missing_required_lib
MYSQL_IMP_ERR = None
try:
import pymysql as mysql_driver
_mysql_cursor_param = 'cursor'
HAS_MYSQL_PACKAGE = True
except ImportError:
try:
import MySQLdb as mysql_driver
import MySQLdb.cursors
_mysql_cursor_param = 'cursorclass'
HAS_MYSQL_PACKAGE = True
except ImportError:
MYSQL_IMP_ERR = 'Cannot find PyMySQL or mysqlclient library.'
HAS_MYSQL_PACKAGE = False
mysql_driver = None
def parse_from_mysql_config_file(cnf):
cp = configparser.ConfigParser()
cp.read(cnf)
return cp
def _version(cursor):
cursor.execute("select version();")
res = cursor.fetchone()
# 2.2.0-72-ge14accd
# 2.3.2-percona-1.1
raw_version = res.get('version()').split('-', 1)
_version = raw_version[0].split('.')
version = dict()
version['full'] = res.get('version()')
version['major'] = int(_version[0])
version['minor'] = int(_version[1])
version['release'] = int(_version[2])
version['suffix'] = raw_version[1]
return version
def mysql_connect(module, login_user=None, login_password=None, config_file='', ssl_cert=None,
ssl_key=None, ssl_ca=None, db=None, cursor_class=None,
connect_timeout=30, autocommit=False, config_overrides_defaults=False):
config = {}
if not HAS_MYSQL_PACKAGE:
module.fail_json(msg=missing_required_lib("pymysql or MySQLdb"), exception=MYSQL_IMP_ERR)
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 config_file and os.path.exists(config_file):
config['read_default_file'] = config_file
cp = parse_from_mysql_config_file(config_file)
# Override some commond defaults with values from config file if needed
if cp and cp.has_section('client') and config_overrides_defaults:
try:
module.params['login_host'] = cp.get('client', 'host', fallback=module.params['login_host'])
module.params['login_port'] = cp.getint('client', 'port', fallback=module.params['login_port'])
except Exception as e:
if "got an unexpected keyword argument 'fallback'" in e.message:
module.fail_json('To use config_overrides_defaults, '
'it needs Python 3.5+ as the default interpreter on a target host')
if ssl_ca is not None or ssl_key is not None or ssl_cert is not None:
config['ssl'] = {}
if module.params['login_unix_socket']:
config['unix_socket'] = module.params['login_unix_socket']
else:
config['host'] = module.params['login_host']
config['port'] = module.params['login_port']
# If login_user or login_password are given, they should override the
# config file
if login_user is not None:
config['user'] = login_user
if login_password is not None:
config['passwd'] = login_password
if ssl_cert is not None:
config['ssl']['cert'] = ssl_cert
if ssl_key is not None:
config['ssl']['key'] = ssl_key
if ssl_ca is not None:
config['ssl']['ca'] = ssl_ca
if db is not None:
config['db'] = db
if connect_timeout is not None:
config['connect_timeout'] = connect_timeout
if _mysql_cursor_param == 'cursor':
# In case of PyMySQL driver:
db_connection = mysql_driver.connect(autocommit=autocommit, **config)
else:
# In case of MySQLdb driver
db_connection = mysql_driver.connect(**config)
if autocommit:
db_connection.autocommit(True)
version = _version(db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}))
if cursor_class == 'DictCursor':
return (db_connection.cursor(**{_mysql_cursor_param: mysql_driver.cursors.DictCursor}),
db_connection,
version)
else:
return (db_connection.cursor(),
db_connection,
version)
def proxysql_common_argument_spec():
return dict(
login_user=dict(type='str', default=None),
login_password=dict(type='str', no_log=True),
login_host=dict(type='str', default='127.0.0.1'),
login_port=dict(type='int', default=6032),
login_unix_socket=dict(type='str'),
config_file=dict(type='path', default=''),
)
def save_config_to_disk(cursor, save_what, variable=None):
if variable and variable.startswith("admin"):
config_type = "ADMIN"
elif save_what == "SCHEDULER":
config_type = ""
else:
config_type = "MYSQL"
cursor.execute("SAVE {0} {1} TO DISK".format(config_type, save_what))
return True
def load_config_to_runtime(cursor, save_what, variable=None):
if variable and variable.startswith("admin"):
config_type = "ADMIN"
elif save_what == "SCHEDULER":
config_type = ""
else:
config_type = "MYSQL"
cursor.execute("LOAD {0} {1} TO RUNTIME".format(config_type, save_what))
return True

View File

@@ -0,0 +1,503 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_backend_servers
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes mysql hosts from proxysql admin interface
description:
- The M(community.proxysql.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.
type: int
default: 0
hostname:
description:
- The ip address at which the mysqld instance can be contacted.
type: str
required: true
port:
description:
- The port at which the mysqld instance can be contacted.
type: int
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).
type: str
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.
type: int
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.
type: int
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.
type: int
max_replication_lag:
description:
- If greater than 0, ProxySQL will regularly 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.
type: int
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).
type: bool
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.
type: int
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.
type: str
default: ''
state:
description:
- When C(present) - adds the host, when C(absent) - removes the host.
type: str
choices: [ "present", "absent" ]
default: present
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
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 community.general.proxysql_manage_config
# module). It uses supplied credentials to connect to the proxysql admin
# interface.
- name: Add a server
community.proxysql.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.
- name: Remove a server
community.proxysql.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"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
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"
)
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()
if isinstance(check_count, tuple):
return int(check_count[0]) > 0
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, "SERVERS")
if self.load_to_runtime:
load_config_to_runtime(cursor, "SERVERS")
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():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
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')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
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, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to modify server.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to remove server.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,244 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_global_variables
author: "Ben Mildren (@bmildren)"
short_description: Gets or sets the proxysql global variables
description:
- The M(community.proxysql.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.
type: str
required: true
value:
description:
- Defines a value the variable specified using I(variable) should be set
to.
type: str
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
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.
- name: Set the value of a variable
community.proxysql.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.
- name: Get the value of a variable
community.proxysql.proxysql_global_variables:
config_file: '~/proxysql.cnf'
variable: 'mysql-default_query_delay'
'''
RETURN = '''
stdout:
description: Returns the mysql variable supplied with it's associated 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"
}
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
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()
if isinstance(check_count, tuple):
return int(check_count[0]) > 0
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(cursor, "VARIABLES", variable)
if load_to_runtime:
load_config_to_runtime(cursor, "VARIABLES", variable)
# ===========================================
# Module execution.
#
def main():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
variable=dict(required=True, type='str'),
value=dict(),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
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, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to get config.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to set config.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,159 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_info
author: "Markus Bergholz (@markuman)"
short_description: Gathers information about proxysql server
description:
- Gathers information about proxysql server.
- Caution. The number of tables that returns, depends on the underlying proyxsql server version.
version_added: '1.2.0'
extends_documentation_fragment:
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
EXAMPLES = '''
- name: Receive information about proxysql setup
community.proxysql.proxysql_info:
login_user: admin
login_password: admin
'''
RETURN = '''
stdout:
description: The number of tables that returns, depends on the underlying proyxsql server version.
returned: Always
type: dict
sample:
changed: false
failed: false
version:
description: Version of proxysql.
sample:
full: 2.1.1-40-g1c2b7e4
major: 2
minor: 1
release: 1
suffix: 40
type: dict
returned: Always
tables:
description: List of tables that exist in the requested proxysql version.
sample:
- global_variables
- mysql_aws_aurora_hostgroups
- mysql_collations
- mysql_firewall_whitelist_rules
- mysql_firewall_whitelist_sqli_fingerprints
- mysql_firewall_whitelist_users
- mysql_galera_hostgroups
- mysql_group_replication_hostgroups
- mysql_query_rules
- mysql_query_rules_fast_routing
- mysql_replication_hostgroups
- mysql_servers
- mysql_users
- proxysql_servers
- restapi_routes
- runtime_checksums_values
- runtime_global_variables
- runtime_mysql_aws_aurora_hostgroups
- runtime_mysql_firewall_whitelist_rules
- runtime_mysql_firewall_whitelist_sqli_fingerprints
- runtime_mysql_firewall_whitelist_users
- runtime_mysql_galera_hostgroups
- runtime_mysql_group_replication_hostgroups
- runtime_mysql_query_rules
- runtime_mysql_query_rules_fast_routing
- runtime_mysql_replication_hostgroups
- runtime_mysql_servers
- runtime_mysql_users
- runtime_proxysql_servers
- runtime_restapi_routes
- runtime_scheduler
- scheduler
type: list
returned: Always
global_variables:
description: Global variables of requested proxysql.
type: dict
returned: Always
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
)
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
def get_tables(cursor):
result = dict()
tables = list()
cursor.execute("show tables")
for table in cursor.fetchall():
tables.append(table.get('tables'))
result['tables'] = tables
for table in result.get('tables'):
cursor.execute("select * from {table}".format(table=table))
if 'global_variables' in table:
result[table] = dict()
for item in cursor.fetchall():
result[table][item.get('variable_name')] = item.get('variable_value')
else:
result[table] = cursor.fetchall()
return result
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=proxysql_common_argument_spec()
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor, db_conn, version = mysql_connect(
module,
login_user,
login_password,
config_file,
cursor_class='DictCursor'
)
except mysql_driver.Error as e:
module.fail_json(msg="unable to connect to ProxySQL Admin Module: %s" % to_native(e))
result = get_tables(cursor)
result['version'] = version
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,212 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_manage_config
author: "Ben Mildren (@bmildren)"
short_description: Writes the proxysql configuration settings between layers
description:
- The M(community.proxysql.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).
type: str
choices: [ "LOAD", "SAVE" ]
required: true
config_settings:
description:
- The I(config_settings) specifies which configuration we're writing.
type: str
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)."
type: str
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.
type: str
choices: [ "MEMORY", "DISK", "RUNTIME", "CONFIG" ]
required: true
extends_documentation_fragment:
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
EXAMPLES = '''
---
# This example saves the mysql users config from memory to disk. It uses
# supplied credentials to connect to the proxysql admin interface.
- name: Save the mysql users config from memory to disk
community.proxysql.proxysql_manage_config:
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 runtime. It
# uses supplied credentials to connect to the proxysql admin interface.
- name: Load the mysql query rules config from memory to runtime
community.proxysql.proxysql_manage_config:
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
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec
)
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
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"])
def manage_config(manage_config_settings, cursor, check_mode):
if not check_mode:
query_string = "%s" % ' '.join(manage_config_settings)
cursor.execute(query_string)
return True
# ===========================================
# Module execution.
#
def main():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
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'])
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
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, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file)
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
)
result = {}
manage_config_settings = \
[action, config_settings, direction, config_layer]
try:
result['changed'] = manage_config(manage_config_settings,
cursor, module.check_mode)
except mysql_driver.Error as e:
module.fail_json(
msg="unable to manage config.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,497 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_mysql_users
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes mysql users from proxysql admin interface
description:
- The M(community.proxysql.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.
type: str
required: true
password:
description:
- Password of the user connecting to the mysqld or ProxySQL instance.
type: str
encrypt_password:
description:
- Encrypt a cleartext password passed in the I(password) option, using
the method defined in I(encryption_method).
default: false
type: bool
encryption_method:
description:
- Encryption method used when I(encrypt_password) is set to C(True).
type: str
choices: [ "mysql_native_password" ]
default: mysql_native_password
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).
type: bool
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).
type: bool
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.
type: int
default_schema:
description:
- The schema to which the connection should change to by default.
type: str
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).
type: bool
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).
type: bool
backend:
description:
- If I(backend) is set to C(True), this (username, password) pair is
used for authenticating to the ProxySQL instance.
default: true
type: bool
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
type: bool
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.
type: int
state:
description:
- When C(present) - adds the user, when C(absent) - removes the user.
type: str
choices: [ "present", "absent" ]
default: present
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
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 community.general.proxysql_manage_config
# module). It uses supplied credentials to connect to the proxysql admin
# interface.
- name: Add a user
community.proxysql.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.
- name: Remove a user
community.proxysql.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
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native, to_bytes
from hashlib import sha1
# ===========================================
# proxysql module specific support methods.
#
def _mysql_native_password(cleartext_password):
mysql_native_encrypted_password = "*" + sha1(sha1(to_bytes(cleartext_password)).digest()).hexdigest().upper()
return mysql_native_encrypted_password
def encrypt_cleartext_password(password_to_encrypt, encryption_method):
encrypted_password = encryption_method(password_to_encrypt)
return encrypted_password
encryption_method_map = {
'mysql_native_password': _mysql_native_password
}
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)
if module.params["password"] is not None and module.params["encrypt_password"]:
encryption_method = encryption_method_map[module.params["encryption_method"]]
encrypted_password = encrypt_cleartext_password(module.params["password"], encryption_method)
self.config_data["password"] = encrypted_password
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, "USERS")
if self.load_to_runtime:
load_config_to_runtime(cursor, "USERS")
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():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
username=dict(required=True, type='str'),
password=dict(no_log=True, type='str'),
encrypt_password=dict(default=False, type='bool', no_log=False),
encryption_method=dict(default='mysql_native_password', choices=list(encryption_method_map.keys())),
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')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to modify user.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to remove user.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,735 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_query_rules
author: "Ben Mildren (@bmildren)"
short_description: Modifies query rules using the proxysql admin interface
description:
- The M(community.proxysql.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.
type: int
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.
type: bool
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.
type: str
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.
type: str
flagIN:
description:
- Used in combination with I(flagOUT) and I(apply) to create chains of
rules.
type: int
client_addr:
description:
- Match traffic from a specific source.
type: str
proxy_addr:
description:
- Match incoming traffic on a specific local IP.
type: str
proxy_port:
description:
- Match incoming traffic on a specific local port.
type: int
digest:
description:
- Match queries with a specific digest, as returned by
stats_mysql_query_digest.digest.
type: str
match_digest:
description:
- Regular expression that matches the query digest. The dialect of
regular expressions used is that of re2 - U(https://github.com/google/re2).
type: str
match_pattern:
description:
- Regular expression that matches the query text. The dialect of regular
expressions used is that of re2 - U(https://github.com/google/re2).
type: str
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.
type: bool
re_modifiers:
description:
- Comma separated list of options to modify the behavior of the RE engine.
With C(CASELESS) the match is case insensitive. With C(GLOBAL) the replace
is global (replaces all matches and not just the first).
For backward compatibility, only C(CASELESS) is the enabled by default.
type: str
version_added: "1.3.0"
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.
type: int
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.
type: str
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(community.proxysql.proxysql_mysql_users)).
type: int
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.
type: int
cache_empty_result:
description:
- Controls if resultset without rows will be cached or not.
type: bool
version_added: '1.1.0'
multiplex:
description:
- If C(0), multiplex will be disabled.
- If C(1), try to enable multiplex. There can be other conditions preventing this (for example, user variables or transactions).
- If C(2), multiplexing is not disabled for just the current query.
- By default, does not change multiplexing policies.
type: int
choices: [0, 1, 2]
version_added: '1.1.0'
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.
type: int
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.
type: int
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.
type: int
next_query_flagIN:
description:
- When is set, its value will become the I(flagIN) value for the next queries.
type: int
version_added: "1.3.0"
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.
type: int
mirror_hostgroup:
description:
- Enables query mirroring. If set I(mirror_hostgroup) can be used to
mirror queries to the same or different hostgroup.
type: int
OK_msg:
description:
- The specified message will be returned for a query that uses the
defined rule.
type: str
version_added: '1.1.0'
error_msg:
description:
- Query will be blocked, and the specified error_msg will be returned to
the client.
type: str
log:
description:
- Query will be logged.
type: bool
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.
type: bool
comment:
description:
- Free form text field, usable for a descriptive comment of the query
rule.
type: str
state:
description:
- When C(present) - adds the rule, when C(absent) - removes the rule.
type: str
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 are not concerned about the
schedules deleted, you can set I(force_delete) to C(True).
type: bool
default: false
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
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 community.general.proxysql_manage_config module). It
# uses supplied credentials to connect to the proxysql admin interface.
- name: Add a rule
community.proxysql.proxysql_query_rules:
login_user: admin
login_password: admin
username: 'guest_ro'
match_pattern: "^SELECT.*"
destination_hostgroup: 1
active: 1
retries: 3
state: present
load_to_runtime: false
# This example demonstrates the situation, if your application tries to set a
# variable that will disable multiplexing, and you think it can be filtered out,
# you can create a filter that returns OK without executing the request.
- name: Add a filter rule
community.proxysql.proxysql_query_rules:
login_user: admin
login_password: admin
match_digest: '^SET @@wait_timeout = ?'
active: 1
OK_msg: 'The wait_timeout variable is ignored'
# This example adds a caching rule for a query that matches the digest.
# The query digest can be obtained from the `stats_mysql_query_digest`
# table. `cache_ttl` is specified in milliseconds. Empty responses are
# not cached.
- name: Add a cache rule
community.proxysql.proxysql_query_rules:
login_user: admin
login_password: admin
rule_id: 1
digest: 0xECA450EA500A9A55
cache_ttl: 30000
cache_empty_result: false
destination_hostgroup: 1
active: true
state: present
save_to_disk: true
load_to_runtime: true
# This example demonstrates how to prevent disabling multiplexing for
# situations where a request contains @.
- name: Add a rule with multiplex
community.proxysql.proxysql_query_rules:
login_user: admin
login_password: admin
rule_id: 1
active: 1
match_digest: '^SELECT @@max_allowed_packet'
multiplex: 2
# This example demonstrates how to use next_query_flagIN argument. It allows
# ProxySQL query rules to be chained. The examples shows how you can have SELECTS
# immediately follow INSERT/UPDATE/DELETE statements to query the primary hostgroup
# and avoid replication lag
- name: Add insert query rule
proxysql_query_rules:
match_digest: "^INSERT"
destination_hostgroup: 1,
next_query_flagIN: 1
- name: Add update query rule
proxysql_query_rules:
match_digest: "^UPDATE"
destination_hostgroup: 1,
next_query_flagIN: 1
- name: Add delete query rules
proxysql_query_rules:
match_digest: "^DELETE"
destination_hostgroup: 1,
next_query_flagIN: 1
- name: Add insert query rules
proxysql_query_rules:
match_digest: ".*"
destination_hostgroup: 1,
next_query_flagIN: 1
comment: Match every queries after an INSERT/UPDATE/DELETE query
# 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.
- name: Remove rules
community.proxysql.proxysql_query_rules:
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,
"cache_empty_result": null,
"multiplex": null,
"client_addr": null,
"comment": null,
"delay": null,
"destination_hostgroup": 1,
"digest": null,
"OK_msg": 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"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
class ProxyQueryRule(object):
def __init__(self, module, version):
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",
"re_modifiers",
"flagOUT",
"replace_pattern",
"destination_hostgroup",
"cache_ttl",
"multiplex",
"timeout",
"retries",
"delay",
"next_query_flagIN",
"mirror_flagOUT",
"mirror_hostgroup",
"error_msg",
"OK_msg",
"multiplex",
"log",
"apply",
"comment"]
if version.get('major') >= 2:
config_data_keys.append("cache_empty_result")
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, "QUERY RULES")
if self.load_to_runtime:
load_config_to_runtime(cursor, "QUERY RULES")
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():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
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'),
re_modifiers=dict(type='str'),
flagOUT=dict(type='int'),
replace_pattern=dict(type='str'),
destination_hostgroup=dict(type='int'),
cache_ttl=dict(type='int'),
cache_empty_result=dict(type='bool'),
multiplex=dict(type='int', choices=[0, 1, 2]),
timeout=dict(type='int'),
retries=dict(type='int'),
delay=dict(type='int'),
next_query_flagIN=dict(type='int'),
mirror_flagOUT=dict(type='int'),
mirror_hostgroup=dict(type='int'),
OK_msg=dict(type='str'),
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')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
)
proxysql_query_rule = ProxyQueryRule(module, version)
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 mysql_driver.Error as e:
module.fail_json(
msg="unable to modify rule.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to remove rule.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,420 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_query_rules_fast_routing
author: "Akim Lindberg (@akimrx)"
short_description: Modifies query rules for fast routing policies using the proxysql admin interface
description:
- The M(community.proxysql.proxysql_query_rules_fast_routing) module modifies query rules for fast
routing policies and attributes using the proxysql admin interface.
version_added: '1.1.0'
options:
username:
description:
- Filtering criteria matching username, a query will match only if the connection is made with
the correct username.
type: str
required: true
schemaname:
description:
- Filtering criteria matching schemaname, a query will match only if the connection uses
schemaname as its default schema.
type: str
required: true
flagIN:
description:
- Evaluated in the same way as I(flagIN) is in B(mysql_query_rules) and correlates to the
I(flagOUT/apply) specified in the B(mysql_query_rules) table.
(see M(community.proxysql.proxysql_query_rules)).
type: int
default: 0
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) (refer to M(community.proxysql.proxysql_mysql_users)).
type: int
required: true
comment:
description:
- Free form text field, usable for a descriptive comment of the query rule.
type: str
default: ''
state:
description:
- When C(present), adds the rule. When C(absent), removes the rule.
type: str
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 are not concerned about the
schedules deleted, you can set I(force_delete) to C(True).
type: bool
default: false
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
EXAMPLES = '''
---
# This example adds a rule for fast routing
- name: Add a rule
community.proxysql.proxysql_query_rules_fast_routing:
login_user: admin
login_password: admin
username: 'user_ro'
schemaname: 'default'
destination_hostgroup: 1
comment: 'fast route user_ro to default schema'
state: present
save_to_disk: true
load_to_runtime: 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_fast_routing",
"rules": [
{
"username": "user_ro",
"schemaname": "default",
"destination_hostgroup": 1,
"flagIN": "0",
"comment": ""
}
],
"state": "present"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
class ProxyQueryRuleFastRouting(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 = [
"username",
"schemaname",
"flagIN",
"destination_hostgroup",
"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_fast_routing "
"WHERE username = %s "
"AND schemaname = %s "
"AND flagIN = %s"
)
query_data = [
self.config_data["username"],
self.config_data["schemaname"],
self.config_data["flagIN"],
]
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_fast_routing"
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 += " WHERE " + col + " = %s"
else:
query_string += " 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):
query_string = (
"SELECT * "
"FROM mysql_query_rules_fast_routing "
"WHERE username = %s "
"AND schemaname = %s "
"AND flagIN = %s"
)
query_data = [
self.config_data["username"],
self.config_data["schemaname"],
self.config_data["flagIN"]
]
for col, val in iteritems(self.config_data):
if val is not None:
query_data.append(val)
query_string += " AND " + col + " = %s"
cursor.execute(query_string, query_data)
rule = cursor.fetchall()
return rule
def create_rule_config(self, cursor):
query_string = "INSERT INTO mysql_query_rules_fast_routing ("
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 += col + ","
query_string = query_string[:-1]
query_string += ") VALUES (" + "%s, " * cols
query_string = query_string[:-2]
query_string += ")"
cursor.execute(query_string, query_data)
return True
def update_rule_config(self, cursor):
query_string = "UPDATE mysql_query_rules_fast_routing"
cols = 0
query_data = [
self.config_data["username"],
self.config_data["schemaname"],
self.config_data["flagIN"]
]
for col, val in iteritems(self.config_data):
if val is not None and col not in ("username", "schemaname", "flagIN"):
query_data.insert(cols, val)
cols += 1
if cols == 1:
query_string += " SET " + col + "= %s,"
else:
query_string += " " + col + " = %s,"
query_string = query_string[:-1]
query_string += (
"WHERE username = %s "
"AND schemaname = %s "
"AND flagIN = %s"
)
cursor.execute(query_string, query_data)
return True
def delete_rule_config(self, cursor):
query_string = "DELETE FROM mysql_query_rules_fast_routing"
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 += " WHERE " + col + " = %s"
else:
query_string += " 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, changed):
if not changed:
return
if self.save_to_disk:
save_config_to_disk(cursor, "QUERY RULES")
if self.load_to_runtime:
load_config_to_runtime(cursor, "QUERY RULES")
def create_rule(self, check_mode, result, cursor):
if not check_mode:
result['changed'] = self.create_rule_config(cursor)
result['msg'] = "Added rule to mysql_query_rules_fast_routing."
self.manage_config(cursor, result['changed'])
result['rules'] = self.get_rule_config(cursor)
else:
result['changed'] = True
result['msg'] = (
"Rule would have been added to "
"mysql_query_rules_fast_routing, "
"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_fast_routing."
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_fast_routing, "
"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_fast_routing."
self.manage_config(cursor, result['changed'])
else:
result['changed'] = True
result['msg'] = (
"Rule would have been deleted from "
"mysql_query_rules_fast_routing, "
"however check_mode is enabled."
)
# ===========================================
# Module execution.
#
def main():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
username=dict(required=True, type='str'),
schemaname=dict(required=True, type='str'),
destination_hostgroup=dict(required=True, type='int'),
flagIN=dict(default=0, type='int'),
comment=dict(default='', 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')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
config_file = module.params["config_file"]
cursor = None
try:
cursor, db_conn, version = mysql_connect(
module,
login_user,
login_password,
config_file,
cursor_class='DictCursor'
)
except mysql_driver.Error as e:
module.fail_json(msg="unable to connect to ProxySQL Admin Module: %s" % to_native(e))
query_rule = ProxyQueryRuleFastRouting(module)
result = {}
result['state'] = query_rule.state
if query_rule.state == "present":
try:
if not query_rule.check_rule_cfg_exists(cursor):
if query_rule.config_data["username"] and query_rule.config_data["schemaname"] and \
query_rule.check_rule_pk_exists(cursor):
query_rule.update_rule(module.check_mode, result, cursor)
else:
query_rule.create_rule(module.check_mode, result, cursor)
else:
result['changed'] = False
result['msg'] = (
"The rule already exists in "
"mysql_query_rules_fast_routing "
"and doesn't need to be updated."
)
result['rules'] = query_rule.get_rule_config(cursor)
except mysql_driver.Error as e:
module.fail_json(msg="unable to modify rule: %s" % to_native(e))
elif query_rule.state == "absent":
try:
existing_rules = query_rule.check_rule_cfg_exists(cursor)
if existing_rules > 0:
if existing_rules == 1 or query_rule.force_delete:
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_fast_routing memory "
"configuration"
)
except mysql_driver.Error as e:
module.fail_json(msg="unable to remove rule: %s" % to_native(e))
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,398 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_replication_hostgroups
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.
type: int
required: true
reader_hostgroup:
description:
- Id of the reader hostgroup.
type: int
required: true
comment:
description:
- Text field that can be used for any purposes defined by the user.
type: str
default: ""
state:
description:
- When C(present) - adds the replication hostgroup, when C(absent) -
removes the replication hostgroup.
type: str
choices: [ "present", "absent" ]
default: present
check_type:
description:
- Which check type to use when detecting that the node is a standby.
- Requires proxysql >= 2.0.1. Otherwise it has no effect.
- C(read_only|innodb_read_only) and C(read_only&innodb_read_only) requires proxysql >= 2.0.8.
type: str
choices: [ "read_only", "innodb_read_only", "super_read_only", "read_only|innodb_read_only", "read_only&innodb_read_only" ]
default: read_only
version_added: 1.3.0
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
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
# community.general.proxysql_manage_config module). It uses supplied credentials
# to connect to the proxysql admin interface.
- name: Add a replication hostgroup
community.proxysql.proxysql_replication_hostgroups:
login_user: 'admin'
login_password: 'admin'
writer_hostgroup: 1
reader_hostgroup: 2
state: present
load_to_runtime: false
- name: Change check_type
community.proxysql.proxysql_replication_hostgroups:
login_user: 'admin'
login_password: 'admin'
writer_hostgroup: 1
reader_hostgroup: 2
check_type: innodb_read_only
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.
- name: Remove a replication hostgroup
community.proxysql.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",
"check_type": "read_only"
},
"state": "present"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if not module.params["writer_hostgroup"] >= 0:
module.fail_json(
msg="writer_hostgroup must be a integer greater than or equal to 0"
)
if module.params["reader_hostgroup"] < 0:
module.fail_json(
msg="reader_hostgroup must be an integer greater than or equal to 0"
)
if module.params["reader_hostgroup"] == module.params["writer_hostgroup"]:
module.fail_json(
msg="reader_hostgroup and writer_hostgroup must be different integer values")
class ProxySQLReplicationHostgroup(object):
def __init__(self, module, version):
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"]
self.check_type = module.params["check_type"]
self.check_type_support = version.get('major') >= 2
self.check_mode = module.check_mode
def check_repl_group_config(self, cursor, keys):
query_string = \
"""SELECT count(*) AS `repl_groups`
FROM mysql_replication_hostgroups
WHERE writer_hostgroup = %s"""
query_data = \
[self.writer_hostgroup]
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"""
query_data = \
[self.writer_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)
if self.check_type_support:
self.update_check_type(cursor)
return True
def delete_repl_group_config(self, cursor):
query_string = \
"""DELETE FROM mysql_replication_hostgroups
WHERE writer_hostgroup = %s"""
query_data = \
[self.writer_hostgroup]
cursor.execute(query_string, query_data)
return True
def manage_config(self, cursor, state):
if state and not self.check_mode:
if self.save_to_disk:
save_config_to_disk(cursor, "SERVERS")
if self.load_to_runtime:
load_config_to_runtime(cursor, "SERVERS")
def create_repl_group(self, result, cursor):
if not self.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, result, cursor):
current = self.get_repl_group_config(cursor)
if self.check_type_support and current.get('check_type') != self.check_type:
result['changed'] = True
if not self.check_mode:
result['msg'] = "Updated replication hostgroups"
self.update_check_type(cursor)
else:
result['msg'] = "Updated replication hostgroups in check_mode"
if current.get('comment') != self.comment:
result['changed'] = True
result['msg'] = "Updated replication hostgroups in check_mode"
if not self.check_mode:
result['msg'] = "Updated replication hostgroups"
self.update_comment(cursor)
if int(current.get('reader_hostgroup')) != self.reader_hostgroup:
result['changed'] = True
result['msg'] = "Updated replication hostgroups in check_mode"
if not self.check_mode:
result['msg'] = "Updated replication hostgroups"
self.update_reader_hostgroup(cursor)
result['repl_group'] = self.get_repl_group_config(cursor)
self.manage_config(cursor,
result['changed'])
def delete_repl_group(self, result, cursor):
if not self.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.")
def update_check_type(self, cursor):
try:
query_string = ("UPDATE mysql_replication_hostgroups "
"SET check_type = %s "
"WHERE writer_hostgroup = %s")
cursor.execute(query_string, (self.check_type, self.writer_hostgroup))
except Exception as e:
pass
def update_reader_hostgroup(self, cursor):
query_string = ("UPDATE mysql_replication_hostgroups "
"SET reader_hostgroup = %s "
"WHERE writer_hostgroup = %s")
cursor.execute(query_string, (self.reader_hostgroup, self.writer_hostgroup))
def update_comment(self, cursor):
query_string = ("UPDATE mysql_replication_hostgroups "
"SET comment = %s "
"WHERE writer_hostgroup = %s ")
cursor.execute(query_string, (self.comment, self.writer_hostgroup))
# ===========================================
# Module execution.
#
def main():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
writer_hostgroup=dict(required=True, type='int'),
reader_hostgroup=dict(required=True, type='int'),
check_type=dict(type='str', default='read_only', choices=['read_only',
'innodb_read_only',
'super_read_only',
'read_only|innodb_read_only',
'read_only&innodb_read_only']),
comment=dict(type='str', default=''),
state=dict(default='present', choices=['present',
'absent']),
save_to_disk=dict(default=True, type='bool'),
load_to_runtime=dict(default=True, type='bool')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
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, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
)
proxysql_repl_group = ProxySQLReplicationHostgroup(module, version)
result = {}
result['state'] = proxysql_repl_group.state
result['changed'] = False
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(result,
cursor)
else:
proxysql_repl_group.update_repl_group(result, cursor)
result['repl_group'] = proxysql_repl_group.get_repl_group_config(cursor)
except mysql_driver.Error as e:
module.fail_json(
msg="unable to modify replication hostgroup.. %s" % to_native(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(result, cursor)
else:
result['changed'] = False
result['msg'] = ("The repl group is already absent from the" +
" mysql_replication_hostgroups memory" +
" configuration")
except mysql_driver.Error as e:
module.fail_json(
msg="unable to delete replication hostgroup.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,409 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: proxysql_scheduler
author: "Ben Mildren (@bmildren)"
short_description: Adds or removes schedules from proxysql admin interface
description:
- The M(community.proxysql.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.
type: bool
default: true
interval_ms:
description:
- How often (in millisecond) the job will be started. The minimum value
for I(interval_ms) is 100 milliseconds.
type: int
default: 10000
filename:
description:
- Full path of the executable to be executed.
type: str
required: true
arg1:
description:
- Argument that can be passed to the job.
type: str
arg2:
description:
- Argument that can be passed to the job.
type: str
arg3:
description:
- Argument that can be passed to the job.
type: str
arg4:
description:
- Argument that can be passed to the job.
type: str
arg5:
description:
- Argument that can be passed to the job.
type: str
comment:
description:
- Text field that can be used for any purposed defined by the user.
type: str
state:
description:
- When C(present) - adds the schedule, when C(absent) - removes the
schedule.
type: str
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 are not concerned about the
schedules deleted, you can set I(force_delete) to C(True).
type: bool
default: false
extends_documentation_fragment:
- community.proxysql.proxysql.managing_config
- community.proxysql.proxysql.connectivity
notes:
- Supports C(check_mode).
'''
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 community.general.proxysql_manage_config
# module). It uses supplied credentials to connect to the proxysql admin
# interface.
- name: Add a schedule
community.proxysql.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.
- name: Remove a schedule
community.proxysql.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"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.proxysql.plugins.module_utils.mysql import (
mysql_connect,
mysql_driver,
proxysql_common_argument_spec,
save_config_to_disk,
load_config_to_runtime,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
# ===========================================
# proxysql module specific support methods.
#
def perform_checks(module):
if module.params["interval_ms"] < 100 \
or module.params["interval_ms"] > 100000000:
module.fail_json(
msg="interval_ms must between 100ms & 100000000ms"
)
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, "SCHEDULER")
if self.load_to_runtime:
load_config_to_runtime(cursor, "SCHEDULER")
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():
argument_spec = proxysql_common_argument_spec()
argument_spec.update(
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')
)
module = AnsibleModule(
supports_check_mode=True,
argument_spec=argument_spec
)
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, db_conn, version = mysql_connect(module,
login_user,
login_password,
config_file,
cursor_class='DictCursor')
except mysql_driver.Error as e:
module.fail_json(
msg="unable to connect to ProxySQL Admin Module.. %s" % to_native(e)
)
proxysql_schedule = ProxySQLSchedule(module)
result = {}
result['state'] = proxysql_schedule.state
result['filename'] = proxysql_schedule.filename
if proxysql_schedule.state == "present":
try:
if 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 mysql_driver.Error as e:
module.fail_json(
msg="unable to modify schedule.. %s" % to_native(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 mysql_driver.Error as e:
module.fail_json(
msg="unable to remove schedule.. %s" % to_native(e)
)
module.exit_json(**result)
if __name__ == '__main__':
main()