1 """Executing test of plugins"""
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
17 from keystoneclient.v3 import client
21 from config_server import *
24 CEILOMETER_NAME = 'ceilometer'
27 class KeystoneException(Exception):
28 """Keystone exception class"""
29 def __init__(self, message, exc=None, response=None):
32 message -- error message
37 message += "\nReason: %s" % exc
38 super(KeystoneException, self).__init__(message)
40 self.response = response
44 class InvalidResponse(KeystoneException):
45 """Invalid Keystone exception class"""
46 def __init__(self, exc, response):
52 super(InvalidResponse, self).__init__(
53 "Invalid response", exc, response)
56 class CeilometerClient(object):
57 """Ceilometer Client to authenticate and request meters"""
58 def __init__(self, bc_logger):
61 bc_logger - logger instance
63 self._auth_token = None
64 self._ceilometer_url = None
65 self._meter_list = None
66 self._logger = bc_logger
71 return self._auth_token
73 def get_ceilometer_url(self):
74 """Get Ceilometer URL"""
75 return self._ceilometer_url
77 def get_ceil_metrics(self, criteria=None):
78 """Get Ceilometer metrics for given criteria
81 criteria -- criteria for ceilometer meter list
83 self._request_meters(criteria)
84 return self._meter_list
86 def _auth_server(self):
87 """Request token in authentication server"""
88 self._logger.debug('Connecting to the auth server {}'.format(os.environ['OS_AUTH_URL']))
89 keystone = client.Client(username=os.environ['OS_USERNAME'],
90 password=os.environ['OS_PASSWORD'],
91 tenant_name=os.environ['OS_TENANT_NAME'],
92 auth_url=os.environ['OS_AUTH_URL'])
93 self._auth_token = keystone.auth_token
94 for service in keystone.service_catalog.get_data():
95 if service['name'] == CEILOMETER_NAME:
96 for service_type in service['endpoints']:
97 if service_type['interface'] == 'internal':
98 self._ceilometer_url = service_type['url']
101 if self._ceilometer_url is None:
102 self._logger.warning('Ceilometer is not registered in service catalog')
104 def _request_meters(self, criteria):
105 """Request meter list values from ceilometer
108 criteria -- criteria for ceilometer meter list
111 url = self._ceilometer_url + ('/v2/samples?limit=400')
113 url = self._ceilometer_url + ('/v2/meters/%s?q.field=resource_id&limit=400' % criteria)
114 headers = {'X-Auth-Token': self._auth_token}
115 resp = requests.get(url, headers=headers)
117 resp.raise_for_status()
118 self._meter_list = resp.json()
119 except (KeyError, ValueError, requests.exceptions.HTTPError) as err:
120 raise InvalidResponse(err, resp)
123 class CSVClient(object):
124 """Client to request CSV meters"""
125 def __init__(self, bc_logger, conf):
128 bc_logger - logger instance
129 conf -- ConfigServer instance
131 self._logger = bc_logger
134 def get_csv_metrics(self, compute_node, plugin_subdirectories, meter_categories):
138 compute_node -- compute node instance
139 plugin_subdirectories -- list of subdirectories of plug-in
140 meter_categories -- categories which will be tested
142 Return list of metrics.
144 stdout = self.conf.execute_command("date '+%Y-%m-%d'", compute_node.get_ip())
145 date = stdout[0].strip()
147 for plugin_subdir in plugin_subdirectories:
148 for meter_category in meter_categories:
149 stdout = self.conf.execute_command(
150 "tail -2 /var/lib/collectd/csv/node-"
151 + "{0}.domain.tld/{1}/{2}-{3}".format(
152 compute_node.get_id(), plugin_subdir, meter_category, date),
153 compute_node.get_ip())
154 #Storing last two values
158 'Getting last two CSV entries of meter category '
159 + '{0} in {1} subdir failed'.format(meter_category, plugin_subdir))
161 old_value = int(values[0][0:values[0].index('.')])
162 new_value = int(values[1][0:values[1].index('.')])
163 metrics.append((plugin_subdir, meter_category, old_value, new_value))
168 """Check whether there is global logger available and if not, define one."""
169 if 'logger' not in globals():
171 logger = logger.Logger("barometercollectd").getLogger()
174 def _process_result(compute_node, test, result, results_list):
175 """Print test result and append it to results list.
178 test -- testcase name
179 result -- boolean test result
180 results_list -- results list
183 logger.info('Compute node {0} test case {1} PASSED.'.format(compute_node, test))
185 logger.error('Compute node {0} test case {1} FAILED.'.format(compute_node, test))
186 results_list.append((compute_node, test, result))
189 def _print_label(label):
190 """Print label on the screen
193 label -- label string
195 label = label.strip()
198 label = ' ' + label + ' '
199 length_label = len(label)
200 length1 = (length - length_label) / 2
201 length2 = length - length_label - length1
202 length1 = max(3, length1)
203 length2 = max(3, length2)
204 logger.info(('=' * length1) + label + ('=' * length2))
207 def _print_plugin_label(plugin, node_id):
208 """Print plug-in label.
211 plugin -- plug-in name
214 _print_label('Node {0}: Plug-in {1} Test case execution'.format(node_id, plugin))
217 def _print_final_result_of_plugin(plugin, compute_ids, results, out_plugins, out_plugin):
218 """Print final results of plug-in.
221 plugin -- plug-in name
222 compute_ids -- list of compute node IDs
223 results -- results list
224 out_plugins -- list of out plug-ins
225 out_plugin -- used out plug-in
228 for id in compute_ids:
229 if out_plugins[id] == out_plugin:
230 if (id, plugin, True) in results:
231 print_line += ' PASS |'
232 elif (id, plugin, False) in results and out_plugins[id] == out_plugin:
233 print_line += ' FAIL |'
235 print_line += ' NOT EX |'
236 elif out_plugin == 'Ceilometer':
237 print_line += ' NOT EX |'
239 print_line += ' SKIP |'
243 def print_overall_summary(compute_ids, tested_plugins, results, out_plugins):
244 """Print overall summary table.
247 compute_ids -- list of compute IDs
248 tested_plugins -- list of plug-ins
249 results -- results list
250 out_plugins -- list of used out plug-ins
252 compute_node_names = ['Node-{}'.format(id) for id in compute_ids]
253 all_computes_in_line = ''
254 for compute in compute_node_names:
255 all_computes_in_line = all_computes_in_line + '| ' + compute + (' ' * (7 - len(compute)))
256 line_of_nodes = '| Test ' + all_computes_in_line + '|'
257 logger.info('=' * 70)
258 logger.info('+' + ('-' * ((9 * len(compute_node_names))+16)) + '+')
260 '|' + ' ' * ((9*len(compute_node_names))/2) + ' OVERALL SUMMARY'
261 + ' ' * (9*len(compute_node_names) - (9*len(compute_node_names))/2) + '|')
262 logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names))
263 logger.info(line_of_nodes)
264 logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names))
265 out_plugins_print = ['Ceilometer']
266 if 'CSV' in out_plugins.values():
267 out_plugins_print.append('CSV')
268 for out_plugin in out_plugins_print:
269 output_plugins_line = ''
270 for id in compute_ids:
271 out_plugin_result = '----'
272 if out_plugin == 'Ceilometer':
273 out_plugin_result = 'PASS' if out_plugins[id] == out_plugin else 'FAIL'
274 if out_plugin == 'CSV':
275 if out_plugins[id] == out_plugin:
276 out_plugin_result = \
278 plugin for comp_id, plugin,
279 res in results if comp_id == id and res] else 'FAIL'
281 out_plugin_result = 'SKIP'
282 output_plugins_line += '| ' + out_plugin_result + ' '
284 '| OUT:{}'.format(out_plugin) + (' ' * (11 - len(out_plugin)))
285 + output_plugins_line + '|')
286 for plugin in sorted(tested_plugins.values()):
287 line_plugin = _print_final_result_of_plugin(
288 plugin, compute_ids, results, out_plugins, out_plugin)
289 logger.info('| IN:{}'.format(plugin) + (' ' * (11-len(plugin))) + '|' + line_plugin)
290 logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names))
291 logger.info('=' * 70)
295 test_labels, name, ceilometer_running, compute_node,
296 conf, results, error_plugins):
297 """Execute the testcase.
300 test_labels -- dictionary of plug-in IDs and their display names
301 name -- plug-in ID, key of test_labels dictionary
302 ceilometer_running -- boolean indicating whether Ceilometer is running
303 compute_node -- compute node ID
304 conf -- ConfigServer instance
305 results -- results list
306 error_plugins -- list of tuples with plug-in errors (plugin, error_description, is_critical):
307 plugin -- plug-in ID, key of test_labels dictionary
308 error_decription -- description of the error
309 is_critical -- boolean value indicating whether error is critical
311 ovs_interfaces = conf.get_ovs_interfaces(compute_node)
312 ovs_configured_interfaces = conf.get_plugin_config_values(
313 compute_node, 'ovs_events', 'Interfaces')
314 ovs_existing_configured_int = [
315 interface for interface in ovs_interfaces
316 if interface in ovs_configured_interfaces]
317 plugin_prerequisites = {
318 'mcelog': [(conf.is_installed(compute_node, 'mcelog'), 'mcelog must be installed.')],
320 len(ovs_existing_configured_int) > 0 or len(ovs_interfaces) > 0,
321 'Interfaces must be configured.')]}
322 ceilometer_criteria_lists = {
323 'hugepages': ['hugepages.vmpage_number'],
324 'mcelog': ['mcelog.errors'],
325 'ovs_events': ['ovs_events.gauge']}
326 ceilometer_substr_lists = {
327 'ovs_events': ovs_existing_configured_int if len(ovs_existing_configured_int) > 0 else ovs_interfaces}
330 'hugepages-mm-2048Kb', 'hugepages-node0-2048Kb', 'hugepages-node1-2048Kb',
331 'hugepages-mm-1048576Kb', 'hugepages-node0-1048576Kb', 'hugepages-node1-1048576Kb'],
332 'mcelog': ['mcelog-SOCKET_0_CHANNEL_0_DIMM_any', 'mcelog-SOCKET_0_CHANNEL_any_DIMM_any'],
334 'ovs_events-{}'.format(interface)
335 for interface in (ovs_existing_configured_int if len(ovs_existing_configured_int) > 0 else ovs_interfaces)]}
336 csv_meter_categories = {
337 'hugepages': ['vmpage_number-free', 'vmpage_number-used'],
339 'errors-corrected_memory_errors', 'errors-uncorrected_memory_errors',
340 'errors-corrected_memory_errors_in_24h', 'errors-uncorrected_memory_errors_in_24h'],
341 'ovs_events': ['gauge-link_status']}
343 _print_plugin_label(test_labels[name] if name in test_labels else name, compute_node.get_id())
344 plugin_critical_errors = [
345 error for plugin, error, critical in error_plugins if plugin == name and critical]
346 if plugin_critical_errors:
347 logger.error('Following critical errors occurred:'.format(name))
348 for error in plugin_critical_errors:
349 logger.error(' * ' + error)
350 _process_result(compute_node.get_id(), test_labels[name], False, results)
353 error for plugin, error, critical in error_plugins if plugin == name and not critical]
355 logger.warning('Following non-critical errors occured:')
356 for error in plugin_errors:
357 logger.warning(' * ' + error)
358 failed_prerequisites = []
359 if name in plugin_prerequisites:
360 failed_prerequisites = [
361 prerequisite_name for prerequisite_passed,
362 prerequisite_name in plugin_prerequisites[name] if not prerequisite_passed]
363 if failed_prerequisites:
365 '{} test will not be executed, '.format(name)
366 + 'following prerequisites failed:')
367 for prerequisite in failed_prerequisites:
368 logger.error(' * {}'.format(prerequisite))
370 if ceilometer_running:
371 res = test_ceilometer_node_sends_data(
372 compute_node.get_id(), conf.get_plugin_interval(compute_node, name),
373 logger=logger, client=CeilometerClient(logger),
374 criteria_list=ceilometer_criteria_lists[name],
375 resource_id_substrings = (ceilometer_substr_lists[name]
376 if name in ceilometer_substr_lists else ['']))
378 res = test_csv_handles_plugin_data(
379 compute_node, conf.get_plugin_interval(compute_node, name), name,
380 csv_subdirs[name], csv_meter_categories[name], logger,
381 CSVClient(logger, conf))
382 if res and plugin_errors:
384 'Test works, but will be reported as failure,'
385 + 'because of non-critical errors.')
387 _process_result(compute_node.get_id(), test_labels[name], res, results)
390 def main(bt_logger=None):
391 """Check each compute node sends ceilometer metrics.
394 bt_logger -- logger instance
396 logging.getLogger("paramiko").setLevel(logging.WARNING)
397 logging.getLogger("stevedore").setLevel(logging.WARNING)
398 if bt_logger is None:
403 conf = ConfigServer('10.20.0.2', 'root', logger)
404 controllers = conf.get_controllers()
405 if len(controllers) == 0:
406 logger.error('No controller nodes found!')
408 computes = conf.get_computes()
409 if len(computes) == 0:
410 logger.error('No compute nodes found!')
413 _print_label('Display of Control and Compute nodes available in the set up')
414 logger.info('controllers: {}'.format([('{0}: {1} ({2})'.format(
415 node.get_id(), node.get_name(), node.get_ip())) for node in controllers]))
416 logger.info('computes: {}'.format([('{0}: {1} ({2})'.format(
417 node.get_id(), node.get_name(), node.get_ip())) for node in computes]))
419 ceilometer_running_on_con = False
420 _print_label('Test Ceilometer on control nodes')
421 for controller in controllers:
422 ceil_client = CeilometerClient(logger)
423 ceil_client.auth_token()
424 ceilometer_running_on_con = (
425 ceilometer_running_on_con or conf.is_ceilometer_running(controller))
426 if ceilometer_running_on_con:
427 logger.info("Ceilometer is running on control node.")
429 logger.error("Ceilometer is not running on control node.")
430 logger.info("CSV will be enabled on compute nodes.")
434 'hugepages': 'Hugepages',
436 'ovs_events': 'OVS events'}
438 for compute_node in computes:
439 node_id = compute_node.get_id()
440 out_plugins[node_id] = 'CSV'
441 compute_ids.append(node_id)
442 #plugins_to_enable = plugin_labels.keys()
443 plugins_to_enable = []
444 _print_label('NODE {}: Test Ceilometer Plug-in'.format(node_id))
445 logger.info('Checking if ceilometer plug-in is included.')
446 if not conf.check_ceil_plugin_included(compute_node):
447 logger.error('Ceilometer plug-in is not included.')
448 logger.info('Testcases on node {} will not be executed'.format(node_id))
450 collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
452 logger.info('Sleeping for {} seconds after collectd restart...'.format(sleep_time))
453 time.sleep(sleep_time)
454 if not collectd_restarted:
455 for warning in collectd_warnings:
456 logger.warning(warning)
457 logger.error('Restart of collectd on node {} failed'.format(node_id))
458 logger.info('Testcases on node {} will not be executed'.format(node_id))
460 for warning in collectd_warnings:
461 logger.warning(warning)
462 ceilometer_running = (
463 ceilometer_running_on_con and test_ceilometer_node_sends_data(
464 node_id, 10, logger=logger, client=CeilometerClient(logger)))
465 if ceilometer_running:
466 out_plugins[node_id] = 'Ceilometer'
467 logger.info("Ceilometer is running.")
469 plugins_to_enable.append('csv')
470 out_plugins[node_id] = 'CSV'
471 logger.error("Ceilometer is not running.")
472 logger.info("CSV will be enabled for verification of test plugins.")
473 if plugins_to_enable:
475 'NODE {}: Enabling Test Plug-in '.format(node_id)
476 + 'and Test case execution')
478 if plugins_to_enable and not conf.enable_plugins(
479 compute_node, plugins_to_enable, error_plugins, create_backup=False):
480 logger.error('Failed to test plugins on node {}.'.format(node_id))
481 logger.info('Testcases on node {} will not be executed'.format(node_id))
483 if plugins_to_enable:
484 collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
487 'Sleeping for {} seconds after collectd restart...'.format(sleep_time))
488 time.sleep(sleep_time)
489 if plugins_to_enable and not collectd_restarted:
490 for warning in collectd_warnings:
491 logger.warning(warning)
492 logger.error('Restart of collectd on node {} failed'.format(node_id))
493 logger.info('Testcases on node {} will not be executed'.format(node_id))
495 if collectd_warnings:
496 for warning in collectd_warnings:
497 logger.warning(warning)
499 for plugin_name in sorted(plugin_labels.keys()):
501 plugin_labels, plugin_name, ceilometer_running,
502 compute_node, conf, results, error_plugins)
504 _print_label('NODE {}: Restoring config file'.format(node_id))
505 conf.restore_config(compute_node)
507 print_overall_summary(compute_ids, plugin_labels, results, out_plugins)
509 if ((len([res for res in results if not res[2]]) > 0)
510 or (len(results) < len(computes) * len(plugin_labels))):
511 logger.error('Some tests have failed or have not been executed')
515 if __name__ == '__main__':