From fa0e739e7fa58af41ad75b8ab0c03cd22e4cd055 Mon Sep 17 00:00:00 2001 From: Andreas Schwarz Date: Thu, 21 Oct 2021 16:31:15 +0200 Subject: [PATCH 1/2] add check feature 'on_exit' allows to forcefully withdraw/advertise ip prefixes on exit keep state from latest checks by default --- README.rst | 11 ++++++ anycast_healthchecker/__init__.py | 1 + anycast_healthchecker/healthchecker.py | 50 ++++++++++++++++++++++++++ anycast_healthchecker/main.py | 5 +-- anycast_healthchecker/utils.py | 36 ++++++++++++++++++- 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 7c5e658..04c8dbd 100644 --- a/README.rst +++ b/README.rst @@ -256,6 +256,7 @@ Below are the default settings for all service checks, see `Configuring checks f :check_fail: 2 :check_disabled: true :on_disabled: withdraw +:on_exit: withdraw :ip_check_disabled: false :custom_bird_reconfigure_cmd_timeout: 2 @@ -447,6 +448,7 @@ Here are few examples:: check_rise = 2 check_disabled = false on_disabled = withdraw + on_exit = none ip_prefix = 10.52.12.1/32 [foo6.bar.com] @@ -456,6 +458,7 @@ Here are few examples:: check_fail = 2 check_disabled = false on_disabled = withdraw + on_exit = none ip_prefix = fd12:aba6:57db:ffff::1/128 ip_check_disabled = false @@ -489,6 +492,14 @@ A service is considered HEALTHY after these many consecutive successful health c What to do when check is disabled, either ``withdraw`` or ``advertise`` +* **on_exit** Defaults to **none** + +What to do when anycast-healthchecker exits, either ``none`` or ``withdraw`` or ``advertise``. + + * ``none`` keep state how it was from checks + * ``advertise`` ensure the ip_prefix to be advertised + * ``withdraw`` ensure the ip_prefix to be withdrawn + * **ip_prefix** Unset by default IP prefix associated with the service. It **must be** assigned to the interface set in ``interface`` parameter unless ``ip_check_disabled`` is set to ``true``. Prefix length is optional and defaults to 32 for IPv4 addresses and to 128 for IPv6 addresses. diff --git a/anycast_healthchecker/__init__.py b/anycast_healthchecker/__init__.py index 8aa86e2..a5138b8 100644 --- a/anycast_healthchecker/__init__.py +++ b/anycast_healthchecker/__init__.py @@ -18,6 +18,7 @@ 'check_fail': 2, 'check_disabled': 'true', 'on_disabled': 'withdraw', + 'on_exit': 'none', 'ip_check_disabled': 'false', 'custom_bird_reconfigure_cmd_timeout': 2, }, diff --git a/anycast_healthchecker/healthchecker.py b/anycast_healthchecker/healthchecker.py index 2fc87a6..d02ece9 100644 --- a/anycast_healthchecker/healthchecker.py +++ b/anycast_healthchecker/healthchecker.py @@ -12,9 +12,12 @@ from anycast_healthchecker.utils import (SERVICE_OPTIONS_TYPE, get_ip_prefixes_from_bird, get_ip_prefixes_from_config, + get_ip_prefixes_from_config_on_exit, reconfigure_bird, write_temp_bird_conf, archive_bird_conf, + AddOperation, + DeleteOperation, ServiceCheckDiedError, run_custom_bird_reconfigure) @@ -226,3 +229,50 @@ def run(self): self.bird_configuration[ip_version]['reconfigure_cmd']) else: run_custom_bird_reconfigure(operation) + + def on_exit(self): + for ip_version in self.bird_configuration: + config_file = self.bird_configuration[ip_version]['config_file'] + ip_prefixes_in_bird = get_ip_prefixes_from_bird(config_file) + _ip_prefix_withdraw = get_ip_prefixes_from_config_on_exit( + self.config, + self.services, + ip_version, + 'withdraw') + _ip_prefix_advertise = get_ip_prefixes_from_config_on_exit( + self.config, + self.services, + ip_version, + 'advertise') + + pprint(vars(_ip_prefix_advertise)) + pprint(vars(_ip_prefix_withdraw)) + + for withdraw_ip_prefix in set(ip_prefixes_in_bird).intersection(_ip_prefix_withdraw): + _delete_operation = DeleteOperation( + name='on_exit', + ip_prefix=withdraw_ip_prefix, + ip_version=ip_version, + bird_reconfigure_timeout=( + self.config['daemon'].get('custom_bird_reconfigure_cmd_timeout') + ), + bird_reconfigure_cmd=( + self.config['daemon'].get('custom_bird_reconfigure_cmd') + ) + ) + self._update_bird_conf_file(_delete_operation) + + for add_ip_prefix in set(_ip_prefix_advertise).difference(ip_prefixes_in_bird): + _add_operation = AddOperation( + name='on_exit', + ip_prefix=add_ip_prefix, + ip_version=ip_version, + bird_reconfigure_timeout=( + self.config['daemon'].get('custom_bird_reconfigure_cmd_timeout') + ), + bird_reconfigure_cmd=( + self.config['daemon'].get('custom_bird_reconfigure_cmd') + ) + ) + self._update_bird_conf_file(_add_operation) + diff --git a/anycast_healthchecker/main.py b/anycast_healthchecker/main.py index fd97ecc..801df53 100644 --- a/anycast_healthchecker/main.py +++ b/anycast_healthchecker/main.py @@ -79,8 +79,10 @@ def main(): pidfile = config.get('daemon', 'pidfile') update_pidfile(pidfile) + checker = healthchecker.HealthChecker(config, bird_configuration) + # Register our shutdown handler to various termination signals. - shutdown_handler = partial(shutdown, pidfile) + shutdown_handler = partial(shutdown, pidfile, checker) signal.signal(signal.SIGHUP, shutdown_handler) signal.signal(signal.SIGTERM, shutdown_handler) signal.signal(signal.SIGABRT, shutdown_handler) @@ -93,7 +95,6 @@ def main(): ip_prefixes_sanity_check(config, bird_configuration) # Create our master process. - checker = healthchecker.HealthChecker(config, bird_configuration) logger.info("starting %s version %s", PROGRAM_NAME, __version__) checker.run() diff --git a/anycast_healthchecker/utils.py b/anycast_healthchecker/utils.py index f0a892e..2b7b48e 100644 --- a/anycast_healthchecker/utils.py +++ b/anycast_healthchecker/utils.py @@ -34,6 +34,7 @@ 'check_fail': 'getint', 'check_disabled': 'getboolean', 'on_disabled': 'get', + 'on_exit': 'get', 'ip_prefix': 'get', 'interface': 'get', 'ip_check_disabled': 'getboolean', @@ -144,6 +145,27 @@ def get_ip_prefixes_from_config(config, services, ip_version): return ip_prefixes +def get_ip_prefixes_from_config_on_exit(config, services, ip_version, on_exit_status): + """Build a set of IP prefixes found in service configuration files. + + Arguments: + config (obg): A configparser object which holds our configuration. + services (list): A list of section names which are the name of the + service checks. + ip_version (int): IP protocol version + + Returns: + A set of IP prefixes. + + """ + ip_prefixes = set() + + for service in services: + if config.get(service, 'on_exit') == on_exit_status: + ip_prefix = ipaddress.ip_network(config.get(service, 'ip_prefix')) + if ip_prefix.version == ip_version: + ip_prefixes.add(ip_prefix.with_prefixlen) + return ip_prefixes def ip_prefixes_sanity_check(config, bird_configuration): """Sanity check on IP prefixes. @@ -414,6 +436,17 @@ def service_configuration_check(config): val=config.get(service, 'on_disabled'))) raise ValueError(msg) + + if (config.get(service, 'on_exit') != 'withdraw' and + config.get(service, 'on_exit') != 'advertise' and + config.get(service, 'on_exit') != 'none'): + msg = ("'on_exit' option has invalid value ({val}) for " + "service check {name}, 'on_exit option should be set " + "either to 'withdraw' or to 'advertise' or to 'none'" + .format(name=service, + val=config.get(service, 'on_exit'))) + raise ValueError(msg) + ip_prefixes.append(config.get(service, 'ip_prefix')) if not valid_ip_prefix(config.get(service, 'ip_prefix')): @@ -923,7 +956,7 @@ def write_pid(pidfile): sys.exit("failed to write pidfile:{e}".format(e=exc)) -def shutdown(pidfile, signalnb=None, frame=None): +def shutdown(pidfile, healthchecker, signalnb=None, frame=None): """Clean up pidfile upon shutdown. Notice: @@ -942,6 +975,7 @@ def shutdown(pidfile, signalnb=None, frame=None): """ log = logging.getLogger(PROGRAM_NAME) log.info("received %s at %s", signalnb, frame) + healthchecker.on_exit() log.info("going to remove pidfile %s", pidfile) # no point to catch possible errors when we delete the pid file os.unlink(pidfile) From 7f6b8a8852f0d26199a750b0da6035f25899fa2f Mon Sep 17 00:00:00 2001 From: Andreas Schwarz Date: Fri, 22 Oct 2021 12:07:12 +0200 Subject: [PATCH 2/2] remove debug output --- anycast_healthchecker/healthchecker.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/anycast_healthchecker/healthchecker.py b/anycast_healthchecker/healthchecker.py index d02ece9..19446a0 100644 --- a/anycast_healthchecker/healthchecker.py +++ b/anycast_healthchecker/healthchecker.py @@ -245,9 +245,6 @@ def on_exit(self): ip_version, 'advertise') - pprint(vars(_ip_prefix_advertise)) - pprint(vars(_ip_prefix_withdraw)) - for withdraw_ip_prefix in set(ip_prefixes_in_bird).intersection(_ip_prefix_withdraw): _delete_operation = DeleteOperation( name='on_exit',