blob: c6bbed2f8ec66e8ad23ad4d0ea9c45c587b2d282 [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:
Hanna Arhipovafae5b5b2019-12-09 15:57:55 +0200121 logging.error(
122 "suppressed incorrect response from pillar_get: {}".format(
123 response
124 ))
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500125 return False
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000126
127
128def init_salt_client():
129 local = salt_remote()
130 return local
131
132
Mikhail Chernik714596e2018-08-10 21:19:07 +0200133def list_to_target_string(node_list, separator, add_spaces=True):
134 if add_spaces:
135 separator = ' ' + separator.strip() + ' '
136 return separator.join(node_list)
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000137
138
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000139def calculate_groups():
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000140 config = get_configuration()
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000141 local_salt_client = init_salt_client()
Oleksii Zhurba30122e12018-03-29 14:01:50 -0500142 node_groups = {}
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000143 nodes_names = set ()
144 expr_form = ''
Hanna Arhipova56eab942019-05-06 20:14:18 +0300145 all_nodes = set(local_salt_client.test_ping(tgt='*', expr_form=None))
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500146 if 'groups' in config.keys() and 'PB_GROUPS' in os.environ.keys() and \
147 os.environ['PB_GROUPS'].lower() != 'false':
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000148 nodes_names.update(config['groups'].keys())
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500149 expr_form = 'compound'
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000150 else:
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500151 for node in all_nodes:
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000152 index = re.search('[0-9]{1,3}$', node.split('.')[0])
153 if index:
154 nodes_names.add(node.split('.')[0][:-len(index.group(0))])
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000155 else:
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000156 nodes_names.add(node)
157 expr_form = 'pcre'
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000158
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500159 gluster_nodes = local_salt_client.test_ping(tgt='I@salt:control and '
Hanna Arhipova56eab942019-05-06 20:14:18 +0300160 'I@glusterfs:server',
161 expr_form='compound')
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500162 kvm_nodes = local_salt_client.test_ping(tgt='I@salt:control and not '
Hanna Arhipova56eab942019-05-06 20:14:18 +0300163 'I@glusterfs:server',
164 expr_form='compound')
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500165
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000166 for node_name in nodes_names:
167 skipped_groups = config.get('skipped_groups') or []
168 if node_name in skipped_groups:
169 continue
170 if expr_form == 'pcre':
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500171 nodes = local_salt_client.test_ping(tgt='{}[0-9]{{1,3}}'.format(node_name),
Hanna Arhipova56eab942019-05-06 20:14:18 +0300172 expr_form=expr_form)
Oleksii Zhurbae0dedb52018-01-16 00:55:25 +0000173 else:
Oleksii Zhurba4bfd2ee2019-04-10 21:56:58 -0500174 nodes = local_salt_client.test_ping(tgt=config['groups'][node_name],
Hanna Arhipova56eab942019-05-06 20:14:18 +0300175 expr_form=expr_form)
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500176 if nodes == {}:
177 continue
178
Hanna Arhipova56eab942019-05-06 20:14:18 +0300179 node_groups[node_name] = [x for x in nodes
180 if x not in config['skipped_nodes']
181 if x not in gluster_nodes.keys()
182 if x not in kvm_nodes.keys()]
Oleksii Zhurbae592ed12018-06-21 18:01:09 -0500183 all_nodes = set(all_nodes - set(node_groups[node_name]))
184 if node_groups[node_name] == []:
185 del node_groups[node_name]
186 if kvm_nodes:
187 node_groups['kvm'] = kvm_nodes.keys()
188 node_groups['kvm_gluster'] = gluster_nodes.keys()
189 all_nodes = set(all_nodes - set(kvm_nodes.keys()))
190 all_nodes = set(all_nodes - set(gluster_nodes.keys()))
191 if all_nodes:
Hanna Arhipova56eab942019-05-06 20:14:18 +0300192 logging.info("These nodes were not collected {0}. Check config (groups section)".format(all_nodes))
Oleksii Zhurbad0ae87f2018-03-26 13:36:25 -0500193 return node_groups
Hanna Arhipova8d4082d2019-07-16 16:07:11 +0300194
195
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000196def get_configuration():
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000197 """function returns configuration for environment
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000198 and for test if it's specified"""
Hanna Arhipova91dc8152019-07-04 12:43:16 +0300199
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000200 global_config_file = os.path.join(
201 os.path.dirname(os.path.abspath(__file__)), "../global_config.yaml")
202 with open(global_config_file, 'r') as file:
Hanna Arhipova8d4082d2019-07-16 16:07:11 +0300203 global_config = yaml.load(file, Loader=yaml.SafeLoader)
Ievgeniia Zadorozhna0c306fd2019-11-12 16:03:51 +0300204
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000205 for param in global_config.keys():
206 if param in os.environ.keys():
207 if ',' in os.environ[param]:
Oleksii Zhurba3dbed242017-10-31 19:58:53 +0000208 global_config[param] = []
Oleksii Zhurbae0668ae2017-10-27 23:58:18 +0000209 for item in os.environ[param].split(','):
210 global_config[param].append(item)
211 else:
Oleksii Zhurba3dbed242017-10-31 19:58:53 +0000212 global_config[param] = os.environ[param]
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000213
Ievgeniia Zadorozhna0c306fd2019-11-12 16:03:51 +0300214 if 'OVERRIDE_CONFIG' in os.environ.keys():
215 try:
216 override_config = yaml.load(
217 os.environ['OVERRIDE_CONFIG'], Loader=yaml.SafeLoader)\
218 .get('override_config')
219 if override_config:
220 for key in override_config:
221 if isinstance(global_config[key], dict):
222 for k in override_config[key]:
223 global_config[key][k] = override_config[key][k]
224 else:
225 global_config[key] = override_config[key]
226 except Exception:
227 pass
228
Oleksii Zhurbaa10927b2017-09-27 22:09:23 +0000229 return global_config