blob: 9a1fd48c147f3c32f0f3e7fa3299226816fc8eb9 [file] [log] [blame]
Alexe0c5b9e2019-04-23 18:51:23 -05001import json
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06002import os
Alex3ebc5632019-04-18 16:47:18 -05003from copy import deepcopy
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06004
Alexe0c5b9e2019-04-23 18:51:23 -05005from cfg_checker.clients import get_salt_remote, salt
Alex3ebc5632019-04-18 16:47:18 -05006from cfg_checker.common import config, const
Alex7c9494e2019-04-22 10:40:59 -05007from cfg_checker.common import logger, logger_cli
Alexe0c5b9e2019-04-23 18:51:23 -05008from cfg_checker.common import utils
9from cfg_checker.common.exception import SaltException
Alex7c9494e2019-04-22 10:40:59 -050010from cfg_checker.common.settings import pkg_dir
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060011
12node_tmpl = {
13 'role': '',
14 'node_group': '',
15 'status': const.NODE_DOWN,
16 'pillars': {},
17 'grains': {}
18}
19
20
21class SaltNodes(object):
22 def __init__(self):
Alexe0c5b9e2019-04-23 18:51:23 -050023 logger_cli.info("# Gathering environment information")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060024 # simple salt rest client
Alexe0c5b9e2019-04-23 18:51:23 -050025 self.salt = salt
26 self.nodes = None
Alex3ebc5632019-04-18 16:47:18 -050027
Alexe0c5b9e2019-04-23 18:51:23 -050028 def gather_node_info(self):
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060029 # Keys for all nodes
30 # this is not working in scope of 2016.8.3, will overide with list
Alexb151fbe2019-04-22 16:53:30 -050031 logger_cli.debug("... collecting node names existing in the cloud")
Alexe0c5b9e2019-04-23 18:51:23 -050032 if not self.salt:
33 self.salt = get_salt_remote(config)
34
Alex Savatieiev9df93a92019-02-27 17:40:16 -060035 try:
36 _keys = self.salt.list_keys()
37 _str = []
38 for _k, _v in _keys.iteritems():
39 _str.append("{}: {}".format(_k, len(_v)))
40 logger_cli.info("-> keys collected: {}".format(", ".join(_str)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060041
Alex Savatieiev9df93a92019-02-27 17:40:16 -060042 self.node_keys = {
43 'minions': _keys['minions']
44 }
Alex3ebc5632019-04-18 16:47:18 -050045 except Exception:
Alex Savatieiev9df93a92019-02-27 17:40:16 -060046 _keys = None
47 self.node_keys = None
Alex3ebc5632019-04-18 16:47:18 -050048
Alex Savatieiev9df93a92019-02-27 17:40:16 -060049 # List of minions with grains
50 _minions = self.salt.list_minions()
51 if _minions:
Alex3ebc5632019-04-18 16:47:18 -050052 logger_cli.info(
53 "-> api reported {} active minions".format(len(_minions))
54 )
Alex Savatieiev9df93a92019-02-27 17:40:16 -060055 elif not self.node_keys:
56 # this is the last resort
57 _minions = config.load_nodes_list()
Alex3ebc5632019-04-18 16:47:18 -050058 logger_cli.info(
59 "-> {} nodes loaded from list file".format(len(_minions))
60 )
Alex Savatieiev9df93a92019-02-27 17:40:16 -060061 else:
62 _minions = self.node_keys['minions']
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060063
Alex Savatieiev9df93a92019-02-27 17:40:16 -060064 # in case API not listed minions, we need all that answer ping
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060065 _active = self.salt.get_active_nodes()
Alex Savatieiev9df93a92019-02-27 17:40:16 -060066 logger_cli.info("-> nodes responded: {}".format(len(_active)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060067 # just inventory for faster interaction
68 # iterate through all accepted nodes and create a dict for it
69 self.nodes = {}
Alex Savatieievefa79c42019-03-14 19:14:04 -050070 self.skip_list = []
Alex Savatieiev9df93a92019-02-27 17:40:16 -060071 for _name in _minions:
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060072 _nc = utils.get_node_code(_name)
73 _rmap = const.all_roles_map
74 _role = _rmap[_nc] if _nc in _rmap else 'unknown'
75 _status = const.NODE_UP if _name in _active else const.NODE_DOWN
Alex Savatieievefa79c42019-03-14 19:14:04 -050076 if _status == const.NODE_DOWN:
77 self.skip_list.append(_name)
78 logger_cli.info("-> '{}' is down, marked to skip".format(
79 _name
80 ))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060081 self.nodes[_name] = deepcopy(node_tmpl)
82 self.nodes[_name]['node_group'] = _nc
83 self.nodes[_name]['role'] = _role
84 self.nodes[_name]['status'] = _status
Alex Savatieievefa79c42019-03-14 19:14:04 -050085 logger_cli.info("-> {} nodes inactive".format(len(self.skip_list)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060086 logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
87
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060088 # form an all nodes compound string to use in salt
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060089 self.active_nodes_compound = self.salt.compound_string_from_list(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060090 filter(
91 lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
92 self.nodes
93 )
94 )
Alex41485522019-04-12 17:26:18 -050095 # get master node fqdn
Alexe0c5b9e2019-04-23 18:51:23 -050096 _filtered = filter(
Alex41485522019-04-12 17:26:18 -050097 lambda nd: self.nodes[nd]['role'] == const.all_roles_map['cfg'],
98 self.nodes
Alexe0c5b9e2019-04-23 18:51:23 -050099 )
100 if len(_filtered) < 1:
101 raise SaltException(
102 "No master node detected! Check/Update node role map."
103 )
104 else:
105 self.salt.master_node = _filtered[0]
Alex3ebc5632019-04-18 16:47:18 -0500106
Alex41485522019-04-12 17:26:18 -0500107 # OpenStack versions
108 self.mcp_release = self.salt.pillar_get(
Alexe0c5b9e2019-04-23 18:51:23 -0500109 self.salt.master_node,
Alex41485522019-04-12 17:26:18 -0500110 "_param:apt_mk_version"
Alexe0c5b9e2019-04-23 18:51:23 -0500111 )[self.salt.master_node]
Alex41485522019-04-12 17:26:18 -0500112 self.openstack_release = self.salt.pillar_get(
Alexe0c5b9e2019-04-23 18:51:23 -0500113 self.salt.master_node,
Alex41485522019-04-12 17:26:18 -0500114 "_param:openstack_version"
Alexe0c5b9e2019-04-23 18:51:23 -0500115 )[self.salt.master_node]
Alex41485522019-04-12 17:26:18 -0500116
Alex Savatieieva1f6f8c2019-03-18 17:13:55 -0500117 def skip_node(self, node):
118 # Add node to skip list
119 # Fro example if it is fails to comply with the rules
120
121 # check if we know such node
122 if node in self.nodes.keys() and node not in self.skip_list:
123 # yes, add it
124 self.skip_list.append(node)
125 return True
126 else:
127 return False
128
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600129 def get_nodes(self):
Alexe0c5b9e2019-04-23 18:51:23 -0500130 if not self.nodes:
131 self.gather_node_info()
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600132 return self.nodes
133
134 def get_specific_pillar_for_nodes(self, pillar_path):
135 """Function gets pillars on given path for all nodes
136
137 :return: no return value, data pulished internally
138 """
Alex3ebc5632019-04-18 16:47:18 -0500139 logger_cli.debug(
Alexb151fbe2019-04-22 16:53:30 -0500140 "... collecting node pillars for '{}'".format(pillar_path)
Alex3ebc5632019-04-18 16:47:18 -0500141 )
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600142 _result = self.salt.pillar_get(self.active_nodes_compound, pillar_path)
Alex Savatieievefa79c42019-03-14 19:14:04 -0500143 self.not_responded = []
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600144 for node, data in self.nodes.iteritems():
Alex Savatieievefa79c42019-03-14 19:14:04 -0500145 if node in self.skip_list:
146 logger_cli.debug(
147 "... '{}' skipped while collecting '{}'".format(
148 node,
149 pillar_path
150 )
151 )
152 continue
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600153 _pillar_keys = pillar_path.split(':')
154 _data = data['pillars']
155 # pre-create nested dict
156 for idx in range(0, len(_pillar_keys)-1):
157 _key = _pillar_keys[idx]
158 if _key not in _data:
159 _data[_key] = {}
160 _data = _data[_key]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500161 if data['status'] == const.NODE_DOWN:
162 _data[_pillar_keys[-1]] = None
163 elif not _result[node]:
164 logger_cli.debug(
165 "... '{}' not responded after '{}'".format(
166 node,
167 config.salt_timeout
168 )
169 )
170 _data[_pillar_keys[-1]] = None
171 self.not_responded.append(node)
172 else:
173 _data[_pillar_keys[-1]] = _result[node]
Alex3ebc5632019-04-18 16:47:18 -0500174
Alexe0c5b9e2019-04-23 18:51:23 -0500175 def prepare_json_on_node(self, node, _dict, filename):
176 # this function assumes that all folders are created
177 _dumps = json.dumps(_dict, indent=2).splitlines()
178 _storage_path = os.path.join(
179 config.salt_file_root, config.salt_scripts_folder
180 )
181 logger_cli.debug(
182 "... uploading data as '{}' "
183 "to master's file cache folder: '{}'".format(
184 filename,
185 _storage_path
186 )
187 )
188 _cache_path = os.path.join(_storage_path, filename)
189 _source_path = os.path.join(
190 'salt://',
191 config.salt_scripts_folder,
192 filename
193 )
194 _target_path = os.path.join(
195 '/root',
196 config.salt_scripts_folder,
197 filename
198 )
199
200 logger_cli.debug("... creating file in cache '{}'".format(_cache_path))
201 self.salt.f_touch_master(_cache_path)
202 self.salt.f_append_master(_cache_path, _dumps)
203 logger.debug("... syncing file to '{}'".format(node))
204 self.salt.get_file(
205 node,
206 _source_path,
207 _target_path,
208 tgt_type="compound"
209 )
210 return _target_path
211
212 def prepare_script_on_active_nodes(self, script_filename):
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600213 # Prepare script
214 _p = os.path.join(pkg_dir, 'scripts', script_filename)
215 with open(_p, 'rt') as fd:
216 _script = fd.read().splitlines()
217 _storage_path = os.path.join(
218 config.salt_file_root, config.salt_scripts_folder
219 )
220 logger_cli.debug(
Alexb151fbe2019-04-22 16:53:30 -0500221 "... uploading script {} "
Alex3ebc5632019-04-18 16:47:18 -0500222 "to master's file cache folder: '{}'".format(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600223 script_filename,
224 _storage_path
225 )
226 )
Alexe0c5b9e2019-04-23 18:51:23 -0500227 self.salt.mkdir(self.salt.master_node, _storage_path)
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600228 # Form cache, source and target path
229 _cache_path = os.path.join(_storage_path, script_filename)
230 _source_path = os.path.join(
231 'salt://',
232 config.salt_scripts_folder,
233 script_filename
234 )
235 _target_path = os.path.join(
236 '/root',
237 config.salt_scripts_folder,
238 script_filename
239 )
240
Alexb151fbe2019-04-22 16:53:30 -0500241 logger_cli.debug("... creating file in cache '{}'".format(_cache_path))
Alex3ebc5632019-04-18 16:47:18 -0500242 self.salt.f_touch_master(_cache_path)
243 self.salt.f_append_master(_cache_path, _script)
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600244 # command salt to copy file to minions
Alex3ebc5632019-04-18 16:47:18 -0500245 logger_cli.debug(
Alexb151fbe2019-04-22 16:53:30 -0500246 "... creating script target folder '{}'".format(
Alex3ebc5632019-04-18 16:47:18 -0500247 _cache_path
248 )
249 )
250 self.salt.mkdir(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600251 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600252 os.path.join(
253 '/root',
254 config.salt_scripts_folder
255 ),
256 tgt_type="compound"
257 )
Alex3ebc5632019-04-18 16:47:18 -0500258 logger.debug("... syncing file to nodes")
259 self.salt.get_file(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600260 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600261 _source_path,
262 _target_path,
263 tgt_type="compound"
264 )
Alexe0c5b9e2019-04-23 18:51:23 -0500265 # return path on nodes, just in case
266 return _target_path
267
268 def execute_script_on_node(self, node, script_filename, args=[]):
269 # Prepare path
270 _target_path = os.path.join(
271 '/root',
272 config.salt_scripts_folder,
273 script_filename
274 )
275
276 # execute script
277 logger.debug("... running script on '{}'".format(node))
278 # handle results for each node
279 _script_arguments = " ".join(args) if args else ""
280 self.not_responded = []
281 _r = self.salt.cmd(
282 node,
283 'cmd.run',
284 param='python {} {}'.format(_target_path, _script_arguments),
285 expr_form="compound"
286 )
287
288 # all false returns means that there is no response
289 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
290 return _r
291
292 def execute_script_on_active_nodes(self, script_filename, args=[]):
293 # Prepare path
294 _target_path = os.path.join(
295 '/root',
296 config.salt_scripts_folder,
297 script_filename
298 )
299
300 # execute script
Alex3ebc5632019-04-18 16:47:18 -0500301 logger.debug("... running script")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600302 # handle results for each node
303 _script_arguments = " ".join(args) if args else ""
Alex Savatieievefa79c42019-03-14 19:14:04 -0500304 self.not_responded = []
305 _r = self.salt.cmd(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600306 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600307 'cmd.run',
308 param='python {} {}'.format(_target_path, _script_arguments),
309 expr_form="compound"
310 )
311
Alex Savatieievefa79c42019-03-14 19:14:04 -0500312 # all false returns means that there is no response
Alex3ebc5632019-04-18 16:47:18 -0500313 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500314 return _r
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600315
Alex Savatieievefa79c42019-03-14 19:14:04 -0500316 def is_node_available(self, node, log=True):
317 if node in self.skip_list:
318 if log:
319 logger_cli.info("-> node '{}' not active".format(node))
320 return False
321 elif node in self.not_responded:
322 if log:
323 logger_cli.info("-> node '{}' not responded".format(node))
324 return False
325 else:
326 return True
Alexe0c5b9e2019-04-23 18:51:23 -0500327
328
329salt_master = SaltNodes()