diff options
Diffstat (limited to 'glustolibs-gluster')
28 files changed, 3016 insertions, 2861 deletions
diff --git a/glustolibs-gluster/glustolibs/gluster/brick_libs.py b/glustolibs-gluster/glustolibs/gluster/brick_libs.py index c3e5afed8..b92832dd1 100644 --- a/glustolibs-gluster/glustolibs/gluster/brick_libs.py +++ b/glustolibs-gluster/glustolibs/gluster/brick_libs.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -17,20 +17,20 @@ """ Description: Module for gluster brick related helper functions. """ import random -from math import ceil +from math import floor import time from glusto.core import Glusto as g from glustolibs.gluster.brickmux_ops import is_brick_mux_enabled +from glustolibs.gluster.gluster_init import restart_glusterd from glustolibs.gluster.volume_ops import (get_volume_info, get_volume_status) -from glustolibs.gluster.volume_libs import (get_subvols, is_tiered_volume, +from glustolibs.gluster.volume_libs import (get_subvols, get_client_quorum_info, get_volume_type_info) +from glustolibs.gluster.lib_utils import (get_extended_attributes_info) def get_all_bricks(mnode, volname): """Get list of all the bricks of the specified volume. - If the volume is 'Tier' volume, the list will contain both - 'hot tier' and 'cold tier' bricks. Args: mnode (str): Node on which command has to be executed @@ -45,19 +45,7 @@ def get_all_bricks(mnode, volname): g.log.error("Unable to get the volinfo of %s.", volname) return None - if 'Tier' in volinfo[volname]['typeStr']: - # Get bricks from hot-tier in case of Tier volume - hot_tier_bricks = get_hot_tier_bricks(mnode, volname) - if hot_tier_bricks is None: - return None - # Get cold-tier bricks in case of Tier volume - cold_tier_bricks = get_cold_tier_bricks(mnode, volname) - if cold_tier_bricks is None: - return None - - return hot_tier_bricks + cold_tier_bricks - - # Get bricks from a non Tier volume + # Get bricks from a volume all_bricks = [] if 'bricks' in volinfo[volname]: if 'brick' in volinfo[volname]['bricks']: @@ -76,88 +64,6 @@ def get_all_bricks(mnode, volname): return None -def get_hot_tier_bricks(mnode, volname): - """Get list of hot-tier bricks of the specified volume - - Args: - mnode (str): Node on which command has to be executed - volname (str): Name of the volume - - Returns: - list : List of hot-tier bricks of the volume on Success. - NoneType: None on failure. - """ - volinfo = get_volume_info(mnode, volname) - if volinfo is None: - g.log.error("Unable to get the volinfo of %s.", volname) - return None - - if 'Tier' not in volinfo[volname]['typeStr']: - g.log.error("Volume %s is not a tiered volume", volname) - return None - - hot_tier_bricks = [] - if 'bricks' in volinfo[volname]: - if 'hotBricks' in volinfo[volname]['bricks']: - if 'brick' in volinfo[volname]['bricks']['hotBricks']: - for brick in volinfo[volname]['bricks']['hotBricks']['brick']: - if 'name' in brick: - hot_tier_bricks.append(brick['name']) - else: - g.log.error("brick %s doesn't have the key 'name' " - "for the volume: %s", brick, volname) - return None - else: - g.log.error("Bricks not found in hotBricks section of volume " - "info for the volume %s", volname) - return None - return hot_tier_bricks - else: - g.log.error("Bricks not found for the volume %s", volname) - return None - - -def get_cold_tier_bricks(mnode, volname): - """Get list of cold-tier bricks of the specified volume - - Args: - mnode (str): Node on which command has to be executed - volname (str): Name of the volume - - Returns: - list : List of cold-tier bricks of the volume on Success. - NoneType: None on failure. - """ - volinfo = get_volume_info(mnode, volname) - if volinfo is None: - g.log.error("Unable to get the volinfo of %s.", volname) - return None - - if 'Tier' not in volinfo[volname]['typeStr']: - g.log.error("Volume %s is not a tiered volume", volname) - return None - - cold_tier_bricks = [] - if 'bricks' in volinfo[volname]: - if 'coldBricks' in volinfo[volname]['bricks']: - if 'brick' in volinfo[volname]['bricks']['coldBricks']: - for brick in volinfo[volname]['bricks']['coldBricks']['brick']: - if 'name' in brick: - cold_tier_bricks.append(brick['name']) - else: - g.log.error("brick %s doesn't have the key 'name' " - "for the volume: %s", brick, volname) - return None - else: - g.log.error("Bricks not found in coldBricks section of volume " - "info for the volume %s", volname) - return None - return cold_tier_bricks - else: - g.log.error("Bricks not found for the volume %s", volname) - return None - - def bring_bricks_offline(volname, bricks_list, bring_bricks_offline_methods=None): """Bring the bricks specified in the bricks_list offline. @@ -304,10 +210,9 @@ def bring_bricks_online(mnode, volname, bricks_list, "the bricks '%s' online", volname, bricks_list) elif bring_brick_online_method == 'glusterd_restart': - bring_brick_online_command = "service glusterd restart" brick_node, _ = brick.split(":") - ret, _, _ = g.run(brick_node, bring_brick_online_command) - if ret != 0: + ret = restart_glusterd(brick_node) + if not ret: g.log.error("Unable to restart glusterd on node %s", brick_node) _rc = False @@ -504,41 +409,29 @@ def select_bricks_to_bring_offline(mnode, volname): being empty list. Example: brick_to_bring_offline = { - 'is_tier': False, - 'hot_tier_bricks': [], - 'cold_tier_bricks': [], 'volume_bricks': [] } """ # Defaulting the values to empty list bricks_to_bring_offline = { - 'is_tier': False, - 'hot_tier_bricks': [], - 'cold_tier_bricks': [], 'volume_bricks': [] - } + } volinfo = get_volume_info(mnode, volname) if volinfo is None: g.log.error("Unable to get the volume info for volume %s", volname) return bricks_to_bring_offline - if is_tiered_volume(mnode, volname): - bricks_to_bring_offline['is_tier'] = True - # Select bricks from tiered volume. - bricks_to_bring_offline = ( - select_tier_volume_bricks_to_bring_offline(mnode, volname)) - else: - # Select bricks from non-tiered volume. - volume_bricks = select_volume_bricks_to_bring_offline(mnode, volname) - bricks_to_bring_offline['volume_bricks'] = volume_bricks + # Select bricks from the volume. + volume_bricks = select_volume_bricks_to_bring_offline(mnode, volname) + bricks_to_bring_offline['volume_bricks'] = volume_bricks return bricks_to_bring_offline def select_volume_bricks_to_bring_offline(mnode, volname): """Randomly selects bricks to bring offline without affecting the cluster - from a non-tiered volume. + from a volume. Args: mnode (str): Node on which commands will be executed. @@ -546,14 +439,10 @@ def select_volume_bricks_to_bring_offline(mnode, volname): Returns: list: On success returns list of bricks that can be brough offline. - If volume doesn't exist or is a tiered volume returns empty list + If volume doesn't exist returns empty list """ volume_bricks_to_bring_offline = [] - # Check if volume is tiered - if is_tiered_volume(mnode, volname): - return volume_bricks_to_bring_offline - # get volume type volume_type_info = get_volume_type_info(mnode, volname) volume_type = volume_type_info['volume_type_info']['typeStr'] @@ -598,162 +487,6 @@ def select_volume_bricks_to_bring_offline(mnode, volname): return volume_bricks_to_bring_offline -def select_tier_volume_bricks_to_bring_offline(mnode, volname): - """Randomly selects bricks to bring offline without affecting the cluster - from a tiered volume. - - Args: - mnode (str): Node on which commands will be executed. - volname (str): Name of the volume. - - Returns: - dict: On success returns dict. Value of each key is list of bricks to - bring offline. - If volume doesn't exist or is not a tiered volume returns dict - with value of each item being empty list. - Example: - brick_to_bring_offline = { - 'hot_tier_bricks': [], - 'cold_tier_bricks': [], - } - """ - # Defaulting the values to empty list - bricks_to_bring_offline = { - 'hot_tier_bricks': [], - 'cold_tier_bricks': [], - } - - volinfo = get_volume_info(mnode, volname) - if volinfo is None: - g.log.error("Unable to get the volume info for volume %s", volname) - return bricks_to_bring_offline - - if is_tiered_volume(mnode, volname): - # Select bricks from both hot tier and cold tier. - hot_tier_bricks = (select_hot_tier_bricks_to_bring_offline - (mnode, volname)) - cold_tier_bricks = (select_cold_tier_bricks_to_bring_offline - (mnode, volname)) - bricks_to_bring_offline['hot_tier_bricks'] = hot_tier_bricks - bricks_to_bring_offline['cold_tier_bricks'] = cold_tier_bricks - return bricks_to_bring_offline - - -def select_hot_tier_bricks_to_bring_offline(mnode, volname): - """Randomly selects bricks to bring offline without affecting the cluster - from a hot tier. - - Args: - mnode (str): Node on which commands will be executed. - volname (str): Name of the volume. - - Returns: - list: On success returns list of bricks that can be brough offline - from hot tier. If volume doesn't exist or is a non tiered volume - returns empty list. - """ - hot_tier_bricks_to_bring_offline = [] - - # Check if volume is tiered - if not is_tiered_volume(mnode, volname): - return hot_tier_bricks_to_bring_offline - - # get volume type - volume_type_info = get_volume_type_info(mnode, volname) - hot_tier_type = volume_type_info['hot_tier_type_info']['hotBrickType'] - - # get subvols - subvols_dict = get_subvols(mnode, volname) - hot_tier_subvols = subvols_dict['hot_tier_subvols'] - - # select bricks from distribute volume - if hot_tier_type == 'Distribute': - hot_tier_bricks_to_bring_offline = [] - - # select bricks from replicated, distributed-replicated volume - if (hot_tier_type == 'Replicate' or - hot_tier_type == 'Distributed-Replicate'): - # Get replica count - hot_tier_replica_count = (volume_type_info - ['hot_tier_type_info']['hotreplicaCount']) - - # Get quorum info - quorum_info = get_client_quorum_info(mnode, volname) - hot_tier_quorum_info = quorum_info['hot_tier_quorum_info'] - - # Get list of bricks to bring offline - hot_tier_bricks_to_bring_offline = ( - get_bricks_to_bring_offline_from_replicated_volume( - hot_tier_subvols, hot_tier_replica_count, - hot_tier_quorum_info)) - - return hot_tier_bricks_to_bring_offline - - -def select_cold_tier_bricks_to_bring_offline(mnode, volname): - """Randomly selects bricks to bring offline without affecting the cluster - from a cold tier. - - Args: - mnode (str): Node on which commands will be executed. - volname (str): Name of the volume. - - Returns: - list: On success returns list of bricks that can be brough offline - from cold tier. If volume doesn't exist or is a non tiered volume - returns empty list. - """ - cold_tier_bricks_to_bring_offline = [] - - # Check if volume is tiered - if not is_tiered_volume(mnode, volname): - return cold_tier_bricks_to_bring_offline - - # get volume type - volume_type_info = get_volume_type_info(mnode, volname) - cold_tier_type = volume_type_info['cold_tier_type_info']['coldBrickType'] - - # get subvols - subvols_dict = get_subvols(mnode, volname) - cold_tier_subvols = subvols_dict['cold_tier_subvols'] - - # select bricks from distribute volume - if cold_tier_type == 'Distribute': - cold_tier_bricks_to_bring_offline = [] - - # select bricks from replicated, distributed-replicated volume - elif (cold_tier_type == 'Replicate' or - cold_tier_type == 'Distributed-Replicate'): - # Get replica count - cold_tier_replica_count = (volume_type_info['cold_tier_type_info'] - ['coldreplicaCount']) - - # Get quorum info - quorum_info = get_client_quorum_info(mnode, volname) - cold_tier_quorum_info = quorum_info['cold_tier_quorum_info'] - - # Get list of bricks to bring offline - cold_tier_bricks_to_bring_offline = ( - get_bricks_to_bring_offline_from_replicated_volume( - cold_tier_subvols, cold_tier_replica_count, - cold_tier_quorum_info)) - - # select bricks from Disperse, Distribured-Disperse volume - elif (cold_tier_type == 'Disperse' or - cold_tier_type == 'Distributed-Disperse'): - - # Get redundancy count - cold_tier_redundancy_count = (volume_type_info['cold_tier_type_info'] - ['coldredundancyCount']) - - # Get list of bricks to bring offline - cold_tier_bricks_to_bring_offline = ( - get_bricks_to_bring_offline_from_disperse_volume( - cold_tier_subvols, cold_tier_redundancy_count)) - - return cold_tier_bricks_to_bring_offline - - def get_bricks_to_bring_offline_from_replicated_volume(subvols_list, replica_count, quorum_info): @@ -761,13 +494,10 @@ def get_bricks_to_bring_offline_from_replicated_volume(subvols_list, for a replicated volume. Args: - subvols_list: list of subvols. It can be volume_subvols, - hot_tier_subvols or cold_tier_subvols. + subvols_list: list of subvols. For example: subvols = volume_libs.get_subvols(mnode, volname) volume_subvols = subvols_dict['volume_subvols'] - hot_tier_subvols = subvols_dict['hot_tier_subvols'] - cold_tier_subvols = subvols_dict['cold_tier_subvols'] replica_count: Replica count of a Replicate or Distributed-Replicate volume. quorum_info: dict containing quorum info of the volume. The dict should @@ -776,8 +506,6 @@ def get_bricks_to_bring_offline_from_replicated_volume(subvols_list, For example: quorum_dict = get_client_quorum_info(mnode, volname) volume_quorum_info = quorum_info['volume_quorum_info'] - hot_tier_quorum_info = quorum_info['hot_tier_quorum_info'] - cold_tier_quorum_info = quorum_info['cold_tier_quorum_info'] Returns: list: List of bricks that can be brought offline without affecting the @@ -805,7 +533,7 @@ def get_bricks_to_bring_offline_from_replicated_volume(subvols_list, offline_bricks_limit = int(replica_count) - int(quorum_count) elif 'auto' in quorum_type: - offline_bricks_limit = ceil(int(replica_count) / 2) + offline_bricks_limit = floor(int(replica_count) // 2) elif quorum_type is None: offline_bricks_limit = int(replica_count) - 1 @@ -835,18 +563,15 @@ def get_bricks_to_bring_offline_from_disperse_volume(subvols_list, for a disperse volume. Args: - subvols_list: list of subvols. It can be volume_subvols, - hot_tier_subvols or cold_tier_subvols. + subvols_list: list of subvols. For example: subvols = volume_libs.get_subvols(mnode, volname) volume_subvols = subvols_dict['volume_subvols'] - hot_tier_subvols = subvols_dict['hot_tier_subvols'] - cold_tier_subvols = subvols_dict['cold_tier_subvols'] redundancy_count: Redundancy count of a Disperse or Distributed-Disperse volume. Returns: - list: List of bricks that can be brought offline without affecting the + list: List of bricks that can be brought offline without affecting the cluster.On any failure return empty list. """ list_of_bricks_to_bring_offline = [] @@ -927,3 +652,42 @@ def is_broken_symlinks_present_on_bricks(mnode, volname): "%s on node %s.", (brick_path, brick_node)) return True return False + + +def validate_xattr_on_all_bricks(bricks_list, file_path, xattr): + """Checks if the xattr of the file/dir is same on all bricks. + + Args: + bricks_list (list): List of bricks. + file_path (str): The path to the file/dir. + xattr (str): The file attribute to get from file. + + Returns: + True if the xattr is same on all the fqpath. False otherwise + + Example: + validate_xattr_on_all_bricks("bricks_list", + "dir1/file1", + "xattr") + """ + + time_counter = 250 + g.log.info("The heal monitoring timeout is : %d minutes", + (time_counter // 60)) + while time_counter > 0: + attr_vals = {} + for brick in bricks_list: + brick_node, brick_path = brick.split(":") + attr_vals[brick] = ( + get_extended_attributes_info(brick_node, + ["{0}/{1}".format(brick_path, + file_path)], + attr_name=xattr)) + ec_version_vals = [list(val.values())[0][xattr] for val in + list(attr_vals.values())] + if len(set(ec_version_vals)) == 1: + return True + else: + time.sleep(120) + time_counter -= 120 + return False diff --git a/glustolibs-gluster/glustolibs/gluster/brickdir.py b/glustolibs-gluster/glustolibs/gluster/brickdir.py index 564f1421b..e864e8247 100644 --- a/glustolibs-gluster/glustolibs/gluster/brickdir.py +++ b/glustolibs-gluster/glustolibs/gluster/brickdir.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2018 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2018-2020 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 @@ -20,20 +20,20 @@ import os from glusto.core import Glusto as g +from glustolibs.gluster.volume_libs import get_volume_type -def get_hashrange(brickdir_path): - """Get the int hash range for a brick +def check_hashrange(brickdir_path): + """Check the hash range for a brick Args: - brickdir_url (str): path of the directory as returned from pathinfo + brickdir_path (str): path of the directory as returned from pathinfo (e.g., server1.example.com:/bricks/brick1/testdir1) Returns: list containing the low and high hash for the brickdir. None on fail. """ (host, fqpath) = brickdir_path.split(':') - command = ("getfattr -n trusted.glusterfs.dht -e hex %s " "2> /dev/null | grep -i trusted.glusterfs.dht | " "cut -d= -f2" % fqpath) @@ -53,6 +53,49 @@ def get_hashrange(brickdir_path): return None +def get_hashrange(brickdir_path): + """Check the gluster version and then the volume type. + And accordingly, get the int hash range for a brick. + + Note: + If the Gluster version is equal to or greater than 6, the hash range + can be calculated only for distributed, distributed-dispersed, + distributed-arbiter and distributed-replicated volume types because of + DHT pass-through option which was introduced in Gluster 6. + + About DHT pass-through option: + There are no user controllable changes with this feature. + The distribute xlator now skips unnecessary checks and operations when + the distribute count is one for a volume, resulting in improved + performance.It comes into play when there is only 1 brick or it is a + pure-replicate or pure-disperse or pure-arbiter volume. + + Args: + brickdir_path (str): path of the directory as returned from pathinfo + (e.g., server1.example.com:/bricks/brick1/testdir1) + + Returns: + list containing the low and high hash for the brickdir. None on fail. + + """ + + (host, _) = brickdir_path.split(':') + ret = get_volume_type(brickdir_path) + if ret in ('Replicate', 'Disperse', 'Arbiter'): + g.log.info("Cannot find hash-range for Replicate/Disperse/Arbiter" + " volume type on Gluster 6.0 and higher.") + return "Skipping for Replicate/Disperse/Arbiter volume type" + else: + ret = check_hashrange(brickdir_path) + hash_range_low = ret[0] + hash_range_high = ret[1] + if ret is not None: + return (hash_range_low, hash_range_high) + else: + g.log.error("Could not get hashrange") + return None + + def file_exists(host, filename): """Check if file exists at path on host @@ -80,12 +123,23 @@ class BrickDir(object): self._hashrange_low = None self._hashrange_high = None - def _get_hashrange(self): + def _check_hashrange(self): """get the hash range for a brick from a remote system""" - self._hashrange = get_hashrange(self._path) + self._hashrange = check_hashrange(self._path) self._hashrange_low = self._hashrange[0] self._hashrange_high = self._hashrange[1] + def _get_hashrange(self): + """get the hash range for a brick from a remote system""" + ret = get_volume_type(self._path) + if ret in ('Replicate', 'Disperse', 'Arbiter'): + g.log.info("Cannot find hash-range as the volume type under" + " test is Replicate/Disperse/Arbiter") + else: + self._hashrange = get_hashrange(self._path) + self._hashrange_low = self._hashrange[0] + self._hashrange_high = self._hashrange[1] + @property def path(self): """The brick url @@ -126,8 +180,13 @@ class BrickDir(object): """The high hash of the brick hashrange""" if self.hashrange is None or self._hashrange_high is None: self._get_hashrange() - - return self._hashrange_high + if self._get_hashrange() is None: + ret = get_volume_type(self._path) + if ret in ('Replicate', 'Disperse', 'Arbiter'): + g.log.info("Cannot find hash-range as the volume type" + " under test is Replicate/Disperse/Arbiter") + else: + return self._hashrange_high def hashrange_contains_hash(self, filehash): """Check if a hash number falls between the brick hashrange diff --git a/glustolibs-gluster/glustolibs/gluster/brickmux_libs.py b/glustolibs-gluster/glustolibs/gluster/brickmux_libs.py new file mode 100644 index 000000000..cb82d8434 --- /dev/null +++ b/glustolibs-gluster/glustolibs/gluster/brickmux_libs.py @@ -0,0 +1,149 @@ +# Copyright (C) 2020 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: Module for Brick multiplexing realted helper functions. +""" + +from itertools import cycle +try: + from itertools import zip_longest +except ImportError: + from itertools import izip_longest as zip_longest + +from glusto.core import Glusto as g +from glustolibs.gluster.volume_ops import get_volume_list +from glustolibs.gluster.lib_utils import get_servers_bricks_dict + + +def get_all_bricks_from_servers_multivol(servers, servers_info): + """ + Form list of all the bricks to create/add-brick from the given + servers and servers_info + + Args: + servers (list): List of servers in the storage pool. + servers_info (dict): Information about all servers. + + Returns: + brickCount (int): Number of bricks available from the servers. + bricks_list (list): List of all bricks from the servers provided. + + example : + servers_info = { + 'abc.lab.eng.xyz.com': { + 'host': 'abc.lab.eng.xyz.com', + 'brick_root': '/bricks', + 'devices': ['/dev/vdb', '/dev/vdc', '/dev/vdd', '/dev/vde'] + }, + 'def.lab.eng.xyz.com':{ + 'host': 'def.lab.eng.xyz.com', + 'brick_root': '/bricks', + 'devices': ['/dev/vdb', '/dev/vdc', '/dev/vdd', '/dev/vde'] + } + } + """ + if not isinstance(servers, list): + servers = [servers] + + brickCount, bricks_list = 0, [] + + servers_bricks = get_servers_bricks_dict(servers, servers_info) + server_ip = cycle(servers_bricks.keys()) + + for item in list(zip_longest(*list(servers_bricks.values()))): + for brick in item: + try: + server = server_ip.next() # Python 2 + except AttributeError: + server = next(server_ip) # Python 3 + if brick: + bricks_list.append(server + ":" + brick) + brickCount += 1 + return brickCount, bricks_list + + +def get_current_brick_index(mnode): + """ + Get the brick current index from the node of the cluster. + + Args: + mnode (str): Node on which commands has to be executed. + + Returns: + NoneType: If there are any errors + int: Count of the bricks in the cluster. + """ + ret, brick_index, err = g.run(mnode, "gluster volume info | egrep " + "\"^Brick[0-9]+\" | grep -v \"ss_brick\"") + if ret: + g.log.error("Error in getting bricklist using gluster v info %s" % err) + return None + + g.log.info("brick_index is ", brick_index) + return len(brick_index.splitlines()) + + +def form_bricks_for_multivol(mnode, volname, number_of_bricks, servers, + servers_info): + """ + Forms brics list for volume create/add-brick given the number_of_bricks + servers, servers_info, for multiple volume cluster and for brick multiplex + enabled cluster. + + Args: + mnode (str): Node on which commands has to be executed. + volname (str): Volume name for which we require brick-list + number_of_bricks (int): The number of bricks for which brick list + has to be created. + servers (str|list): A server|List of servers from which the bricks + needs to be selected for creating the brick list. + servers_info (dict): Dict of server info of each servers. + + Returns: + list: List of bricks to use with volume create. + Nonetype: If unable to fetch the brick list + + """ + if not isinstance(servers, list): + servers = [servers] + + brick_index, brick_list_for_volume = 0, [] + + # Importing get_all_bricks() from bricks_libs to avoid cyclic imports + from glustolibs.gluster.brick_libs import get_all_bricks + + # Get all volume list present in the cluster from mnode + current_vol_list = get_volume_list(mnode) + for volume in current_vol_list: + brick_index = brick_index + len(get_all_bricks(mnode, volume)) + g.log.info("current brick_index %s" % brick_index) + + # Get all bricks_count and bricks_list + all_brick_count, bricks_list = get_all_bricks_from_servers_multivol( + servers, servers_info) + if not (all_brick_count > 1): + g.log.error("Unable to get the bricks present in the specified" + "servers") + return None + + for num in range(number_of_bricks): + brick = brick_index % all_brick_count + brick_list_for_volume.append("%s/%s_brick%d" % (bricks_list[brick], + volname, brick_index)) + brick_index += 1 + + return brick_list_for_volume diff --git a/glustolibs-gluster/glustolibs/gluster/brickmux_ops.py b/glustolibs-gluster/glustolibs/gluster/brickmux_ops.py index eeb4e2a50..b56434741 100755 --- a/glustolibs-gluster/glustolibs/gluster/brickmux_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/brickmux_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2017-2019 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2017-2020 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 @@ -119,7 +119,7 @@ def check_brick_pid_matches_glusterfsd_pid(mnode, volname): "of brick path %s", brick_node, brick_path) _rc = False - cmd = "pidof glusterfsd" + cmd = "pgrep -x glusterfsd" ret, pid, _ = g.run(brick_node, cmd) if ret != 0: g.log.error("Failed to run the command %s on " @@ -127,7 +127,7 @@ def check_brick_pid_matches_glusterfsd_pid(mnode, volname): _rc = False else: - glusterfsd_pid = pid.split() + glusterfsd_pid = pid.split('\n')[:-1] if brick_pid not in glusterfsd_pid: g.log.error("Brick pid %s doesn't match glusterfsd " @@ -149,8 +149,10 @@ def get_brick_processes_count(mnode): int: Number of brick processes running on the node. None: If the command fails to execute. """ - ret, out, _ = g.run(mnode, "pidof glusterfsd") + ret, out, _ = g.run(mnode, "pgrep -x glusterfsd") if not ret: - return len(out.split(" ")) + list_of_pids = out.split("\n") + list_of_pids.pop() + return len(list_of_pids) else: return None diff --git a/glustolibs-gluster/glustolibs/gluster/ctdb_libs.py b/glustolibs-gluster/glustolibs/gluster/ctdb_libs.py new file mode 100644 index 000000000..9dfa5f8f6 --- /dev/null +++ b/glustolibs-gluster/glustolibs/gluster/ctdb_libs.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# Copyright (C) 2020 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: + Samba ctdb base classes. + Pre-requisite: + Please install samba ctdb packages + on all servers +""" + +from glusto.core import Glusto as g +from glustolibs.gluster.ctdb_ops import ( + edit_hook_script, + enable_ctdb_cluster, + create_nodes_file, + create_public_address_file, + start_ctdb_service, + is_ctdb_status_healthy, + teardown_samba_ctdb_cluster) +from glustolibs.gluster.gluster_base_class import GlusterBaseClass +from glustolibs.gluster.exceptions import ExecutionError +from glustolibs.gluster.volume_libs import ( + setup_volume, + wait_for_volume_process_to_be_online) + + +class SambaCtdbBaseClass(GlusterBaseClass): + """ + Creates samba ctdb cluster + """ + @classmethod + def setUpClass(cls): + """ + Setup variable for samba ctdb test. + """ + super(SambaCtdbBaseClass, cls).setUpClass() + + cls.ctdb_volume_rep_count = int(len(cls.ctdb_nodes)) + cls.primary_node = cls.servers[0] + g.log.info("VOLUME REP COUNT %s", cls.ctdb_volume_rep_count) + + cls.ctdb_vips = (g.config['gluster']['cluster_config'] + ['smb']['ctdb_vips']) + cls.ctdb_nodes = (g.config['gluster']['cluster_config'] + ['smb']['ctdb_nodes']) + cls.ctdb_volname = (g.config['gluster']['cluster_config'] + ['smb']['ctdb_volname']) + cls.ctdb_volume_config = (g.config['gluster']['cluster_config']['smb'] + ['ctdb_volume_config']) + + @classmethod + def setup_samba_ctdb_cluster(cls): + """ + Create ctdb-samba cluster if doesn't exists + + Returns: + bool: True if successfully setup samba else false + """ + # Check if ctdb setup is up and running + if is_ctdb_status_healthy(cls.primary_node): + g.log.info("ctdb setup already up skipping " + "ctdb setup creation") + return True + g.log.info("Proceeding with ctdb setup creation") + for mnode in cls.servers: + ret = edit_hook_script(mnode, cls.ctdb_volname) + if not ret: + return False + ret = enable_ctdb_cluster(mnode) + if not ret: + return False + ret = create_nodes_file(mnode, cls.ctdb_nodes) + if not ret: + return False + ret = create_public_address_file(mnode, cls.ctdb_vips) + if not ret: + return False + server_info = cls.all_servers_info + ctdb_config = cls.ctdb_volume_config + g.log.info("Setting up ctdb volume %s", cls.ctdb_volname) + ret = setup_volume(mnode=cls.primary_node, + all_servers_info=server_info, + volume_config=ctdb_config) + if not ret: + g.log.error("Failed to setup ctdb volume %s", cls.ctdb_volname) + return False + g.log.info("Successful in setting up volume %s", cls.ctdb_volname) + + # Wait for volume processes to be online + g.log.info("Wait for volume %s processes to be online", + cls.ctdb_volname) + ret = wait_for_volume_process_to_be_online(cls.mnode, cls.ctdb_volname) + if not ret: + g.log.error("Failed to wait for volume %s processes to " + "be online", cls.ctdb_volname) + return False + g.log.info("Successful in waiting for volume %s processes to be " + "online", cls.ctdb_volname) + + # start ctdb services + ret = start_ctdb_service(cls.servers) + if not ret: + return False + + ret = is_ctdb_status_healthy(cls.primary_node) + if not ret: + g.log.error("CTDB setup creation failed - exiting") + return False + g.log.info("CTDB setup creation successfull") + return True + + @classmethod + def tearDownClass(cls, delete_samba_ctdb_cluster=False): + """ + Teardown samba ctdb cluster. + """ + super(SambaCtdbBaseClass, cls).tearDownClass() + + if delete_samba_ctdb_cluster: + ret = teardown_samba_ctdb_cluster( + cls.servers, cls.ctdb_volname) + if not ret: + raise ExecutionError("Cleanup of samba ctdb " + "cluster failed") + g.log.info("Teardown samba ctdb cluster succeeded") + else: + g.log.info("Skipping teardown samba ctdb cluster...") diff --git a/glustolibs-gluster/glustolibs/gluster/ctdb_ops.py b/glustolibs-gluster/glustolibs/gluster/ctdb_ops.py new file mode 100644 index 000000000..8bf57ba05 --- /dev/null +++ b/glustolibs-gluster/glustolibs/gluster/ctdb_ops.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python +# Copyright (C) 2020 Red Hat, Inc. <http://www.redeat.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. + +""" +CTDB library operations +pre-requisite : CTDB and Samba packages +needs to be installed on all the server nodes. +""" + +import re +from time import sleep +from glusto.core import Glusto as g +from glustolibs.gluster.lib_utils import (add_services_to_firewall, + is_rhel6, list_files) +from glustolibs.gluster.mount_ops import umount_volume +from glustolibs.gluster.volume_libs import cleanup_volume + + +def edit_hook_script(mnode, ctdb_volname): + """ + Edit the hook scripts with ctdb volume name + + Args: + mnode (str): Node on which commands has to be executed. + ctdb_volname (str): Name of the ctdb volume + Returns: + bool: True if successfully edits the hook-scripts else false + """ + # Replace META='all' to META=ctdb_volname setup hook script + cmd = ("sed -i -- 's/META=\"all\"/META=\"%s\"/g' " + "/var/lib/glusterd/hooks/1" + "/start/post/S29CTDBsetup.sh") + ret, _, _ = g.run(mnode, cmd % ctdb_volname) + if ret: + g.log.error("Hook script - S29CTDBsetup edit failed on %s", mnode) + return False + + g.log.info("Hook script - S29CTDBsetup edit success on %s", mnode) + # Replace META='all' to META=ctdb_volname teardown hook script + cmd = ("sed -i -- 's/META=\"all\"/META=\"%s\"/g' " + "/var/lib/glusterd/hooks/1" + "/stop/pre/S29CTDB-teardown.sh") + + ret, _, _ = g.run(mnode, cmd % ctdb_volname) + if ret: + g.log.error("Hook script - S29CTDB-teardown edit failed on %s", mnode) + return False + g.log.info("Hook script - S29CTDBteardown edit success on %s", mnode) + return True + + +def enable_ctdb_cluster(mnode): + """ + Edit the smb.conf to add clustering = yes + + Args: + mnode (str): Node on which commands has to be executed. + + Returns: + bool: True if successfully enable ctdb cluster else false + """ + # Add clustering = yes in smb.conf if not already there + cmd = (r"grep -q 'clustering = yes' " + r"/etc/samba/smb.conf || sed -i.bak '/\[global\]/a " + r"clustering = yes' /etc/samba/smb.conf") + ret, _, _ = g.run(mnode, cmd) + if ret: + g.log.error("Failed to add cluster = yes to smb.conf in %s", mnode) + return False + g.log.info("Successfully added 'clustering = yes' to smb.conf " + "in all nodes") + return True + + +def check_file_availability(mnode, file_path, filename): + """ + Check for ctdb files and delete + + Args: + mnode(str): Node on which command is executed + filepath(str): Absolute path of the file to be validated + filename(str): File to be deleted if available in /etc/ctdb/ + + Returns: + bool: True if concerned files are available else false + """ + if file_path in list_files(mnode, "/etc/ctdb/", filename): + ret, _, _ = g.run(mnode, "rm -rf %s" % file_path) + if ret: + return False + return True + + +def create_nodes_file(mnode, node_ips): + """ + Create nodes file and add node ips + + Args: + mnode (str): Node on which commands has to be executed. + + Returns: + bool: True if successfully create nodes file else false + """ + # check if nodes file is available and delete + node_file_path = "/etc/ctdb/nodes" + ret = check_file_availability(mnode, node_file_path, "nodes") + if not ret: + g.log.info("Failed to delete pre-existing nodes file in %s", mnode) + return False + g.log.info("Deleted pre-existing nodes file in %s", mnode) + for node_ip in node_ips: + ret, _, _ = g.run(mnode, "echo -e %s " + ">> %s" % (node_ip, node_file_path)) + if ret: + g.log.error("Failed to add nodes list in %s", mnode) + return False + g.log.info("Nodes list added succssfully to %s" + "file in all servers", node_file_path) + return True + + +def create_public_address_file(mnode, vips): + """ + Create public_addresses file and add vips + + Args: + mnode (str): Node on which commands has to be executed. + vips (list): List of virtual ips + + Returns: + bool: True if successfully creates public_address file else false + """ + publicip_file_path = "/etc/ctdb/public_addresses" + ret = check_file_availability(mnode, + publicip_file_path, + "public_addresses") + if not ret: + g.log.info("Failed to delete pre-existing public_addresses" + "file in %s", mnode) + return False + g.log.info("Deleted pre-existing public_addresses" + "file in %s", mnode) + for vip in vips: + ret, _, _ = g.run(mnode, "echo -e %s >>" + " %s" % (vip, publicip_file_path)) + if ret: + g.log.error("Failed to add vip list in %s", mnode) + return False + g.log.info("vip list added succssfully to %s" + "file in all node", publicip_file_path) + return True + + +def ctdb_service_status(servers, mnode): + """ + Status of ctdb service on the specified node. + + Args: + mnode (str): Node on which ctdb status needs to be checked + + Returns: + tuple: Tuple containing three elements (ret, out, err). + The first element 'ret' is of type 'int' and is the return value + of command execution. + + The second element 'out' is of type 'str' and is the stdout value + of the command execution. + + The third element 'err' is of type 'str' and is the stderr value + of the command execution. + """ + g.log.info("Getting ctdb service status on %s", mnode) + if is_rhel6(servers): + return g.run(mnode, "service ctdb status") + return g.run(mnode, "systemctl status ctdb") + + +def is_ctdb_service_running(servers, mnode): + """ + Check if ctdb service is running on node + + Args: + servers (str|list): list|str of cluster nodes + mnode (str): Node on which ctdb service has to be checked + + Returns: + bool: True if ctdb service running else False + """ + g.log.info("Check if ctdb service is running on %s", mnode) + ret, out, _ = ctdb_service_status(servers, mnode) + if ret: + g.log.error("Execution error service ctdb status " + "on %s", mnode) + return False + if "Active: active (running)" in out: + g.log.info("ctdb service is running on %s", mnode) + return True + else: + g.log.error("ctdb service is not " + "running on %s", mnode) + return False + + +def start_ctdb_service(servers): + """ + start ctdb services on all nodes & + wait for 40 seconds + + Args: + servers (list): IP of samba nodes + + Returns: + bool: True if successfully starts ctdb service else false + """ + cmd = "pgrep ctdb || service ctdb start" + for mnode in servers: + ret, out, _ = g.run(mnode, cmd) + if ret: + g.log.error("Unable to start ctdb on server %s", str(out)) + return False + if not is_ctdb_service_running(servers, mnode): + g.log.error("ctdb services not running %s", str(out)) + return False + g.log.info("Start ctdb on server %s successful", mnode) + # sleep for 40sec as ctdb status takes time to enable + sleep(40) + return True + + +def stop_ctdb_service(servers): + """ + stop ctdb services on all nodes + + Args: + servers (list): IP of samba nodes + + Returns: + bool: True if successfully stops ctdb service else false + """ + cmd = "service ctdb stop" + for mnode in servers: + ret, out, _ = g.run(mnode, cmd) + if ret: + g.log.error("Unable to stop ctdb on server %s", str(out)) + return False + if is_ctdb_service_running(servers, mnode): + g.log.error("ctdb services still running %s", str(out)) + return False + g.log.info("Stop ctdb on server %s successful", mnode) + return True + + +def ctdb_server_firewall_settings(servers): + """ + Do firewall settings for ctdb + + Args: + servers(list): IP of sambe nodes + + Returns: + bool: True if successfully added firewall services else false + """ + # List of services to enable + services = ['samba', 'rpc-bind'] + ret = add_services_to_firewall(servers, services, True) + if not ret: + g.log.error("Failed to set firewall zone " + "permanently on ctdb nodes") + return False + + # Add ctdb and samba port + if not is_rhel6(servers): + for mnode in servers: + ret, _, _ = g.run(mnode, "firewall-cmd --add-port=4379/tcp " + "--add-port=139/tcp") + if ret: + g.log.error("Failed to add firewall port in %s", mnode) + return False + g.log.info("samba ctdb port added successfully in %s", mnode) + ret, _, _ = g.run(mnode, "firewall-cmd --add-port=4379/tcp " + "--add-port=139/tcp --permanent") + if ret: + g.log.error("Failed to add firewall port permanently in %s", + mnode) + return False + return True + + +def parse_ctdb_status(status): + """ + Parse the ctdb status output + + Number of nodes:4 + pnn:0 <ip> OK (THIS NODE) + pnn:1 <ip> OK + pnn:2 <ip> OK + pnn:3 <ip> UHEALTHY + Generation:763624485 + Size:4 + hash:0 lmaster:0 + hash:1 lmaster:1 + hash:2 lmaster:2 + hash:3 lmaster:3 + Recovery mode:NORMAL (0) + Recovery master:3 + + Args: + status: output of ctdb status(string) + + Returns: + dict: {<ip>: status} + """ + cmd = r'pnn\:\d+\s*(\S+)\s*(\S+)' + ip_nodes = re.findall(cmd, status, re.S) + if ip_nodes: + # Empty dictionary to capture ctdb status output + node_status = {} + for item in ip_nodes: + node_status[item[0]] = item[1] + g.log.info("ctdb node status %s", node_status) + return node_status + else: + return {} + + +def ctdb_status(mnode): + """ + Execute ctdb status + + Args: + mnode(str): primary node out of the servers + + Returns: + tuple: Tuple containing three elements (ret, out, err). + The first element 'ret' is of type 'int' and is the return value + of command execution. + + The second element 'out' is of type 'str' and is the stdout value + of the command execution. + + The third element 'err' is of type 'str' and is the stderr value + of the command execution. + + """ + cmd = "ctdb status" + return g.run(mnode, cmd) + + +def is_ctdb_status_healthy(mnode): + """ + Check if ctdb is up & running + + Args: + mnode(str): primary node out of the servers + + Returns: + bool: True if ctdb status healthy else false + """ + # Get the ctdb status details + status_res = ctdb_status(mnode) + if status_res[0]: + g.log.info("CTDB is not enabled for the cluster") + return False + # Get the ctdb status output + output = status_res[1] + # Parse the ctdb status output + node_status = parse_ctdb_status(output) + if not node_status: + g.log.error("ctdb status return empty list") + return False + for node_ip, status in node_status.iteritems(): + # Check if ctdb status is OK or not + if node_status[node_ip] != 'OK': + g.log.error("CTDB node %s is %s", + str(node_ip), status) + return False + g.log.info("CTDB node %s is %s", + str(node_ip), status) + return True + + +def edit_hookscript_for_teardown(mnode, ctdb_volname): + """ + Edit the hook scripts with ctdb volume name + + Args: + mnode (str): Node on which commands has to be executed. + ctdb_volname (str): Name of ctdb volume + Returns: + bool: True if successfully edits hook-scripts else false + """ + # Replace META='ctdb_vol' to META=all setup hook script + cmd = ("sed -i -- 's/META=\"%s\"/META=\"all\"/g' " + "/var/lib/glusterd/hooks/1" + "/start/post/S29CTDBsetup.sh" % ctdb_volname) + ret, _, _ = g.run(mnode, cmd) + if ret: + g.log.error("Hook script - S29CTDBsetup edit failed on %s", mnode) + return False + + g.log.info("Hook script - S29CTDBsetup edit success on %s", mnode) + # Replace META='all' to META=ctdb_volname teardown hook script + cmd = ("sed -i -- 's/META=\"%s\"/META=\"all\"/g' " + "/var/lib/glusterd/hooks/1" + "/stop/pre/S29CTDB-teardown.sh" % ctdb_volname) + ret, _, _ = g.run(mnode, cmd) + if ret: + g.log.error("Hook script - S29CTDB-teardown edit failed on %s", mnode) + return False + g.log.info("Hook script - S29CTDBteardown edit success on %s", mnode) + return True + + +def teardown_samba_ctdb_cluster(servers, ctdb_volname): + """ + Tear down samba ctdb setup + + Args: + servers (list): Nodes in ctdb cluster to teardown entire + cluster + ctdb_volname (str): Name of ctdb volume + + Returns: + bool: True if successfully tear downs ctdb cluster else false + """ + + node_file_path = "/etc/ctdb/nodes" + publicip_file_path = "/etc/ctdb/public_addresses" + g.log.info("Executing force cleanup...") + # Stop ctdb service + if stop_ctdb_service(servers): + for mnode in servers: + # check if nodes file is available and delete + ret = check_file_availability(mnode, node_file_path, "nodes") + if not ret: + g.log.info("Failed to delete existing " + "nodes file in %s", mnode) + return False + g.log.info("Deleted existing nodes file in %s", mnode) + + # check if public_addresses file is available and delete + ret = check_file_availability(mnode, publicip_file_path, + "public_addresses") + if not ret: + g.log.info("Failed to delete existing public_addresses" + " file in %s", mnode) + return False + g.log.info("Deleted existing public_addresses" + "file in %s", mnode) + + ctdb_mount = '/gluster/lock' + ret, _, _ = umount_volume(mnode, ctdb_mount, 'glusterfs') + if ret: + g.log.error("Unable to unmount lock volume in %s", mnode) + return False + if not edit_hookscript_for_teardown(mnode, ctdb_volname): + return False + mnode = servers[0] + ret = cleanup_volume(mnode, ctdb_volname) + if not ret: + g.log.error("Failed to delete ctdb volume - %s", ctdb_volname) + return False + return True + return False diff --git a/glustolibs-gluster/glustolibs/gluster/dht_test_utils.py b/glustolibs-gluster/glustolibs/gluster/dht_test_utils.py index 692f09baf..11f2eda62 100644 --- a/glustolibs-gluster/glustolibs/gluster/dht_test_utils.py +++ b/glustolibs-gluster/glustolibs/gluster/dht_test_utils.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2018 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2018-2020 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 @@ -21,32 +21,43 @@ import os from glusto.core import Glusto as g -from glustolibs.gluster.glusterfile import GlusterFile, calculate_hash +from glustolibs.gluster.glusterfile import (GlusterFile, calculate_hash, + get_pathinfo, file_exists) from glustolibs.gluster.glusterdir import GlusterDir from glustolibs.gluster.layout import Layout import glustolibs.gluster.constants as k import glustolibs.gluster.exceptions as gex from glustolibs.gluster.brickdir import BrickDir -from glustolibs.gluster.volume_libs import get_subvols +from glustolibs.gluster.volume_libs import get_subvols, get_volume_type +from glustolibs.misc.misc_libs import upload_scripts -def run_layout_tests(fqpath, layout, test_type): +def run_layout_tests(mnode, fqpath, layout, test_type): """run the is_complete and/or is_balanced tests""" - if test_type & k.TEST_LAYOUT_IS_COMPLETE: - g.log.info("Testing layout complete for %s" % fqpath) - if not layout.is_complete: - msg = ("Layout for %s IS NOT COMPLETE" % fqpath) - g.log.error(msg) - raise gex.LayoutIsNotCompleteError(msg) - if test_type & k.TEST_LAYOUT_IS_BALANCED: - g.log.info("Testing layout balance for %s" % fqpath) - if not layout.is_balanced: - msg = ("Layout for %s IS NOT BALANCED" % fqpath) - g.log.error(msg) - raise gex.LayoutIsNotBalancedError(msg) - - # returning True until logic requires non-exception error check(s) - return True + ret = get_pathinfo(mnode, fqpath) + brick_path_list = ret.get('brickdir_paths') + for brickdir_path in brick_path_list: + (server_ip, _) = brickdir_path.split(':') + if get_volume_type(brickdir_path) in ('Replicate', 'Disperse', + 'Arbiter'): + g.log.info("Cannot check for layout completeness as" + " volume under test is Replicate/Disperse/Arbiter") + else: + if test_type & k.TEST_LAYOUT_IS_COMPLETE: + g.log.info("Testing layout complete for %s" % fqpath) + if not layout.is_complete: + msg = ("Layout for %s IS NOT COMPLETE" % fqpath) + g.log.error(msg) + raise gex.LayoutIsNotCompleteError(msg) + if test_type & k.TEST_LAYOUT_IS_BALANCED: + g.log.info("Testing layout balance for %s" % fqpath) + if not layout.is_balanced: + msg = ("Layout for %s IS NOT BALANCED" % fqpath) + g.log.error(msg) + raise gex.LayoutIsNotBalancedError(msg) + + # returning True until logic requires non-exception error check(s) + return True def run_hashed_bricks_test(gfile): @@ -62,13 +73,13 @@ def run_hashed_bricks_test(gfile): return True -def validate_files_in_dir(host, rootdir, +def validate_files_in_dir(mnode, rootdir, file_type=k.FILETYPE_ALL, test_type=k.TEST_ALL): """walk a directory tree and check if layout is_complete. Args: - host (str): The host of the directory being traversed. + mnode (str): The host of the directory being traversed. rootdir (str): The fully qualified path of the dir being traversed. file_type (int): An or'd set of constants defining the file types to test. @@ -108,16 +119,32 @@ def validate_files_in_dir(host, rootdir, """ layout_cache = {} - conn = g.rpyc_get_connection(host) - - for walkies in conn.modules.os.walk(rootdir): + script_path = ("/usr/share/glustolibs/scripts/walk_dir.py") + if not file_exists(mnode, script_path): + if upload_scripts(mnode, script_path, + "/usr/share/glustolibs/scripts/"): + g.log.info("Successfully uploaded script " + "walk_dir.py!") + else: + g.log.error("Faild to upload walk_dir.py!") + return False + else: + g.log.info("compute_hash.py already present!") + + cmd = ("/usr/bin/env python {0} {1}".format(script_path, rootdir)) + ret, out, _ = g.run(mnode, cmd) + if ret: + g.log.error('Unable to run the script on node {0}' + .format(mnode)) + return False + for walkies in eval(out): g.log.info("TESTING DIRECTORY %s..." % walkies[0]) # check directories if file_type & k.FILETYPE_DIR: for testdir in walkies[1]: fqpath = os.path.join(walkies[0], testdir) - gdir = GlusterDir(host, fqpath) + gdir = GlusterDir(mnode, fqpath) if gdir.parent_dir in layout_cache: layout = layout_cache[gdir.parent_dir] @@ -125,7 +152,7 @@ def validate_files_in_dir(host, rootdir, layout = Layout(gdir.parent_dir_pathinfo) layout_cache[gdir.parent_dir] = layout - run_layout_tests(gdir.parent_dir, layout, test_type) + run_layout_tests(mnode, gdir.parent_dir, layout, test_type) if test_type & k.TEST_FILE_EXISTS_ON_HASHED_BRICKS: run_hashed_bricks_test(gdir) @@ -134,7 +161,7 @@ def validate_files_in_dir(host, rootdir, if file_type & k.FILETYPE_FILE: for file in walkies[2]: fqpath = os.path.join(walkies[0], file) - gfile = GlusterFile(host, fqpath) + gfile = GlusterFile(mnode, fqpath) if gfile.parent_dir in layout_cache: layout = layout_cache[gfile.parent_dir] @@ -142,11 +169,11 @@ def validate_files_in_dir(host, rootdir, layout = Layout(gfile.parent_dir_pathinfo) layout_cache[gfile.parent_dir] = layout - run_layout_tests(gfile.parent_dir, layout, test_type) + run_layout_tests(mnode, gfile.parent_dir, layout, + test_type) if test_type & k.TEST_FILE_EXISTS_ON_HASHED_BRICKS: run_hashed_bricks_test(gfile) - return True @@ -281,11 +308,22 @@ def find_new_hashed(subvols, parent_path, oldname): g.log.error("could not form brickobject list") return None + for bro in brickobject: + bro._get_hashrange() + low = bro._hashrange_low + high = bro._hashrange_high + g.log.debug("low hashrange %s high hashrange %s", str(low), str(high)) + g.log.debug("absoulte path %s", bro._fqpath) + + hash_num = calculate_hash(brickobject[0]._host, oldname) oldhashed, _ = find_hashed_subvol(subvols, parent_path, oldname) if oldhashed is None: g.log.error("could not find old hashed subvol") return None + g.log.debug("oldhashed: %s oldname: %s oldhash %s", oldhashed._host, + oldname, hash_num) + count = -1 for item in range(1, 5000, 1): newhash = calculate_hash(brickobject[0]._host, str(item)) @@ -293,7 +331,7 @@ def find_new_hashed(subvols, parent_path, oldname): count += 1 ret = brickdir.hashrange_contains_hash(newhash) if ret == 1: - if oldhashed._host != brickdir._host: + if oldhashed._fqpath != brickdir._fqpath: g.log.debug("oldhashed %s new %s count %s", oldhashed, brickdir._host, str(count)) return NewHashed(item, brickdir, count) @@ -302,6 +340,44 @@ def find_new_hashed(subvols, parent_path, oldname): return None +def find_specific_hashed(subvols, parent_path, subvol, existing_names=None): + """ Finds filename that hashes to a specific subvol. + + Args: + subvols(list): list of subvols + parent_path(str): parent path (relative to mount) of "oldname" + subvol(str): The subvol to which the new name has to be hashed + existing_names(int|list): The name(s) already hashed to subvol + + Returns: + (Class Object): For success returns an object of type NewHashed + holding information pertaining to new name. + None, otherwise + Note: The new hash will be searched under the same parent + """ + # pylint: disable=protected-access + if not isinstance(existing_names, list): + existing_names = [existing_names] + brickobject = create_brickobjectlist(subvols, parent_path) + if brickobject is None: + g.log.error("could not form brickobject list") + return None + count = -1 + for item in range(1, 5000, 1): + newhash = calculate_hash(brickobject[0]._host, str(item)) + for brickdir in brickobject: + count += 1 + if (subvol._fqpath == brickdir._fqpath and + item not in existing_names): + ret = brickdir.hashrange_contains_hash(newhash) + if ret: + g.log.debug("oldhashed %s new %s count %s", + subvol, brickdir._host, str(count)) + return NewHashed(item, brickdir, count) + count = -1 + return None + + class NewHashed(object): ''' Helper Class to hold new hashed info @@ -380,3 +456,5 @@ def is_layout_complete(mnode, volname, dirpath): return False elif hash_difference < 1: g.log.error("Layout has overlaps") + + return True diff --git a/glustolibs-gluster/glustolibs/gluster/geo_rep_libs.py b/glustolibs-gluster/glustolibs/gluster/geo_rep_libs.py index a850681ce..20531b946 100644 --- a/glustolibs-gluster/glustolibs/gluster/geo_rep_libs.py +++ b/glustolibs-gluster/glustolibs/gluster/geo_rep_libs.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2018 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2017-2020 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 @@ -36,6 +36,8 @@ from glustolibs.gluster.lib_utils import (group_add, ssh_copy_id, is_group_exists, is_user_exists, is_passwordless_ssh_configured) from glustolibs.gluster.glusterdir import get_dir_contents +from glustolibs.gluster.volume_ops import set_volume_options +from glustolibs.gluster.volume_libs import setup_volume def georep_prerequisites(mnode, snode, passwd, user="root", group=None, @@ -302,3 +304,58 @@ def georep_create_session(mnode, snode, mastervol, slavevol, (user, mastervol, slavevol)) return False return True + + +def setup_master_and_slave_volumes(mnode, all_servers_info, + master_volume_config, + snode, all_slaves_info, + slave_volume_config, + force=False): + """Create master and slave volumes for geo-replication. + + Args: + mnode(str): The primary master node where the commands are executed. + all_servers_info(dict): Information about all master servers. + master_volume_config(dict): Dict containing volume information + of master. + snode(str): slave node where the commande are executed. + all_slaves_info(dict): Information about all slave servers. + slave_volume_config(dict): Dict containing volume information + of slave. + kwargs: + force(bool): If set to true then will create volumes + with force option. + + Returns: + bool : True if volumes created successfully, false if there are + any failures in the middle. + + Example: + setup_master_and_slave_volumes( + cls.mode, cls.all_servers_info, cls.master_volume, + cls.snode, cls.all_slaves_info, cls.slave_volume) + >>> True + """ + # Setting up the master and the slave volume. + ret = setup_volume(mnode, all_servers_info, master_volume_config, + force) + if not ret: + g.log.error("Failed to Setup master volume %s", + master_volume_config['name']) + return False + + ret = setup_volume(snode, all_slaves_info, slave_volume_config, + force) + if not ret: + g.log.error("Failed to Setup slave volume %s", + slave_volume_config['name']) + return False + + # Setting performance.quick-read to off. + ret = set_volume_options(snode, slave_volume_config['name'], + {"performance.quick-read": "off"}) + if not ret: + g.log.error("Failed to performance.quick-read to off on " + "slave volume %s", slave_volume_config['name']) + return False + return True diff --git a/glustolibs-gluster/glustolibs/gluster/geo_rep_ops.py b/glustolibs-gluster/glustolibs/gluster/geo_rep_ops.py index 7e12113c9..7d0f5a73e 100755 --- a/glustolibs-gluster/glustolibs/gluster/geo_rep_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/geo_rep_ops.py @@ -292,7 +292,7 @@ def georep_config_set(mnode, mastervol, slaveip, slavevol, config, value, """ if user: - cmd = ("gluster volume geo-replication %s %s::%s config %s %s" % + cmd = ("gluster volume geo-replication %s %s@%s::%s config %s %s" % (mastervol, user, slaveip, slavevol, config, value)) else: cmd = ("gluster volume geo-replication %s %s::%s config %s %s" % diff --git a/glustolibs-gluster/glustolibs/gluster/gluster_base_class.py b/glustolibs-gluster/glustolibs/gluster/gluster_base_class.py index 7cd128f1a..65061cb13 100644..100755 --- a/glustolibs-gluster/glustolibs/gluster/gluster_base_class.py +++ b/glustolibs-gluster/glustolibs/gluster/gluster_base_class.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2018-2021 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 @@ -29,6 +29,7 @@ from socket import ( gaierror, ) from unittest import TestCase +from time import sleep from glusto.core import Glusto as g @@ -41,17 +42,27 @@ from glustolibs.gluster.mount_ops import create_mount_objs from glustolibs.gluster.nfs_libs import export_volume_through_nfs from glustolibs.gluster.peer_ops import ( is_peer_connected, - peer_status, + peer_probe_servers, peer_status ) +from glustolibs.gluster.gluster_init import ( + restart_glusterd, stop_glusterd, wait_for_glusterd_to_start) from glustolibs.gluster.samba_libs import share_volume_over_smb +from glustolibs.gluster.shared_storage_ops import is_shared_volume_mounted from glustolibs.gluster.volume_libs import ( cleanup_volume, log_volume_info_and_status, setup_volume, wait_for_volume_process_to_be_online, ) -from glustolibs.gluster.volume_ops import set_volume_options +from glustolibs.gluster.brick_libs import ( + wait_for_bricks_to_be_online, get_offline_bricks_list) +from glustolibs.gluster.volume_ops import ( + set_volume_options, volume_reset, volume_start) from glustolibs.io.utils import log_mounts_info +from glustolibs.gluster.geo_rep_libs import setup_master_and_slave_volumes +from glustolibs.gluster.nfs_ganesha_ops import ( + teardown_nfs_ganesha_cluster) +from glustolibs.misc.misc_libs import kill_process class runs_on(g.CarteTestClass): @@ -95,6 +106,7 @@ class GlusterBaseClass(TestCase): # defaults in setUpClass() volume_type = None mount_type = None + error_or_failure_exists = False @staticmethod def get_super_method(obj, method_name): @@ -184,6 +196,11 @@ class GlusterBaseClass(TestCase): Returns (bool): True if all peers are in connected with other peers. False otherwise. """ + + # If the setup has single node server, by pass this validation. + if len(cls.servers) == 1: + return True + # Validate if peer is connected from all the servers g.log.info("Validating if servers %s are connected from other servers " "in the cluster", cls.servers) @@ -206,11 +223,121 @@ class GlusterBaseClass(TestCase): return True + def _is_error_or_failure_exists(self): + """Function to get execution error in case of + failures in testcases + """ + if hasattr(self, '_outcome'): + # Python 3.4+ + result = self.defaultTestResult() + self._feedErrorsToResult(result, self._outcome.errors) + else: + # Python 2.7-3.3 + result = getattr( + self, '_outcomeForDoCleanups', self._resultForDoCleanups) + ok_result = True + for attr in ('errors', 'failures'): + if not hasattr(result, attr): + continue + exc_list = getattr(result, attr) + if exc_list and exc_list[-1][0] is self: + ok_result = ok_result and not exc_list[-1][1] + if hasattr(result, '_excinfo'): + ok_result = ok_result and not result._excinfo + if ok_result: + return False + self.error_or_failure_exists = True + GlusterBaseClass.error_or_failure_exists = True + return True + + @classmethod + def scratch_cleanup(cls, error_or_failure_exists): + """ + This scratch_cleanup script will run only when the code + currently running goes into execution or assertion error. + + Args: + error_or_failure_exists (bool): If set True will cleanup setup + atlast of testcase only if exectution or assertion error in + teststeps. False will skip this scratch cleanup step. + + Returns (bool): True if setup cleanup is successful. + False otherwise. + """ + if error_or_failure_exists: + shared_storage_mounted = False + if is_shared_volume_mounted(cls.mnode): + shared_storage_mounted = True + ret = stop_glusterd(cls.servers) + if not ret: + g.log.error("Failed to stop glusterd") + cmd_list = ("pkill `pidof glusterd`", + "rm /var/run/glusterd.socket") + for server in cls.servers: + for cmd in cmd_list: + ret, _, _ = g.run(server, cmd, "root") + if ret: + g.log.error("Failed to stop glusterd") + return False + for server in cls.servers: + ret, out, _ = g.run(server, "pgrep glusterfsd", "root") + if not ret: + ret = kill_process(server, + process_ids=out.strip().split('\n')) + if not ret: + g.log.error("Unable to kill process {}".format( + out.strip().split('\n'))) + return False + if not shared_storage_mounted: + cmd_list = ( + "rm -rf /var/lib/glusterd/vols/*", + "rm -rf /var/lib/glusterd/snaps/*", + "rm -rf /var/lib/glusterd/peers/*", + "rm -rf {}/*/*".format( + cls.all_servers_info[server]['brick_root'])) + else: + cmd_list = ( + "for vol in `ls /var/lib/glusterd/vols/ | " + "grep -v gluster_shared_storage`;do " + "rm -rf /var/lib/glusterd/vols/$vol;done", + "rm -rf /var/lib/glusterd/snaps/*" + "rm -rf {}/*/*".format( + cls.all_servers_info[server]['brick_root'])) + for cmd in cmd_list: + ret, _, _ = g.run(server, cmd, "root") + if ret: + g.log.error( + "failed to cleanup server {}".format(server)) + return False + ret = restart_glusterd(cls.servers) + if not ret: + g.log.error("Failed to start glusterd") + return False + sleep(2) + ret = wait_for_glusterd_to_start(cls.servers) + if not ret: + g.log.error("Failed to bring glusterd up") + return False + if not shared_storage_mounted: + ret = peer_probe_servers(cls.mnode, cls.servers) + if not ret: + g.log.error("Failed to peer probe servers") + return False + for client in cls.clients: + cmd_list = ("umount /mnt/*", "rm -rf /mnt/*") + for cmd in cmd_list: + ret = g.run(client, cmd, "root") + if ret: + g.log.error( + "failed to unmount/already unmounted {}" + .format(client)) + return True + @classmethod - def setup_volume(cls, volume_create_force=False): + def setup_volume(cls, volume_create_force=False, only_volume_create=False): """Setup the volume: - Create the volume, Start volume, Set volume - options, enable snapshot/quota/tier if specified in the config + options, enable snapshot/quota if specified in the config file. - Wait for volume processes to be online - Export volume as NFS/SMB share if mount_type is NFS or SMB @@ -219,6 +346,9 @@ class GlusterBaseClass(TestCase): Args: volume_create_force(bool): True if create_volume should be executed with 'force' option. + only_volume_create(bool): True, only volume creation is needed + False, by default volume creation and + start. Returns (bool): True if all the steps mentioned in the descriptions passes. False otherwise. @@ -241,12 +371,19 @@ class GlusterBaseClass(TestCase): g.log.info("Setting up volume %s", cls.volname) ret = setup_volume(mnode=cls.mnode, all_servers_info=cls.all_servers_info, - volume_config=cls.volume, force=force_volume_create) + volume_config=cls.volume, force=force_volume_create, + create_only=only_volume_create) if not ret: g.log.error("Failed to Setup volume %s", cls.volname) return False g.log.info("Successful in setting up volume %s", cls.volname) + # Returning the value without proceeding for next steps + if only_volume_create and ret: + g.log.info("Setup volume with volume creation {} " + "successful".format(cls.volname)) + return True + # Wait for volume processes to be online g.log.info("Wait for volume %s processes to be online", cls.volname) ret = wait_for_volume_process_to_be_online(cls.mnode, cls.volname) @@ -337,6 +474,9 @@ class GlusterBaseClass(TestCase): """ g.log.info("Starting to mount volume %s", cls.volname) for mount_obj in mounts: + # For nfs-ganesha, mount is done via vip + if cls.enable_nfs_ganesha: + mount_obj.server_system = cls.vips[0] g.log.info("Mounting volume '%s:%s' on '%s:%s'", mount_obj.server_system, mount_obj.volname, mount_obj.client_system, mount_obj.mountpoint) @@ -385,6 +525,53 @@ class GlusterBaseClass(TestCase): return True @classmethod + def bricks_online_and_volume_reset(cls): + """ + reset the volume if any bricks are offline. + waits for all bricks to be online and resets + volume options set + """ + bricks_offline = get_offline_bricks_list(cls.mnode, cls.volname) + if bricks_offline is not None: + ret = volume_start(cls.mnode, cls.volname, force=True) + if not ret: + raise ExecutionError("Failed to force start volume" + "%s" % cls.volname) + ret = wait_for_bricks_to_be_online(cls.mnode, cls.volname) + if not ret: + raise ExecutionError("Failed to bring bricks online" + "for volume %s" % cls.volname) + + ret, _, _ = volume_reset(cls.mnode, cls.volname, force=True) + if ret: + raise ExecutionError("Failed to reset volume %s" % cls.volname) + g.log.info("Successful in volume reset %s", cls.volname) + + @classmethod + def setup_and_mount_geo_rep_master_and_slave_volumes(cls, force=False): + """Setup geo-rep master and slave volumes. + + Returns (bool): True if cleanup volume is successful. False otherwise. + """ + # Creating and starting master and slave volume. + ret = setup_master_and_slave_volumes( + cls.mode, cls.all_servers_info, cls.master_volume, + cls.snode, cls.all_slaves_info, cls.slave_volume, + force) + if not ret: + g.log.error('Failed to create master and slave volumes.') + return False + + # Mounting master and slave volumes + for mount in [cls.master_mounts, cls.slave_mounts]: + ret = cls.mount_volume(cls, mount) + if not ret: + g.log.error('Failed to mount volume %s.', + mount['volname']) + return False + return True + + @classmethod def unmount_volume(cls, mounts): """Unmount all mounts for the volume @@ -460,6 +647,7 @@ class GlusterBaseClass(TestCase): Returns (bool): True if cleanup volume is successful. False otherwise. """ + cls.bricks_online_and_volume_reset() g.log.info("Cleanup Volume %s", cls.volname) ret = cleanup_volume(mnode=cls.mnode, volname=cls.volname) if not ret: @@ -729,6 +917,25 @@ class GlusterBaseClass(TestCase): cls.mnode = cls.servers[0] cls.vol_options = cls.volume['options'] + # Define useful variable for geo-rep volumes. + if cls.slaves: + # For master volume + cls.master_volume = cls.volume + cls.master_volume['name'] = ('master_testvol_%s' + % cls.volume_type) + cls.master_volname = cls.master_volume['name'] + cls.master_voltype = (cls.master_volume['voltype'] + ['type']) + + # For slave volume + cls.slave_volume = deepcopy(cls.volume) + cls.slave_volume['name'] = ('slave_testvol_%s' + % cls.volume_type) + cls.slave_volume['servers'] = cls.slaves + cls.slave_volname = cls.slave_volume['name'] + cls.slave_voltype = (cls.slave_volume['voltype'] + ['type']) + # Get the mount configuration. cls.mounts = [] if cls.mount_type: @@ -777,6 +984,22 @@ class GlusterBaseClass(TestCase): cls.mounts = create_mount_objs(cls.mounts_dict_list) + # Setting mounts for geo-rep volumes. + if cls.slaves: + + # For master volume mount + cls.master_mounts = cls.mounts + + # For slave volume mount + slave_mount_dict_list = deepcopy(cls.mounts_dict_list) + for mount_dict in slave_mount_dict_list: + mount_dict['volname'] = cls.slave_volume + mount_dict['server'] = cls.mnode_slave + mount_dict['mountpoint'] = path_join( + "/mnt", '_'.join([cls.slave_volname, + cls.mount_type])) + cls.slave_mounts = create_mount_objs(slave_mount_dict_list) + # Defining clients from mounts. cls.clients = [] for mount in cls.mounts_dict_list: @@ -814,6 +1037,31 @@ class GlusterBaseClass(TestCase): datetime.now().strftime('%H_%M_%d_%m_%Y')) cls.glustotest_run_id = g.config['glustotest_run_id'] + if cls.enable_nfs_ganesha: + g.log.info("Setup NFS_Ganesha") + cls.num_of_nfs_ganesha_nodes = int(cls.num_of_nfs_ganesha_nodes) + cls.servers_in_nfs_ganesha_cluster = ( + cls.servers[:cls.num_of_nfs_ganesha_nodes]) + cls.vips_in_nfs_ganesha_cluster = ( + cls.vips[:cls.num_of_nfs_ganesha_nodes]) + + # Obtain hostname of servers in ganesha cluster + cls.ganesha_servers_hostname = [] + for ganesha_server in cls.servers_in_nfs_ganesha_cluster: + ret, hostname, _ = g.run(ganesha_server, "hostname") + if ret: + raise ExecutionError("Failed to obtain hostname of %s" + % ganesha_server) + hostname = hostname.strip() + g.log.info("Obtained hostname: IP- %s, hostname- %s", + ganesha_server, hostname) + cls.ganesha_servers_hostname.append(hostname) + from glustolibs.gluster.nfs_ganesha_libs import setup_nfs_ganesha + ret = setup_nfs_ganesha(cls) + if not ret: + raise ExecutionError("Failed to setup nfs ganesha") + g.log.info("Successful in setting up NFS Ganesha Cluster") + msg = "Setupclass: %s : %s" % (cls.__name__, cls.glustotest_run_id) g.log.info(msg) cls.inject_msg_in_gluster_logs(msg) @@ -836,3 +1084,264 @@ class GlusterBaseClass(TestCase): msg = "Teardownclass: %s : %s" % (cls.__name__, cls.glustotest_run_id) g.log.info(msg) cls.inject_msg_in_gluster_logs(msg) + + def doCleanups(self): + if (self.error_or_failure_exists or + self._is_error_or_failure_exists()): + ret = self.scratch_cleanup(self.error_or_failure_exists) + g.log.info(ret) + return self.get_super_method(self, 'doCleanups')() + + @classmethod + def doClassCleanups(cls): + if (GlusterBaseClass.error_or_failure_exists or + cls._is_error_or_failure_exists()): + ret = cls.scratch_cleanup( + GlusterBaseClass.error_or_failure_exists) + g.log.info(ret) + return cls.get_super_method(cls, 'doClassCleanups')() + + @classmethod + def delete_nfs_ganesha_cluster(cls): + ret = teardown_nfs_ganesha_cluster( + cls.servers_in_nfs_ganesha_cluster) + if not ret: + g.log.error("Teardown got failed. Hence, cleaning up " + "nfs-ganesha cluster forcefully") + ret = teardown_nfs_ganesha_cluster( + cls.servers_in_nfs_ganesha_cluster, force=True) + if not ret: + raise ExecutionError("Force cleanup of nfs-ganesha " + "cluster failed") + g.log.info("Teardown nfs ganesha cluster succeeded") + + @classmethod + def start_memory_and_cpu_usage_logging(cls, test_id, interval=60, + count=100): + """Upload logger script and start logging usage on cluster + + Args: + test_id(str): ID of the test running fetched from self.id() + + Kawrgs: + interval(int): Time interval after which logs are to be collected + (Default: 60) + count(int): Number of samples to be collected(Default: 100) + + Returns: + proc_dict(dict):Dictionary of logging processes + """ + # imports are added inside function to make it them + # optional and not cause breakage on installation + # which don't use the resource leak library + from glustolibs.io.memory_and_cpu_utils import ( + check_upload_memory_and_cpu_logger_script, + log_memory_and_cpu_usage_on_cluster) + + # Checking if script is present on servers or not if not then + # upload it to servers. + if not check_upload_memory_and_cpu_logger_script(cls.servers): + return None + + # Checking if script is present on clients or not if not then + # upload it to clients. + if not check_upload_memory_and_cpu_logger_script(cls.clients): + return None + + # Start logging on servers and clients + proc_dict = log_memory_and_cpu_usage_on_cluster( + cls.servers, cls.clients, test_id, interval, count) + + return proc_dict + + @classmethod + def compute_and_print_usage_stats(cls, test_id, proc_dict, + kill_proc=False): + """Compute and print CPU and memory usage statistics + + Args: + proc_dict(dict):Dictionary of logging processes + test_id(str): ID of the test running fetched from self.id() + + Kwargs: + kill_proc(bool): Kill logging process if true else wait + for process to complete execution + """ + # imports are added inside function to make it them + # optional and not cause breakage on installation + # which don't use the resource leak library + from glustolibs.io.memory_and_cpu_utils import ( + wait_for_logging_processes_to_stop, kill_all_logging_processes, + compute_data_usage_stats_on_servers, + compute_data_usage_stats_on_clients) + + # Wait or kill running logging process + if kill_proc: + nodes = cls.servers + cls.clients + ret = kill_all_logging_processes(proc_dict, nodes, cluster=True) + if not ret: + g.log.error("Unable to stop logging processes.") + else: + ret = wait_for_logging_processes_to_stop(proc_dict, cluster=True) + if not ret: + g.log.error("Processes didn't complete still running.") + + # Compute and print stats for servers + ret = compute_data_usage_stats_on_servers(cls.servers, test_id) + g.log.info('*' * 50) + g.log.info(ret) # TODO: Make logged message more structured + g.log.info('*' * 50) + + # Compute and print stats for clients + ret = compute_data_usage_stats_on_clients(cls.clients, test_id) + g.log.info('*' * 50) + g.log.info(ret) # TODO: Make logged message more structured + g.log.info('*' * 50) + + @classmethod + def check_for_memory_leaks_and_oom_kills_on_servers(cls, test_id, + gain=30.0): + """Check for memory leaks and OOM kills on servers + + Args: + test_id(str): ID of the test running fetched from self.id() + + Kwargs: + gain(float): Accepted amount of leak for a given testcase in MB + (Default:30) + + Returns: + bool: True if memory leaks or OOM kills are observed else false + """ + # imports are added inside function to make it them + # optional and not cause breakage on installation + # which don't use the resource leak library + from glustolibs.io.memory_and_cpu_utils import ( + check_for_memory_leaks_in_glusterd, + check_for_memory_leaks_in_glusterfs, + check_for_memory_leaks_in_glusterfsd, + check_for_oom_killers_on_servers) + + # Check for memory leaks on glusterd + if check_for_memory_leaks_in_glusterd(cls.servers, test_id, gain): + g.log.error("Memory leak on glusterd.") + return True + + if cls.volume_type != "distributed": + # Check for memory leaks on shd + if check_for_memory_leaks_in_glusterfs(cls.servers, test_id, + gain): + g.log.error("Memory leak on shd.") + return True + + # Check for memory leaks on brick processes + if check_for_memory_leaks_in_glusterfsd(cls.servers, test_id, gain): + g.log.error("Memory leak on brick process.") + return True + + # Check OOM kills on servers for all gluster server processes + if check_for_oom_killers_on_servers(cls.servers): + g.log.error('OOM kills present on servers.') + return True + return False + + @classmethod + def check_for_memory_leaks_and_oom_kills_on_clients(cls, test_id, gain=30): + """Check for memory leaks and OOM kills on clients + + Args: + test_id(str): ID of the test running fetched from self.id() + + Kwargs: + gain(float): Accepted amount of leak for a given testcase in MB + (Default:30) + + Returns: + bool: True if memory leaks or OOM kills are observed else false + """ + # imports are added inside function to make it them + # optional and not cause breakage on installation + # which don't use the resource leak library + from glustolibs.io.memory_and_cpu_utils import ( + check_for_memory_leaks_in_glusterfs_fuse, + check_for_oom_killers_on_clients) + + # Check for memory leak on glusterfs fuse process + if check_for_memory_leaks_in_glusterfs_fuse(cls.clients, test_id, + gain): + g.log.error("Memory leaks observed on FUSE clients.") + return True + + # Check for oom kills on clients + if check_for_oom_killers_on_clients(cls.clients): + g.log.error("OOM kills present on clients.") + return True + return False + + @classmethod + def check_for_cpu_usage_spikes_on_servers(cls, test_id, threshold=3): + """Check for CPU usage spikes on servers + + Args: + test_id(str): ID of the test running fetched from self.id() + + Kwargs: + threshold(int): Accepted amount of instances of 100% CPU usage + (Default:3) + Returns: + bool: True if CPU spikes are more than threshold else False + """ + # imports are added inside function to make it them + # optional and not cause breakage on installation + # which don't use the resource leak library + from glustolibs.io.memory_and_cpu_utils import ( + check_for_cpu_usage_spikes_on_glusterd, + check_for_cpu_usage_spikes_on_glusterfs, + check_for_cpu_usage_spikes_on_glusterfsd) + + # Check for CPU usage spikes on glusterd + if check_for_cpu_usage_spikes_on_glusterd(cls.servers, test_id, + threshold): + g.log.error("CPU usage spikes observed more than threshold " + "on glusterd.") + return True + + if cls.volume_type != "distributed": + # Check for CPU usage spikes on shd + if check_for_cpu_usage_spikes_on_glusterfs(cls.servers, test_id, + threshold): + g.log.error("CPU usage spikes observed more than threshold " + "on shd.") + return True + + # Check for CPU usage spikes on brick processes + if check_for_cpu_usage_spikes_on_glusterfsd(cls.servers, test_id, + threshold): + g.log.error("CPU usage spikes observed more than threshold " + "on shd.") + return True + return False + + @classmethod + def check_for_cpu_spikes_on_clients(cls, test_id, threshold=3): + """Check for CPU usage spikes on clients + + Args: + test_id(str): ID of the test running fetched from self.id() + + Kwargs: + threshold(int): Accepted amount of instances of 100% CPU usage + (Default:3) + Returns: + bool: True if CPU spikes are more than threshold else False + """ + # imports are added inside function to make it them + # optional and not cause breakage on installation + # which don't use the resource leak library + from glustolibs.io.memory_and_cpu_utils import ( + check_for_cpu_usage_spikes_on_glusterfs_fuse) + + ret = check_for_cpu_usage_spikes_on_glusterfs_fuse(cls.clients, + test_id, + threshold) + return ret diff --git a/glustolibs-gluster/glustolibs/gluster/gluster_init.py b/glustolibs-gluster/glustolibs/gluster/gluster_init.py index 29059e6a1..6a49ffc8b 100644 --- a/glustolibs-gluster/glustolibs/gluster/gluster_init.py +++ b/glustolibs-gluster/glustolibs/gluster/gluster_init.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2020 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -23,13 +23,17 @@ from time import sleep from glusto.core import Glusto as g -def start_glusterd(servers): +def start_glusterd(servers, enable_retry=True): """Starts glusterd on specified servers if they are not running. Args: servers (str|list): A server|List of server hosts on which glusterd has to be started. + Kwargs: + enable_retry(Bool): If set to True then runs reset-failed else + do nothing. + Returns: bool : True if starting glusterd is successful on all servers. False otherwise. @@ -46,10 +50,13 @@ def start_glusterd(servers): if retcode != 0: g.log.error("Unable to start glusterd on server %s", server) _rc = False - if not _rc: - return False + if not _rc and enable_retry: + ret = reset_failed_glusterd(servers) + if ret: + ret = start_glusterd(servers) + return ret - return True + return _rc def stop_glusterd(servers): @@ -81,13 +88,17 @@ def stop_glusterd(servers): return True -def restart_glusterd(servers): +def restart_glusterd(servers, enable_retry=True): """Restart the glusterd on specified servers. Args: servers (str|list): A server|List of server hosts on which glusterd has to be restarted. + Kwargs: + enable_retry(Bool): If set to True than runs reset-failed else + do nothing. + Returns: bool : True if restarting glusterd is successful on all servers. False otherwise. @@ -104,9 +115,35 @@ def restart_glusterd(servers): if retcode != 0: g.log.error("Unable to restart glusterd on server %s", server) _rc = False - if not _rc: - return False + if not _rc and enable_retry: + ret = reset_failed_glusterd(servers) + if ret: + ret = restart_glusterd(servers) + return ret + return _rc + + +def reset_failed_glusterd(servers): + """Reset-failed glusterd on specified servers. + + Args: + servers (str|list): A server|List of server hosts on which glusterd + has to be reset-failed. + + Returns: + bool : True if reset-failed glusterd is successful on all servers. + False otherwise. + """ + if not isinstance(servers, list): + servers = [servers] + + cmd = "systemctl reset-failed glusterd" + results = g.run_parallel(servers, cmd) + for server, (retcode, _, _) in results.items(): + if retcode: + g.log.error("Unable to reset glusterd on server %s", server) + return False return True @@ -260,10 +297,10 @@ def get_gluster_version(host): host(str): IP of the host whose gluster version has to be checked. Returns: - (float): The gluster version value. + str: The gluster version value. """ command = 'gluster --version' _, out, _ = g.run(host, command) g.log.info("The Gluster verion of the cluster under test is %s", out) - return float(out.split(' ')[1]) + return out.split(' ')[1] diff --git a/glustolibs-gluster/glustolibs/gluster/glusterdir.py b/glustolibs-gluster/glustolibs/gluster/glusterdir.py index f2981cb93..5618926c8 100644 --- a/glustolibs-gluster/glustolibs/gluster/glusterdir.py +++ b/glustolibs-gluster/glustolibs/gluster/glusterdir.py @@ -82,22 +82,29 @@ def rmdir(host, fqpath, force=False): return False -def get_dir_contents(host, path): +def get_dir_contents(host, path, recursive=False): """Get the files and directories present in a given directory. Args: host (str): The hostname/ip of the remote system. path (str): The path to the directory. + Kwargs: + recursive (bool): lists all entries recursively + Returns: file_dir_list (list): List of files and directories on path. None: In case of error or failure. """ - ret, out, _ = g.run(host, ' ls '+path) - if ret != 0: + if recursive: + cmd = "find {}".format(path) + else: + cmd = "ls " + path + ret, out, _ = g.run(host, cmd) + if ret: + g.log.error("No such file or directory {}".format(path)) return None - file_dir_list = list(filter(None, out.split("\n"))) - return file_dir_list + return(list(filter(None, out.split("\n")))) class GlusterDir(GlusterFile): diff --git a/glustolibs-gluster/glustolibs/gluster/glusterfile.py b/glustolibs-gluster/glustolibs/gluster/glusterfile.py index 413a4f9a7..ee9b6040d 100755 --- a/glustolibs-gluster/glustolibs/gluster/glusterfile.py +++ b/glustolibs-gluster/glustolibs/gluster/glusterfile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2018 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2018-2020 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 @@ -27,8 +27,8 @@ import os import re from glusto.core import Glusto as g - from glustolibs.gluster.layout import Layout +from glustolibs.misc.misc_libs import upload_scripts def calculate_hash(host, filename): @@ -39,26 +39,43 @@ def calculate_hash(host, filename): Returns: An integer representation of the hash + + TODO: For testcases specifically testing hashing routine + consider using a baseline external Davies-Meyer hash_value.c + Creating comparison hash from same library we are testing + may not be best practice here. (Holloway) """ - # TODO: For testcases specifically testing hashing routine - # consider using a baseline external Davies-Meyer hash_value.c - # Creating comparison hash from same library we are testing - # may not be best practice here. (Holloway) try: # Check if libglusterfs.so.0 is available locally glusterfs = ctypes.cdll.LoadLibrary("libglusterfs.so.0") g.log.debug("Library libglusterfs.so.0 loaded locally") + computed_hash = ( + ctypes.c_uint32(glusterfs.gf_dm_hashfn(filename, + len(filename)))) + hash_value = int(computed_hash.value) except OSError: - conn = g.rpyc_get_connection(host) - glusterfs = \ - conn.modules.ctypes.cdll.LoadLibrary("libglusterfs.so.0") - g.log.debug("Library libglusterfs.so.0 loaded via rpyc") - - computed_hash = \ - ctypes.c_uint32(glusterfs.gf_dm_hashfn(filename, len(filename))) - # conn.close() - - return int(computed_hash.value) + script_path = ("/usr/share/glustolibs/scripts/" + "compute_hash.py") + if not file_exists(host, script_path): + if upload_scripts(host, script_path, + '/usr/share/glustolibs/scripts/'): + g.log.info("Successfully uploaded script " + "compute_hash.py!") + else: + g.log.error('Unable to upload the script to node {0}' + .format(host)) + return 0 + else: + g.log.info("compute_hash.py already present!") + cmd = ("/usr/bin/env python {0} {1}".format(script_path, + filename)) + ret, out, _ = g.run(host, cmd) + if ret: + g.log.error('Unable to run the script on node {0}' + .format(host)) + return 0 + hash_value = int(out.split('\n')[0]) + return hash_value def get_mountpoint(host, fqpath): @@ -80,40 +97,50 @@ def get_mountpoint(host, fqpath): return None -def get_fattr(host, fqpath, fattr): +def get_fattr(host, fqpath, fattr, encode="hex"): """getfattr for filepath on remote system Args: host (str): The hostname/ip of the remote system. fqpath (str): The fully-qualified path to the file. fattr (str): name of the fattr to retrieve - + Kwargs: + encode(str): The supported types of encoding are + [hex|text|base64] + Defaults to hex type of encoding Returns: getfattr result on success. None on fail. """ - command = ("getfattr --absolute-names --only-values -n '%s' %s" % - (fattr, fqpath)) + command = ("getfattr --absolute-names -e '%s' " + "-n '%s' %s" % + (encode, fattr, fqpath)) rcode, rout, rerr = g.run(host, command) - - if rcode == 0: - return rout.strip() + if not rcode: + return rout.strip().split('=')[1].replace('"', '') g.log.error('getfattr failed: %s' % rerr) return None -def get_fattr_list(host, fqpath): +def get_fattr_list(host, fqpath, encode_hex=False): """List of xattr for filepath on remote system. Args: host (str): The hostname/ip of the remote system. fqpath (str): The fully-qualified path to the file. + Kwargs: + encode_hex(bool): Fetch xattr in hex if True + (Default:False) + Returns: Dictionary of xattrs on success. None on fail. """ - command = "getfattr --absolute-names -d -m - %s" % fqpath - rcode, rout, rerr = g.run(host, command) + cmd = "getfattr --absolute-names -d -m - {}".format(fqpath) + if encode_hex: + cmd = ("getfattr --absolute-names -d -m - -e hex {}" + .format(fqpath)) + rcode, rout, rerr = g.run(host, cmd) if rcode == 0: xattr_list = {} @@ -220,7 +247,7 @@ def get_file_stat(host, fqpath): Returns: A dictionary of file stat data. None on fail. """ - statformat = '%F:%n:%i:%a:%s:%h:%u:%g:%U:%G' + statformat = '%F$%n$%i$%a$%s$%h$%u$%g$%U$%G$%x$%y$%z$%X$%Y$%Z' command = "stat -c '%s' %s" % (statformat, fqpath) rcode, rout, rerr = g.run(host, command) if rcode == 0: @@ -228,7 +255,9 @@ def get_file_stat(host, fqpath): stat_string = rout.strip() (filetype, filename, inode, access, size, links, - uid, gid, username, groupname) = stat_string.split(":") + uid, gid, username, groupname, + atime, mtime, ctime, epoch_atime, + epoch_mtime, epoch_ctime) = stat_string.split("$") stat_data['filetype'] = filetype stat_data['filename'] = filename @@ -240,6 +269,12 @@ def get_file_stat(host, fqpath): stat_data["groupname"] = groupname stat_data["uid"] = uid stat_data["gid"] = gid + stat_data["atime"] = atime + stat_data["mtime"] = mtime + stat_data["ctime"] = ctime + stat_data["epoch_atime"] = epoch_atime + stat_data["epoch_mtime"] = epoch_mtime + stat_data["epoch_ctime"] = epoch_ctime return stat_data @@ -365,7 +400,8 @@ def get_pathinfo(host, fqpath): A dictionary of pathinfo data for a remote file. None on fail. """ pathinfo = {} - pathinfo['raw'] = get_fattr(host, fqpath, 'trusted.glusterfs.pathinfo') + pathinfo['raw'] = get_fattr(host, fqpath, 'trusted.glusterfs.pathinfo', + encode="text") pathinfo['brickdir_paths'] = re.findall(r".*?POSIX.*?:(\S+)\>", pathinfo['raw']) @@ -388,17 +424,14 @@ def is_linkto_file(host, fqpath): """ command = 'file %s' % fqpath rcode, rout, _ = g.run(host, command) - if rcode == 0: - if 'sticky empty' in rout.strip(): + # An additional ',' is there for newer platforms + if 'sticky empty' or 'sticky, empty' in rout.strip(): stat = get_file_stat(host, fqpath) if int(stat['size']) == 0: - # xattr = get_fattr(host, fqpath, - # 'trusted.glusterfs.dht.linkto') xattr = get_dht_linkto_xattr(host, fqpath) if xattr is not None: return True - return False @@ -412,7 +445,8 @@ def get_dht_linkto_xattr(host, fqpath): Returns: Return value of get_fattr trusted.glusterfs.dht.linkto call. """ - linkto_xattr = get_fattr(host, fqpath, 'trusted.glusterfs.dht.linkto') + linkto_xattr = get_fattr(host, fqpath, 'trusted.glusterfs.dht.linkto', + encode="text") return linkto_xattr @@ -463,6 +497,154 @@ def check_if_pattern_in_file(host, pattern, fqpath): return 0 +def occurences_of_pattern_in_file(node, search_pattern, filename): + """ + Get the number of occurences of pattern in the file + + Args: + node (str): Host on which the command is executed. + search_pattern (str): Pattern to be found in the file. + filename (str): File in which the pattern is to be validated + + Returns: + (int): (-1), When the file doesn't exists. + (0), When pattern doesn't exists in the file. + (number), When pattern is found and the number of + occurences of pattern in the file. + + Example: + occurences_of_pattern_in_file(node, search_pattern, filename) + """ + + ret = file_exists(node, filename) + if not ret: + g.log.error("File %s is not present on the node " % filename) + return -1 + + cmd = ("grep -c '%s' %s" % (search_pattern, filename)) + ret, out, _ = g.run(node, cmd) + if ret: + g.log.error("No occurence of the pattern found in the file %s" % + filename) + return 0 + return int(out.strip('\n')) + + +def create_link_file(node, file, link, soft=False): + """ + Create hard or soft link for an exisiting file + + Args: + node(str): Host on which the command is executed. + file(str): Path to the source file. + link(str): Path to the link file. + + Kawrgs: + soft(bool): Create soft link if True else create + hard link. + + Returns: + (bool): True if command successful else False. + + Example: + >>> create_link_file('10.20.30.40', '/mnt/mp/file.txt', + '/mnt/mp/link') + True + """ + cmd = "ln {} {}".format(file, link) + if soft: + cmd = "ln -s {} {}".format(file, link) + + ret, _, err = g.run(node, cmd) + if ret: + if soft: + g.log.error('Failed to create soft link on {} ' + 'for file {} with error {}' + .format(node, file, err)) + else: + g.log.error('Failed to create hard link on {} ' + 'for file {} with error {}' + .format(node, file, err)) + return False + return True + + +def set_acl(client, rule, fqpath): + """Set acl rule on a specific file + + Args: + client(str): Host on which the command is executed. + rule(str): The acl rule to be set on the file. + fqpath (str): The fully-qualified path to the file. + + Returns: + (bool): True if command successful else False. + """ + cmd = "setfacl -m {} {}".format(rule, fqpath) + ret, _, _ = g.run(client, cmd) + if ret: + g.log.error('Failed to set rule {} on file {}'.format(rule, fqpath)) + return False + return True + + +def get_acl(client, path, filename): + """Get all acl rules set to a file + + Args: + client(str): Host on which the command is executed. + path (str): The fully-qualified path to the dir where file is present. + filename(str): Name of the file for which rules have to be fetched. + + Returns: + (dict): A dictionary with the formatted output of the command. + (None): In case of failures + + Example: + >>> get_acl('dhcp35-4.lab.eng.blr.redhat.com', '/root/', 'file') + {'owner': 'root', 'rules': ['user::rw-', 'user:root:rwx', 'group::r--', + 'mask::rwx', 'other::r--'], 'group': 'root', 'file': 'file'} + """ + cmd = "cd {};getfacl {}".format(path, filename) + ret, out, _ = g.run(client, cmd) + if ret: + return None + + # Generate a dict out of the output + output_dict = {} + data = out.strip().split('\n') + for key, index in (('file', 0), ('owner', 1), ('group', 2)): + output_dict[key] = data[index].split(' ')[2] + output_dict['rules'] = data[3:] + + return output_dict + + +def delete_acl(client, fqpath, rule=None): + """Delete a specific or all acl rules set on a file + + Args: + client(str): Host on which the command is executed. + fqpath (str): The fully-qualified path to the file. + + Kwargs: + rule(str): The acl rule to be removed from the file. + + Returns: + (bool): True if command successful else False. + """ + # Remove all acls set on a file + cmd = "setfacl -b {}".format(fqpath) + # Remove a specific acl of the file + if rule: + cmd = "setfacl -x {} {}".format(rule, fqpath) + + ret, _, _ = g.run(client, cmd) + if ret: + return False + return True + + class GlusterFile(object): """Class to handle files specific to Gluster (client and backend)""" def __init__(self, host, fqpath): diff --git a/glustolibs-gluster/glustolibs/gluster/heal_libs.py b/glustolibs-gluster/glustolibs/gluster/heal_libs.py index 2b0e9ed33..4a551cd48 100755 --- a/glustolibs-gluster/glustolibs/gluster/heal_libs.py +++ b/glustolibs-gluster/glustolibs/gluster/heal_libs.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2016-2021 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 @@ -135,7 +135,8 @@ def are_all_self_heal_daemons_are_online(mnode, volname): return False -def monitor_heal_completion(mnode, volname, timeout_period=1200): +def monitor_heal_completion(mnode, volname, timeout_period=1200, + bricks=None, interval_check=120): """Monitors heal completion by looking into .glusterfs/indices/xattrop directory of every brick for certain time. When there are no entries in all the brick directories then heal is successful. Otherwise heal is @@ -147,6 +148,12 @@ def monitor_heal_completion(mnode, volname, timeout_period=1200): heal_monitor_timeout : time until which the heal monitoring to be done. Default: 1200 i.e 20 minutes. + Kwargs: + bricks : list of bricks to monitor heal, if not provided + heal will be monitored on all bricks of volume + interval_check : Time in seconds, for every given interval checks + the heal info, defaults to 120. + Return: bool: True if heal is complete within timeout_period. False otherwise """ @@ -158,7 +165,7 @@ def monitor_heal_completion(mnode, volname, timeout_period=1200): # Get all bricks from glustolibs.gluster.brick_libs import get_all_bricks - bricks_list = get_all_bricks(mnode, volname) + bricks_list = bricks or get_all_bricks(mnode, volname) if bricks_list is None: g.log.error("Unable to get the bricks list. Hence unable to verify " "whether self-heal-daemon process is running or not " @@ -177,10 +184,15 @@ def monitor_heal_completion(mnode, volname, timeout_period=1200): if heal_complete: break else: - time.sleep(120) - time_counter = time_counter - 120 + time.sleep(interval_check) + time_counter = time_counter - interval_check + + if heal_complete and bricks: + # In EC volumes, check heal completion only on online bricks + # and `gluster volume heal info` fails for an offline brick + return True - if heal_complete: + if heal_complete and not bricks: heal_completion_status = is_heal_complete(mnode, volname) if heal_completion_status is True: g.log.info("Heal has successfully completed on volume %s" % @@ -395,30 +407,26 @@ def do_bricks_exist_in_shd_volfile(mnode, volname, brick_list): host = brick = None parse = False - # Establish connection to mnode - conn = g.rpyc_get_connection(mnode) - if conn is None: - g.log.info("Not able to establish connection to node %s" % mnode) - return False - try: - fd = conn.builtins.open(GLUSTERSHD) - for each_line in fd: - each_line = each_line.strip() - if volume_clients in each_line: - parse = True - elif "end-volume" in each_line: - if parse: - brick_list_server_vol.append("%s:%s" % (host, brick)) - parse = False - elif parse: - if "option remote-subvolume" in each_line: - brick = each_line.split(" ")[2] - if "option remote-host" in each_line: - host = each_line.split(" ")[2] - - except IOError as e: - g.log.info("I/O error ({0}): {1}".format(e.errno, e.strerror)) + cmd = "cat {0}".format(GLUSTERSHD) + ret, out, _ = g.run(mnode, cmd) + if ret: + g.log.error("Unable to cat the GLUSTERSHD file.") return False + fd = out.split('\n') + + for each_line in fd: + each_line = each_line.strip() + if volume_clients in each_line: + parse = True + elif "end-volume" in each_line: + if parse: + brick_list_server_vol.append("%s:%s" % (host, brick)) + parse = False + elif parse: + if "option remote-subvolume" in each_line: + brick = each_line.split(" ")[2] + if "option remote-host" in each_line: + host = each_line.split(" ")[2] g.log.info("Brick List from volume info : %s" % brick_list) g.log.info("Brick List from glustershd server volume " @@ -513,3 +521,71 @@ def bring_self_heal_daemon_process_offline(nodes): _rc = False return _rc + + +def is_shd_daemon_running(mnode, node, volname): + """ + Verifies whether the shd daemon is up and running on a particular node by + checking the existence of shd pid and parsing the get volume status output. + + Args: + mnode (str): The first node in servers list + node (str): The node to be checked for whether the glustershd + process is up or not + volname (str): Name of the volume created + + Returns: + boolean: True if shd is running on the node, False, otherwise + """ + + # Get glustershd pid from node. + ret, glustershd_pids = get_self_heal_daemon_pid(node) + if not ret and glustershd_pids[node] != -1: + return False + # Verifying glustershd process is no longer running from get status. + vol_status = get_volume_status(mnode, volname) + if vol_status is None: + return False + try: + _ = vol_status[volname][node]['Self-heal Daemon'] + return True + except KeyError: + return False + + +def enable_granular_heal(mnode, volname): + """Enable granular heal on a given volume + + Args: + mnode(str): Node on which command has to be exectued + volname(str): Name of the volume on which granular heal is to be enabled + + Returns: + bool: True if granular heal is enabled successfully else False + """ + cmd = "gluster volume heal {} granular-entry-heal enable".format(volname) + ret, _, _ = g.run(mnode, cmd) + if ret: + g.log.error('Unable to enable granular-entry-heal on volume %s', + volname) + return False + return True + + +def disable_granular_heal(mnode, volname): + """Diable granular heal on a given volume + + Args: + mnode(str): Node on which command will be exectued + volname(str): Name of the volume on which granular heal is to be disabled + + Returns: + bool: True if granular heal is disabled successfully else False + """ + cmd = "gluster volume heal {} granular-entry-heal disable".format(volname) + ret, _, _ = g.run(mnode, cmd) + if ret: + g.log.error('Unable to disable granular-entry-heal on volume %s', + volname) + return False + return True diff --git a/glustolibs-gluster/glustolibs/gluster/layout.py b/glustolibs-gluster/glustolibs/gluster/layout.py index c1ddb40f8..ea5a5bc8b 100644 --- a/glustolibs-gluster/glustolibs/gluster/layout.py +++ b/glustolibs-gluster/glustolibs/gluster/layout.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2018 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2018-2020 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 @@ -28,11 +28,25 @@ class Layout(object): """ def _get_layout(self): """Discover brickdir data and cache in instance for further use""" + # Adding here to avoid cyclic imports + from glustolibs.gluster.volume_libs import get_volume_type + self._brickdirs = [] for brickdir_path in self._pathinfo['brickdir_paths']: - brickdir = BrickDir(brickdir_path) - g.log.debug("%s: %s" % (brickdir.path, brickdir.hashrange)) - self._brickdirs.append(brickdir) + (host, _) = brickdir_path.split(':') + ret = get_volume_type(brickdir_path) + if ret in ('Replicate', 'Disperse', 'Arbiter'): + g.log.info("Cannot get layout as volume under test is" + " Replicate/Disperse/Arbiter and DHT" + " pass-through was enabled after Gluster 6.0") + else: + brickdir = BrickDir(brickdir_path) + if brickdir is None: + g.log.error("Failed to get the layout") + else: + g.log.debug("%s: %s" % (brickdir.path, + brickdir.hashrange)) + self._brickdirs.append(brickdir) def __init__(self, pathinfo): """Init the layout class @@ -59,48 +73,60 @@ class Layout(object): ends at 32-bits high, and has no holes or overlaps """ - joined_hashranges = [] - for brickdir in self.brickdirs: - # join all of the hashranges into a single list - joined_hashranges += brickdir.hashrange - g.log.debug("joined range list: %s" % joined_hashranges) - # remove duplicate hashes - collapsed_ranges = list(set(joined_hashranges)) - # sort the range list for good measure - collapsed_ranges.sort() - - # first hash in the list is 0? - if collapsed_ranges[0] != 0: - g.log.error('First hash in range (%d) is not zero' % - collapsed_ranges[0]) - return False - - # last hash in the list is 32-bits high? - if collapsed_ranges[-1] != int(0xffffffff): - g.log.error('Last hash in ranges (%s) is not 0xffffffff' % - hex(collapsed_ranges[-1])) - return False - - # remove the first and last hashes - clipped_ranges = collapsed_ranges[1:-1] - g.log.debug('clipped: %s' % clipped_ranges) - - # walk through the list in pairs and look for diff == 1 - iter_ranges = iter(clipped_ranges) - for first in iter_ranges: - second = next(iter_ranges) - hash_difference = second - first - g.log.debug('%d - %d = %d' % (second, first, hash_difference)) - if hash_difference > 1: - g.log.error("Layout has holes") - - return False - elif hash_difference < 1: - g.log.error("Layout has overlaps") - - return False + # Adding here to avoid cyclic imports + from glustolibs.gluster.volume_libs import get_volume_type - return True + for brickdir_path in self._pathinfo['brickdir_paths']: + (host, _) = brickdir_path.split(':') + if get_volume_type(brickdir_path) in ('Replicate', 'Disperse', + 'Arbiter'): + g.log.info("Cannot check for layout completeness as volume" + " under test is Replicate/Disperse/Arbiter and DHT" + " pass-though was enabled after Gluster 6.") + else: + joined_hashranges = [] + for brickdir in self.brickdirs: + # join all of the hashranges into a single list + joined_hashranges += brickdir.hashrange + g.log.debug("joined range list: %s" % joined_hashranges) + # remove duplicate hashes + collapsed_ranges = list(set(joined_hashranges)) + # sort the range list for good measure + collapsed_ranges.sort() + + # first hash in the list is 0? + if collapsed_ranges[0] != 0: + g.log.error('First hash in range (%d) is not zero' % + collapsed_ranges[0]) + return False + + # last hash in the list is 32-bits high? + if collapsed_ranges[-1] != int(0xffffffff): + g.log.error('Last hash in ranges (%s) is not 0xffffffff' % + hex(collapsed_ranges[-1])) + return False + + # remove the first and last hashes + clipped_ranges = collapsed_ranges[1:-1] + g.log.debug('clipped: %s' % clipped_ranges) + + # walk through the list in pairs and look for diff == 1 + iter_ranges = iter(clipped_ranges) + for first in iter_ranges: + second = next(iter_ranges) + hash_difference = second - first + g.log.debug('%d - %d = %d' % (second, first, + hash_difference)) + if hash_difference > 1: + g.log.error("Layout has holes") + + return False + elif hash_difference < 1: + g.log.error("Layout has overlaps") + + return False + + return True @property def has_zero_hashranges(self): diff --git a/glustolibs-gluster/glustolibs/gluster/lib_utils.py b/glustolibs-gluster/glustolibs/gluster/lib_utils.py index e1a9182aa..b04976b1c 100755 --- a/glustolibs-gluster/glustolibs/gluster/lib_utils.py +++ b/glustolibs-gluster/glustolibs/gluster/lib_utils.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2021 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 @@ -26,8 +26,6 @@ import re import time from collections import OrderedDict import tempfile -import subprocess -import random ONE_GB_BYTES = 1073741824.0 @@ -53,23 +51,16 @@ def append_string_to_file(mnode, filename, str_to_add_in_file, Returns: True, on success, False otherwise """ - try: - conn = g.rpyc_get_connection(mnode, user=user) - if conn is None: - g.log.error("Unable to get connection to 'root' of node %s" - " in append_string_to_file()" % mnode) - return False - - with conn.builtin.open(filename, 'a') as _filehandle: - _filehandle.write(str_to_add_in_file) - - return True - except IOError: - g.log.error("Exception occurred while adding string to " - "file %s in append_string_to_file()", filename) + cmd = "echo '{0}' >> {1}".format(str_to_add_in_file, + filename) + ret, out, err = g.run(mnode, cmd, user) + if ret or out or err: + g.log.error("Unable to append string '{0}' to file " + "'{1}' on node {2} using user {3}" + .format(str_to_add_in_file, filename, + mnode, user)) return False - finally: - g.rpyc_close_connection(host=mnode, user=user) + return True def search_pattern_in_file(mnode, search_pattern, filename, start_str_to_parse, @@ -268,31 +259,19 @@ def list_files(mnode, dir_path, parse_str="", user="root"): NoneType: None if command execution fails, parse errors. list: files with absolute name """ - - try: - conn = g.rpyc_get_connection(mnode, user=user) - if conn is None: - g.log.error("Unable to get connection to 'root' of node %s" - % mnode) - return None - - filepaths = [] - for root, directories, files in conn.modules.os.walk(dir_path): - for filename in files: - if parse_str != "": - if parse_str in filename: - filepath = conn.modules.os.path.join(root, filename) - filepaths.append(filepath) - else: - filepath = conn.modules.os.path.join(root, filename) - filepaths.append(filepath) - return filepaths - except StopIteration: - g.log.error("Exception occurred in list_files()") + if parse_str == "": + cmd = "find {0} -type f".format(dir_path) + else: + cmd = "find {0} -type f | grep {1}".format(dir_path, + parse_str) + ret, out, err = g.run(mnode, cmd, user) + if ret or err: + g.log.error("Unable to get the list of files on path " + "{0} on node {1} using user {2} due to error {3}" + .format(dir_path, mnode, user, err)) return None - - finally: - g.rpyc_close_connection(host=mnode, user=user) + file_list = out.split('\n') + return file_list[0:len(file_list)-1] def get_servers_bricks_dict(servers, servers_info): @@ -408,7 +387,8 @@ def get_servers_unused_bricks_dict(mnode, servers, servers_info): return servers_unused_bricks_dict -def form_bricks_list(mnode, volname, number_of_bricks, servers, servers_info): +def form_bricks_list(mnode, volname, number_of_bricks, servers, servers_info, + dirname=None): """Forms bricks list for create-volume/add-brick given the num_of_bricks servers and servers_info. @@ -421,6 +401,9 @@ def form_bricks_list(mnode, volname, number_of_bricks, servers, servers_info): needs to be selected for creating the brick list. servers_info (dict): dict of server info of each servers. + kwargs: + dirname (str): Name of the directory for glusterfs brick + Returns: list - List of bricks to use with volume-create/add-brick None - if number_of_bricks is greater than unused bricks. @@ -458,10 +441,18 @@ def form_bricks_list(mnode, volname, number_of_bricks, servers, servers_info): list(servers_unused_bricks_dict.values())[dict_index]) brick_path = '' if current_server_unused_bricks_list: - brick_path = ("%s:%s/%s_brick%s" % - (current_server, - current_server_unused_bricks_list[0], volname, num)) - bricks_list.append(brick_path) + if dirname and (" " not in dirname): + brick_path = ("%s:%s/%s_brick%s" % + (current_server, + current_server_unused_bricks_list[0], dirname, + num)) + bricks_list.append(brick_path) + else: + brick_path = ("%s:%s/%s_brick%s" % + (current_server, + current_server_unused_bricks_list[0], volname, + num)) + bricks_list.append(brick_path) # Remove the added brick from the current_server_unused_bricks_list list(servers_unused_bricks_dict.values())[dict_index].pop(0) @@ -544,22 +535,13 @@ def get_disk_usage(mnode, path, user="root"): Example: get_disk_usage("abc.com", "/mnt/glusterfs") """ - - inst = random.randint(10, 100) - conn = g.rpyc_get_connection(mnode, user=user, instance=inst) - if conn is None: - g.log.error("Failed to get rpyc connection") - return None - cmd = 'stat -f ' + path - p = conn.modules.subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = p.communicate() - ret = p.returncode - if ret != 0: - g.log.error("Failed to execute stat command") + cmd = 'stat -f {0}'.format(path) + ret, out, err = g.run(mnode, cmd, user) + if ret: + g.log.error("Unable to get stat of path {0} on node {1} " + "using user {2} due to error {3}".format(path, mnode, + user, err)) return None - - g.rpyc_close_connection(host=mnode, user=user, instance=inst) res = ''.join(out) match = re.match(r'.*Block size:\s(\d+).*Blocks:\sTotal:\s(\d+)\s+?' r'Free:\s(\d+)\s+?Available:\s(\d+).*Inodes:\s' @@ -1034,6 +1016,30 @@ def group_add(servers, groupname): return True +def group_del(servers, groupname): + """ + Deletes a group in all the servers. + + Args: + servers(list|str): Nodes on which cmd is to be executed. + groupname(str): Name of the group to be removed. + + Return always True + """ + if not isinstance(servers, list): + servers = [servers] + + cmd = "groupdel %s" % groupname + results = g.run_parallel(servers, cmd) + + for server, ret_value in list(results.items()): + retcode, _, err = ret_value + if retcode != 0 and "does not exist" in err: + g.log.error("Group %s on server %s already removed", + groupname, server) + return True + + def ssh_keygen(mnode): """ Creates a pair of ssh private and public key if not present @@ -1183,3 +1189,71 @@ def is_passwordless_ssh_configured(fromnode, tonode, username): (fromnode, tonode, username)) return False return True + + +def collect_bricks_arequal(bricks_list): + """Collects arequal for all bricks in list + + Args: + bricks_list (list): List of bricks. + Example: + bricks_list = 'gluster.blr.cluster.com:/bricks/brick1/vol' + + Returns: + tuple(bool, list): + On success returns (True, list of arequal-checksums of each brick) + On failure returns (False, list of arequal-checksums of each brick) + arequal-checksum for a brick would be 'None' when failed to + collect arequal for that brick. + + Example: + >>> all_bricks = get_all_bricks(self.mnode, self.volname) + >>> ret, arequal = collect_bricks_arequal(all_bricks) + >>> ret + True + """ + # Converting a bricks_list to list if not. + if not isinstance(bricks_list, list): + bricks_list = [bricks_list] + + return_code, arequal_list = True, [] + for brick in bricks_list: + + # Running arequal-checksum on the brick. + node, brick_path = brick.split(':') + cmd = ('arequal-checksum -p {} -i .glusterfs -i .landfill -i .trashcan' + .format(brick_path)) + ret, arequal, _ = g.run(node, cmd) + + # Generating list accordingly + if ret: + g.log.error('Failed to get arequal on brick %s', brick) + return_code = False + arequal_list.append(None) + else: + g.log.info('Successfully calculated arequal for brick %s', brick) + arequal_list.append(arequal) + + return (return_code, arequal_list) + + +def get_usable_size_per_disk(brickpath, min_free_limit=10): + """Get the usable size per disk + + Args: + brickpath(str): Brick path to be used to calculate usable size + + Kwargs: + min_free_limit(int): Min free disk limit to be used + + Returns: + (int): Usable size in GB. None in case of errors. + """ + node, brick_path = brickpath.split(':') + size = get_size_of_mountpoint(node, brick_path) + if not size: + return None + size = int(size) + min_free_size = size * min_free_limit // 100 + usable_size = ((size - min_free_size) // 1048576) + 1 + return usable_size diff --git a/glustolibs-gluster/glustolibs/gluster/mount_ops.py b/glustolibs-gluster/glustolibs/gluster/mount_ops.py index c41ef8528..c8fbddd05 100755 --- a/glustolibs-gluster/glustolibs/gluster/mount_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/mount_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -336,10 +336,10 @@ def mount_volume(volname, mtype, mpoint, mserver, mclient, options='', if mtype == 'nfs': if not options: - options = "-o vers=3" + options = "-o vers=4.1" elif options and 'vers' not in options: - options = options + ",vers=3" + options = options + ",vers=4.1" if mserver: mcmd = ("mount -t %s %s %s:/%s %s" % @@ -356,23 +356,10 @@ def mount_volume(volname, mtype, mpoint, mserver, mclient, options='', # Check if client is running rhel. If so add specific options cifs_options = "" - try: - conn = g.rpyc_get_connection(mclient, user=user) - if conn is None: - g.log.error("Unable to get connection to %s on node %s" - " in mount_volume()", user, mclient) - return (1, '', '') - - os, version, name = conn.modules.platform.linux_distribution() - if "Santiago" in name: - cifs_options = "sec=ntlmssp" - except Exception as e: - g.log.error("Exception occurred while getting the platform " - "of node %s: %s", mclient, str(e)) - return (1, '', '') - finally: - g.rpyc_close_connection(host=mclient, user=user) - + cmd = "cat /etc/redhat-release | grep Santiago" + ret, _, _ = g.run(mclient, cmd, user=user) + if not ret: + cifs_options = "sec=ntlmssp" mcmd = ("mount -t cifs -o username=%s,password=%s,%s " "\\\\\\\\%s\\\\gluster-%s %s" % (smbuser, smbpasswd, cifs_options, mserver, diff --git a/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_libs.py b/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_libs.py index 19e98408e..5f69e68f6 100644..100755 --- a/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_libs.py +++ b/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_libs.py @@ -32,179 +32,123 @@ from glustolibs.gluster.nfs_ganesha_ops import ( create_nfs_ganesha_cluster, configure_ports_on_clients, ganesha_client_firewall_settings) -from glustolibs.gluster.gluster_base_class import GlusterBaseClass -from glustolibs.gluster.exceptions import ExecutionError, ConfigError from glustolibs.gluster.volume_libs import is_volume_exported +from glustolibs.gluster.lib_utils import is_rhel7 -class NfsGaneshaClusterSetupClass(GlusterBaseClass): - """Creates nfs ganesha cluster +def setup_nfs_ganesha(cls): """ - @classmethod - def setUpClass(cls): - """ - Setup variable for nfs-ganesha tests. - """ - # pylint: disable=too-many-statements, too-many-branches - super(NfsGaneshaClusterSetupClass, cls).setUpClass() - - # Check if enable_nfs_ganesha is set in config file - if not cls.enable_nfs_ganesha: - raise ConfigError("Please enable nfs ganesha in config") - - # Read num_of_nfs_ganesha_nodes from config file and create - # nfs ganesha cluster accordingly - cls.num_of_nfs_ganesha_nodes = int(cls.num_of_nfs_ganesha_nodes) - cls.servers_in_nfs_ganesha_cluster = ( - cls.servers[:cls.num_of_nfs_ganesha_nodes]) - cls.vips_in_nfs_ganesha_cluster = ( - cls.vips[:cls.num_of_nfs_ganesha_nodes]) - - # Obtain hostname of servers in ganesha cluster - cls.ganesha_servers_hostname = [] - for ganesha_server in cls.servers_in_nfs_ganesha_cluster: - ret, hostname, _ = g.run(ganesha_server, "hostname") - if ret: - raise ExecutionError("Failed to obtain hostname of %s" - % ganesha_server) - hostname = hostname.strip() - g.log.info("Obtained hostname: IP- %s, hostname- %s", - ganesha_server, hostname) - cls.ganesha_servers_hostname.append(hostname) - - @classmethod - def setup_nfs_ganesha(cls): - """ - Create nfs-ganesha cluster if not exists - Set client configurations for nfs-ganesha - - Returns: - True(bool): If setup is successful - False(bool): If setup is failure - """ - # pylint: disable = too-many-statements, too-many-branches - # pylint: disable = too-many-return-statements - cluster_exists = is_nfs_ganesha_cluster_exists( + Create nfs-ganesha cluster if not exists + Set client configurations for nfs-ganesha + + Returns: + True(bool): If setup is successful + False(bool): If setup is failure + """ + # pylint: disable = too-many-statements, too-many-branches + # pylint: disable = too-many-return-statements + cluster_exists = is_nfs_ganesha_cluster_exists( + cls.servers_in_nfs_ganesha_cluster[0]) + if cluster_exists: + is_healthy = is_nfs_ganesha_cluster_in_healthy_state( cls.servers_in_nfs_ganesha_cluster[0]) - if cluster_exists: - is_healthy = is_nfs_ganesha_cluster_in_healthy_state( - cls.servers_in_nfs_ganesha_cluster[0]) - - if is_healthy: - g.log.info("Nfs-ganesha Cluster exists and is in healthy " - "state. Skipping cluster creation...") - else: - g.log.info("Nfs-ganesha Cluster exists and is not in " - "healthy state.") - g.log.info("Tearing down existing cluster which is not in " - "healthy state") - ganesha_ha_file = ("/var/run/gluster/shared_storage/" - "nfs-ganesha/ganesha-ha.conf") - - g.log.info("Collecting server details of existing " - "nfs ganesha cluster") - conn = g.rpyc_get_connection( - cls.servers_in_nfs_ganesha_cluster[0], user="root") - if not conn: - tmp_node = cls.servers_in_nfs_ganesha_cluster[0] - g.log.error("Unable to get connection to 'root' of node" - " %s", tmp_node) - return False - - if not conn.modules.os.path.exists(ganesha_ha_file): - g.log.error("Unable to locate %s", ganesha_ha_file) - return False - with conn.builtin.open(ganesha_ha_file, "r") as fhand: - ganesha_ha_contents = fhand.read() - g.rpyc_close_connection( - host=cls.servers_in_nfs_ganesha_cluster[0], user="root") - servers_in_existing_cluster = re.findall(r'VIP_(.*)\=.*', - ganesha_ha_contents) - - ret = teardown_nfs_ganesha_cluster( - servers_in_existing_cluster, force=True) - if not ret: - g.log.error("Failed to teardown unhealthy ganesha " - "cluster") - return False - - g.log.info("Existing unhealthy cluster got teardown " - "successfully") - - if (not cluster_exists) or (not is_healthy): - g.log.info("Creating nfs-ganesha cluster of %s nodes" - % str(cls.num_of_nfs_ganesha_nodes)) - g.log.info("Nfs-ganesha cluster node info: %s" - % cls.servers_in_nfs_ganesha_cluster) - g.log.info("Nfs-ganesha cluster vip info: %s" - % cls.vips_in_nfs_ganesha_cluster) - - ret = create_nfs_ganesha_cluster( - cls.ganesha_servers_hostname, - cls.vips_in_nfs_ganesha_cluster) + + if is_healthy: + g.log.info("Nfs-ganesha Cluster exists and is in healthy " + "state. Skipping cluster creation...") + else: + g.log.info("Nfs-ganesha Cluster exists and is not in " + "healthy state.") + g.log.info("Tearing down existing cluster which is not in " + "healthy state") + ganesha_ha_file = ("/var/run/gluster/shared_storage/" + "nfs-ganesha/ganesha-ha.conf") + g_node = cls.servers_in_nfs_ganesha_cluster[0] + + g.log.info("Collecting server details of existing " + "nfs ganesha cluster") + + # Check whether ganesha ha file exists + cmd = "[ -f {} ]".format(ganesha_ha_file) + ret, _, _ = g.run(g_node, cmd) + if ret: + g.log.error("Unable to locate %s", ganesha_ha_file) + return False + + # Read contents of ganesha_ha_file + cmd = "cat {}".format(ganesha_ha_file) + ret, ganesha_ha_contents, _ = g.run(g_node, cmd) + if ret: + g.log.error("Failed to read %s", ganesha_ha_file) + return False + + servers_in_existing_cluster = re.findall(r'VIP_(.*)\=.*', + ganesha_ha_contents) + + ret = teardown_nfs_ganesha_cluster( + servers_in_existing_cluster, force=True) if not ret: - g.log.error("Creation of nfs-ganesha cluster failed") + g.log.error("Failed to teardown unhealthy ganesha " + "cluster") return False - if not is_nfs_ganesha_cluster_in_healthy_state( - cls.servers_in_nfs_ganesha_cluster[0]): - g.log.error("Nfs-ganesha cluster is not healthy") - return False - g.log.info("Nfs-ganesha Cluster exists is in healthy state") + g.log.info("Existing unhealthy cluster got teardown " + "successfully") - ret = configure_ports_on_clients(cls.clients) + if (not cluster_exists) or (not is_healthy): + g.log.info("Creating nfs-ganesha cluster of %s nodes" + % str(cls.num_of_nfs_ganesha_nodes)) + g.log.info("Nfs-ganesha cluster node info: %s" + % cls.servers_in_nfs_ganesha_cluster) + g.log.info("Nfs-ganesha cluster vip info: %s" + % cls.vips_in_nfs_ganesha_cluster) + + ret = create_nfs_ganesha_cluster( + cls.ganesha_servers_hostname, + cls.vips_in_nfs_ganesha_cluster) if not ret: - g.log.error("Failed to configure ports on clients") + g.log.error("Creation of nfs-ganesha cluster failed") return False - ret = ganesha_client_firewall_settings(cls.clients) + if not is_nfs_ganesha_cluster_in_healthy_state( + cls.servers_in_nfs_ganesha_cluster[0]): + g.log.error("Nfs-ganesha cluster is not healthy") + return False + g.log.info("Nfs-ganesha Cluster exists is in healthy state") + + if is_rhel7(cls.clients): + ret = configure_ports_on_clients(cls.clients) if not ret: - g.log.error("Failed to do firewall setting in clients") + g.log.error("Failed to configure ports on clients") return False - for server in cls.servers: - for client in cls.clients: - cmd = ("if [ -z \"$(grep -R \"%s\" /etc/hosts)\" ]; then " - "echo \"%s %s\" >> /etc/hosts; fi" - % (client, socket.gethostbyname(client), client)) - ret, _, _ = g.run(server, cmd) - if ret != 0: - g.log.error("Failed to add entry of client %s in " - "/etc/hosts of server %s" - % (client, server)) + ret = ganesha_client_firewall_settings(cls.clients) + if not ret: + g.log.error("Failed to do firewall setting in clients") + return False + for server in cls.servers: for client in cls.clients: - for server in cls.servers: - cmd = ("if [ -z \"$(grep -R \"%s\" /etc/hosts)\" ]; then " - "echo \"%s %s\" >> /etc/hosts; fi" - % (server, socket.gethostbyname(server), server)) - ret, _, _ = g.run(client, cmd) - if ret != 0: - g.log.error("Failed to add entry of server %s in " - "/etc/hosts of client %s" - % (server, client)) - return True - - @classmethod - def tearDownClass(cls, delete_nfs_ganesha_cluster=True): - """Teardown nfs ganesha cluster. - """ - super(NfsGaneshaClusterSetupClass, cls).tearDownClass() - - if delete_nfs_ganesha_cluster: - ret = teardown_nfs_ganesha_cluster( - cls.servers_in_nfs_ganesha_cluster) - if not ret: - g.log.error("Teardown got failed. Hence, cleaning up " - "nfs-ganesha cluster forcefully") - ret = teardown_nfs_ganesha_cluster( - cls.servers_in_nfs_ganesha_cluster, force=True) - if not ret: - raise ExecutionError("Force cleanup of nfs-ganesha " - "cluster failed") - g.log.info("Teardown nfs ganesha cluster succeeded") - else: - g.log.info("Skipping teardown nfs-ganesha cluster...") + cmd = ("if [ -z \"$(grep -R \"%s\" /etc/hosts)\" ]; then " + "echo \"%s %s\" >> /etc/hosts; fi" + % (client, socket.gethostbyname(client), client)) + ret, _, _ = g.run(server, cmd) + if ret != 0: + g.log.error("Failed to add entry of client %s in " + "/etc/hosts of server %s" + % (client, server)) + + for client in cls.clients: + for server in cls.servers: + cmd = ("if [ -z \"$(grep -R \"%s\" /etc/hosts)\" ]; then " + "echo \"%s %s\" >> /etc/hosts; fi" + % (server, socket.gethostbyname(server), server)) + ret, _, _ = g.run(client, cmd) + if ret != 0: + g.log.error("Failed to add entry of server %s in " + "/etc/hosts of client %s" + % (server, client)) + return True def wait_for_nfs_ganesha_volume_to_get_exported(mnode, volname, timeout=120): diff --git a/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_ops.py b/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_ops.py index 3e53ec29d..d8486c7d2 100644..100755 --- a/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/nfs_ganesha_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2016-2017 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2016-2021 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 @@ -23,10 +23,10 @@ """ import os -import random from glusto.core import Glusto as g from glustolibs.gluster.glusterdir import mkdir -from glustolibs.gluster.lib_utils import add_services_to_firewall +from glustolibs.gluster.lib_utils import (add_services_to_firewall, + is_rhel7) from glustolibs.gluster.shared_storage_ops import enable_shared_storage from glustolibs.gluster.peer_ops import peer_probe_servers @@ -50,17 +50,33 @@ def teardown_nfs_ganesha_cluster(servers, force=False): Example: teardown_nfs_ganesha_cluster(servers) """ + # Copy ganesha.conf before proceeding to clean up + for server in servers: + cmd = "cp /etc/ganesha/ganesha.conf ganesha.conf" + ret, _, _ = g.run(server, cmd) + if ret: + g.log.error("Failed to copy ganesha.conf") + if force: g.log.info("Executing force cleanup...") + cleanup_ops = ['--teardown', '--cleanup'] for server in servers: - cmd = ("/usr/libexec/ganesha/ganesha-ha.sh --teardown " - "/var/run/gluster/shared_storage/nfs-ganesha") - _, _, _ = g.run(server, cmd) - cmd = ("/usr/libexec/ganesha/ganesha-ha.sh --cleanup /var/run/" - "gluster/shared_storage/nfs-ganesha") - _, _, _ = g.run(server, cmd) + # Perform teardown and cleanup + for op in cleanup_ops: + cmd = ("/usr/libexec/ganesha/ganesha-ha.sh {} /var/run/" + "gluster/shared_storage/nfs-ganesha".format(op)) + _, _, _ = g.run(server, cmd) + + # Stop nfs ganesha service _, _, _ = stop_nfs_ganesha_service(server) + + # Clean shared storage, ganesha.conf, and replace with backup + for cmd in ("rm -rf /var/run/gluster/shared_storage/*", + "rm -rf /etc/ganesha/ganesha.conf", + "cp ganesha.conf /etc/ganesha/ganesha.conf"): + _, _, _ = g.run(server, cmd) return True + ret, _, _ = disable_nfs_ganesha(servers[0]) if ret != 0: g.log.error("Nfs-ganesha disable failed") @@ -667,14 +683,17 @@ def create_nfs_ganesha_cluster(servers, vips): False(bool): If failed to configure ganesha cluster """ # pylint: disable=too-many-return-statements + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements ganesha_mnode = servers[0] - # Configure ports in ganesha servers - g.log.info("Defining statd service ports") - ret = configure_ports_on_servers(servers) - if not ret: - g.log.error("Failed to set statd service ports on nodes.") - return False + # Configure ports in ganesha servers for RHEL7 + if is_rhel7(servers): + g.log.info("Defining statd service ports") + ret = configure_ports_on_servers(servers) + if not ret: + g.log.error("Failed to set statd service ports on nodes.") + return False # Firewall settings for nfs-ganesha ret = ganesha_server_firewall_settings(servers) @@ -752,6 +771,22 @@ def create_nfs_ganesha_cluster(servers, vips): # Create backup of ganesha-ha.conf file in ganesha_mnode g.upload(ganesha_mnode, tmp_ha_conf, '/etc/ganesha/') + # setsebool ganesha_use_fusefs on + cmd = "setsebool ganesha_use_fusefs on" + for server in servers: + ret, _, _ = g.run(server, cmd) + if ret: + g.log.error("Failed to 'setsebool ganesha_use_fusefs on' on %", + server) + return False + + # Verify ganesha_use_fusefs is on + _, out, _ = g.run(server, "getsebool ganesha_use_fusefs") + if "ganesha_use_fusefs --> on" not in out: + g.log.error("Failed to 'setsebool ganesha_use_fusefs on' on %", + server) + return False + # Enabling ganesha g.log.info("Enable nfs-ganesha") ret, _, _ = enable_nfs_ganesha(ganesha_mnode) @@ -765,6 +800,31 @@ def create_nfs_ganesha_cluster(servers, vips): # pcs status output _, _, _ = g.run(ganesha_mnode, "pcs status") + # pacemaker status output + _, _, _ = g.run(ganesha_mnode, "systemctl status pacemaker") + + return True + + +def enable_firewall(servers): + """Enables Firewall if not enabled already + Args: + servers(list): Hostname of ganesha nodes + Returns: + Status (bool) : True/False based on the status of firewall enable + """ + + cmd = "systemctl status firewalld | grep Active" + for server in servers: + ret, out, _ = g.run(server, cmd) + if 'inactive' in out: + g.log.info("Firewalld is not running. Enabling Firewalld") + for command in ("enable", "start"): + ret, out, _ = g.run(server, + "systemctl {} firewalld".format(command)) + if ret: + g.log.error("Failed to enable Firewalld on %s", server) + return False return True @@ -778,9 +838,11 @@ def ganesha_server_firewall_settings(servers): True(bool): If successfully set the firewall settings False(bool): If failed to do firewall settings """ + if not enable_firewall(servers): + return False + services = ['nfs', 'rpc-bind', 'high-availability', 'nlm', 'mountd', 'rquota'] - ret = add_services_to_firewall(servers, services, True) if not ret: g.log.error("Failed to set firewall zone permanently on ganesha nodes") @@ -852,47 +914,51 @@ def create_nfs_passwordless_ssh(mnode, gnodes, guser='root'): False(bool): On failure """ loc = "/var/lib/glusterd/nfs/" - mconn_inst = random.randint(20, 100) - mconn = g.rpyc_get_connection(host=mnode, instance=mconn_inst) - if not mconn.modules.os.path.isfile('/root/.ssh/id_rsa'): + # Check whether key is present + cmd = "[ -f /root/.ssh/id_rsa ]" + ret, _, _ = g.run(mnode, cmd) + if ret: # Generate key on mnode if not already present - if not mconn.modules.os.path.isfile('%s/secret.pem' % loc): + g.log.info("id_rsa not found") + cmd = "[ -f %s/secret.pem ]" % loc + ret, _, _ = g.run(mnode, cmd) + if ret: + g.log.info("Secret.pem file not found. Creating new") ret, _, _ = g.run( mnode, "ssh-keygen -f %s/secret.pem -q -N ''" % loc) - if ret != 0: + if ret: g.log.error("Failed to generate the secret pem file") return False g.log.info("Key generated on %s" % mnode) else: - mconn.modules.shutil.copyfile("/root/.ssh/id_rsa", - "%s/secret.pem" % loc) - g.log.info("Copying the id_rsa.pub to secret.pem.pub") - mconn.modules.shutil.copyfile("/root/.ssh/id_rsa.pub", - "%s/secret.pem.pub" % loc) + g.log.info("Found existing key") + # Copy the .pem and .pyb files + for file, to_file in (('id_rsa', 'secret.pem'), ('id_rsa.pub', + 'secret.pem.pub')): + cmd = "cp /root/.ssh/{} {}{}".format(file, loc, to_file) + ret, _, err = g.run(mnode, cmd) + if ret: + g.log.error("Failed to copy {} to {} file {}".format(file, + to_file, + err)) + return False # Create password less ssh from mnode to all ganesha nodes + cmd = "cat /root/.ssh/id_rsa.pub" + ret, id_rsa, _ = g.run(mnode, cmd, user=guser) + if ret: + g.log.info("Failed to read key from %s", mnode) + return False for gnode in gnodes: - gconn_inst = random.randint(20, 100) - gconn = g.rpyc_get_connection(gnode, user=guser, instance=gconn_inst) - try: - glocal = gconn.modules.os.path.expanduser('~') - gfhand = gconn.builtin.open("%s/.ssh/authorized_keys" % glocal, - "a") - with mconn.builtin.open("/root/.ssh/id_rsa.pub", 'r') as fhand: - for line in fhand: - gfhand.write(line) - gfhand.close() - except Exception as exep: - g.log.error("Exception occurred while trying to establish " - "password less ssh from %s@%s to %s@%s. Exception: %s" - % ('root', mnode, guser, gnode, exep)) + file = "~/.ssh/authorized_keys" + cmd = ("grep -q '{}' {} || echo '{}' >> {}" + .format(id_rsa.rstrip(), file, id_rsa.rstrip(), file)) + ret, _, _ = g.run(gnode, cmd, user=guser) + if ret: + g.log.info("Failed to add ssh key for %s", gnode) return False - finally: - g.rpyc_close_connection( - host=gnode, user=guser, instance=gconn_inst) - - g.rpyc_close_connection(host=mnode, instance=mconn_inst) + g.log.info("Successfully copied ssh key to all Ganesha nodes") # Copy the ssh key pair from mnode to all the nodes in the Ganesha-HA # cluster @@ -906,8 +972,8 @@ def create_nfs_passwordless_ssh(mnode, gnodes, guser='root'): % (loc, loc, guser, gnode, loc)) ret, _, _ = g.run(mnode, cmd) if ret != 0: - g.log.error("Failed to copy the ssh key pair from %s to %s", - mnode, gnode) + g.log.error("Failed to copy the ssh key pair from " + "%s to %s", mnode, gnode) return False return True @@ -923,7 +989,7 @@ def create_ganesha_ha_conf(hostnames, vips, temp_ha_file): """ hosts = ','.join(hostnames) - with open(temp_ha_file, 'wb') as fhand: + with open(temp_ha_file, 'w') as fhand: fhand.write('HA_NAME="ganesha-ha-360"\n') fhand.write('HA_CLUSTER_NODES="%s"\n' % hosts) for (hostname, vip) in zip(hostnames, vips): @@ -940,7 +1006,6 @@ def cluster_auth_setup(servers): True(bool): If configuration of cluster services is success False(bool): If failed to configure cluster services """ - result = True for node in servers: # Enable pacemaker.service ret, _, _ = g.run(node, "systemctl enable pacemaker.service") @@ -965,13 +1030,15 @@ def cluster_auth_setup(servers): return False # Perform cluster authentication between the nodes + auth_type = 'cluster' if is_rhel7(servers) else 'host' for node in servers: - ret, _, _ = g.run(node, "pcs cluster auth %s -u hacluster -p " - "hacluster" % ' '.join(servers)) - if ret != 0: - g.log.error("pcs cluster auth command failed on %s", node) - result = False - return result + ret, _, _ = g.run(node, "pcs %s auth %s -u hacluster -p hacluster" + % (auth_type, ' '.join(servers))) + if ret: + g.log.error("pcs %s auth command failed on %s", + auth_type, node) + return False + return True def configure_ports_on_servers(servers): diff --git a/glustolibs-gluster/glustolibs/gluster/rebalance_ops.py b/glustolibs-gluster/glustolibs/gluster/rebalance_ops.py index 1c8c10a4b..1011c89c6 100644 --- a/glustolibs-gluster/glustolibs/gluster/rebalance_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/rebalance_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -401,3 +401,76 @@ def get_remove_brick_status(mnode, volname, bricks_list): else: remove_brick_status[element.tag] = element.text return remove_brick_status + + +def wait_for_remove_brick_to_complete(mnode, volname, bricks_list, + timeout=1200): + """Waits for the remove brick to complete + + Args: + mnode (str): Node on which command has to be executed. + volname (str): volume name + bricks_list (str): List of bricks participating in + remove-brick operation + + Kwargs: + timeout (int): timeout value in seconds to wait for remove brick + to complete + + Returns: + True on success, False otherwise + + Examples: + >>> wait_for_remove_brick_to_complete("abc.com", "testvol") + """ + + count = 0 + while count < timeout: + status_info = get_remove_brick_status(mnode, volname, bricks_list) + if status_info is None: + return False + status = status_info['aggregate']['statusStr'] + if status == 'completed': + g.log.info("Remove brick is successfully completed in %s sec", + count) + return True + elif status == 'failed': + g.log.error(" Remove brick failed on one or more nodes. " + "Check remove brick status for more details") + return False + else: + time.sleep(10) + count += 10 + g.log.error("Remove brick operation has not completed. " + "Wait timeout is %s" % count) + return False + + +def set_rebalance_throttle(mnode, volname, throttle_type='normal'): + """Sets rebalance throttle + + Args: + mnode (str): Node on which cmd has to be executed. + volname (str): volume name + + Kwargs: + throttle_type (str): throttling type (lazy|normal|aggressive) + Defaults to 'normal' + + Returns: + tuple: Tuple containing three elements (ret, out, err). + The first element 'ret' is of type 'int' and is the return value + of command execution. + + The second element 'out' is of type 'str' and is the stdout value + of the command execution. + + The third element 'err' is of type 'str' and is the stderr value + of the command execution. + + Example: + set_rebalance_throttle(mnode, testvol, throttle_type='aggressive') + """ + cmd = ("gluster volume set {} rebal-throttle {}".format + (volname, throttle_type)) + return g.run(mnode, cmd) diff --git a/glustolibs-gluster/glustolibs/gluster/snap_ops.py b/glustolibs-gluster/glustolibs/gluster/snap_ops.py index 1e792ada7..0fba7771b 100644 --- a/glustolibs-gluster/glustolibs/gluster/snap_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/snap_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -859,3 +859,29 @@ def snap_deactivate(mnode, snapname): cmd = "gluster snapshot deactivate %s --mode=script" % snapname return g.run(mnode, cmd) + + +def terminate_snapd_on_node(mnode): + """Terminate snapd on the specified node + + Args: + mnode(str):node on which commands has to be executed + + Returns: + tuple: Tuple containing three elements (ret, out, err). + The first element 'ret' is of type 'int' and is the return value + of command execution. + + The second element 'out' is of type 'str' and is the stdout value + of the command execution. + + The third element 'err' is of type 'str' and is the stderr value + of the command execution. + """ + cmd = "ps aux| grep -m1 snapd | awk '{print $2}'" + _, out, _ = g.run(mnode, cmd) + if out is None: + g.log.error("Failed to get the snapd PID using command %s", cmd) + return None + cmd = "kill -9 %s" % out + return g.run(mnode, cmd) diff --git a/glustolibs-gluster/glustolibs/gluster/ssl_ops.py b/glustolibs-gluster/glustolibs/gluster/ssl_ops.py deleted file mode 100644 index f5d310d01..000000000 --- a/glustolibs-gluster/glustolibs/gluster/ssl_ops.py +++ /dev/null @@ -1,226 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2017-2018 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: Module for creating ssl machines for - validating basic ssl cases -""" - -from io import StringIO - -from glusto.core import Glusto as g - - -def create_ssl_machine(servers, clients): - """Following are the steps to create ssl machines: - - Stop glusterd on all servers - - Run: openssl genrsa -out /etc/ssl/glusterfs.key 2048 - - Run: openssl req -new -x509 -key /etc/ssl/glusterfs.key - -subj "/CN=ip's" -days 365 -out /etc/ssl/glusterfs.pem - - copy glusterfs.pem files into glusterfs.ca from all - the nodes(servers+clients) to all the servers - - touch /var/lib/glusterd/secure-access - - Start glusterd on all servers - Args: - servers: List of servers - clients: List of clients - - Returns: - bool : True if successfully created ssl machine. False otherwise. - """ - # pylint: disable=too-many-statements, too-many-branches - # pylint: disable=too-many-return-statements - # Variable to collect all servers ca_file for servers - ca_file_server = StringIO() - - # Stop glusterd on all servers - ret = g.run_parallel(servers, "systemctl stop glusterd") - if not ret: - g.log.error("Failed to stop glusterd on all servers") - return False - - # Generate key file on all servers - cmd = "openssl genrsa -out /etc/ssl/glusterfs.key 2048" - ret = g.run_parallel(servers, cmd) - if not ret: - g.log.error("Failed to create /etc/ssl/glusterfs.key " - "file on all servers") - return False - - # Generate glusterfs.pem file on all servers - for server in servers: - _, hostname, _ = g.run(server, "hostname") - cmd = ("openssl req -new -x509 -key /etc/ssl/glusterfs.key -subj " - "/CN=%s -days 365 -out /etc/ssl/glusterfs.pem" % (hostname)) - ret = g.run(server, cmd) - if not ret: - g.log.error("Failed to create /etc/ssl/glusterfs.pem " - "file on server %s", server) - return False - - # Copy glusterfs.pem file of all servers into ca_file_server - for server in servers: - conn1 = g.rpyc_get_connection(server) - if conn1 == "None": - g.log.error("Failed to get rpyc connection on %s", server) - - with conn1.builtin.open('/etc/ssl/glusterfs.pem') as fin: - ca_file_server.write(fin.read()) - - # Copy all ca_file_server for clients use - ca_file_client = ca_file_server.getvalue() - - # Generate key file on all clients - for client in clients: - _, hostname, _ = g.run(client, "hostname -s") - cmd = "openssl genrsa -out /etc/ssl/glusterfs.key 2048" - ret = g.run(client, cmd) - if not ret: - g.log.error("Failed to create /etc/ssl/glusterfs.key " - "file on client %s", client) - return False - - # Generate glusterfs.pem file on all clients - cmd = ("openssl req -new -x509 -key /etc/ssl/glusterfs.key -subj " - "/CN=%s -days 365 -out /etc/ssl/glusterfs.pem" % (client)) - ret = g.run(client, cmd) - if not ret: - g.log.error("Failed to create /etc/ssl/glusterf.pem " - "file on client %s", client) - return False - - # Copy glusterfs.pem file of client to a ca_file_server - conn2 = g.rpyc_get_connection(client) - if conn2 == "None": - g.log.error("Failed to get rpyc connection on %s", server) - with conn2.builtin.open('/etc/ssl/glusterfs.pem') as fin: - ca_file_server.write(fin.read()) - - # Copy glusterfs.pem file to glusterfs.ca of client such that - # clients shouldn't share respectives ca file each other - cmd = "cp /etc/ssl/glusterfs.pem /etc/ssl/glusterfs.ca" - ret, _, _ = g.run(client, cmd) - if ret != 0: - g.log.error("Failed to copy the glusterfs.pem to " - "glusterfs.ca of client") - return False - - # Now copy the ca_file of all servers to client ca file - with conn2.builtin.open('/etc/ssl/glusterfs.ca', 'a') as fout: - fout.write(ca_file_client) - - # Create /var/lib/glusterd directory on clients - ret = g.run(client, "mkdir -p /var/lib/glusterd/") - if not ret: - g.log.error("Failed to create directory /var/lib/glusterd/" - " on clients") - - # Copy ca_file_server to all servers - for server in servers: - conn3 = g.rpyc_get_connection(server) - if conn3 == "None": - g.log.error("Failed to get rpyc connection on %s", server) - - with conn3.builtin.open('/etc/ssl/glusterfs.ca', 'w') as fout: - fout.write(ca_file_server.getvalue()) - - # Touch /var/lib/glusterd/secure-access on all servers - ret = g.run_parallel(servers, "touch /var/lib/glusterd/secure-access") - if not ret: - g.log.error("Failed to touch the file on servers") - return False - - # Touch /var/lib/glusterd/secure-access on all clients - ret = g.run_parallel(clients, "touch /var/lib/glusterd/secure-access") - if not ret: - g.log.error("Failed to touch the file on clients") - return False - - # Start glusterd on all servers - ret = g.run_parallel(servers, "systemctl start glusterd") - if not ret: - g.log.error("Failed to stop glusterd on servers") - return False - - return True - - -def cleanup_ssl_setup(servers, clients): - """ - Following are the steps to cleanup ssl setup: - - Stop glusterd on all servers - - Remove folder /etc/ssl/* - - Remove /var/lib/glusterd/* - - Start glusterd on all servers - - Args: - servers: List of servers - clients: List of clients - - Returns: - bool : True if successfully cleaned ssl machine. False otherwise. - """ - # pylint: disable=too-many-return-statements - _rc = True - - # Stop glusterd on all servers - ret = g.run_parallel(servers, "systemctl stop glusterd") - if not ret: - _rc = False - g.log.error("Failed to stop glusterd on all servers") - - # Remove glusterfs.key, glusterfs.pem and glusterfs.ca file - # from all servers - cmd = "rm -rf /etc/ssl/glusterfs*" - ret = g.run_parallel(servers, cmd) - if not ret: - _rc = False - g.log.error("Failed to remove folder /etc/ssl/glusterfs* " - "on all servers") - - # Remove folder /var/lib/glusterd/secure-access from servers - cmd = "rm -rf /var/lib/glusterd/secure-access" - ret = g.run_parallel(servers, cmd) - if not ret: - _rc = False - g.log.error("Failed to remove folder /var/lib/glusterd/secure-access " - "on all servers") - - # Remove glusterfs.key, glusterfs.pem and glusterfs.ca file - # from all clients - cmd = "rm -rf /etc/ssl/glusterfs*" - ret = g.run_parallel(clients, cmd) - if not ret: - _rc = False - g.log.error("Failed to remove folder /etc/ssl/glusterfs* " - "on all clients") - - # Remove folder /var/lib/glusterd/secure-access from clients - cmd = "rm -rf /var/lib/glusterd/secure-access" - ret = g.run_parallel(clients, cmd) - if not ret: - _rc = False - g.log.error("Failed to remove folder /var/lib/glusterd/secure-access " - "on all clients") - - # Start glusterd on all servers - ret = g.run_parallel(servers, "systemctl start glusterd") - if not ret: - _rc = False - g.log.error("Failed to stop glusterd on servers") - - return _rc diff --git a/glustolibs-gluster/glustolibs/gluster/tiering_ops.py b/glustolibs-gluster/glustolibs/gluster/tiering_ops.py deleted file mode 100644 index 357b3d471..000000000 --- a/glustolibs-gluster/glustolibs/gluster/tiering_ops.py +++ /dev/null @@ -1,1023 +0,0 @@ -#!/usr/bin/env python -# 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 tiering operations. -""" - -import re -import time -from glusto.core import Glusto as g -from glustolibs.gluster.peer_ops import peer_probe_servers -from glustolibs.gluster.gluster_init import start_glusterd -from glustolibs.gluster.lib_utils import list_files - -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree - - -def add_extra_servers_to_cluster(mnode, extra_servers): - """Adds the given extra servers to cluster - - Args: - mnode (str): Node on which cmd has to be executed. - extra_servers (str|list) : A server|list of extra servers to be - attached to cluster - - Returns: - bool: True, if extra servers are attached to cluster - False, otherwise - - Example: - add_extra_servers_to_cluster("abc.com", ['peer_node1','peer_node2']) - """ - - if not isinstance(extra_servers, list): - extra_servers = [extra_servers] - - ret = start_glusterd(servers=extra_servers) - if not ret: - g.log.error("glusterd did not start in peer nodes") - return False - - ret = peer_probe_servers(mnode, servers=extra_servers) - if not ret: - g.log.error("Unable to do peer probe on extra server machines") - return False - - return True - - -def tier_attach(mnode, volname, num_bricks_to_add, extra_servers, - extra_servers_info, replica=1, force=False): - """Attaches tier to the volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - num_bricks_to_add (str): number of bricks to be added as hot tier - extra_servers (str|list): from this server|these servers, - hot tier will be added to volume - extra_servers_info (dict): dict of server info of each extra servers - - Kwargs: - replica (str): replica count of the hot tier - force (bool): If this option is set to True, then attach tier - will get executed with force option. If it is set to False, - then attach tier will get executed without force option - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_attach("abc.com", testvol, '2', ['extra_server1','extra_server2'], - extra_server_info) - """ - if not isinstance(extra_servers, list): - extra_servers = [extra_servers] - - replica = int(replica) - repc = '' - if replica != 1: - repc = "replica %d" % replica - - frce = '' - if force: - frce = 'force' - - num_bricks_to_add = int(num_bricks_to_add) - - from glustolibs.gluster.lib_utils import form_bricks_list - bricks_list = form_bricks_list(mnode, volname, num_bricks_to_add, - extra_servers[:], extra_servers_info) - if bricks_list is None: - g.log.error("number of bricks required are greater than " - "unused bricks") - return (-1, '', '') - - bricks_path = ' '.join(bricks_list) - bricks_path = [re.sub(r"(.*\/\S+\_)brick(\d+)", r"\1tier\2", item) - for item in bricks_path.split() if item] - tier_bricks_path = " ".join(bricks_path) - cmd = ("gluster volume tier %s attach %s %s %s --mode=script" - % (volname, repc, tier_bricks_path, frce)) - - return g.run(mnode, cmd) - - -def tier_start(mnode, volname, force=False): - """Starts the tier volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Kwargs: - force (bool): If this option is set to True, then attach tier - will get executed with force option. If it is set to False, - then attach tier will get executed without force option - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_start("abc.com", testvol) - """ - - frce = '' - if force: - frce = 'force' - - cmd = ("gluster volume tier %s start %s --mode=script" - % (volname, frce)) - return g.run(mnode, cmd) - - -def tier_status(mnode, volname): - """executes tier status command - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_status("abc.com", testvol) - """ - - cmd = "gluster volume tier %s status" % volname - ret = g.run(mnode, cmd) - - return ret - - -def get_tier_status(mnode, volname): - """Parse the output of 'gluster tier status' command. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - dict: dict on success. - - Examples: - >>> get_tier_status('abc.lab.eng.xyz.com', 'testvol') - {'node': [{'promotedFiles': '0', 'demotedFiles': '0', 'nodeName': - 'localhost', 'statusStr': 'in progress'}, {'promotedFiles': '0', - 'demotedFiles': '0', 'nodeName': '10.70.47.16', 'statusStr': - 'in progress'}], 'task-id': '2ed28cbd-4246-493a-87b8-1fdcce313b34', - 'nodeCount': '4', 'op': '7'} - """ - - cmd = "gluster volume tier %s status --xml" % volname - ret, out, _ = g.run(mnode, cmd) - if ret != 0: - g.log.error("Failed to execute 'tier status' on node %s. " - "Hence failed to get tier status.", mnode) - return None - - try: - root = etree.XML(out) - except etree.ParseError: - g.log.error("Failed to parse the gluster tier status xml output.") - return None - - tier_status = {} - tier_status["node"] = [] - for info in root.findall("volRebalance"): - for element in info.getchildren(): - if element.tag == "node": - status_info = {} - for elmt in element.getchildren(): - status_info[elmt.tag] = elmt.text - tier_status[element.tag].append(status_info) - else: - tier_status[element.tag] = element.text - return tier_status - - -def tier_detach_start(mnode, volname): - """starts detaching tier on given volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_detach_start("abc.com", testvol) - - """ - - cmd = "gluster volume tier %s detach start --mode=script" % volname - return g.run(mnode, cmd) - - -def tier_detach_status(mnode, volname): - """executes detach tier status on given volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_detach_status("abc.com", testvol) - - """ - - cmd = "gluster volume tier %s detach status --mode=script" % volname - return g.run(mnode, cmd) - - -def tier_detach_stop(mnode, volname): - """stops detaching tier on given volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_detach_stop("abc.com", testvol) - - """ - - cmd = "gluster volume tier %s detach stop --mode=script" % volname - return g.run(mnode, cmd) - - -def tier_detach_commit(mnode, volname): - """commits detach tier on given volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_detach_commit("abc.com", testvol) - - """ - - cmd = "gluster volume tier %s detach commit --mode=script" % volname - return g.run(mnode, cmd) - - -def tier_detach_force(mnode, volname): - """detaches tier forcefully on given volume - - Args: - mnode (str): Node on which cmd has to be executed. - volname (str): volume name - - Returns: - tuple: Tuple containing three elements (ret, out, err). - The first element 'ret' is of type 'int' and is the return value - of command execution. - - The second element 'out' is of type 'str' and is the stdout value - of the command execution. - - The third element 'err' is of type 'str' and is the stderr value - of the command execution. - - Example: - tier_detach_force("abc.com", testvol) - - """ - - cmd = "gluster volume tier %s detach force --mode=script" % volname - return g.run(mnode, cmd) - - -def get_detach_tier_status(mnode, volname): - """Parse the output of 'gluster volume tier detach status' command. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - dict: dict on success. - - Examples: - >>> get_detach_tier_status('abc.lab.eng.xyz.com', "testvol") - {'node': [{'files': '0', 'status': '3', 'lookups': '1', 'skipped': '0', - 'nodeName': 'localhost', 'failures': '0', 'runtime': '0.00', 'id': - '11336017-9561-4e88-9ac3-a94d4b403340', 'statusStr': 'completed', - 'size': '0'}, {'files': '0', 'status': '3', 'lookups': '0', 'skipped': - '0', 'nodeName': '10.70.47.16', 'failures': '0', 'runtime': '0.00', - 'id': 'a2b88b10-eba2-4f97-add2-8dc37df08b27', 'statusStr': 'completed', - 'size': '0'}], 'nodeCount': '4', 'aggregate': {'files': '0', 'status': - '3', 'lookups': '1', 'skipped': '0', 'failures': '0', 'runtime': '0.0', - 'statusStr': 'completed', 'size': '0'}} - """ - - cmd = "gluster volume tier %s detach status --xml" % volname - ret, out, _ = g.run(mnode, cmd) - if ret != 0: - g.log.error("Failed to execute 'detach tier status' on node %s. " - "Hence failed to get detach tier status.", mnode) - return None - - try: - root = etree.XML(out) - except etree.ParseError: - g.log.error("Failed to parse the detach tier status xml output.") - return None - - tier_status = {} - tier_status["node"] = [] - for info in root.findall("volDetachTier"): - for element in info.getchildren(): - if element.tag == "node": - status_info = {} - for elmt in element.getchildren(): - status_info[elmt.tag] = elmt.text - tier_status[element.tag].append(status_info) - elif element.tag == "aggregate": - status_info = {} - for elmt in element.getchildren(): - status_info[elmt.tag] = elmt.text - tier_status[element.tag] = status_info - else: - tier_status[element.tag] = element.text - return tier_status - - -def tier_detach_start_and_get_taskid(mnode, volname): - """Parse the output of 'gluster volume tier detach start' command. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - dict: dict on success. - - Examples: - >>> tier_detach_start_and_get_taskid('abc.lab.eng.xyz.com', - "testvol") - {'task-id': '8020835c-ff0d-4ea1-9f07-62dd067e92d4'} - """ - - cmd = "gluster volume tier %s detach start --xml" % volname - ret, out, _ = g.run(mnode, cmd) - if ret != 0: - g.log.error("Failed to execute 'detach tier start' on node %s. " - "Hence failed to parse the detach tier start.", mnode) - return None - - try: - root = etree.XML(out) - except etree.ParseError: - g.log.error("Failed to parse the gluster detach tier " - "start xml output.") - return None - - tier_status = {} - for info in root.findall("volDetachTier"): - for element in info.getchildren(): - tier_status[element.tag] = element.text - return tier_status - - -def tier_detach_stop_and_get_status(mnode, volname): - """Parse the output of 'gluster volume tier detach stop' command. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - dict: dict on success. - - Examples: - >>> tier_detach_stop_and_get_status('abc.lab.eng.xyz.com', - "testvol") - {'node': [{'files': '0', 'status': '3', 'lookups': '1', 'skipped': '0', - 'nodeName': 'localhost', 'failures': '0', 'runtime': '0.00', 'id': - '11336017-9561-4e88-9ac3-a94d4b403340', 'statusStr': 'completed', - 'size': '0'}, {'files': '0', 'status': '3', 'lookups': '0', 'skipped': - '0', 'nodeName': '10.70.47.16', 'failures': '0', 'runtime': '0.00', - 'id': 'a2b88b12-eba2-4f97-add2-8dc37df08b27', 'statusStr': 'completed', - 'size': '0'}], 'nodeCount': '4', 'aggregate': {'files': '0', 'status': - '3', 'lookups': '1', 'skipped': '0', 'failures': '0', 'runtime': '0.0', - 'statusStr': 'completed', 'size': '0'}} - """ - - cmd = "gluster volume tier %s detach stop --xml" % volname - ret, out, _ = g.run(mnode, cmd) - if ret != 0: - g.log.error("Failed to execute 'tier start' on node %s. " - "Hence failed to parse the tier start.", mnode) - return None - - try: - root = etree.XML(out) - except etree.ParseError: - g.log.error("Failed to parse the gluster detach tier stop" - " xml output.") - return None - - tier_status = {} - tier_status["node"] = [] - for info in root.findall("volDetachTier"): - for element in info.getchildren(): - if element.tag == "node": - status_info = {} - for elmt in element.getchildren(): - status_info[elmt.tag] = elmt.text - tier_status[element.tag].append(status_info) - elif element.tag == "aggregate": - status_info = {} - for elmt in element.getchildren(): - status_info[elmt.tag] = elmt.text - tier_status[element.tag] = status_info - else: - tier_status[element.tag] = element.text - return tier_status - - -def wait_for_detach_tier_to_complete(mnode, volname, timeout=300): - """Waits for the detach tier to complete - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Kwargs: - timeout (int): timeout value to wait for detach tier to complete - - Returns: - True on success, False otherwise - - Examples: - >>> wait_for_detach_tier_to_complete(mnode, "testvol") - """ - - count = 0 - flag = 0 - while (count < timeout): - status_info = get_detach_tier_status(mnode, volname) - if status_info is None: - return False - - status = status_info['aggregate']['statusStr'] - if status == 'completed': - flag = 1 - break - - time.sleep(10) - count = count + 10 - if not flag: - g.log.error("detach tier is not completed") - return False - else: - g.log.info("detach tier is successfully completed") - return True - - -def get_files_from_hot_tier(mnode, volname): - """Lists files from hot tier for the given volume - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - Emptylist: if there are no files in hot tier. - list: list of files in hot tier on success. - - Examples: - >>>get_files_from_hot_tier(mnode, "testvol") - """ - - files = [] - from glustolibs.gluster.volume_libs import get_subvols - subvols = get_subvols(mnode, volname) - for subvol in subvols['hot_tier_subvols']: - info = subvol[0].split(':') - file_list = list_files(info[0], info[1]) - for file in file_list: - if ".glusterfs" not in file: - files.append(file) - - return files - - -def get_files_from_cold_tier(mnode, volname): - """Lists files from cold tier for the given volume - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - Emptylist: if there are no files in cold tier. - list: list of files in cold tier on success. - - Examples: - >>>get_files_from_hot_tier("testvol") - """ - - files = [] - from glustolibs.gluster.volume_libs import get_subvols - subvols = get_subvols(mnode, volname) - for subvol in subvols['cold_tier_subvols']: - info = subvol[0].split(':') - file_list = list_files(info[0], info[1]) - for file in file_list: - if ".glusterfs" not in file: - files.append(file) - - return files - - -def get_tier_promote_frequency(mnode, volname): - """Gets tier promote frequency value for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: promote frequency value on success. - - Examples: - >>>get_tier_promote_frequency("abc.com", "testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.tier-promote-frequency'] - - -def get_tier_demote_frequency(mnode, volname): - """Gets tier demote frequency value for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: demote frequency value on success. - - Examples: - >>>get_tier_demote_frequency("abc.com", "testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.tier-demote-frequency'] - - -def get_tier_mode(mnode, volname): - """Gets tier mode for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: tier mode on success. - - Examples: - >>>get_tier_mode("testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.tier-mode'] - - -def get_tier_max_mb(mnode, volname): - """Gets tier max mb for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: tier max mb on success. - - Examples: - >>>get_tier_max_mb("abc.com", "testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.tier-max-mb'] - - -def get_tier_max_files(mnode, volname): - """Gets tier max files for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: tier max files on success. - - Examples: - >>>get_tier_max_files("abc.com", "testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.tier-max-files'] - - -def get_tier_watermark_high_limit(mnode, volname): - """Gets tier watermark high limit for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: tier watermark high limit on success. - - Examples: - >>>get_tier_watermark_high_limit(mnode, "testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.watermark-hi'] - - -def get_tier_watermark_low_limit(mnode, volname): - """Gets tier watermark low limit for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: tier watermark low limit on success. - - Examples: - >>>get_tier_watermark_low_limit("abc.com", "testvol") - """ - - from glustolibs.gluster.volume_ops import get_volume_options - vol_options = get_volume_options(mnode, volname) - if vol_options is None: - g.log.error("Failed to get volume options") - return None - - return vol_options['cluster.watermark-low'] - - -def set_tier_promote_frequency(mnode, volname, value): - """Sets tier promote frequency value for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): promote frequency value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_promote_frequency("abc.com", "testvol", '1000') - """ - - option = {'cluster.tier-promote-frequency': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set promote frequency to %s" - % value) - return False - - return True - - -def set_tier_demote_frequency(mnode, volname, value): - """Sets tier demote frequency value for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): demote frequency value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_demote_frequency("abc.com", "testvol", "500") - """ - - option = {'cluster.tier-demote-frequency': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set demote frequency to %s" - % value) - return False - - return True - - -def set_tier_mode(mnode, volname, value): - """Sets tier mode for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): tier mode value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_mode("abc.com", "testvol", "cache") - """ - - option = {'cluster.tier-mode': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set tier mode to %s" - % value) - return False - - return True - - -def set_tier_max_mb(mnode, volname, value): - """Sets tier max mb for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): tier mode value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_max_mb("abc.com", "testvol", "50") - """ - - option = {'cluster.tier-max-mb': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set tier max mb to %s" - % value) - return False - - return True - - -def set_tier_max_files(mnode, volname, value): - """Sets tier max files for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): tier mode value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_max_files("abc.com", "testvol", "10") - """ - - option = {'cluster.tier-max-files': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set tier max files to %s" - % value) - return False - - return True - - -def set_tier_watermark_high_limit(mnode, volname, value): - """Sets tier watermark high limit for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): tier mode value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_watermark_high_limit("abc.com", "testvol", "95") - """ - - option = {'cluster.watermark-hi': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set tier watermark high limit to %s" - % value) - return False - - return True - - -def set_tier_watermark_low_limit(mnode, volname, value): - """Sets tier watermark low limit for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - value (str): tier mode value - - Returns: - bool: True on success, False Otherwise - - Examples: - >>>set_tier_watermark_low_limit("abc.com", "testvol", "40") - """ - - option = {'cluster.watermark-low': value} - - from glustolibs.gluster.volume_ops import set_volume_options - if not set_volume_options(mnode, volname, - options=option): - g.log.error("Failed to set tier watermark low limit to %s" - % value) - return False - - return True - - -def get_tier_pid(mnode, volname): - """Gets tier pid for given volume. - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - NoneType: None if command execution fails, parse errors. - str: pid of tier process on success. - - Examples: - >>>get_tier_pid("abc.xyz.com", "testvol") - """ - - cmd = ("ps -ef | grep -v grep | grep '/var/log/glusterfs/%s-tier.log' |" - "awk '{print $2}'" % volname) - ret, out, err = g.run(mnode, cmd) - if ret != 0: - g.log.error("Failed to execute 'ps' cmd") - return None - - return out.strip("\n") - - -def is_tier_process_running(mnode, volname): - """Checks whether tier process is running - - Args: - mnode (str): Node on which command has to be executed. - volname (str): volume name - - Returns: - True on success, False otherwise - - Examples: - >>>is_tier_process_running("abc.xyz.com", "testvol") - """ - - pid = get_tier_pid(mnode, volname) - if pid == '': - return False - return True diff --git a/glustolibs-gluster/glustolibs/gluster/volume_libs.py b/glustolibs-gluster/glustolibs/gluster/volume_libs.py index e5a4f4f1c..87e70ca8c 100644 --- a/glustolibs-gluster/glustolibs/gluster/volume_libs.py +++ b/glustolibs-gluster/glustolibs/gluster/volume_libs.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2019 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -24,15 +24,13 @@ except ImportError: import xml.etree.ElementTree as etree from glusto.core import Glusto as g from glustolibs.gluster.lib_utils import form_bricks_list +from glustolibs.gluster.brickmux_libs import form_bricks_for_multivol from glustolibs.gluster.volume_ops import (volume_create, volume_start, set_volume_options, get_volume_info, volume_stop, volume_delete, volume_info, volume_status, get_volume_options, get_volume_list) -from glustolibs.gluster.tiering_ops import (add_extra_servers_to_cluster, - tier_attach, - is_tier_process_running) from glustolibs.gluster.quota_ops import (quota_enable, quota_limit_usage, is_quota_enabled) from glustolibs.gluster.uss_ops import enable_uss, is_uss_enabled @@ -65,7 +63,8 @@ def volume_exists(mnode, volname): return False -def setup_volume(mnode, all_servers_info, volume_config, force=False): +def setup_volume(mnode, all_servers_info, volume_config, multi_vol=False, + force=False, create_only=False): """Setup Volume with the configuration defined in volume_config Args: @@ -99,13 +98,20 @@ def setup_volume(mnode, all_servers_info, volume_config, force=False): 'size': '100GB'}, 'enable': False}, 'uss': {'enable': False}, - 'tier': {'create_tier': True, - 'tier_type': {'type': 'distributed-replicated', - 'replica_count': 2, - 'dist_count': 2, - 'transport': 'tcp'}}, 'options': {'performance.readdir-ahead': True} } + Kwargs: + multi_vol (bool): True, If bricks need to created for multiple + volumes(more than 5) + False, Otherwise. By default, value is set to False. + force (bool): If this option is set to True, then volume creation + command is executed with force option. + False, without force option. + By default, value is set to False. + create_only(bool): True, if only volume creation is needed. + False, will do volume create, start, set operation + if any provided in the volume_config. + By default, value is set to False. Returns: bool : True on successful setup. False Otherwise @@ -118,8 +124,8 @@ def setup_volume(mnode, all_servers_info, volume_config, force=False): return False # Check if the volume already exists - volinfo = get_volume_info(mnode=mnode) - if volinfo is not None and volname in volinfo.keys(): + vollist = get_volume_list(mnode=mnode) + if vollist is not None and volname in vollist: g.log.info("volume %s already exists. Returning...", volname) return True @@ -261,10 +267,15 @@ def setup_volume(mnode, all_servers_info, volume_config, force=False): return False # get bricks_list - bricks_list = form_bricks_list(mnode=mnode, volname=volname, - number_of_bricks=number_of_bricks, - servers=servers, - servers_info=all_servers_info) + if multi_vol: + bricks_list = form_bricks_for_multivol( + mnode=mnode, volname=volname, number_of_bricks=number_of_bricks, + servers=servers, servers_info=all_servers_info) + else: + bricks_list = form_bricks_list(mnode=mnode, volname=volname, + number_of_bricks=number_of_bricks, + servers=servers, + servers_info=all_servers_info) if not bricks_list: g.log.error("Number_of_bricks is greater than the unused bricks on " "servers") @@ -278,6 +289,25 @@ def setup_volume(mnode, all_servers_info, volume_config, force=False): g.log.error("Unable to create volume %s", volname) return False + if create_only and (ret == 0): + g.log.info("Volume creation of {} is done successfully".format( + volname)) + return True + + is_ganesha = False + if 'nfs_ganesha' in volume_config: + is_ganesha = bool(volume_config['nfs_ganesha']['enable']) + + if not is_ganesha: + # Set all the volume options: + if 'options' in volume_config: + volume_options = volume_config['options'] + ret = set_volume_options(mnode=mnode, volname=volname, + options=volume_options) + if not ret: + g.log.error("Unable to set few volume options") + return False + # Start Volume time.sleep(2) ret = volume_start(mnode, volname) @@ -285,68 +315,6 @@ def setup_volume(mnode, all_servers_info, volume_config, force=False): g.log.error("volume start %s failed", volname) return False - # Create Tier volume - if ('tier' in volume_config and 'create_tier' in volume_config['tier'] and - volume_config['tier']['create_tier']): - # get servers info for tier attach - if ('extra_servers' in volume_config and - volume_config['extra_servers']): - extra_servers = volume_config['extra_servers'] - ret = add_extra_servers_to_cluster(mnode, extra_servers) - if not ret: - return False - else: - extra_servers = volume_config['servers'] - - # get the tier volume type - if 'tier_type' in volume_config['tier']: - if 'type' in volume_config['tier']['tier_type']: - tier_volume_type = volume_config['tier']['tier_type']['type'] - dist = rep = 1 - if tier_volume_type == 'distributed': - if 'dist_count' in volume_config['tier']['tier_type']: - dist = (volume_config['tier']['tier_type'] - ['dist_count']) - - elif tier_volume_type == 'replicated': - if 'replica_count' in volume_config['tier']['tier_type']: - rep = (volume_config['tier']['tier_type'] - ['replica_count']) - - elif tier_volume_type == 'distributed-replicated': - if 'dist_count' in volume_config['tier']['tier_type']: - dist = (volume_config['tier']['tier_type'] - ['dist_count']) - if 'replica_count' in volume_config['tier']['tier_type']: - rep = (volume_config['tier']['tier_type'] - ['replica_count']) - else: - tier_volume_type = 'distributed' - dist = 1 - rep = 1 - number_of_bricks = dist * rep - - # Attach Tier - ret, _, _ = tier_attach(mnode=mnode, volname=volname, - extra_servers=extra_servers, - extra_servers_info=all_servers_info, - num_bricks_to_add=number_of_bricks, - replica=rep) - if ret != 0: - g.log.error("Unable to attach tier") - return False - - time.sleep(30) - # Check if tier is running - _rc = True - for server in extra_servers: - ret = is_tier_process_running(server, volname) - if not ret: - g.log.error("Tier process not running on %s", server) - _rc = False - if not _rc: - return False - # Enable Quota if ('quota' in volume_config and 'enable' in volume_config['quota'] and volume_config['quota']['enable']): @@ -396,13 +364,73 @@ def setup_volume(mnode, all_servers_info, volume_config, force=False): g.log.error("USS is not enabled on the volume %s", volname) return False - # Set all the volume options: - if 'options' in volume_config: - volume_options = volume_config['options'] - ret = set_volume_options(mnode=mnode, volname=volname, - options=volume_options) + if is_ganesha: + # Set all the volume options for NFS Ganesha + if 'options' in volume_config: + volume_options = volume_config['options'] + ret = set_volume_options(mnode=mnode, volname=volname, + options=volume_options) + if not ret: + g.log.error("Unable to set few volume options") + return False + + return True + + +def bulk_volume_creation(mnode, number_of_volumes, servers_info, + volume_config, vol_prefix="mult_vol_", + is_force=False, is_create_only=False): + """ + Creates the number of volumes user has specified + + Args: + mnode (str): Node on which commands has to be executed. + number_of_volumes (int): Specify the number of volumes + to be created. + servers_info (dict): Information about all servers. + volume_config (dict): Dict containing the volume information + + Kwargs: + vol_prefix (str): Prefix to be added to the volume name. + is_force (bool): True, If volume create command need to be executed + with force, False Otherwise. Defaults to False. + create_only(bool): True, if only volume creation is needed. + False, will do volume create, start, set operation + if any provided in the volume_config. + By default, value is set to False. + Returns: + bool: True on successful bulk volume creation, False Otherwise. + + example: + volume_config = { + 'name': 'testvol', + 'servers': ['server-vm1', 'server-vm2', 'server-vm3', + 'server-vm4'], + 'voltype': {'type': 'distributed', + 'dist_count': 4, + 'transport': 'tcp'}, + 'extra_servers': ['server-vm9', 'server-vm10', + 'server-vm11', 'server-vm12'], + 'quota': {'limit_usage': {'path': '/', 'percent': None, + 'size': '100GB'}, + 'enable': False}, + 'uss': {'enable': False}, + 'options': {'performance.readdir-ahead': True} + } + """ + + if not (number_of_volumes > 1): + g.log.error("Provide number of volume greater than 1") + return False + + volume_name = volume_config['name'] + for volume in range(number_of_volumes): + volume_config['name'] = vol_prefix + volume_name + str(volume) + ret = setup_volume(mnode, servers_info, volume_config, multi_vol=True, + force=is_force, create_only=is_create_only) if not ret: - g.log.error("Unable to set few volume options") + g.log.error("Volume creation failed for the volume %s" + % volume_config['name']) return False return True @@ -548,77 +576,11 @@ def get_subvols(mnode, volname): get_subvols("abc.xyz.com", "testvol") """ - subvols = { - 'is_tier': False, - 'hot_tier_subvols': [], - 'cold_tier_subvols': [], - 'volume_subvols': [] - } + subvols = {'volume_subvols': []} + volinfo = get_volume_info(mnode, volname) if volinfo is not None: voltype = volinfo[volname]['typeStr'] - if voltype == 'Tier': - # Set is_tier to True - subvols['is_tier'] = True - - # Get hot tier subvols - hot_tier_type = (volinfo[volname]["bricks"] - ['hotBricks']['hotBrickType']) - tmp = volinfo[volname]["bricks"]['hotBricks']["brick"] - hot_tier_bricks = [x["name"] for x in tmp if "name" in x] - if hot_tier_type == 'Distribute': - for brick in hot_tier_bricks: - subvols['hot_tier_subvols'].append([brick]) - - elif (hot_tier_type == 'Replicate' or - hot_tier_type == 'Distributed-Replicate'): - rep_count = int( - (volinfo[volname]["bricks"]['hotBricks'] - ['numberOfBricks']).split("=", 1)[0].split("x")[1].strip() - ) - subvol_list = ( - [hot_tier_bricks[i:i + rep_count] - for i in range(0, len(hot_tier_bricks), rep_count)]) - subvols['hot_tier_subvols'] = subvol_list - - # Get cold tier subvols - cold_tier_type = (volinfo[volname]["bricks"]['coldBricks'] - ['coldBrickType']) - tmp = volinfo[volname]["bricks"]['coldBricks']["brick"] - cold_tier_bricks = [x["name"] for x in tmp if "name" in x] - - # Distribute volume - if cold_tier_type == 'Distribute': - for brick in cold_tier_bricks: - subvols['cold_tier_subvols'].append([brick]) - - # Replicate or Distribute-Replicate volume - elif (cold_tier_type == 'Replicate' or - cold_tier_type == 'Distributed-Replicate'): - rep_count = int( - (volinfo[volname]["bricks"]['coldBricks'] - ['numberOfBricks']).split("=", 1)[0].split("x")[1].strip() - ) - subvol_list = ( - [cold_tier_bricks[i:i + rep_count] - for i in range(0, len(cold_tier_bricks), rep_count)]) - subvols['cold_tier_subvols'] = subvol_list - - # Disperse or Distribute-Disperse volume - elif (cold_tier_type == 'Disperse' or - cold_tier_type == 'Distributed-Disperse'): - disp_count = sum( - [int(nums) for nums in ( - (volinfo[volname]["bricks"]['coldBricks'] - ['numberOfBricks']).split("x", 1)[1]. - strip().split("=")[0].strip().strip("()"). - split()) if nums.isdigit()]) - subvol_list = [cold_tier_bricks[i:i + disp_count] - for i in range(0, len(cold_tier_bricks), - disp_count)] - subvols['cold_tier_subvols'] = subvol_list - return subvols - tmp = volinfo[volname]["bricks"]["brick"] bricks = [x["name"] for x in tmp if "name" in x] if voltype == 'Replicate' or voltype == 'Distributed-Replicate': @@ -639,29 +601,6 @@ def get_subvols(mnode, volname): return subvols -def is_tiered_volume(mnode, volname): - """Check if volume is tiered volume. - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - bool : True if the volume is tiered volume. False otherwise - NoneType: None if volume does not exist. - """ - volinfo = get_volume_info(mnode, volname) - if volinfo is None: - g.log.error("Unable to get the volume info for volume %s", volname) - return None - - voltype = volinfo[volname]['typeStr'] - if voltype == 'Tier': - return True - else: - return False - - def is_distribute_volume(mnode, volname): """Check if volume is a plain distributed volume @@ -678,20 +617,10 @@ def is_distribute_volume(mnode, volname): g.log.error("Unable to check if the volume %s is distribute", volname) return False - if volume_type_info['is_tier']: - hot_tier_type = (volume_type_info['hot_tier_type_info'] - ['hotBrickType']) - cold_tier_type = (volume_type_info['cold_tier_type_info'] - ['coldBrickType']) - if hot_tier_type == 'Distribute' and cold_tier_type == 'Distribute': - return True - else: - return False + if volume_type_info['volume_type_info']['typeStr'] == 'Distribute': + return True else: - if volume_type_info['volume_type_info']['typeStr'] == 'Distribute': - return True - else: - return False + return False def get_volume_type_info(mnode, volname): @@ -705,9 +634,6 @@ def get_volume_type_info(mnode, volname): dict : Dict containing the keys, values defining the volume type: Example: volume_type_info = { - 'is_tier': False, - 'hot_tier_type_info': {}, - 'cold_tier_type_info': {}, 'volume_type_info': { 'typeStr': 'Disperse', 'replicaCount': '1', @@ -719,18 +645,6 @@ def get_volume_type_info(mnode, volname): } volume_type_info = { - 'is_tier': True, - 'hot_tier_type_info': { - 'hotBrickType': 'Distribute', - 'hotreplicaCount': '1' - }, - 'cold_tier_type_info': { - 'coldBrickType': 'Disperse', - 'coldreplicaCount': '1', - 'coldarbiterCount': '0', - 'colddisperseCount': '3', - 'numberOfBricks':1 - }, 'volume_type_info': {} @@ -741,138 +655,26 @@ def get_volume_type_info(mnode, volname): g.log.error("Unable to get the volume info for volume %s", volname) return None - volume_type_info = { - 'is_tier': False, - 'hot_tier_type_info': {}, - 'cold_tier_type_info': {}, - 'volume_type_info': {} - } - - voltype = volinfo[volname]['typeStr'] - if voltype == 'Tier': - volume_type_info['is_tier'] = True - - hot_tier_type_info = get_hot_tier_type_info(mnode, volname) - volume_type_info['hot_tier_type_info'] = hot_tier_type_info - - cold_tier_type_info = get_cold_tier_type_info(mnode, volname) - volume_type_info['cold_tier_type_info'] = cold_tier_type_info - - else: - non_tiered_volume_type_info = { - 'typeStr': '', - 'replicaCount': '', - 'arbiterCount': '', - 'stripeCount': '', - 'disperseCount': '', - 'redundancyCount': '' - } - for key in non_tiered_volume_type_info.keys(): - if key in volinfo[volname]: - non_tiered_volume_type_info[key] = volinfo[volname][key] - else: - g.log.error("Unable to find key '%s' in the volume info for " - "the volume %s", key, volname) - non_tiered_volume_type_info[key] = None - volume_type_info['volume_type_info'] = non_tiered_volume_type_info - - return volume_type_info - - -def get_cold_tier_type_info(mnode, volname): - """Returns cold tier type information for the specified volume. - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - dict : Dict containing the keys, values defining the cold tier type: - Example: - cold_tier_type_info = { - 'coldBrickType': 'Disperse', - 'coldreplicaCount': '1', - 'coldarbiterCount': '0', - 'colddisperseCount': '3', - 'numberOfBricks': '3' - } - NoneType: None if volume does not exist or is not a tiered volume or - any other key errors. - """ - volinfo = get_volume_info(mnode, volname) - if volinfo is None: - g.log.error("Unable to get the volume info for volume %s", volname) - return None - - if not is_tiered_volume(mnode, volname): - g.log.error("Volume %s is not a tiered volume", volname) - return None - - cold_tier_type_info = { - 'coldBrickType': '', - 'coldreplicaCount': '', - 'coldarbiterCount': '', - 'colddisperseCount': '', - 'numberOfBricks': '' - } - for key in cold_tier_type_info.keys(): - if key in volinfo[volname]['bricks']['coldBricks']: - cold_tier_type_info[key] = (volinfo[volname]['bricks'] - ['coldBricks'][key]) - else: - g.log.error("Unable to find key '%s' in the volume info for the " - "volume %s", key, volname) - return None - - if 'Disperse' in cold_tier_type_info['coldBrickType']: - redundancy_count = (cold_tier_type_info['numberOfBricks']. - split("x", 1)[1].strip(). - split("=")[0].strip().strip("()").split()[2]) - cold_tier_type_info['coldredundancyCount'] = redundancy_count - - return cold_tier_type_info - - -def get_hot_tier_type_info(mnode, volname): - """Returns hot tier type information for the specified volume. - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - dict : Dict containing the keys, values defining the hot tier type: - Example: - hot_tier_type_info = { - 'hotBrickType': 'Distribute', - 'hotreplicaCount': '1' - } - NoneType: None if volume does not exist or is not a tiered volume or - any other key errors. - """ - volinfo = get_volume_info(mnode, volname) - if volinfo is None: - g.log.error("Unable to get the volume info for volume %s", volname) - return None - - if not is_tiered_volume(mnode, volname): - g.log.error("Volume %s is not a tiered volume", volname) - return None - - hot_tier_type_info = { - 'hotBrickType': '', - 'hotreplicaCount': '' - } - for key in hot_tier_type_info.keys(): - if key in volinfo[volname]['bricks']['hotBricks']: - hot_tier_type_info[key] = (volinfo[volname]['bricks']['hotBricks'] - [key]) + volume_type_info = {'volume_type_info': {}} + + all_volume_type_info = { + 'typeStr': '', + 'replicaCount': '', + 'arbiterCount': '', + 'stripeCount': '', + 'disperseCount': '', + 'redundancyCount': '' + } + for key in all_volume_type_info.keys(): + if key in volinfo[volname]: + all_volume_type_info[key] = volinfo[volname][key] else: - g.log.error("Unable to find key '%s' in the volume info for the " - "volume %s", key, volname) - return None + g.log.error("Unable to find key '%s' in the volume info for " + "the volume %s", key, volname) + all_volume_type_info[key] = None + volume_type_info['volume_type_info'] = all_volume_type_info - return hot_tier_type_info + return volume_type_info def get_num_of_bricks_per_subvol(mnode, volname): @@ -887,86 +689,21 @@ def get_num_of_bricks_per_subvol(mnode, volname): number of bricks per subvol Example: num_of_bricks_per_subvol = { - 'is_tier': False, - 'hot_tier_num_of_bricks_per_subvol': None, - 'cold_tier_num_of_bricks_per_subvol': None, 'volume_num_of_bricks_per_subvol': 2 } - num_of_bricks_per_subvol = { - 'is_tier': True, - 'hot_tier_num_of_bricks_per_subvol': 3, - 'cold_tier_num_of_bricks_per_subvol': 2, - 'volume_num_of_bricks_per_subvol': None - } - - NoneType: None if volume does not exist or is a tiered volume. + NoneType: None if volume does not exist. """ - bricks_per_subvol_dict = { - 'is_tier': False, - 'hot_tier_num_of_bricks_per_subvol': None, - 'cold_tier_num_of_bricks_per_subvol': None, - 'volume_num_of_bricks_per_subvol': None - } + bricks_per_subvol_dict = {'volume_num_of_bricks_per_subvol': None} subvols_dict = get_subvols(mnode, volname) if subvols_dict['volume_subvols']: bricks_per_subvol_dict['volume_num_of_bricks_per_subvol'] = ( len(subvols_dict['volume_subvols'][0])) - else: - if (subvols_dict['hot_tier_subvols'] and - subvols_dict['cold_tier_subvols']): - bricks_per_subvol_dict['is_tier'] = True - bricks_per_subvol_dict['hot_tier_num_of_bricks_per_subvol'] = ( - len(subvols_dict['hot_tier_subvols'][0])) - bricks_per_subvol_dict['cold_tier_num_of_bricks_per_subvol'] = ( - len(subvols_dict['cold_tier_subvols'][0])) return bricks_per_subvol_dict -def get_cold_tier_num_of_bricks_per_subvol(mnode, volname): - """Returns number of bricks per subvol in cold tier - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - int : Number of bricks per subvol on cold tier. - NoneType: None if volume does not exist or not a tiered volume. - """ - if not is_tiered_volume(mnode, volname): - g.log.error("Volume %s is not a tiered volume", volname) - return None - subvols_dict = get_subvols(mnode, volname) - if subvols_dict['cold_tier_subvols']: - return len(subvols_dict['cold_tier_subvols'][0]) - else: - return None - - -def get_hot_tier_num_of_bricks_per_subvol(mnode, volname): - """Returns number of bricks per subvol in hot tier - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - int : Number of bricks per subvol on hot tier. - NoneType: None if volume does not exist or not a tiered volume. - """ - if not is_tiered_volume(mnode, volname): - g.log.error("Volume %s is not a tiered volume", volname) - return None - subvols_dict = get_subvols(mnode, volname) - if subvols_dict['hot_tier_subvols']: - return len(subvols_dict['hot_tier_subvols'][0]) - else: - return None - - def get_replica_count(mnode, volname): """Get the replica count of the volume @@ -978,17 +715,8 @@ def get_replica_count(mnode, volname): dict : Dict contain keys, values defining Replica count of the volume. Example: replica_count_info = { - 'is_tier': False, - 'hot_tier_replica_count': None, - 'cold_tier_replica_count': None, 'volume_replica_count': 3 } - replica_count_info = { - 'is_tier': True, - 'hot_tier_replica_count': 2, - 'cold_tier_replica_count': 3, - 'volume_replica_count': None - } NoneType: None if it is parse failure. """ vol_type_info = get_volume_type_info(mnode, volname) @@ -997,69 +725,14 @@ def get_replica_count(mnode, volname): volname) return None - replica_count_info = { - 'is_tier': False, - 'hot_tier_replica_count': None, - 'cold_tier_replica_count': None, - 'volume_replica_count': None - } - - replica_count_info['is_tier'] = vol_type_info['is_tier'] - if replica_count_info['is_tier']: - replica_count_info['hot_tier_replica_count'] = ( - vol_type_info['hot_tier_type_info']['hotreplicaCount']) - replica_count_info['cold_tier_replica_count'] = ( - vol_type_info['cold_tier_type_info']['coldreplicaCount']) + replica_count_info = {'volume_replica_count': None} - else: - replica_count_info['volume_replica_count'] = ( - vol_type_info['volume_type_info']['replicaCount']) + replica_count_info['volume_replica_count'] = ( + vol_type_info['volume_type_info']['replicaCount']) return replica_count_info -def get_cold_tier_replica_count(mnode, volname): - """Get the replica count of cold tier. - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - int : Replica count of the cold tier. - NoneType: None if volume does not exist or not a tiered volume. - """ - is_tier = is_tiered_volume(mnode, volname) - if not is_tier: - return None - else: - volinfo = get_volume_info(mnode, volname) - cold_tier_replica_count = (volinfo[volname]["bricks"]['coldBricks'] - ['coldreplicaCount']) - return cold_tier_replica_count - - -def get_hot_tier_replica_count(mnode, volname): - """Get the replica count of hot tier. - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - int : Replica count of the hot tier. - NoneType: None if volume does not exist or not a tiered volume. - """ - is_tier = is_tiered_volume(mnode, volname) - if not is_tier: - return None - else: - volinfo = get_volume_info(mnode, volname) - hot_tier_replica_count = (volinfo[volname]["bricks"]['hotBricks'] - ['hotreplicaCount']) - return hot_tier_replica_count - - def get_disperse_count(mnode, volname): """Get the disperse count of the volume @@ -1071,15 +744,8 @@ def get_disperse_count(mnode, volname): dict : Dict contain keys, values defining Disperse count of the volume. Example: disperse_count_info = { - 'is_tier': False, - 'cold_tier_disperse_count': None, 'volume_disperse_count': 3 } - disperse_count_info = { - 'is_tier': True, - 'cold_tier_disperse_count': 3, - 'volume_disperse_count': None - } None: If it is non dispersed volume. """ vol_type_info = get_volume_type_info(mnode, volname) @@ -1088,45 +754,14 @@ def get_disperse_count(mnode, volname): volname) return None - disperse_count_info = { - 'is_tier': False, - 'cold_tier_disperse_count': None, - 'volume_disperse_count': None - } - - disperse_count_info['is_tier'] = vol_type_info['is_tier'] - if disperse_count_info['is_tier']: - disperse_count_info['cold_tier_disperse_count'] = ( - vol_type_info['cold_tier_type_info']['colddisperseCount']) + disperse_count_info = {'volume_disperse_count': None} - else: - disperse_count_info['volume_disperse_count'] = ( + disperse_count_info['volume_disperse_count'] = ( vol_type_info['volume_type_info']['disperseCount']) return disperse_count_info -def get_cold_tier_disperse_count(mnode, volname): - """Get the disperse count of cold tier. - - Args: - mnode (str): Node on which commands are executed. - volname (str): Name of the volume. - - Returns: - int : disperse count of the cold tier. - NoneType: None if volume does not exist or not a tiered volume. - """ - is_tier = is_tiered_volume(mnode, volname) - if not is_tier: - return None - else: - volinfo = get_volume_info(mnode, volname) - cold_tier_disperse_count = (volinfo[volname]["bricks"]['coldBricks'] - ['colddisperseCount']) - return cold_tier_disperse_count - - def enable_and_validate_volume_options(mnode, volname, volume_options_list, time_delay=5): """Enable the volume option and validate whether the option has be @@ -1173,7 +808,6 @@ def enable_and_validate_volume_options(mnode, volname, volume_options_list, def form_bricks_list_to_add_brick(mnode, volname, servers, all_servers_info, - add_to_hot_tier=False, **kwargs): """Forms list of bricks to add-bricks to the volume. @@ -1196,9 +830,6 @@ def form_bricks_list_to_add_brick(mnode, volname, servers, all_servers_info, } } Kwargs: - add_to_hot_tier (bool): True If bricks are to be added to hot_tier. - False otherwise. Defaults to False. - The keys, values in kwargs are: - replica_count : (int)|None. Increase the current_replica_count by replica_count @@ -1237,19 +868,8 @@ def form_bricks_list_to_add_brick(mnode, volname, servers, all_servers_info, bricks_per_subvol_dict = get_num_of_bricks_per_subvol(mnode, volname) # Get number of bricks to add. - if bricks_per_subvol_dict['is_tier']: - if add_to_hot_tier: - num_of_bricks_per_subvol = ( - bricks_per_subvol_dict['hot_tier_num_of_bricks_per_subvol'] - ) - else: - num_of_bricks_per_subvol = ( - bricks_per_subvol_dict - ['cold_tier_num_of_bricks_per_subvol'] - ) - else: - num_of_bricks_per_subvol = ( - bricks_per_subvol_dict['volume_num_of_bricks_per_subvol']) + num_of_bricks_per_subvol = ( + bricks_per_subvol_dict['volume_num_of_bricks_per_subvol']) if num_of_bricks_per_subvol is None: g.log.error("Number of bricks per subvol is None. " @@ -1265,15 +885,7 @@ def form_bricks_list_to_add_brick(mnode, volname, servers, all_servers_info, if replica_count: # Get Subvols subvols_info = get_subvols(mnode, volname) - - # Calculate number of bricks to add - if subvols_info['is_tier']: - if add_to_hot_tier: - num_of_subvols = len(subvols_info['hot_tier_subvols']) - else: - num_of_subvols = len(subvols_info['cold_tier_subvols']) - else: - num_of_subvols = len(subvols_info['volume_subvols']) + num_of_subvols = len(subvols_info['volume_subvols']) if num_of_subvols == 0: g.log.error("No Sub-Volumes available for the volume %s." @@ -1311,7 +923,7 @@ def form_bricks_list_to_add_brick(mnode, volname, servers, all_servers_info, def expand_volume(mnode, volname, servers, all_servers_info, force=False, - add_to_hot_tier=False, **kwargs): + **kwargs): """Forms list of bricks to add and adds those bricks to the volume. Args: @@ -1337,9 +949,6 @@ def expand_volume(mnode, volname, servers, all_servers_info, force=False, will get executed with force option. If it is set to False, then add-brick command will get executed without force option - add_to_hot_tier (bool): True If bricks are to be added to hot_tier. - False otherwise. Defaults to False. - **kwargs The keys, values in kwargs are: - replica_count : (int)|None. @@ -1351,11 +960,9 @@ def expand_volume(mnode, volname, servers, all_servers_info, force=False, bool: True of expanding volumes is successful. False otherwise. - NOTE: adding bricks to hot tier is yet to be added in this function. """ bricks_list = form_bricks_list_to_add_brick(mnode, volname, servers, - all_servers_info, - add_to_hot_tier, **kwargs) + all_servers_info, **kwargs) if not bricks_list: g.log.info("Unable to get bricks list to add-bricks. " @@ -1367,17 +974,8 @@ def expand_volume(mnode, volname, servers, all_servers_info, force=False, # Get replica count info. replica_count_info = get_replica_count(mnode, volname) - - if is_tiered_volume(mnode, volname): - if add_to_hot_tier: - current_replica_count = ( - int(replica_count_info['hot_tier_replica_count'])) - else: - current_replica_count = ( - int(replica_count_info['cold_tier_replica_count'])) - else: - current_replica_count = ( - int(replica_count_info['volume_replica_count'])) + current_replica_count = ( + int(replica_count_info['volume_replica_count'])) kwargs['replica_count'] = current_replica_count + replica_count @@ -1393,8 +991,7 @@ def expand_volume(mnode, volname, servers, all_servers_info, force=False, def form_bricks_list_to_remove_brick(mnode, volname, subvol_num=None, - replica_num=None, - remove_from_hot_tier=False, **kwargs): + replica_num=None, **kwargs): """Form bricks list for removing the bricks. Args: @@ -1411,9 +1008,6 @@ def form_bricks_list_to_remove_brick(mnode, volname, subvol_num=None, If replica_num = 0, then 1st brick from each subvolume is removed. the replica_num starts from 0. - remove_from_hot_tier (bool): True If bricks are to be removed from - hot_tier. False otherwise. Defaults to False. - **kwargs The keys, values in kwargs are: - replica_count : (int)|None. Specify the number of replicas @@ -1456,27 +1050,13 @@ def form_bricks_list_to_remove_brick(mnode, volname, subvol_num=None, is_arbiter = False # Calculate bricks to remove - if subvols_info['is_tier']: - if remove_from_hot_tier: - current_replica_count = ( - int(replica_count_info['hot_tier_replica_count'])) - subvols_list = subvols_info['hot_tier_subvols'] - else: - current_replica_count = ( - int(replica_count_info['cold_tier_replica_count'])) - subvols_list = subvols_info['cold_tier_subvols'] - arbiter_count = int(volume_type_info['cold_tier_type_info'] - ['coldarbiterCount']) - if arbiter_count == 1: - is_arbiter = True - else: - current_replica_count = ( - int(replica_count_info['volume_replica_count'])) - subvols_list = subvols_info['volume_subvols'] - arbiter_count = int(volume_type_info['volume_type_info'] - ['arbiterCount']) - if arbiter_count == 1: - is_arbiter = True + current_replica_count = ( + int(replica_count_info['volume_replica_count'])) + subvols_list = subvols_info['volume_subvols'] + arbiter_count = int(volume_type_info['volume_type_info'] + ['arbiterCount']) + if arbiter_count == 1: + is_arbiter = True # If replica_num is specified select the bricks of that replica number # from all the subvolumes. @@ -1522,14 +1102,7 @@ def form_bricks_list_to_remove_brick(mnode, volname, subvol_num=None, # remove bricks from sub-volumes if subvol_num is not None or 'distribute_count' in kwargs: - if subvols_info['is_tier']: - if remove_from_hot_tier: - subvols_list = subvols_info['hot_tier_subvols'] - else: - subvols_list = subvols_info['cold_tier_subvols'] - else: - subvols_list = subvols_info['volume_subvols'] - + subvols_list = subvols_info['volume_subvols'] if not subvols_list: g.log.error("No Sub-Volumes available for the volume %s", volname) return None @@ -1565,7 +1138,7 @@ def form_bricks_list_to_remove_brick(mnode, volname, subvol_num=None, def shrink_volume(mnode, volname, subvol_num=None, replica_num=None, force=False, rebalance_timeout=300, delete_bricks=True, - remove_from_hot_tier=False, **kwargs): + **kwargs): """Remove bricks from the volume. Args: @@ -1592,9 +1165,6 @@ def shrink_volume(mnode, volname, subvol_num=None, replica_num=None, delete_bricks (bool): After remove-brick delete the removed bricks. - remove_from_hot_tier (bool): True If bricks are to be removed from - hot_tier. False otherwise. Defaults to False. - **kwargs The keys, values in kwargs are: - replica_count : (int)|None. Specify the replica count to @@ -1605,12 +1175,10 @@ def shrink_volume(mnode, volname, subvol_num=None, replica_num=None, bool: True if removing bricks from the volume is successful. False otherwise. - NOTE: remove-bricks from hot-tier is yet to be added in this function. """ # Form bricks list to remove-bricks bricks_list_to_remove = form_bricks_list_to_remove_brick( - mnode, volname, subvol_num, replica_num, remove_from_hot_tier, - **kwargs) + mnode, volname, subvol_num, replica_num, **kwargs) if not bricks_list_to_remove: g.log.error("Failed to form bricks list to remove-brick. " @@ -1629,16 +1197,8 @@ def shrink_volume(mnode, volname, subvol_num=None, replica_num=None, # Get replica count info. replica_count_info = get_replica_count(mnode, volname) - if is_tiered_volume(mnode, volname): - if remove_from_hot_tier: - current_replica_count = ( - int(replica_count_info['hot_tier_replica_count'])) - else: - current_replica_count = ( - int(replica_count_info['cold_tier_replica_count'])) - else: - current_replica_count = ( - int(replica_count_info['volume_replica_count'])) + current_replica_count = ( + int(replica_count_info['volume_replica_count'])) kwargs['replica_count'] = current_replica_count - replica_count @@ -1756,8 +1316,7 @@ def shrink_volume(mnode, volname, subvol_num=None, replica_num=None, def form_bricks_to_replace_brick(mnode, volname, servers, all_servers_info, - src_brick=None, dst_brick=None, - replace_brick_from_hot_tier=False): + src_brick=None, dst_brick=None): """Get src_brick, dst_brick to replace brick Args: @@ -1784,9 +1343,6 @@ def form_bricks_to_replace_brick(mnode, volname, servers, all_servers_info, dst_brick (str): New brick to replace the faulty brick - replace_brick_from_hot_tier (bool): True If brick are to be - replaced from hot_tier. False otherwise. Defaults to False. - Returns: Tuple: (src_brick, dst_brick) Nonetype: if volume doesn't exists or any other failure. @@ -1812,13 +1368,7 @@ def form_bricks_to_replace_brick(mnode, volname, servers, all_servers_info, if not src_brick: # Randomly pick up a brick to bring the brick down and replace. - if subvols_info['is_tier']: - if replace_brick_from_hot_tier: - subvols_list = subvols_info['hot_tier_subvols'] - else: - subvols_list = subvols_info['cold_tier_subvols'] - else: - subvols_list = subvols_info['volume_subvols'] + subvols_list = subvols_info['volume_subvols'] src_brick = (random.choice(random.choice(subvols_list))) @@ -1827,8 +1377,7 @@ def form_bricks_to_replace_brick(mnode, volname, servers, all_servers_info, def replace_brick_from_volume(mnode, volname, servers, all_servers_info, src_brick=None, dst_brick=None, - delete_brick=True, - replace_brick_from_hot_tier=False): + delete_brick=True, multi_vol=False): """Replace faulty brick from the volume. Args: @@ -1857,8 +1406,9 @@ def replace_brick_from_volume(mnode, volname, servers, all_servers_info, delete_bricks (bool): After remove-brick delete the removed bricks. - replace_brick_from_hot_tier (bool): True If brick are to be - replaced from hot_tier. False otherwise. Defaults to False. + multi_vol (bool): True, If bricks need to created for multiple + volumes(more than 5) + False, Otherwise. By default, value is set to False. Returns: bool: True if replacing brick from the volume is successful. @@ -1876,10 +1426,17 @@ def replace_brick_from_volume(mnode, volname, servers, all_servers_info, subvols_info = get_subvols(mnode, volname) if not dst_brick: - dst_brick = form_bricks_list(mnode=mnode, volname=volname, - number_of_bricks=1, - servers=servers, - servers_info=all_servers_info) + if multi_vol: + dst_brick = form_bricks_for_multivol(mnode=mnode, + volname=volname, + number_of_bricks=1, + servers=servers, + servers_info=all_servers_info) + else: + dst_brick = form_bricks_list(mnode=mnode, volname=volname, + number_of_bricks=1, + servers=servers, + servers_info=all_servers_info) if not dst_brick: g.log.error("Failed to get a new brick to replace the faulty " "brick") @@ -1888,13 +1445,7 @@ def replace_brick_from_volume(mnode, volname, servers, all_servers_info, if not src_brick: # Randomly pick up a brick to bring the brick down and replace. - if subvols_info['is_tier']: - if replace_brick_from_hot_tier: - subvols_list = subvols_info['hot_tier_subvols'] - else: - subvols_list = subvols_info['cold_tier_subvols'] - else: - subvols_list = subvols_info['volume_subvols'] + subvols_list = subvols_info['volume_subvols'] src_brick = (random.choice(random.choice(subvols_list))) @@ -1959,17 +1510,6 @@ def get_client_quorum_info(mnode, volname): Returns: dict: client quorum information for the volume. client_quorum_dict = { - 'is_tier': False, - 'hot_tier_quorum_info':{ - 'is_quorum_applicable': False, - 'quorum_type': None, - 'quorum_count': None - }, - 'cold_tier_quorum_info':{ - 'is_quorum_applicable': False, - 'quorum_type': None, - 'quorum_count': None - }, 'volume_quorum_info':{ 'is_quorum_applicable': False, 'quorum_type': None, @@ -1979,17 +1519,6 @@ def get_client_quorum_info(mnode, volname): NoneType: None if volume does not exist. """ client_quorum_dict = { - 'is_tier': False, - 'hot_tier_quorum_info': { - 'is_quorum_applicable': False, - 'quorum_type': None, - 'quorum_count': None - }, - 'cold_tier_quorum_info': { - 'is_quorum_applicable': False, - 'quorum_type': None, - 'quorum_count': None - }, 'volume_quorum_info': { 'is_quorum_applicable': False, 'quorum_type': None, @@ -2015,111 +1544,37 @@ def get_client_quorum_info(mnode, volname): # Set the quorum info volume_type_info = get_volume_type_info(mnode, volname) - if volume_type_info['is_tier'] is True: - client_quorum_dict['is_tier'] = True - - # Hot Tier quorum info - hot_tier_type = volume_type_info['hot_tier_type_info']['hotBrickType'] - if (hot_tier_type == 'Replicate' or - hot_tier_type == 'Distributed-Replicate'): - - (client_quorum_dict['hot_tier_quorum_info'] - ['is_quorum_applicable']) = True - replica_count = (volume_type_info['hot_tier_type_info'] - ['hotreplicaCount']) - - # Case1: Replica 2 - if int(replica_count) == 2: - if 'none' not in quorum_type: - (client_quorum_dict['hot_tier_quorum_info'] - ['quorum_type']) = quorum_type - - if quorum_type == 'fixed': - if not quorum_count == '(null)': - (client_quorum_dict['hot_tier_quorum_info'] - ['quorum_count']) = quorum_count - - # Case2: Replica > 2 - if int(replica_count) > 2: - if quorum_type == 'none': - (client_quorum_dict['hot_tier_quorum_info'] - ['quorum_type']) = 'auto' - quorum_type == 'auto' - else: - (client_quorum_dict['hot_tier_quorum_info'] - ['quorum_type']) = quorum_type - if quorum_type == 'fixed': - if not quorum_count == '(null)': - (client_quorum_dict['hot_tier_quorum_info'] - ['quorum_count']) = quorum_count - - # Cold Tier quorum info - cold_tier_type = (volume_type_info['cold_tier_type_info'] - ['coldBrickType']) - if (cold_tier_type == 'Replicate' or - cold_tier_type == 'Distributed-Replicate'): - (client_quorum_dict['cold_tier_quorum_info'] - ['is_quorum_applicable']) = True - replica_count = (volume_type_info['cold_tier_type_info'] - ['coldreplicaCount']) - - # Case1: Replica 2 - if int(replica_count) == 2: - if 'none' not in quorum_type: - (client_quorum_dict['cold_tier_quorum_info'] - ['quorum_type']) = quorum_type - - if quorum_type == 'fixed': - if not quorum_count == '(null)': - (client_quorum_dict['cold_tier_quorum_info'] - ['quorum_count']) = quorum_count - - # Case2: Replica > 2 - if int(replica_count) > 2: - if quorum_type == 'none': - (client_quorum_dict['cold_tier_quorum_info'] - ['quorum_type']) = 'auto' - quorum_type == 'auto' - else: - (client_quorum_dict['cold_tier_quorum_info'] - ['quorum_type']) = quorum_type - if quorum_type == 'fixed': - if not quorum_count == '(null)': - (client_quorum_dict['cold_tier_quorum_info'] - ['quorum_count']) = quorum_count - else: - volume_type = (volume_type_info['volume_type_info']['typeStr']) - if (volume_type == 'Replicate' or - volume_type == 'Distributed-Replicate'): - (client_quorum_dict['volume_quorum_info'] - ['is_quorum_applicable']) = True - replica_count = (volume_type_info['volume_type_info'] - ['replicaCount']) - - # Case1: Replica 2 - if int(replica_count) == 2: - if 'none' not in quorum_type: - (client_quorum_dict['volume_quorum_info'] - ['quorum_type']) = quorum_type + volume_type = (volume_type_info['volume_type_info']['typeStr']) + if (volume_type == 'Replicate' or + volume_type == 'Distributed-Replicate'): + (client_quorum_dict['volume_quorum_info'] + ['is_quorum_applicable']) = True + replica_count = (volume_type_info['volume_type_info']['replicaCount']) + + # Case1: Replica 2 + if int(replica_count) == 2: + if 'none' not in quorum_type: + (client_quorum_dict['volume_quorum_info'] + ['quorum_type']) = quorum_type - if quorum_type == 'fixed': - if not quorum_count == '(null)': - (client_quorum_dict['volume_quorum_info'] - ['quorum_count']) = quorum_count - - # Case2: Replica > 2 - if int(replica_count) > 2: - if quorum_type == 'none': - (client_quorum_dict['volume_quorum_info'] - ['quorum_type']) = 'auto' - quorum_type == 'auto' - else: - (client_quorum_dict['volume_quorum_info'] - ['quorum_type']) = quorum_type if quorum_type == 'fixed': if not quorum_count == '(null)': (client_quorum_dict['volume_quorum_info'] - ['quorum_count']) = quorum_count + ['quorum_count']) = quorum_count + + # Case2: Replica > 2 + if int(replica_count) > 2: + if quorum_type == 'none': + (client_quorum_dict['volume_quorum_info'] + ['quorum_type']) = 'auto' + quorum_type == 'auto' + else: + (client_quorum_dict['volume_quorum_info'] + ['quorum_type']) = quorum_type + if quorum_type == 'fixed': + if not quorum_count == '(null)': + (client_quorum_dict['volume_quorum_info'] + ['quorum_count']) = quorum_count return client_quorum_dict @@ -2215,3 +1670,100 @@ def get_files_and_dirs_from_brick(brick_node, brick_path, brick_node, brick_path) result.extend(out.splitlines()) return result + + +def get_volume_type(brickdir_path): + """Checks for the type of volume under test. + + Args: + brickdir_path(str): The complete brick path. + (e.g., server1.example.com:/bricks/brick1/testvol_brick0/) + + Returns: + volume type(str): The volume type in str. + NoneType : None on failure + """ + # Adding import here to avoid cyclic imports + from glustolibs.gluster.brick_libs import get_all_bricks + (host, brick_path_info) = brickdir_path.split(':') + path_info = (brick_path_info[:-2] if brick_path_info.endswith("//") + else brick_path_info[:-1]) + for volume in get_volume_list(host): + brick_paths = [brick.split(':')[1] for brick in get_all_bricks(host, + volume)] + if path_info in brick_paths: + ret = get_volume_info(host, volume) + if ret is None: + g.log.error("Failed to get volume type for %s", volume) + return None + list_of_replica = ('Replicate', 'Distributed-Replicate') + if (ret[volume].get('typeStr') in list_of_replica and + int(ret[volume]['arbiterCount']) == 1): + if int(ret[volume]['distCount']) >= 2: + return 'Distributed-Arbiter' + else: + return 'Arbiter' + else: + return ret[volume].get('typeStr') + else: + g.log.info("Failed to find brick-path %s for volume %s", + brickdir_path, volume) + + +def parse_vol_file(mnode, vol_file): + """ Parses the .vol file and returns the content as a dict + Args: + mnode (str): Node on which commands will be executed. + vol_file(str) : Path to the .vol file + Returns: + (dict): Content of the .vol file + None : if failure happens + Example: + >>> ret = parse_vol_file("abc@xyz.com", + "/var/lib/glusterd/vols/testvol_distributed/ + trusted-testvol_distributed.tcp-fuse.vol") + {'testvol_distributed-client-0': {'type': 'protocol/client', + 'option': {'send-gids': 'true','transport.socket.keepalive-count': '9', + 'transport.socket.keepalive-interval': '2', + 'transport.socket.keepalive-time': '20', + 'transport.tcp-user-timeout': '0', + 'transport.socket.ssl-enabled': 'off', 'password': + 'bcc934b3-9e76-47fd-930c-c31ad9f6e2f0', 'username': + '23bb8f1c-b373-4f85-8bab-aaa77b4918ce', 'transport.address-family': + 'inet', 'transport-type': 'tcp', 'remote-subvolume': + '/gluster/bricks/brick1/testvol_distributed_brick0', + 'remote-host': 'xx.xx.xx.xx', 'ping-timeout': '42'}}} + """ + vol_dict, data, key = {}, {}, None + + def _create_dict_from_list(cur_dict, keys, value): + """Creates dynamic dictionary from a given list of keys and values""" + if len(keys) == 1: + cur_dict[keys[0]] = value + return + if keys[0] not in cur_dict: + cur_dict[keys[0]] = {} + _create_dict_from_list(cur_dict[keys[0]], keys[1:], value) + + ret, file_contents, err = g.run(mnode, "cat {}".format(vol_file)) + if ret: + g.log.error("Failed to read the .vol file : %s", err) + return None + if not file_contents: + g.log.error("The given .vol file is empty") + return None + for line in file_contents.split("\n"): + if line: + line = line.strip() + if line.startswith('end-volume'): + vol_dict[key] = data + data = {} + elif line.startswith("volume "): + key = line.split(" ")[-1] + elif line.startswith("subvolumes "): + key_list = line.split(" ")[0] + _create_dict_from_list(data, [key_list], line.split(" ")[1:]) + else: + key_list = line.split(" ")[:-1] + _create_dict_from_list(data, key_list, line.split(" ")[-1]) + return vol_dict diff --git a/glustolibs-gluster/glustolibs/gluster/volume_ops.py b/glustolibs-gluster/glustolibs/gluster/volume_ops.py index 9bc86498c..d25a9349b 100644 --- a/glustolibs-gluster/glustolibs/gluster/volume_ops.py +++ b/glustolibs-gluster/glustolibs/gluster/volume_ops.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2015-2016 Red Hat, Inc. <http://www.redhat.com> +# Copyright (C) 2015-2020 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 @@ -20,6 +20,11 @@ import re import copy from glusto.core import Glusto as g from pprint import pformat +import io +try: + import ConfigParser as configparser # Python 2 +except ImportError: + import configparser as configparser # Python 3 try: import xml.etree.cElementTree as etree except ImportError: @@ -233,15 +238,8 @@ def volume_delete(mnode, volname, xfail=False): ) return False - if volinfo[volname]['typeStr'] == 'Tier': - tmp_hot_brick = volinfo[volname]["bricks"]["hotBricks"]["brick"] - hot_bricks = [x["name"] for x in tmp_hot_brick if "name" in x] - tmp_cold_brick = volinfo[volname]["bricks"]["coldBricks"]["brick"] - cold_bricks = [x["name"] for x in tmp_cold_brick if "name" in x] - bricks = hot_bricks + cold_bricks - else: - bricks = [x["name"] for x in volinfo[volname]["bricks"]["brick"] - if "name" in x] + bricks = [x["name"] for x in volinfo[volname]["bricks"]["brick"] if + "name" in x] ret, out, err = g.run(mnode, "gluster volume delete {} --mode=script" .format(volname)) if ret != 0: @@ -263,7 +261,7 @@ def volume_delete(mnode, volname, xfail=False): ret, out, err = g.run(node, "rm -rf %s" % vol_dir) if ret != 0: if not xfail: - g.log.err( + g.log.error( "Unexpected: rm -rf {} failed ({}: {})" .format(vol_dir, out, err) ) @@ -387,27 +385,34 @@ def get_volume_status(mnode, volname='all', service='', options=''): NoneType: on failure Example: - get_volume_status("10.70.47.89", volname="testvol") - >>>{'testvol': {'10.70.47.89': {'/bricks/brick1/a11': {'status': '1', - 'pid': '28963', 'bricktype': 'cold', 'port': '49163', 'peerid': - '7fc9015e-8134-4753-b837-54cbc6030c98', 'ports': {'rdma': 'N/A', - 'tcp': '49163'}}, '/bricks/brick2/a31': {'status': '1', 'pid': - '28982', 'bricktype': 'cold', 'port': '49164', 'peerid': - '7fc9015e-8134-4753-b837-54cbc6030c98', 'ports': {'rdma': 'N/A', - 'tcp': '49164'}}, 'NFS Server': {'status': '1', 'pid': '30525', - 'port': '2049', 'peerid': '7fc9015e-8134-4753-b837-54cbc6030c98', - 'ports': {'rdma': 'N/A', 'tcp': '2049'}}, '/bricks/brick1/a12': - {'status': '1', 'pid': '30505', 'bricktype': 'hot', 'port': '49165', - 'peerid': '7fc9015e-8134-4753-b837-54cbc6030c98', 'ports': {'rdma': - 'N/A', 'tcp': '49165'}}}, '10.70.47.118': {'/bricks/brick1/a21': - {'status': '1', 'pid': '5427', 'bricktype': 'cold', 'port': '49162', - 'peerid': '5397d8f5-2986-453a-b0b5-5c40a9bb87ff', 'ports': {'rdma': - 'N/A', 'tcp': '49162'}}, '/bricks/brick2/a41': {'status': '1', 'pid': - '5446', 'bricktype': 'cold', 'port': '49163', 'peerid': - '5397d8f5-2986-453a-b0b5-5c40a9bb87ff', 'ports': {'rdma': 'N/A', - 'tcp': '49163'}}, 'NFS Server': {'status': '1', 'pid': '6397', 'port': - '2049', 'peerid': '5397d8f5-2986-453a-b0b5-5c40a9bb87ff', 'ports': - {'rdma': 'N/A', 'tcp': '2049'}}}}} + get_volume_status(host1, volname="testvol_replicated") + >>>{'testvol_replicated': {'host1': {'Self-heal Daemon': {'status': + '1', 'pid': '2479', 'port': 'N/A', 'peerid': + 'b7a02af9-eea4-4657-8b86-3b21ec302f48', 'ports': {'rdma': 'N/A', + 'tcp': 'N/A'}}, '/bricks/brick4/testvol_replicated_brick2': {'status': + '1', 'pid': '2468', 'bricktype': 'None', 'port': '49160', 'peerid': + 'b7a02af9-eea4-4657-8b86-3b21ec302f48', 'ports': {'rdma': 'N/A', + 'tcp': '49160'}}}, 'host2': {'Self-heal Daemon': {'status': '1', + 'pid': '2513', 'port': 'N/A', 'peerid': + '7f6fb9ed-3e0b-4f27-89b3-9e4f836c2332', 'ports': {'rdma': 'N/A', + 'tcp': 'N/A'}}, '/bricks/brick4/testvol_replicated_brick1': {'status': + '1', 'pid': '2456', 'bricktype': 'None', 'port': '49160', 'peerid': + '7f6fb9ed-3e0b-4f27-89b3-9e4f836c2332', 'ports': {'rdma': 'N/A', + 'tcp': '49160'}}}, 'host3': {'Self-heal Daemon': {'status': '1', 'pid' + : '2515', 'port': 'N/A', 'peerid': + '6172cfab-9d72-43b5-ba6f-612e5cfc020c', 'ports': {'rdma': 'N/A', + 'tcp': 'N/A'}}}, 'host4': {'Self-heal Daemon': {'status': '1', 'pid': + '2445', 'port': 'N/A', 'peerid': 'c16a1660-ee73-4e0f-b9c7-d2e830e39539 + ', 'ports': {'rdma': 'N/A', 'tcp': 'N/A'}}}, 'host5': + {'Self-heal Daemon': {'status': '1', 'pid': '2536', 'port': 'N/A', + 'peerid': '79ea9f52-88f0-4293-ae21-8ea13f44b58d', 'ports': + {'rdma': 'N/A', 'tcp': 'N/A'}}}, 'host6': {'Self-heal Daemon': + {'status': '1', 'pid': '2526', 'port': 'N/A', 'peerid': + 'c00a3c5e-668f-440b-860c-da43e999737b', 'ports': {'rdma': 'N/A', + 'tcp': 'N/A'}}, '/bricks/brick4/testvol_replicated_brick0': {'status': + '1', 'pid': '2503', 'bricktype': 'None', 'port': '49160', 'peerid': + 'c00a3c5e-668f-440b-860c-da43e999737b', 'ports': {'rdma': 'N/A', + 'tcp': '49160'}}}}} """ cmd = "gluster vol status %s %s %s --xml" % (volname, service, options) @@ -428,8 +433,6 @@ def get_volume_status(mnode, volname='all', service='', options=''): for volume in volume_list: tmp_dict1 = {} tmp_dict2 = {} - hot_bricks = [] - cold_bricks = [] vol_name = [vol.text for vol in volume if vol.tag == "volName"] # parsing volume status xml output @@ -449,24 +452,7 @@ def get_volume_status(mnode, volname='all', service='', options=''): elem_tag = [] for elem in volume.getchildren(): elem_tag.append(elem.tag) - if ('hotBricks' in elem_tag) or ('coldBricks' in elem_tag): - for elem in volume.getchildren(): - if (elem.tag == 'hotBricks'): - nodes = elem.findall("node") - hot_bricks = [node.find('path').text - for node in nodes - if ( - node.find('path').text.startswith('/'))] - if (elem.tag == 'coldBricks'): - for n in elem.findall("node"): - nodes.append(n) - cold_bricks = [node.find('path').text - for node in nodes - if ( - (node.find('path'). - text.startswith('/')))] - else: - nodes = volume.findall("node") + nodes = volume.findall("node") for each_node in nodes: if each_node.find('path').text.startswith('/'): @@ -479,12 +465,7 @@ def get_volume_status(mnode, volname='all', service='', options=''): tmp_dict3 = {} if "hostname" in node_dict.keys(): if node_dict['path'].startswith('/'): - if node_dict['path'] in hot_bricks: - node_dict["bricktype"] = 'hot' - elif node_dict['path'] in cold_bricks: - node_dict["bricktype"] = 'cold' - else: - node_dict["bricktype"] = 'None' + node_dict["bricktype"] = 'None' tmp = node_dict["path"] tmp_dict3[node_dict["path"]] = node_dict else: @@ -673,29 +654,42 @@ def get_volume_info(mnode, volname='all', xfail=False): dict: volume info in dict of dicts Example: - get_volume_info("abc.com", volname="testvol") - >>>{'testvol': {'status': '1', 'xlators': None, 'disperseCount': '0', - 'bricks': {'coldBricks': {'colddisperseCount': '0', - 'coldarbiterCount': '0', 'coldBrickType': 'Distribute', - 'coldbrickCount': '4', 'numberOfBricks': '4', 'brick': - [{'isArbiter': '0', 'name': '10.70.47.89:/bricks/brick1/a11', - 'hostUuid': '7fc9015e-8134-4753-b837-54cbc6030c98'}, {'isArbiter': - '0', 'name': '10.70.47.118:/bricks/brick1/a21', 'hostUuid': - '7fc9015e-8134-4753-b837-54cbc6030c98'}, {'isArbiter': '0', 'name': - '10.70.47.89:/bricks/brick2/a31', 'hostUuid': - '7fc9015e-8134-4753-b837-54cbc6030c98'}, {'isArbiter': '0', - 'name': '10.70.47.118:/bricks/brick2/a41', 'hostUuid': - '7fc9015e-8134-4753-b837-54cbc6030c98'}], 'coldreplicaCount': '1'}, - 'hotBricks': {'hotBrickType': 'Distribute', 'numberOfBricks': '1', - 'brick': [{'name': '10.70.47.89:/bricks/brick1/a12', 'hostUuid': - '7fc9015e-8134-4753-b837-54cbc6030c98'}], 'hotbrickCount': '1', - 'hotreplicaCount': '1'}}, 'type': '5', 'distCount': '1', - 'replicaCount': '1', 'brickCount': '5', 'options': - {'cluster.tier-mode': 'cache', 'performance.readdir-ahead': 'on', - 'features.ctr-enabled': 'on'}, 'redundancyCount': '0', 'transport': - '0', 'typeStr': 'Tier', 'stripeCount': '1', 'arbiterCount': '0', - 'id': 'ffa8a8d1-546f-4ebf-8e82-fcc96c7e4e05', 'statusStr': 'Started', - 'optCount': '3'}} + get_volume_info("host1", volname="testvol") + >>>{'testvol': {'status': '1', 'disperseCount': '6', + 'bricks': {'brick': [{'isArbiter': '0', 'name': + 'host1:/bricks/brick6/testvol_brick0', 'hostUuid': + 'c00a3c5e-668f-440b-860c-da43e999737b'}, {'isArbiter': '0', 'name': + 'host2:/bricks/brick6/testvol_brick1', 'hostUuid': + '7f6fb9ed-3e0b-4f27-89b3-9e4f836c2332'}, {'isArbiter': '0', 'name': + 'host3:/bricks/brick6/testvol_brick2', 'hostUuid': + 'b7a02af9-eea4-4657-8b86-3b21ec302f48'}, {'isArbiter': '0', 'name': + 'host4:/bricks/brick4/testvol_brick3', 'hostUuid': + '79ea9f52-88f0-4293-ae21-8ea13f44b58d'}, {'isArbiter': '0', 'name': + 'host5:/bricks/brick2/testvol_brick4', 'hostUuid': + 'c16a1660-ee73-4e0f-b9c7-d2e830e39539'}, {'isArbiter': '0', 'name': + 'host6:/bricks/brick2/testvol_brick5', 'hostUuid': + '6172cfab-9d72-43b5-ba6f-612e5cfc020c'}, {'isArbiter': '0', 'name': + 'host1:/bricks/brick7/testvol_brick6', 'hostUuid': + 'c00a3c5e-668f-440b-860c-da43e999737b'}, {'isArbiter': '0', 'name': + 'host2:/bricks/brick7/testvol_brick7', 'hostUuid': + '7f6fb9ed-3e0b-4f27-89b3-9e4f836c2332'}, {'isArbiter': '0', 'name': + 'host3:/bricks/brick7/testvol_brick8', 'hostUuid': + 'b7a02af9-eea4-4657-8b86-3b21ec302f48'}, {'isArbiter': '0', 'name': + 'host4:/bricks/brick5/testvol_brick9', 'hostUuid': + '79ea9f52-88f0-4293-ae21-8ea13f44b58d'}, {'isArbiter': '0', 'name': + 'host5:/bricks/brick4/testvol_brick10', 'hostUuid': + 'c16a1660-ee73-4e0f-b9c7-d2e830e39539'}, {'isArbiter': '0', 'name': + 'host6:/bricks/brick4/testvol_brick11', 'hostUuid': + '6172cfab-9d72-43b5-ba6f-612e5cfc020c'}]}, + 'type': '9', 'distCount': '2', 'replicaCount': '1', 'brickCount': + '12', 'options': {'nfs.disable': 'on', 'cluster.server-quorum-ratio': + '90%', 'storage.fips-mode-rchecksum': 'on', + 'transport.address-family': 'inet', 'cluster.brick-multiplex': + 'disable'}, 'redundancyCount': '2', 'snapshotCount': '0', + 'transport': '0', 'typeStr': 'Distributed-Disperse', 'stripeCount': + '1', 'arbiterCount': '0', + 'id': '8d217fa3-094b-4293-89b5-41d447c06d22', 'statusStr': 'Started', + 'optCount': '5'}} """ cmd = "gluster volume info %s --xml" % volname @@ -727,18 +721,6 @@ def get_volume_info(mnode, volname='all', xfail=False): (volinfo[volname]["bricks"]["brick"]. append(brick_info_dict)) - if el.tag == "hotBricks" or el.tag == "coldBricks": - volinfo[volname]["bricks"][el.tag] = {} - volinfo[volname]["bricks"][el.tag]["brick"] = [] - for elmt in el.getchildren(): - if elmt.tag == 'brick': - brick_info_dict = {} - for el_brk in elmt.getchildren(): - brick_info_dict[el_brk.tag] = el_brk.text - (volinfo[volname]["bricks"][el.tag]["brick"]. - append(brick_info_dict)) - else: - volinfo[volname]["bricks"][el.tag][elmt.tag] = elmt.text # noqa: E501 elif elem.tag == "options": volinfo[volname]["options"] = {} for option in elem.findall("option"): @@ -840,3 +822,76 @@ def get_volume_list(mnode): vol_list.append(elem.text) return vol_list + + +def get_gluster_state(mnode): + """Executes the 'gluster get-state' command on the specified node, checks + for the data dump, reads the glusterd state dump and returns it. + + Args: + mnode (str): Node on which command has to be executed + + Returns: + dict: The output of gluster get-state command in dict format + + Example: + >>>get_gluster_state(self.mnode) + {'Global': {'myuuid': 'e92964c8-a7d2-4e59-81ac-feb0687df55e', + 'op-version': '70000'}, 'Global options': {}, 'Peers': + {'peer1.primary_hostname': 'dhcp43-167.lab.eng.blr.redhat.com', + 'peer1.uuid': 'd3a85b6a-134f-4df2-ba93-4bd0321b6d6a', 'peer1.state': + 'Peer in Cluster', 'peer1.connected': 'Connected', + 'peer1.othernames': '', 'peer2.primary_hostname': + 'dhcp43-68.lab.eng.blr.redhat.com', 'peer2.uuid': + 'f488aa35-bc56-4aea-9581-8db54e137937', 'peer2.state': + 'Peer in Cluster', 'peer2.connected': 'Connected', + 'peer2.othernames': '', 'peer3.primary_hostname': + 'dhcp43-64.lab.eng.blr.redhat.com', 'peer3.uuid': + 'dfe75b01-2988-4eac-879a-cf3d701e1382', 'peer3.state': + 'Peer in Cluster', 'peer3.connected': 'Connected', + 'peer3.othernames': '', 'peer4.primary_hostname': + 'dhcp42-147.lab.eng.blr.redhat.com', 'peer4.uuid': + '05e3858b-33bf-449a-b170-2d3dac9adc45', 'peer4.state': + 'Peer in Cluster', 'peer4.connected': 'Connected', + 'peer4.othernames': '', 'peer5.primary_hostname': + 'dhcp41-246.lab.eng.blr.redhat.com', 'peer5.uuid': + 'c2e3f833-98fa-42d9-ae63-2bc471515810', 'peer5.state': + 'Peer in Cluster', 'peer5.connected': 'Connected', + 'peer5.othernames': ''}, 'Volumes': {}, 'Services': {'svc1.name': + 'glustershd', 'svc1.online_status': 'Offline', 'svc2.name': 'nfs', + 'svc2.online_status': 'Offline', 'svc3.name': 'bitd', + 'svc3.online_status': 'Offline', 'svc4.name': 'scrub', + 'svc4.online_status': 'Offline', 'svc5.name': 'quotad', + 'svc5.online_status': 'Offline'}, 'Misc': {'base port': '49152', + 'last allocated port': '49154'}} + """ + + ret, out, _ = g.run(mnode, "gluster get-state") + if ret: + g.log.error("Failed to execute gluster get-state command!") + return None + # get-state should dump properly. + # Checking whether a path is returned or not and then + # extracting path from the out data + + path = re.search(r"/.*?/.\S*", out).group() + if not path: + g.log.error("Failed to get the gluster state dump file path.") + return None + ret, out, _ = g.run(mnode, "cat {}".format(path)) + if ret: + g.log.error("Failed to read the gluster state dump.") + return None + g.log.info("Command Executed successfully and the data dump verified") + + # Converting the string to unicode for py2/3 compatibility + out = u"".join(out) + data_buf = io.StringIO(out) + config = configparser.ConfigParser() + try: + config.read_file(data_buf) # Python3 + except AttributeError: + config.readfp(data_buf) # Python2 + # Converts the config parser object to a dictionary and returns it + return {section: dict(config.items(section)) for section in + config.sections()} diff --git a/glustolibs-gluster/scripts/compute_hash.py b/glustolibs-gluster/scripts/compute_hash.py new file mode 100644 index 000000000..7cab7c494 --- /dev/null +++ b/glustolibs-gluster/scripts/compute_hash.py @@ -0,0 +1,32 @@ +# Copyright (C) 2020 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 __future__ import print_function +import ctypes +import sys + +filename = sys.argv[1] +glusterfs = ctypes.cdll.LoadLibrary("libglusterfs.so.0") + +# In case of python3 encode string to ascii +if sys.version_info.major == 3: + computed_hash = ctypes.c_uint32(glusterfs.gf_dm_hashfn( + filename.encode('ascii'), len(filename))) +else: + computed_hash = ctypes.c_uint32(glusterfs.gf_dm_hashfn( + filename, len(filename))) + +print(computed_hash.value) diff --git a/glustolibs-gluster/scripts/walk_dir.py b/glustolibs-gluster/scripts/walk_dir.py new file mode 100644 index 000000000..02d115b0b --- /dev/null +++ b/glustolibs-gluster/scripts/walk_dir.py @@ -0,0 +1,26 @@ +# Copyright (C) 2020 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 __future__ import print_function +import os +import sys + +rootdir = sys.argv[1] + +list_of_levels = [] +for level in os.walk(rootdir): + list_of_levels.append(level) +print(list_of_levels) diff --git a/glustolibs-gluster/setup.py b/glustolibs-gluster/setup.py index b3f9cb09c..05e59fde6 100644 --- a/glustolibs-gluster/setup.py +++ b/glustolibs-gluster/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2016-2019 Red Hat, Inc. +# Copyright (c) 2016-2020 Red Hat, Inc. # # 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 @@ -49,6 +49,8 @@ setup( ) try: - dir_util.copy_tree('./gdeploy_configs', '/usr/share/glustolibs/gdeploy_configs') + for srcdir, destdir in (('./gdeploy_configs', '/usr/share/glustolibs/gdeploy_configs'), + ('./scripts', '/usr/share/glustolibs/scripts/')): + dir_util.copy_tree(srcdir, destdir) except: pass |