1 # -*- coding: utf-8 -*-
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
15 """Classes used by collectd.py"""
23 ID_RSA_PATH = '/home/opnfv/.ssh/id_rsa'
24 SSH_KEYS_SCRIPT = '/home/opnfv/barometer/baro_utils/get_ssh_keys.sh'
25 DEF_PLUGIN_INTERVAL = 10
26 COLLECTD_CONF = '/etc/collectd.conf'
27 COLLECTD_CONF_DIR = '/etc/collectd/collectd.conf.d'
28 NOTIFICATION_FILE = '/var/log/python-notifications.dump'
29 COLLECTD_NOTIFICATION = '/etc/collectd_notification_dump.py'
33 """Node configuration class"""
34 def __init__(self, attrs):
35 self.__null = attrs[0]
37 self.__name = attrs[2]
38 self.__status = attrs[3] if attrs[3] else None
39 self.__taskState = attrs[4]
40 self.__pwrState = attrs[5]
41 self.__ip = re.sub('^[a-z]+=', '', attrs[6])
52 """Get node IP address"""
60 class ConfigServer(object):
61 """Class to get env configuration"""
62 def __init__(self, host, user, logger, priv_key=None):
66 self.__priv_key = priv_key
68 self.__logger = logger
70 self.__private_key_file = ID_RSA_PATH
71 if not os.path.isfile(self.__private_key_file):
73 "Private key file '{}'".format(self.__private_key_file)
75 raise IOError("Private key file '{}' not found.".format(
76 self.__private_key_file))
78 # get list of available nodes
79 ssh, sftp = self.__open_sftp_session(
80 self.__host, self.__user, self.__passwd)
82 fuel_node_passed = False
84 while (attempt <= 10) and not fuel_node_passed:
85 stdin, stdout, stderr = ssh.exec_command(
86 "source stackrc; nova list")
87 stderr_lines = stderr.readlines()
89 self.__logger.warning(
90 "'fuel node' command failed (try {}):".format(attempt))
91 for line in stderr_lines:
92 self.__logger.debug(line.strip())
94 fuel_node_passed = True
97 "'fuel node' command passed (try {})".format(attempt))
99 if not fuel_node_passed:
101 "'fuel node' command failed. This was the last try.")
103 "'fuel node' command failed. This was the last try.")
104 node_table = stdout.readlines()\
106 # skip table title and parse table values
108 for entry in node_table[3:]:
109 if entry[0] == '+' or entry[0] == '\n':
114 Node([str(x.strip(' \n')) for x in entry.split('|')]))
116 def get_controllers(self):
117 # Get list of controllers
118 print self.__nodes[0]._Node__ip
120 [node for node in self.__nodes if 'controller' in node.get_name()])
122 def get_computes(self):
123 # Get list of computes
125 [node for node in self.__nodes if 'compute' in node.get_name()])
131 def __open_sftp_session(self, host, user, passwd=None):
132 # Connect to given host.
133 """Keyword arguments:
134 host -- host to connect
136 passwd -- password to use
138 Return tuple of SSH and SFTP client instances.
141 ssh = paramiko.SSHClient()
142 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
144 # try a direct access using password or private key
145 if not passwd and not self.__priv_key:
147 self.__priv_key = paramiko.RSAKey.from_private_key_file(
148 self.__private_key_file)
150 # connect to the server
152 host, username=user, password=passwd, pkey=self.__priv_key)
153 sftp = ssh.open_sftp()
155 # return SFTP client instance
158 def get_plugin_interval(self, compute, plugin):
159 """Find the plugin interval in collectd configuration.
162 compute -- compute node instance
163 plugin -- plug-in name
165 If found, return interval value, otherwise the default value"""
166 ssh, sftp = self.__open_sftp_session(
167 compute.get_ip(), 'root', 'opnfvapex')
170 default_interval = DEF_PLUGIN_INTERVAL
171 config_files = [COLLECTD_CONF] + [
172 COLLECTD_CONF_DIR + '/'
173 + conf_file for conf_file in sftp.listdir(COLLECTD_CONF_DIR)]
174 for config_file in config_files:
176 with sftp.open(config_file) as config:
177 for line in config.readlines():
179 if len(words) > 1 and words[0] == '<LoadPlugin':
181 plugin_name = words[1].strip('">')
182 if words and words[0] == '</LoadPlugin>':
184 if words and words[0] == 'Interval':
185 if in_plugin and plugin_name == plugin:
188 default_interval = int(words[1])
190 self.__logger.error("Could not open collectd.conf file.")
191 return default_interval
193 def get_plugin_config_values(self, compute, plugin, parameter):
194 """Get parameter values from collectd config file.
197 compute -- compute node instance
198 plugin -- plug-in name
199 parameter -- plug-in parameter
201 Return list of found values."""
202 ssh, sftp = self.__open_sftp_session(
203 compute.get_ip(), 'root', 'opnfvapex')
204 # find the plugin value
208 config_files = [COLLECTD_CONF] + [
209 COLLECTD_CONF_DIR + '/'
210 + conf_file for conf_file in sftp.listdir(COLLECTD_CONF_DIR)]
211 for config_file in config_files:
213 with sftp.open(config_file) as config:
214 for line in config.readlines():
216 if len(words) > 1 and words[0] == '<Plugin':
218 plugin_name = words[1].strip('">')
219 if len(words) > 0 and words[0] == '</Plugin>':
221 if len(words) > 0 and words[0] == parameter:
222 if in_plugin and plugin_name == plugin:
223 return [word.strip('"') for word in words[1:]]
225 self.__logger.error("Could not open collectd.conf file.")
226 return default_values
228 def execute_command(self, command, host_ip=None, ssh=None):
229 """Execute command on node and return list of lines of standard output.
233 host_ip -- IP of the node
234 ssh -- existing open SSH session to use
236 One of host_ip or ssh must not be None. If both are not None,
237 existing ssh session is used.
239 if host_ip is None and ssh is None:
240 raise ValueError('One of host_ip or ssh must not be None.')
242 ssh, sftp = self.__open_sftp_session(host_ip, 'root', 'opnfvapex')
243 stdin, stdout, stderr = ssh.exec_command(command)
244 return stdout.readlines()
246 def get_ovs_interfaces(self, compute):
247 """Get list of configured OVS interfaces
250 compute -- compute node instance
252 stdout = self.execute_command("ovs-vsctl list-br", compute.get_ip())
253 return [interface.strip() for interface in stdout]
255 def is_gnocchi_running(self, controller):
256 """Check whether Gnocchi is running on controller.
259 controller -- controller node instance
261 Return boolean value whether Gnocchi is running.
263 gnocchi_present = False
264 lines = self.execute_command(
265 'source overcloudrc.v3;openstack service list | grep gnocchi',
268 if 'gnocchi' in line:
269 gnocchi_present = True
270 return not gnocchi_present
272 def is_installed(self, compute, package):
273 """Check whether package exists on compute node.
276 compute -- compute node instance
277 package -- Linux package to search for
279 Return boolean value whether package is installed.
281 stdout = self.execute_command(
282 'yum list installed | grep {}'.format(package),
284 return len(stdout) > 0
286 def is_libpqos_on_node(self, compute):
287 """Check whether libpqos is present on compute node"""
288 ssh, sftp = self.__open_sftp_session(
289 compute.get_ip(), 'root', 'opnfvapex')
290 stdin, stdout, stderr = \
291 ssh.exec_command("ls /usr/local/lib/ | grep libpqos")
292 output = stdout.readlines()
298 def check_gnocchi_plugin_included(self, compute):
299 """Check if gnocchi plugin is included in collectd.conf file.
300 If not, try to enable it.
303 compute -- compute node instance
305 Return boolean value whether gnocchi plugin is included
306 or it's enabling was successful.
308 ssh, sftp = self.__open_sftp_session(
309 compute.get_ip(), 'root', 'opnfvapex')
311 config = sftp.open(COLLECTD_CONF, mode='r')
314 'Cannot open {} on node {}'.format(
315 COLLECTD_CONF, compute.get_name()))
317 in_lines = config.readlines()
318 out_lines = in_lines[:]
319 include_section_indexes = [
320 (start, end) for start in range(len(in_lines))
321 for end in range(len(in_lines))
323 and '<Include' in in_lines[start]
324 and COLLECTD_CONF_DIR in in_lines[start]
325 and '#' not in in_lines[start]
326 and '</Include>' in in_lines[end]
327 and '#' not in in_lines[end]
329 i for i in in_lines[start + 1: end]
330 if 'Filter' in i and '*.conf' in i and '#' not in i]) > 0]
331 if len(include_section_indexes) == 0:
332 out_lines.append('<Include "{}">\n'.format(COLLECTD_CONF_DIR))
333 out_lines.append(' Filter "*.conf"\n')
334 out_lines.append('</Include>\n')
336 config = sftp.open(COLLECTD_CONF, mode='w')
337 config.writelines(out_lines)
339 self.__logger.info('Creating backup of collectd.conf...')
340 config = sftp.open(COLLECTD_CONF + '.backup', mode='w')
341 config.writelines(in_lines)
345 def check_ceil_plugin_included(self, compute):
346 """Check if ceilometer plugin is included in collectd.conf file.
347 If not, try to enable it.
350 compute -- compute node instance
352 Return boolean value whether ceilometer plugin is included
353 or it's enabling was successful.
355 ssh, sftp = self.__open_sftp_session(compute.get_ip(), 'root')
357 config = sftp.open(COLLECTD_CONF, mode='r')
360 'Cannot open {} on node {}'.format(
361 COLLECTD_CONF, compute.get_id()))
363 in_lines = config.readlines()
364 out_lines = in_lines[:]
365 include_section_indexes = [
366 (start, end) for start in range(len(in_lines))
367 for end in range(len(in_lines))
369 and '<Include' in in_lines[start]
370 and COLLECTD_CONF_DIR in in_lines[start]
371 and '#' not in in_lines[start]
372 and '</Include>' in in_lines[end]
373 and '#' not in in_lines[end]
375 i for i in in_lines[start + 1: end]
376 if 'Filter' in i and '*.conf' in i and '#' not in i]) > 0]
377 if len(include_section_indexes) == 0:
378 out_lines.append('<Include "{}">\n'.format(COLLECTD_CONF_DIR))
379 out_lines.append(' Filter "*.conf"\n')
380 out_lines.append('</Include>\n')
382 config = sftp.open(COLLECTD_CONF, mode='w')
383 config.writelines(out_lines)
385 self.__logger.info('Creating backup of collectd.conf...')
386 config = sftp.open(COLLECTD_CONF + '.backup', mode='w')
387 config.writelines(in_lines)
392 self, compute, plugins, error_plugins, create_backup=True):
393 """Enable plugins on compute node
396 compute -- compute node instance
397 plugins -- list of plugins to be enabled
398 error_plugins -- list of tuples with found errors, new entries
399 may be added there (plugin, error_description, is_critical):
400 plugin -- plug-in name
401 error_decription -- description of the error
402 is_critical -- boolean value indicating whether error
404 create_backup -- boolean value indicating whether backup
407 Return boolean value indicating whether function was successful.
409 plugins = sorted(plugins)
410 ssh, sftp = self.__open_sftp_session(
411 compute.get_ip(), 'root', 'opnfvapex')
412 plugins_to_enable = plugins[:]
413 for plugin in plugins:
414 plugin_file = '/usr/lib64/collectd/{}.so'.format(plugin)
416 sftp.stat(plugin_file)
419 'Plugin file {} not found on node'.format(plugin_file)
420 + ' {0}, plugin {1} will not be enabled'.format(
421 compute.get_name(), plugin))
422 error_plugins.append((
423 plugin, 'plugin file {} not found'.format(plugin_file),
425 plugins_to_enable.remove(plugin)
427 'Following plugins will be enabled on node {}: {}'.format(
428 compute.get_name(), ', '.join(plugins_to_enable)))
430 config = sftp.open(COLLECTD_CONF, mode='r')
432 self.__logger.warning(
433 'Cannot open {} on node {}'.format(
434 COLLECTD_CONF, compute.get_name()))
436 in_lines = config.readlines()
439 enabled_sections = []
441 comment_section = False
442 uncomment_section = False
443 for line in in_lines:
444 if 'LoadPlugin' in line:
445 for plugin in plugins_to_enable:
447 commented = '#' in line
448 # list of uncommented lines which contain LoadPlugin
451 ll for ll in in_lines if 'LoadPlugin' in ll
452 and plugin in ll and '#' not in ll]
453 if len(loadlines) == 0:
454 if plugin not in enabled_plugins:
455 line = line.lstrip(string.whitespace + '#')
456 enabled_plugins.append(plugin)
457 error_plugins.append((
458 plugin, 'plugin not enabled in '
459 + '{}, trying to enable it'.format(
460 COLLECTD_CONF), False))
462 if plugin not in enabled_plugins:
463 enabled_plugins.append(plugin)
466 error_plugins.append((
467 plugin, 'plugin enabled more than once '
468 + '(additional occurrence of LoadPlugin '
469 + 'found in {}), '.format(COLLECTD_CONF)
470 + 'trying to comment it out.', False))
471 elif line.lstrip(string.whitespace + '#').find('<Plugin') == 0:
473 for plugin in plugins_to_enable:
475 commented = '#' in line
476 # list of uncommented lines which contain Plugin for
479 pl for pl in in_lines if '<Plugin' in pl
480 and plugin in pl and '#' not in pl]
481 if len(pluginlines) == 0:
482 if plugin not in enabled_sections:
483 line = line[line.rfind('#') + 1:]
484 uncomment_section = True
485 enabled_sections.append(plugin)
486 error_plugins.append((
487 plugin, 'plugin section found in '
488 + '{}, but commented'.format(COLLECTD_CONF)
489 + ' out, trying to uncomment it.', False))
491 if plugin not in enabled_sections:
492 enabled_sections.append(plugin)
495 comment_section = True
496 error_plugins.append((
497 plugin, 'additional occurrence of plugin '
498 + 'section found in {}'.format(
500 + ', trying to comment it out.', False))
502 if comment_section and '#' not in line:
504 if uncomment_section and '#' in line:
505 line = line[line.rfind('#') + 1:]
506 if '</Plugin>' in line:
509 comment_section = False
510 uncomment_section = False
511 elif '</Plugin>' in line:
513 'Unexpected closure os plugin section on line'
514 + ' {} in collectd.conf'.format(len(out_lines) + 1)
515 + ', matching section start not found.')
517 out_lines.append(line)
520 'Unexpected end of file collectd.conf, '
521 + 'closure of last plugin section not found.')
524 'LoadPlugin {}\n'.format(plugin) for plugin in plugins_to_enable
525 if plugin not in enabled_plugins] + out_lines
526 for plugin in plugins_to_enable:
527 if plugin not in enabled_plugins:
528 error_plugins.append((
529 plugin, 'plugin not enabled in {},'.format(COLLECTD_CONF)
530 + ' trying to enable it.', False))
531 unenabled_sections = [plugin for plugin in plugins_to_enable
532 if plugin not in enabled_sections]
533 if unenabled_sections:
535 'Plugin sections for following plugins not found: {}'.format(
536 ', '.join(unenabled_sections)))
541 self.__logger.info('Creating backup of collectd.conf...')
542 config = sftp.open(COLLECTD_CONF + '.backup', mode='w')
543 config.writelines(in_lines)
545 self.__logger.info('Updating collectd.conf...')
546 config = sftp.open(COLLECTD_CONF, mode='w')
547 config.writelines(out_lines)
550 "diff {} {}.backup".format(COLLECTD_CONF, COLLECTD_CONF)
551 stdin, stdout, stderr = ssh.exec_command(diff_command)
552 self.__logger.debug(diff_command)
553 for line in stdout.readlines():
554 self.__logger.debug(line.strip())
557 def restore_config(self, compute):
558 """Restore collectd config file from backup on compute node.
561 compute -- compute node instance
563 ssh, sftp = self.__open_sftp_session(
564 compute.get_ip(), 'root', 'opnfvapex')
566 self.__logger.info('Restoring config file from backup...')
567 ssh.exec_command("cp {0} {0}.used".format(COLLECTD_CONF))
568 ssh.exec_command("cp {0}.backup {0}".format(COLLECTD_CONF))
570 def restart_collectd(self, compute):
571 """Restart collectd on compute node.
574 compute -- compute node instance
576 Retrun tuple with boolean indicating success and list of warnings
577 received during collectd start.
580 def get_collectd_processes(ssh_session):
581 """Get number of running collectd processes.
584 ssh_session -- instance of SSH session in which to check
587 stdin, stdout, stderr = ssh_session.exec_command(
589 return len(stdout.readlines())
591 ssh, sftp = self.__open_sftp_session(
592 compute.get_ip(), 'root', 'opnfvapex')
594 self.__logger.info('Stopping collectd service...')
595 stdout = self.execute_command("service collectd stop", ssh=ssh)
597 if get_collectd_processes(ssh):
598 self.__logger.error('Collectd is still running...')
600 self.__logger.info('Starting collectd service...')
601 stdout = self.execute_command("service collectd start", ssh=ssh)
603 warning = [output.strip() for output in stdout if 'WARN: ' in output]
604 if get_collectd_processes(ssh) == 0:
605 self.__logger.error('Collectd is still not running...')
606 return False, warning
609 def test_gnocchi_is_sending_data(self, controller):
610 """ Checking if Gnocchi is sending metrics to controller"""
614 ssh, sftp = self.__open_sftp_session(
615 controller.get_ip(), 'root', 'opnfvapex')
617 self.__logger.info('Getting gnocchi metric list on{}'.format(
618 controller.get_name()))
619 stdout = self.execute_command(
620 "source overcloudrc.v3;gnocchi metric list | grep if_packets",
623 metric_ids = [r.split('|')[1] for r in stdout]
624 self.__logger.info("Metric ids = {}" .format(metric_ids))
625 for metric_id in metric_ids:
626 metric_id = metric_id.replace("u", "")
627 stdout = self.execute_command(
628 "source overcloudrc.v3;gnocchi measures show {}" .format(
630 self.__logger.info("stdout measures ={}" .format(stdout))
635 self.__logger.info("Line = {}" .format(line))
636 timestamps1 = [line.split('|')[1]]
637 self.__logger.info("Last line timetamp1 = {}" .format(timestamps1))
639 stdout = self.execute_command(
640 "source overcloudrc.v3;gnocchi measures show {}" .format(
646 timestamps2 = [line.split('|')[1]]
647 self.__logger.info("Last line timetamp2 = {}" .format(timestamps2))
648 if timestamps1 == timestamps2:
649 self.__logger.info("False")
653 self.__logger.info("True")