Hide keyboard shortcuts

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 

4 

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 

11 

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): 

23 logger_cli.info("# Gathering environment information") 

24 # simple salt rest client 

25 self.salt = salt 

26 self.nodes = None 

27 

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) 

34 

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))) 

41 

42 self.node_keys = { 

43 'minions': _keys['minions'] 

44 } 

45 except Exception: 

46 _keys = None 

47 self.node_keys = None 

48 

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'] 

63 

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))) 

86 

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] 

107 

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'] 

127 

128 def skip_node(self, node): 

129 # Add node to skip list 

130 # Fro example if it is fails to comply with the rules 

131 

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 

139 

140 def get_nodes(self): 

141 if not self.nodes: 

142 self.gather_node_info() 

143 return self.nodes 

144 

145 def get_info(self): 

146 _info = { 

147 'mcp_release': self.mcp_release, 

148 'openstack_release': self.openstack_release 

149 } 

150 return _info 

151 

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 

155 

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(): 

167 

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] 

194 

195 def get_specific_pillar_for_nodes(self, pillar_path): 

196 """Function gets pillars on given path for all nodes 

197 

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] 

235 

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 ) 

260 

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 

272 

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 ) 

301 

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 

328 

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 ) 

336 

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 ) 

348 

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 

352 

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 ) 

360 

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 ) 

372 

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 

376 

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 ) 

386 

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 

390 

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 

402 

403 

404salt_master = SaltNodes()