diff options
author | John Mulligan <jmulligan@redhat.com> | 2017-12-19 17:44:47 -0500 |
---|---|---|
committer | John Mulligan <jmulligan@redhat.com> | 2018-01-19 14:31:07 -0500 |
commit | c9c9f72e2e038643bb96436701ea8abac3fe47c7 (patch) | |
tree | bdeed728e53f0380dbd21d06dde2256d10f51d9b /cns-libs/cnslibs/common | |
parent | 993cc40e8e79e9a09114286d018660c98a336ac0 (diff) |
add a test to verify multiple PVC requests create multiple volumes
This is sort of a trial of the both the test framework, my ability
to add tests to it, and the workflow of adding changes to the repo.
Change-Id: I17b61f298c04f45df8b77555b16d292fd2b2a3a8
Signed-off-by: John Mulligan <jmulligan@redhat.com>
Diffstat (limited to 'cns-libs/cnslibs/common')
-rw-r--r-- | cns-libs/cnslibs/common/naming.py | 56 | ||||
-rw-r--r-- | cns-libs/cnslibs/common/openshift_ops.py | 101 | ||||
-rw-r--r-- | cns-libs/cnslibs/common/waiter.py | 34 |
3 files changed, 191 insertions, 0 deletions
diff --git a/cns-libs/cnslibs/common/naming.py b/cns-libs/cnslibs/common/naming.py new file mode 100644 index 00000000..b44559ad --- /dev/null +++ b/cns-libs/cnslibs/common/naming.py @@ -0,0 +1,56 @@ +"""Helper functions for working with names for volumes, resources, etc. +""" + +import string +import random +import re + +# we only use lowercase here because kubernetes requires +# names to be lowercase or digits, so that is our default +UNIQUE_CHARS = (string.lowercase + string.digits) + + +def make_unique_label(prefix=None, suffix=None, sep='-', + clean=r'[^a-zA-Z0-9]+', unique_len=8, + unique_chars=UNIQUE_CHARS): + """Generate a unique name string based on an optional prefix, + suffix, and pseudo-random set of alphanumeric characters. + + Args: + prefix (str): Start of the unique string. + suffix (str): End of the unique string. + sep (str): Separator string (between sections/invalid chars). + clean (str): Reqular expression matching invalid chars. + that will be replaced by `sep` if found in the prefix or suffix + unique_len (int): Length of the unique part. + unique_chars (str): String representing the set of characters + the unique part will draw from. + Returns: + str: The uniqueish string. + """ + cre = re.compile(clean) + parts = [] + if prefix: + parts.append(cre.sub(sep, prefix)) + parts.append(''.join(random.choice(unique_chars) + for _ in range(unique_len))) + if suffix: + parts.append(cre.sub(sep, suffix)) + return sep.join(parts) + + +def extract_method_name(full_name, keep_class=False): + """Given a full test name as returned from TestCase.id() return + just the method part or class.method. + + Args: + full_name (str): Dot separated name of test. + keep_class (str): Retain the class name, if false only the + method name will be returned. + Returns: + str: Method name or class.method_name. + """ + offset = -1 + if keep_class: + offset = -2 + return '.'.join(full_name.split('.')[offset:]) diff --git a/cns-libs/cnslibs/common/openshift_ops.py b/cns-libs/cnslibs/common/openshift_ops.py index dbe89d0e..5920d51f 100644 --- a/cns-libs/cnslibs/common/openshift_ops.py +++ b/cns-libs/cnslibs/common/openshift_ops.py @@ -166,3 +166,104 @@ def oc_rsh(ocp_node, pod_name, command, log_level=None): # our docstring ret, stdout, stderr = g.run(ocp_node, cmd, log_level=log_level) return (ret, stdout, stderr) + + +def oc_create(ocp_node, filename): + """Create a resource based on the contents of the given file name. + + Args: + ocp_node (str): Node on which the ocp command will run + filename (str): Filename (on remote) to be passed to oc create + command + Raises: + AssertionError: Raised when resource fails to create. + """ + ret, out, err = g.run(ocp_node, ['oc', 'create', '-f', filename]) + if ret != 0: + g.log.error('Failed to create resource: %r; %r', out, err) + raise AssertionError('failed to create resource: %r; %r' % (out, err)) + g.log.info('Created resource from file (%s)', filename) + return + + +def oc_delete(ocp_node, rtype, name): + """Delete an OCP resource by name. + + Args: + ocp_node (str): Node on which the ocp command will run. + rtype (str): Name of the resource type (pod, storageClass, etc). + name (str): Name of the resource to delete. + Raises: + AssertionError: Raised when resource fails to create. + """ + ret, out, err = g.run(ocp_node, ['oc', 'delete', rtype, name]) + if ret != 0: + g.log.error('Failed to delete resource: %s, %s: %r; %r', + rtype, name, out, err) + raise AssertionError('failed to delete resource: %r; %r' % (out, err)) + g.log.info('Deleted resource: %r %r', rtype, name) + return + + +def oc_get_yaml(ocp_node, rtype, name=None, raise_on_error=True): + """Get an OCP resource by name. + + Args: + ocp_node (str): Node on which the ocp command will run. + rtype (str): Name of the resource type (pod, storageClass, etc). + name (str|None): Name of the resource to fetch. + raise_on_error (bool): If set to true a failure to fetch + resource inforation will raise an error, otherwise + an empty dict will be returned. + Returns: + dict: Dictionary containting data about the resource + Raises: + AssertionError: Raised when unable to get resource and + `raise_on_error` is true. + """ + cmd = ['oc', 'get', '-oyaml', rtype] + if name is not None: + cmd.append(name) + ret, out, err = g.run(ocp_node, cmd) + if ret != 0: + g.log.error('Failed to get %s: %s: %r', rtype, name, err) + if raise_on_error: + raise AssertionError('failed to get %s: %s: %r' + % (rtype, name, err)) + return {} + return yaml.load(out) + + +def oc_get_pvc(ocp_node, name): + """Get information on a persistant volume claim. + + Args: + ocp_node (str): Node on which the ocp command will run. + name (str): Name of the PVC. + Returns: + dict: Dictionary containting data about the PVC. + """ + return oc_get_yaml(ocp_node, 'pvc', name) + + +def oc_get_pv(ocp_node, name): + """Get information on a persistant volume. + + Args: + ocp_node (str): Node on which the ocp command will run. + name (str): Name of the PV. + Returns: + dict: Dictionary containting data about the PV. + """ + return oc_get_yaml(ocp_node, 'pv', name) + + +def oc_get_all_pvs(ocp_node): + """Get information on all persistent volumes. + + Args: + ocp_node (str): Node on which the ocp command will run. + Returns: + dict: Dictionary containting data about the PV. + """ + return oc_get_yaml(ocp_node, 'pv', None) diff --git a/cns-libs/cnslibs/common/waiter.py b/cns-libs/cnslibs/common/waiter.py new file mode 100644 index 00000000..89a264df --- /dev/null +++ b/cns-libs/cnslibs/common/waiter.py @@ -0,0 +1,34 @@ +"""Helper object to encapsulate waiting for timeouts. + +Provide a Waiter class which encapsulates the operation +of doing an action in a loop until a timeout values elapses. +It aims to avoid having to write boilerplate code comparing times. +""" + +import time + +class Waiter(object): + """A wait-retry loop as iterable. + This object abstracts away the wait logic allowing functions + to write the retry logic in a for-loop. + """ + def __init__(self, timeout=60, interval=1): + self.timeout = timeout + self.interval = interval + self.expired = False + self._attempt = 0 + self._start = None + + def __iter__(self): + return self + + def next(self): + if self._start is None: + self._start = time.time() + if time.time() - self._start > self.timeout: + self.expired = True + raise StopIteration() + if self._attempt != 0: + time.sleep(self.interval) + self._attempt += 1 + return self |