import os import re from subprocess import check_output from charms.reactive import set_flag, when, when_not, hook from charms.reactive.flags import clear_flag from charmhelpers.core.templating import render from charmhelpers.fetch import apt_update, apt_install from charmhelpers.core.hookenv import config, is_leader from charmhelpers.core.host import service_restart from charmhelpers.core.host import service_pause, service_resume from charms.layer import status SYSCTL_FILE = os.path.join(os.sep, 'etc', 'sysctl.d', '50-keepalived.conf') KEEPALIVED_CONFIG_FILE = os.path.join(os.sep, 'etc', 'keepalived', 'keepalived.conf') @when_not('keepalived.package.installed') def install_keepalived_package(): ''' Install keepalived package ''' status.maintenance('Installing keepalived') apt_update(fatal=True) apt_install(['keepalived', 'net-tools'], fatal=True) set_flag('keepalived.package.installed') def default_route_interface(): ''' Returns the network interface of the system's default route ''' default_interface = None cmd = ['route'] output = check_output(cmd).decode('utf8') for line in output.split('\n'): if 'default' in line: default_interface = line.split(' ')[-1] return default_interface @when('keepalived.package.installed') @when_not('keepalived.started') @when_not('upgrade.series.in-progress') def configure_keepalived_service(): ''' Set up the keepalived service ''' virtual_ip = config().get('virtual_ip') if virtual_ip == "": status.blocked('Please configure virtual ips') return network_interface = config().get('network_interface') if network_interface == "": network_interface = default_route_interface() context = {'is_leader': is_leader(), 'virtual_ip': virtual_ip, 'network_interface': network_interface, 'router_id': config().get('router_id'), 'service_port': config().get('port'), 'healthcheck_interval': config().get('healthcheck_interval'), } render(source='keepalived.conf', target=KEEPALIVED_CONFIG_FILE, context=context, perms=0o644) service_restart('keepalived') render(source='50-keepalived.conf', target=SYSCTL_FILE, context={'sysctl': {'net.ipv4.ip_nonlocal_bind': 1}}, perms=0o644) service_restart('procps') status.active('VIP ready') set_flag('keepalived.started') @when('config.changed') def reconfigure(): clear_flag('keepalived.started') @when('website.available', 'keepalived.started') def website_available(website): ipaddr = re.split('/', config()['virtual_ip'])[0] vip_hostname = config()['vip_hostname'] hostname = vip_hostname if vip_hostname else ipaddr # a port to export over a relation # TODO: this could be more tightly coupled with the actual # service via a relation port = config()['port'] website.configure(port=port, private_address=ipaddr, hostname=hostname) @when('loadbalancer.available', 'keepalived.started') def loadbalancer_available(loadbalancer): ''' Send the virtual IP ''' ipaddr = re.split('/', config()['virtual_ip'])[0] port = config()['port'] loadbalancer.set_address_port(ipaddr, port) @hook('upgrade-charm') def upgrade_charm(): clear_flag('keepalived.started') @hook('pre-series-upgrade') def pre_series_upgrade(): service_pause('keepalived') service_pause('procps') status.blocked('Series upgrade in progress') @hook('post-series-upgrade') def post_series_upgrade(): service_resume('keepalived') service_resume('procps') clear_flag('keepalived.started')