|
|
3604df |
From 322ca565ff22481a1406a205d8f6f44359b68009 Mon Sep 17 00:00:00 2001
|
|
|
3604df |
From: Aravinda VK <avishwan@redhat.com>
|
|
|
3604df |
Date: Tue, 31 May 2016 13:39:05 +0530
|
|
|
3604df |
Subject: [PATCH 54/86] extras/cliutils: Utils for creating CLI tools for Gluster
|
|
|
3604df |
|
|
|
3604df |
Refer README.md for documentation.
|
|
|
3604df |
|
|
|
3604df |
This patch also includes changes from #14923 since without that
|
|
|
3604df |
build will fail.
|
|
|
3604df |
|
|
|
3604df |
> Reviewed-on: http://review.gluster.org/14627
|
|
|
3604df |
> Reviewed-on: http://review.gluster.org/14923
|
|
|
3604df |
> Reviewed-by: Prashanth Pai <ppai@redhat.com>
|
|
|
3604df |
> Reviewed-by: Niels de Vos <ndevos@redhat.com>
|
|
|
3604df |
> Smoke: Gluster Build System <jenkins@build.gluster.org>
|
|
|
3604df |
> NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
|
|
|
3604df |
> CentOS-regression: Gluster Build System <jenkins@build.gluster.org>
|
|
|
3604df |
> Reviewed-by: Jeff Darcy <jdarcy@redhat.com>
|
|
|
3604df |
|
|
|
3604df |
BUG: 1351589
|
|
|
3604df |
Change-Id: Ic88504177137136bbb4b8b2c304ecc4af9bcfe30
|
|
|
3604df |
Signed-off-by: Aravinda VK <avishwan@redhat.com>
|
|
|
3604df |
Reviewed-on: https://code.engineering.redhat.com/gerrit/84799
|
|
|
3604df |
Reviewed-by: Milind Changire <mchangir@redhat.com>
|
|
|
3604df |
Reviewed-by: Atin Mukherjee <amukherj@redhat.com>
|
|
|
3604df |
---
|
|
|
3604df |
configure.ac | 1 +
|
|
|
3604df |
extras/Makefile.am | 2 +-
|
|
|
3604df |
extras/cliutils/Makefile.am | 4 +
|
|
|
3604df |
extras/cliutils/README.md | 233 +++++++++++++++++++++++++++++++++++++++++++
|
|
|
3604df |
extras/cliutils/__init__.py | 29 ++++++
|
|
|
3604df |
extras/cliutils/cliutils.py | 212 +++++++++++++++++++++++++++++++++++++++
|
|
|
3604df |
glusterfs.spec.in | 4 +
|
|
|
3604df |
7 files changed, 484 insertions(+), 1 deletions(-)
|
|
|
3604df |
create mode 100644 extras/cliutils/Makefile.am
|
|
|
3604df |
create mode 100644 extras/cliutils/README.md
|
|
|
3604df |
create mode 100644 extras/cliutils/__init__.py
|
|
|
3604df |
create mode 100644 extras/cliutils/cliutils.py
|
|
|
3604df |
|
|
|
3604df |
diff --git a/configure.ac b/configure.ac
|
|
|
3604df |
index d84398d..75ebcd4 100644
|
|
|
3604df |
--- a/configure.ac
|
|
|
3604df |
+++ b/configure.ac
|
|
|
3604df |
@@ -194,6 +194,7 @@ AC_CONFIG_FILES([Makefile
|
|
|
3604df |
doc/Makefile
|
|
|
3604df |
extras/Makefile
|
|
|
3604df |
extras/glusterd.vol
|
|
|
3604df |
+ extras/cliutils/Makefile
|
|
|
3604df |
extras/init.d/Makefile
|
|
|
3604df |
extras/init.d/glusterd.plist
|
|
|
3604df |
extras/init.d/glusterd-Debian
|
|
|
3604df |
diff --git a/extras/Makefile.am b/extras/Makefile.am
|
|
|
3604df |
index 609d497..37b297f 100644
|
|
|
3604df |
--- a/extras/Makefile.am
|
|
|
3604df |
+++ b/extras/Makefile.am
|
|
|
3604df |
@@ -5,7 +5,7 @@ EditorModedir = $(docdir)
|
|
|
3604df |
EditorMode_DATA = glusterfs-mode.el glusterfs.vim
|
|
|
3604df |
|
|
|
3604df |
SUBDIRS = init.d systemd benchmarking hook-scripts $(OCF_SUBDIR) LinuxRPM \
|
|
|
3604df |
- $(GEOREP_EXTRAS_SUBDIR) ganesha snap_scheduler firewalld
|
|
|
3604df |
+ $(GEOREP_EXTRAS_SUBDIR) ganesha snap_scheduler firewalld cliutils
|
|
|
3604df |
|
|
|
3604df |
confdir = $(sysconfdir)/glusterfs
|
|
|
3604df |
conf_DATA = glusterfs-logrotate gluster-rsyslog-7.2.conf gluster-rsyslog-5.8.conf \
|
|
|
3604df |
diff --git a/extras/cliutils/Makefile.am b/extras/cliutils/Makefile.am
|
|
|
3604df |
new file mode 100644
|
|
|
3604df |
index 0000000..7039703
|
|
|
3604df |
--- /dev/null
|
|
|
3604df |
+++ b/extras/cliutils/Makefile.am
|
|
|
3604df |
@@ -0,0 +1,4 @@
|
|
|
3604df |
+EXTRA_DIST= cliutils.py __init__.py
|
|
|
3604df |
+
|
|
|
3604df |
+cliutilsdir = @BUILD_PYTHON_SITE_PACKAGES@/gluster/cliutils
|
|
|
3604df |
+cliutils_PYTHON = cliutils.py __init__.py
|
|
|
3604df |
diff --git a/extras/cliutils/README.md b/extras/cliutils/README.md
|
|
|
3604df |
new file mode 100644
|
|
|
3604df |
index 0000000..ccb6080
|
|
|
3604df |
--- /dev/null
|
|
|
3604df |
+++ b/extras/cliutils/README.md
|
|
|
3604df |
@@ -0,0 +1,233 @@
|
|
|
3604df |
+# CLI utility for creating Cluster aware CLI tools for Gluster
|
|
|
3604df |
+cliutils is a Python library which provides wrapper around `gluster system::
|
|
|
3604df |
+execute` command to extend the functionalities of Gluster.
|
|
|
3604df |
+
|
|
|
3604df |
+Example use cases:
|
|
|
3604df |
+- Start a service in all peer nodes of Cluster
|
|
|
3604df |
+- Collect the status of a service from all peer nodes
|
|
|
3604df |
+- Collect the config values from each peer nodes and display latest
|
|
|
3604df |
+ config based on version.
|
|
|
3604df |
+- Copy a file present in GLUSTERD_WORKDIR from one peer node to all
|
|
|
3604df |
+ other peer nodes.(Geo-replication create push-pem is using this to
|
|
|
3604df |
+ distribute the SSH public keys from all master nodes to all slave
|
|
|
3604df |
+ nodes)
|
|
|
3604df |
+- Generate pem keys in all peer nodes and collect all the public keys
|
|
|
3604df |
+ to one place(Geo-replication gsec_create is doing this)
|
|
|
3604df |
+- Provide Config sync CLIs for new features like `gluster-eventsapi`,
|
|
|
3604df |
+ `gluster-restapi`, `gluster-mountbroker` etc.
|
|
|
3604df |
+
|
|
|
3604df |
+## Introduction
|
|
|
3604df |
+
|
|
|
3604df |
+If a executable file present in `$GLUSTER_LIBEXEC` directory in all
|
|
|
3604df |
+peer nodes(Filename startswith `peer_`) then it can be executed by
|
|
|
3604df |
+running `gluster system:: execute` command from any one peer node.
|
|
|
3604df |
+
|
|
|
3604df |
+- This command will not copy any executables to peer nodes, Script
|
|
|
3604df |
+ should exist in all peer nodes to use this infrastructure. Raises
|
|
|
3604df |
+ error in case script not exists in any one of the peer node.
|
|
|
3604df |
+- Filename should start with `peer_` and should exist in
|
|
|
3604df |
+ `$GLUSTER_LIBEXEC` directory.
|
|
|
3604df |
+- This command can not be called from outside the cluster.
|
|
|
3604df |
+
|
|
|
3604df |
+To understand the functionality, create a executable file `peer_hello`
|
|
|
3604df |
+under $GLUSTER_LIBEXEC directory and copy to all peer nodes.
|
|
|
3604df |
+
|
|
|
3604df |
+ #!/usr/bin/env bash
|
|
|
3604df |
+ echo "Hello from $(gluster system:: uuid get)"
|
|
|
3604df |
+
|
|
|
3604df |
+Now run the following command from any one gluster node,
|
|
|
3604df |
+
|
|
|
3604df |
+ gluster system:: execute hello
|
|
|
3604df |
+
|
|
|
3604df |
+**Note:** Gluster will not copy the executable script to all nodes,
|
|
|
3604df |
+ copy `peer_hello` script to all peer nodes to use `gluster system::
|
|
|
3604df |
+ execute` infrastructure.
|
|
|
3604df |
+
|
|
|
3604df |
+It will run `peer_hello` executable in all peer nodes and shows the
|
|
|
3604df |
+output from each node(Below example shows output from my two nodes
|
|
|
3604df |
+cluster)
|
|
|
3604df |
+
|
|
|
3604df |
+ Hello from UUID: e7a3c5c8-e7ad-47ad-aa9c-c13907c4da84
|
|
|
3604df |
+ Hello from UUID: c680fc0a-01f9-4c93-a062-df91cc02e40f
|
|
|
3604df |
+
|
|
|
3604df |
+## cliutils
|
|
|
3604df |
+A Python wrapper around `gluster system:: execute` command is created
|
|
|
3604df |
+to address the following issues
|
|
|
3604df |
+
|
|
|
3604df |
+- If a node is down in the cluster, `system:: execute` just skips it
|
|
|
3604df |
+ and runs only in up nodes.
|
|
|
3604df |
+- `system:: execute` commands are not user friendly
|
|
|
3604df |
+- It captures only stdout, so handling errors is tricky.
|
|
|
3604df |
+
|
|
|
3604df |
+**Advantages of cliutils:**
|
|
|
3604df |
+
|
|
|
3604df |
+- Single executable file will act as node component as well as User CLI.
|
|
|
3604df |
+- `execute_in_peers` utility function will merge the `gluster system::
|
|
|
3604df |
+ execute` output with `gluster peer status` to identify offline nodes.
|
|
|
3604df |
+- Easy CLI Arguments handling.
|
|
|
3604df |
+- If node component returns non zero return value then, `gluster
|
|
|
3604df |
+ system:: execute` will fail to aggregate the output from other
|
|
|
3604df |
+ nodes. `node_output_ok` or `node_output_notok` utility functions
|
|
|
3604df |
+ returns zero both in case of success or error, but returns json
|
|
|
3604df |
+ with ok: true or ok:false respectively.
|
|
|
3604df |
+- Easy to iterate on the node outputs.
|
|
|
3604df |
+- Better error handling - Geo-rep CLIs `gluster system:: execute
|
|
|
3604df |
+ mountbroker`, `gluster system:: execute gsec_create` and `gluster
|
|
|
3604df |
+ system:: add_secret_pub` are suffering from error handling. These
|
|
|
3604df |
+ tools are not notifying user if any failures during execute or if a node
|
|
|
3604df |
+ is down during execute.
|
|
|
3604df |
+
|
|
|
3604df |
+### Hello World
|
|
|
3604df |
+Create a file in `$LIBEXEC/glusterfs/peer_message.py` with following
|
|
|
3604df |
+content.
|
|
|
3604df |
+
|
|
|
3604df |
+ #!/usr/bin/env python
|
|
|
3604df |
+ from gluster.cliutils import Cmd, runcli, execute_in_peers, node_output_ok
|
|
|
3604df |
+
|
|
|
3604df |
+ class NodeHello(Cmd):
|
|
|
3604df |
+ name = "node-hello"
|
|
|
3604df |
+
|
|
|
3604df |
+ def run(self, args):
|
|
|
3604df |
+ node_output_ok("Hello")
|
|
|
3604df |
+
|
|
|
3604df |
+ class Hello(Cmd):
|
|
|
3604df |
+ name = "hello"
|
|
|
3604df |
+
|
|
|
3604df |
+ def run(self, args):
|
|
|
3604df |
+ out = execute_in_peers("node-hello")
|
|
|
3604df |
+ for row in out:
|
|
|
3604df |
+ print ("{0} from {1}".format(row.output, row.hostname))
|
|
|
3604df |
+
|
|
|
3604df |
+ if __name__ == "__main__":
|
|
|
3604df |
+ runcli()
|
|
|
3604df |
+
|
|
|
3604df |
+When we run `python peer_message.py`, it will have two subcommands,
|
|
|
3604df |
+"node-hello" and "hello". This file should be copied to
|
|
|
3604df |
+`$LIBEXEC/glusterfs` directory in all peer nodes. User will call
|
|
|
3604df |
+subcommand "hello" from any one peer node, which internally call
|
|
|
3604df |
+`gluster system:: execute message.py node-hello`(This runs in all peer
|
|
|
3604df |
+nodes and collect the outputs)
|
|
|
3604df |
+
|
|
|
3604df |
+For node component do not print the output directly, use
|
|
|
3604df |
+`node_output_ok` or `node_output_notok` functions. `node_output_ok`
|
|
|
3604df |
+additionally collects the node UUID and prints in JSON
|
|
|
3604df |
+format. `execute_in_peers` function will collect this output and
|
|
|
3604df |
+merges with `peers list` so that we don't miss the node information if
|
|
|
3604df |
+that node is offline.
|
|
|
3604df |
+
|
|
|
3604df |
+If you observed already, function `args` is optional, if you don't
|
|
|
3604df |
+have arguments then no need to create a function. When we run the
|
|
|
3604df |
+file, we will have two subcommands. For example,
|
|
|
3604df |
+
|
|
|
3604df |
+ python peer_message.py hello
|
|
|
3604df |
+ python peer_message.py node-hello
|
|
|
3604df |
+
|
|
|
3604df |
+First subcommand calls second subcommand in all peer nodes. Basically
|
|
|
3604df |
+`execute_in_peers(NAME, ARGS)` will be converted into
|
|
|
3604df |
+
|
|
|
3604df |
+ CMD_NAME = FILENAME without "peers_"
|
|
|
3604df |
+ gluster system:: execute <CMD_NAME> <SUBCOMMAND> <ARGS>
|
|
|
3604df |
+
|
|
|
3604df |
+In our example,
|
|
|
3604df |
+
|
|
|
3604df |
+ filename = "peer_message.py"
|
|
|
3604df |
+ cmd_name = "message.py"
|
|
|
3604df |
+ gluster system:: execute ${cmd_name} node-hello
|
|
|
3604df |
+
|
|
|
3604df |
+Now create symlink in `/usr/bin` or `/usr/sbin` directory depending on
|
|
|
3604df |
+the usecase.(Optional step for usability)
|
|
|
3604df |
+
|
|
|
3604df |
+ ln -s /usr/libexec/glusterfs/peer_message.py /usr/bin/gluster-message
|
|
|
3604df |
+
|
|
|
3604df |
+Now users can use `gluster-message` instead of calling
|
|
|
3604df |
+`/usr/libexec/glusterfs/peer_message.py`
|
|
|
3604df |
+
|
|
|
3604df |
+ gluster-message hello
|
|
|
3604df |
+
|
|
|
3604df |
+### Showing CLI output as Table
|
|
|
3604df |
+
|
|
|
3604df |
+Following example uses prettytable library, which can be installed
|
|
|
3604df |
+using `pip install prettytable` or `dnf install python-prettytable`
|
|
|
3604df |
+
|
|
|
3604df |
+ #!/usr/bin/env python
|
|
|
3604df |
+ from prettytable import PrettyTable
|
|
|
3604df |
+ from gluster.cliutils import Cmd, runcli, execute_in_peers, node_output_ok
|
|
|
3604df |
+
|
|
|
3604df |
+ class NodeHello(Cmd):
|
|
|
3604df |
+ name = "node-hello"
|
|
|
3604df |
+
|
|
|
3604df |
+ def run(self, args):
|
|
|
3604df |
+ node_output_ok("Hello")
|
|
|
3604df |
+
|
|
|
3604df |
+ class Hello(Cmd):
|
|
|
3604df |
+ name = "hello"
|
|
|
3604df |
+
|
|
|
3604df |
+ def run(self, args):
|
|
|
3604df |
+ out = execute_in_peers("node-hello")
|
|
|
3604df |
+ # Initialize the CLI table
|
|
|
3604df |
+ table = PrettyTable(["ID", "NODE", "NODE STATUS", "MESSAGE"])
|
|
|
3604df |
+ table.align["NODE STATUS"] = "r"
|
|
|
3604df |
+ for row in out:
|
|
|
3604df |
+ table.add_row([row.nodeid,
|
|
|
3604df |
+ row.hostname,
|
|
|
3604df |
+ "UP" if row.node_up else "DOWN",
|
|
|
3604df |
+ row.output if row.ok else row.error])
|
|
|
3604df |
+
|
|
|
3604df |
+ print table
|
|
|
3604df |
+
|
|
|
3604df |
+ if __name__ == "__main__":
|
|
|
3604df |
+ runcli()
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+Example output,
|
|
|
3604df |
+
|
|
|
3604df |
+ +--------------------------------------+-----------+-------------+---------+
|
|
|
3604df |
+ | ID | NODE | NODE STATUS | MESSAGE |
|
|
|
3604df |
+ +--------------------------------------+-----------+-------------+---------+
|
|
|
3604df |
+ | e7a3c5c8-e7ad-47ad-aa9c-c13907c4da84 | localhost | UP | Hello |
|
|
|
3604df |
+ | bb57a4c4-86eb-4af5-865d-932148c2759b | vm2 | UP | Hello |
|
|
|
3604df |
+ | f69b918f-1ffa-4fe5-b554-ee10f051294e | vm3 | DOWN | N/A |
|
|
|
3604df |
+ +--------------------------------------+-----------+-------------+---------+
|
|
|
3604df |
+
|
|
|
3604df |
+## How to package in Gluster
|
|
|
3604df |
+If the project is created in `$GLUSTER_SRC/tools/message`
|
|
|
3604df |
+
|
|
|
3604df |
+Add "message" to SUBDIRS list in `$GLUSTER_SRC/tools/Makefile.am`
|
|
|
3604df |
+
|
|
|
3604df |
+and then create a `Makefile.am` in `$GLUSTER_SRC/tools/message`
|
|
|
3604df |
+directory with following content.
|
|
|
3604df |
+
|
|
|
3604df |
+ EXTRA_DIST = peer_message.py
|
|
|
3604df |
+
|
|
|
3604df |
+ peertoolsdir = $(libexecdir)/glusterfs/
|
|
|
3604df |
+ peertools_SCRIPTS = peer_message.py
|
|
|
3604df |
+
|
|
|
3604df |
+ install-exec-hook:
|
|
|
3604df |
+ $(mkdir_p) $(DESTDIR)$(bindir)
|
|
|
3604df |
+ rm -f $(DESTDIR)$(bindir)/gluster-message
|
|
|
3604df |
+ ln -s $(libexecdir)/glusterfs/peer_message.py \
|
|
|
3604df |
+ $(DESTDIR)$(bindir)/gluster-message
|
|
|
3604df |
+
|
|
|
3604df |
+ uninstall-hook:
|
|
|
3604df |
+ rm -f $(DESTDIR)$(bindir)/gluster-message
|
|
|
3604df |
+
|
|
|
3604df |
+Thats all. Add following files in `glusterfs.spec.in` if packaging is
|
|
|
3604df |
+required.(Under `%files` section)
|
|
|
3604df |
+
|
|
|
3604df |
+ %{_libexecdir}/glusterfs/peer_message.py*
|
|
|
3604df |
+ %{_bindir}/gluster-message
|
|
|
3604df |
+
|
|
|
3604df |
+## Who is using cliutils
|
|
|
3604df |
+- gluster-mountbroker http://review.gluster.org/14544
|
|
|
3604df |
+- gluster-eventsapi http://review.gluster.org/14248
|
|
|
3604df |
+- gluster-georep-sshkey http://review.gluster.org/14732
|
|
|
3604df |
+- gluster-restapi https://github.com/aravindavk/glusterfs-restapi
|
|
|
3604df |
+
|
|
|
3604df |
+## Limitations/TODOs
|
|
|
3604df |
+- Not yet possible to create CLI without any subcommand, For example
|
|
|
3604df |
+ `gluster-message` without any arguments
|
|
|
3604df |
+- Hiding node subcommands in `--help`(`gluster-message --help` will
|
|
|
3604df |
+ show all subcommands including node subcommands)
|
|
|
3604df |
+- Only positional arguments supported for node arguments, Optional
|
|
|
3604df |
+ arguments can be used for other commands.
|
|
|
3604df |
+- API documentation
|
|
|
3604df |
diff --git a/extras/cliutils/__init__.py b/extras/cliutils/__init__.py
|
|
|
3604df |
new file mode 100644
|
|
|
3604df |
index 0000000..4bb8395
|
|
|
3604df |
--- /dev/null
|
|
|
3604df |
+++ b/extras/cliutils/__init__.py
|
|
|
3604df |
@@ -0,0 +1,29 @@
|
|
|
3604df |
+# -*- coding: utf-8 -*-
|
|
|
3604df |
+# Reexporting the utility funcs and classes
|
|
|
3604df |
+from cliutils import (runcli,
|
|
|
3604df |
+ sync_file_to_peers,
|
|
|
3604df |
+ execute_in_peers,
|
|
|
3604df |
+ execute,
|
|
|
3604df |
+ node_output_ok,
|
|
|
3604df |
+ node_output_notok,
|
|
|
3604df |
+ output_error,
|
|
|
3604df |
+ oknotok,
|
|
|
3604df |
+ yesno,
|
|
|
3604df |
+ get_node_uuid,
|
|
|
3604df |
+ Cmd,
|
|
|
3604df |
+ GlusterCmdException)
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+# This will be useful when `from cliutils import *`
|
|
|
3604df |
+__all__ = ["runcli",
|
|
|
3604df |
+ "sync_file_to_peers",
|
|
|
3604df |
+ "execute_in_peers",
|
|
|
3604df |
+ "execute",
|
|
|
3604df |
+ "node_output_ok",
|
|
|
3604df |
+ "node_output_notok",
|
|
|
3604df |
+ "output_error",
|
|
|
3604df |
+ "oknotok",
|
|
|
3604df |
+ "yesno",
|
|
|
3604df |
+ "get_node_uuid",
|
|
|
3604df |
+ "Cmd",
|
|
|
3604df |
+ "GlusterCmdException"]
|
|
|
3604df |
diff --git a/extras/cliutils/cliutils.py b/extras/cliutils/cliutils.py
|
|
|
3604df |
new file mode 100644
|
|
|
3604df |
index 0000000..4e035d7
|
|
|
3604df |
--- /dev/null
|
|
|
3604df |
+++ b/extras/cliutils/cliutils.py
|
|
|
3604df |
@@ -0,0 +1,212 @@
|
|
|
3604df |
+# -*- coding: utf-8 -*-
|
|
|
3604df |
+from __future__ import print_function
|
|
|
3604df |
+from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
|
|
3604df |
+import inspect
|
|
|
3604df |
+import subprocess
|
|
|
3604df |
+import os
|
|
|
3604df |
+import xml.etree.cElementTree as etree
|
|
|
3604df |
+import json
|
|
|
3604df |
+import sys
|
|
|
3604df |
+
|
|
|
3604df |
+MY_UUID = None
|
|
|
3604df |
+parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
|
|
|
3604df |
+ description=__doc__)
|
|
|
3604df |
+subparsers = parser.add_subparsers(dest="mode")
|
|
|
3604df |
+
|
|
|
3604df |
+subcommands = {}
|
|
|
3604df |
+cache_data = {}
|
|
|
3604df |
+ParseError = etree.ParseError if hasattr(etree, 'ParseError') else SyntaxError
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+class GlusterCmdException(Exception):
|
|
|
3604df |
+ pass
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def get_node_uuid():
|
|
|
3604df |
+ # Caches the Node UUID in global variable,
|
|
|
3604df |
+ # Executes gluster system:: uuid get command only if
|
|
|
3604df |
+ # calling this function for first time
|
|
|
3604df |
+ global MY_UUID
|
|
|
3604df |
+ if MY_UUID is not None:
|
|
|
3604df |
+ return MY_UUID
|
|
|
3604df |
+
|
|
|
3604df |
+ cmd = ["gluster", "system::", "uuid", "get", "--xml"]
|
|
|
3604df |
+ rc, out, err = execute(cmd)
|
|
|
3604df |
+
|
|
|
3604df |
+ if rc != 0:
|
|
|
3604df |
+ return None
|
|
|
3604df |
+
|
|
|
3604df |
+ tree = etree.fromstring(out)
|
|
|
3604df |
+ uuid_el = tree.find("uuidGenerate/uuid")
|
|
|
3604df |
+ MY_UUID = uuid_el.text
|
|
|
3604df |
+ return MY_UUID
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def yesno(flag):
|
|
|
3604df |
+ return "Yes" if flag else "No"
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def oknotok(flag):
|
|
|
3604df |
+ return "OK" if flag else "NOT OK"
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def output_error(message):
|
|
|
3604df |
+ print (message, file=sys.stderr)
|
|
|
3604df |
+ sys.exit(1)
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def node_output_ok(message=""):
|
|
|
3604df |
+ # Prints Success JSON output and exits with returncode zero
|
|
|
3604df |
+ out = {"ok": True, "nodeid": get_node_uuid(), "output": message}
|
|
|
3604df |
+ print (json.dumps(out))
|
|
|
3604df |
+ sys.exit(0)
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def node_output_notok(message):
|
|
|
3604df |
+ # Prints Error JSON output and exits with returncode zero
|
|
|
3604df |
+ out = {"ok": False, "nodeid": get_node_uuid(), "error": message}
|
|
|
3604df |
+ print (json.dumps(out))
|
|
|
3604df |
+ sys.exit(0)
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def execute(cmd):
|
|
|
3604df |
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
3604df |
+ out, err = p.communicate()
|
|
|
3604df |
+ return p.returncode, out, err
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def get_pool_list():
|
|
|
3604df |
+ cmd = ["gluster", "--mode=script", "pool", "list", "--xml"]
|
|
|
3604df |
+ rc, out, err = execute(cmd)
|
|
|
3604df |
+ if rc != 0:
|
|
|
3604df |
+ output_error("Failed to get Pool Info: {0}".format(err))
|
|
|
3604df |
+
|
|
|
3604df |
+ tree = etree.fromstring(out)
|
|
|
3604df |
+
|
|
|
3604df |
+ pool = []
|
|
|
3604df |
+ try:
|
|
|
3604df |
+ for p in tree.findall('peerStatus/peer'):
|
|
|
3604df |
+ pool.append({"nodeid": p.find("uuid").text,
|
|
|
3604df |
+ "hostname": p.find("hostname").text,
|
|
|
3604df |
+ "connected": (True if p.find("connected").text == "1"
|
|
|
3604df |
+ else False)})
|
|
|
3604df |
+ except (ParseError, AttributeError, ValueError) as e:
|
|
|
3604df |
+ output_error("Failed to parse Pool Info: {0}".format(e))
|
|
|
3604df |
+
|
|
|
3604df |
+ return pool
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+class NodeOutput(object):
|
|
|
3604df |
+ def __init__(self, **kwargs):
|
|
|
3604df |
+ self.nodeid = kwargs.get("nodeid", "")
|
|
|
3604df |
+ self.hostname = kwargs.get("hostname", "")
|
|
|
3604df |
+ self.node_up = kwargs.get("node_up", False)
|
|
|
3604df |
+ self.ok = kwargs.get("ok", False)
|
|
|
3604df |
+ self.output = kwargs.get("output", "N/A")
|
|
|
3604df |
+ self.error = kwargs.get("error", "N/A")
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def execute_in_peers(name, args=[]):
|
|
|
3604df |
+ # Get the file name of Caller function, If the file name is peer_example.py
|
|
|
3604df |
+ # then Gluster peer command will be gluster system:: execute example.py
|
|
|
3604df |
+ # Command name is without peer_
|
|
|
3604df |
+ frame = inspect.stack()[1]
|
|
|
3604df |
+ module = inspect.getmodule(frame[0])
|
|
|
3604df |
+ actual_file = module.__file__
|
|
|
3604df |
+ # If file is symlink then find actual file
|
|
|
3604df |
+ if os.path.islink(actual_file):
|
|
|
3604df |
+ actual_file = os.readlink(actual_file)
|
|
|
3604df |
+
|
|
|
3604df |
+ # Get the name of file without peer_
|
|
|
3604df |
+ cmd_name = os.path.basename(actual_file).replace("peer_", "")
|
|
|
3604df |
+ cmd = ["gluster", "system::", "execute", cmd_name, name] + args
|
|
|
3604df |
+ rc, out, err = execute(cmd)
|
|
|
3604df |
+ if rc != 0:
|
|
|
3604df |
+ raise GlusterCmdException((rc, out, err, " ".join(cmd)))
|
|
|
3604df |
+
|
|
|
3604df |
+ out = out.strip().splitlines()
|
|
|
3604df |
+
|
|
|
3604df |
+ # JSON decode each line and construct one object with node id as key
|
|
|
3604df |
+ all_nodes_data = {}
|
|
|
3604df |
+ for node_data in out:
|
|
|
3604df |
+ data = json.loads(node_data)
|
|
|
3604df |
+ all_nodes_data[data["nodeid"]] = {
|
|
|
3604df |
+ "nodeid": data.get("nodeid"),
|
|
|
3604df |
+ "ok": data.get("ok"),
|
|
|
3604df |
+ "output": data.get("output", ""),
|
|
|
3604df |
+ "error": data.get("error", "")}
|
|
|
3604df |
+
|
|
|
3604df |
+ # gluster pool list
|
|
|
3604df |
+ pool_list = get_pool_list()
|
|
|
3604df |
+
|
|
|
3604df |
+ data_out = []
|
|
|
3604df |
+ # Iterate pool_list and merge all_nodes_data collected above
|
|
|
3604df |
+ # If a peer node is down then set node_up = False
|
|
|
3604df |
+ for p in pool_list:
|
|
|
3604df |
+ p_data = all_nodes_data.get(p.get("nodeid"), None)
|
|
|
3604df |
+ row_data = NodeOutput(node_up=False,
|
|
|
3604df |
+ hostname=p.get("hostname"),
|
|
|
3604df |
+ nodeid=p.get("nodeid"),
|
|
|
3604df |
+ ok=False)
|
|
|
3604df |
+
|
|
|
3604df |
+ if p_data is not None:
|
|
|
3604df |
+ # Node is UP
|
|
|
3604df |
+ row_data.node_up = True
|
|
|
3604df |
+ row_data.ok = p_data.get("ok")
|
|
|
3604df |
+ row_data.output = p_data.get("output")
|
|
|
3604df |
+ row_data.error = p_data.get("error")
|
|
|
3604df |
+
|
|
|
3604df |
+ data_out.append(row_data)
|
|
|
3604df |
+
|
|
|
3604df |
+ return data_out
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def sync_file_to_peers(fname):
|
|
|
3604df |
+ # Copy file from current node to all peer nodes, fname
|
|
|
3604df |
+ # is path after GLUSTERD_WORKDIR
|
|
|
3604df |
+ cmd = ["gluster", "system::", "copy", "file", fname]
|
|
|
3604df |
+ rc, out, err = execute(cmd)
|
|
|
3604df |
+ if rc != 0:
|
|
|
3604df |
+ raise GlusterCmdException((rc, out, err))
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+class Cmd(object):
|
|
|
3604df |
+ name = ""
|
|
|
3604df |
+
|
|
|
3604df |
+ def run(self, args):
|
|
|
3604df |
+ # Must required method. Raise NotImplementedError if derived class
|
|
|
3604df |
+ # not implemented this method
|
|
|
3604df |
+ raise NotImplementedError("\"run(self, args)\" method is "
|
|
|
3604df |
+ "not implemented by \"{0}\"".format(
|
|
|
3604df |
+ self.__class__.__name__))
|
|
|
3604df |
+
|
|
|
3604df |
+
|
|
|
3604df |
+def runcli():
|
|
|
3604df |
+ # Get list of Classes derived from class "Cmd" and create
|
|
|
3604df |
+ # a subcommand as specified in the Class name. Call the args
|
|
|
3604df |
+ # method by passing subcommand parser, Derived class can add
|
|
|
3604df |
+ # arguments to the subcommand parser.
|
|
|
3604df |
+ for c in Cmd.__subclasses__():
|
|
|
3604df |
+ cls = c()
|
|
|
3604df |
+ if getattr(cls, "name", "") == "":
|
|
|
3604df |
+ raise NotImplementedError("\"name\" is not added "
|
|
|
3604df |
+ "to \"{0}\"".format(
|
|
|
3604df |
+ cls.__class__.__name__))
|
|
|
3604df |
+
|
|
|
3604df |
+ p = subparsers.add_parser(cls.name)
|
|
|
3604df |
+ args_func = getattr(cls, "args", None)
|
|
|
3604df |
+ if args_func is not None:
|
|
|
3604df |
+ args_func(p)
|
|
|
3604df |
+
|
|
|
3604df |
+ # A dict to save subcommands, key is name of the subcommand
|
|
|
3604df |
+ subcommands[cls.name] = cls
|
|
|
3604df |
+
|
|
|
3604df |
+ # Get all parsed arguments
|
|
|
3604df |
+ args = parser.parse_args()
|
|
|
3604df |
+
|
|
|
3604df |
+ # Get the subcommand to execute
|
|
|
3604df |
+ cls = subcommands.get(args.mode, None)
|
|
|
3604df |
+
|
|
|
3604df |
+ # Run
|
|
|
3604df |
+ if cls is not None:
|
|
|
3604df |
+ cls.run(args)
|
|
|
3604df |
diff --git a/glusterfs.spec.in b/glusterfs.spec.in
|
|
|
3604df |
index 06f1de1..a60f216 100644
|
|
|
3604df |
--- a/glusterfs.spec.in
|
|
|
3604df |
+++ b/glusterfs.spec.in
|
|
|
3604df |
@@ -1249,6 +1249,7 @@ exit 0
|
|
|
3604df |
# introducing glusterfs module in site packages.
|
|
|
3604df |
# so that all other gluster submodules can reside in the same namespace.
|
|
|
3604df |
%{python_sitelib}/gluster/__init__.*
|
|
|
3604df |
+%{python_sitelib}/gluster/cliutils
|
|
|
3604df |
|
|
|
3604df |
%if ( 0%{!?_without_rdma:1} )
|
|
|
3604df |
%files rdma
|
|
|
3604df |
@@ -2008,6 +2009,9 @@ end
|
|
|
3604df |
|
|
|
3604df |
%changelog
|
|
|
3604df |
* Fri Sep 16 2016 Aravinda VK <avishwan@redhat.com>
|
|
|
3604df |
+- Added Python subpackage "cliutils" under gluster (#1342356)
|
|
|
3604df |
+
|
|
|
3604df |
+* Fri Sep 16 2016 Aravinda VK <avishwan@redhat.com>
|
|
|
3604df |
- Changed attribute of eventsconfig.json file as same as other configs (#1375532)
|
|
|
3604df |
|
|
|
3604df |
* Fri Sep 16 2016 Aravinda VK <avishwan@redhat.com>
|
|
|
3604df |
--
|
|
|
3604df |
1.7.1
|
|
|
3604df |
|