From 57f0075acd83e2ba00da7270b1062db4263de1e8 Mon Sep 17 00:00:00 2001 From: "Peter Klein (Let's make sense GmbH)" Date: Sun, 26 Nov 2017 19:03:04 +0100 Subject: [PATCH 1/5] Add monitor failover function --- hetzner/failover.py | 28 ++++++++++++++++++++++++++++ hetznerctl | 30 +++++++++++++++--------------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/hetzner/failover.py b/hetzner/failover.py index 2ebdd7f..64ce39b 100644 --- a/hetzner/failover.py +++ b/hetzner/failover.py @@ -1,3 +1,6 @@ +import re +import subprocess + from hetzner import RobotError __all__ = ['Failover', 'FailoverManager'] @@ -58,3 +61,28 @@ def set(self, ip, new_destination): result = self.conn.post('/failover/%s' % ip, {'active_server_ip': new_destination}) return Failover(result.get('failover')) + + + def monitor(self): + """Check if container with failover IP is running on host + and if IP is not mapped to host change settings + """ + msgs = [] + ips = self._get_active_ips() + print(ips) + return msgs + + def _get_active_ips(self): + ips = [] + try: + out = subprocess.check_output(["lxc-ls", "--active", "-fF", "IPV4"]) + except subprocess.CalledProcessError as e: + raise RobotError(str(e)) + except Exception as e: + raise RobotError(str(e)) + else: + [ips.extend([ip.strip() for ip in line.strip().split(',')]) + for line in out.split('\n') + if re.search(r'\d+\.\d+\.\d+\.\d', line)] + return ips + diff --git a/hetznerctl b/hetznerctl index 77fb109..20bf09d 100755 --- a/hetznerctl +++ b/hetznerctl @@ -233,6 +233,9 @@ class Failover(SubCommand): make_option('-s', '--set', dest='setfailover', action='store_true', default=False, help="Assign failover IP address to server"), + make_option('-m', '--monitor', dest='monitorfailover', + action='store_true', default=False, + help="Assign failover IP address to server"), make_option('ip', nargs='?', default=None, help="Failover IP address to assign"), make_option('destination', nargs='?', default=None, @@ -240,28 +243,25 @@ class Failover(SubCommand): ] def execute(self, robot, parser, args): + msgs = [] if args.setfailover: - errs = [] if not args.ip: - errs.append("Error: you need to set the failover IP you" + msgs.append("Error: you need to set the failover IP you" " want to assign. Option 'ip'") if not args.destination: - errs.append("Error: you need to set the new destination of" + msgs.append("Error: you need to set the new destination of" " the failover IP. Option 'dest'") - if len(errs) > 0: - for err in errs: - self.putline(err) - else: + if len(msgs) == 0: failover = robot.failover.set(args.ip, args.destination) - self.putline("Failover IP successfully assigned to new" - " destination") - self.putline(str(failover)) + msgs.append("Failover IP successfully assigned to new" + " destination") + msgs.append(failover) + elif args.monitorfailover: + msgs.extend(robot.failover.monitor()) else: - failovers = robot.failover.list() - if len(failovers) > 0: - self.putline("Found %s failover IPs" % len(failovers)) - for failover in failovers.values(): - self.putline(str(failover)) + msgs = robot.failover.list() + for msg in msgs.values(): + self.putline(str(msg)) class Admin(SubCommand): From ed7cb7727afe75b2a6475613945661000704d1e3 Mon Sep 17 00:00:00 2001 From: "Peter Klein (Let's make sense GmbH)" Date: Sun, 26 Nov 2017 23:32:09 +0100 Subject: [PATCH 2/5] Add monitor failover function --- hetzner/failover.py | 24 +++++++++++++++++++++--- hetznerctl | 5 +++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/hetzner/failover.py b/hetzner/failover.py index 64ce39b..48c2be5 100644 --- a/hetzner/failover.py +++ b/hetzner/failover.py @@ -62,14 +62,22 @@ def set(self, ip, new_destination): % ip, {'active_server_ip': new_destination}) return Failover(result.get('failover')) - def monitor(self): """Check if container with failover IP is running on host and if IP is not mapped to host change settings """ msgs = [] - ips = self._get_active_ips() - print(ips) + failovers = self.list() + if len(failovers) > 0: + ips = self._get_active_ips() + host_ip = self._get_host_ip() + for failover_ip, failover in failovers.items(): + if failover_ip in ips and failover.active_server_ip != host_ip: + new_failover = self.set(failover_ip, host_ip) + if new_failover: + msgs.append("Failover IP successfully assigned to new" + " destination") + msgs.append(str(failover)) return msgs def _get_active_ips(self): @@ -86,3 +94,13 @@ def _get_active_ips(self): if re.search(r'\d+\.\d+\.\d+\.\d', line)] return ips + def _get_host_ip(self): + try: + host_ip = subprocess.check_output(["hostname", "--ip-address"]) + except subprocess.CalledProcessError as e: + raise RobotError(str(e)) + except Exception as e: + raise RobotError(str(e)) + else: + return host_ip.strip() + diff --git a/hetznerctl b/hetznerctl index 20bf09d..4f4467a 100755 --- a/hetznerctl +++ b/hetznerctl @@ -260,8 +260,9 @@ class Failover(SubCommand): msgs.extend(robot.failover.monitor()) else: msgs = robot.failover.list() - for msg in msgs.values(): - self.putline(str(msg)) + if len(msgs) > 0: + for msg in msgs.values(): + self.putline(str(msg)) class Admin(SubCommand): From 38d6f29b055688742fabc08cf88b05e389a427c3 Mon Sep 17 00:00:00 2001 From: "Peter Klein (Let's make sense GmbH)" Date: Mon, 27 Nov 2017 11:45:55 +0100 Subject: [PATCH 3/5] Corrected: messages are list not dictionary --- hetznerctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hetznerctl b/hetznerctl index 4f4467a..3d94728 100755 --- a/hetznerctl +++ b/hetznerctl @@ -261,7 +261,7 @@ class Failover(SubCommand): else: msgs = robot.failover.list() if len(msgs) > 0: - for msg in msgs.values(): + for msg in msgs: self.putline(str(msg)) From 57811bb72614b99346fae5a2425478400cff6bdd Mon Sep 17 00:00:00 2001 From: "Peter Klein (Let's make sense GmbH)" Date: Mon, 27 Nov 2017 11:55:07 +0100 Subject: [PATCH 4/5] Set complete failover information as output message --- hetznerctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hetznerctl b/hetznerctl index 3d94728..711c555 100755 --- a/hetznerctl +++ b/hetznerctl @@ -259,7 +259,7 @@ class Failover(SubCommand): elif args.monitorfailover: msgs.extend(robot.failover.monitor()) else: - msgs = robot.failover.list() + msgs = robot.failover.list().values() if len(msgs) > 0: for msg in msgs: self.putline(str(msg)) From 9ad242607d279910d465a0096b85c2ef0020ff5a Mon Sep 17 00:00:00 2001 From: Peter Klein Date: Mon, 11 May 2020 11:16:37 +0200 Subject: [PATCH 5/5] Accept ip list as new destination --- .gitignore | 1 + hetzner/failover.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b565d21..cf0b7fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc /build/ /result +.idea diff --git a/hetzner/failover.py b/hetzner/failover.py index 48c2be5..0ae1115 100644 --- a/hetzner/failover.py +++ b/hetzner/failover.py @@ -48,12 +48,13 @@ def set(self, ip, new_destination): "Invalid IP address '%s'. Failover IP addresses are %s" % (ip, failovers.keys())) failover = failovers.get(ip) - if new_destination == failover.active_server_ip: + dest_list = new_destination.split(' ') + if failover.active_server_ip in dest_list: raise RobotError( "%s is already the active destination of failover IP %s" % (new_destination, ip)) - available_dests = [s.ip for s in list(self.servers)] - if new_destination not in available_dests: + available_dests = set([s.ip for s in list(self.servers)]) + if len(available_dests.intersection(set(dest_list))) == 0: raise RobotError( "Invalid destination '%s'. " "The destination is not in your server list: %s"