blob: bcdf01f2a3646c95eb3f03ebd6214725cc74fc6d [file] [log] [blame]
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +00001import os
2import yaml
3import requests
4import re
Oleksii Zhurbae592ed12018-06-21 18:01:09 -05005import sys, traceback
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -05006import time
Hanna Arhipova56eab942019-05-06 20:14:18 +03007import json
Hanna Arhipova56eab942019-05-06 20:14:18 +03008import logging
Oleksii Zhurba3dbed242017-10-31 19:58:53 +00009
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +000010
Mikhail Chernikc492a682018-08-27 22:41:17 +020011class AuthenticationError(Exception):
12 pass
13
14
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +000015class salt_remote:
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050016 def __init__(self):
17 self.config = get_configuration()
18 self.skipped_nodes = self.config.get('skipped_nodes') or []
19 self.url = self.config['SALT_URL'].strip()
20 if not re.match("^(http|https)://", self.url):
Valentyn Khalin81dd23d2018-09-20 15:39:07 +030021 raise AuthenticationError("Salt URL should start \
Hanna Arhipova56eab942019-05-06 20:14:18 +030022 with http or https, given - {}".format(self.url))
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050023 self.login_payload = {'username': self.config['SALT_USERNAME'],
24 'password': self.config['SALT_PASSWORD'], 'eauth': 'pam'}
25 # TODO: proxies
26 self.proxies = {"http": None, "https": None}
27 self.expires = ''
28 self.cookies = []
29 self.headers = {'Accept': 'application/json'}
30 self._login()
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +000031
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050032 def _login (self):
Oleksii Zhurbae592ed12018-06-21 18:01:09 -050033 try:
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050034 login_request = requests.post(os.path.join(self.url, 'login'),
35 headers={'Accept': 'application/json'},
36 data=self.login_payload,
37 proxies=self.proxies)
Mikhail Chernikc492a682018-08-27 22:41:17 +020038 if not login_request.ok:
39 raise AuthenticationError("Authentication to SaltMaster failed")
Mikhail Chernikc492a682018-08-27 22:41:17 +020040 except Exception as e:
Hanna Arhipova56eab942019-05-06 20:14:18 +030041 logging.warning("\033[91m\nConnection to SaltMaster "
42 "was not established.\n"
43 "Please make sure that you "
44 "provided correct credentials.\n"
45 "Error message: {}\033[0m\n".format(e.message or e))
Oleksii Zhurbae592ed12018-06-21 18:01:09 -050046 traceback.print_exc(file=sys.stdout)
47 sys.exit()
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050048 self.expire = login_request.json()['return'][0]['expire']
49 self.cookies = login_request.cookies
50 self.headers['X-Auth-Token'] = login_request.json()['return'][0]['token']
51
52 def cmd(self, tgt, fun='cmd.run', param=None, expr_form=None, tgt_type=None, check_status=False, retries=3):
53 if self.expire < time.time() + 300:
54 self.headers['X-Auth-Token'] = self._login()
55 accept_key_payload = {'fun': fun, 'tgt': tgt, 'client': 'local',
56 'expr_form': expr_form, 'tgt_type': tgt_type,
57 'timeout': self.config['salt_timeout']}
58 if param:
59 accept_key_payload['arg'] = param
60
61 for i in range(retries):
Hanna Arhipova56eab942019-05-06 20:14:18 +030062 logging.info("="*100)
63 logging.info("Send Request: {}".format(json.dumps(
64 accept_key_payload,
65 indent=4,
66 sort_keys=True)))
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050067 request = requests.post(self.url, headers=self.headers,
68 data=accept_key_payload,
69 cookies=self.cookies,
70 proxies=self.proxies)
Hanna Arhipova56eab942019-05-06 20:14:18 +030071 logging.info("-"*100)
72 logging.info("Response: {}".format(json.dumps(
73 request.json(),
74 indent=4
75 )))
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050076 if not request.ok or not isinstance(request.json()['return'][0], dict):
Hanna Arhipova56eab942019-05-06 20:14:18 +030077 logging.warning("Salt master is not responding or response is incorrect. Output: {}".format(request))
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050078 continue
79 response = request.json()['return'][0]
80 result = {key: response[key] for key in response if key not in self.skipped_nodes}
Oleksii Zhurbabea17c32019-05-28 21:41:46 -050081 if check_status and (False in result.values() or not result):
Hanna Arhipova56eab942019-05-06 20:14:18 +030082 logging.warning("One or several nodes are not responding. Output {}".format(json.dumps(result, indent=4)))
83 continue
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -050084 break
85 else:
86 raise Exception("Error with Salt Master response")
87 return result
88
89 def test_ping(self, tgt, expr_form='pillar'):
90 return self.cmd(tgt=tgt, fun='test.ping', param=None, expr_form=expr_form)
91
92 def cmd_any(self, tgt, param=None, expr_form='pillar'):
93 """
94 This method returns first non-empty result on node or nodes.
95 If all nodes returns nothing, then exception is thrown.
96 """
97 response = self.cmd(tgt=tgt, param=param, expr_form=expr_form)
98 for node in response.keys():
99 if response[node] or response[node] == '':
100 return response[node]
101 else:
102 raise Exception("All minions are down")
103
104 def pillar_get(self, tgt='salt:master', param=None, expr_form='pillar', fail_if_empty=False):
105 """
106 This method is for fetching pillars only.
107 Returns value for pillar, False (if no such pillar) or if fail_if_empty=True - exception
Hanna Arhipovab9635f92019-07-18 19:00:35 +0300108 :param tgt, string, target when the salt command will be executed
109 :param param, additional parameter for salt command
110 :param expr_form
111 :param fail_if_empty
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500112 """
113 response = self.cmd(tgt=tgt, fun='pillar.get', param=param, expr_form=expr_form)
114 for node in response.keys():
115 if response[node] or response[node] != '':
116 return response[node]
117 else:
118 if fail_if_empty:
119 raise Exception("No pillar found or it is empty.")
120 else:
121 return False
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000122
123
124def init_salt_client():
125 local = salt_remote()
126 return local
127
128
Mikhail Chernik714596e2018-08-10 21:19:07 +0200129def list_to_target_string(node_list, separator, add_spaces=True):
130 if add_spaces:
131 separator = ' ' + separator.strip() + ' '
132 return separator.join(node_list)
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000133
134
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000135def calculate_groups():
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000136 config = get_configuration()
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000137 local_salt_client = init_salt_client()
Oleksii Zhurba30122e12018-03-29 14:01:50 -0500138 node_groups = {}
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000139 nodes_names = set ()
140 expr_form = ''
Hanna Arhipova56eab942019-05-06 20:14:18 +0300141 all_nodes = set(local_salt_client.test_ping(tgt='*', expr_form=None))
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500142 if 'groups' in config.keys() and 'PB_GROUPS' in os.environ.keys() and \
143 os.environ['PB_GROUPS'].lower() != 'false':
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000144 nodes_names.update(config['groups'].keys())
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500145 expr_form = 'compound'
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000146 else:
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500147 for node in all_nodes:
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000148 index = re.search('[0-9]{1,3}$', node.split('.')[0])
149 if index:
150 nodes_names.add(node.split('.')[0][:-len(index.group(0))])
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000151 else:
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000152 nodes_names.add(node)
153 expr_form = 'pcre'
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000154
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500155 gluster_nodes = local_salt_client.test_ping(tgt='I@salt:control and '
Hanna Arhipova56eab942019-05-06 20:14:18 +0300156 'I@glusterfs:server',
157 expr_form='compound')
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500158 kvm_nodes = local_salt_client.test_ping(tgt='I@salt:control and not '
Hanna Arhipova56eab942019-05-06 20:14:18 +0300159 'I@glusterfs:server',
160 expr_form='compound')
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500161
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000162 for node_name in nodes_names:
163 skipped_groups = config.get('skipped_groups') or []
164 if node_name in skipped_groups:
165 continue
166 if expr_form == 'pcre':
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500167 nodes = local_salt_client.test_ping(tgt='{}[0-9]{{1,3}}'.format(node_name),
Hanna Arhipova56eab942019-05-06 20:14:18 +0300168 expr_form=expr_form)
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000169 else:
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500170 nodes = local_salt_client.test_ping(tgt=config['groups'][node_name],
Hanna Arhipova56eab942019-05-06 20:14:18 +0300171 expr_form=expr_form)
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500172 if nodes == {}:
173 continue
174
Hanna Arhipova56eab942019-05-06 20:14:18 +0300175 node_groups[node_name] = [x for x in nodes
176 if x not in config['skipped_nodes']
177 if x not in gluster_nodes.keys()
178 if x not in kvm_nodes.keys()]
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500179 all_nodes = set(all_nodes - set(node_groups[node_name]))
180 if node_groups[node_name] == []:
181 del node_groups[node_name]
182 if kvm_nodes:
183 node_groups['kvm'] = kvm_nodes.keys()
184 node_groups['kvm_gluster'] = gluster_nodes.keys()
185 all_nodes = set(all_nodes - set(kvm_nodes.keys()))
186 all_nodes = set(all_nodes - set(gluster_nodes.keys()))
187 if all_nodes:
Hanna Arhipova56eab942019-05-06 20:14:18 +0300188 logging.info("These nodes were not collected {0}. Check config (groups section)".format(all_nodes))
Oleksii Zhurbad0ae87f2018-03-26 13:36:25 -0500189 return node_groups
Hanna Arhipova8d4082d2019-07-16 16:07:11 +0300190
191
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000192def get_configuration():
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000193 """function returns configuration for environment
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000194 and for test if it's specified"""
Hanna Arhipova91dc8152019-07-04 12:43:16 +0300195
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000196 global_config_file = os.path.join(
197 os.path.dirname(os.path.abspath(__file__)), "../global_config.yaml")
198 with open(global_config_file, 'r') as file:
Hanna Arhipova8d4082d2019-07-16 16:07:11 +0300199 global_config = yaml.load(file, Loader=yaml.SafeLoader)
Ievgeniia Zadorozhna0c306fd2019-11-12 16:03:51 +0300200
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000201 for param in global_config.keys():
202 if param in os.environ.keys():
203 if ',' in os.environ[param]:
Oleksii Zhurba3dbed242017-10-31 19:58:53 +0000204 global_config[param] = []
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000205 for item in os.environ[param].split(','):
206 global_config[param].append(item)
207 else:
Oleksii Zhurba3dbed242017-10-31 19:58:53 +0000208 global_config[param] = os.environ[param]
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000209
Ievgeniia Zadorozhna0c306fd2019-11-12 16:03:51 +0300210 if 'OVERRIDE_CONFIG' in os.environ.keys():
211 try:
212 override_config = yaml.load(
213 os.environ['OVERRIDE_CONFIG'], Loader=yaml.SafeLoader)\
214 .get('override_config')
215 if override_config:
216 for key in override_config:
217 if isinstance(global_config[key], dict):
218 for k in override_config[key]:
219 global_config[key][k] = override_config[key][k]
220 else:
221 global_config[key] = override_config[key]
222 except Exception:
223 pass
224
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000225 return global_config