summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/distaf/__init__.py0
-rw-r--r--tests/distaf/distaf_libs/__init__.py0
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/brick_ops.py90
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/gluster_init.py70
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/mount_ops.py62
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/peer_ops.py204
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/quota_ops.py44
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/rebalance.py121
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/snap_ops.py85
-rw-r--r--tests/distaf/distaf_libs/gluster_libs/volume_ops.py517
-rw-r--r--tests/distaf/distaf_libs/io_libs/file_ops.py245
-rw-r--r--tests/distaf/tests_d/__init__.py0
-rw-r--r--tests/distaf/tests_d/examples/__init__.py0
-rw-r--r--tests/distaf/tests_d/examples/test_basic_gluster_tests.py49
-rw-r--r--tests/distaf/tests_d/examples/test_docstring.py130
-rw-r--r--tests/distaf/tests_d/examples/test_passfail.py97
16 files changed, 1714 insertions, 0 deletions
diff --git a/tests/distaf/__init__.py b/tests/distaf/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tests/distaf/__init__.py
diff --git a/tests/distaf/distaf_libs/__init__.py b/tests/distaf/distaf_libs/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tests/distaf/distaf_libs/__init__.py
diff --git a/tests/distaf/distaf_libs/gluster_libs/brick_ops.py b/tests/distaf/distaf_libs/gluster_libs/brick_ops.py
new file mode 100644
index 00000000000..3c363c2bfed
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/brick_ops.py
@@ -0,0 +1,90 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import re
+from distaf.util import tc
+
+"""
+ This file contains the gluster brick operations like
+ add-brick, bring_down_brick replace/remove brick
+"""
+
+
+def add_brick(volname, nbricks, replica=1, stripe=1, peers='', mnode=''):
+ """
+ Does the gluster add-brick. If peer is '', peers from the config
+ is taken. And replica/stripe will not be used by default.
+ Returns the output of add-brick command, which would be a tuple of
+ (retcode, stdout, sstderr) from gluster add-brick command.
+ """
+ global tc
+ if peers == '':
+ peers = tc.peers[:]
+ if mnode == '':
+ mnode = tc.nodes[0]
+ replica = int(replica)
+ stripe = int(stripe)
+ volinfo = tc.run(mnode, "gluster volume info | egrep \"^Brick[0-9]+\"", \
+ verbose=False)
+ if volinfo[0] != 0:
+ tc.logger.error("Unable to get volinfo for add-brick")
+ return (-1, -1, -1)
+ bi = int(re.findall(r"%s_brick([0-9]+)" % volname, volinfo[1])[-1]) + 1
+ tempn = 0
+ n = 0
+ add_bricks = ''
+ brick_root = "/bricks"
+ for i in range(bi, bi + nbricks):
+ sn = len(re.findall(r"%s" % peers[n], volinfo[1])) + tempn
+ add_bricks = "%s %s:%s/brick%d/%s_brick%d" % (add_bricks, peers[n], \
+ brick_root, sn, volname, i)
+ if n < len(peers[:]) - 1:
+ n = n + 1
+ else:
+ n = 0
+ tempn = tempn + 1
+ repc = strc = ''
+ if replica != 1:
+ repc = "replica %d" % replica
+ if stripe != 1:
+ strc = "stripe %d" % stripe
+ ret = tc.run(mnode, "gluster volume add-brick %s %s %s %s" % \
+ (volname, repc, strc, add_bricks))
+ return ret
+
+
+def bring_down_brick(volname, bindex, node=''):
+ """
+ Kills the glusterfsd process of the particular brick
+ Returns True on success and False on failure
+ """
+ global tc
+ if node == '':
+ node = tc.nodes[0]
+ ret, rnode, _ = tc.run(node, "gluster volume info %s | egrep \"^Brick%d:\""
+ " | awk '{print $2}' | awk -F : '{print $1}'"
+ % (volname, bindex))
+ if ret != 0:
+ return False
+ ret, _, _ = tc.run(rnode.rstrip(), \
+ "pid=`cat /var/lib/glusterd/vols/%s/run/*%s_brick%d.pid` && kill -15 $pid \
+ || kill -9 $pid" % (volname, volname, bindex - 1))
+ if ret != 0:
+ return False
+ else:
+ return True
diff --git a/tests/distaf/distaf_libs/gluster_libs/gluster_init.py b/tests/distaf/distaf_libs/gluster_libs/gluster_init.py
new file mode 100644
index 00000000000..633208ca49c
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/gluster_init.py
@@ -0,0 +1,70 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc
+
+"""
+ This file contains the glusterd and other initial gluster
+ options like start/stop glusterd and env_setup_servers for
+ initial back-end brick preperation
+"""
+
+
+def start_glusterd(servers=''):
+ """
+ Starts glusterd in all servers if they are not running
+
+ Returns True if glusterd started in all servers
+ Returns False if glusterd failed to start in any server
+
+ (Will be enhanced to support systemd in future)
+ """
+ if servers == '':
+ servers = tc.servers
+ ret, _ = tc.run_servers("pgrep glusterd || service glusterd start", \
+ servers=servers)
+ return ret
+
+
+def stop_glusterd(servers=''):
+ """
+ Stops the glusterd in specified machine(s)
+
+ Returns True if glusterd is stopped in all nodes
+ Returns False on failure
+ """
+ if servers == '':
+ servers = tc.servers
+ ret, _ = tc.run_servers("service glusterd stop", servers=servers)
+ return ret
+
+
+#TODO: THIS IS NOT IMPLEMENTED YET. PLEASE DO THIS MANUALLY
+# TILL WE IMPLEMENT THIS PART
+
+def env_setup_servers(snap=True, servers=''):
+ """
+ Sets up the env for all the tests
+ Install all the gluster bits and it's dependencies
+ Installs the xfs bits and then formats the backend fs for gluster use
+
+ Returns 0 on success and non-zero upon failing
+ """
+ tc.logger.info("The function isn't implemented yet")
+ tc.logger.info("Please setup the bricks manually.")
+ return True
diff --git a/tests/distaf/distaf_libs/gluster_libs/mount_ops.py b/tests/distaf/distaf_libs/gluster_libs/mount_ops.py
new file mode 100644
index 00000000000..165e389f4da
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/mount_ops.py
@@ -0,0 +1,62 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc
+
+
+def mount_volume(volname, mtype='glusterfs', mpoint='/mnt/glusterfs', \
+ mserver='', mclient='', options=''):
+ """
+ Mount the gluster volume with specified options
+ Takes the volume name as mandatory argument
+
+ Returns a tuple of (returncode, stdout, stderr)
+ Returns (0, '', '') if already mounted
+ """
+ global tc
+ if mserver == '':
+ mserver = tc.nodes[0]
+ if mclient == '':
+ mclient = tc.clients[0]
+ if options != '':
+ options = "-o %s" % options
+ if mtype == 'nfs' and options != '':
+ options = "%s,vers=3" % options
+ elif mtype == 'nfs' and options == '':
+ options = '-o vers=3'
+ ret, _, _ = tc.run(mclient, "mount | grep %s | grep %s | grep \"%s\"" \
+ % (volname, mpoint, mserver), verbose=False)
+ if ret == 0:
+ tc.logger.debug("Volume %s is already mounted at %s" \
+ % (volname, mpoint))
+ return (0, '', '')
+ mcmd = "mount -t %s %s %s:%s %s" % \
+ (mtype, options, mserver, volname, mpoint)
+ tc.run(mclient, "test -d %s || mkdir -p %s" % (mpoint, mpoint), \
+ verbose=False)
+ return tc.run(mclient, mcmd)
+
+
+def umount_volume(client, mountpoint):
+ """
+ unmounts the mountpoint
+ Returns the output of umount command
+ """
+ cmd = "umount %s || umount -f %s || umount -l %s" \
+ % (mountpoint, mountpoint, mountpoint)
+ return tc.run(client, cmd)
diff --git a/tests/distaf/distaf_libs/gluster_libs/peer_ops.py b/tests/distaf/distaf_libs/gluster_libs/peer_ops.py
new file mode 100644
index 00000000000..f2c0d0c0009
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/peer_ops.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ Description: Library for gluster peer operations.
+"""
+
+
+import re
+import time
+from distaf.util import tc
+
+
+def peer_probe(pnode='', servers='', timeout=10):
+ """
+ Does peer probe and validates the same
+ Returns True on success and False on failure
+ Note: Input for parameter 'servers' should be in list format
+ """
+ if pnode == '':
+ pnode = tc.nodes[0]
+ if servers == '':
+ servers = tc.nodes[1:]
+
+ nodes_pool_list = nodes_from_pool_list(pnode)
+ if not nodes_pool_list:
+ return False
+ for server in servers:
+ if server not in nodes_pool_list:
+ ret = tc.run(pnode, "gluster peer probe %s" % server)
+ if ret[0] != 0 or \
+ re.search(r'^peer\sprobe\:\ssuccess(.*)', ret[1]) is None:
+ tc.logger.error("Failed to do peer probe for node %s" % server)
+ return False
+ time.sleep(timeout)
+
+ #Validating whether peer probe is successful
+ if not validate_peer_status(pnode, servers):
+ tc.logger.error("peer probe validation failed")
+ return False
+
+ return True
+
+
+def peer_detach(pnode='', servers='', force=False, timeout=10):
+ """
+ Does peer detach and validates the same
+ Returns True on success and False on failure
+ Note: Input for parameter 'servers' should be in list format
+ """
+ if pnode == '':
+ pnode = tc.nodes[0]
+ if servers == '':
+ servers = tc.nodes[1:]
+
+ for server in servers:
+ if force:
+ cmd = "gluster peer detach %s force" % server
+ else:
+ cmd = "gluster peer detach %s" % server
+ ret = tc.run(pnode, cmd)
+ if ret[0] != 0 or re.search(r'^peer\sdetach\:\ssuccess(.*)', ret[1]) \
+ is None:
+ tc.logger.error("Failed to do peer detach for node %s" % server)
+ return False
+
+ time.sleep(timeout)
+ #Validating whether peer detach is successful
+ if validate_peer_status(pnode, servers):
+ tc.logger.error("peer detach validatiom failed")
+ return False
+
+ return True
+
+
+def peer_status(pnode=''):
+ """
+ Does peer status on the given node
+ Returns: On success, peer status information in list of dicts
+ If there is only one peer, then an empty list is sent
+ On failure, None
+ """
+ if pnode == '':
+ pnode = tc.nodes[0]
+ ret = tc.run(pnode, "gluster peer status")
+ if ret[0] != 0:
+ tc.logger.error("Failed to execute peer status command in node %s" \
+ % pnode)
+ return None
+
+ status_list = ret[1].strip().split('\n')
+ if "Number of Peers: 0" == status_list[0] and len(status_list) == 1:
+ tc.logger.debug("Only one server in the cluster")
+ return status_list[1:]
+
+ status_list = status_list[1:]
+ peer_list = []
+ for status in status_list:
+ stat = [stat for stat in status.split('\n') if stat != '']
+ temp_dict = {}
+ for element in stat:
+ elmt = element.split(':')
+ temp_dict[elmt[0].strip()] = elmt[1].strip()
+ peer_list.append(temp_dict)
+ return peer_list
+
+
+def validate_peer_status(pnode='', servers=''):
+ """
+ Checks whether peer probe succeeds using peer status command
+ Returns True on success and False on failure
+ Note: Input for parameter 'servers' should be in list format
+ """
+ if pnode == '':
+ pnode = tc.nodes[0]
+ if servers == '':
+ servers = tc.nodes[1:]
+
+ check_flag = False
+ status = peer_status(pnode)
+ if status == []:
+ return True
+ if status == None:
+ return False
+
+ for stat in status:
+ if stat['Hostname'] in servers:
+ if re.match(r'([0-9a-f]{8})(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}', \
+ stat['Uuid'], re.I) is None or \
+ stat['State'] != "Peer in Cluster (Connected)":
+ tc.logger.error("peer probe unsuccessful for node %s" % \
+ stat['Hostname'])
+ check_flag = True
+
+ if check_flag or not len(set(servers).intersection([stat_key['Hostname'] \
+ for stat_key in status])):
+ return False
+
+ return True
+
+
+def pool_list(pnode=''):
+ """
+ Does pool list on the given node
+ Returns: On success, pool list information in list of dictionary format
+ On Failure, None
+ """
+ if pnode == '':
+ pnode = tc.nodes[0]
+
+ ret = tc.run(pnode, "gluster pool list")
+ if ret[0] != 0:
+ tc.logger.error("Failed to execute pool list in node %s" % pnode)
+ return None
+
+ pool_info = []
+ for index, item in enumerate(ret[1].split('\n')[:-1]):
+ match = re.search(r'(\S+)\s*\t*\s*(\S+)\s*\t*\s*(\S+)\s*', item)
+ if match is not None:
+ if index == 0:
+ keys = match.groups()
+ else:
+ temp_dict = {}
+ for num, element in enumerate(match.groups()):
+ temp_dict[keys[num]] = element
+ pool_info.append(temp_dict)
+
+ return pool_info
+
+
+def nodes_from_pool_list(pnode=''):
+ """
+ Runs the "gluster pool list" on the specified server
+ and returns a list of servers from the pool list
+
+ Returns False on failure and list of servers on success
+ """
+ if pnode == '':
+ pnode = tc.nodes[0]
+ servers = []
+ nodes_from_pool = pool_list(pnode)
+ if not nodes_from_pool:
+ return False
+ for server in nodes_from_pool:
+ if server.get('Hostname') == "localhost":
+ servers.insert(0, pnode)
+ else:
+ servers.append(server.get("Hostname"))
+ return servers
diff --git a/tests/distaf/distaf_libs/gluster_libs/quota_ops.py b/tests/distaf/distaf_libs/gluster_libs/quota_ops.py
new file mode 100644
index 00000000000..fe90b5f359f
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/quota_ops.py
@@ -0,0 +1,44 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc
+
+
+def enable_quota(volname, server=''):
+ """
+ Enables quota on the specified volume
+
+ Returns the output of quota cli command
+ """
+ if server == '':
+ server = tc.nodes[0]
+ cmd = "gluster volume quota %s enable" % volname
+ return tc.run(server, cmd)
+
+
+def set_quota_limit(volname, path='/', limit='100GB', server=''):
+ """
+ Sets limit-usage on the path of the specified volume to
+ specified limit
+
+ Returs the output of quota limit-usage command
+ """
+ if server == '':
+ server = tc.nodes[0]
+ cmd = "gluster volume quota %s limit-usage %s %s" % (volname, path, limit)
+ return tc.run(server, cmd)
diff --git a/tests/distaf/distaf_libs/gluster_libs/rebalance.py b/tests/distaf/distaf_libs/gluster_libs/rebalance.py
new file mode 100644
index 00000000000..e36b7ee9855
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/rebalance.py
@@ -0,0 +1,121 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import re
+import time
+from distaf.util import tc
+
+"""
+ Libraries containing gluster rebalance operations
+"""
+
+def get_rebal_nodes(server):
+ '''
+ This function finds out the number of rebalance nodes from
+ gluster v info command
+
+ Returns the number of nodes participating in rebalance process
+ '''
+ val = tc.run(server, \
+"gluster v info | grep 'Brick[0-9]' | cut -d ':' -f 2 | sed 's/\ //'")
+ nlist = val[1].rstrip().split('\n')
+ nnodes = list(set(nlist))
+ return len(nnodes)
+
+def get_rebal_dict(status):
+ '''
+ This function returns the rebalance status info in the form of dictionary
+ '''
+ _list = status.split('\n')
+ rebal_dict = {}
+ for _item in _list:
+ _match = re.search('.*?(\S+)\s+(\d+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\w+\s*\w+)\s+(\S+).*', _item, re.S)
+ if _match is not None:
+ rebal_dict[_match.group(1)] = [_match.group(2), _match.group(3), \
+ _match.group(4), _match.group(5), _match.group(6), \
+ _match.group(7),_match.group(8)]
+ return rebal_dict
+
+
+def get_rebal_status(volname, server=''):
+ '''
+ This function gives rebalance status
+ Valid states are started/failed/in progress/completed
+ if the server pararmeter is empty it takes node info from config file
+ '''
+ if server == "":
+ server = tc.nodes[0]
+ status = tc.run(server, "gluster v rebalance %s status" %volname)
+ if status[0] != 0:
+ if "not started" in status[2]:
+ tc.logger.error("Rebalance has not started")
+ return ("not started", " ")
+ else:
+ tc.logger.error("error")
+ return ("error", " ")
+ else:
+ rebal_dict = get_rebal_dict(status[1])
+ if "failed" in status[1]:
+ tc.logger.error("Rebalance status command failed")
+ return ("failed", rebal_dict)
+ elif "in progress" in status[1]:
+ tc.logger.info("Rebalance is in progress")
+ return ("in progress", rebal_dict)
+ elif "completed" in status[1]:
+ counter = status[1].count("completed")
+ nnodes = get_rebal_nodes(server)
+ if counter == nnodes:
+ tc.logger.info("Rebalance is completed")
+ return ("completed", rebal_dict)
+ else:
+ tc.logger.error("Rebalacne has not completed on all nodes")
+ return ("invalid status", rebal_dict)
+
+def wait_rebal_complete(volname, time_out = 300, server=''):
+ '''
+ This function calls rebalance_status_once function and
+ waits if the rebalance status is in progress, exists on timeout,
+ default timeout is 300sec(5 min)
+ '''
+ ret = get_rebal_status(volname, server)
+ while time_out != 0 and ret[0] == "in progress":
+ time_out = time_out - 20
+ time.sleep(20)
+ ret = get_rebal_status(volname, server)
+ return ret
+
+
+def rebal_start(volname, server=''):
+ """
+ Simple interface to start the gluster rebalance
+ @ pararmeter:
+ * volname
+ * server - defaults to tc.nodes[0]
+ @ returns:
+ True on success
+ False otherwise
+ """
+ if server == '':
+ server = tc.nodes[0]
+ ret = tc.run(server, "gluster volume rebalance %s start" % volname)
+ if ret[0] != 0:
+ tc.logger.error("rebalance start %s failed" % volname)
+ return False
+ else:
+ tc.logger.debug("rebalance start %s successful" % volname)
+ return True
diff --git a/tests/distaf/distaf_libs/gluster_libs/snap_ops.py b/tests/distaf/distaf_libs/gluster_libs/snap_ops.py
new file mode 100644
index 00000000000..bc4067d8621
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/snap_ops.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc
+from distaf.gluster_libs.volume_ops import start_volume, stop_volume
+
+
+def snap_create(volname, snapname, server='', desc=''):
+ """
+ Runs snap create command and returns the output
+ """
+ if server == '':
+ server = tc.nodes[0]
+ if desc != '':
+ desc = "description %s" % desc
+ ret = tc.run(server, "gluster snapshot create %s %s %s" \
+ % (volname, snapname, desc))
+ return ret
+
+
+def snap_activate(snapname, server=''):
+ """
+ Activate the snap and returns the output
+ """
+ if server == '':
+ server = tc.nodes[0]
+ return tc.run(server, "gluster snapshot activate %s" % snapname)
+
+
+def snap_delete(snapname, server=''):
+ """
+ Deletes snapshot and returns the output
+ """
+ if server == '':
+ server = tc.nodes[0]
+ cmd = "gluster snapshot delete %s --mode=script" % snapname
+ return tc.run(server, cmd)
+
+
+def snap_delete_all(volname, server=''):
+ """
+ Deletes one or more snaps and returns the output
+ """
+ if server == '':
+ server = tc.nodes[0]
+ cmd = 'ret=0; for i in `gluster snapshot list %s`; do \
+gluster snapshot delete $i --mode=script || ret=1; done ; exit $ret' % volname
+ return tc.run(server, cmd)
+
+
+def snap_restore(volname, snapname, server=''):
+ """
+ stops the volume restore the snapshot and starts the volume
+
+ Returns True upon success, False on in any step
+ """
+ if server == '':
+ server = tc.nodes[0]
+ ret = stop_volume(volname, server)
+ if not ret:
+ return False
+ ret = tc.run(server, "gluster snapshot restore %s" % snapname)
+ if ret[0] != 0:
+ tc.logger.error("snapshot restore failed")
+ return False
+ ret = start_volume(volname, server)
+ if not ret:
+ return False
+ return True
diff --git a/tests/distaf/distaf_libs/gluster_libs/volume_ops.py b/tests/distaf/distaf_libs/gluster_libs/volume_ops.py
new file mode 100644
index 00000000000..6de08719e0c
--- /dev/null
+++ b/tests/distaf/distaf_libs/gluster_libs/volume_ops.py
@@ -0,0 +1,517 @@
+#!/usr/bin/env python
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import re
+import time
+from distaf.util import tc
+from pprint import pformat
+try:
+ import xml.etree.cElementTree as etree
+except ImportError:
+ import xml.etree.ElementTree as etree
+from distaf.gluster_libs.mount_ops import mount_volume
+from distaf.gluster_libs.peer_ops import peer_probe, nodes_from_pool_list
+from distaf.gluster_libs.gluster_init import env_setup_servers, start_glusterd
+
+"""
+ This file contains the gluster volume operations like create volume,
+ start/stop volume etc
+"""
+
+
+def create_volume(volname, dist, rep=1, stripe=1, trans='tcp', servers='', \
+ snap=True, disp=1, dispd=1, red=1):
+ """
+ Create the gluster volume specified configuration
+ volname and distribute count are mandatory argument
+ """
+ if servers == '':
+ servers = nodes_from_pool_list()
+ if not servers:
+ servers = tc.nodes
+ dist = int(dist)
+ rep = int(rep)
+ stripe = int(stripe)
+ disp = int(disp)
+ dispd = int(dispd)
+ red = int(red)
+ dispc = 1
+
+ if disp != 1 and dispd != 1:
+ tc.logger.error("volume can't have both disperse and disperse-data")
+ return (-1, None, None)
+ if disp != 1:
+ dispc = int(disp)
+ elif dispd != 1:
+ dispc = int(dispd) + int(red)
+
+ number_of_bricks = dist * rep * stripe * dispc
+ replica = stripec = disperse = disperse_data = redundancy = ''
+ brick_root = '/bricks'
+ n = 0
+ tempn = 0
+ bricks_list = ''
+ rc = tc.run(servers[0], "gluster volume info | egrep \"^Brick[0-9]+\"", \
+ verbose=False)
+ for i in range(0, number_of_bricks):
+ if not snap:
+ bricks_list = "%s %s:%s/%s_brick%d" % \
+ (bricks_list, servers[n], brick_root, volname, i)
+ else:
+ sn = len(re.findall(servers[n], rc[1])) + tempn
+ bricks_list = "%s %s:%s/brick%d/%s_brick%d" % \
+ (bricks_list, servers[n], brick_root, sn, volname, i)
+ if n < len(servers[:]) - 1:
+ n = n + 1
+ else:
+ n = 0
+ tempn = tempn + 1
+
+ if rep != 1:
+ replica = "replica %d" % rep
+ if stripe != 1:
+ stripec = "stripe %d" % stripe
+ ttype = "transport %s" % trans
+ if disp != 1:
+ disperse = "disperse %d" % disp
+ redundancy = "redundancy %d" % red
+ elif dispd != 1:
+ disperse_data = "disperse-data %d" % dispd
+ redundancy = "redundancy %d" % red
+
+ ret = tc.run(servers[0], "gluster volume create %s %s %s %s %s %s %s %s \
+--mode=script" % (volname, replica, stripec, disperse, disperse_data, \
+redundancy, ttype, bricks_list))
+ return ret
+
+
+def start_volume(volname, mnode='', force=False):
+ """
+ Starts the gluster volume
+ Returns True if success and False if failure
+ """
+ if mnode == '':
+ mnode = tc.nodes[0]
+ frce = ''
+ if force:
+ frce = 'force'
+ ret = tc.run(mnode, "gluster volume start %s %s" % (volname, frce))
+ if ret[0] != 0:
+ return False
+ return True
+
+
+def stop_volume(volname, mnode='', force=False):
+ """
+ Stops the gluster volume
+ Returns True if success and False if failure
+ """
+ if mnode == '':
+ mnode = tc.nodes[0]
+ frce = ''
+ if force:
+ frce = 'force'
+ ret = tc.run(mnode, "gluster volume stop %s %s --mode=script" \
+ % (volname, frce))
+ if ret[0] != 0:
+ return False
+ return True
+
+
+def delete_volume(volname, mnode=''):
+ """
+ Deletes the gluster volume
+ Returns True if success and False if failure
+ """
+ if mnode == '':
+ mnode = tc.nodes[0]
+ volinfo = get_volume_info(volname, mnode)
+ if volinfo is None or volname not in volinfo:
+ tc.logger.info("Volume %s does not exist in %s" % (volname, mnode))
+ return True
+ bricks = volinfo[volname]['bricks']
+ ret = tc.run(mnode, "gluster volume delete %s --mode=script" % volname)
+ if ret[0] != 0:
+ return False
+ try:
+ del tc.global_flag[volname]
+ except KeyError:
+ pass
+ for brick in bricks:
+ node, vol_dir = brick.split(":")
+ ret = tc.run(node, "rm -rf %s" % vol_dir)
+
+ return True
+
+
+def reset_volume(volname, mnode='', force=False):
+ """
+ Reset the gluster volume
+ Returns True if success and False if failure
+ """
+ if mnode == '':
+ mnode = tc.nodes[0]
+ frce = ''
+ if force:
+ frce = 'force'
+ ret = tc.run(mnode, "gluster volume reset %s %s --mode=script" \
+ % (volname, frce))
+ if ret[0] != 0:
+ return False
+ return True
+
+
+def cleanup_volume(volname, mnode=''):
+ """
+ stops and deletes the volume
+ returns True on success and False otherwise
+
+ TODO: Add snapshot cleanup part here
+ """
+ if mnode == '':
+ mnode = tc.nodes[0]
+ ret = stop_volume(volname, mnode, True) | \
+ delete_volume(volname, mnode)
+ if not ret:
+ tc.logger.error("Unable to cleanup the volume %s" % volname)
+ return False
+ return True
+
+
+def setup_meta_vol(servers=''):
+ """
+ Creates, starts and mounts the gluster meta-volume on the servers
+ specified.
+ """
+ if servers == '':
+ servers = tc.nodes
+ meta_volname = 'gluster_shared_storage'
+ mount_point = '/var/run/gluster/shared_storage'
+ metav_dist = int(tc.config_data['META_VOL_DIST_COUNT'])
+ metav_rep = int(tc.config_data['META_VOL_REP_COUNT'])
+ _num_bricks = metav_dist * metav_rep
+ repc = ''
+ if metav_rep > 1:
+ repc = "replica %d" % metav_rep
+ bricks = ''
+ brick_root = "/bricks"
+ _n = 0
+ for i in range(0, _num_bricks):
+ bricks = "%s %s:%s/%s_brick%d" % (bricks, servers[_n], \
+ brick_root, meta_volname, i)
+ if _n < len(servers) - 1:
+ _n = _n + 1
+ else:
+ _n = 0
+ gluster_cmd = "gluster volume create %s %s %s force" \
+ % (meta_volname, repc, bricks)
+ ret = tc.run(servers[0], gluster_cmd)
+ if ret[0] != 0:
+ tc.logger.error("Unable to create meta volume")
+ return False
+ ret = start_volume(meta_volname, servers[0])
+ if not ret:
+ tc.logger.error("Unable to start the meta volume")
+ return False
+ time.sleep(5)
+ for server in servers:
+ ret = mount_volume(meta_volname, 'glusterfs', mount_point, server, \
+ server)
+ if ret[0] != 0:
+ tc.logger.error("Unable to mount meta volume on %s" % server)
+ return False
+ return True
+
+
+def setup_vol(volname='', dist='', rep='', dispd='', red='', stripe='', \
+ trans='', servers=''):
+ """
+ Setup a gluster volume for testing.
+ It first formats the back-end bricks and then creates a
+ trusted storage pool by doing peer probe. And then it creates
+ a volume of specified configuration.
+
+ When the volume is created, it sets a global flag to indicate
+ that the volume is created. If another testcase calls this
+ function for the second time with same volume name, the function
+ checks for the flag and if found, will return True.
+
+ Returns True on success and False for failure.
+ """
+ if servers == '':
+ servers = tc.nodes
+ volinfo = get_volume_info(server=servers[0])
+ if volinfo is not None and volname in volinfo.keys():
+ tc.logger.debug("volume %s already exists in %s. Returning..." \
+ % (volname, servers[0]))
+ return True
+ ret = env_setup_servers(servers=servers)
+ if not ret:
+ tc.logger.error("Formatting backend bricks failed. Aborting...")
+ return False
+ ret = start_glusterd(servers)
+ if not ret:
+ tc.logger.error("glusterd did not start in at least one server")
+ return False
+ time.sleep(5)
+ ret = peer_probe(servers[0], servers[1:])
+ if not ret:
+ tc.logger.error("Unable to peer probe one or more machines")
+ return False
+ if rep != 1 and dispd != 1:
+ tc.logger.warning("Both replica count and disperse count is specified")
+ tc.logger.warning("Ignoring the disperse and using the replica count")
+ dispd = 1
+ red = 1
+ ret = create_volume(volname, dist, rep, stripe, trans, servers, \
+ dispd=dispd, red=red)
+ if ret[0] != 0:
+ tc.logger.error("Unable to create volume %s" % volname)
+ return False
+ time.sleep(2)
+ ret = start_volume(volname, servers[0])
+ if not ret:
+ tc.logger.error("volume start %s failed" % volname)
+ return False
+ tc.global_flag[volname] = True
+ return True
+
+
+def _parse_volume_status_xml(root_xml):
+ """
+ Helper module for get_volume_status. It takes root xml object as input,
+ parses and returns the 'volume' tag xml object.
+ """
+ for element in root_xml:
+ if element.findall("volume"):
+ return element.findall("volume")
+ root_vol = _parse_volume_status_xml(element)
+ if root_vol is not None:
+ return root_vol
+
+
+def parse_xml(tag_obj):
+ """
+ This module takes any xml element object and parses all the child nodes
+ and returns the parsed data in dictionary format
+ """
+ node_dict = {}
+ for tag in tag_obj:
+ if re.search(r'\n\s+', tag.text) is not None:
+ port_dict = {}
+ port_dict = parse_xml(tag)
+ node_dict[tag.tag] = port_dict
+ else:
+ node_dict[tag.tag] = tag.text
+ return node_dict
+
+
+def get_volume_status(volname='all', service='', options='', mnode=''):
+ """
+ This module gets the status of all or specified volume(s)/brick
+ @parameter:
+ * mnode - <str> (optional) name of the node to execute the volume
+ status command. If not given, the function takes the
+ first node from config file
+ * volname - <str> (optional) name of the volume to get status. It not
+ given, the function returns the status of all volumes
+ * service - <str> (optional) name of the service to get status.
+ serivce can be, [nfs|shd|<BRICK>|quotad]], If not given,
+ the function returns all the services
+ * options - <str> (optional) options can be,
+ [detail|clients|mem|inode|fd|callpool|tasks]. If not given,
+ the function returns the output of gluster volume status
+ @Returns: volume status in dict of dictionary format, on success
+ None, on failure
+ """
+
+ if mnode == '':
+ mnode = tc.nodes[0]
+
+ cmd = "gluster vol status %s %s %s --xml" % (volname, service, options)
+
+ ret = tc.run(mnode, cmd)
+ if ret[0] != 0:
+ tc.logger.error("Failed to execute gluster volume status command")
+ return None
+
+ root = etree.XML(ret[1])
+ volume_list = _parse_volume_status_xml(root)
+ if volume_list is None:
+ tc.logger.error("No volumes exists in the gluster")
+ return None
+
+ vol_status = {}
+ for volume in volume_list:
+ tmp_dict1 = {}
+ tmp_dict2 = {}
+ vol_name = [vol.text for vol in volume if vol.tag == "volName"]
+
+ # parsing volume status xml output
+ if options == 'tasks':
+ tasks = volume.findall("tasks")
+ for each_task in tasks:
+ tmp_dict3 = parse_xml(each_task)
+ node_name = 'task_status'
+ if 'task' in tmp_dict3.keys():
+ if node_name in tmp_dict2.keys():
+ tmp_dict2[node_name].append(tmp_dict3['task'])
+ else:
+ tmp_dict2[node_name] = [tmp_dict3['task']]
+ else:
+ tmp_dict2[node_name] = [tmp_dict3]
+ else:
+ nodes = volume.findall("node")
+ for each_node in nodes:
+ if each_node.find('path').text.startswith('/'):
+ node_name = each_node.find('hostname').text
+ else:
+ node_name = each_node.find('path').text
+ node_dict = parse_xml(each_node)
+ tmp_dict3 = {}
+ if "hostname" in node_dict.keys():
+ if node_dict['path'].startswith('/'):
+ tmp = node_dict["path"]
+ tmp_dict3[node_dict["path"]] = node_dict
+ else:
+ tmp_dict3[node_dict["hostname"]] = node_dict
+ tmp = node_dict["hostname"]
+ del tmp_dict3[tmp]["path"]
+ del tmp_dict3[tmp]["hostname"]
+
+ if node_name in tmp_dict1.keys():
+ tmp_dict1[node_name].append(tmp_dict3)
+ else:
+ tmp_dict1[node_name] = [tmp_dict3]
+
+ tmp_dict4 = {}
+ for item in tmp_dict1[node_name]:
+ for key, val in item.items():
+ tmp_dict4[key] = val
+ tmp_dict2[node_name] = tmp_dict4
+
+ vol_status[vol_name[0]] = tmp_dict2
+ tc.logger.debug("Volume status output: %s" \
+ % pformat(vol_status, indent=10))
+ return vol_status
+
+
+def get_volume_option(volname, option='all', server=''):
+ """
+ This module gets the option values for the given volume.
+ @parameter:
+ * volname - <str> name of the volume to get status.
+ * option - <str> (optional) name of the volume option to get status.
+ If not given, the function returns all the options for
+ the given volume
+ * server - <str> (optional) name of the server to execute the volume
+ status command. If not given, the function takes the
+ first node from config file
+ @Returns: value for the given volume option in dict format, on success
+ None, on failure
+ """
+ if server == '':
+ server = tc.nodes[0]
+
+ cmd = "gluster volume get %s %s" % (volname, option)
+ ret = tc.run(server, cmd)
+ if ret[0] != 0:
+ tc.logger.error("Failed to execute gluster volume get command")
+ return None
+
+ volume_option = {}
+ raw_output = ret[1].split("\n")
+ for line in raw_output[2:-1]:
+ match = re.search(r'^(\S+)(.*)', line.strip())
+ if match is None:
+ tc.logger.error("gluster get volume output is not in \
+ expected format")
+ return None
+
+ volume_option[match.group(1)] = match.group(2).strip()
+
+ return volume_option
+
+
+def get_volume_info(volname='all', server=''):
+ """
+ Fetches the volume information as displayed in the volume info.
+ Uses xml output of volume info and parses the into to a dict
+
+ Returns a dict of dicts.
+ -- Volume name is the first key
+ -- distCount/replicaCount/Type etc are second keys
+ -- The value of the each second level dict depends on the key
+ -- For distCount/replicaCount etc the value is key
+ -- For bricks, the value is a list of bricks (hostname:/brick_path)
+ """
+ if server == '':
+ server = tc.nodes[0]
+ ret = tc.run(server, "gluster volume info %s --xml" % volname, \
+ verbose=False)
+ if ret[0] != 0:
+ tc.logger.error("volume info returned error")
+ return None
+ root = etree.XML(ret[1])
+ volinfo = {}
+ for volume in root.findall("volInfo/volumes/volume"):
+ for elem in volume.getchildren():
+ if elem.tag == "name":
+ volname = elem.text
+ volinfo[volname] = {}
+ elif elem.tag == "bricks":
+ volinfo[volname]["bricks"] = []
+ for el in elem.getiterator():
+ if el.tag == "name":
+ volinfo[volname]["bricks"].append(el.text)
+ elif elem.tag == "options":
+ volinfo[volname]["options"] = {}
+ for option in elem.findall("option"):
+ for el in option.getchildren():
+ if el.tag == "name":
+ opt = el.text
+ if el.tag == "value":
+ volinfo[volname]["options"][opt] = el.text
+ else:
+ volinfo[volname][elem.tag] = elem.text
+ return volinfo
+
+
+def set_volume_option(volname, options, server=''):
+ """
+ This module sets the option values for the given volume.
+ @parameter:
+ * volname - <str> name of the volume to get status.
+ * option - list of <dict> volume options in key value format.
+ * server - <str> (optional) name of the server to execute the volume
+ status command. If not given, the function takes the
+ first node from config file
+ @Returns: True, on success
+ False, on failure
+ """
+ if server == '':
+ server = tc.nodes[0]
+ _rc = True
+ for option in options:
+ cmd = "gluster volume set %s %s %s" % (volname, option, \
+ options[option])
+ ret = tc.run(server, cmd)
+ if ret[0] != 0:
+ _rc = False
+ return _rc
diff --git a/tests/distaf/distaf_libs/io_libs/file_ops.py b/tests/distaf/distaf_libs/io_libs/file_ops.py
new file mode 100644
index 00000000000..e5c17432c44
--- /dev/null
+++ b/tests/distaf/distaf_libs/io_libs/file_ops.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Description: Library for file operations.
+"""
+
+
+import re
+import socket
+from distaf.util import tc
+
+
+def write_file(filename, file_contents=" ", create_mode='', filesize='', \
+ server=''):
+ """
+ This module writes the file along with file contents
+ @paramater:
+ * filename - <str> absolute path name of the file to be created
+ * file_contents - <str> (optional) file content
+ * create_mode - <str> (optional) mode to create the file
+ * filesize - <str> (optional) filesize
+ * server - <str> (optional) name of the server to write the
+ file. If not given, the function takes the
+ first node from config file
+ @Returns: True, on success
+ False, on failure
+ """
+ if server == '':
+ server = tc.nodes[0]
+
+ if create_mode == '':
+ create_mode = 'open'
+
+ if create_mode != 'open':
+ try:
+ conn = tc.get_connection(server, 'root')
+ if conn == -1:
+ tc.logger.error("Unable to get connection to 'root' of "
+ "node %s" % server)
+ return False
+ if not conn.modules.os.path.exists(conn.modules.os.path.\
+ dirname(filename)):
+ conn.modules.os.makedirs(conn.modules.os.path.\
+ dirname(filename))
+ except:
+ tc.logger.error("Exception occured while creating directory for "
+ "file %s" % filename)
+ return False
+ finally:
+ conn.close()
+
+ if create_mode == 'open':
+ try:
+ conn = tc.get_connection(server, 'root')
+ if conn == -1:
+ tc.logger.error("Unable to get connection to 'root' of node "
+ "%s" % server)
+ return False
+
+ if not conn.modules.os.path.exists(conn.modules.os.path.\
+ dirname(filename)):
+ conn.modules.os.makedirs(conn.modules.os.path.\
+ dirname(filename))
+
+ with conn.builtin.open(filename, 'w') as _filehandle:
+ _filehandle.write(file_contents)
+ except:
+ tc.logger.error("Exception occured while writing file %s" \
+ % filename)
+ return False
+
+ finally:
+ conn.close()
+ elif create_mode == 'echo':
+ cmd = "echo " + file_contents + " > " + filename
+ ret, _, _ = tc.run(server, cmd)
+ if ret != 0:
+ return False
+ elif create_mode == 'touch':
+ cmd = "touch " + filename
+ ret, _, _ = tc.run(server, cmd)
+ if ret != 0:
+ return False
+ elif create_mode == 'dd':
+ if filesize == '':
+ tc.logger.error("Invalid argument for dd cmd. Pass correct \
+ number of parameters")
+ return False
+
+ cmd = "dd if=/dev/zero of=%s bs=%s count=1" % (filename, filesize)
+ ret, _, _ = tc.run(server, cmd)
+ if ret != 0:
+ return False
+
+ return True
+
+
+def remove_file(filename, server=''):
+ """
+ This module removes the given file
+ @paramater:
+ * filename - <str> absolute path name of the file to be created
+ * server - <str> (optional) name of the server to write the
+ file. If not given, the function takes the
+ first node from config file
+ @Returns: True, on success
+ False, on failure
+ """
+ if server == '':
+ server = tc.nodes[0]
+ try:
+ conn = tc.get_connection(server, 'root')
+ if conn == -1:
+ tc.logger.error("Unable to get connection to 'root' of node %s" \
+ % server)
+ return False
+ if conn.modules.os.path.exists(filename):
+ conn.modules.os.remove(filename)
+ except:
+ tc.logger.error("Exception occured while removing file %s" % filename)
+ return False
+
+ finally:
+ conn.close()
+
+ return True
+
+
+def calculate_checksum(file_list, server=''):
+ """
+ This module calculates checksum (sha256sum) for the given file list
+ @paramater:
+ * file_list - <list> absolute file names for which checksum to be
+ calculated
+ * server - <str> (optional) name of the server.
+ If not given, the function takes the
+ first node from config file
+ @Returns: checksum value in dict format, on success
+ None, on failure
+ """
+ if server == '':
+ server = tc.nodes[0]
+
+ cmd = "sha256sum %s" % ' '.join(file_list)
+ ret = tc.run(server, cmd)
+ if ret[0] != 0:
+ tc.logger.error("Failed to execute checksum command in server %s" \
+ % server)
+ return None
+
+ checksum_dict = {}
+ for line in ret[1].split('\n')[:-1]:
+ match = re.search(r'^(\S+)\s+(\S+)', line.strip())
+ if match is None:
+ tc.logger.error("checksum output is not in \
+ expected format")
+ return None
+
+ checksum_dict[match.group(2)] = match.group(1)
+
+ return checksum_dict
+
+
+def get_extended_attributes_info(file_list, encoding='hex', attr_name='', \
+ server=''):
+ """
+ This module gets extended attribute info for the given file list
+ @paramater:
+ * file_list - <list> absolute file names
+ * encoding - <str> (optional) encoding format
+ * server - <str> (optional) name of the server.
+ If not given, the function takes the
+ first node from config file
+ @Returns: Extended attribute info in dict format, on success
+ None, on failure
+ """
+ if server == '':
+ server = tc.nodes[0]
+
+ server = socket.gethostbyname(server)
+ if attr_name == '':
+ cmd = "getfattr -d -m . -e %s %s" % (encoding, ' '.join(file_list))
+ else:
+ cmd = "getfattr -d -m . -n %s %s" % (attr_name, ' '.join(file_list))
+
+ ret = tc.run(server, cmd)
+ if ret[0] != 0:
+ tc.logger.error("Failed to execute getfattr command in server %s" \
+ % server)
+ return None
+
+ attr_dict = {}
+ for each_attr in ret[1].split('\n\n')[:-1]:
+ for line in each_attr.split('\n'):
+ if line.startswith('#'):
+ match = re.search(r'.*file:\s(\S+).*', line)
+ if match is None:
+ tc.logger.error("getfattr output isn't in expected format")
+ return None
+ key = "/" + match.group(1)
+ attr_dict[key] = {}
+ else:
+ output = line.split('=')
+ attr_dict[key][output[0]] = output[1]
+ return attr_dict
+
+
+def get_filepath_from_rhsnode(filename, server=''):
+ """
+ This module gets filepath of the given file from rhsnode
+ @paramater:
+ * filename - <str> absolute name of the file
+ * server - <str> (optional) name of the server.
+ If not given, the function takes the
+ first client from config file
+ @Returns: file path for the given file in rhs node in list format
+ on success None, on failure
+ """
+ if server == '':
+ server = tc.clients[0]
+
+ output = get_extended_attributes_info([filename], \
+ attr_name='trusted.glusterfs.pathinfo', server=server)
+ if output is None:
+ tc.logger.error("Failed to get path info")
+ return None
+
+ pathinfo = output[filename]['trusted.glusterfs.pathinfo']
+ return re.findall(".*?POSIX.*?:(\S+)\>", pathinfo)
diff --git a/tests/distaf/tests_d/__init__.py b/tests/distaf/tests_d/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tests/distaf/tests_d/__init__.py
diff --git a/tests/distaf/tests_d/examples/__init__.py b/tests/distaf/tests_d/examples/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tests/distaf/tests_d/examples/__init__.py
diff --git a/tests/distaf/tests_d/examples/test_basic_gluster_tests.py b/tests/distaf/tests_d/examples/test_basic_gluster_tests.py
new file mode 100644
index 00000000000..47dfd9372a7
--- /dev/null
+++ b/tests/distaf/tests_d/examples/test_basic_gluster_tests.py
@@ -0,0 +1,49 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc, testcase
+from distaf.gluster_base_class import GlusterBaseClass
+from distaf.gluster_libs.mount_ops import mount_volume, umount_volume
+from distaf.gluster_libs.volume_ops import (setup_vol, stop_volume,
+ delete_volume)
+
+
+@testcase("gluster_basic_test")
+class gluster_basic_test(GlusterBaseClass):
+ """
+ runs_on_volumes: ALL
+ runs_on_protocol: [ glusterfs, nfs ]
+ reuse_setup: True
+ """
+ def run(self):
+ _rc = True
+ client = self.clients[0]
+ tc.run(self.mnode, "gluster volume status %s" % self.volname)
+ ret, _, _ = mount_volume(self.volname, self.mount_proto, \
+ self.mountpoint, mclient=client)
+ if ret != 0:
+ tc.logger.error("Unable to mount the volume %s in %s" \
+ "Please check the logs" % (self.volname, client))
+ return False
+ ret, _, _ = tc.run(client, "cp -r /etc %s" % self.mountpoint)
+ if ret != 0:
+ tc.logger.error("cp failed in %s. Please check the logs" % client)
+ _rc = False
+ tc.run(client, "rm -rf %s/etc" % self.mountpoint)
+ umount_volume(client, self.mountpoint)
+ return _rc
diff --git a/tests/distaf/tests_d/examples/test_docstring.py b/tests/distaf/tests_d/examples/test_docstring.py
new file mode 100644
index 00000000000..1a73998eb66
--- /dev/null
+++ b/tests/distaf/tests_d/examples/test_docstring.py
@@ -0,0 +1,130 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc, testcase
+from distaf.gluster_base_class import GlusterBaseClass
+
+
+# An example with both doc and config in docstring
+@testcase("doc_and_config_test")
+class DocAndConfig(GlusterBaseClass):
+ """ Testing docstring configuration options
+ This is an example of a basic distaf test with mixed comment and config
+ Any necessary description doc string text goes here and can include any
+ plain text normally found in a docstring.
+ Distaf specific config yaml can be included using the yaml standard
+ document triple-dash separator below.
+ ---
+ runs_on_volumes: [ distributed ]
+ runs_on_protocol: [ glusterfs ]
+ reuse_setup: False
+ tags:
+ - tag1
+ - tag2
+ - tag3
+ """
+ def run(self):
+ tc.logger.info("Running with doc and config in docstring")
+ config = self.config_data
+ tc.logger.debug("Tag 2 is %s" % config["tags"][1])
+ tag2 = config["tags"][1]
+ if tag2 == "tag2":
+ return True
+
+ return False
+
+ def setup(self):
+ return True
+
+ def cleanup(self):
+ return True
+
+ def teardown(self):
+ return True
+
+
+# An example with only config in docstring
+@testcase("config_only_test")
+class ConfigOnly(GlusterBaseClass):
+ """
+ runs_on_volumes: [ distributed ]
+ runs_on_protocol: [ glusterfs, cifs ]
+ reuse_setup: False
+ tags:
+ - tag1
+ - tag2
+ - tag3
+ """
+ def run(self):
+ tc.logger.info("Running with only config in docstring")
+ config = self.config_data
+ tc.logger.debug("Tag 2 is %s" % config["tags"][1])
+ tag2 = config["tags"][1]
+ if tag2 == "tag2":
+ return True
+
+ return False
+
+ def setup(self):
+ return True
+
+ def cleanup(self):
+ return True
+
+ def teardown(self):
+ return True
+
+
+# An example with only doc in docstring
+@testcase("doc_only_test")
+class DocOnly(GlusterBaseClass):
+ """ Testing docstring configuration options
+ This is an example of a basic distaf test with mixed comment and config
+ Any necessary description doc string text goes here and can include any
+ plain text normally found in a docstring.
+ """
+ def run(self):
+ tc.logger.info("Running with only doc in docstring")
+ return True
+
+ def setup(self):
+ return True
+
+ def cleanup(self):
+ return True
+
+ def teardown(self):
+ return True
+
+
+# An example without a docstring
+@testcase("no_docstring_test")
+class NoDocstring(GlusterBaseClass):
+ def run(self):
+ tc.logger.info("Running with no docstring")
+
+ return True
+
+ def setup(self):
+ return True
+
+ def cleanup(self):
+ return True
+
+ def teardown(self):
+ return True
diff --git a/tests/distaf/tests_d/examples/test_passfail.py b/tests/distaf/tests_d/examples/test_passfail.py
new file mode 100644
index 00000000000..3b14c117190
--- /dev/null
+++ b/tests/distaf/tests_d/examples/test_passfail.py
@@ -0,0 +1,97 @@
+# This file is part of DiSTAF
+# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+from distaf.util import tc, testcase
+from distaf.gluster_base_class import GlusterBaseClass
+
+
+@testcase("this_should_pass")
+class GoingToPass(GlusterBaseClass):
+ """ Testing connectivity and framework pass
+ This is an example of a basic distaf test with mixed comment and config
+ Any necessary description doc string text goes here and can include any
+ plain text normally found in a docstring.
+ Distaf specific config yaml can be included using the yaml standard
+ document triple-dash separator below.
+ ---
+ runs_on_volumes: [ distributed ]
+ runs_on_protocol: [ glusterfs ]
+ reuse_setup: False
+ tags:
+ - tag1
+ - tag2
+ - tag3
+ """
+ def setup(self):
+ return True
+
+ def run(self):
+ config = self.config_data
+ tc.logger.info("Testing connection and command exec")
+ tc.logger.debug("Tag 1 is %s" % config["tags"][0])
+ ret, _, _ = tc.run(self.nodes[0], "hostname")
+ if ret != 0:
+ tc.logger.error("hostname command failed")
+ return False
+ else:
+ return True
+
+ def setup(self):
+ return True
+
+ def cleanup(self):
+ return True
+
+ def teardown(self):
+ return True
+
+
+@testcase("this_should_fail")
+class GoingToFail(GlusterBaseClass):
+ """ Testing connectivity and fail
+ ---
+ runs_on_volumes: [ distributed ]
+ runs_on_protocol: [ glusterfs, cifs ]
+ reuse_setup: False
+ tags:
+ - tag1
+ - tag2
+ - tag3
+ """
+ def setup(self):
+ return True
+
+ def run(self):
+ config = self.config_data
+ tc.logger.info("Testing fail output")
+ tc.logger.debug("Tag 1 is %s" % config["tags"][0])
+ ret, _, _ = tc.run(self.nodes[0], "false")
+ if ret != 0:
+ tc.logger.error("false command failed")
+ return False
+ else:
+ return True
+
+ def setup(self):
+ return True
+
+ def cleanup(self):
+ return True
+
+ def teardown(self):
+ return True