blob: c6bbed2f8ec66e8ad23ad4d0ea9c45c587b2d282 [file] [log] [blame]
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +03001import os
2import yaml
3import requests
4import re
5import sys, traceback
6import time
Hanna Arhipova1eef8312019-05-06 20:14:18 +03007import json
Hanna Arhipova1eef8312019-05-06 20:14:18 +03008import logging
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +03009
10
11class AuthenticationError(Exception):
12 pass
13
14
15class salt_remote:
16 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):
21 raise AuthenticationError("Salt URL should start \
Hanna Arhipova1eef8312019-05-06 20:14:18 +030022 with http or https, given - {}".format(self.url))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +030023 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()
31
32 def _login (self):
33 try:
34 login_request = requests.post(os.path.join(self.url, 'login'),
35 headers={'Accept': 'application/json'},
36 data=self.login_payload,
37 proxies=self.proxies)
38 if not login_request.ok:
39 raise AuthenticationError("Authentication to SaltMaster failed")
40 except Exception as e:
Hanna Arhipova1eef8312019-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))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +030046 traceback.print_exc(file=sys.stdout)
47 sys.exit()
48 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 Arhipova1eef8312019-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)))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +030067 request = requests.post(self.url, headers=self.headers,
68 data=accept_key_payload,
69 cookies=self.cookies,
70 proxies=self.proxies)
Hanna Arhipova1eef8312019-05-06 20:14:18 +030071 logging.info("-"*100)
72 logging.info("Response: {}".format(json.dumps(
73 request.json(),
74 indent=4
75 )))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +030076 if not request.ok or not isinstance(request.json()['return'][0], dict):
Hanna Arhipova1eef8312019-05-06 20:14:18 +030077 logging.warning("Salt master is not responding or response is incorrect. Output: {}".format(request))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +030078 continue
79 response = request.json()['return'][0]
80 result = {key: response[key] for key in response if key not in self.skipped_nodes}
Oleksii Zhurbab4d88022019-05-28 21:41:46 -050081 if check_status and (False in result.values() or not result):
Hanna Arhipova1eef8312019-05-06 20:14:18 +030082 logging.warning("One or several nodes are not responding. Output {}".format(json.dumps(result, indent=4)))
83 continue
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +030084 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 Arhipovaacb94532019-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
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300112 """
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 Arhipova3fc06572019-12-09 15:57:55 +0200121 logging.error(
122 "suppressed incorrect response from pillar_get: {}".format(
123 response
124 ))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300125 return False
126
127
128def init_salt_client():
129 local = salt_remote()
130 return local
131
132
133def list_to_target_string(node_list, separator, add_spaces=True):
134 if add_spaces:
135 separator = ' ' + separator.strip() + ' '
136 return separator.join(node_list)
137
138
139def calculate_groups():
140 config = get_configuration()
141 local_salt_client = init_salt_client()
142 node_groups = {}
143 nodes_names = set ()
144 expr_form = ''
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300145 all_nodes = set(local_salt_client.test_ping(tgt='*', expr_form=None))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300146 if 'groups' in config.keys() and 'PB_GROUPS' in os.environ.keys() and \
147 os.environ['PB_GROUPS'].lower() != 'false':
148 nodes_names.update(config['groups'].keys())
149 expr_form = 'compound'
150 else:
151 for node in all_nodes:
152 index = re.search('[0-9]{1,3}$', node.split('.')[0])
153 if index:
154 nodes_names.add(node.split('.')[0][:-len(index.group(0))])
155 else:
156 nodes_names.add(node)
157 expr_form = 'pcre'
158
159 gluster_nodes = local_salt_client.test_ping(tgt='I@salt:control and '
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300160 'I@glusterfs:server',
161 expr_form='compound')
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300162 kvm_nodes = local_salt_client.test_ping(tgt='I@salt:control and not '
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300163 'I@glusterfs:server',
164 expr_form='compound')
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300165
166 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':
171 nodes = local_salt_client.test_ping(tgt='{}[0-9]{{1,3}}'.format(node_name),
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300172 expr_form=expr_form)
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300173 else:
174 nodes = local_salt_client.test_ping(tgt=config['groups'][node_name],
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300175 expr_form=expr_form)
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300176 if nodes == {}:
177 continue
178
Hanna Arhipova1eef8312019-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()]
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300183 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 Arhipova1eef8312019-05-06 20:14:18 +0300192 logging.info("These nodes were not collected {0}. Check config (groups section)".format(all_nodes))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300193 return node_groups
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300194
195
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300196def get_configuration():
197 """function returns configuration for environment
198 and for test if it's specified"""
Hanna Arhipova474ba922019-07-04 12:43:16 +0300199
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300200 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 Arhipovac64990a2019-07-16 16:07:11 +0300203 global_config = yaml.load(file, Loader=yaml.SafeLoader)
Ievgeniia Zadorozhnaff57ae82019-11-12 16:03:51 +0300204
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300205 for param in global_config.keys():
206 if param in os.environ.keys():
207 if ',' in os.environ[param]:
208 global_config[param] = []
209 for item in os.environ[param].split(','):
210 global_config[param].append(item)
211 else:
212 global_config[param] = os.environ[param]
213
Ievgeniia Zadorozhnaff57ae82019-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
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300229 return global_config