Skip to content

Instantly share code, notes, and snippets.

@zastari
Created March 12, 2014 15:49
Show Gist options
  • Save zastari/9509625 to your computer and use it in GitHub Desktop.
Save zastari/9509625 to your computer and use it in GitHub Desktop.
#!/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())
@abg
Copy link

abg commented Mar 18, 2014

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

  • I think it's simpler to just use ConfigParser to convert a config file to a dictionary. I replaced the config class with a (hopefully) straightforward 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.
  • for 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.
  • I see you have an --address option but I'm not sure what it's intention was.
  • [bikeshedding] MHAUtils doesn't really maintain state. This probably makes more sense as simple functions instead of class methods and I guess I don't see the namespace adding much here.
  • [bikeshedding] I think using python logging rather than print statements makes sense here instead of print statements. This makes it somewhat easier to log to a file or enable excessive debugging (especially if you want redirect pyrax debugging elsewhere).
  • [bikeshedding] you'll probably find the python optparse module to be much easier and convenient than getopt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment