blob: 776c8b2cba13df2c8f9d35269deffffa6421444e [file] [log] [blame]
Alex Savatieiev9b2f6512019-02-20 18:05:00 -06001import json
2import os
3import sys
4
5from copy import deepcopy
6
7from cfg_checker.common import utils, const
8from cfg_checker.common import config, logger, logger_cli, pkg_dir
9from cfg_checker.common import salt_utils
10
11node_tmpl = {
12 'role': '',
13 'node_group': '',
14 'status': const.NODE_DOWN,
15 'pillars': {},
16 'grains': {}
17}
18
19
20class SaltNodes(object):
21 def __init__(self):
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060022 logger_cli.info("# Collecting nodes")
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060023 # simple salt rest client
24 self.salt = salt_utils.SaltRemote()
Alex41485522019-04-12 17:26:18 -050025
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060026 # Keys for all nodes
27 # this is not working in scope of 2016.8.3, will overide with list
Alex Savatieiev42b89fa2019-03-07 18:45:26 -060028 logger_cli.debug("...collecting node names existing in the cloud")
Alex Savatieiev9df93a92019-02-27 17:40:16 -060029 try:
30 _keys = self.salt.list_keys()
31 _str = []
32 for _k, _v in _keys.iteritems():
33 _str.append("{}: {}".format(_k, len(_v)))
34 logger_cli.info("-> keys collected: {}".format(", ".join(_str)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060035
Alex Savatieiev9df93a92019-02-27 17:40:16 -060036 self.node_keys = {
37 'minions': _keys['minions']
38 }
39 except Exception as e:
40 _keys = None
41 self.node_keys = None
42
43 # List of minions with grains
44 _minions = self.salt.list_minions()
45 if _minions:
46 logger_cli.info("-> api reported {} active minions".format(len(_minions)))
47 elif not self.node_keys:
48 # this is the last resort
49 _minions = config.load_nodes_list()
50 logger_cli.info("-> {} nodes loaded from list file".format(len(_minions)))
51 else:
52 _minions = self.node_keys['minions']
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060053
Alex Savatieiev9df93a92019-02-27 17:40:16 -060054 # in case API not listed minions, we need all that answer ping
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060055 _active = self.salt.get_active_nodes()
Alex Savatieiev9df93a92019-02-27 17:40:16 -060056 logger_cli.info("-> nodes responded: {}".format(len(_active)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060057 # just inventory for faster interaction
58 # iterate through all accepted nodes and create a dict for it
59 self.nodes = {}
Alex Savatieievefa79c42019-03-14 19:14:04 -050060 self.skip_list = []
Alex Savatieiev9df93a92019-02-27 17:40:16 -060061 for _name in _minions:
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060062 _nc = utils.get_node_code(_name)
63 _rmap = const.all_roles_map
64 _role = _rmap[_nc] if _nc in _rmap else 'unknown'
65 _status = const.NODE_UP if _name in _active else const.NODE_DOWN
Alex Savatieievefa79c42019-03-14 19:14:04 -050066 if _status == const.NODE_DOWN:
67 self.skip_list.append(_name)
68 logger_cli.info("-> '{}' is down, marked to skip".format(
69 _name
70 ))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060071 self.nodes[_name] = deepcopy(node_tmpl)
72 self.nodes[_name]['node_group'] = _nc
73 self.nodes[_name]['role'] = _role
74 self.nodes[_name]['status'] = _status
Alex Savatieievefa79c42019-03-14 19:14:04 -050075 logger_cli.info("-> {} nodes inactive".format(len(self.skip_list)))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060076 logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
77
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060078 # form an all nodes compound string to use in salt
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -060079 self.active_nodes_compound = self.salt.compound_string_from_list(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -060080 filter(
81 lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
82 self.nodes
83 )
84 )
Alex41485522019-04-12 17:26:18 -050085 # get master node fqdn
86 self.master_node = filter(
87 lambda nd: self.nodes[nd]['role'] == const.all_roles_map['cfg'],
88 self.nodes
89 )[0]
90
91 # OpenStack versions
92 self.mcp_release = self.salt.pillar_get(
93 self.master_node,
94 "_param:apt_mk_version"
95 )[self.master_node]
96 self.openstack_release = self.salt.pillar_get(
97 self.master_node,
98 "_param:openstack_version"
99 )[self.master_node]
100
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600101
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 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600122 logger_cli.debug("...collecting node pillars for '{}'".format(pillar_path))
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600123 _result = self.salt.pillar_get(self.active_nodes_compound, pillar_path)
Alex Savatieievefa79c42019-03-14 19:14:04 -0500124 self.not_responded = []
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600125 for node, data in self.nodes.iteritems():
Alex Savatieievefa79c42019-03-14 19:14:04 -0500126 if node in self.skip_list:
127 logger_cli.debug(
128 "... '{}' skipped while collecting '{}'".format(
129 node,
130 pillar_path
131 )
132 )
133 continue
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600134 _pillar_keys = pillar_path.split(':')
135 _data = data['pillars']
136 # pre-create nested dict
137 for idx in range(0, len(_pillar_keys)-1):
138 _key = _pillar_keys[idx]
139 if _key not in _data:
140 _data[_key] = {}
141 _data = _data[_key]
Alex Savatieievefa79c42019-03-14 19:14:04 -0500142 if data['status'] == const.NODE_DOWN:
143 _data[_pillar_keys[-1]] = None
144 elif not _result[node]:
145 logger_cli.debug(
146 "... '{}' not responded after '{}'".format(
147 node,
148 config.salt_timeout
149 )
150 )
151 _data[_pillar_keys[-1]] = None
152 self.not_responded.append(node)
153 else:
154 _data[_pillar_keys[-1]] = _result[node]
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600155
156 def execute_script_on_active_nodes(self, script_filename, args=[]):
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600157 # Prepare script
158 _p = os.path.join(pkg_dir, 'scripts', script_filename)
159 with open(_p, 'rt') as fd:
160 _script = fd.read().splitlines()
161 _storage_path = os.path.join(
162 config.salt_file_root, config.salt_scripts_folder
163 )
164 logger_cli.debug(
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600165 "...Uploading script {} to master's file cache folder: '{}'".format(
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600166 script_filename,
167 _storage_path
168 )
169 )
170 _result = self.salt.mkdir("cfg01*", _storage_path)
171 # Form cache, source and target path
172 _cache_path = os.path.join(_storage_path, script_filename)
173 _source_path = os.path.join(
174 'salt://',
175 config.salt_scripts_folder,
176 script_filename
177 )
178 _target_path = os.path.join(
179 '/root',
180 config.salt_scripts_folder,
181 script_filename
182 )
183
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600184 logger_cli.debug("...creating file in cache '{}'".format(_cache_path))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600185 _result = self.salt.f_touch_master(_cache_path)
186 _result = self.salt.f_append_master(_cache_path, _script)
187 # command salt to copy file to minions
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600188 logger_cli.debug("...creating script target folder '{}'".format(_cache_path))
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600189 _result = self.salt.mkdir(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600190 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600191 os.path.join(
192 '/root',
193 config.salt_scripts_folder
194 ),
195 tgt_type="compound"
196 )
197 logger_cli.info("-> Running script to all active nodes")
198 _result = self.salt.get_file(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600199 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600200 _source_path,
201 _target_path,
202 tgt_type="compound"
203 )
204 # execute pkg collecting script
205 logger.debug("Running script to all nodes")
206 # handle results for each node
207 _script_arguments = " ".join(args) if args else ""
Alex Savatieievefa79c42019-03-14 19:14:04 -0500208 self.not_responded = []
209 _r = self.salt.cmd(
Alex Savatieiev01f0d7f2019-03-07 17:53:29 -0600210 self.active_nodes_compound,
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600211 'cmd.run',
212 param='python {} {}'.format(_target_path, _script_arguments),
213 expr_form="compound"
214 )
215
Alex Savatieievefa79c42019-03-14 19:14:04 -0500216 # all false returns means that there is no response
217 self.not_responded = [_n for _n in _r.keys() if not _r[_n]]
218 return _r
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600219
Alex Savatieievefa79c42019-03-14 19:14:04 -0500220 def is_node_available(self, node, log=True):
221 if node in self.skip_list:
222 if log:
223 logger_cli.info("-> node '{}' not active".format(node))
224 return False
225 elif node in self.not_responded:
226 if log:
227 logger_cli.info("-> node '{}' not responded".format(node))
228 return False
229 else:
230 return True
231