blob: ba1233bb8a87b70b72ae4237c5423200b7e45a72 [file] [log] [blame]
savex4448e132018-04-25 15:51:14 +02001"""
2Module to handle interaction with salt
3"""
4import os
5import requests
6import time
7
Alex Savatieiev5118de02019-02-20 15:50:42 -06008from cfg_checker.common import logger, config
savex4448e132018-04-25 15:51:14 +02009
10
11def list_to_target_string(node_list, separator):
12 result = ''
13 for node in node_list:
14 result += node + ' ' + separator + ' '
15 return result[:-(len(separator)+2)]
16
17
18class SaltRest(object):
19 _host = config.salt_host
20 _port = config.salt_port
21 uri = "http://" + config.salt_host + ":" + config.salt_port
22 _auth = {}
23
24 default_headers = {
25 'Accept': 'application/json',
26 'Content-Type': 'application/json',
27 'X-Auth-Token': None
28 }
29
30 def __init__(self):
31 self._token = self._login()
32 self.last_response = None
33
34 def get(self, path='', headers=default_headers, cookies=None):
35 _path = os.path.join(self.uri, path)
36 logger.debug("GET '{}'\nHeaders: '{}'\nCookies: {}".format(
37 _path,
38 headers,
39 cookies
40 ))
41 return requests.get(
42 _path,
43 headers=headers,
44 cookies=cookies
45 )
46
47 def post(self, data, path='', headers=default_headers, cookies=None):
48 if data is None:
49 data = {}
50 _path = os.path.join(self.uri, path)
51 if path == 'login':
52 _data = str(data).replace(config.salt_pass, "*****")
53 else:
54 _data = data
55 logger.debug("POST '{}'\nHeaders: '{}'\nCookies: {}\nBody: {}".format(
56 _path,
57 headers,
58 cookies,
59 _data
60 ))
61 return requests.post(
62 os.path.join(self.uri, path),
63 headers=headers,
64 json=data,
65 cookies=cookies
66 )
67
68 def _login(self):
69 login_payload = {
70 'username': config.salt_user,
71 'password': config.salt_pass,
72 'eauth': 'pam'
73 }
74
75 logger.debug("Logging in to salt master...")
76 _response = self.post(login_payload, path='login')
77
78 if _response.ok:
79 self._auth['response'] = _response.json()['return'][0]
80 self._auth['cookies'] = _response.cookies
81 self.default_headers['X-Auth-Token'] = \
82 self._auth['response']['token']
83 return self._auth['response']['token']
84 else:
85 raise EnvironmentError(
86 "HTTP:{}, Not authorized?".format(_response.status_code)
87 )
88
89 def salt_request(self, fn, *args, **kwargs):
90 # if token will expire in 5 min, re-login
91 if self._auth['response']['expire'] < time.time() + 300:
92 self._auth['response']['X-Auth-Token'] = self._login()
93
94 _method = getattr(self, fn)
95 _response = _method(*args, **kwargs)
96 self.last_response = _response
97 _content = "..."
98 _len = len(_response.content)
99 if _len < 1024:
100 _content = _response.content
101 logger.debug(
102 "Response (HTTP {}/{}), {}: {}".format(
103 _response.status_code,
104 _response.reason,
105 _len,
106 _content
107 )
108 )
109 if _response.ok:
110 return _response.json()['return']
111 else:
112 raise EnvironmentError(
113 "Salt Error: HTTP:{}, '{}'".format(
114 _response.status_code,
115 _response.reason
116 )
117 )
118
119
120class SaltRemote(SaltRest):
121 def __init__(self):
122 super(SaltRemote, self).__init__()
123
124 def cmd(
125 self,
126 tgt,
127 fun,
128 param=None,
129 client='local',
130 kwarg=None,
131 expr_form=None,
132 tgt_type=None,
133 timeout=None
134 ):
135 _timeout = timeout if timeout is not None else config.salt_timeout
136 _payload = {
137 'fun': fun,
138 'tgt': tgt,
139 'client': client,
140 'timeout': _timeout
141 }
142
143 if expr_form:
144 _payload['expr_form'] = expr_form
145 if tgt_type:
146 _payload['tgt_type'] = tgt_type
147 if param:
148 _payload['arg'] = param
149 if kwarg:
150 _payload['kwarg'] = kwarg
151
152 _response = self.salt_request('post', [_payload])
153 if isinstance(_response, list):
154 return _response[0]
155 else:
156 raise EnvironmentError(
157 "Unexpected response from from salt-api/LocalClient: "
158 "{}".format(_response)
159 )
160
161 def run(self, fun, kwarg=None):
162 _payload = {
163 'client': 'runner',
164 'fun': fun,
165 'timeout': config.salt_timeout
166 }
167
168 if kwarg:
169 _payload['kwarg'] = kwarg
170
171 _response = self.salt_request('post', [_payload])
172 if isinstance(_response, list):
173 return _response[0]
174 else:
175 raise EnvironmentError(
176 "Unexpected response from from salt-api/RunnerClient: "
177 "{}".format(_response)
178 )
179
180 def wheel(self, fun, arg=None, kwarg=None):
181 _payload = {
182 'client': 'wheel',
183 'fun': fun,
184 'timeout': config.salt_timeout
185 }
186
187 if arg:
188 _payload['arg'] = arg
189 if kwarg:
190 _payload['kwarg'] = kwarg
191
192 _response = self.salt_request('post', _payload)['data']
193 if _response['success']:
194 return _response
195 else:
196 raise EnvironmentError(
197 "Salt Error: '{}'".format(_response['return']))
198
199 def pillar_request(self, node_target, pillar_submodule, argument):
200 # example cli: 'salt "ctl01*" pillar.keys rsyslog'
201 _type = "compound"
202 if isinstance(node_target, list):
203 _type = "list"
204 return self.cmd(
205 node_target,
206 "pillar." + pillar_submodule,
207 argument,
208 expr_form=_type
209 )
210
211 def pillar_keys(self, node_target, argument):
212 return self.pillar_request(node_target, 'keys', argument)
213
214 def pillar_get(self, node_target, argument):
215 return self.pillar_request(node_target, 'get', argument)
216
217 def pillar_data(self, node_target, argument):
218 return self.pillar_request(node_target, 'data', argument)
219
220 def pillar_raw(self, node_target, argument):
221 return self.pillar_request(node_target, 'raw', argument)
222
223 def list_minions(self):
224 """
225 Fails in salt version 2016.3.8
226 api returns dict of minions with grains
227 """
228 return self.salt_request('get', 'minions')
229
230 def list_keys(self):
231 """
232 Fails in salt version 2016.3.8
233 api should return dict:
234 {
235 'local': [],
236 'minions': [],
237 'minions_denied': [],
238 'minions_pre': [],
239 'minions_rejected': [],
240 }
241 """
242 return self.salt_request('get', path='keys')
243
244 def get_status(self):
245 """
246 'runner' client is the equivalent of 'salt-run'
247 Returns the
248 """
249 return self.run(
250 'manage.status',
251 kwarg={'timeout': 10}
252 )
253
254 def get_active_nodes(self):
255 if config.skip_nodes:
256 logger.info("Nodes to be skipped: {0}".format(config.skip_nodes))
257 return self.cmd(
258 '* and not ' + list_to_target_string(
259 config.skip_nodes,
260 'and not'
261 ),
262 'test.ping',
263 expr_form='compound')
264 else:
265 return self.cmd('*', 'test.ping')
266
267 def get_monitoring_ip(self, param_name):
268 salt_output = self.cmd(
269 'docker:client:stack:monitoring',
270 'pillar.get',
271 param=param_name,
272 expr_form='pillar')
273 return salt_output[salt_output.keys()[0]]
274
275 def f_touch_master(self, path, makedirs=True):
276 _kwarg = {
277 "makedirs": makedirs
278 }
279 salt_output = self.cmd(
280 "cfg01*",
281 "file.touch",
282 param=path,
283 kwarg=_kwarg
284 )
285 return salt_output[salt_output.keys()[0]]
286
287 def f_append_master(self, path, strings_list, makedirs=True):
288 _kwarg = {
289 "makedirs": makedirs
290 }
291 _args = [path]
292 _args.extend(strings_list)
293 salt_output = self.cmd(
294 "cfg01*",
295 "file.write",
296 param=_args,
297 kwarg=_kwarg
298 )
299 return salt_output[salt_output.keys()[0]]
300
301 def mkdir(self, target, path, tgt_type=None):
302 salt_output = self.cmd(
303 target,
304 "file.mkdir",
305 param=path,
306 expr_form=tgt_type
307 )
308 return salt_output
309
310 def f_manage_file(self, target_path, source,
311 sfn='', ret='{}',
312 source_hash={},
313 user='root', group='root', backup_mode='755',
314 show_diff='base',
315 contents='', makedirs=True):
316 """
317 REST variation of file.get_managed
318 CLI execution goes like this (10 agrs):
319 salt cfg01\* file.manage_file /root/test_scripts/pkg_versions.py
320 '' '{}' /root/diff_pkg_version.py
321 '{hash_type: 'md5', 'hsum': <md5sum>}' root root '755' base ''
322 makedirs=True
323 param: name - target file placement when managed
324 param: source - source for the file
325 """
326 _source_hash = {
327 "hash_type": "md5",
328 "hsum": 000
329 }
330 _arg = [
331 target_path,
332 sfn,
333 ret,
334 source,
335 _source_hash,
336 user,
337 group,
338 backup_mode,
339 show_diff,
340 contents
341 ]
342 _kwarg = {
343 "makedirs": makedirs
344 }
345 salt_output = self.cmd(
346 "cfg01*",
347 "file.manage_file",
348 param=_arg,
349 kwarg=_kwarg
350 )
351 return salt_output[salt_output.keys()[0]]
352
353 def cache_file(self, target, source_path):
354 salt_output = self.cmd(
355 target,
356 "cp.cache_file",
357 param=source_path
358 )
359 return salt_output[salt_output.keys()[0]]
360
361 def get_file(self, target, source_path, target_path, tgt_type=None):
362 return self.cmd(
363 target,
364 "cp.get_file",
365 param=[source_path, target_path],
366 expr_form=tgt_type
367 )
368
369 @staticmethod
370 def compound_string_from_list(nodes_list):
371 return " or ".join(nodes_list)