glusterfs/extras/cliutils/cliutils.py
Aravinda VK 19adaad015 extras/cliutils: Utils for creating CLI tools for Gluster
Refer README.md for documentation.

BUG: 1342356
Change-Id: Ic88504177137136bbb4b8b2c304ecc4af9bcfe30
Signed-off-by: Aravinda VK <avishwan@redhat.com>
Reviewed-on: http://review.gluster.org/14627
Reviewed-by: Prashanth Pai <ppai@redhat.com>
Reviewed-by: Niels de Vos <ndevos@redhat.com>
Smoke: Gluster Build System <jenkins@build.gluster.org>
NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
CentOS-regression: Gluster Build System <jenkins@build.gluster.org>
Reviewed-by: Jeff Darcy <jdarcy@redhat.com>
2016-07-12 09:07:29 -07:00

213 lines
6.3 KiB
Python

# -*- 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
class GlusterCmdException(Exception):
pass
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):
print (message, file=sys.stderr)
sys.exit(1)
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)
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.
for c in Cmd.__subclasses__():
cls = c()
if getattr(cls, "name", "") == "":
raise NotImplementedError("\"name\" is not added "
"to \"{0}\"".format(
cls.__class__.__name__))
p = subparsers.add_parser(cls.name)
args_func = getattr(cls, "args", None)
if args_func is not None:
args_func(p)
# A dict to save subcommands, key is name of the subcommand
subcommands[cls.name] = cls
# 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)