# -*- coding: utf-8 -*- from __future__ import print_function from argparse import ArgumentParser, RawDescriptionHelpFormatter import inspect import subprocess import os import xml.etree.cElementTree as etree import json import sys MY_UUID = None parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, description=__doc__) subparsers = parser.add_subparsers(dest="mode") subcommands = {} cache_data = {} ParseError = etree.ParseError if hasattr(etree, 'ParseError') else SyntaxError _common_args_func = lambda p: True class GlusterCmdException(Exception): def __init__(self, message): self.message = message try: # Python 3 super().__init__(message) except TypeError: # Python 2 super(GlusterCmdException, self).__init__(message) def get_node_uuid(): # Caches the Node UUID in global variable, # Executes gluster system:: uuid get command only if # calling this function for first time global MY_UUID if MY_UUID is not None: return MY_UUID cmd = ["gluster", "system::", "uuid", "get", "--xml"] rc, out, err = execute(cmd) if rc != 0: return None tree = etree.fromstring(out) uuid_el = tree.find("uuidGenerate/uuid") MY_UUID = uuid_el.text return MY_UUID def yesno(flag): return "Yes" if flag else "No" def oknotok(flag): return "OK" if flag else "NOT OK" def output_error(message, errcode=1): print (message, file=sys.stderr) sys.exit(errcode) def node_output_ok(message=""): # Prints Success JSON output and exits with returncode zero out = {"ok": True, "nodeid": get_node_uuid(), "output": message} print (json.dumps(out)) sys.exit(0) def node_output_notok(message): # Prints Error JSON output and exits with returncode zero out = {"ok": False, "nodeid": get_node_uuid(), "error": message} print (json.dumps(out)) sys.exit(0) def execute(cmd): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) out, err = p.communicate() return p.returncode, out, err def get_pool_list(): cmd = ["gluster", "--mode=script", "pool", "list", "--xml"] rc, out, err = execute(cmd) if rc != 0: output_error("Failed to get Pool Info: {0}".format(err)) tree = etree.fromstring(out) pool = [] try: for p in tree.findall('peerStatus/peer'): pool.append({"nodeid": p.find("uuid").text, "hostname": p.find("hostname").text, "connected": (True if p.find("connected").text == "1" else False)}) except (ParseError, AttributeError, ValueError) as e: output_error("Failed to parse Pool Info: {0}".format(e)) return pool class NodeOutput(object): def __init__(self, **kwargs): self.nodeid = kwargs.get("nodeid", "") self.hostname = kwargs.get("hostname", "") self.node_up = kwargs.get("node_up", False) self.ok = kwargs.get("ok", False) self.output = kwargs.get("output", "N/A") self.error = kwargs.get("error", "N/A") def execute_in_peers(name, args=[]): # Get the file name of Caller function, If the file name is peer_example.py # then Gluster peer command will be gluster system:: execute example.py # Command name is without peer_ frame = inspect.stack()[1] module = inspect.getmodule(frame[0]) actual_file = module.__file__ # If file is symlink then find actual file if os.path.islink(actual_file): actual_file = os.readlink(actual_file) # Get the name of file without peer_ cmd_name = os.path.basename(actual_file).replace("peer_", "") cmd = ["gluster", "system::", "execute", cmd_name, name] + args rc, out, err = execute(cmd) if rc != 0: raise GlusterCmdException((rc, out, err, " ".join(cmd))) out = out.strip().splitlines() # JSON decode each line and construct one object with node id as key all_nodes_data = {} for node_data in out: data = json.loads(node_data) all_nodes_data[data["nodeid"]] = { "nodeid": data.get("nodeid"), "ok": data.get("ok"), "output": data.get("output", ""), "error": data.get("error", "")} # gluster pool list pool_list = get_pool_list() data_out = [] # Iterate pool_list and merge all_nodes_data collected above # If a peer node is down then set node_up = False for p in pool_list: p_data = all_nodes_data.get(p.get("nodeid"), None) row_data = NodeOutput(node_up=False, hostname=p.get("hostname"), nodeid=p.get("nodeid"), ok=False) if p_data is not None: # Node is UP row_data.node_up = True row_data.ok = p_data.get("ok") row_data.output = p_data.get("output") row_data.error = p_data.get("error") data_out.append(row_data) return data_out def sync_file_to_peers(fname): # Copy file from current node to all peer nodes, fname # is path after GLUSTERD_WORKDIR cmd = ["gluster", "system::", "copy", "file", fname] rc, out, err = execute(cmd) if rc != 0: raise GlusterCmdException((rc, out, err)) class Cmd(object): name = "" def run(self, args): # Must required method. Raise NotImplementedError if derived class # not implemented this method raise NotImplementedError("\"run(self, args)\" method is " "not implemented by \"{0}\"".format( self.__class__.__name__)) def runcli(): # Get list of Classes derived from class "Cmd" and create # a subcommand as specified in the Class name. Call the args # method by passing subcommand parser, Derived class can add # arguments to the subcommand parser. metavar_data = [] for c in Cmd.__subclasses__(): cls = c() if getattr(cls, "name", "") == "": raise NotImplementedError("\"name\" is not added " "to \"{0}\"".format( cls.__class__.__name__)) # Do not show in help message if subcommand starts with node- if not cls.name.startswith("node-"): metavar_data.append(cls.name) p = subparsers.add_parser(cls.name) args_func = getattr(cls, "args", None) if args_func is not None: args_func(p) # Apply common args if any _common_args_func(p) # A dict to save subcommands, key is name of the subcommand subcommands[cls.name] = cls # Hide node commands in Help message subparsers.metavar = "{" + ",".join(metavar_data) + "}" # Get all parsed arguments args = parser.parse_args() # Get the subcommand to execute cls = subcommands.get(args.mode, None) # Run if cls is not None: cls.run(args) def set_common_args_func(func): global _common_args_func _common_args_func = func