Fix for Functest 'BarometerCollectd ERROR - Private key file not found'
[barometer.git] / baro_tests / collectd.py
1 """Executing test of plugins"""
2 # -*- coding: utf-8 -*-
3
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
7 #
8 #      http://www.apache.org/licenses/LICENSE-2.0
9 #
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
14 # under the License.
15
16 import requests
17 from keystoneclient.v3 import client
18 import os
19 import time
20 import logging
21 from config_server import *
22 from tests import *
23 from opnfv.deployment import factory
24 from functest.utils import functest_utils
25 from functest.utils.constants import CONST
26
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)
35
36
37 class KeystoneException(Exception):
38     """Keystone exception class"""
39     def __init__(self, message, exc=None, response=None):
40         """
41         Keyword arguments:
42         message -- error message
43         exc -- exception
44         response -- response
45         """
46         if exc:
47             message += "\nReason: %s" % exc
48         super(KeystoneException, self).__init__(message)
49
50         self.response = response
51         self.exception = exc
52
53
54 class InvalidResponse(KeystoneException):
55     """Invalid Keystone exception class"""
56     def __init__(self, exc, response):
57         """
58         Keyword arguments:
59         exc -- exception
60         response -- response
61         """
62         super(InvalidResponse, self).__init__(
63             "Invalid response", exc, response)
64
65
66 class CeilometerClient(object):
67     """Ceilometer Client to authenticate and request meters"""
68     def __init__(self, bc_logger):
69         """
70         Keyword arguments:
71         bc_logger - logger instance
72         """
73         self._auth_token = None
74         self._ceilometer_url = None
75         self._meter_list = None
76         self._logger = bc_logger
77
78     def auth_token(self):
79         """Get auth token"""
80         self._auth_server()
81         return self._auth_token
82
83     def get_ceilometer_url(self):
84         """Get Ceilometer URL"""
85         return self._ceilometer_url
86
87     def get_ceil_metrics(self, criteria=None):
88         """Get Ceilometer metrics for given criteria
89
90         Keyword arguments:
91         criteria -- criteria for ceilometer meter list
92         """
93         self._request_meters(criteria)
94         return self._meter_list
95
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']
109                         break
110
111         if self._ceilometer_url is None:
112             self._logger.warning('Ceilometer is not registered in service catalog')
113
114     def _request_meters(self, criteria):
115         """Request meter list values from ceilometer
116
117         Keyword arguments:
118         criteria -- criteria for ceilometer meter list
119         """
120         if criteria is None:
121             url = self._ceilometer_url + ('/v2/samples?limit=400')
122         else:
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)
126         try:
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)
131
132
133 class CSVClient(object):
134     """Client to request CSV meters"""
135     def __init__(self, bc_logger, conf):
136         """
137         Keyword arguments:
138         bc_logger - logger instance
139         conf -- ConfigServer instance
140         """
141         self._logger = bc_logger
142         self.conf = conf
143
144     def get_csv_metrics(self, compute_node, plugin_subdirectories, meter_categories):
145         """Get CSV metrics.
146
147         Keyword arguments:
148         compute_node -- compute node instance
149         plugin_subdirectories -- list of subdirectories of plug-in
150         meter_categories -- categories which will be tested
151
152         Return list of metrics.
153         """
154         stdout = self.conf.execute_command("date '+%Y-%m-%d'", compute_node.get_ip())
155         date = stdout[0].strip()
156         metrics = []
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
165                 values = stdout
166                 if len(values) < 2:
167                     self._logger.error(
168                         'Getting last two CSV entries of meter category '
169                         + '{0} in {1} subdir failed'.format(meter_category, plugin_subdir))
170                 else:
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))
174         return metrics
175
176
177 def _check_logger():
178     """Check whether there is global logger available and if not, define one."""
179     if 'logger' not in globals():
180         global logger
181         logger = logger.Logger("barometercollectd").getLogger()
182
183
184 def _process_result(compute_node, test, result, results_list):
185     """Print test result and append it to results list.
186
187     Keyword arguments:
188     test -- testcase name
189     result -- boolean test result
190     results_list -- results list
191     """
192     if result:
193         logger.info('Compute node {0} test case {1} PASSED.'.format(compute_node, test))
194     else:
195         logger.error('Compute node {0} test case {1} FAILED.'.format(compute_node, test))
196     results_list.append((compute_node, test, result))
197
198
199 def _print_label(label):
200     """Print label on the screen
201
202     Keyword arguments:
203     label -- label string
204     """
205     label = label.strip()
206     length = 70
207     if label != '':
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))
215
216
217 def _print_plugin_label(plugin, node_id):
218     """Print plug-in label.
219
220     Keyword arguments:
221     plugin -- plug-in name
222     node_id -- node ID
223     """
224     _print_label('Node {0}: Plug-in {1} Test case execution'.format(node_id, plugin))
225
226
227 def _print_final_result_of_plugin(plugin, compute_ids, results, out_plugins, out_plugin):
228     """Print final results of plug-in.
229
230     Keyword arguments:
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
236     """
237     print_line = ''
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   |'
244             else:
245                 print_line += ' NOT EX |'
246         elif out_plugin == 'Ceilometer':
247             print_line += ' NOT EX |'
248         else:
249             print_line += ' SKIP   |'
250     return print_line
251
252
253 def print_overall_summary(compute_ids, tested_plugins, results, out_plugins):
254     """Print overall summary table.
255
256     Keyword arguments:
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
261     """
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)) + '+')
269     logger.info(
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 = \
287                         'PASS' if [
288                             plugin for comp_id, plugin,
289                             res in results if comp_id == id and res] else 'FAIL'
290                 else:
291                     out_plugin_result = 'SKIP'
292             output_plugins_line += '| ' + out_plugin_result + '   '
293         logger.info(
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)
302
303
304 def _exec_testcase(
305         test_labels, name, ceilometer_running, compute_node,
306         conf, results, error_plugins):
307     """Execute the testcase.
308
309     Keyword arguments:
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
320     """
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.')],
329         'ovs_events': [(
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}
338     csv_subdirs = {
339         'hugepages': [
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'],
343         'ovs_events': [
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'],
348         'mcelog': [
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']}
352
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)
361     else:
362         plugin_errors = [
363             error for plugin, error, critical in error_plugins if plugin == name and not critical]
364         if plugin_errors:
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:
374             logger.error(
375                 '{} test will not be executed, '.format(name)
376                 + 'following prerequisites failed:')
377             for prerequisite in failed_prerequisites:
378                 logger.error(' * {}'.format(prerequisite))
379         else:
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 ['']))
387             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:
393                 logger.info(
394                     'Test works, but will be reported as failure,'
395                     + 'because of non-critical errors.')
396                 res = False
397             _process_result(compute_node.get_id(), test_labels[name], res, results)
398
399 def get_ssh_keys():
400     if not os.path.isdir(ID_RSA_DST_DIR):
401         os.makedirs(ID_RSA_DST_DIR)
402     if not os.path.isfile(ID_RSA_DST):
403         logger.info("RSA key file {} doesn't exist, it will be downloaded from instaler node.".format(ID_RSA_DST))
404         handler = factory.Factory.get_handler('fuel', FUEL_IP, FUEL_USER, installer_pwd=FUEL_PW)
405         fuel = handler.get_installer_node()
406         fuel.get_file(ID_RSA_SRC, ID_RSA_DST)
407     else:
408         logger.info("RSA key file {} exists.".format(ID_RSA_DST))
409
410
411 def main(bt_logger=None):
412     """Check each compute node sends ceilometer metrics.
413
414     Keyword arguments:
415     bt_logger -- logger instance
416     """
417     logging.getLogger("paramiko").setLevel(logging.WARNING)
418     logging.getLogger("stevedore").setLevel(logging.WARNING)
419     logging.getLogger("opnfv.deployment.manager").setLevel(logging.WARNING)
420     if bt_logger is None:
421         _check_logger()
422     else:
423         global logger
424         logger = bt_logger
425     get_ssh_keys()
426     conf = ConfigServer(FUEL_IP, FUEL_USER, logger)
427     controllers = conf.get_controllers()
428     if len(controllers) == 0:
429         logger.error('No controller nodes found!')
430         return 1
431     computes = conf.get_computes()
432     if len(computes) == 0:
433         logger.error('No compute nodes found!')
434         return 1
435
436     _print_label('Display of Control and Compute nodes available in the set up')
437     logger.info('controllers: {}'.format([('{0}: {1} ({2})'.format(
438         node.get_id(), node.get_name(), node.get_ip())) for node in controllers]))
439     logger.info('computes: {}'.format([('{0}: {1} ({2})'.format(
440         node.get_id(), node.get_name(), node.get_ip())) for node in computes]))
441
442     ceilometer_running_on_con = False
443     _print_label('Test Ceilometer on control nodes')
444     for controller in controllers:
445         ceil_client = CeilometerClient(logger)
446         ceil_client.auth_token()
447         ceilometer_running_on_con = (
448             ceilometer_running_on_con or conf.is_ceilometer_running(controller))
449     if ceilometer_running_on_con:
450         logger.info("Ceilometer is running on control node.")
451     else:
452         logger.error("Ceilometer is not running on control node.")
453         logger.info("CSV will be enabled on compute nodes.")
454     compute_ids = []
455     results = []
456     plugin_labels = {
457         'hugepages': 'Hugepages',
458         'mcelog': 'Mcelog',
459         'ovs_events': 'OVS events'}
460     out_plugins = {}
461     for compute_node in computes:
462         node_id = compute_node.get_id()
463         out_plugins[node_id] = 'CSV'
464         compute_ids.append(node_id)
465         #plugins_to_enable = plugin_labels.keys()
466         plugins_to_enable = []
467         _print_label('NODE {}: Test Ceilometer Plug-in'.format(node_id))
468         logger.info('Checking if ceilometer plug-in is included.')
469         if not conf.check_ceil_plugin_included(compute_node):
470             logger.error('Ceilometer plug-in is not included.')
471             logger.info('Testcases on node {} will not be executed'.format(node_id))
472         else:
473             collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
474             sleep_time = 30
475             logger.info('Sleeping for {} seconds after collectd restart...'.format(sleep_time))
476             time.sleep(sleep_time)
477             if not collectd_restarted:
478                 for warning in collectd_warnings:
479                     logger.warning(warning)
480                 logger.error('Restart of collectd on node {} failed'.format(node_id))
481                 logger.info('Testcases on node {} will not be executed'.format(node_id))
482             else:
483                 for warning in collectd_warnings:
484                     logger.warning(warning)
485                 ceilometer_running = (
486                     ceilometer_running_on_con and test_ceilometer_node_sends_data(
487                         node_id, 10, logger=logger, client=CeilometerClient(logger)))
488                 if ceilometer_running:
489                     out_plugins[node_id] = 'Ceilometer'
490                     logger.info("Ceilometer is running.")
491                 else:
492                     plugins_to_enable.append('csv')
493                     out_plugins[node_id] = 'CSV'
494                     logger.error("Ceilometer is not running.")
495                     logger.info("CSV will be enabled for verification of test plugins.")
496                 if plugins_to_enable:
497                     _print_label(
498                         'NODE {}: Enabling Test Plug-in '.format(node_id)
499                         + 'and Test case execution')
500                 error_plugins = []
501                 if plugins_to_enable and not conf.enable_plugins(
502                         compute_node, plugins_to_enable, error_plugins, create_backup=False):
503                     logger.error('Failed to test plugins on node {}.'.format(node_id))
504                     logger.info('Testcases on node {} will not be executed'.format(node_id))
505                 else:
506                     if plugins_to_enable:
507                         collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
508                         sleep_time = 30
509                         logger.info(
510                             'Sleeping for {} seconds after collectd restart...'.format(sleep_time))
511                         time.sleep(sleep_time)
512                     if plugins_to_enable and not collectd_restarted:
513                         for warning in collectd_warnings:
514                             logger.warning(warning)
515                         logger.error('Restart of collectd on node {} failed'.format(node_id))
516                         logger.info('Testcases on node {} will not be executed'.format(node_id))
517                     else:
518                         if collectd_warnings:
519                             for warning in collectd_warnings:
520                                 logger.warning(warning)
521
522                         for plugin_name in sorted(plugin_labels.keys()):
523                             _exec_testcase(
524                                 plugin_labels, plugin_name, ceilometer_running,
525                                 compute_node, conf, results, error_plugins)
526
527             _print_label('NODE {}: Restoring config file'.format(node_id))
528             conf.restore_config(compute_node)
529
530     print_overall_summary(compute_ids, plugin_labels, results, out_plugins)
531
532     if ((len([res for res in results if not res[2]]) > 0)
533             or (len(results) < len(computes) * len(plugin_labels))):
534         logger.error('Some tests have failed or have not been executed')
535         return 1
536     return 0
537
538 if __name__ == '__main__':
539      sys.exit(main())