oadm_manage_node.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. # pylint: skip-file
  2. # flake8: noqa
  3. class ManageNodeException(Exception):
  4. ''' manage-node exception class '''
  5. pass
  6. class ManageNodeConfig(OpenShiftCLIConfig):
  7. ''' ManageNodeConfig is a DTO for the manage-node command.'''
  8. def __init__(self, kubeconfig, node_options):
  9. super(ManageNodeConfig, self).__init__(None, None, kubeconfig, node_options)
  10. # pylint: disable=too-many-instance-attributes
  11. class ManageNode(OpenShiftCLI):
  12. ''' Class to wrap the oc command line tools '''
  13. # pylint allows 5
  14. # pylint: disable=too-many-arguments
  15. def __init__(self,
  16. config,
  17. verbose=False):
  18. ''' Constructor for ManageNode '''
  19. super(ManageNode, self).__init__(None, config.kubeconfig)
  20. self.config = config
  21. def evacuate(self):
  22. ''' formulate the params and run oadm manage-node '''
  23. return self._evacuate(node=self.config.config_options['node']['value'],
  24. selector=self.config.config_options['selector']['value'],
  25. pod_selector=self.config.config_options['pod_selector']['value'],
  26. dry_run=self.config.config_options['dry_run']['value'],
  27. grace_period=self.config.config_options['grace_period']['value'],
  28. force=self.config.config_options['force']['value'],
  29. )
  30. def get_nodes(self, node=None, selector=''):
  31. '''perform oc get node'''
  32. _node = None
  33. _sel = None
  34. if node:
  35. _node = node
  36. if selector:
  37. _sel = selector
  38. results = self._get('node', rname=_node, selector=_sel)
  39. if results['returncode'] != 0:
  40. return results
  41. nodes = []
  42. items = None
  43. if results['results'][0]['kind'] == 'List':
  44. items = results['results'][0]['items']
  45. else:
  46. items = results['results']
  47. for node in items:
  48. _node = {}
  49. _node['name'] = node['metadata']['name']
  50. _node['schedulable'] = True
  51. if 'unschedulable' in node['spec']:
  52. _node['schedulable'] = False
  53. nodes.append(_node)
  54. return nodes
  55. def get_pods_from_node(self, node, pod_selector=None):
  56. '''return pods for a node'''
  57. results = self._list_pods(node=[node], pod_selector=pod_selector)
  58. if results['returncode'] != 0:
  59. return results
  60. # When a selector or node is matched it is returned along with the json.
  61. # We are going to split the results based on the regexp and then
  62. # load the json for each matching node.
  63. # Before we return we are going to loop over the results and pull out the node names.
  64. # {'node': [pod, pod], 'node': [pod, pod]}
  65. # 3.2 includes the following lines in stdout: "Listing matched pods on node:"
  66. all_pods = []
  67. if "Listing matched" in results['results']:
  68. listing_match = re.compile('\n^Listing matched.*$\n', flags=re.MULTILINE)
  69. pods = listing_match.split(results['results'])
  70. for pod in pods:
  71. if pod:
  72. all_pods.extend(json.loads(pod)['items'])
  73. # 3.3 specific
  74. else:
  75. # this is gross but I filed a bug...
  76. # https://bugzilla.redhat.com/show_bug.cgi?id=1381621
  77. # build our own json from the output.
  78. all_pods = json.loads(results['results'])['items']
  79. return all_pods
  80. def list_pods(self):
  81. ''' run oadm manage-node --list-pods'''
  82. _nodes = self.config.config_options['node']['value']
  83. _selector = self.config.config_options['selector']['value']
  84. _pod_selector = self.config.config_options['pod_selector']['value']
  85. if not _nodes:
  86. _nodes = self.get_nodes(selector=_selector)
  87. else:
  88. _nodes = [{'name': name} for name in _nodes]
  89. all_pods = {}
  90. for node in _nodes:
  91. results = self.get_pods_from_node(node['name'], pod_selector=_pod_selector)
  92. if isinstance(results, dict):
  93. return results
  94. all_pods[node['name']] = results
  95. results = {}
  96. results['nodes'] = all_pods
  97. results['returncode'] = 0
  98. return results
  99. def schedulable(self):
  100. '''oadm manage-node call for making nodes unschedulable'''
  101. nodes = self.config.config_options['node']['value']
  102. selector = self.config.config_options['selector']['value']
  103. if not nodes:
  104. nodes = self.get_nodes(selector=selector)
  105. else:
  106. tmp_nodes = []
  107. for name in nodes:
  108. tmp_result = self.get_nodes(name)
  109. if isinstance(tmp_result, dict):
  110. tmp_nodes.append(tmp_result)
  111. continue
  112. tmp_nodes.extend(tmp_result)
  113. nodes = tmp_nodes
  114. # This is a short circuit based on the way we fetch nodes.
  115. # If node is a dict/list then we've already fetched them.
  116. for node in nodes:
  117. if isinstance(node, dict) and 'returncode' in node:
  118. return {'results': nodes, 'returncode': node['returncode']}
  119. if isinstance(node, list) and 'returncode' in node[0]:
  120. return {'results': nodes, 'returncode': node[0]['returncode']}
  121. # check all the nodes that were returned and verify they are:
  122. # node['schedulable'] == self.config.config_options['schedulable']['value']
  123. if any([node['schedulable'] != self.config.config_options['schedulable']['value'] for node in nodes]):
  124. results = self._schedulable(node=self.config.config_options['node']['value'],
  125. selector=self.config.config_options['selector']['value'],
  126. schedulable=self.config.config_options['schedulable']['value'])
  127. # 'NAME STATUS AGE\\nip-172-31-49-140.ec2.internal Ready 4h\\n' # E501
  128. # normalize formatting with previous return objects
  129. if results['results'].startswith('NAME'):
  130. nodes = []
  131. # removing header line and trailing new line character of node lines
  132. for node_results in results['results'].split('\n')[1:-1]:
  133. parts = node_results.split()
  134. nodes.append({'name': parts[0], 'schedulable': parts[1] == 'Ready'})
  135. results['nodes'] = nodes
  136. return results
  137. results = {}
  138. results['returncode'] = 0
  139. results['changed'] = False
  140. results['nodes'] = nodes
  141. return results
  142. @staticmethod
  143. def run_ansible(params, check_mode):
  144. '''run the idempotent ansible code'''
  145. nconfig = ManageNodeConfig(params['kubeconfig'],
  146. {'node': {'value': params['node'], 'include': True},
  147. 'selector': {'value': params['selector'], 'include': True},
  148. 'pod_selector': {'value': params['pod_selector'], 'include': True},
  149. 'schedulable': {'value': params['schedulable'], 'include': True},
  150. 'list_pods': {'value': params['list_pods'], 'include': True},
  151. 'evacuate': {'value': params['evacuate'], 'include': True},
  152. 'dry_run': {'value': params['dry_run'], 'include': True},
  153. 'force': {'value': params['force'], 'include': True},
  154. 'grace_period': {'value': params['grace_period'], 'include': True},
  155. })
  156. oadm_mn = ManageNode(nconfig)
  157. # Run the oadm manage-node commands
  158. results = None
  159. changed = False
  160. if params['schedulable'] != None:
  161. if check_mode:
  162. # schedulable returns results after the fact.
  163. # We need to redo how this works to support check_mode completely.
  164. return {'changed': True, 'msg': 'CHECK_MODE: would have called schedulable.'}
  165. results = oadm_mn.schedulable()
  166. if 'changed' not in results:
  167. changed = True
  168. if params['evacuate']:
  169. results = oadm_mn.evacuate()
  170. changed = True
  171. elif params['list_pods']:
  172. results = oadm_mn.list_pods()
  173. if not results or results['returncode'] != 0:
  174. return {'failed': True, 'msg': results}
  175. return {'changed': changed, 'results': results, 'state': "present"}