Commit c9bc3dcb authored by Alain Takoudjou's avatar Alain Takoudjou

repmam: Databases are added to cluster by requesting slave instances

To add a Database, user should request a slave instance. Each slave
instance is a database.

- Destroy slave database if slave instance was destroyed

- Update and remove slave database only on master mariadb

- Allow to create database slave with character sets and collate options

- Generate slave database password if not set
parent 6f9487e1
......@@ -14,11 +14,11 @@
# not need these here).
[instance.cfg]
filename = instance.cfg.in
md5sum = 8a08be95a04f1a47098c4fdef80bdfed
md5sum = 9aeb37bd4590aa7fa746ef70d78cc0d0
[instance-repman.cfg]
_update_hash_filename_ = instance-repman.cfg.jinja2.in
md5sum = 40f8b4c91b756207318726916b9b072b
md5sum = e5b2d17e6ef5448e851521fee612f0c4
[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 = 55800ab1e939509cd4bfdb0975c260cd
md5sum = f1a03197b3a208cf6167769157e2df47
[template-my-cnf]
_update_hash_filename_ = templates/my.cnf.in
......@@ -42,7 +42,11 @@ md5sum = f3661b788099bb31d71ba6e7d36836d9
[template-mariadb-initial-setup]
_update_hash_filename_ = templates/mariadb_initial_setup.sql.in
md5sum = df44c2d6fb1971df582345daedad280c
md5sum = 96c151c376600a90fe2a44825f2de550
[template-publish-slave-information]
_update_hash_filename_ = publish-database-slave-parameters.cfg.in
md5sum = 9616477ff993c55c2b43bf8797db6994
[mariadb-init-root-sql]
_update_hash_filename_ = templates/mariadb_init_root.sql.in
......@@ -50,7 +54,7 @@ md5sum = d8a0a7d72b02f911dafda655941b805d
[init-root-wrapper-in]
_update_hash_filename_ = templates/init_root_wrapper.in
md5sum = 7661e3b2f66982b9b17bd2ab73e2e8ef
md5sum = b3dffa3c8b9b2cc86b536d411cbde9d4
[repman-manager-sh.in]
_update_hash_filename_ = templates/repman-manager.sh.in
......@@ -67,3 +71,7 @@ md5sum = 8f14054b3761b167cf8a86f4f084a574
[proxy-need-start-stop.sh.in]
_update_hash_filename_ = templates/proxy-need-start-stop.sh.in
md5sum = 455aaf369bf5141758dc57f2c0e67b08
[slave-db-manage.in]
_update_hash_filename_ = templates/slave-db-manage.in
md5sum = 6af818d4513561eea7def0f4f8d42164
......@@ -17,18 +17,11 @@
[{{ section('publish') }}]
recipe = slapos.cookbook:publish.serialised
-extends = publish-early
database-host = {{ host }}:{{ port }}
monitor-base-url = ${monitor-publish-parameters:monitor-base-url}
partition-path = ${buildout:directory}
receiver-port = ${dbjob-parameter:socat-port}
[publish-early]
recipe = slapos.cookbook:publish-early
-init =
database-list = {{ dumps(database_list) }}
[jinja2-template-base]
recipe = slapos.recipe.template:jinja2
mode = 644
......@@ -37,6 +30,22 @@ mode = 644
< = jinja2-template-base
mode = 755
[jinja2-template-script-base]
< = jinja2-template-executable
context =
key tmp_dir directory:tmp
key partition_dir buildout:directory
raw username {{ slapparameter_dict['repman-user'] }}
raw password {{ slapparameter_dict['repman-passwd'] }}
raw cluster_name {{ slapparameter_dict['cluster'] }}
raw secure_url {{ slapparameter_dict['repman-secure-url'] }}
raw mysql_conf ${directory:etc}/mysql/my.cnf
raw dash_bin {{ dash }}
raw jq_bin {{ jq_bin }}
raw curl_bin {{ curl_bin }}
raw mysql_bin {{ parameter_dict['mariadb-location'] }}/bin/mysql
${:extra-context}
[simplefile]
< = jinja2-template-base
template = inline:{{ '{{ content }}' }}
......@@ -92,6 +101,7 @@ heartbeat-user = {{ slapparameter_dict['heartbeat-user'] }}
[init-script]
< = jinja2-template-executable
extensions = jinja2.ext.do
# XXX: is there a better location ?
rendered = ${directory:etc}/mariadb_initial_setup.sql
template = {{ parameter_dict['template-mariadb-initial-setup'] }}
......@@ -106,20 +116,25 @@ init-script = ${init-script:rendered}
mysql_tzinfo_to_sql = ${binary-wrap-mysql_tzinfo_to_sql:wrapper-path}
[{{ section('update-mysql-script') }}]
< = jinja2-template-executable
< = jinja2-template-script-base
rendered = ${directory:scripts}/mariadb_update
init-password = ${directory:etc}/.init-passwd.done
upgrade-done = ${directory:lib}/mariadb-update-done
context =
extra-context =
key init_password_done :init-password
key upgrade_done :upgrade-done
key init_root_sql init-root-sql:rendered
key mysql_update update-mysql:output
raw mysql_conf ${directory:etc}/mysql/my.cnf
raw dash_bin {{ dash }}
raw mysql_bin {{ parameter_dict['mariadb-location'] }}/bin/mysql
key init_database_sql init-script:rendered
template = {{ parameter_dict['template-init-root-wrapper'] }}
[mysql-slave-db-cleanup]
< = jinja2-template-script-base
rendered = ${directory:bin}/manage_slave_db
extra-context =
raw database_name {{ slapparameter_dict['database-name'] }}
template = {{ parameter_dict['template-manage-db'] }}
[mysqld]
< = jinja2-template-executable
rendered = ${directory:bin}/mysqld
......@@ -285,6 +300,13 @@ name = mariadb-need-start
frequency = * * * * *
command = ${template-mysqld-need-start:rendered}
[{{ section('db-remove-cron-entry') }}]
recipe = slapos.cookbook:cron.d
cron-entries = ${cron:cron-entries}
name = slave-db-remove
frequency = */5 * * * *
command = ${mysql-slave-db-cleanup:rendered}
[template-mysqld-need-start]
< = jinja2-template-executable
rendered = ${directory:bin}/mysqld_need_start
......
{
"$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",
"type": "string",
"default": ""
},
"db_charset": {
"description": "Database Character set information. This parameter is set only if database is not created yet.",
"title": "Database Character set information",
"type": "string",
"enum": [
"latin1",
"latin2",
"latin5",
"latin7",
"ascii",
"latin5",
"utf8",
"utf8mb4",
"utf16",
"utf16le",
"utf32",
"binary",
"utf8_general_ci",
"utf8_unicode_ci",
"utf8_unicode_ci",
"utf8mb4_bin",
"utf8mb4_general_ci",
"utf8mb4_unicode_ci",
"latin1_general_ci",
"latin1_bin",
"ascii_general_ci",
"ascii_bin"
],
"default": ""
}
},
"title": "Input Parameters",
"type": "object"
}
......@@ -10,6 +10,15 @@
{% set cluster_list = [] -%}
{% set tag_list = ["gtidstrict", "bind", "pkg", "innodb", "noquerycache", "slow", "pfs", "linux", "readonly", "diskmonitor", "sqlerror", "compressbinlog", "bm4ci", "mroonga", "utctime", "readcommitted", "nohandshake"] -%}
{% set frontend_parameter_dict = slapparameter_dict.get('slave-frontend', {}) -%}
{% set database_slave_list = [] -%}
{% set db_name_list = [] -%}
{% macro password(name) -%}
[{{ name }}-password]
recipe = slapos.cookbook:generate.password
bytes = 12
{% endmacro -%}
[directory]
recipe = slapos.cookbook:mkdirectory
......@@ -58,6 +67,24 @@ template = inline:#!{{ bash_bin }}
rendered = ${directory:bin}/update-proxysql-config
mode = 755
{% 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'),
'password': instance_dict.get('db_password', '${' ~ instance_dict['slave_reference'] ~ '-password:passwd}'),
'slave_reference': instance_dict['slave_reference'],
'charset': instance_dict.get('db_charset', '')
} -%}
{% do database_slave_list.append(slave_dict) -%}
{% do db_name_list.append(slave_dict['name']) -%}
{{ password(instance_dict['slave_reference']) }}
{% endfor %}
[database-slave-information]
{% for slave_dict in database_slave_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",
......@@ -67,7 +94,6 @@ mode = 755
{% 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 database_list = parameter_dict.get('database-list', [{'name': 'repdb', 'user': 'user', 'password': 'insecure'}]) -%}
# Request mariadb instances
{% set db_amount = parameter_dict.get('database-amount', 2) -%}
{% if db_amount < 2 -%}
......@@ -77,12 +103,13 @@ mode = 755
{% for i in range(0, db_amount) -%}
{% do mariadb_dict.__setitem__('tcp-port', 2099 + (i * 100)) -%}
{% set section = 'request-mariadb-' ~ i -%}
{% set dbname = 'Mariadb-' ~ i -%}
{% set dbname = 'Mariadb-' ~ i %}
[{{ section }}]
<= request-common
software-type = mariadb
name = {{ dbname }}
name = {{ dbname ~ '-' ~ name}}
sla-computer_guid = {{ dumps(parameter_dict.get('-sla-' ~ i ~'-computer_guid', '')) }}
{% for key, value in mariadb_dict.items() -%}
config-{{ key }} = {{ dumps(value) }}
......@@ -91,12 +118,13 @@ config-monitor-passwd = ${publish-early:monitor-password}
config-root-password = ${publish-early:db-root-password}
config-repman-user = ${repman-parameter:username}
config-heartbeat-user = ${repman-parameter:heartbeat-user}
#config-repman-passwd = ${repman-parameter:password}
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 = {{ dumps(database_list) }}
config-database-list = !py!{{ database_slave_list }}
config-database-name = {{ dumps(db_list) }}
return =
database-host
receiver-port
......@@ -248,20 +276,27 @@ config-port = {{ '${' ~ name ~ '-cluster-parameter:proxy-port}' }}
{% do part_list.append('config-' ~ name) -%}
{% do cluster_list.append("{'name': '" ~ name ~ "', 'host': '" ~ ipv4 ~ "', 'port': '${" ~ name ~ "-cluster-parameter:proxy-admin-port}'}") -%}
{% set publish_database_list = [] -%}
{% set publish_database_v6_list = [] -%}
{% for database in database_list -%}
{% if database.get('user') -%}
{% do publish_database_list.append("mysql://" ~ database['user'] ~ ":" ~ database['password'] ~ "@" ~ ipv4 ~ ":${" ~ name ~ "-cluster-parameter:proxy-port}/" ~ database['name']) -%}
{% do publish_database_v6_list.append("mysql://" ~ database['user'] ~ ":" ~ database['password'] ~ "@[" ~ ip ~ "]:${" ~ name ~ "-cluster-parameter:proxy-port}/" ~ database['name']) -%}
{% else -%}
{% do publish_database_list.append("mysql://" ~ ipv4 ~ ":${" ~ name ~ "-cluster-parameter:proxy-port}/" ~ database['name']) -%}
{% do publish_database_v6_list.append("mysql://[" ~ ip ~ "]:${" ~ name ~ "-cluster-parameter:proxy-port}/" ~ database['name']) -%}
{% endif -%}
{% endfor -%}
{% do publish_dict.__setitem__(name ~ '-database-list', "!py!['" ~ publish_database_list | join("', '") ~ "']") -%}
{% do publish_dict.__setitem__(name ~ '-database-list-v6', "!py!['" ~ publish_database_v6_list | join("', '") ~ "']") -%}
[{{ name}}-publish-slave-information]
recipe = slapos.cookbook:switch-softwaretype
default = {{ 'dynamic-' ~name ~ '-publish-slave-information:rendered' }}
RootSoftwareInstance = ${:default}
[dynamic-{{ name}}-publish-slave-information]
recipe = slapos.recipe.template:jinja2
rendered = ${buildout:directory}/database-publish-slave-information.cfg
extensions = jinja2.ext.do
template = {{ template_publish_slave_information }}
context =
import json_module json
key proxy_port {{ name }}-cluster-parameter:proxy-port
raw eggs_directory {{ eggs_directory }}
raw develop_eggs_directory {{ develop_eggs_directory }}
raw ipv6 {{ ip }}
raw ipv4 {{ ipv4 }}
section slave_dict database-slave-information
{% do part_list.append(name ~ '-publish-slave-information') -%}
{% endfor -%}
[slap-configuration]
......@@ -469,6 +504,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 }}
[publish-connection-parameter]
<= monitor-publish
......@@ -477,6 +513,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 }}
{% for name, value in publish_dict.items() -%}
{{ name }} = {{ value }}
{% endfor %}
......@@ -546,6 +583,15 @@ command = ${template-proxysql-need-stop-start:rendered}
# Deploy replication-manager instance
#
#############################
[slap_connection]
# Kept for backward compatibility
computer_id = ${slap-connection:computer-id}
partition_id = ${slap-connection:partition-id}
server_url = ${slap-connection:server-url}
software_release_url = ${slap-connection:software-release-url}
key_file = ${slap-connection:key-file}
cert_file = ${slap-connection:cert-file}
[buildout]
extends =
{{ template_monitor }}
......
......@@ -56,6 +56,7 @@ extra-context =
<= jinja2-template-base
template= {{ template_repman_cfg }}
extra-context =
key slave_instance_list slap-configuration:slave-instance-list
raw gowork_bin {{ gowork_bin }}
raw haproxy_location {{ haproxy_location }}
raw nginx_bin {{ nginx_location }}/sbin/nginx
......@@ -71,6 +72,7 @@ extra-context =
raw proxysql_location {{ proxysql_location }}
raw template_repman_manager_sh {{ template_repman_manager_sh }}
raw template_proxy_need_stopstart {{ proxy_need_stop_start_template }}
raw template_publish_slave_information {{ template_publish_slave }}
[template-mariadb-parameters]
bash = {{ bash_location }}
......@@ -83,6 +85,7 @@ template-mariadb-init-root = {{ template_init_root_sql }}
template-init-root-wrapper = {{ template_init_root_wrapper }}
template-mysqld-wrapper = {{ template_mysqld_wrapper }}
template-mysqld-need-start = {{ mysqld_start_template }}
template-manage-db = {{ template_slave_db_manage }}
link-binary = {{ dumps(mariadb_link_binary) }}
check-computer-memory-binary = {{ bin_directory }}/check-computer-memory
bin-directory = {{ bin_directory }}
......
{% set part_list = [] -%}
[slap_connection]
# Kept for backward compatibility
computer_id = ${slap-connection:computer-id}
partition_id = ${slap-connection:partition-id}
server_url = ${slap-connection:server-url}
software_release_url = ${slap-connection:software-release-url}
key_file = ${slap-connection:key-file}
cert_file = ${slap-connection:cert-file}
{% for _, database in slave_dict.items() -%}
{% set slave_reference = database['slave_reference'] -%}
{% do part_list.append('publish-slave-' ~ slave_reference) -%}
[publish-slave-{{ slave_reference }}]
recipe = slapos.cookbook:publish
-slave-reference = {{ slave_reference }}
{% if database.get('user') -%}
url_v4 = {{ "mysql://" ~ database['user'] ~ ":" ~ database['password'] ~ "@" ~ ipv4 ~ ":" ~ proxy_port ~ '/' ~ database['name'] }}
url = {{ "mysql://" ~ database['user'] ~ ":" ~ database['password'] ~ "@[" ~ ipv6 ~ "]:" ~ proxy_port ~ '/' ~ database['name'] }}
{% else -%}
url = {{ "mysql://" ~ ipv4 ~ ":" ~ proxy_port ~ database['name'] }}
url_v6 = {{ "mysql://[" ~ ip ~ "]:" ~ proxy_port ~ database['name'] }}
{% endif -%}
{% endfor %}
[buildout]
eggs-directory = {{ eggs_directory }}
develop-eggs-directory = {{ develop_eggs_directory }}
offline = true
parts =
{% for part in part_list -%}
{{ ' %s' % part }}
{% endfor -%}
......@@ -87,6 +87,8 @@ context =
key template_init_root_sql mariadb-init-root-sql:target
key template_init_root_wrapper init-root-wrapper-in:target
key template_repman_cfg instance-repman.cfg:target
key template_publish_slave template-publish-slave-information:target
key template_slave_db_manage slave-db-manage.in:target
key unixodbc_location unixodbc:location
key sysbench_location sysbench:location
key proxysql_location proxysql:location
......@@ -126,6 +128,9 @@ link-binary =
[template-mariadb-initial-setup]
<= download-file
[template-publish-slave-information]
<= download-file
[template-my-cnf]
<= download-file
......@@ -146,3 +151,6 @@ link-binary =
[proxy-need-start-stop.sh.in]
<= download-file
[slave-db-manage.in]
<= download-file
......@@ -10,6 +10,15 @@
"request": "instance-repman-input-schema.json",
"response": "instance-repman-output-schema.json",
"index": 0
},
"default-slave": {
"title": "Replication Manager Database",
"description": "Replication Manager Database",
"software-type": "default",
"request": "instance-repman-slave-input-schema.json",
"response": "instance-repman-output-schema.json",
"shared": true,
"index": 1
}
}
}
......@@ -2,7 +2,7 @@
run_mysql () {
{{ mysql_bin }} --defaults-file="{{ mysql_conf }}" \
--protocol=socket -uroot -hlocalhost $@
--protocol=socket -uroot -hlocalhost "$@"
}
if [ ! -f "{{ init_password_done }}" ]; then
......@@ -29,4 +29,7 @@ if [ ! -f "{{ upgrade_done }}" ]; then
if [ $? -eq 0 ]; then
touch {{ upgrade_done }};
fi
else
# only create databases now
run_mysql < {{ init_database_sql }} || exit 1;
fi
......@@ -8,8 +8,26 @@ SOURCE {{ parameter_dict['mroonga-mariadb-install-sql'] }};
DROP FUNCTION IF EXISTS sphinx_snippets;
#CREATE FUNCTION sphinx_snippets RETURNS STRING SONAME 'ha_sphinx.so';
{% macro database(name, user, password) -%}
CREATE DATABASE IF NOT EXISTS `{{ name }}`;
CREATE DATABASE IF NOT EXISTS `repman_slave_definition`;
{% macro database(name, user, password, charset="") -%}
{% set charset_collate = "" -%}
{% set charset_dict = {'utf8_general_ci': 'utf8',
'utf8_unicode_ci': 'utf8',
'utf8mb4_bin': 'utf8mb4',
'utf8mb4_general_ci': 'utf8mb4',
'utf8mb4_unicode_ci': 'utf8mb4',
'latin1_general_ci': 'latin1',
'latin1_bin': 'latin1',
'ascii_general_ci': 'ascii',
'ascii_bin': 'ascii'} -%}
{% if charset %}
{% set charset_collate = "CHARACTER SET %s" % charset -%}
{% if charset_dict.get(charset) -%}
{% set charset_collate = "CHARACTER SET %s COLLATE %s" % (charset_dict[charset], charset) -%}
{% endif -%}
{% endif -%}
CREATE DATABASE IF NOT EXISTS `{{ name }}` {{ charset_collate }};
{% if user -%}
GRANT ALL PRIVILEGES ON `{{ name }}`.* TO `{{ user }}`@`%` IDENTIFIED BY '{{ password }}';
GRANT ALL PRIVILEGES ON `{{ name }}`.* TO `{{ user }}`@localhost IDENTIFIED BY '{{ password }}';
......@@ -18,5 +36,5 @@ GRANT ALL PRIVILEGES ON `{{ name }}`.* TO `{{ user }}`@'::' IDENTIFIED BY '{{ pa
{% endmacro -%}
{% for entry in parameter_dict['database-list'] -%}
{{ database(entry['name'], entry.get('user'), entry.get('password')) }}
{{ database(entry['name'], entry.get('user'), entry.get('password'), entry.get('charset')) }}
{% endfor -%}
#!/bin/bash
curl () {
{{ curl_bin }} -k --silent -H "Accept: application/json" "$@"
}
get_token () {
curl -s -X POST --data '{"username":"{{ username }}","password":"{{ password}}"}' {{ secure_url }}/api/login
}
run_mysql () {
{{ mysql_bin }} --defaults-file="{{ mysql_conf }}" \
--protocol=socket -uroot -hlocalhost "$@"
}
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')
# Only write or delete on master database else, we break replication.
if [ "$DATADIR" = "{{ partition_dir }}" ]; then
cat << EOF > {{ tmp_dir }}/.script.sql
use repman_slave_definition;
CREATE TABLE IF NOT EXISTS \`slave\` (
\`name\` varchar(20) NOT NULL,
\`state\` tinyint(1) DEFAULT NULL,
PRIMARY KEY (\`name\`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
UPDATE \`slave\` set \`state\`=false;
{% for name in database_name.split(' ') -%}
REPLACE INTO \`slave\` VALUES ('{{ name }}', true);
{% endfor -%}
EOF
# Update requested slaves database
run_mysql < {{ tmp_dir }}/.script.sql
rm -f {{ tmp_dir }}/.script.sql
DBNAME=$(run_mysql -Be "SELECT name FROM repman_slave_definition.slave WHERE state=false");
RET=$?
if [ ! "$RET" = "0" ]; then
echo "Mysql command failed: $DBNAME"
exit $RET
fi
for NAME in $DBNAME; do
if [ ! "$NAME" = "name" ]; then
echo "Deleting database $NAME..."
run_mysql -e "DROP DATABASE IF EXISTS $NAME";
run_mysql -e "DELETE FROM repman_slave_definition.slave WHERE name='$NAME'";
echo "Done."
fi
done
fi
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