Commit c5d93b74 authored by Alain Takoudjou's avatar Alain Takoudjou

Replication manager: Correctly handle request database slaves and multi clusters

See merge request !1209
parents 65bfd447 4ded1e20
Pipeline #22834 failed with stage
in 0 seconds
......@@ -30,4 +30,4 @@ go.importpath = github.com/signal18/replication-manager
repository = https://github.com/signal18/replication-manager
#branch = 2.1
branch = develop
revision = 774fde2ce67942a31b99200fea6301189344ecd9
\ No newline at end of file
revision = ecd7fa29cb517b292e70ca557757882cd748016e
\ No newline at end of file
......@@ -18,7 +18,7 @@ md5sum = 55c7fd4dd6a39b31878889fbfb00f995
[instance-repman.cfg]
_update_hash_filename_ = instance-repman.cfg.jinja2.in
md5sum = 4ce8808677773a033f4b59bdf6b8e653
md5sum = 6fc82c4b645bceb97ba7b6f5b56d161b
[config-toml.in]
_update_hash_filename_ = templates/config.toml.in
......@@ -34,7 +34,7 @@ md5sum = 0eeb24c6aa0760f0d33c4cc2828ddf30
[template-mariadb.cfg]
_update_hash_filename_ = instance-mariadb.cfg.jinja2.in
md5sum = 34eed1b6d7fc45dc078f30235e22d04f
md5sum = bd5b0e72e8b5ddcd691e7f90a2947e50
[template-my-cnf]
_update_hash_filename_ = templates/my.cnf.in
......@@ -46,7 +46,7 @@ md5sum = 47b76144e1b116580c8acf08274af976
[template-publish-slave-information]
_update_hash_filename_ = publish-database-slave-parameters.cfg.in
md5sum = 9616477ff993c55c2b43bf8797db6994
md5sum = 2e9f3afde619e20f08803013a4225e94
[mariadb-init-root-sql]
_update_hash_filename_ = templates/mariadb_init_root.sql.in
......@@ -58,7 +58,7 @@ md5sum = c203f40a58386310a433b58fd345a341
[repman-manager-sh.in]
_update_hash_filename_ = templates/repman-manager.sh.in
md5sum = 852dfab6d798aa1382eec4de2fd624f9
md5sum = 70201395116d79b5bb4aabef13079c10
[dbjobs-in]
_update_hash_filename_ = templates/dbjobs.in
......@@ -74,4 +74,4 @@ md5sum = 455aaf369bf5141758dc57f2c0e67b08
[slave-db-manage.in]
_update_hash_filename_ = templates/slave-db-manage.in
md5sum = b45313ae5fb06972cc7fc945e34e434a
md5sum = cefcb8c7d17367b14414314ffd325c26
......@@ -166,9 +166,9 @@ url = {{ parameter_dict['template-init-root-wrapper'] }}
[{{ section('mysql-slave-db-cleanup') }}]
< = jinja2-template-script-base
output = ${directory:scripts}/manage-slave-db
db-name = {{ slapparameter_dict['database-name'] }}
db-name = {{ dumps(slapparameter_dict['database-name']) }}
extra-context =
key database_name :db-name
key database_name_list :db-name
url = {{ parameter_dict['template-manage-db'] }}
[mysqld]
......@@ -199,7 +199,7 @@ wait-for-files =
{{ supervisord_lib.supervisord("mariadb-ctl", buildout_bin_directory, supervisord_conf, use_service_hash=False) }}
{% do part_list.append("supervisord-mariadb-ctl") -%}
{% set maradb_program_dict = {"name": "mariadb", "command": "${mysqld-launcher:wrapper-path}",
"stopwaitsecs": 86400, "startretries": 10, "autorestart": True, "environment": [],
"stopwaitsecs": 86400, "startretries": 10, "autorestart": False, "environment": [],
"stdout_logfile": "${directory:log}/mariadb_stdout.log",
"stderr_logfile": "${directory:log}/mariadb_stdout.log" } %}
......@@ -381,9 +381,6 @@ config = ${directory:etc}/mysql/my.cnf
command = ${mysql-get-config:output}
update-command = ${:command}
[dash]
dash = {{ dumps(dash) }}
[{{ section('promise-check-computer-memory') }}]
<= monitor-promise-base
promise = check_command_execute
......
......@@ -113,12 +113,6 @@
"patternProperties": {
".*": {
"properties": {
"name": {
"title": "Name of the cluster",
"description": "Name of the cluster: Should not contains spaces or any special characters.",
"type": "string",
"default": ""
},
"database-amount": {
"title": "Amount of databases for cluster",
"description": "Database amount to deploy with this cluster. Minimal amount is 2 required to enable replication.",
......
{
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"db_user": {
"description": "Database User, default is 'user'. This parameter is set only if database is not created yet.",
"title": "Database User",
"type": "string",
"default": "user"
},
"db_password": {
"description": "Database password. If no password set, a password will be generated. This parameter is set only if database is not created yet.",
"title": "Initial database password",
......@@ -42,6 +36,12 @@
"ascii_bin"
],
"default": ""
},
"cluster_id": {
"title": "Replication Manager Cluster Name",
"description": "When a cluster name is given, try to request the slave in the specified cluster name. If cluster name is not provided or does not exist, the slave is requested in the first cluster.",
"type": "string",
"default": ""
}
},
"title": "Input Parameters",
......
......@@ -2,9 +2,6 @@
{% set part_list = [] -%}
{% set monitor_base_url_dict = {} -%}
{% set mariadb_dict = {} -%}
{% set mariadb_server_list = [] -%}
{% set receiver_port_list = [] -%}
{% set mariadb_path_list = [] -%}
{% set ip = (ipv6_set | list)[0] -%}
{% set ipv4 = (ipv4_set | list)[0] -%}
{% set cluster_list = [] -%}
......@@ -12,12 +9,17 @@
"pfs", "linux", "readonly", "diskmonitor", "sqlerror", "compressbinlog", "bm4ci",
"mroonga", "utctime", "readcommitted", "nohandshake", "ssl"] -%}
{% set frontend_parameter_dict = slapparameter_dict.get('slave-frontend', {}) -%}
{% set database_slave_list = [] -%}
{% set db_name_list = [] -%}
{% set count = namespace(value=2) %}
{% set caucase_bind = '[' ~ ip ~ ']:8890' -%}
{% set caucase_url = 'http://' ~ caucase_bind -%}
{% set csrid_list = [] -%}
{% set default_parameter_dict = {"cluster1": {"db-prefered-master": "",
"database-amount": 2, "proxysql-user": "external",
"logical-backup-cron": "0 21 * * *", "physical-backup-cron": "0 1 * * *"}} -%}
{% set cluster_name_list = slapparameter_dict.get('repman-cluster-dict', default_parameter_dict).keys() -%}
{% set database_slave_dict = {} -%}
{% set db_name_dict = {} -%}
{% set default_slave_cluster_id = list(cluster_name_list)[0] -%}
{% macro password(name) -%}
[{{ name }}-password]
......@@ -46,8 +48,14 @@ ssl = ${:etc}/ssl
[proxysql-directory]
recipe = slapos.cookbook:mkdirectory
home = ${buildout:directory}/proxy
config = ${directory:etc}/proxysql
ssl = ${:config}/ssl
ssl = ${:config}/default-ssl
{% for cluster in cluster_name_list -%}
{{ cluster }} = ${:home}/{{ cluster }}
{% do database_slave_dict.__setitem__(cluster, []) -%}
{% do db_name_dict.__setitem__(cluster, []) -%}
{% endfor -%}
{% import "supervisord_lib" as supervisord_lib with context %}
{% set proxysql_controller = "proxysql-ctl" -%}
......@@ -81,40 +89,44 @@ inline =
if [ -z "$CONFIG" ]; then
CONFIG="${proxysql-directory:config}/proxysql.cnf"
fi
ETCDIR=`dirname $CONFIG`
mkdir -p ${repman:config-tmp}/proxies
mkdir -p $ETCDIR/proxysql
ln -sf ${proxysql-directory:ssl} $ETCDIR/proxysql/ssl
cd ${repman:config-tmp}/proxies
{{ curl_bin }} -o proxies-$NAME.tar.gz ${nginx-parameter:repman-url}/api/clusters/$NAME/servers/$HOST/$PORT/config
tar -xzf proxies-$NAME.tar.gz
cp etc/proxysql/proxysql.cnf $CONFIG
output = ${directory:bin}/update-proxysql-config
{% set slave_information_list = [] -%}
{% for instance_dict in slave_instance_list -%}
{% set slave_dict = {
'name': 'db_%s' % instance_dict['slave_reference'].replace('-', '_').lower(),
'user': instance_dict.get('db_user', 'user'),
'user': instance_dict['slave_reference'].replace('_', '').replace('-', '').lower(),
'password': instance_dict.get('db_password', '${' ~ instance_dict['slave_reference'] ~ '-password:passwd}'),
'slave_reference': instance_dict['slave_reference'],
'charset': instance_dict.get('db_charset', ''),
'cluster_id': instance_dict.get('cluster_id', default_slave_cluster_id),
'require_ssl': True
} -%}
{% do database_slave_list.append(slave_dict) -%}
{% do db_name_list.append(slave_dict['name']) -%}
{% do slave_information_list.append(slave_dict) -%}
{% do database_slave_dict[slave_dict['cluster_id']].append(slave_dict) -%}
{% do db_name_dict[slave_dict['cluster_id']].append([slave_dict['name'], slave_dict['user']]) -%}
{{ password(instance_dict['slave_reference']) }}
{% endfor %}
# set each database dict which will be used to publish slave information
[database-slave-information]
{% for slave_dict in database_slave_list -%}
{{ slave_dict['name'] }} = !py!{{ slave_dict }}
{% for slave_dict in slave_information_list -%}
{{ slave_dict['name'] }} = !py!{{ slave_dict }}
{% endfor %}
{% set db_list = db_name_list | join(' ') -%}
{% do mariadb_dict.__setitem__('computer-memory-percent-threshold', 80) -%}
{% set default_parameter_dict = {"cluster1": {"name": "cluster1", "db-prefered-master": "",
"database-amount": 2, "proxysql-user": "external",
"logical-backup-cron": "0 21 * * *", "physical-backup-cron": "0 1 * * *"}} -%}
{% for name, parameter_dict in slapparameter_dict.get('repman-cluster-dict', default_parameter_dict).items() -%}
{% do mariadb_dict.__setitem__('innodb-file-per-table', parameter_dict.get('innodb-file-per-table', 1)) -%}
{% do mariadb_dict.__setitem__('use-ipv6', parameter_dict.get('use-ipv6', True)) -%}
{% set db_list = db_name_dict[name] -%}
# Request mariadb instances
{% set db_amount = parameter_dict.get('database-amount', 2) -%}
......@@ -123,16 +135,19 @@ output = ${directory:bin}/update-proxysql-config
{% endif -%}
{% set count.value = count.value + db_amount %}
{% set mariadb_path_list = [] -%}
{% set mariadb_server_list = [] -%}
{% set receiver_port_list = [] -%}
{% for i in range(0, db_amount) -%}
{% do mariadb_dict.__setitem__('tcp-port', 2099 + (i * 100)) -%}
{% set section = 'request-mariadb-' ~ i -%}
{% set section = 'request-mariadb-' ~ i ~ '-' ~ name -%}
{% set dbname = 'Mariadb-' ~ i %}
{% set instance_name = dbname ~ '-' ~ name-%}
[{{ section }}]
<= request-common
software-type = mariadb
name = {{ dbname ~ '-' ~ name}}
name = {{ instance_name }}
sla-computer_guid = {{ dumps(parameter_dict.get('-sla-' ~ i ~'-computer_guid', '')) }}
{% for key, value in mariadb_dict.items() -%}
config-{{ key }} = {{ dumps(value) }}
......@@ -145,8 +160,8 @@ config-repman-passwd = ${repman-parameter:password}
config-repman-url = ${nginx-parameter:backend-url}
config-repman-secure-url = ${nginx-parameter:backend-ssl-url}
config-cluster = {{ name }}
config-name = {{ dbname }}
config-database-list = !py!{{ database_slave_list }}
config-name = {{ instance_name }}
config-database-list = !py!{{ database_slave_dict[name] }}
config-database-name = {{ dumps(db_list) }}
config-require-ssl = {{ dumps(slapparameter_dict.get('require-ssl', False)) }}
return =
......@@ -160,7 +175,7 @@ return =
{% do mariadb_server_list.append('${' ~ section ~ ':connection-database-host}') -%}
{% do receiver_port_list.append('${' ~ section ~ ':connection-receiver-port}') -%}
{% do mariadb_path_list.append('${' ~ section ~ ':connection-partition-path}') -%}
{% do monitor_base_url_dict.__setitem__('mariadb' ~ i, '${' ~ section ~ ':connection-monitor-base-url}') -%}
{% do monitor_base_url_dict.__setitem__(instance_name, '${' ~ section ~ ':connection-monitor-base-url}') -%}
{% do csrid_list.append('${' ~ section ~ ':connection-csr-id}') -%}
{% endfor -%}
......@@ -205,7 +220,7 @@ db-prefered-master = {{ mariadb_server_list[0] }}
proxysql-servers = {{ ipv4 }}
proxysql-servers-ipv6 = [{{ ip }}]
password = ${repman-parameter:password}
proxysql-partition = ${buildout:directory}
proxysql-partition = {{ '${proxysql-directory:' ~ name ~ '}' }}
receiver-port-list = {{ receiver_port_list | join(',') }}
enabled-tags = {{ parameter_dict.get("tag-list", tag_list) | join(',') }}
proxy-tags = {{ parameter_dict.get("proxy-tags", ["pkg", "masterslave", "linux", "noreadwritesplit", "ssl"]) | join(',') }}
......@@ -248,7 +263,7 @@ context =
recipe = plone.recipe.command
# if Repman is not started, cannot download config from server
stop-on-error = false
config = ${proxysql-directory:config}/proxysql.cnf
config = ${proxysql-directory:home}/{{ name }}/etc/proxysql.cnf
data = ${repman:proxy-data}/{{ name }}
command =
mkdir -p ${:data} &&
......@@ -295,7 +310,7 @@ config-port = {{ '${' ~ name ~ '-cluster-parameter:proxy-port}' }}
{% set service_name = "proxysql-" ~ name -%}
{% set proxysql_dict = {"name": service_name, "command": "${" ~ service_name ~ "-wrapper:wrapper-path}",
"stopwaitsecs": 60, "startretries": 10, "autorestart": True, "environment": [],
"stopwaitsecs": 60, "startretries": 10, "autorestart": False, "environment": [],
"stdout_logfile": "${repman:proxies-log}/" ~ service_name ~ ".log",
"stderr_logfile": "${repman:proxies-log}/" ~ service_name ~ ".log" } %}
......@@ -313,7 +328,7 @@ RootSoftwareInstance = ${:default}
[dynamic-{{ name}}-publish-slave-information]
recipe = slapos.recipe.template:jinja2
output = ${buildout:directory}/database-publish-slave-information.cfg
output = ${buildout:directory}/database-{{ name}}-publish-slave-information.cfg
extensions = jinja2.ext.do
url = {{ template_publish_slave_information }}
context =
......@@ -321,6 +336,7 @@ context =
key proxy_port {{ name }}-cluster-parameter:proxy-port
raw eggs_directory {{ eggs_directory }}
raw develop_eggs_directory {{ develop_eggs_directory }}
raw cluster {{ name }}
raw ipv6 {{ ip }}
raw ipv4 {{ ipv4 }}
section slave_dict database-slave-information
......@@ -617,7 +633,7 @@ recipe = slapos.cookbook:publish-early
monitor-password monitor-htpasswd:passwd
db-root-password gen-root-password:passwd
repman-password repman-password:passwd
database-list = {{ db_list }}
database-dict = {{ db_name_dict }}
[publish-connection-parameter]
<= monitor-publish
......@@ -626,7 +642,7 @@ recipe = slapos.cookbook:publish
backend-url = ${nginx-parameter:backend-ssl-url}
url = ${repman-frontend:connection-secure_access}
username = ${repman-parameter:username}
database-list = {{ db_list }}
database-dict = {{ db_name_dict }}
{% for name, value in publish_dict.items() -%}
{{ name }} = {{ value }}
{% endfor %}
......
......@@ -11,6 +11,7 @@ cert_file = ${slap-connection:cert-file}
{% for _, database in slave_dict.items() -%}
{% if database['cluster_id'] == cluster -%}
{% set slave_reference = database['slave_reference'] -%}
{% do part_list.append('publish-slave-' ~ slave_reference) -%}
......@@ -24,6 +25,8 @@ url = {{ "mysql://" ~ database['user'] ~ ":" ~ database['password'] ~ "@[" ~ ipv
url = {{ "mysql://" ~ ipv4 ~ ":" ~ proxy_port ~ database['name'] }}
url_v6 = {{ "mysql://[" ~ ip ~ "]:" ~ proxy_port ~ database['name'] }}
{% endif -%}
cluster = {{ database['cluster_id'] }}
{% endif -%}
{% endfor %}
[buildout]
......
#!{{ bash_bin }}
#set -e
set -e
curl () {
{{ curl_bin }} -k --silent -H "Accept: application/json" "$@"
......@@ -33,6 +33,19 @@ wait_database () {
done
}
check_cluster () {
# Check if cluster is boostrapped
NAME=$1
TOKEN=$(get_token | {{ jq_bin }} -r '.token')
ERRORS=$(curl -H "Authorization: Bearer ${TOKEN}" {{ secure_url }}/api/clusters/$NAME/topology/alerts | {{ jq_bin }} -r '.errors')
if [ "$ERRORS" != "null" ] && [ ! -z "$ERRORS" ]; then
echo "ERROR: Bootstrap replication of cluster $NAME failed!";
echo $ERRORS;
return 1;
fi
return 0
}
activate_proxy () {
NAME=$1
URL="{{ secure_url }}/api/clusters/$NAME/settings/actions/switch/database-hearbeat"
......@@ -46,14 +59,42 @@ activate_proxy () {
fi
}
check_add () {
NAME=$1
STATUS=$(curl -H "Authorization: Bearer ${TOKEN}" -o /dev/null -w "%{http_code}" {{ secure_url }}/api/clusters/$NAME)
if [ $STATUS != 200 ]; then
# if cluster doesn't exists we add it
curl -H "Authorization: Bearer ${TOKEN}" \
{{ secure_url }}/api/clusters/actions/add/$NAME
fi
}
reload_config () {
# check if etc/repman/cluster.d/config-clusterXX.toml has changed and call reload
NAME=$1
FILE="{{ parameter_dict['config-tmp'] }}/$NAME-config-cheksum.txt"
CHECKSUM=$(md5sum {{ parameter_dict['clusters'] }}/config-$NAME.toml)
if [ -s $FILE ]; then
OLDSUM=$(cat $FILE);
fi
echo "$CHECKSUM" > $FILE
if [ "$CHECKSUM" != "$OLDSUM" ]; then
echo "Reloading settings for $NAME to apply changes..."
curl -H "Authorization: Bearer ${TOKEN}" \
{{ secure_url }}/api/clusters/$NAME/settings/actions/reload
else
echo "$NAME config is up to date."
fi
}
TOKEN=$(get_token | {{ jq_bin }} -r '.token')
# Always reload cluster configuration to apply recent changes
{% for name in cluster_name_list -%}
# reload {{ name }} settings
echo "Reloading settings for {{ name }}..."
curl -H "Authorization: Bearer ${TOKEN}" \
{{ secure_url }}/api/clusters/{{ name }}/settings/actions/reload
check_add {{ name }}
reload_config {{ name }}
# Start Replication on {{ name }}
if [ ! -f "{{ parameter_dict['bootstrap'] }}/{{ name }}_bootstrapped" ]; then
......@@ -66,7 +107,6 @@ if [ ! -f "{{ parameter_dict['bootstrap'] }}/{{ name }}_bootstrapped" ]; then
curl -H "Authorization: Bearer ${TOKEN}" \
{{ secure_url }}/api/clusters/{{ name }}/actions/replication/cleanup
CODE=$(curl -H "Authorization: Bearer ${TOKEN}" -o /dev/null -w "%{http_code}" {{ secure_url }}/api/clusters/{{ name }}/actions/replication/bootstrap/master-slave)
SUCCESS=0
if [ $CODE -eq 200 ]; then
activate_proxy {{ name }}
if [ $? -eq 0 ]; then
......@@ -77,6 +117,9 @@ if [ ! -f "{{ parameter_dict['bootstrap'] }}/{{ name }}_bootstrapped" ]; then
else
echo "ERROR: Failed to bootstrap cluster {{ name }}... http_code $CODE"
fi
else
# Check cluster health
check_cluster {{ name }}
fi
{% endfor %}
......@@ -17,6 +17,21 @@ run_mysql () {
TOKEN=$(get_token | {{ jq_bin }} -r '.token')
DATADIR=$(curl -H "Authorization: Bearer ${TOKEN}" \ {{ secure_url }}/api/clusters/{{ cluster_name }}/topology/master | {{ jq_bin }} -r '.slaposDatadir')
revoke_user () {
DB=$1
UNAME=$2
if [ ! -z "$UNAME" ]; then
echo "Revoking all grants for user '$USER'";
run_mysql -Be "
REVOKE ALL PRIVILEGES, GRANT OPTION FROM '$UNAME';
DROP USER IF EXISTS '$UNAME'@'%';
DROP USER IF EXISTS '$UNAME'@'localhost';
DROP USER IF EXISTS '$UNAME'@'::';
FLUSH PRIVILEGES;
"
fi
}
# Only write or delete on master database else, we break replication.
if [ "$DATADIR" = "{{ partition_dir }}" ]; then
......@@ -26,21 +41,24 @@ use repman_slave_definition;
CREATE TABLE IF NOT EXISTS \`slave\` (
\`name\` varchar(80) NOT NULL,
\`state\` tinyint(1) DEFAULT NULL,
\`user\` varchar(80) NOT NULL,
PRIMARY KEY (\`name\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
UPDATE \`slave\` set \`state\`=false;
{% for name in database_name.split(' ') -%}
{% for name, user in database_name_list -%}
{% if name -%}
REPLACE INTO \`slave\` VALUES ('{{ name }}', true);
REPLACE INTO \`slave\` VALUES ('{{ name }}', true, '{{ user }}');
{% endif -%}
{% endfor -%}
EOF
# Update requested slaves database
OUTPUT="{{ tmp_dir }}/removed_db.txt"
run_mysql < {{ tmp_dir }}/.script.sql
rm -f {{ tmp_dir }}/.script.sql
run_mysql -NBe "SELECT name, user FROM repman_slave_definition.slave WHERE state=false" > $OUTPUT
DBNAME=$(run_mysql --skip-column-names -Be "SELECT name FROM repman_slave_definition.slave WHERE state=false");
RET=$?
......@@ -49,12 +67,17 @@ EOF
echo "Mysql command failed: $DBNAME"
exit $RET
fi
if [ -z "$DBNAME" ]; then
echo "No database for slave to remove.";
fi
for NAME in $DBNAME; do
if [ ! -z "$NAME" ]; then
USER=$(grep -oP "$NAME\s*\K\w+" $OUTPUT);
if [ ! -z "$USER" ]; then
revoke_user $NAME $USER;
fi
echo "Deleting database $NAME..."
run_mysql -e "DROP DATABASE IF EXISTS $NAME";
run_mysql -e "DELETE FROM repman_slave_definition.slave WHERE name='$NAME'";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment