From bf53ceb0b352c453c3b7d86239bd74cef051abae Mon Sep 17 00:00:00 2001 From: Akarsha Rai Date: Fri, 21 Dec 2018 14:35:33 +0530 Subject: Modules to perform volume operations Change-Id: I42f652f9b983d33a90fa86fb6576e57067f1a7e1 Signed-off-by: Akarsha Rai --- .../glustolibs/gluster/volume_libs.py | 564 +++++++++++++++++++++ 1 file changed, 564 insertions(+) create mode 100644 glustolibs-gluster-gd2/glustolibs/gluster/volume_libs.py diff --git a/glustolibs-gluster-gd2/glustolibs/gluster/volume_libs.py b/glustolibs-gluster-gd2/glustolibs/gluster/volume_libs.py new file mode 100644 index 0000000..24acba8 --- /dev/null +++ b/glustolibs-gluster-gd2/glustolibs/gluster/volume_libs.py @@ -0,0 +1,564 @@ +# Copyright (C) 2018 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 +# 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 gluster volume related helper functions. """ + +import time +from glusto.core import Glusto as g +from glustolibs.gluster.lib_utils import form_bricks_list, to_list +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) + + +def volume_exists(mnode, volname): + """Check if volume already exists + Args: + mnode (str): Node on which commands has to be executed + volname (str): Name of the volume. + Returns: + bool : True if volume exists. False Otherwise + """ + volinfo = get_volume_info(mnode, volname) + if volinfo: + g.log.info("Volume %s exists", volname) + return True + + g.log.error("Volume %s doesnot exist", volname) + return False + + +def setup_volume(mnode, all_servers_info, volume_config, force=True): + """Setup Volume with the configuration defined in volume_config + Args: + mnode (str): Node on which commands has to be executed + all_servers_info (dict): Information about all servers. + example : + all_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'] + } + } + volume_config (dict): Dict containing volume information + 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}, + } + Returns: + bool : True on successful setup. False Otherwise + """ + dist_count = replica_count = arbiter_count = 0 + # Checking for missing parameters + for param in ['name', 'servers', 'voltype']: + if param not in volume_config: + g.log.error("Unable to get %s info from config", param) + return False + + # Get volume name + volname = volume_config['name'] + + # Check if the volume already exists + volume = volume_exists(mnode, volname) + if not volume: + g.log.info("Volume %s does not exist in %s", volname, mnode) + return True + + # Get servers + servers = volume_config['servers'] + + # Get the volume type and values + volume_type = volume_config['voltype']['type'] + if not volume_type: + g.log.error("Volume type is not defined in the config") + return False + + number_of_bricks = 1 + if volume_type == 'distributed': + if 'dist_count' in volume_config['voltype']: + dist_count = (volume_config['voltype']['dist_count']) + else: + g.log.error("Distribute count not specified in the volume config") + return False + + number_of_bricks = dist_count + + elif volume_type == 'replicated': + if 'replica_count' in volume_config['voltype']: + replica_count = (volume_config['voltype']['replica_count']) + else: + g.log.error("Replica count not specified in the volume config") + return False + + if 'arbiter_count' in volume_config['voltype']: + arbiter_count = (volume_config['voltype']['arbiter_count']) + + number_of_bricks = replica_count + arbiter_count + + elif volume_type == 'distributed-replicated': + if 'dist_count' in volume_config['voltype']: + dist_count = (volume_config['voltype']['dist_count']) + else: + g.log.error("Distribute count not specified in the volume config") + return False + + if 'replica_count' in volume_config['voltype']: + replica_count = (volume_config['voltype']['replica_count']) + else: + g.log.error("Replica count not specified in the volume config") + return False + + if 'arbiter_count' in volume_config['voltype']: + arbiter_count = (volume_config['voltype']['arbiter_count']) + + number_of_bricks = dist_count * (replica_count + arbiter_count) + + # 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 not bricks_list: + g.log.error("Number_of_bricks is greater than the unused bricks on " + "servers") + return False + + # Create volume + g.log.info("Force %s", force) + ret, out, err = volume_create(mnode=mnode, volname=volname, + bricks_list=bricks_list, force=force, + replica_count=replica_count, + arbiter_count=arbiter_count) + g.log.info("ret %s out %s err %s", ret, out, err) + if ret: + g.log.error("Unable to create volume %s", volname) + return False + + # Start Volume + ret, _, _ = volume_start(mnode, volname) + if ret: + g.log.error("volume start %s failed", 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 not ret: + g.log.error("Unable to set few volume options") + return False + return True + + +def cleanup_volume(mnode, volname): + """deletes snapshots in the volume, stops and deletes the gluster + volume if given volume exists in gluster and deletes the + directories in the bricks associated with the given volume + Args: + volname (str): volume name + mnode (str): Node on which cmd has to be executed. + Returns: + bool: True, if volume is deleted successfully + False, otherwise + Example: + cleanup_volume("", "testvol") + """ + volume = volume_exists(mnode, volname) + if not volume: + g.log.info("Volume %s does not exist in %s", volname, mnode) + return True + + ret, _, _ = g.run(mnode, "glustercli snapshot delete all %s" % volname) + if ret: + g.log.error("Failed to delete the snapshots in volume %s", volname) + return False + + ret, _, _ = volume_stop(mnode, volname) + if ret: + g.log.error("Failed to stop volume %s" % volname) + return False + + ret = volume_delete(mnode, volname) + if not ret: + g.log.error("Unable to cleanup the volume %s", volname) + return False + + return True + + +def log_volume_info_and_status(mnode, volname): + """Logs volume info and status + Args: + mnode (str): Node on which cmd has to be executed. + volname (str): volume name + Returns: + bool: Returns True if getting volume info and status is successful. + False Otherwise. + """ + ret, _, _ = volume_info(mnode, volname) + if ret: + g.log.error("Failed to get volume info %s", volname) + return False + + ret, _, _ = volume_status(mnode, volname) + if ret: + g.log.error("Failed to get volume status %s", volname) + return False + + return True + + +def verify_all_process_of_volume_are_online(mnode, volname): + """Verifies whether all the processes of volume are online + Args: + mnode (str): Node on which cmd has to be executed. + volname (str): volume name + Returns: + bool: Returns True if all the processes of volume are online. + False Otherwise. + """ + # Importing here to avoid cyclic imports + from glustolibs.gluster.brick_libs import are_bricks_online, get_all_bricks + # Verify all the brick process are online + bricks_list = get_all_bricks(mnode, volname) + if not bricks_list: + g.log.error("Failed to get the brick list " + "from the volume %s", volname) + return False + + ret = are_bricks_online(mnode, volname, bricks_list) + if not ret: + g.log.error("All bricks are not online of " + "the volume %s", volname) + return False + + # ToDO: Verify all self-heal-daemons are running for non-distribute volumes + + return True + + +def get_subvols(mnode, volname): + """Gets the subvolumes in the given volume + Args: + volname (str): volume name + mnode (str): Node on which cmd has to be executed. + Returns: + dict: with empty list values for all keys, if volume doesn't exist + dict: Dictionary of subvols, value of each key is list of lists + containing subvols + Example: + get_subvols("abc.xyz.com", "testvol") + """ + + bricks_path = [] + subvols = {'volume_subvols':[]} + volinfo = get_volume_info(mnode, volname) + if volinfo: + subvol_info = volinfo['subvols'] + for subvol in subvol_info: + path = [] + for brick in subvol['bricks']: + path1 = ':'.join([brick['host'], brick['path']]) + path.append(path1) + bricks_path.append(path) + subvols['volume_subvols'] = bricks_path + return subvols + + +def is_distribute_volume(mnode, volname): + """Check if volume is a plain distributed volume + Args: + mnode (str): Node on which commands are executed. + volname (str): Name of the volume. + Returns: + bool : True if the volume is distributed volume. False otherwise + NoneType: None if volume does not exist. + """ + volume_type_info = get_volume_type_info(mnode, volname) + if not volume_type_info: + g.log.error("Unable to check if the volume %s is distribute", volname) + return False + + return bool(volume_type_info['type'] == 'Distribute') + + +def get_volume_type_info(mnode, volname): + """Returns volume 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 volume type: + NoneType: None if volume does not exist or any other key errors. + """ + volinfo = get_volume_info(mnode, volname) + if not volinfo: + g.log.error("Unable to get the volume info for volume %s", volname) + return None + + volume_type_info = { + 'type': '', + 'replica-count': '', + 'arbiter-count': '', + 'distribute-count': '' + } + for key in volume_type_info.keys(): + if key in volinfo: + volume_type_info[key] = volinfo[key] + else: + volume_type_info[key] = None + + return volume_type_info + + +def get_num_of_bricks_per_subvol(mnode, volname): + """Returns number of bricks per subvol + Args: + mnode (str): Node on which commands are executed. + volname (str): Name of the volume. + Returns: + dict : Dict containing the keys, values defining + number of bricks per subvol + NoneType: None if volume does not exist. + """ + 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])) + + return bricks_per_subvol_dict + + +def get_replica_count(mnode, volname): + """Get the replica count of the volume + Args: + mnode (str): Node on which commands are executed. + volname (str): Name of the volume. + Returns: + dict : Dict contain keys, values defining Replica count of the volume. + NoneType: None if it is parse failure. + """ + vol_type_info = get_volume_type_info(mnode, volname) + if not vol_type_info: + g.log.error("Unable to get the replica count info for the volume %s", + volname) + return None + + return vol_type_info['replica-count'] + + +def enable_and_validate_volume_options(mnode, volname, volume_options_list, + time_delay=1): + """Enable the volume option and validate whether the option has be + successfully enabled or not + Args: + mnode (str): Node on which commands are executed. + volname (str): Name of the volume. + volume_options_list (str|list): A volume option|List of volume options + to be enabled + time_delay (int): Time delay between 2 volume set operations + Returns: + bool: True when enabling and validating all volume options is + successful. False otherwise + """ + + volume_options_list = to_list(volume_options_list) + + for option in volume_options_list: + # Set volume option to 'enable' + g.log.info("Setting the volume option : %s", ) + ret = set_volume_options(mnode, volname, {option: "on"}) + if not ret: + return False + + # Validate whether the option is set on the volume + g.log.info("Validating the volume option : %s to be set to 'enable'", + option) + option_dict = get_volume_options(mnode, volname, option) + g.log.info("Options Dict: %s", option_dict) + if not option_dict: + g.log.error("%s is not enabled on the volume %s", option, volname) + return False + + if option not in option_dict['name'] or "on" not in option_dict['value']: + g.log.error("%s is not enabled on the volume %s", option, volname) + return False + + g.log.info("%s is enabled on the volume %s", option, volname) + time.sleep(time_delay) + + return True + + +def form_bricks_list_to_add_brick(mnode, volname, servers, all_servers_info, + replica_count=None, + distribute_count=None): + """Forms list of bricks to add-bricks to the volume. + Args: + mnode (str): Node on which commands has to be executed + volname (str): volume name + servers (list): List of servers in the storage pool. + all_servers_info (dict): Information about all servers. + example : + all_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'] + } + } + Kwargs: + - replica_count : (int)|None. + Increase the current_replica_count by replica_count + - distribute_count: (int)|None. + Increase the current_distribute_count by distribute_count + Returns: + list: List of bricks to add if there are enough bricks to add on + the servers. + nonetype: None if there are not enough bricks to add on the servers or + volume doesn't exists or any other failure. + """ + + # Check if volume exists + if not volume_exists(mnode, volname): + g.log.error("Volume %s doesn't exists.", volname) + return None + + # Check if replica_count and distribute_count is given + if not replica_count and not distribute_count: + distribute_count = 1 + + # Check if the volume has to be expanded by n distribute count. + num_of_distribute_bricks_to_add = 0 + if distribute_count: + # Get Number of bricks per subvolume. + bricks_per_subvol = get_num_of_bricks_per_subvol(mnode, volname) + num_of_bricks_per_subvol = ( + bricks_per_subvol['volume_num_of_bricks_per_subvol']) + + # Get number of bricks to add. + if not num_of_bricks_per_subvol: + g.log.error("Number of bricks per subvol is None. " + "Something majorly went wrong on the volume %s", + volname) + return False + + num_of_distribute_bricks_to_add = (num_of_bricks_per_subvol * + distribute_count) + + # Check if the volume has to be expanded by n replica count. + num_of_replica_bricks_to_add = 0 + if replica_count: + # Get Subvols + subvols_info = get_subvols(mnode, volname) + + # Calculate number of bricks to add + if subvols_info['volume_subvols']: + num_of_subvols = len(subvols_info['volume_subvols']) + + if not num_of_subvols: + g.log.error("No Sub-Volumes available for the volume %s." + " Hence cannot proceed with add-brick", volname) + return None + + num_of_replica_bricks_to_add = replica_count * num_of_subvols + + # Calculate total number of bricks to add + if (num_of_distribute_bricks_to_add != 0 and + num_of_replica_bricks_to_add != 0): + num_of_bricks_to_add = ( + num_of_distribute_bricks_to_add + + num_of_replica_bricks_to_add + + (distribute_count * replica_count) + ) + else: + num_of_bricks_to_add = ( + num_of_distribute_bricks_to_add + + num_of_replica_bricks_to_add + ) + + # Form bricks list to add bricks to the volume. + bricks_list = form_bricks_list(mnode=mnode, volname=volname, + number_of_bricks=num_of_bricks_to_add, + 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. Hence failed to form bricks list to " + "add-brick") + return None + return bricks_list + + + +def wait_for_volume_process_to_be_online(mnode, volname, timeout=300): + """Waits for the volume's processes to be online until timeout + Args: + mnode (str): Node on which commands will be executed. + volname (str): Name of the volume. + Kwargs: + timeout (int): timeout value in seconds to wait for all volume + processes to be online. + Returns: + True if the volume's processes are online within timeout, + False otherwise + """ + # Adding import here to avoid cyclic imports + from glustolibs.gluster.brick_libs import wait_for_bricks_to_be_online + + # Wait for bricks to be online + bricks_online_status = wait_for_bricks_to_be_online(mnode, volname, + timeout) + if not bricks_online_status: + g.log.error("Failed to wait for the volume '%s' processes " + "to be online", volname) + return False + + # ToDo: Wait for self-heal-daemons to be online + + # TODO: Add any process checks here + + g.log.info("Volume '%s' processes are all online", volname) + return True -- cgit