diff options
-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 |