diff options
-rw-r--r-- | cns-libs/cnslibs/common/command.py | 20 | ||||
-rw-r--r-- | cns-libs/cnslibs/common/openshift_ops.py | 41 | ||||
-rw-r--r-- | cns-libs/setup.py | 2 | ||||
-rw-r--r-- | tests/functional/common/arbiter/__init__.py | 0 | ||||
-rw-r--r-- | tests/functional/common/arbiter/test_arbiter.py | 122 | ||||
-rw-r--r-- | tox.ini | 1 |
6 files changed, 185 insertions, 1 deletions
diff --git a/cns-libs/cnslibs/common/command.py b/cns-libs/cnslibs/common/command.py new file mode 100644 index 00000000..e2c41545 --- /dev/null +++ b/cns-libs/cnslibs/common/command.py @@ -0,0 +1,20 @@ +from glusto.core import Glusto as g + + +def cmd_run(cmd, hostname, raise_on_error=True): + """Glusto's command runner wrapper. + + Args: + cmd (str): Shell command to run on the specified hostname. + hostname (str): hostname where Glusto should run specified command. + raise_on_error (bool): defines whether we should raise exception + in case command execution failed. + Returns: + str: Stripped shell command's stdout value. + """ + ret, out, err = g.run(hostname, cmd, "root") + if raise_on_error: + msg = ("Failed to execute command '%s' on '%s' node. Got non-zero " + "return code '%s'. Err: %s" % (cmd, hostname, ret, err)) + assert int(ret) == 0, msg + return out.strip() diff --git a/cns-libs/cnslibs/common/openshift_ops.py b/cns-libs/cnslibs/common/openshift_ops.py index bbfa4947..ea1cea6a 100644 --- a/cns-libs/cnslibs/common/openshift_ops.py +++ b/cns-libs/cnslibs/common/openshift_ops.py @@ -9,8 +9,11 @@ import re import types from glusto.core import Glusto as g +from glustolibs.gluster import volume_ops +import mock import yaml +from cnslibs.common import command from cnslibs.common import exceptions from cnslibs.common import utils from cnslibs.common import waiter @@ -530,3 +533,41 @@ def scale_dc_pod_amount_and_wait(hostname, dc_name, wait_for_resource_absence(hostname, 'pod', pod) else: wait_for_pod_be_ready(hostname, pod) + + +def get_gluster_vol_info_by_pvc_name(ocp_node, pvc_name): + """Get Gluster volume info based on the PVC name. + + Args: + ocp_node (str): Node to execute OCP commands on. + pvc_name (str): Name of a PVC to get bound Gluster volume info. + Returns: + dict: Dictionary containting data about a Gluster volume. + """ + + # Get PV ID from PVC + get_pvc_cmd = "oc get pvc %s -o=custom-columns=:.spec.volumeName" % ( + pvc_name) + pv_name = command.cmd_run(get_pvc_cmd, hostname=ocp_node) + assert pv_name, "PV name should not be empty: '%s'" % pv_name + + # Get volume ID from PV + get_pv_cmd = "oc get pv %s -o=custom-columns=:.spec.glusterfs.path" % ( + pv_name) + vol_id = command.cmd_run(get_pv_cmd, hostname=ocp_node) + assert vol_id, "Gluster volume ID should not be empty: '%s'" % vol_id + + # Get name of one the Gluster PODs + gluster_pod = get_ocp_gluster_pod_names(ocp_node)[1] + + # Get Gluster volume info + vol_info_cmd = "oc exec %s -- gluster v info %s --xml" % ( + gluster_pod, vol_id) + vol_info = command.cmd_run(vol_info_cmd, hostname=ocp_node) + + # Parse XML output to python dict + with mock.patch('glusto.core.Glusto.run', return_value=(0, vol_info, '')): + vol_info = volume_ops.get_volume_info(vol_id) + vol_info = vol_info[list(vol_info.keys())[0]] + vol_info["gluster_vol_id"] = vol_id + return vol_info diff --git a/cns-libs/setup.py b/cns-libs/setup.py index 2c25c8a7..bc376b58 100644 --- a/cns-libs/setup.py +++ b/cns-libs/setup.py @@ -22,6 +22,6 @@ setup( 'Programming Language :: Python :: 2.7' 'Topic :: Software Development :: Testing' ], - install_requires=['glusto', 'ddt', 'rtyaml'], + install_requires=['glusto', 'ddt', 'mock', 'rtyaml'], dependency_links=['http://github.com/loadtheaccumulator/glusto/tarball/master#egg=glusto'], ) diff --git a/tests/functional/common/arbiter/__init__.py b/tests/functional/common/arbiter/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/functional/common/arbiter/__init__.py diff --git a/tests/functional/common/arbiter/test_arbiter.py b/tests/functional/common/arbiter/test_arbiter.py new file mode 100644 index 00000000..dc21acee --- /dev/null +++ b/tests/functional/common/arbiter/test_arbiter.py @@ -0,0 +1,122 @@ +from cnslibs.cns import cns_baseclass +from cnslibs.common.dynamic_provisioning import ( + verify_pvc_status_is_bound, +) +from cnslibs.common import heketi_ops +from cnslibs.common.openshift_ops import ( + get_gluster_vol_info_by_pvc_name, + oc_create_pvc, + oc_create_sc, + oc_create_secret, + oc_delete, + wait_for_resource_absence, +) + + +class TestArbiterVolumeCreateExpandDelete(cns_baseclass.CnsBaseClass): + + @classmethod + def setUpClass(cls): + super(TestArbiterVolumeCreateExpandDelete, cls).setUpClass() + if cls.deployment_type != "cns": + # Do nothing and switch to the step with test skip operations + return + + # Mark one of the Heketi nodes as arbiter-supported if none of + # existent nodes or devices already enabled to support it. + heketi_server_url = cls.cns_storage_class['storage_class1']['resturl'] + arbiter_tags = ('required', 'supported') + arbiter_already_supported = False + + node_id_list = heketi_ops.heketi_node_list( + cls.heketi_client_node, heketi_server_url) + + for node_id in node_id_list[::-1]: + node_info = heketi_ops.heketi_node_info( + cls.heketi_client_node, heketi_server_url, node_id, json=True) + if node_info.get('tags', {}).get('arbiter') in arbiter_tags: + arbiter_already_supported = True + break + for device in node_info['devices'][::-1]: + if device.get('tags', {}).get('arbiter') in arbiter_tags: + arbiter_already_supported = True + break + else: + continue + break + if not arbiter_already_supported: + heketi_ops.set_arbiter_tag( + cls.heketi_client_node, heketi_server_url, + 'node', node_id_list[0], 'supported') + + def setUp(self): + super(TestArbiterVolumeCreateExpandDelete, self).setUp() + + # Skip test if it is not CNS deployment + if self.deployment_type != "cns": + raise self.skipTest("This test can run only on CNS deployment.") + self.node = self.ocp_master_node[0] + + def _create_storage_class(self): + sc = self.cns_storage_class['storage_class1'] + secret = self.cns_secret['secret1'] + + # Create secret file for usage in storage class + self.secret_name = oc_create_secret( + self.node, namespace=secret['namespace'], + data_key=self.heketi_cli_key, secret_type=secret['type']) + self.addCleanup( + oc_delete, self.node, 'secret', self.secret_name) + + # Create storage class + self.sc_name = oc_create_sc( + self.node, resturl=sc['resturl'], + restuser=sc['restuser'], secretnamespace=sc['secretnamespace'], + secretname=self.secret_name, + volumeoptions="user.heketi.arbiter true", + ) + self.addCleanup(oc_delete, self.node, 'sc', self.sc_name) + + def _create_and_wait_for_pvc(self, pvc_size=1): + # Create PVC + self.pvc_name = oc_create_pvc( + self.node, self.sc_name, pvc_name_prefix='arbiter-pvc', + pvc_size=pvc_size) + self.addCleanup( + wait_for_resource_absence, self.node, 'pvc', self.pvc_name) + self.addCleanup(oc_delete, self.node, 'pvc', self.pvc_name) + + # Wait for PVC to be in bound state + verify_pvc_status_is_bound(self.node, self.pvc_name) + + def test_arbiter_pvc_create(self): + """Test case CNS-944""" + + # Create sc with gluster arbiter info + self._create_storage_class() + + # Create PVC and wait for it to be in 'Bound' state + self._create_and_wait_for_pvc() + + # Get vol info + vol_info = get_gluster_vol_info_by_pvc_name(self.node, self.pvc_name) + + # Verify amount proportion of data and arbiter bricks + bricks = vol_info['bricks']['brick'] + arbiter_brick_amount = sum([int(b['isArbiter']) for b in bricks]) + data_brick_amount = len(bricks) - arbiter_brick_amount + self.assertGreater( + data_brick_amount, 0, + "Data brick amount is expected to be bigger than 0. " + "Actual amount is '%s'." % arbiter_brick_amount) + self.assertGreater( + arbiter_brick_amount, 0, + "Arbiter brick amount is expected to be bigger than 0. " + "Actual amount is '%s'." % arbiter_brick_amount) + self.assertEqual( + data_brick_amount, + (arbiter_brick_amount * 2), + "Expected 1 arbiter brick per 2 data bricks. " + "Arbiter brick amount is '%s', Data brick amount is '%s'." % ( + arbiter_brick_amount, data_brick_amount) + ) @@ -21,6 +21,7 @@ commands = commands = {[testenv]commands} pip install \ + mock \ rtyaml \ ddt \ git+git://github.com/loadtheaccumulator/glusto.git \ |