charm-percona-cluster/hooks/percona_hooks.py

284 lines
8.4 KiB
Python
Executable File

#!/usr/bin/python
# TODO: Support relevant configuration options
# TODO: Add db and db-admin hooks for mysql compat
# TODO: Support changes to root and sstuser passwords
import sys
import os
import glob
from charmhelpers.core.hookenv import (
Hooks, UnregisteredHookError,
log,
relation_get,
relation_set,
relation_ids,
unit_get,
config,
service_name,
remote_unit,
relation_type
)
from charmhelpers.core.host import (
service_restart,
file_hash
)
from charmhelpers.fetch import (
apt_update,
apt_install,
)
from percona_utils import (
PACKAGES,
MY_CNF,
setup_percona_repo,
render_template,
get_host_ip,
get_cluster_hosts,
configure_sstuser,
seeded, mark_seeded,
configure_mysql_root_password,
relation_clear,
)
from mysql import get_mysql_password
from charmhelpers.contrib.hahelpers.cluster import (
peer_units,
oldest_peer,
eligible_leader,
is_clustered,
is_leader
)
from mysql import configure_db
from unison import (
ssh_authorized_peers,
sync_to_peers
)
hooks = Hooks()
@hooks.hook('install')
def install():
setup_percona_repo()
configure_mysql_root_password(config('root-password'))
render_config() # Render base configuation (no cluster)
apt_update(fatal=True)
apt_install(PACKAGES, fatal=True)
configure_sstuser(config('sst-password'))
def render_config(clustered=False, hosts=[]):
if not os.path.exists(os.path.dirname(MY_CNF)):
os.makedirs(os.path.dirname(MY_CNF))
with open(MY_CNF, 'w') as conf:
context = {
'cluster_name': 'juju_cluster',
'private_address': get_host_ip(),
'clustered': clustered,
'cluster_hosts': ",".join(hosts),
'sst_password': get_mysql_password(username='sstuser',
password=config('sst-password'))
}
conf.write(render_template(os.path.basename(MY_CNF), context))
# TODO: set 0640 and change group to mysql if avaliable
os.chmod(MY_CNF, 0644)
@hooks.hook('cluster-relation-joined')
def cluster_relation_joined():
ssh_authorized_peers(peer_interface='cluster',
user='juju_ssh', group='root',
ensure_local_user=True)
@hooks.hook('cluster-relation-changed')
@hooks.hook('upgrade-charm')
@hooks.hook('config-changed')
def cluster_changed():
ssh_authorized_peers(peer_interface='cluster',
user='juju_ssh', group='root',
ensure_local_user=True)
hosts = get_cluster_hosts()
clustered = len(hosts) > 1
pre_hash = file_hash(MY_CNF)
render_config(clustered, hosts)
if file_hash(MY_CNF) != pre_hash:
oldest = oldest_peer(peer_units())
if clustered and not oldest and not seeded():
# Bootstrap node into seeded cluster
service_restart('mysql')
mark_seeded()
elif not clustered:
# Restart with new configuration
service_restart('mysql')
if eligible_leader(LEADER_RES):
sync_files()
def sync_files():
''' Sync shared charm state files to all peers '''
files = glob.glob('/var/lib/charm/{}/*'.format(service_name()))
sync_to_peers(peer_interface='cluster',
user='juju_ssh', paths=files)
LEADER_RES = 'res_mysql_vip'
@hooks.hook('db-relation-changed')
@hooks.hook('db-admin-relation-changed')
def db_changed():
if not eligible_leader(LEADER_RES):
log('Service is peered, clearing db relation'
' as this service unit is not the leader')
relation_clear()
return
if is_clustered():
db_host = config('vip')
else:
db_host = unit_get('private-address')
admin = relation_type() == 'db-admin-relation-changed'
database_name, _ = remote_unit().split("/")
username = database_name # TODO: is this OK? mysql used a random username
password = configure_db(relation_get('private-address'),
database_name,
username,
admin=admin)
relation_set(database=database_name,
user=username,
password=password,
host=db_host)
sync_files()
@hooks.hook('shared-db-relation-changed')
def shared_db_changed():
if not eligible_leader(LEADER_RES):
log('Service is peered, clearing shared-db relation'
' as this service unit is not the leader')
relation_clear()
return
settings = relation_get()
if is_clustered():
db_host = config('vip')
else:
db_host = unit_get('private-address')
singleset = set([
'database',
'username',
'hostname'
])
if singleset.issubset(settings):
# Process a single database configuration
password = configure_db(settings['hostname'],
settings['database'],
settings['username'])
relation_set(db_host=db_host,
password=password)
else:
# Process multiple database setup requests.
# from incoming relation data:
# nova_database=xxx nova_username=xxx nova_hostname=xxx
# quantum_database=xxx quantum_username=xxx quantum_hostname=xxx
# create
#{
# "nova": {
# "username": xxx,
# "database": xxx,
# "hostname": xxx
# },
# "quantum": {
# "username": xxx,
# "database": xxx,
# "hostname": xxx
# }
#}
#
databases = {}
for k, v in settings.iteritems():
db = k.split('_')[0]
x = '_'.join(k.split('_')[1:])
if db not in databases:
databases[db] = {}
databases[db][x] = v
return_data = {}
for db in databases:
if singleset.issubset(databases[db]):
return_data['_'.join([db, 'password'])] = \
configure_db(databases[db]['hostname'],
databases[db]['database'],
databases[db]['username'])
if len(return_data) > 0:
relation_set(**return_data)
relation_set(db_host=db_host)
sync_files()
@hooks.hook('ha-relation-joined')
def ha_relation_joined():
vip = config('vip')
vip_iface = config('vip_iface')
vip_cidr = config('vip_cidr')
corosync_bindiface = config('ha-bindiface')
corosync_mcastport = config('ha-mcastport')
if None in [vip, vip_cidr, vip_iface]:
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),
}
groups = {'grp_percona_cluster': 'res_mysql_vip'}
for rel_id in relation_ids('ha'):
relation_set(rid=rel_id,
corosync_bindiface=corosync_bindiface,
corosync_mcastport=corosync_mcastport,
resources=resources,
resource_params=resource_params,
groups=groups)
@hooks.hook('ha-relation-changed')
def ha_relation_changed():
clustered = relation_get('clustered')
if (clustered and is_leader(LEADER_RES)):
log('Cluster configured, notifying other services')
# Tell all related services to start using the VIP
for r_id in relation_ids('shared-db'):
relation_set(rid=r_id,
db_host=config('vip'))
for r_id in relation_ids('db'):
relation_set(rid=r_id,
host=config('vip'))
for r_id in relation_ids('db-admin'):
relation_set(rid=r_id,
host=config('vip'))
else:
# Clear any settings data for non-leader units
log('Cluster configured, not leader, clearing relation data')
for r_id in relation_ids('shared-db'):
relation_clear(r_id)
for r_id in relation_ids('db'):
relation_clear(r_id)
for r_id in relation_ids('db-admin'):
relation_clear(r_id)
def main():
try:
hooks.execute(sys.argv)
except UnregisteredHookError as e:
log('Unknown hook {} - skipping.'.format(e))
if __name__ == '__main__':
main()