Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.pyc
/build/
/result
.idea
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep out all editor-specific ignores, so can you please drop this hunk?

53 changes: 50 additions & 3 deletions hetzner/failover.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import re
import subprocess

from hetzner import RobotError

__all__ = ['Failover', 'FailoverManager']
Expand Down Expand Up @@ -45,16 +48,60 @@ 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(' ')
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't stringly-type things, I'd rather prefer the function signature to be something like:

def set(self, ip: str, *destinations: str) -> None:
    ...

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"
% (new_destination, available_dests))
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 = []
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):
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

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()

Comment on lines +65 to +107
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, these are lxc-specific methods which clearly have nothing to do with the library. If we really want people to extend the hetznerctl tool, we should instead introduce a plugin system, where people can introduce such changes without becoming a maintenance burden upstream.

31 changes: 16 additions & 15 deletions hetznerctl
Original file line number Diff line number Diff line change
Expand Up @@ -233,35 +233,36 @@ 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"),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate help comment.

make_option('ip', nargs='?', default=None,
help="Failover IP address to assign"),
make_option('destination', nargs='?', default=None,
help="IP address of new failover destination")
]

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().values()
if len(msgs) > 0:
for msg in msgs:
self.putline(str(msg))


class Admin(SubCommand):
Expand Down