blob: bcdf01f2a3646c95eb3f03ebd6214725cc74fc6d [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:
121 return False
122
123
124def init_salt_client():
125 local = salt_remote()
126 return local
127
128
129def list_to_target_string(node_list, separator, add_spaces=True):
130 if add_spaces:
131 separator = ' ' + separator.strip() + ' '
132 return separator.join(node_list)
133
134
135def calculate_groups():
136 config = get_configuration()
137 local_salt_client = init_salt_client()
138 node_groups = {}
139 nodes_names = set ()
140 expr_form = ''
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300141 all_nodes = set(local_salt_client.test_ping(tgt='*', expr_form=None))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300142 if 'groups' in config.keys() and 'PB_GROUPS' in os.environ.keys() and \
143 os.environ['PB_GROUPS'].lower() != 'false':
144 nodes_names.update(config['groups'].keys())
145 expr_form = 'compound'
146 else:
147 for node in all_nodes:
148 index = re.search('[0-9]{1,3}$', node.split('.')[0])
149 if index:
150 nodes_names.add(node.split('.')[0][:-len(index.group(0))])
151 else:
152 nodes_names.add(node)
153 expr_form = 'pcre'
154
155 gluster_nodes = local_salt_client.test_ping(tgt='I@salt:control and '
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300156 'I@glusterfs:server',
157 expr_form='compound')
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300158 kvm_nodes = local_salt_client.test_ping(tgt='I@salt:control and not '
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300159 'I@glusterfs:server',
160 expr_form='compound')
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300161
162 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':
167 nodes = local_salt_client.test_ping(tgt='{}[0-9]{{1,3}}'.format(node_name),
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300168 expr_form=expr_form)
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300169 else:
170 nodes = local_salt_client.test_ping(tgt=config['groups'][node_name],
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300171 expr_form=expr_form)
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300172 if nodes == {}:
173 continue
174
Hanna Arhipova1eef8312019-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()]
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300179 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 Arhipova1eef8312019-05-06 20:14:18 +0300188 logging.info("These nodes were not collected {0}. Check config (groups section)".format(all_nodes))
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300189 return node_groups
Hanna Arhipova1eef8312019-05-06 20:14:18 +0300190
191
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300192def get_configuration():
193 """function returns configuration for environment
194 and for test if it's specified"""
Hanna Arhipova474ba922019-07-04 12:43:16 +0300195
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300196 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 Arhipovac64990a2019-07-16 16:07:11 +0300199 global_config = yaml.load(file, Loader=yaml.SafeLoader)
Ievgeniia Zadorozhnaff57ae82019-11-12 16:03:51 +0300200
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300201 for param in global_config.keys():
202 if param in os.environ.keys():
203 if ',' in os.environ[param]:
204 global_config[param] = []
205 for item in os.environ[param].split(','):
206 global_config[param].append(item)
207 else:
208 global_config[param] = os.environ[param]
209
Ievgeniia Zadorozhnaff57ae82019-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
Hanna Arhipovae6ed8e42019-05-15 16:27:08 +0300225 return global_config