diff --git a/config.yaml b/config.yaml index 1f6ac32..001aa38 100644 --- a/config.yaml +++ b/config.yaml @@ -43,4 +43,8 @@ options: The IP address and netmask of the 'access' network (e.g., 192.168.0.0/24) . This network will be used for access to database services. + prefer-ipv6: + default: false + type: boolean + description: "Enable IPv6." diff --git a/hooks/mysql.py b/hooks/mysql.py index 91f65a7..326f736 100644 --- a/hooks/mysql.py +++ b/hooks/mysql.py @@ -10,7 +10,8 @@ from string import upper from charmhelpers.core.host import pwgen, mkdir, write_file from charmhelpers.core.hookenv import unit_get, service_name from charmhelpers.core.hookenv import config as config_get -from charmhelpers.fetch import apt_install, filter_installed_packages +from charmhelpers.fetch import apt_install, apt_update, \ + filter_installed_packages from charmhelpers.contrib.peerstorage import ( peer_store, @@ -19,6 +20,7 @@ from charmhelpers.contrib.peerstorage import ( try: import MySQLdb except ImportError: + apt_update(fatal=True) apt_install(filter_installed_packages(['python-mysqldb']), fatal=True) import MySQLdb @@ -179,7 +181,9 @@ def configure_db(hostname, username, admin=False): ''' Configure access to database for username from hostname ''' - if hostname != unit_get('private-address'): + if config_get('prefer-ipv6'): + remote_ip = hostname + elif hostname != unit_get('private-address'): remote_ip = socket.gethostbyname(hostname) else: remote_ip = '127.0.0.1' diff --git a/hooks/percona_hooks.py b/hooks/percona_hooks.py index 6a4e164..aa6fbd6 100755 --- a/hooks/percona_hooks.py +++ b/hooks/percona_hooks.py @@ -3,6 +3,8 @@ import sys import os +import socket + from charmhelpers.core.hookenv import ( Hooks, UnregisteredHookError, log, @@ -27,7 +29,8 @@ from charmhelpers.fetch import ( add_source, ) from charmhelpers.contrib.peerstorage import ( - peer_echo + peer_echo, + peer_store, ) from percona_utils import ( PACKAGES, @@ -57,6 +60,7 @@ from charmhelpers.payload.execd import execd_preinstall from charmhelpers.contrib.network.ip import ( is_address_in_network, get_address_in_network, + get_ipv6_addr, ) hooks = Hooks() @@ -88,6 +92,11 @@ def render_config(clustered=False, hosts=[]): 'sst_password': get_mysql_password(username='sstuser', password=config('sst-password')) } + + if config('prefer-ipv6'): + context['bind_address'] = '::' + context['wsrep_provider_options'] = 'gmcast.listen_addr=tcp://:::4567;' + context.update(parse_config()) write_file(path=MY_CNF, content=render_template(os.path.basename(MY_CNF), context), @@ -118,7 +127,9 @@ def config_changed(): @hooks.hook('cluster-relation-changed') def cluster_changed(): - peer_echo() + if config('prefer-ipv6'): + peer_store('private-address', get_ipv6_addr()) + peer_store('hostname', socket.gethostname()) config_changed() @@ -135,10 +146,16 @@ def db_changed(relation_id=None, unit=None, admin=None): relation_clear(relation_id) return - if is_clustered(): - db_host = config('vip') + if config('prefer-ipv6'): + if is_clustered(): + db_host = '[%s]' % config('vip') + else: + db_host = '[%s]' % get_ipv6_addr() else: - db_host = unit_get('private-address') + if is_clustered(): + db_host = config('vip') + else: + db_host = unit_get('private-address') if admin not in [True, False]: admin = relation_type() == 'db-admin' @@ -172,10 +189,16 @@ def shared_db_changed(relation_id=None, unit=None): settings = relation_get(unit=unit, rid=relation_id) - if is_clustered(): - db_host = config('vip') + if config('prefer-ipv6'): + if is_clustered(): + db_host = '[%s]' % config('vip') + else: + db_host = '[%s]' % get_ipv6_addr() else: - db_host = unit_get('private-address') + if is_clustered(): + db_host = config('vip') + else: + db_host = unit_get('private-address') access_network = config('access-network') @@ -256,11 +279,17 @@ def ha_relation_joined(): log('Insufficient VIP information to configure cluster') sys.exit(1) - resources = {'res_mysql_vip': 'ocf:heartbeat:IPaddr2'} - resource_params = { - 'res_mysql_vip': 'params ip="%s" cidr_netmask="%s" nic="%s"' % - (vip, vip_cidr, vip_iface), - } + if config('prefer-ipv6'): + res_mysql_vip = 'ocf:heartbeat:IPv6addr' + vip_params = 'params ipv6addr="%s" cidr_netmask="%s" nic="%s"' % \ + (vip, vip_cidr, vip_iface) + else: + res_mysql_vip = 'ocf:heartbeat:IPaddr2' + vip_params = 'params ip="%s" cidr_netmask="%s" nic="%s"' % \ + (vip, vip_cidr, vip_iface) + + resources = {'res_mysql_vip': res_mysql_vip} + resource_params = {'res_mysql_vip': vip_params} groups = {'grp_percona_cluster': 'res_mysql_vip'} for rel_id in relation_ids('ha'): diff --git a/hooks/percona_utils.py b/hooks/percona_utils.py index f0a13f4..4b1591b 100644 --- a/hooks/percona_utils.py +++ b/hooks/percona_utils.py @@ -1,5 +1,4 @@ ''' General utilities for percona ''' -import subprocess from subprocess import Popen, PIPE import socket import os @@ -13,11 +12,15 @@ from charmhelpers.core.hookenv import ( relation_get, relation_set, local_unit, + config, ) from charmhelpers.fetch import ( apt_install, filter_installed_packages, ) +from charmhelpers.contrib.network.ip import ( + get_ipv6_addr, +) from mysql import get_mysql_root_password, MySQLHelper try: @@ -75,6 +78,14 @@ def render_template(template_name, context, template_dir=TEMPLATES_DIR): # TODO: goto charm-helpers (I use this everywhere) def get_host_ip(hostname=None): + if config('prefer-ipv6'): + private_address = get_ipv6_addr() + hostname = socket.gethostname() + host_map = {} + host_map[private_address] = hostname + render_hosts(host_map) + return hostname + hostname = hostname or unit_get('private-address') try: # Test to see if already an IPv4 address @@ -90,10 +101,20 @@ def get_host_ip(hostname=None): def get_cluster_hosts(): hosts = [get_host_ip()] + hosts_map = {} for relid in relation_ids('cluster'): for unit in related_units(relid): - hosts.append(get_host_ip(relation_get('private-address', - unit, relid))) + private_address = relation_get('private-address', unit, relid) + if config('prefer-ipv6'): + hostname = relation_get('hostname', unit, relid) + if not hostname or hostname in hosts: + continue + hosts_map[private_address] = hostname + hosts.append(hostname) + else: + hosts.append(get_host_ip(private_address)) + + render_hosts(hosts_map) return hosts SQL_SST_USER_SETUP = "GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.*" \ @@ -133,3 +154,18 @@ def relation_clear(r_id=None): settings[setting] = None relation_set(relation_id=r_id, **settings) + + +def render_hosts(map): + if len(map) == 0: + return + + with open('/etc/hosts', 'a+r') as hosts: + for ip, hostname in map.items(): + if not ip or not hostname: + continue + for line in hosts: + if line.startswith(ip): + break + else: + hosts.write(ip + ' ' + hostname + '\n') diff --git a/templates/my.cnf b/templates/my.cnf index 37e750b..e7293c2 100644 --- a/templates/my.cnf +++ b/templates/my.cnf @@ -1,6 +1,14 @@ # Juju managed file - don't change as charm will overwrite your changed! [mysqld] +{% if bind_address %} +bind-address = {{ bind_address }} +{% endif %} + +{% if wsrep_provider_options %} +wsrep_provider_options = {{ wsrep_provider_options }} +{% endif %} + datadir=/var/lib/mysql user=mysql