blob: 3990cecff031d61f38b35d10bc2ba1ea39b5f76b [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
Alex7c9494e2019-04-22 10:40:59 -05005from cfg_checker.common import logger, logger_cli
Alex3ebc5632019-04-18 16:47:18 -05006from cfg_checker.common import salt_utils, utils
Alex7c9494e2019-04-22 10:40:59 -05007from cfg_checker.common.settings import pkg_dir
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06008
9node_tmpl = {
10 'role': '',
11 'node_group': '',
12 'status': const.NODE_DOWN,
13 'pillars': {},
14 'grains': {}
15}
16
17
18class SaltNodes(object):
19 def __init__(self):
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060020 logger_cli.info("# Collecting nodes")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060021 # simple salt rest client
22 self.salt = salt_utils.SaltRemote()
Alex3ebc5632019-04-18 16:47:18 -050023
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060024 # Keys for all nodes
25 # this is not working in scope of 2016.8.3, will overide with list
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060026 logger_cli.debug("...collecting node names existing in the cloud")
Alex Savatieiev9df93a92019-02-27 17:40:16 -060027 try:
28 _keys = self.salt.list_keys()
29 _str = []
30 for _k, _v in _keys.iteritems():
31 _str.append("{}: {}".format(_k, len(_v)))
32 logger_cli.info("-> keys collected: {}".format(", ".join(_str)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060033
Alex Savatieiev9df93a92019-02-27 17:40:16 -060034 self.node_keys = {
35 'minions': _keys['minions']
36 }
Alex3ebc5632019-04-18 16:47:18 -050037 except Exception:
Alex Savatieiev9df93a92019-02-27 17:40:16 -060038 _keys = None
39 self.node_keys = None
Alex3ebc5632019-04-18 16:47:18 -050040
Alex Savatieiev9df93a92019-02-27 17:40:16 -060041 # List of minions with grains
42 _minions = self.salt.list_minions()
43 if _minions:
Alex3ebc5632019-04-18 16:47:18 -050044 logger_cli.info(
45 "-> api reported {} active minions".format(len(_minions))
46 )
Alex Savatieiev9df93a92019-02-27 17:40:16 -060047 elif not self.node_keys:
48 # this is the last resort
49 _minions = config.load_nodes_list()
Alex3ebc5632019-04-18 16:47:18 -050050 logger_cli.info(
51 "-> {} nodes loaded from list file".format(len(_minions))
52 )
Alex Savatieiev9df93a92019-02-27 17:40:16 -060053 else:
54 _minions = self.node_keys['minions']
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060055
Alex Savatieiev9df93a92019-02-27 17:40:16 -060056 # in case API not listed minions, we need all that answer ping
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060057 _active = self.salt.get_active_nodes()
Alex Savatieiev9df93a92019-02-27 17:40:16 -060058 logger_cli.info("-> nodes responded: {}".format(len(_active)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060059 # just inventory for faster interaction
60 # iterate through all accepted nodes and create a dict for it
61 self.nodes = {}
Alex Savatieievefa79c42019-03-14 19:14:04 -050062 self.skip_list = []
Alex Savatieiev9df93a92019-02-27 17:40:16 -060063 for _name in _minions:
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060064 _nc = utils.get_node_code(_name)
65 _rmap = const.all_roles_map
66 _role = _rmap[_nc] if _nc in _rmap else 'unknown'
67 _status = const.NODE_UP if _name in _active else const.NODE_DOWN
Alex Savatieievefa79c42019-03-14 19:14:04 -050068 if _status == const.NODE_DOWN:
69 self.skip_list.append(_name)
70 logger_cli.info("-> '{}' is down, marked to skip".format(
71 _name
72 ))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060073 self.nodes[_name] = deepcopy(node_tmpl)
74 self.nodes[_name]['node_group'] = _nc
75 self.nodes[_name]['role'] = _role
76 self.nodes[_name]['status'] = _status
Alex Savatieievefa79c42019-03-14 19:14:04 -050077 logger_cli.info("-> {} nodes inactive".format(len(self.skip_list)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060078 logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
79
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060080 # form an all nodes compound string to use in salt
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060081 self.active_nodes_compound = self.salt.compound_string_from_list(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060082 filter(
83 lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
84 self.nodes
85 )
86 )
Alex41485522019-04-12 17:26:18 -050087 # get master node fqdn
88 self.master_node = filter(
89 lambda nd: self.nodes[nd]['role'] == const.all_roles_map['cfg'],
90 self.nodes
91 )[0]
Alex3ebc5632019-04-18 16:47:18 -050092
Alex41485522019-04-12 17:26:18 -050093 # OpenStack versions
94 self.mcp_release = self.salt.pillar_get(
95 self.master_node,
96 "_param:apt_mk_version"
97 )[self.master_node]
98 self.openstack_release = self.salt.pillar_get(
99 self.master_node,
100 "_param:openstack_version"
101 )[self.master_node]
102
Alex Savatieieva1f6f8c2019-03-18 17:13:55 -0500103 def skip_node(self, node):
104 # Add node to skip list
105 # Fro example if it is fails to comply with the rules
106
107 # check if we know such node
108 if node in self.nodes.keys() and node not in self.skip_list:
109 # yes, add it
110 self.skip_list.append(node)
111 return True
112 else:
113 return False
114
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600115 def get_nodes(self):
116 return self.nodes
117
118 def get_specific_pillar_for_nodes(self, pillar_path):
119 """Function gets pillars on given path for all nodes
120
121 :return: no return value, data pulished internally
122 """
Alex3ebc5632019-04-18 16:47:18 -0500123 logger_cli.debug(
124 "...collecting node pillars for '{}'".format(pillar_path)
125 )
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600126 _result = self.salt.pillar_get(self.active_nodes_compound, pillar_path)
Alex Savatieievefa79c42019-03-14 19:14:04 -0500127 self.not_responded = []
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600128 for node, data in self.nodes.iteritems():
Alex Savatieievefa79c42019-03-14 19:14:04 -0500129 if node in self.skip_list:
130 logger_cli.debug(
131 "... '{}' skipped while collecting '{}'".format(
132 node,
133 pillar_path
134 )
135 )
136 continue
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600137 _pillar_keys = pillar_path.split(':')
138 _data = data['pillars']
139 # pre-create nested dict
140 for idx in range(0, len(_pillar_keys)-1):
141 _key = _pillar_keys[idx]
142 if _key not in _data:
143 _data[_key] = {}
144 _data = _data[_key]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500145 if data['status'] == const.NODE_DOWN:
146 _data[_pillar_keys[-1]] = None
147 elif not _result[node]:
148 logger_cli.debug(
149 "... '{}' not responded after '{}'".format(
150 node,
151 config.salt_timeout
152 )
153 )
154 _data[_pillar_keys[-1]] = None
155 self.not_responded.append(node)
156 else:
157 _data[_pillar_keys[-1]] = _result[node]
Alex3ebc5632019-04-18 16:47:18 -0500158
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600159 def execute_script_on_active_nodes(self, script_filename, args=[]):
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600160 # Prepare script
161 _p = os.path.join(pkg_dir, 'scripts', script_filename)
162 with open(_p, 'rt') as fd:
163 _script = fd.read().splitlines()
164 _storage_path = os.path.join(
165 config.salt_file_root, config.salt_scripts_folder
166 )
167 logger_cli.debug(
Alex3ebc5632019-04-18 16:47:18 -0500168 "...Uploading script {} "
169 "to master's file cache folder: '{}'".format(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600170 script_filename,
171 _storage_path
172 )
173 )
Alex3ebc5632019-04-18 16:47:18 -0500174 self.salt.mkdir("cfg01*", _storage_path)
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600175 # Form cache, source and target path
176 _cache_path = os.path.join(_storage_path, script_filename)
177 _source_path = os.path.join(
178 'salt://',
179 config.salt_scripts_folder,
180 script_filename
181 )
182 _target_path = os.path.join(
183 '/root',
184 config.salt_scripts_folder,
185 script_filename
186 )
187
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600188 logger_cli.debug("...creating file in cache '{}'".format(_cache_path))
Alex3ebc5632019-04-18 16:47:18 -0500189 self.salt.f_touch_master(_cache_path)
190 self.salt.f_append_master(_cache_path, _script)
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600191 # command salt to copy file to minions
Alex3ebc5632019-04-18 16:47:18 -0500192 logger_cli.debug(
193 "...creating script target folder '{}'".format(
194 _cache_path
195 )
196 )
197 self.salt.mkdir(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600198 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600199 os.path.join(
200 '/root',
201 config.salt_scripts_folder
202 ),
203 tgt_type="compound"
204 )
205 logger_cli.info("-> Running script to all active nodes")
Alex3ebc5632019-04-18 16:47:18 -0500206 logger.debug("... syncing file to nodes")
207 self.salt.get_file(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600208 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600209 _source_path,
210 _target_path,
211 tgt_type="compound"
212 )
213 # execute pkg collecting script
Alex3ebc5632019-04-18 16:47:18 -0500214 logger.debug("... running script")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600215 # handle results for each node
216 _script_arguments = " ".join(args) if args else ""
Alex Savatieievefa79c42019-03-14 19:14:04 -0500217 self.not_responded = []
218 _r = self.salt.cmd(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600219 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600220 'cmd.run',
221 param='python {} {}'.format(_target_path, _script_arguments),
222 expr_form="compound"
223 )
224
Alex Savatieievefa79c42019-03-14 19:14:04 -0500225 # all false returns means that there is no response
Alex3ebc5632019-04-18 16:47:18 -0500226 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500227 return _r
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600228
Alex Savatieievefa79c42019-03-14 19:14:04 -0500229 def is_node_available(self, node, log=True):
230 if node in self.skip_list:
231 if log:
232 logger_cli.info("-> node '{}' not active".format(node))
233 return False
234 elif node in self.not_responded:
235 if log:
236 logger_cli.info("-> node '{}' not responded".format(node))
237 return False
238 else:
239 return True