1 """Classes used by client.py"""
2 # -*- coding: utf-8 -*-
4 #Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
21 ID_RSA_PATH = '/home/opnfv/.ssh/id_rsa'
22 SSH_KEYS_SCRIPT = '/home/opnfv/barometer/baro_utils/get_ssh_keys.sh'
23 DEF_PLUGIN_INTERVAL = 10
24 COLLECTD_CONF = '/etc/collectd/collectd.conf'
25 COLLECTD_CONF_DIR = '/etc/collectd/collectd.conf.d'
29 """Node configuration class"""
30 def __init__(self, attrs):
31 self.__id = int(attrs[0])
32 self.__status = attrs[1]
33 self.__name = attrs[2]
34 self.__cluster = int(attrs[3]) if attrs[3] else None
37 self.__roles = [x.strip(' ') for x in attrs[6].split(',')]
38 self.__pending_roles = attrs[7]
39 self.__online = int(attrs[8]) if attrs[3] and attrs[8]else None
40 self.__group_id = int(attrs[9]) if attrs[3] else None
51 """Get node IP address"""
59 class ConfigServer(object):
60 """Class to get env configuration"""
61 def __init__(self, host, user, logger, passwd=None):
64 self.__passwd = passwd
65 self.__priv_key = None
67 self.__logger = logger
69 self.__private_key_file = ID_RSA_PATH
70 if not os.path.isfile(self.__private_key_file):
72 "Private key file '{}' not found.".format(self.__private_key_file))
73 raise IOError("Private key file '{}' not found.".format(self.__private_key_file))
75 # get list of available nodes
76 ssh, sftp = self.__open_sftp_session(self.__host, self.__user, self.__passwd)
78 fuel_node_passed = False
80 while (attempt <= 10) and not fuel_node_passed:
81 stdin, stdout, stderr = ssh.exec_command("fuel node")
82 stderr_lines = stderr.readlines()
84 self.__logger.warning("'fuel node' command failed (try {}):".format(attempt))
85 for line in stderr_lines:
86 self.__logger.debug(line.strip())
88 fuel_node_passed = True
90 self.__logger.info("'fuel node' command passed (try {})".format(attempt))
92 if not fuel_node_passed:
93 self.__logger.error("'fuel node' command failed. This was the last try.")
94 raise OSError("'fuel node' command failed. This was the last try.")
95 node_table = stdout.readlines()\
97 # skip table title and parse table values
98 for entry in node_table[2:]:
99 self.__nodes.append(Node([str(x.strip(' \n')) for x in entry.split('|')]))
101 def get_controllers(self):
102 """Get list of controllers"""
103 return [node for node in self.__nodes if 'controller' in node.get_roles()]
105 def get_computes(self):
106 """Get list of computes"""
107 return [node for node in self.__nodes if 'compute' in node.get_roles()]
110 """Get list of nodes"""
113 def __open_sftp_session(self, host, user, passwd=None):
114 """Connect to given host.
117 host -- host to connect
119 passwd -- password to use
121 Return tuple of SSH and SFTP client instances.
124 ssh = paramiko.SSHClient()
125 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
127 # try a direct access using password or private key
128 if not passwd and not self.__priv_key:
130 self.__priv_key = paramiko.RSAKey.from_private_key_file(self.__private_key_file)
132 # connect to the server
133 ssh.connect(host, username=user, password=passwd, pkey=self.__priv_key)
134 sftp = ssh.open_sftp()
136 # return SFTP client instance
139 def get_plugin_interval(self, compute, plugin):
140 """Find the plugin interval in collectd configuration.
143 compute -- compute node instance
144 plugin -- plug-in name
146 If found, return interval value, otherwise the default value"""
147 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
150 default_interval = DEF_PLUGIN_INTERVAL
151 config_files = [COLLECTD_CONF] \
152 + [COLLECTD_CONF_DIR + '/' + conf_file for conf_file in sftp.listdir(COLLECTD_CONF_DIR)]
153 for config_file in config_files:
155 with sftp.open(config_file) as config:
156 for line in config.readlines():
158 if len(words) > 1 and words[0] == '<LoadPlugin':
160 plugin_name = words[1].strip('">')
161 if words and words[0] == '</LoadPlugin>':
163 if words and words[0] == 'Interval':
164 if in_plugin and plugin_name == plugin:
167 default_interval = int(words[1])
169 self.__logger.error("Could not open collectd.conf file.")
170 return default_interval
172 def get_plugin_config_values(self, compute, plugin, parameter):
173 """Get parameter values from collectd config file.
176 compute -- compute node instance
177 plugin -- plug-in name
178 parameter -- plug-in parameter
180 Return list of found values."""
181 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
182 # find the plugin value
186 config_files = [COLLECTD_CONF] \
187 + [COLLECTD_CONF_DIR + '/' + conf_file for conf_file in sftp.listdir(COLLECTD_CONF_DIR)]
188 for config_file in config_files:
190 with sftp.open(config_file) as config:
191 for line in config.readlines():
193 if len(words) > 1 and words[0] == '<Plugin':
195 plugin_name = words[1].strip('">')
196 if len(words) > 0 and words[0] == '</Plugin>':
198 if len(words) > 0 and words[0] == parameter:
199 if in_plugin and plugin_name == plugin:
200 return [word.strip('"') for word in words[1:]]
202 self.__logger.error("Could not open collectd.conf file.")
203 return default_values
205 def execute_command(self, command, host_ip=None, ssh=None):
206 """Execute command on node and return list of lines of standard output.
210 host_ip -- IP of the node
211 ssh -- existing open SSH session to use
213 One of host_ip or ssh must not be None. If both are not None, existing ssh session is used.
215 if host_ip is None and ssh is None:
216 raise ValueError('One of host_ip or ssh must not be None.')
218 ssh, sftp = self.__open_sftp_session(host_ip, 'root')
219 stdin, stdout, stderr = ssh.exec_command(command)
220 return stdout.readlines()
222 def get_ovs_interfaces(self, compute):
223 """Get list of configured OVS interfaces
226 compute -- compute node instance
228 stdout = self.execute_command("ovs-vsctl list-br", compute.get_ip())
229 return [interface.strip() for interface in stdout]
231 def is_ceilometer_running(self, controller):
232 """Check whether Ceilometer is running on controller.
235 controller -- controller node instance
237 Return boolean value whether Ceilometer is running.
239 lines = self.execute_command('service --status-all | grep ceilometer', controller.get_ip())
243 if '[ + ] ceilometer-agent-notification' in line:
245 if '[ + ] ceilometer-collector' in line:
247 return agent and collector
249 def is_installed(self, compute, package):
250 """Check whether package exists on compute node.
253 compute -- compute node instance
254 package -- Linux package to search for
256 Return boolean value whether package is installed.
258 stdout = self.execute_command('dpkg -l | grep {}'.format(package), compute.get_ip())
259 return len(stdout) > 0
261 def check_ceil_plugin_included(self, compute):
262 """Check if ceilometer plugin is included in collectd.conf file If not,
266 compute -- compute node instance
268 Return boolean value whether ceilometer plugin is included or it's enabling was successful.
270 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
272 config = sftp.open(COLLECTD_CONF, mode='r')
275 'Cannot open {} on node {}'.format(COLLECTD_CONF, compute.get_id()))
277 in_lines = config.readlines()
278 out_lines = in_lines[:]
279 include_section_indexes = [
280 (start, end) for start in range(len(in_lines)) for end in range(len(in_lines))
282 and '<Include' in in_lines[start]
283 and COLLECTD_CONF_DIR in in_lines[start]
284 and '#' not in in_lines[start]
285 and '</Include>' in in_lines[end]
286 and '#' not in in_lines[end]
287 and len([i for i in in_lines[start + 1: end]
288 if 'Filter' in i and '*.conf' in i and '#' not in i]) > 0]
289 if len(include_section_indexes) == 0:
290 out_lines.append('<Include "{}">\n'.format(COLLECTD_CONF_DIR))
291 out_lines.append(' Filter "*.conf"\n')
292 out_lines.append('</Include>\n')
294 config = sftp.open(COLLECTD_CONF, mode='w')
295 config.writelines(out_lines)
297 self.__logger.info('Creating backup of collectd.conf...')
298 config = sftp.open(COLLECTD_CONF + '.backup', mode='w')
299 config.writelines(in_lines)
303 def enable_plugins(self, compute, plugins, error_plugins, create_backup=True):
304 """Enable plugins on compute node
307 compute -- compute node instance
308 plugins -- list of plugins to be enabled
309 error_plugins -- list of tuples with found errors, new entries may be added there
310 (plugin, error_description, is_critical):
311 plugin -- plug-in name
312 error_decription -- description of the error
313 is_critical -- boolean value indicating whether error is critical
314 create_backup -- boolean value indicating whether backup shall be created
316 Return boolean value indicating whether function was successful.
318 plugins = sorted(plugins)
319 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
320 plugins_to_enable = plugins[:]
321 for plugin in plugins:
322 plugin_file = '/usr/lib/collectd/{}.so'.format(plugin)
324 sftp.stat(plugin_file)
327 'Plugin file {0} not found on node {1}, plugin {2} will not be enabled'.format(
328 plugin_file, compute.get_id(), plugin))
329 error_plugins.append((plugin, 'plugin file {} not found'.format(plugin_file), True))
330 plugins_to_enable.remove(plugin)
331 self.__logger.debug('Following plugins will be enabled on node {}: {}'.format(
332 compute.get_id(), ', '.join(plugins_to_enable)))
334 config = sftp.open(COLLECTD_CONF, mode='r')
336 self.__logger.warning(
337 'Cannot open {} on node {}'.format(COLLECTD_CONF, compute.get_id()))
339 in_lines = config.readlines()
342 enabled_sections = []
344 comment_section = False
345 uncomment_section = False
346 for line in in_lines:
347 if 'LoadPlugin' in line:
348 for plugin in plugins_to_enable:
350 commented = '#' in line
351 #list of uncommented lines which contain LoadPlugin for this plugin
353 ll for ll in in_lines if 'LoadPlugin' in ll
354 and plugin in ll and '#' not in ll]
355 if len(loadlines) == 0:
356 if plugin not in enabled_plugins:
357 line = line.lstrip(string.whitespace + '#')
358 enabled_plugins.append(plugin)
359 error_plugins.append((
360 plugin, 'plugin not enabled in '
361 + '{}, trying to enable it'.format(COLLECTD_CONF), False))
363 if plugin not in enabled_plugins:
364 enabled_plugins.append(plugin)
367 error_plugins.append((
368 plugin, 'plugin enabled more than once '
369 + '(additional occurrence of LoadPlugin found in '
370 + '{}), trying to comment it out.'.format(
371 COLLECTD_CONF), False))
372 elif line.lstrip(string.whitespace + '#').find('<Plugin') == 0:
374 for plugin in plugins_to_enable:
376 commented = '#' in line
377 #list of uncommented lines which contain Plugin for this plugin
379 pl for pl in in_lines if '<Plugin' in pl
380 and plugin in pl and '#' not in pl]
381 if len(pluginlines) == 0:
382 if plugin not in enabled_sections:
383 line = line[line.rfind('#') + 1:]
384 uncomment_section = True
385 enabled_sections.append(plugin)
386 error_plugins.append((
387 plugin, 'plugin section found in '
388 + '{}, but commented out, trying to uncomment it.'.format(
389 COLLECTD_CONF), False))
391 if plugin not in enabled_sections:
392 enabled_sections.append(plugin)
395 comment_section = True
396 error_plugins.append((
398 'additional occurrence of plugin section found in '
399 + '{}, trying to comment it out.'.format(COLLECTD_CONF),
402 if comment_section and '#' not in line:
404 if uncomment_section and '#' in line:
405 line = line[line.rfind('#') + 1:]
406 if '</Plugin>' in line:
409 comment_section = False
410 uncomment_section = False
411 elif '</Plugin>' in line:
413 'Unexpected closure os plugin section on line'
414 + ' {} in collectd.conf, matching section start not found.'.format(
417 out_lines.append(line)
420 'Unexpected end of file collectd.conf, '
421 + 'closure of last plugin section not found.')
424 'LoadPlugin {}\n'.format(plugin) for plugin in plugins_to_enable
425 if plugin not in enabled_plugins] + out_lines
426 for plugin in plugins_to_enable:
427 if plugin not in enabled_plugins:
428 error_plugins.append((
430 'plugin not enabled in {}, trying to enable it.'.format(COLLECTD_CONF),
432 unenabled_sections = [
433 plugin for plugin in plugins_to_enable if plugin not in enabled_sections]
434 if unenabled_sections:
435 self.__logger.error('Plugin sections for following plugins not found: {}'.format(
436 ', '.join(unenabled_sections)))
441 self.__logger.info('Creating backup of collectd.conf...')
442 config = sftp.open(COLLECTD_CONF + '.backup', mode='w')
443 config.writelines(in_lines)
445 self.__logger.info('Updating collectd.conf...')
446 config = sftp.open(COLLECTD_CONF, mode='w')
447 config.writelines(out_lines)
449 diff_command = "diff {} {}.backup".format(COLLECTD_CONF, COLLECTD_CONF)
450 stdin, stdout, stderr = ssh.exec_command(diff_command)
451 self.__logger.debug(diff_command)
452 for line in stdout.readlines():
453 self.__logger.debug(line.strip())
456 def restore_config(self, compute):
457 """Restore collectd config file from backup on compute node.
460 compute -- compute node instance
462 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
464 self.__logger.info('Restoring config file from backup...')
465 ssh.exec_command("cp {0} {0}.used".format(COLLECTD_CONF))
466 ssh.exec_command("cp {0}.backup {0}".format(COLLECTD_CONF))
468 def restart_collectd(self, compute):
469 """Restart collectd on compute node.
472 compute -- compute node instance
474 Retrun tuple with boolean indicating success and list of warnings received
475 during collectd start.
478 def get_collectd_processes(ssh_session):
479 """Get number of running collectd processes.
482 ssh_session -- instance of SSH session in which to check for processes
484 stdin, stdout, stderr = ssh_session.exec_command("pgrep collectd")
485 return len(stdout.readlines())
487 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
489 self.__logger.info('Stopping collectd service...')
490 stdout = self.execute_command("service collectd stop", ssh=ssh)
492 if get_collectd_processes(ssh):
493 self.__logger.error('Collectd is still running...')
495 self.__logger.info('Starting collectd service...')
496 stdout = self.execute_command("service collectd start", ssh=ssh)
498 warning = [output.strip() for output in stdout if 'WARN: ' in output]
499 if get_collectd_processes(ssh) == 0:
500 self.__logger.error('Collectd is still not running...')
501 return False, warning