summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cns-libs/cnslibs/common/command.py20
-rw-r--r--cns-libs/cnslibs/common/openshift_ops.py41
-rw-r--r--cns-libs/setup.py2
-rw-r--r--tests/functional/common/arbiter/__init__.py0
-rw-r--r--tests/functional/common/arbiter/test_arbiter.py122
-rw-r--r--tox.ini1
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)
+ )
diff --git a/tox.ini b/tox.ini
index edb108ed..c198d7da 100644
--- a/tox.ini
+++ b/tox.ini
@@ -21,6 +21,7 @@ commands =
commands =
{[testenv]commands}
pip install \
+ mock \
rtyaml \
ddt \
git+git://github.com/loadtheaccumulator/glusto.git \