Coverage for cfg_checker/nodes.py : 15%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import json
2import os
3from copy import deepcopy
5from cfg_checker.clients import get_salt_remote, salt
6from cfg_checker.common import config, const
7from cfg_checker.common import logger, logger_cli
8from cfg_checker.common import utils
9from cfg_checker.common.exception import SaltException
10from cfg_checker.common.settings import pkg_dir
12node_tmpl = {
13 'role': '',
14 'node_group': '',
15 'status': const.NODE_DOWN,
16 'pillars': {},
17 'grains': {}
18}
21class SaltNodes(object):
22 def __init__(self):
23 logger_cli.info("# Gathering environment information")
24 # simple salt rest client
25 self.salt = salt
26 self.nodes = None
28 def gather_node_info(self):
29 # Keys for all nodes
30 # this is not working in scope of 2016.8.3, will overide with list
31 logger_cli.debug("... collecting node names existing in the cloud")
32 if not self.salt:
33 self.salt = get_salt_remote(config)
35 try:
36 _keys = self.salt.list_keys()
37 _str = []
38 for _k, _v in _keys.items():
39 _str.append("{}: {}".format(_k, len(_v)))
40 logger_cli.info("-> keys collected: {}".format(", ".join(_str)))
42 self.node_keys = {
43 'minions': _keys['minions']
44 }
45 except Exception:
46 _keys = None
47 self.node_keys = None
49 # List of minions with grains
50 _minions = self.salt.list_minions()
51 if _minions:
52 logger_cli.info(
53 "-> api reported {} active minions".format(len(_minions))
54 )
55 elif not self.node_keys:
56 # this is the last resort
57 _minions = config.load_nodes_list()
58 logger_cli.info(
59 "-> {} nodes loaded from list file".format(len(_minions))
60 )
61 else:
62 _minions = self.node_keys['minions']
64 # in case API not listed minions, we need all that answer ping
65 _active = self.salt.get_active_nodes()
66 logger_cli.info("-> nodes responded: {}".format(len(_active)))
67 # iterate through all accepted nodes and create a dict for it
68 self.nodes = {}
69 self.skip_list = []
70 for _name in _minions:
71 _nc = utils.get_node_code(_name)
72 _rmap = const.all_roles_map
73 _role = _rmap[_nc] if _nc in _rmap else 'unknown'
74 _status = const.NODE_UP if _name in _active else const.NODE_DOWN
75 if _status == const.NODE_DOWN:
76 self.skip_list.append(_name)
77 logger_cli.info("-> '{}' is down, marked to skip".format(
78 _name
79 ))
80 self.nodes[_name] = deepcopy(node_tmpl)
81 self.nodes[_name]['node_group'] = _nc
82 self.nodes[_name]['role'] = _role
83 self.nodes[_name]['status'] = _status
84 logger_cli.info("-> {} nodes inactive".format(len(self.skip_list)))
85 logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
87 # form an all nodes compound string to use in salt
88 self.active_nodes_compound = self.salt.compound_string_from_list(
89 filter(
90 lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
91 self.nodes
92 )
93 )
94 # get master node fqdn
95 # _filtered = filter(
96 # lambda nd: self.nodes[nd]['role'] == const.all_roles_map['cfg'],
97 # self.nodes
98 # )
99 _role = const.all_roles_map['cfg']
100 _filtered = [n for n, v in self.nodes.items() if v['role'] == _role]
101 if len(_filtered) < 1:
102 raise SaltException(
103 "No master node detected! Check/Update node role map."
104 )
105 else:
106 self.salt.master_node = _filtered[0]
108 # OpenStack versions
109 self.mcp_release = self.salt.pillar_get(
110 self.salt.master_node,
111 "_param:apt_mk_version"
112 )[self.salt.master_node]
113 self.openstack_release = self.salt.pillar_get(
114 self.salt.master_node,
115 "_param:openstack_version"
116 )[self.salt.master_node]
117 # Preload codenames
118 # do additional queries to get linux codename and arch for each node
119 self.get_specific_pillar_for_nodes("_param:linux_system_codename")
120 self.get_specific_pillar_for_nodes("_param:linux_system_architecture")
121 for _name in self.nodes.keys():
122 _n = self.nodes[_name]
123 if _name not in self.skip_list:
124 _p = _n['pillars']['_param']
125 _n['linux_codename'] = _p['linux_system_codename']
126 _n['linux_arch'] = _p['linux_system_architecture']
128 def skip_node(self, node):
129 # Add node to skip list
130 # Fro example if it is fails to comply with the rules
132 # check if we know such node
133 if node in self.nodes.keys() and node not in self.skip_list:
134 # yes, add it
135 self.skip_list.append(node)
136 return True
137 else:
138 return False
140 def get_nodes(self):
141 if not self.nodes:
142 self.gather_node_info()
143 return self.nodes
145 def get_info(self):
146 _info = {
147 'mcp_release': self.mcp_release,
148 'openstack_release': self.openstack_release
149 }
150 return _info
152 def get_cmd_for_nodes(self, cmd, target_key, target_dict=None, nodes=None):
153 """Function runs. cmd.run and parses result into place
154 or into dict structure provided
156 :return: no return value, data pulished internally
157 """
158 logger_cli.debug(
159 "... collecting results for '{}'".format(cmd)
160 )
161 if target_dict:
162 _nodes = target_dict
163 else:
164 _nodes = self.nodes
165 _result = self.execute_cmd_on_active_nodes(cmd, nodes=nodes)
166 for node, data in _nodes.items():
168 if node in self.skip_list:
169 logger_cli.debug(
170 "... '{}' skipped while collecting '{}'".format(
171 node,
172 cmd
173 )
174 )
175 continue
176 # Prepare target key
177 if target_key not in data:
178 data[target_key] = None
179 # Save data
180 if data['status'] == const.NODE_DOWN:
181 data[target_key] = None
182 elif node not in _result:
183 continue
184 elif not _result[node]:
185 logger_cli.debug(
186 "... '{}' not responded after '{}'".format(
187 node,
188 config.salt_timeout
189 )
190 )
191 data[target_key] = None
192 else:
193 data[target_key] = _result[node]
195 def get_specific_pillar_for_nodes(self, pillar_path):
196 """Function gets pillars on given path for all nodes
198 :return: no return value, data pulished internally
199 """
200 logger_cli.debug(
201 "... collecting node pillars for '{}'".format(pillar_path)
202 )
203 _result = self.salt.pillar_get(self.active_nodes_compound, pillar_path)
204 self.not_responded = []
205 for node, data in self.nodes.items():
206 if node in self.skip_list:
207 logger_cli.debug(
208 "... '{}' skipped while collecting '{}'".format(
209 node,
210 pillar_path
211 )
212 )
213 continue
214 _pillar_keys = pillar_path.split(':')
215 _data = data['pillars']
216 # pre-create nested dict
217 for idx in range(0, len(_pillar_keys)-1):
218 _key = _pillar_keys[idx]
219 if _key not in _data:
220 _data[_key] = {}
221 _data = _data[_key]
222 if data['status'] == const.NODE_DOWN:
223 _data[_pillar_keys[-1]] = None
224 elif not _result[node]:
225 logger_cli.debug(
226 "... '{}' not responded after '{}'".format(
227 node,
228 config.salt_timeout
229 )
230 )
231 _data[_pillar_keys[-1]] = None
232 self.not_responded.append(node)
233 else:
234 _data[_pillar_keys[-1]] = _result[node]
236 def prepare_json_on_node(self, node, _dict, filename):
237 # this function assumes that all folders are created
238 _dumps = json.dumps(_dict, indent=2).splitlines()
239 _storage_path = os.path.join(
240 config.salt_file_root, config.salt_scripts_folder
241 )
242 logger_cli.debug(
243 "... uploading data as '{}' "
244 "to master's file cache folder: '{}'".format(
245 filename,
246 _storage_path
247 )
248 )
249 _cache_path = os.path.join(_storage_path, filename)
250 _source_path = os.path.join(
251 'salt://',
252 config.salt_scripts_folder,
253 filename
254 )
255 _target_path = os.path.join(
256 '/root',
257 config.salt_scripts_folder,
258 filename
259 )
261 logger_cli.debug("... creating file in cache '{}'".format(_cache_path))
262 self.salt.f_touch_master(_cache_path)
263 self.salt.f_append_master(_cache_path, _dumps)
264 logger.debug("... syncing file to '{}'".format(node))
265 self.salt.get_file(
266 node,
267 _source_path,
268 _target_path,
269 tgt_type="compound"
270 )
271 return _target_path
273 def prepare_script_on_active_nodes(self, script_filename):
274 # Prepare script
275 _p = os.path.join(pkg_dir, 'scripts', script_filename)
276 with open(_p, 'rt') as fd:
277 _script = fd.read().splitlines()
278 _storage_path = os.path.join(
279 config.salt_file_root, config.salt_scripts_folder
280 )
281 logger_cli.debug(
282 "... uploading script {} "
283 "to master's file cache folder: '{}'".format(
284 script_filename,
285 _storage_path
286 )
287 )
288 self.salt.mkdir(self.salt.master_node, _storage_path)
289 # Form cache, source and target path
290 _cache_path = os.path.join(_storage_path, script_filename)
291 _source_path = os.path.join(
292 'salt://',
293 config.salt_scripts_folder,
294 script_filename
295 )
296 _target_path = os.path.join(
297 '/root',
298 config.salt_scripts_folder,
299 script_filename
300 )
302 logger_cli.debug("... creating file in cache '{}'".format(_cache_path))
303 self.salt.f_touch_master(_cache_path)
304 self.salt.f_append_master(_cache_path, _script)
305 # command salt to copy file to minions
306 logger_cli.debug(
307 "... creating script target folder '{}'".format(
308 _cache_path
309 )
310 )
311 self.salt.mkdir(
312 self.active_nodes_compound,
313 os.path.join(
314 '/root',
315 config.salt_scripts_folder
316 ),
317 tgt_type="compound"
318 )
319 logger.debug("... syncing file to nodes")
320 self.salt.get_file(
321 self.active_nodes_compound,
322 _source_path,
323 _target_path,
324 tgt_type="compound"
325 )
326 # return path on nodes, just in case
327 return _target_path
329 def execute_script_on_node(self, node, script_filename, args=[]):
330 # Prepare path
331 _target_path = os.path.join(
332 '/root',
333 config.salt_scripts_folder,
334 script_filename
335 )
337 # execute script
338 logger.debug("... running script on '{}'".format(node))
339 # handle results for each node
340 _script_arguments = " ".join(args) if args else ""
341 self.not_responded = []
342 _r = self.salt.cmd(
343 node,
344 'cmd.run',
345 param='python {} {}'.format(_target_path, _script_arguments),
346 expr_form="compound"
347 )
349 # all false returns means that there is no response
350 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
351 return _r
353 def execute_script_on_active_nodes(self, script_filename, args=[]):
354 # Prepare path
355 _target_path = os.path.join(
356 '/root',
357 config.salt_scripts_folder,
358 script_filename
359 )
361 # execute script
362 logger_cli.debug("... running script")
363 # handle results for each node
364 _script_arguments = " ".join(args) if args else ""
365 self.not_responded = []
366 _r = self.salt.cmd(
367 self.active_nodes_compound,
368 'cmd.run',
369 param='python {} {}'.format(_target_path, _script_arguments),
370 expr_form="compound"
371 )
373 # all false returns means that there is no response
374 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
375 return _r
377 def execute_cmd_on_active_nodes(self, cmd, nodes=None):
378 # execute cmd
379 self.not_responded = []
380 _r = self.salt.cmd(
381 nodes if nodes else self.active_nodes_compound,
382 'cmd.run',
383 param=cmd,
384 expr_form="compound"
385 )
387 # all false returns means that there is no response
388 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
389 return _r
391 def is_node_available(self, node, log=True):
392 if node in self.skip_list:
393 if log:
394 logger_cli.info("-> node '{}' not active".format(node))
395 return False
396 elif node in self.not_responded:
397 if log:
398 logger_cli.info("-> node '{}' not responded".format(node))
399 return False
400 else:
401 return True
404salt_master = SaltNodes()