blob: d518e752a8512edf1a322a5b722af347076a7a9c [file] [log] [blame]
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06001import os
Alex3ebc5632019-04-18 16:47:18 -05002from copy import deepcopy
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06003
Alex3ebc5632019-04-18 16:47:18 -05004from cfg_checker.common import config, const
5from cfg_checker.common import logger, logger_cli, pkg_dir
6from cfg_checker.common import salt_utils, utils
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06007
8node_tmpl = {
9 'role': '',
10 'node_group': '',
11 'status': const.NODE_DOWN,
12 'pillars': {},
13 'grains': {}
14}
15
16
17class SaltNodes(object):
18 def __init__(self):
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060019 logger_cli.info("# Collecting nodes")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060020 # simple salt rest client
21 self.salt = salt_utils.SaltRemote()
Alex3ebc5632019-04-18 16:47:18 -050022
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060023 # Keys for all nodes
24 # this is not working in scope of 2016.8.3, will overide with list
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060025 logger_cli.debug("...collecting node names existing in the cloud")
Alex Savatieiev9df93a92019-02-27 17:40:16 -060026 try:
27 _keys = self.salt.list_keys()
28 _str = []
29 for _k, _v in _keys.iteritems():
30 _str.append("{}: {}".format(_k, len(_v)))
31 logger_cli.info("-> keys collected: {}".format(", ".join(_str)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060032
Alex Savatieiev9df93a92019-02-27 17:40:16 -060033 self.node_keys = {
34 'minions': _keys['minions']
35 }
Alex3ebc5632019-04-18 16:47:18 -050036 except Exception:
Alex Savatieiev9df93a92019-02-27 17:40:16 -060037 _keys = None
38 self.node_keys = None
Alex3ebc5632019-04-18 16:47:18 -050039
Alex Savatieiev9df93a92019-02-27 17:40:16 -060040 # List of minions with grains
41 _minions = self.salt.list_minions()
42 if _minions:
Alex3ebc5632019-04-18 16:47:18 -050043 logger_cli.info(
44 "-> api reported {} active minions".format(len(_minions))
45 )
Alex Savatieiev9df93a92019-02-27 17:40:16 -060046 elif not self.node_keys:
47 # this is the last resort
48 _minions = config.load_nodes_list()
Alex3ebc5632019-04-18 16:47:18 -050049 logger_cli.info(
50 "-> {} nodes loaded from list file".format(len(_minions))
51 )
Alex Savatieiev9df93a92019-02-27 17:40:16 -060052 else:
53 _minions = self.node_keys['minions']
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060054
Alex Savatieiev9df93a92019-02-27 17:40:16 -060055 # in case API not listed minions, we need all that answer ping
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060056 _active = self.salt.get_active_nodes()
Alex Savatieiev9df93a92019-02-27 17:40:16 -060057 logger_cli.info("-> nodes responded: {}".format(len(_active)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060058 # just inventory for faster interaction
59 # iterate through all accepted nodes and create a dict for it
60 self.nodes = {}
Alex Savatieievefa79c42019-03-14 19:14:04 -050061 self.skip_list = []
Alex Savatieiev9df93a92019-02-27 17:40:16 -060062 for _name in _minions:
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060063 _nc = utils.get_node_code(_name)
64 _rmap = const.all_roles_map
65 _role = _rmap[_nc] if _nc in _rmap else 'unknown'
66 _status = const.NODE_UP if _name in _active else const.NODE_DOWN
Alex Savatieievefa79c42019-03-14 19:14:04 -050067 if _status == const.NODE_DOWN:
68 self.skip_list.append(_name)
69 logger_cli.info("-> '{}' is down, marked to skip".format(
70 _name
71 ))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060072 self.nodes[_name] = deepcopy(node_tmpl)
73 self.nodes[_name]['node_group'] = _nc
74 self.nodes[_name]['role'] = _role
75 self.nodes[_name]['status'] = _status
Alex Savatieievefa79c42019-03-14 19:14:04 -050076 logger_cli.info("-> {} nodes inactive".format(len(self.skip_list)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060077 logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
78
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060079 # form an all nodes compound string to use in salt
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060080 self.active_nodes_compound = self.salt.compound_string_from_list(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060081 filter(
82 lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
83 self.nodes
84 )
85 )
Alex41485522019-04-12 17:26:18 -050086 # get master node fqdn
87 self.master_node = filter(
88 lambda nd: self.nodes[nd]['role'] == const.all_roles_map['cfg'],
89 self.nodes
90 )[0]
Alex3ebc5632019-04-18 16:47:18 -050091
Alex41485522019-04-12 17:26:18 -050092 # OpenStack versions
93 self.mcp_release = self.salt.pillar_get(
94 self.master_node,
95 "_param:apt_mk_version"
96 )[self.master_node]
97 self.openstack_release = self.salt.pillar_get(
98 self.master_node,
99 "_param:openstack_version"
100 )[self.master_node]
101
Alex Savatieieva1f6f8c2019-03-18 17:13:55 -0500102 def skip_node(self, node):
103 # Add node to skip list
104 # Fro example if it is fails to comply with the rules
105
106 # check if we know such node
107 if node in self.nodes.keys() and node not in self.skip_list:
108 # yes, add it
109 self.skip_list.append(node)
110 return True
111 else:
112 return False
113
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600114 def get_nodes(self):
115 return self.nodes
116
117 def get_specific_pillar_for_nodes(self, pillar_path):
118 """Function gets pillars on given path for all nodes
119
120 :return: no return value, data pulished internally
121 """
Alex3ebc5632019-04-18 16:47:18 -0500122 logger_cli.debug(
123 "...collecting node pillars for '{}'".format(pillar_path)
124 )
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600125 _result = self.salt.pillar_get(self.active_nodes_compound, pillar_path)
Alex Savatieievefa79c42019-03-14 19:14:04 -0500126 self.not_responded = []
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600127 for node, data in self.nodes.iteritems():
Alex Savatieievefa79c42019-03-14 19:14:04 -0500128 if node in self.skip_list:
129 logger_cli.debug(
130 "... '{}' skipped while collecting '{}'".format(
131 node,
132 pillar_path
133 )
134 )
135 continue
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600136 _pillar_keys = pillar_path.split(':')
137 _data = data['pillars']
138 # pre-create nested dict
139 for idx in range(0, len(_pillar_keys)-1):
140 _key = _pillar_keys[idx]
141 if _key not in _data:
142 _data[_key] = {}
143 _data = _data[_key]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500144 if data['status'] == const.NODE_DOWN:
145 _data[_pillar_keys[-1]] = None
146 elif not _result[node]:
147 logger_cli.debug(
148 "... '{}' not responded after '{}'".format(
149 node,
150 config.salt_timeout
151 )
152 )
153 _data[_pillar_keys[-1]] = None
154 self.not_responded.append(node)
155 else:
156 _data[_pillar_keys[-1]] = _result[node]
Alex3ebc5632019-04-18 16:47:18 -0500157
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600158 def execute_script_on_active_nodes(self, script_filename, args=[]):
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600159 # Prepare script
160 _p = os.path.join(pkg_dir, 'scripts', script_filename)
161 with open(_p, 'rt') as fd:
162 _script = fd.read().splitlines()
163 _storage_path = os.path.join(
164 config.salt_file_root, config.salt_scripts_folder
165 )
166 logger_cli.debug(
Alex3ebc5632019-04-18 16:47:18 -0500167 "...Uploading script {} "
168 "to master's file cache folder: '{}'".format(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600169 script_filename,
170 _storage_path
171 )
172 )
Alex3ebc5632019-04-18 16:47:18 -0500173 self.salt.mkdir("cfg01*", _storage_path)
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600174 # Form cache, source and target path
175 _cache_path = os.path.join(_storage_path, script_filename)
176 _source_path = os.path.join(
177 'salt://',
178 config.salt_scripts_folder,
179 script_filename
180 )
181 _target_path = os.path.join(
182 '/root',
183 config.salt_scripts_folder,
184 script_filename
185 )
186
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600187 logger_cli.debug("...creating file in cache '{}'".format(_cache_path))
Alex3ebc5632019-04-18 16:47:18 -0500188 self.salt.f_touch_master(_cache_path)
189 self.salt.f_append_master(_cache_path, _script)
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600190 # command salt to copy file to minions
Alex3ebc5632019-04-18 16:47:18 -0500191 logger_cli.debug(
192 "...creating script target folder '{}'".format(
193 _cache_path
194 )
195 )
196 self.salt.mkdir(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600197 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600198 os.path.join(
199 '/root',
200 config.salt_scripts_folder
201 ),
202 tgt_type="compound"
203 )
204 logger_cli.info("-> Running script to all active nodes")
Alex3ebc5632019-04-18 16:47:18 -0500205 logger.debug("... syncing file to nodes")
206 self.salt.get_file(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600207 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600208 _source_path,
209 _target_path,
210 tgt_type="compound"
211 )
212 # execute pkg collecting script
Alex3ebc5632019-04-18 16:47:18 -0500213 logger.debug("... running script")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600214 # handle results for each node
215 _script_arguments = " ".join(args) if args else ""
Alex Savatieievefa79c42019-03-14 19:14:04 -0500216 self.not_responded = []
217 _r = self.salt.cmd(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600218 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600219 'cmd.run',
220 param='python {} {}'.format(_target_path, _script_arguments),
221 expr_form="compound"
222 )
223
Alex Savatieievefa79c42019-03-14 19:14:04 -0500224 # all false returns means that there is no response
Alex3ebc5632019-04-18 16:47:18 -0500225 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500226 return _r
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600227
Alex Savatieievefa79c42019-03-14 19:14:04 -0500228 def is_node_available(self, node, log=True):
229 if node in self.skip_list:
230 if log:
231 logger_cli.info("-> node '{}' not active".format(node))
232 return False
233 elif node in self.not_responded:
234 if log:
235 logger_cli.info("-> node '{}' not responded".format(node))
236 return False
237 else:
238 return True