summaryrefslogtreecommitdiffstats
path: root/roles/lib_openshift/src/class/oc_adm_manage_node.py
blob: 6d9f24baa361d92664e01a25f2acbab932267030 (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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# pylint: skip-file
# flake8: noqa


class ManageNodeException(Exception):
    ''' manage-node exception class '''
    pass


class ManageNodeConfig(OpenShiftCLIConfig):
    ''' ManageNodeConfig is a DTO for the manage-node command.'''
    def __init__(self, kubeconfig, node_options):
        super(ManageNodeConfig, self).__init__(None, None, kubeconfig, node_options)


# pylint: disable=too-many-instance-attributes
class ManageNode(OpenShiftCLI):
    ''' Class to wrap the oc command line tools '''

    # pylint allows 5
    # pylint: disable=too-many-arguments
    def __init__(self,
                 config,
                 verbose=False):
        ''' Constructor for ManageNode '''
        super(ManageNode, self).__init__(None, kubeconfig=config.kubeconfig, verbose=verbose)
        self.config = config

    def evacuate(self):
        ''' formulate the params and run oadm manage-node '''
        return self._evacuate(node=self.config.config_options['node']['value'],
                              selector=self.config.config_options['selector']['value'],
                              pod_selector=self.config.config_options['pod_selector']['value'],
                              dry_run=self.config.config_options['dry_run']['value'],
                              grace_period=self.config.config_options['grace_period']['value'],
                              force=self.config.config_options['force']['value'],
                             )
    def get_nodes(self, node=None, selector=''):
        '''perform oc get node'''
        _node = None
        _sel = None
        if node:
            _node = node
        if selector:
            _sel = selector

        results = self._get('node', name=_node, selector=_sel)
        if results['returncode'] != 0:
            return results

        nodes = []
        items = None
        if results['results'][0]['kind'] == 'List':
            items = results['results'][0]['items']
        else:
            items = results['results']

        for node in items:
            _node = {}
            _node['name'] = node['metadata']['name']
            _node['schedulable'] = True
            if 'unschedulable' in node['spec']:
                _node['schedulable'] = False
            nodes.append(_node)

        return nodes

    def get_pods_from_node(self, node, pod_selector=None):
        '''return pods for a node'''
        results = self._list_pods(node=[node], pod_selector=pod_selector)

        if results['returncode'] != 0:
            return results

        # When a selector or node is matched it is returned along with the json.
        # We are going to split the results based on the regexp and then
        # load the json for each matching node.
        # Before we return we are going to loop over the results and pull out the node names.
        # {'node': [pod, pod], 'node': [pod, pod]}
        # 3.2 includes the following lines in stdout: "Listing matched pods on node:"
        all_pods = []
        if "Listing matched" in results['results']:
            listing_match = re.compile('\n^Listing matched.*$\n', flags=re.MULTILINE)
            pods = listing_match.split(results['results'])
            for pod in pods:
                if pod:
                    all_pods.extend(json.loads(pod)['items'])

        # 3.3 specific
        else:
            # this is gross but I filed a bug...
            # https://bugzilla.redhat.com/show_bug.cgi?id=1381621
            # build our own json from the output.
            all_pods = json.loads(results['results'])['items']

        return all_pods

    def list_pods(self):
        ''' run oadm manage-node --list-pods'''
        _nodes = self.config.config_options['node']['value']
        _selector = self.config.config_options['selector']['value']
        _pod_selector = self.config.config_options['pod_selector']['value']

        if not _nodes:
            _nodes = self.get_nodes(selector=_selector)
        else:
            _nodes = [{'name': name} for name in _nodes]

        all_pods = {}
        for node in _nodes:
            results = self.get_pods_from_node(node['name'], pod_selector=_pod_selector)
            if isinstance(results, dict):
                return results
            all_pods[node['name']] = results

        results = {}
        results['nodes'] = all_pods
        results['returncode'] = 0
        return results

    def schedulable(self):
        '''oadm manage-node call for making nodes unschedulable'''
        nodes = self.config.config_options['node']['value']
        selector = self.config.config_options['selector']['value']

        if not nodes:
            nodes = self.get_nodes(selector=selector)
        else:
            tmp_nodes = []
            for name in nodes:
                tmp_result = self.get_nodes(name)
                if isinstance(tmp_result, dict):
                    tmp_nodes.append(tmp_result)
                    continue
                tmp_nodes.extend(tmp_result)
            nodes = tmp_nodes

        # This is a short circuit based on the way we fetch nodes.
        # If node is a dict/list then we've already fetched them.
        for node in nodes:
            if isinstance(node, dict) and 'returncode' in node:
                return {'results': nodes, 'returncode': node['returncode']}
            if isinstance(node, list) and 'returncode' in node[0]:
                return {'results': nodes, 'returncode': node[0]['returncode']}
        # check all the nodes that were returned and verify they are:
        # node['schedulable'] == self.config.config_options['schedulable']['value']
        if any([node['schedulable'] != self.config.config_options['schedulable']['value'] for node in nodes]):

            results = self._schedulable(node=self.config.config_options['node']['value'],
                                        selector=self.config.config_options['selector']['value'],
                                        schedulable=self.config.config_options['schedulable']['value'])

            # 'NAME                            STATUS    AGE\\nip-172-31-49-140.ec2.internal   Ready     4h\\n'  # E501
            # normalize formatting with previous return objects
            if results['results'].startswith('NAME'):
                nodes = []
                # removing header line and trailing new line character of node lines
                for node_results in results['results'].split('\n')[1:-1]:
                    parts = node_results.split()
                    nodes.append({'name': parts[0], 'schedulable': parts[1] == 'Ready'})
                results['nodes'] = nodes

            return results

        results = {}
        results['returncode'] = 0
        results['changed'] = False
        results['nodes'] = nodes

        return results

    @staticmethod
    def run_ansible(params, check_mode):
        '''run the idempotent ansible code'''
        nconfig = ManageNodeConfig(params['kubeconfig'],
                                   {'node': {'value': params['node'], 'include': True},
                                    'selector': {'value': params['selector'], 'include': True},
                                    'pod_selector': {'value': params['pod_selector'], 'include': True},
                                    'schedulable': {'value': params['schedulable'], 'include': True},
                                    'list_pods': {'value': params['list_pods'], 'include': True},
                                    'evacuate': {'value': params['evacuate'], 'include': True},
                                    'dry_run': {'value': params['dry_run'], 'include': True},
                                    'force': {'value': params['force'], 'include': True},
                                    'grace_period': {'value': params['grace_period'], 'include': True},
                                   })

        oadm_mn = ManageNode(nconfig)
        # Run the oadm manage-node commands
        results = None
        changed = False
        if params['schedulable'] != None:
            if check_mode:
                # schedulable returns results after the fact.
                # We need to redo how this works to support check_mode completely.
                return {'changed': True, 'msg': 'CHECK_MODE: would have called schedulable.'}
            results = oadm_mn.schedulable()
            if 'changed' not in results:
                changed = True

        if params['evacuate']:
            results = oadm_mn.evacuate()
            changed = True
        elif params['list_pods']:
            results = oadm_mn.list_pods()

        if not results or results['returncode'] != 0:
            return {'failed': True, 'msg': results}

        return {'changed': changed, 'results': results, 'state': "present"}