diff options
author | M S Vishwanath Bhat <msvbhat@gmail.com> | 2016-03-30 17:05:05 +0530 |
---|---|---|
committer | Jeff Darcy <jdarcy@redhat.com> | 2016-04-07 08:08:32 -0700 |
commit | 0fa83a3310274845fa9b445d1aed124e9fb43410 (patch) | |
tree | 090787d7b5435fe36817760b7e5cb66499c420a8 | |
parent | 6602376e3e9e6d9f4f695475569322b61ccc2411 (diff) |
Adding distaf tests directory structure to tests dir
Since it was decided to have distaf test cases and related
libraries inside of glusterfs.git, this patch has the basic
skeleton directory structure and few of the libs.
Please note that, this patch by itself will not enable to
run any distaf tests right away. The distaf package needs to
be installed from the github.com/gluster/distaf. Once that
package is installed, it will put all the libs into respective
standard location and only then the tests in this patch can be
executed.
The plan to have distaf_libs packaged or installable using setup.py
This will enable tests to assume that all the libs are present in
the standard location.
Change-Id: I925ac0ad7e9cf6164c4380319a1f786b5241c74a
Signed-off-by: M S Vishwanath Bhat <msvbhat@gmail.com>
Reviewed-on: http://review.gluster.org/13853
Smoke: Gluster Build System <jenkins@build.gluster.com>
Tested-by: Raghavendra Talur <rtalur@redhat.com>
NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
CentOS-regression: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Raghavendra Talur <rtalur@redhat.com>
-rw-r--r-- | tests/distaf/__init__.py | 0 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/__init__.py | 0 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/brick_ops.py | 90 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/gluster_init.py | 70 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/mount_ops.py | 62 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/peer_ops.py | 204 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/quota_ops.py | 44 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/rebalance.py | 121 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/snap_ops.py | 85 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/gluster_libs/volume_ops.py | 517 | ||||
-rw-r--r-- | tests/distaf/distaf_libs/io_libs/file_ops.py | 245 | ||||
-rw-r--r-- | tests/distaf/tests_d/__init__.py | 0 | ||||
-rw-r--r-- | tests/distaf/tests_d/examples/__init__.py | 0 | ||||
-rw-r--r-- | tests/distaf/tests_d/examples/test_basic_gluster_tests.py | 49 | ||||
-rw-r--r-- | tests/distaf/tests_d/examples/test_docstring.py | 130 | ||||
-rw-r--r-- | tests/distaf/tests_d/examples/test_passfail.py | 97 |
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 |