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 *
23 from opnfv.deployment import factory
24 from functest.utils import functest_utils
25 from functest.utils.constants import CONST
27 CEILOMETER_NAME = 'ceilometer'
28 ID_RSA_SRC = '/root/.ssh/id_rsa'
29 ID_RSA_DST_DIR = '/home/opnfv/.ssh'
30 ID_RSA_DST = ID_RSA_DST_DIR + '/id_rsa'
31 INSTALLER_PARAMS_YAML = os.path.join(CONST.dir_repo_functest, 'functest/ci/installer_params.yaml')
32 FUEL_IP = functest_utils.get_parameter_from_yaml('fuel.ip', INSTALLER_PARAMS_YAML)
33 FUEL_USER = functest_utils.get_parameter_from_yaml('fuel.user', INSTALLER_PARAMS_YAML)
34 FUEL_PW = functest_utils.get_parameter_from_yaml('fuel.password', INSTALLER_PARAMS_YAML)
37 class KeystoneException(Exception):
38 """Keystone exception class"""
39 def __init__(self, message, exc=None, response=None):
42 message -- error message
47 message += "\nReason: %s" % exc
48 super(KeystoneException, self).__init__(message)
50 self.response = response
54 class InvalidResponse(KeystoneException):
55 """Invalid Keystone exception class"""
56 def __init__(self, exc, response):
62 super(InvalidResponse, self).__init__(
63 "Invalid response", exc, response)
66 class CeilometerClient(object):
67 """Ceilometer Client to authenticate and request meters"""
68 def __init__(self, bc_logger):
71 bc_logger - logger instance
73 self._auth_token = None
74 self._ceilometer_url = None
75 self._meter_list = None
76 self._logger = bc_logger
81 return self._auth_token
83 def get_ceilometer_url(self):
84 """Get Ceilometer URL"""
85 return self._ceilometer_url
87 def get_ceil_metrics(self, criteria=None):
88 """Get Ceilometer metrics for given criteria
91 criteria -- criteria for ceilometer meter list
93 self._request_meters(criteria)
94 return self._meter_list
96 def _auth_server(self):
97 """Request token in authentication server"""
98 self._logger.debug('Connecting to the auth server {}'.format(os.environ['OS_AUTH_URL']))
99 keystone = client.Client(username=os.environ['OS_USERNAME'],
100 password=os.environ['OS_PASSWORD'],
101 tenant_name=os.environ['OS_TENANT_NAME'],
102 auth_url=os.environ['OS_AUTH_URL'])
103 self._auth_token = keystone.auth_token
104 for service in keystone.service_catalog.get_data():
105 if service['name'] == CEILOMETER_NAME:
106 for service_type in service['endpoints']:
107 if service_type['interface'] == 'internal':
108 self._ceilometer_url = service_type['url']
111 if self._ceilometer_url is None:
112 self._logger.warning('Ceilometer is not registered in service catalog')
114 def _request_meters(self, criteria):
115 """Request meter list values from ceilometer
118 criteria -- criteria for ceilometer meter list
121 url = self._ceilometer_url + ('/v2/samples?limit=400')
123 url = self._ceilometer_url + ('/v2/meters/%s?q.field=resource_id&limit=400' % criteria)
124 headers = {'X-Auth-Token': self._auth_token}
125 resp = requests.get(url, headers=headers)
127 resp.raise_for_status()
128 self._meter_list = resp.json()
129 except (KeyError, ValueError, requests.exceptions.HTTPError) as err:
130 raise InvalidResponse(err, resp)
133 class CSVClient(object):
134 """Client to request CSV meters"""
135 def __init__(self, bc_logger, conf):
138 bc_logger - logger instance
139 conf -- ConfigServer instance
141 self._logger = bc_logger
144 def get_csv_metrics(self, compute_node, plugin_subdirectories, meter_categories):
148 compute_node -- compute node instance
149 plugin_subdirectories -- list of subdirectories of plug-in
150 meter_categories -- categories which will be tested
152 Return list of metrics.
154 stdout = self.conf.execute_command("date '+%Y-%m-%d'", compute_node.get_ip())
155 date = stdout[0].strip()
157 for plugin_subdir in plugin_subdirectories:
158 for meter_category in meter_categories:
159 stdout = self.conf.execute_command(
160 "tail -2 /var/lib/collectd/csv/node-"
161 + "{0}.domain.tld/{1}/{2}-{3}".format(
162 compute_node.get_id(), plugin_subdir, meter_category, date),
163 compute_node.get_ip())
164 # Storing last two values
168 'Getting last two CSV entries of meter category '
169 + '{0} in {1} subdir failed'.format(meter_category, plugin_subdir))
171 old_value = int(values[0][0:values[0].index('.')])
172 new_value = int(values[1][0:values[1].index('.')])
173 metrics.append((plugin_subdir, meter_category, old_value, new_value))
178 """Check whether there is global logger available and if not, define one."""
179 if 'logger' not in globals():
181 logger = logger.Logger("barometercollectd").getLogger()
184 def _process_result(compute_node, test, result, results_list):
185 """Print test result and append it to results list.
188 test -- testcase name
189 result -- boolean test result
190 results_list -- results list
193 logger.info('Compute node {0} test case {1} PASSED.'.format(compute_node, test))
195 logger.error('Compute node {0} test case {1} FAILED.'.format(compute_node, test))
196 results_list.append((compute_node, test, result))
199 def _print_label(label):
200 """Print label on the screen
203 label -- label string
205 label = label.strip()
208 label = ' ' + label + ' '
209 length_label = len(label)
210 length1 = (length - length_label) / 2
211 length2 = length - length_label - length1
212 length1 = max(3, length1)
213 length2 = max(3, length2)
214 logger.info(('=' * length1) + label + ('=' * length2))
217 def _print_plugin_label(plugin, node_id):
218 """Print plug-in label.
221 plugin -- plug-in name
224 _print_label('Node {0}: Plug-in {1} Test case execution'.format(node_id, plugin))
227 def _print_final_result_of_plugin(plugin, compute_ids, results, out_plugins, out_plugin):
228 """Print final results of plug-in.
231 plugin -- plug-in name
232 compute_ids -- list of compute node IDs
233 results -- results list
234 out_plugins -- list of out plug-ins
235 out_plugin -- used out plug-in
238 for id in compute_ids:
239 if out_plugins[id] == out_plugin:
240 if (id, plugin, True) in results:
241 print_line += ' PASS |'
242 elif (id, plugin, False) in results and out_plugins[id] == out_plugin:
243 print_line += ' FAIL |'
245 print_line += ' NOT EX |'
246 elif out_plugin == 'Ceilometer':
247 print_line += ' NOT EX |'
249 print_line += ' SKIP |'
253 def print_overall_summary(compute_ids, tested_plugins, results, out_plugins):
254 """Print overall summary table.
257 compute_ids -- list of compute IDs
258 tested_plugins -- list of plug-ins
259 results -- results list
260 out_plugins -- list of used out plug-ins
262 compute_node_names = ['Node-{}'.format(id) for id in compute_ids]
263 all_computes_in_line = ''
264 for compute in compute_node_names:
265 all_computes_in_line = all_computes_in_line + '| ' + compute + (' ' * (7 - len(compute)))
266 line_of_nodes = '| Test ' + all_computes_in_line + '|'
267 logger.info('=' * 70)
268 logger.info('+' + ('-' * ((9 * len(compute_node_names))+16)) + '+')
270 '|' + ' ' * ((9*len(compute_node_names))/2) + ' OVERALL SUMMARY'
271 + ' ' * (9*len(compute_node_names) - (9*len(compute_node_names))/2) + '|')
272 logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names))
273 logger.info(line_of_nodes)
274 logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names))
275 out_plugins_print = ['Ceilometer']
276 if 'CSV' in out_plugins.values():
277 out_plugins_print.append('CSV')
278 for out_plugin in out_plugins_print:
279 output_plugins_line = ''
280 for id in compute_ids:
281 out_plugin_result = '----'
282 if out_plugin == 'Ceilometer':
283 out_plugin_result = 'PASS' if out_plugins[id] == out_plugin else 'FAIL'
284 if out_plugin == 'CSV':
285 if out_plugins[id] == out_plugin:
286 out_plugin_result = \
288 plugin for comp_id, plugin,
289 res in results if comp_id == id and res] else 'FAIL'
291 out_plugin_result = 'SKIP'
292 output_plugins_line += '| ' + out_plugin_result + ' '
294 '| OUT:{}'.format(out_plugin) + (' ' * (11 - len(out_plugin)))
295 + output_plugins_line + '|')
296 for plugin in sorted(tested_plugins.values()):
297 line_plugin = _print_final_result_of_plugin(
298 plugin, compute_ids, results, out_plugins, out_plugin)
299 logger.info('| IN:{}'.format(plugin) + (' ' * (11-len(plugin))) + '|' + line_plugin)
300 logger.info('+' + ('-' * 16) + '+' + (('-' * 8) + '+') * len(compute_node_names))
301 logger.info('=' * 70)
305 test_labels, name, ceilometer_running, compute_node,
306 conf, results, error_plugins):
307 """Execute the testcase.
310 test_labels -- dictionary of plug-in IDs and their display names
311 name -- plug-in ID, key of test_labels dictionary
312 ceilometer_running -- boolean indicating whether Ceilometer is running
313 compute_node -- compute node ID
314 conf -- ConfigServer instance
315 results -- results list
316 error_plugins -- list of tuples with plug-in errors (plugin, error_description, is_critical):
317 plugin -- plug-in ID, key of test_labels dictionary
318 error_decription -- description of the error
319 is_critical -- boolean value indicating whether error is critical
321 ovs_interfaces = conf.get_ovs_interfaces(compute_node)
322 ovs_configured_interfaces = conf.get_plugin_config_values(
323 compute_node, 'ovs_events', 'Interfaces')
324 ovs_existing_configured_int = [
325 interface for interface in ovs_interfaces
326 if interface in ovs_configured_interfaces]
327 plugin_prerequisites = {
328 'mcelog': [(conf.is_installed(compute_node, 'mcelog'), 'mcelog must be installed.')],
330 len(ovs_existing_configured_int) > 0 or len(ovs_interfaces) > 0,
331 'Interfaces must be configured.')]}
332 ceilometer_criteria_lists = {
333 'hugepages': ['hugepages.vmpage_number'],
334 'mcelog': ['mcelog.errors'],
335 'ovs_events': ['ovs_events.gauge']}
336 ceilometer_substr_lists = {
337 'ovs_events': ovs_existing_configured_int if len(ovs_existing_configured_int) > 0 else ovs_interfaces}
340 'hugepages-mm-2048Kb', 'hugepages-node0-2048Kb', 'hugepages-node1-2048Kb',
341 'hugepages-mm-1048576Kb', 'hugepages-node0-1048576Kb', 'hugepages-node1-1048576Kb'],
342 'mcelog': ['mcelog-SOCKET_0_CHANNEL_0_DIMM_any', 'mcelog-SOCKET_0_CHANNEL_any_DIMM_any'],
344 'ovs_events-{}'.format(interface)
345 for interface in (ovs_existing_configured_int if len(ovs_existing_configured_int) > 0 else ovs_interfaces)]}
346 csv_meter_categories = {
347 'hugepages': ['vmpage_number-free', 'vmpage_number-used'],
349 'errors-corrected_memory_errors', 'errors-uncorrected_memory_errors',
350 'errors-corrected_memory_errors_in_24h', 'errors-uncorrected_memory_errors_in_24h'],
351 'ovs_events': ['gauge-link_status']}
353 _print_plugin_label(test_labels[name] if name in test_labels else name, compute_node.get_id())
354 plugin_critical_errors = [
355 error for plugin, error, critical in error_plugins if plugin == name and critical]
356 if plugin_critical_errors:
357 logger.error('Following critical errors occurred:'.format(name))
358 for error in plugin_critical_errors:
359 logger.error(' * ' + error)
360 _process_result(compute_node.get_id(), test_labels[name], False, results)
363 error for plugin, error, critical in error_plugins if plugin == name and not critical]
365 logger.warning('Following non-critical errors occured:')
366 for error in plugin_errors:
367 logger.warning(' * ' + error)
368 failed_prerequisites = []
369 if name in plugin_prerequisites:
370 failed_prerequisites = [
371 prerequisite_name for prerequisite_passed,
372 prerequisite_name in plugin_prerequisites[name] if not prerequisite_passed]
373 if failed_prerequisites:
375 '{} test will not be executed, '.format(name)
376 + 'following prerequisites failed:')
377 for prerequisite in failed_prerequisites:
378 logger.error(' * {}'.format(prerequisite))
380 if ceilometer_running:
381 res = test_ceilometer_node_sends_data(
382 compute_node.get_id(), conf.get_plugin_interval(compute_node, name),
383 logger=logger, client=CeilometerClient(logger),
384 criteria_list=ceilometer_criteria_lists[name],
385 resource_id_substrings=(ceilometer_substr_lists[name]
386 if name in ceilometer_substr_lists else ['']))
388 res = test_csv_handles_plugin_data(
389 compute_node, conf.get_plugin_interval(compute_node, name), name,
390 csv_subdirs[name], csv_meter_categories[name], logger,
391 CSVClient(logger, conf))
392 if res and plugin_errors:
394 'Test works, but will be reported as failure,'
395 + 'because of non-critical errors.')
397 _process_result(compute_node.get_id(), test_labels[name], res, results)
400 def mcelog_install(logger):
401 """Install mcelog on compute nodes.
404 logger - logger instance
406 _print_label('Enabling mcelog on compute nodes')
407 handler = factory.Factory.get_handler('fuel', FUEL_IP, FUEL_USER, installer_pwd='')
408 nodes = handler.get_nodes()
409 openstack_version = handler.get_openstack_version()
410 if openstack_version.find('14.') != 0:
411 logger.info('Mcelog will not be installed,'
412 + ' unsupported Openstack version found ({}).'.format(openstack_version))
415 if node.is_compute():
416 ubuntu_release = node.run_cmd('lsb_release -r')
417 if '16.04' not in ubuntu_release:
418 logger.info('Mcelog will not be enabled'
419 + 'on node-{0}, unsupported Ubuntu release found ({1}).'.format(
420 node.get_dict()['id'], ubuntu_release))
422 logger.info('Checking if mcelog is enabled on node-{}...'.format(
423 node.get_dict()['id']))
424 res = node.run_cmd('ls /root/')
425 if 'mce-inject_df' and 'corrected' in res:
426 logger.info('Mcelog seems to be already installed on node-{}.'.format(
427 node.get_dict()['id']))
428 res = node.run_cmd('modprobe mce-inject')
429 res = node.run_cmd('/root/mce-inject_df < /root/corrected')
431 logger.info('Mcelog will be enabled on node-{}...'.format(
432 node.get_dict()['id']))
433 res = node.put_file('/home/opnfv/repos/barometer/baro_utils/mce-inject_df',
434 '/root/mce-inject_df')
435 res = node.run_cmd('chmod a+x /root/mce-inject_df')
436 res = node.run_cmd('echo "CPU 0 BANK 0" > /root/corrected')
437 res = node.run_cmd('echo "STATUS 0xcc00008000010090" >> /root/corrected')
438 res = node.run_cmd('echo "ADDR 0x0010FFFFFFF" >> /root/corrected')
439 res = node.run_cmd('modprobe mce-inject')
440 res = node.run_cmd('/root/mce-inject_df < /root/corrected')
441 logger.info('Mcelog is installed on all compute nodes')
444 def mcelog_delete(logger):
445 """Uninstall mcelog from compute nodes.
448 logger - logger instance
450 handler = factory.Factory.get_handler('fuel', FUEL_IP, FUEL_USER, installer_pwd='')
451 nodes = handler.get_nodes()
453 if node.is_compute():
454 output = node.run_cmd('ls /root/')
455 if 'mce-inject_df' in output:
456 res = node.run_cmd('rm /root/mce-inject_df')
457 if 'corrected' in output:
458 res = node.run_cmd('rm /root/corrected')
459 res = node.run_cmd('systemctl restart mcelog')
460 logger.info('Mcelog is deleted from all compute nodes')
464 if not os.path.isdir(ID_RSA_DST_DIR):
465 os.makedirs(ID_RSA_DST_DIR)
466 if not os.path.isfile(ID_RSA_DST):
467 logger.info("RSA key file {} doesn't exist, it will be downloaded from installer node.".format(ID_RSA_DST))
468 handler = factory.Factory.get_handler('fuel', FUEL_IP, FUEL_USER, installer_pwd=FUEL_PW)
469 fuel = handler.get_installer_node()
470 fuel.get_file(ID_RSA_SRC, ID_RSA_DST)
472 logger.info("RSA key file {} exists.".format(ID_RSA_DST))
475 def main(bt_logger=None):
476 """Check each compute node sends ceilometer metrics.
479 bt_logger -- logger instance
481 logging.getLogger("paramiko").setLevel(logging.WARNING)
482 logging.getLogger("stevedore").setLevel(logging.WARNING)
483 logging.getLogger("opnfv.deployment.manager").setLevel(logging.WARNING)
484 if bt_logger is None:
490 conf = ConfigServer(FUEL_IP, FUEL_USER, logger)
491 controllers = conf.get_controllers()
492 if len(controllers) == 0:
493 logger.error('No controller nodes found!')
495 computes = conf.get_computes()
496 if len(computes) == 0:
497 logger.error('No compute nodes found!')
500 _print_label('Display of Control and Compute nodes available in the set up')
501 logger.info('controllers: {}'.format([('{0}: {1} ({2})'.format(
502 node.get_id(), node.get_name(), node.get_ip())) for node in controllers]))
503 logger.info('computes: {}'.format([('{0}: {1} ({2})'.format(
504 node.get_id(), node.get_name(), node.get_ip())) for node in computes]))
506 mcelog_install(logger) # installation of mcelog
508 ceilometer_running_on_con = False
509 _print_label('Test Ceilometer on control nodes')
510 for controller in controllers:
511 ceil_client = CeilometerClient(logger)
512 ceil_client.auth_token()
513 ceilometer_running_on_con = (
514 ceilometer_running_on_con or conf.is_ceilometer_running(controller))
515 if ceilometer_running_on_con:
516 logger.info("Ceilometer is running on control node.")
518 logger.error("Ceilometer is not running on control node.")
519 logger.info("CSV will be enabled on compute nodes.")
523 'hugepages': 'Hugepages',
525 'ovs_events': 'OVS events'}
527 for compute_node in computes:
528 node_id = compute_node.get_id()
529 out_plugins[node_id] = 'CSV'
530 compute_ids.append(node_id)
531 # plugins_to_enable = plugin_labels.keys()
532 plugins_to_enable = []
533 _print_label('NODE {}: Test Ceilometer Plug-in'.format(node_id))
534 logger.info('Checking if ceilometer plug-in is included.')
535 if not conf.check_ceil_plugin_included(compute_node):
536 logger.error('Ceilometer plug-in is not included.')
537 logger.info('Testcases on node {} will not be executed'.format(node_id))
539 collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
541 logger.info('Sleeping for {} seconds after collectd restart...'.format(sleep_time))
542 time.sleep(sleep_time)
543 if not collectd_restarted:
544 for warning in collectd_warnings:
545 logger.warning(warning)
546 logger.error('Restart of collectd on node {} failed'.format(node_id))
547 logger.info('Testcases on node {} will not be executed'.format(node_id))
549 for warning in collectd_warnings:
550 logger.warning(warning)
551 ceilometer_running = (
552 ceilometer_running_on_con and test_ceilometer_node_sends_data(
553 node_id, 10, logger=logger, client=CeilometerClient(logger)))
554 if ceilometer_running:
555 out_plugins[node_id] = 'Ceilometer'
556 logger.info("Ceilometer is running.")
558 plugins_to_enable.append('csv')
559 out_plugins[node_id] = 'CSV'
560 logger.error("Ceilometer is not running.")
561 logger.info("CSV will be enabled for verification of test plugins.")
562 if plugins_to_enable:
564 'NODE {}: Enabling Test Plug-in '.format(node_id)
565 + 'and Test case execution')
567 if plugins_to_enable and not conf.enable_plugins(
568 compute_node, plugins_to_enable, error_plugins, create_backup=False):
569 logger.error('Failed to test plugins on node {}.'.format(node_id))
570 logger.info('Testcases on node {} will not be executed'.format(node_id))
572 if plugins_to_enable:
573 collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
576 'Sleeping for {} seconds after collectd restart...'.format(sleep_time))
577 time.sleep(sleep_time)
578 if plugins_to_enable and not collectd_restarted:
579 for warning in collectd_warnings:
580 logger.warning(warning)
581 logger.error('Restart of collectd on node {} failed'.format(node_id))
582 logger.info('Testcases on node {} will not be executed'.format(node_id))
584 if collectd_warnings:
585 for warning in collectd_warnings:
586 logger.warning(warning)
588 for plugin_name in sorted(plugin_labels.keys()):
590 plugin_labels, plugin_name, ceilometer_running,
591 compute_node, conf, results, error_plugins)
593 _print_label('NODE {}: Restoring config file'.format(node_id))
594 conf.restore_config(compute_node)
596 mcelog_delete(logger) # uninstalling mcelog from compute nodes
598 print_overall_summary(compute_ids, plugin_labels, results, out_plugins)
600 if ((len([res for res in results if not res[2]]) > 0)
601 or (len(results) < len(computes) * len(plugin_labels))):
602 logger.error('Some tests have failed or have not been executed')
607 if __name__ == '__main__':