summaryrefslogtreecommitdiffstats
path: root/openshift-storage-libs/openshiftstoragelibs/podcmd.py
blob: 33e88a26057bb7a44277ebacac5aabf448e02979 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""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
import mock
import six

from openshiftstoragelibs import openshift_ops

# Define a namedtuple that allows us to address pods instead of just
# hosts,
Pod = namedtuple('Pod', 'node podname')


def run(target, command, user=None, 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 str object and
            it equals to 'auto_get_gluster_endpoint', then
            Gluster endpoint gets autocalculated to be any of
            Gluster PODs or nodes depending on the deployment type of
            a Gluster cluster.
            If it is str object with other value, then it is considered to be
            an endpoint for command.
            If 'target' is of the 'Pod' type,
            then command will run on the specified POD.
        command (str|list): Command to run.
        user (str|None): user to be passed on to glusto's run method
        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

    ocp_client_node = list(g.config['ocp_servers']['client'].keys())[0]
    with mock.patch.object(g, 'run', new=orig_run):
        gluster_pods = openshift_ops.get_ocp_gluster_pod_details(
            ocp_client_node)

    if target == 'auto_get_gluster_endpoint':
        if gluster_pods:
            target = Pod(ocp_client_node, gluster_pods[0]["pod_name"])
        else:
            target = list(g.config.get("gluster_servers", {}).keys())[0]
    elif not isinstance(target, Pod) and gluster_pods:
        for g_pod in gluster_pods:
            if target in (g_pod['pod_host_ip'], g_pod['pod_hostname']):
                target = Pod(ocp_client_node, g_pod['pod_name'])
                break

    if isinstance(target, Pod):
        prefix = ['oc', 'rsh', target.podname]
        if isinstance(command, six.string_types):
            cmd = ' '.join(prefix + [command])
        else:
            cmd = prefix + command

        # unpack the tuple to make sure our return value exactly matches
        # our docstring
        return g.run(target.node, cmd, user=user, log_level=log_level)
    else:
        return orig_run(target, command, user=user, 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