diff options
author | Krishnaram Karthick Ramdoss <kramdoss@redhat.com> | 2018-01-11 10:48:56 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit2@gerrit.host.prod.eng.bos.redhat.com> | 2018-01-11 10:48:56 +0000 |
commit | b627cd9eb0289a0d3e261b6f83cb9c1bf7d98313 (patch) | |
tree | 279375009e5d9195ea8b7d58d043818c0cf79fdc | |
parent | c825884d20669d333ff80feefb5eeeb1b7d5bf0d (diff) | |
parent | 0a48b5d2fb2dfde79e8117188863006ff2d8e849 (diff) |
Merge "add a utility library to support patching glusto for pod support"
-rw-r--r-- | cns-libs/cnslibs/common/openshift_ops.py | 6 | ||||
-rw-r--r-- | cns-libs/cnslibs/common/podcmd.py | 120 |
2 files changed, 124 insertions, 2 deletions
diff --git a/cns-libs/cnslibs/common/openshift_ops.py b/cns-libs/cnslibs/common/openshift_ops.py index beff3272..dbe89d0e 100644 --- a/cns-libs/cnslibs/common/openshift_ops.py +++ b/cns-libs/cnslibs/common/openshift_ops.py @@ -142,7 +142,7 @@ def switch_oc_project(ocp_node, project_name): return True -def oc_rsh(ocp_node, pod_name, command): +def oc_rsh(ocp_node, pod_name, command, log_level=None): """Run a command in the ocp pod using `oc rsh`. Args: @@ -150,6 +150,8 @@ def oc_rsh(ocp_node, pod_name, command): pod_name (str): Name of the pod on which the command will be executed. command (str|list): command to run. + log_level (str|None): log level to be passed to glusto's run + method. Returns: A tuple consisting of the command return code, stdout, and stderr. @@ -162,5 +164,5 @@ def oc_rsh(ocp_node, pod_name, command): # unpack the tuple to make sure our return value exactly matches # our docstring - ret, stdout, stderr = g.run(ocp_node, cmd) + ret, stdout, stderr = g.run(ocp_node, cmd, log_level=log_level) return (ret, stdout, stderr) diff --git a/cns-libs/cnslibs/common/podcmd.py b/cns-libs/cnslibs/common/podcmd.py new file mode 100644 index 00000000..f8c89d5b --- /dev/null +++ b/cns-libs/cnslibs/common/podcmd.py @@ -0,0 +1,120 @@ +"""Convenience wrappers for running commands within a pod + +The goal of this module is to support running glusto commands in pods +without a lot of boilerplate and hassle. The basic idea is that we +have our own run() function that can be addressed to a pod using the +Pod object (namedtuple). This run function will work like a normal +g.run() when not using the Pod object. + +Example: + >>> run("my-host.local", ["parted", "/dev/sda", "p"]) + 0, "<...stdout...>", "<...stderr...>" + + >>> run(Pod("my-host.local", "my-pod-426ln"), + ... ["pvs"]) + 0, "<...stdout...>", "<...stderr...>" + +In addition, if there's a need to to use some higher level functions +that directly call into glusto run we can monkey-patch the glusto object +using the GlustoPod context manager. GlustoPod can also be used as a +decorator. + +Imagine a function that direcly calls g.run: + >>> def get_magic_number(host, ticket): + ... s, out, _ = g.run(host, ['magicall', '--ticket', ticket]) + ... if s != 0: + ... return None + ... return out.strip() + +If you want to have this operate within a pod you can use the GlustoPod +manager to enable the pod-aware run method and pass it a Pod object +as the first argument. Example: + >>> def check_magic_number(ticket): + ... with GlustoPod(): + ... m = get_magic_number(Pod('myhost', 'mypod'), ticket) + ... return m > 42 + +Similarly it can be used as a context manager: + >>> @GlustoPod() + ... def funky(x): + ... m = get_magic_number(Pod('myhost', 'mypod'), ticket) + ... return m > 42 + +Because the custom run fuction only runs commands in pods when passed +a Pod object it is fairly safe to enable the monkey-patch over the +lifetime of a function that addresses both hosts and pods. +""" + +from collections import namedtuple +from functools import partial, wraps + +from glusto.core import Glusto as g + +from cnslibs.common.openshift_ops import oc_rsh + +# Define a namedtuple that allows us to address pods instead of just +# hosts, +Pod = namedtuple('Pod', 'node podname') + + +def run(target, command, log_level=None, orig_run=g.run): + """Function that runs a command on a host or in a pod via a host. + Wraps glusto's run function. + + Args: + target (str|Pod): If target is a anything other than a Pod + object the command will be run directly on the target + (hostname or IP). If target is a Pod object it will run + on the named pod, going through the node named in the + Pod object. + command (str|list): Command to run. + log_level (str|None): log level to be passed on to glusto's + run method + orig_run (function): The default implementation of the + run method. Will be used when target is not a pod. + + Returns: + A tuple of the command's return code, stdout, and stderr. + """ + # NOTE: orig_run captures the glusto run method at function + # definition time in order to capture the method before + # any additional monkeypatching by other code + if isinstance(target, Pod): + return oc_rsh(target.node, target.podname, command, + log_level=log_level) + else: + return orig_run(target, command, log_level=log_level) + + +class GlustoPod(object): + """A context manager / decorator that monkeypatches the + glusto object to support running commands in pods. + """ + + def __init__(self, glusto_obj=None): + self.runfunc = None + self._g = glusto_obj or g + + def __enter__(self): + """Patch glusto to use the wrapped run method. + """ + self.runfunc = self._g.run + # we "capture" the prior glusto run method here in order to + # stack on top of any previous monkeypatches if they exist + self._g.run = partial(run, orig_run=self.runfunc) + + def __exit__(self, etype, value, tb): + """Restore the orginal run method. + """ + self._g.run = self.runfunc + self.runfunc = None + + def __call__(self, func): + """Allow GlustoPod to be used as a decorator. + """ + @wraps(func) + def wrapper(*args, **kwargs): + with self: + result = func(*args, **kwargs) + return result + return wrapper |