bae5841487
Make Popen py2 and py3 compatiable fixes: bz#1643935 Change-Id: Ife34cb38024dcdc0420436e7d76fd208223f9d86 Signed-off-by: Kotresh HR <khiremat@redhat.com> |
||
---|---|---|
.. | ||
__init__.py | ||
cliutils.py | ||
Makefile.am | ||
README.md |
CLI utility for creating Cluster aware CLI tools for Gluster
cliutils is a Python library which provides wrapper around gluster system:: execute
command to extend the functionalities of Gluster.
Example use cases:
- Start a service in all peer nodes of Cluster
- Collect the status of a service from all peer nodes
- Collect the config values from each peer nodes and display latest config based on version.
- Copy a file present in GLUSTERD_WORKDIR from one peer node to all other peer nodes.(Geo-replication create push-pem is using this to distribute the SSH public keys from all master nodes to all slave nodes)
- Generate pem keys in all peer nodes and collect all the public keys to one place(Geo-replication gsec_create is doing this)
- Provide Config sync CLIs for new features like
gluster-eventsapi
,gluster-restapi
,gluster-mountbroker
etc.
Introduction
If a executable file present in $GLUSTER_LIBEXEC
directory in all
peer nodes(Filename startswith peer_
) then it can be executed by
running gluster system:: execute
command from any one peer node.
- This command will not copy any executables to peer nodes, Script should exist in all peer nodes to use this infrastructure. Raises error in case script not exists in any one of the peer node.
- Filename should start with
peer_
and should exist in$GLUSTER_LIBEXEC
directory. - This command can not be called from outside the cluster.
To understand the functionality, create a executable file peer_hello
under $GLUSTER_LIBEXEC directory and copy to all peer nodes.
#!/usr/bin/env bash
echo "Hello from $(gluster system:: uuid get)"
Now run the following command from any one gluster node,
gluster system:: execute hello
Note: Gluster will not copy the executable script to all nodes,
copy peer_hello
script to all peer nodes to use gluster system:: execute
infrastructure.
It will run peer_hello
executable in all peer nodes and shows the
output from each node(Below example shows output from my two nodes
cluster)
Hello from UUID: e7a3c5c8-e7ad-47ad-aa9c-c13907c4da84
Hello from UUID: c680fc0a-01f9-4c93-a062-df91cc02e40f
cliutils
A Python wrapper around gluster system:: execute
command is created
to address the following issues
- If a node is down in the cluster,
system:: execute
just skips it and runs only in up nodes. system:: execute
commands are not user friendly- It captures only stdout, so handling errors is tricky.
Advantages of cliutils:
- Single executable file will act as node component as well as User CLI.
execute_in_peers
utility function will merge thegluster system:: execute
output withgluster peer status
to identify offline nodes.- Easy CLI Arguments handling.
- If node component returns non zero return value then,
gluster system:: execute
will fail to aggregate the output from other nodes.node_output_ok
ornode_output_notok
utility functions returns zero both in case of success or error, but returns json with ok: true or ok:false respectively. - Easy to iterate on the node outputs.
- Better error handling - Geo-rep CLIs
gluster system:: execute mountbroker
,gluster system:: execute gsec_create
andgluster system:: add_secret_pub
are suffering from error handling. These tools are not notifying user if any failures during execute or if a node is down during execute.
Hello World
Create a file in $LIBEXEC/glusterfs/peer_message.py
with following
content.
#!/usr/bin/python3
from gluster.cliutils import Cmd, runcli, execute_in_peers, node_output_ok
class NodeHello(Cmd):
name = "node-hello"
def run(self, args):
node_output_ok("Hello")
class Hello(Cmd):
name = "hello"
def run(self, args):
out = execute_in_peers("node-hello")
for row in out:
print ("{0} from {1}".format(row.output, row.hostname))
if __name__ == "__main__":
runcli()
When we run python peer_message.py
, it will have two subcommands,
"node-hello" and "hello". This file should be copied to
$LIBEXEC/glusterfs
directory in all peer nodes. User will call
subcommand "hello" from any one peer node, which internally call
gluster system:: execute message.py node-hello
(This runs in all peer
nodes and collect the outputs)
For node component do not print the output directly, use
node_output_ok
or node_output_notok
functions. node_output_ok
additionally collects the node UUID and prints in JSON
format. execute_in_peers
function will collect this output and
merges with peers list
so that we don't miss the node information if
that node is offline.
If you observed already, function args
is optional, if you don't
have arguments then no need to create a function. When we run the
file, we will have two subcommands. For example,
python peer_message.py hello
python peer_message.py node-hello
First subcommand calls second subcommand in all peer nodes. Basically
execute_in_peers(NAME, ARGS)
will be converted into
CMD_NAME = FILENAME without "peers_"
gluster system:: execute <CMD_NAME> <SUBCOMMAND> <ARGS>
In our example,
filename = "peer_message.py"
cmd_name = "message.py"
gluster system:: execute ${cmd_name} node-hello
Now create symlink in /usr/bin
or /usr/sbin
directory depending on
the usecase.(Optional step for usability)
ln -s /usr/libexec/glusterfs/peer_message.py /usr/bin/gluster-message
Now users can use gluster-message
instead of calling
/usr/libexec/glusterfs/peer_message.py
gluster-message hello
Showing CLI output as Table
Following example uses prettytable library, which can be installed
using pip install prettytable
or dnf install python-prettytable
#!/usr/bin/python3
from prettytable import PrettyTable
from gluster.cliutils import Cmd, runcli, execute_in_peers, node_output_ok
class NodeHello(Cmd):
name = "node-hello"
def run(self, args):
node_output_ok("Hello")
class Hello(Cmd):
name = "hello"
def run(self, args):
out = execute_in_peers("node-hello")
# Initialize the CLI table
table = PrettyTable(["ID", "NODE", "NODE STATUS", "MESSAGE"])
table.align["NODE STATUS"] = "r"
for row in out:
table.add_row([row.nodeid,
row.hostname,
"UP" if row.node_up else "DOWN",
row.output if row.ok else row.error])
print table
if __name__ == "__main__":
runcli()
Example output,
+--------------------------------------+-----------+-------------+---------+
| ID | NODE | NODE STATUS | MESSAGE |
+--------------------------------------+-----------+-------------+---------+
| e7a3c5c8-e7ad-47ad-aa9c-c13907c4da84 | localhost | UP | Hello |
| bb57a4c4-86eb-4af5-865d-932148c2759b | vm2 | UP | Hello |
| f69b918f-1ffa-4fe5-b554-ee10f051294e | vm3 | DOWN | N/A |
+--------------------------------------+-----------+-------------+---------+
How to package in Gluster
If the project is created in $GLUSTER_SRC/tools/message
Add "message" to SUBDIRS list in $GLUSTER_SRC/tools/Makefile.am
and then create a Makefile.am
in $GLUSTER_SRC/tools/message
directory with following content.
EXTRA_DIST = peer_message.py
peertoolsdir = $(libexecdir)/glusterfs/
peertools_SCRIPTS = peer_message.py
install-exec-hook:
$(mkdir_p) $(DESTDIR)$(bindir)
rm -f $(DESTDIR)$(bindir)/gluster-message
ln -s $(libexecdir)/glusterfs/peer_message.py \
$(DESTDIR)$(bindir)/gluster-message
uninstall-hook:
rm -f $(DESTDIR)$(bindir)/gluster-message
Thats all. Add following files in glusterfs.spec.in
if packaging is
required.(Under %files
section)
%{_libexecdir}/glusterfs/peer_message.py*
%{_bindir}/gluster-message
Who is using cliutils
- gluster-mountbroker http://review.gluster.org/14544
- gluster-eventsapi http://review.gluster.org/14248
- gluster-georep-sshkey http://review.gluster.org/14732
- gluster-restapi https://github.com/aravindavk/glusterfs-restapi
Limitations/TODOs
- Not yet possible to create CLI without any subcommand, For example
gluster-message
without any arguments - Hiding node subcommands in
--help
(gluster-message --help
will show all subcommands including node subcommands) - Only positional arguments supported for node arguments, Optional arguments can be used for other commands.
- API documentation