diff options
Diffstat (limited to 'baro_tests/collectd.py')
-rw-r--r-- | baro_tests/collectd.py | 519 |
1 files changed, 510 insertions, 9 deletions
diff --git a/baro_tests/collectd.py b/baro_tests/collectd.py index 5631cf55..3f2067af 100644 --- a/baro_tests/collectd.py +++ b/baro_tests/collectd.py @@ -1,15 +1,516 @@ -#!/usr/bin/python +"""Executing test of plugins""" +# -*- coding: utf-8 -*- -import sys +#Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import requests +from keystoneclient.v3 import client +import os +import time +import logging +from config_server import * +from tests import * -def main(logger): - logger.info("Running Baromtercollectd test suite...") - # - # TODO: implementation - # - logger.info("Test suite successfully completed.") +CEILOMETER_NAME = 'ceilometer' + + +class KeystoneException(Exception): + """Keystone exception class""" + def __init__(self, message, exc=None, response=None): + """ + Keyword arguments: + message -- error message + exc -- exception + response -- response + """ + if exc: + message += "\nReason: %s" % exc + super(KeystoneException, self).__init__(message) + + self.response = response + self.exception = exc + + +class InvalidResponse(KeystoneException): + """Invalid Keystone exception class""" + def __init__(self, exc, response): + """ + Keyword arguments: + exc -- exception + response -- response + """ + super(InvalidResponse, self).__init__( + "Invalid response", exc, response) + + +class CeilometerClient(object): + """Ceilometer Client to authenticate and request meters""" + def __init__(self, bc_logger): + """ + Keyword arguments: + bc_logger - logger instance + """ + self._auth_token = None + self._ceilometer_url = None + self._meter_list = None + self._logger = bc_logger + + def auth_token(self): + """Get auth token""" + self._auth_server() + return self._auth_token + + def get_ceilometer_url(self): + """Get Ceilometer URL""" + return self._ceilometer_url + + def get_ceil_metrics(self, criteria=None): + """Get Ceilometer metrics for given criteria + + Keyword arguments: + criteria -- criteria for ceilometer meter list + """ + self._request_meters(criteria) + return self._meter_list + + def _auth_server(self): + """Request token in authentication server""" + self._logger.debug('Connecting to the auth server {}'.format(os.environ['OS_AUTH_URL'])) + keystone = client.Client(username=os.environ['OS_USERNAME'], + password=os.environ['OS_PASSWORD'], + tenant_name=os.environ['OS_TENANT_NAME'], + auth_url=os.environ['OS_AUTH_URL']) + self._auth_token = keystone.auth_token + for service in keystone.service_catalog.get_data(): + if service['name'] == CEILOMETER_NAME: + for service_type in service['endpoints']: + if service_type['interface'] == 'internal': + self._ceilometer_url = service_type['url'] + break + + if self._ceilometer_url is None: + self._logger.warning('Ceilometer is not registered in service catalog') + + def _request_meters(self, criteria): + """Request meter list values from ceilometer + + Keyword arguments: + criteria -- criteria for ceilometer meter list + """ + if criteria is None: + url = self._ceilometer_url + ('/v2/samples?limit=400') + else: + url = self._ceilometer_url + ('/v2/meters/%s?q.field=resource_id&limit=400' % criteria) + headers = {'X-Auth-Token': self._auth_token} + resp = requests.get(url, headers=headers) + try: + resp.raise_for_status() + self._meter_list = resp.json() + except (KeyError, ValueError, requests.exceptions.HTTPError) as err: + raise InvalidResponse(err, resp) + + +class CSVClient(object): + """Client to request CSV meters""" + def __init__(self, bc_logger, conf): + """ + Keyword arguments: + bc_logger - logger instance + conf -- ConfigServer instance + """ + self._logger = bc_logger + self.conf = conf + + def get_csv_metrics(self, compute_node, plugin_subdirectories, meter_categories): + """Get CSV metrics. + + Keyword arguments: + compute_node -- compute node instance + plugin_subdirectories -- list of subdirectories of plug-in + meter_categories -- categories which will be tested + + Return list of metrics. + """ + stdout = self.conf.execute_command("date '+%Y-%m-%d'", compute_node.get_ip()) + date = stdout[0].strip() + metrics = [] + for plugin_subdir in plugin_subdirectories: + for meter_category in meter_categories: + stdout = self.conf.execute_command( + "tail -2 /var/lib/collectd/csv/node-" + + "{0}.domain.tld/{1}/{2}-{3}".format( + compute_node.get_id(), plugin_subdir, meter_category, date), + compute_node.get_ip()) + #Storing last two values + values = stdout + if len(values) < 2: + self._logger.error( + 'Getting last two CSV entries of meter category ' + + '{0} in {1} subdir failed'.format(meter_category, plugin_subdir)) + else: + old_value = int(values[0][0:values[0].index('.')]) + new_value = int(values[1][0:values[1].index('.')]) + metrics.append((plugin_subdir, meter_category, old_value, new_value)) + return metrics + + +def _check_logger(): + """Check whether there is global logger available and if not, define one.""" + if 'logger' not in globals(): + global logger + logger = logger.Logger("barometercollectd").getLogger() + + +def _process_result(compute_node, test, result, results_list): + """Print test result and append it to results list. + + Keyword arguments: + test -- testcase name + result -- boolean test result + results_list -- results list + """ + if result: + logger.info('Compute node {0} test case {1} PASSED.'.format(compute_node, test)) + else: + logger.error('Compute node {0} test case {1} FAILED.'.format(compute_node, test)) + results_list.append((compute_node, test, result)) + + +def _print_label(label): + """Print label on the screen + + Keyword arguments: + label -- label string + """ + label = label.strip() + length = 70 + if label != '': + label = ' ' + label + ' ' + length_label = len(label) + length1 = (length - length_label) / 2 + length2 = length - length_label - length1 + length1 = max(3, length1) + length2 = max(3, length2) + logger.info(('=' * length1) + label + ('=' * length2)) + + +def _print_plugin_label(plugin, node_id): + """Print plug-in label. + + Keyword arguments: + plugin -- plug-in name + node_id -- node ID + """ + _print_label('Node {0}: Plug-in {1} Test case execution'.format(node_id, plugin)) + + +def _print_final_result_of_plugin(plugin, compute_ids, results, out_plugins, out_plugin): + """Print final results of plug-in. + + Keyword arguments: + plugin -- plug-in name + compute_ids -- list of compute node IDs + results -- results list + out_plugins -- list of out plug-ins + out_plugin -- used out plug-in + """ + print_line = '' + for id in compute_ids: + if out_plugins[id] == out_plugin: + if (id, plugin, True) in results: + print_line += ' PASS |' + elif (id, plugin, False) in results and out_plugins[id] == out_plugin: + print_line += ' FAIL |' + else: + print_line += ' NOT EX |' + elif out_plugin == 'Ceilometer': + print_line += ' NOT EX |' + else: + print_line += ' SKIP |' + return print_line + + +def print_overall_summary(compute_ids, tested_plugins, results, out_plugins): + """Print overall summary table. + + Keyword arguments: + compute_ids -- list of compute IDs + tested_plugins -- list of plug-ins + results -- results list + out_plugins -- list of used out plug-ins + """ + compute_node_names = ['Node-{}'.format(id) for id in compute_ids] + all_computes_in_line = '' + for compute in compute_node_names: + all_computes_in_line = all_computes_in_line + '| ' + compute + (' ' * (7 - len(compute))) + line_of_nodes = '| Test ' + all_computes_in_line + '|' + logger.info('=' * 70) + logger.info('+' + ('-' * ((9 * len(compute_node_names))+16)) + '+') + logger.info( + '|' + ' ' * ((9*len(compute_node_names))/2) + ' OVERALL SUMMARY' + + ' ' * (9*len(compute_node_names) - (9*len(compute_node_names))/2) + '|') + logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names)) + logger.info(line_of_nodes) + logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names)) + out_plugins_print = ['Ceilometer'] + if 'CSV' in out_plugins.values(): + out_plugins_print.append('CSV') + for out_plugin in out_plugins_print: + output_plugins_line = '' + for id in compute_ids: + out_plugin_result = '----' + if out_plugin == 'Ceilometer': + out_plugin_result = 'PASS' if out_plugins[id] == out_plugin else 'FAIL' + if out_plugin == 'CSV': + if out_plugins[id] == out_plugin: + out_plugin_result = \ + 'PASS' if [ + plugin for comp_id, plugin, + res in results if comp_id == id and res] else 'FAIL' + else: + out_plugin_result = 'SKIP' + output_plugins_line += '| ' + out_plugin_result + ' ' + logger.info( + '| OUT:{}'.format(out_plugin) + (' ' * (11 - len(out_plugin))) + + output_plugins_line + '|') + for plugin in sorted(tested_plugins.values()): + line_plugin = _print_final_result_of_plugin( + plugin, compute_ids, results, out_plugins, out_plugin) + logger.info('| IN:{}'.format(plugin) + (' ' * (11-len(plugin))) + '|' + line_plugin) + logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names)) + logger.info('=' * 70) + + +def _exec_testcase( + test_labels, name, ceilometer_running, compute_node, + conf, results, error_plugins): + """Execute the testcase. + + Keyword arguments: + test_labels -- dictionary of plug-in IDs and their display names + name -- plug-in ID, key of test_labels dictionary + ceilometer_running -- boolean indicating whether Ceilometer is running + compute_node -- compute node ID + conf -- ConfigServer instance + results -- results list + error_plugins -- list of tuples with plug-in errors (plugin, error_description, is_critical): + plugin -- plug-in ID, key of test_labels dictionary + error_decription -- description of the error + is_critical -- boolean value indicating whether error is critical + """ + ovs_interfaces = conf.get_ovs_interfaces(compute_node) + ovs_configured_interfaces = conf.get_plugin_config_values( + compute_node, 'ovs_events', 'Interfaces') + ovs_existing_configured_int = [ + interface for interface in ovs_interfaces + if interface in ovs_configured_interfaces] + plugin_prerequisites = { + 'mcelog': [(conf.is_installed(compute_node, 'mcelog'), 'mcelog must be installed.')], + 'ovs_events': [( + len(ovs_existing_configured_int) > 0 or len(ovs_interfaces) > 0, + 'Interfaces must be configured.')]} + ceilometer_criteria_lists = { + 'hugepages': ['hugepages.vmpage_number'], + 'mcelog': ['mcelog.errors'], + 'ovs_events': ['ovs_events.gauge']} + ceilometer_substr_lists = { + 'ovs_events': ovs_existing_configured_int if len(ovs_existing_configured_int) > 0 else ovs_interfaces} + csv_subdirs = { + 'hugepages': [ + 'hugepages-mm-2048Kb', 'hugepages-node0-2048Kb', 'hugepages-node1-2048Kb', + 'hugepages-mm-1048576Kb', 'hugepages-node0-1048576Kb', 'hugepages-node1-1048576Kb'], + 'mcelog': ['mcelog-SOCKET_0_CHANNEL_0_DIMM_any', 'mcelog-SOCKET_0_CHANNEL_any_DIMM_any'], + 'ovs_events': [ + 'ovs_events-{}'.format(interface) + for interface in (ovs_existing_configured_int if len(ovs_existing_configured_int) > 0 else ovs_interfaces)]} + csv_meter_categories = { + 'hugepages': ['vmpage_number-free', 'vmpage_number-used'], + 'mcelog': [ + 'errors-corrected_memory_errors', 'errors-uncorrected_memory_errors', + 'errors-corrected_memory_errors_in_24h', 'errors-uncorrected_memory_errors_in_24h'], + 'ovs_events': ['gauge-link_status']} + + _print_plugin_label(test_labels[name] if name in test_labels else name, compute_node.get_id()) + plugin_critical_errors = [ + error for plugin, error, critical in error_plugins if plugin == name and critical] + if plugin_critical_errors: + logger.error('Following critical errors occurred:'.format(name)) + for error in plugin_critical_errors: + logger.error(' * ' + error) + _process_result(compute_node.get_id(), test_labels[name], False, results) + else: + plugin_errors = [ + error for plugin, error, critical in error_plugins if plugin == name and not critical] + if plugin_errors: + logger.warning('Following non-critical errors occured:') + for error in plugin_errors: + logger.warning(' * ' + error) + failed_prerequisites = [] + if name in plugin_prerequisites: + failed_prerequisites = [ + prerequisite_name for prerequisite_passed, + prerequisite_name in plugin_prerequisites[name] if not prerequisite_passed] + if failed_prerequisites: + logger.error( + '{} test will not be executed, '.format(name) + + 'following prerequisites failed:') + for prerequisite in failed_prerequisites: + logger.error(' * {}'.format(prerequisite)) + else: + if ceilometer_running: + res = test_ceilometer_node_sends_data( + compute_node.get_id(), conf.get_plugin_interval(compute_node, name), + logger=logger, client=CeilometerClient(logger), + criteria_list=ceilometer_criteria_lists[name], + resource_id_substrings = (ceilometer_substr_lists[name] + if name in ceilometer_substr_lists else [''])) + else: + res = test_csv_handles_plugin_data( + compute_node, conf.get_plugin_interval(compute_node, name), name, + csv_subdirs[name], csv_meter_categories[name], logger, + CSVClient(logger, conf)) + if res and plugin_errors: + logger.info( + 'Test works, but will be reported as failure,' + + 'because of non-critical errors.') + res = False + _process_result(compute_node.get_id(), test_labels[name], res, results) + + +def main(bt_logger=None): + """Check each compute node sends ceilometer metrics. + + Keyword arguments: + bt_logger -- logger instance + """ + logging.getLogger("paramiko").setLevel(logging.WARNING) + logging.getLogger("stevedore").setLevel(logging.WARNING) + if bt_logger is None: + _check_logger() + else: + global logger + logger = bt_logger + conf = ConfigServer('10.20.0.2', 'root', logger) + controllers = conf.get_controllers() + if len(controllers) == 0: + logger.error('No controller nodes found!') + return 1 + computes = conf.get_computes() + if len(computes) == 0: + logger.error('No compute nodes found!') + return 1 + + _print_label('Display of Control and Compute nodes available in the set up') + logger.info('controllers: {}'.format([('{0}: {1} ({2})'.format( + node.get_id(), node.get_name(), node.get_ip())) for node in controllers])) + logger.info('computes: {}'.format([('{0}: {1} ({2})'.format( + node.get_id(), node.get_name(), node.get_ip())) for node in computes])) + + ceilometer_running_on_con = False + _print_label('Test Ceilometer on control nodes') + for controller in controllers: + ceil_client = CeilometerClient(logger) + ceil_client.auth_token() + ceilometer_running_on_con = ( + ceilometer_running_on_con or conf.is_ceilometer_running(controller)) + if ceilometer_running_on_con: + logger.info("Ceilometer is running on control node.") + else: + logger.error("Ceilometer is not running on control node.") + logger.info("CSV will be enabled on compute nodes.") + compute_ids = [] + results = [] + plugin_labels = { + 'hugepages': 'Hugepages', + 'mcelog': 'Mcelog', + 'ovs_events': 'OVS events'} + out_plugins = {} + for compute_node in computes: + node_id = compute_node.get_id() + out_plugins[node_id] = 'CSV' + compute_ids.append(node_id) + #plugins_to_enable = plugin_labels.keys() + plugins_to_enable = [] + _print_label('NODE {}: Test Ceilometer Plug-in'.format(node_id)) + logger.info('Checking if ceilometer plug-in is included.') + if not conf.check_ceil_plugin_included(compute_node): + logger.error('Ceilometer plug-in is not included.') + logger.info('Testcases on node {} will not be executed'.format(node_id)) + else: + collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node) + sleep_time = 30 + logger.info('Sleeping for {} seconds after collectd restart...'.format(sleep_time)) + time.sleep(sleep_time) + if not collectd_restarted: + for warning in collectd_warnings: + logger.warning(warning) + logger.error('Restart of collectd on node {} failed'.format(node_id)) + logger.info('Testcases on node {} will not be executed'.format(node_id)) + else: + for warning in collectd_warnings: + logger.warning(warning) + ceilometer_running = ( + ceilometer_running_on_con and test_ceilometer_node_sends_data( + node_id, 10, logger=logger, client=CeilometerClient(logger))) + if ceilometer_running: + out_plugins[node_id] = 'Ceilometer' + logger.info("Ceilometer is running.") + else: + plugins_to_enable.append('csv') + out_plugins[node_id] = 'CSV' + logger.error("Ceilometer is not running.") + logger.info("CSV will be enabled for verification of test plugins.") + if plugins_to_enable: + _print_label( + 'NODE {}: Enabling Test Plug-in '.format(node_id) + + 'and Test case execution') + error_plugins = [] + if plugins_to_enable and not conf.enable_plugins( + compute_node, plugins_to_enable, error_plugins, create_backup=False): + logger.error('Failed to test plugins on node {}.'.format(node_id)) + logger.info('Testcases on node {} will not be executed'.format(node_id)) + else: + if plugins_to_enable: + collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node) + sleep_time = 30 + logger.info( + 'Sleeping for {} seconds after collectd restart...'.format(sleep_time)) + time.sleep(sleep_time) + if plugins_to_enable and not collectd_restarted: + for warning in collectd_warnings: + logger.warning(warning) + logger.error('Restart of collectd on node {} failed'.format(node_id)) + logger.info('Testcases on node {} will not be executed'.format(node_id)) + else: + if collectd_warnings: + for warning in collectd_warnings: + logger.warning(warning) + + for plugin_name in sorted(plugin_labels.keys()): + _exec_testcase( + plugin_labels, plugin_name, ceilometer_running, + compute_node, conf, results, error_plugins) + + _print_label('NODE {}: Restoring config file'.format(node_id)) + conf.restore_config(compute_node) + + print_overall_summary(compute_ids, plugin_labels, results, out_plugins) + + if ((len([res for res in results if not res[2]]) > 0) + or (len(results) < len(computes) * len(plugin_labels))): + logger.error('Some tests have failed or have not been executed') + return 1 return 0 if __name__ == '__main__': - sys.exit(main()) + sys.exit(main()) |