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