Created
March 12, 2014 15:49
-
-
Save zastari/9509625 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import pyrax | |
import ConfigParser | |
import sys | |
import getopt | |
class Config(ConfigParser.ConfigParser): | |
def __getitems__(self, section): | |
try: | |
return dict(self.items(section)) | |
except ConfigParser.NoSectionError: | |
raise KeyError(section) | |
def __generate__(self, conf_file): | |
try: | |
self.readfp(open(conf_file)) | |
except ConfigParser.Error, exc: | |
print >>sys.stderr, "manager.cfg error on parse ", exc | |
return 1 | |
except IOError, exc: | |
print >>sys.stderr, "failed to open manager.cfg ", exc | |
return 1 | |
self.auth = {"user" : "", "apikey" : ""} | |
if "auth" in self.sections(): | |
self.auth.update(self.__getitems__("auth")) | |
else: | |
print >>sys.stderr, "[auth] section not found in manager.cfg ", exc | |
return 1 | |
self.lb = {"master-id" : "", "slave-id" : ""} | |
if "lb" in self.sections(): | |
self.lb.update(self.__getitems__("lb")) | |
else: | |
print >>sys.stderr, "[lb] section not found in manager.cfg ", exc | |
class MHAUtils: | |
def print_config(self, lb): | |
print "%s (%s) %s SRC %s" % (lb.name, lb.id, lb.virtual_ips[0].address, | |
lb.sourceAddresses['ipv4Servicenet']) | |
for node in lb.nodes: | |
print " -- %s: %s (Server is currently %s)" % (node.address, | |
node.condition, node.status) | |
def verify_master(self, lb, master_address): | |
hit_count = 0 | |
errors = [] | |
writable_slaves = [] | |
for node in lb.nodes: | |
if node.address == master_address: | |
hit_count += 1 | |
if node.condition != 'ENABLED': | |
errors.append(1) | |
else: | |
if node.condition == 'ENABLED': | |
errors.append(2) | |
writable_slaves.append(node.address) | |
if hit_count != 1 or errors != []: | |
if hit_count == 0: | |
print >>sys.stderr, "Master Address %s not found in LB %s. Is this the correct IP / Load Balancer?" % (master_address, lb.name) | |
for error in errors: | |
if error == 1: | |
print >>sys.stderr, "Master Address %s is not listed as enabled" % (master_address) | |
if error == 2: | |
print >>sys.stderr, "Slave(s) are listed as enabled: %s" % (', '.join(writable_slaves)) | |
return 1 | |
else: | |
print "All checks completed successfully" | |
return 0 | |
def _set_node_status(self, lb, address, status): | |
print "setting node %s to %s on Load Balancer %s" % (address, status, lb.name) | |
for node in lb.nodes: | |
if node.address == address: | |
if node.condition != status: | |
node.condition = status | |
node.update() | |
pyrax.utils.wait_until(lb, "status", "ACTIVE", interval=1, | |
attempts=30, verbose=True) | |
print "Success: Node condition change complete" | |
return 0 | |
else: | |
print >>sys.stderr, "Warning: Target node (%s) is already set to condition %s" % (address, status) | |
return 0 | |
def disable_node(self, lb, address): | |
return self._set_node_status(lb, address, 'DISABLED') | |
def enable_node(self, lb, address): | |
return self._set_node_status(lb, address, 'ENABLED') | |
def auth(auth_profile): | |
pyrax.set_setting("identity_type", "rackspace") | |
pyrax.set_credentials(auth_profile['user'], auth_profile['apikey']) | |
def main(): | |
conf_file = '/etc/masterha/ip_manager.cnf' | |
config = Config() | |
config.__generate__(conf_file) | |
auth(config.auth) | |
mha_utils = MHAUtils() | |
lb_master = pyrax.cloud_loadbalancers.get(config.lb['master-id']) | |
lb_slave = pyrax.cloud_loadbalancers.get(config.lb['slave-id']) | |
mha_utils.print_config(lb_master) | |
mha_utils.print_config(lb_slave) | |
mha_options = ['command=', 'ssh_user=', 'orig_master_host=', | |
'orig_master_ip=', 'orig_master_port=', 'new_master_host=', | |
'new_master_ip=', 'new_master_port=', 'new_master_user=', | |
'new_master_password=' | |
] | |
direct_options = ['address='] | |
try: | |
options, arguments = getopt.getopt(sys.argv[1:], '', mha_options + direct_options) | |
except getopt.GetoptError as exc: | |
print str(exc) | |
return 1 | |
command = None | |
orig_master_ip = None | |
new_master_ip = None | |
for option, argument in options: | |
if option == '--command': | |
command = argument | |
elif option == '--orig_master_ip': | |
orig_master_ip = argument | |
elif option == '--new_master_ip': | |
new_master_ip = argument | |
if command == 'status': | |
return mha_utils.verify_master(lb_master, orig_master_ip) | |
elif command == 'stop' or command == 'stopssh': | |
lb_master_status = mha_utils.disable_node(lb_master, orig_master_ip) | |
lb_slave_status = mha_utils.disable_node(lb_slave, orig_master_ip) | |
return max(lb_master_status, lb_slave_status) | |
elif command == 'start': | |
lb_master_status = mha_utils.enable_node(lb_master, new_master_ip) | |
lb_slave_status = mha_utils.enable_node(lb_slave, new_master_ip) | |
return max(lb_master_status, lb_slave_status) | |
if __name__ == '__main__': | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I made some minor modifications in my forked gist here:
https://gist.github.com/abg/9626615
I haven't really tested it thoroughly, so take it with a grain of salt.
Some commentary (apologies for excessive bike shedding):
load_config(path)
method, as an example of how I might solve this._set_node_status()
should really return a non-zero integer if it's return value will be used as the script exit status. Here it is returning 0 or None (if the loop falls through). I think just returning 1 makes sense here. Right now I don't think failures here are being detected if a node doesn't exist in the lb config.enable_node()
makes me nervous as it only ensures the master node is active on the load balancer, but doesn't necessarily ensure there is only one active node. This should be the case if MHA does it's job properly (i.e. only running --command=start if --command=stop[ssh] completed successfully), but probably some additional sanity checking here makes sense.auth()
'region' should probably be supported - this could be set in ~/.pyrax.cfg but I don't think that's necessarily reliable or best practice.