Initial Barometer Functest Scripts
[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
24 CEILOMETER_NAME = 'ceilometer'
25
26
27 class KeystoneException(Exception):
28     """Keystone exception class"""
29     def __init__(self, message, exc=None, response=None):
30         """
31         Keyword arguments:
32         message -- error message
33         exc -- exception
34         response -- response
35         """
36         if exc:
37             message += "\nReason: %s" % exc
38         super(KeystoneException, self).__init__(message)
39
40         self.response = response
41         self.exception = exc
42
43
44 class InvalidResponse(KeystoneException):
45     """Invalid Keystone exception class"""
46     def __init__(self, exc, response):
47         """
48         Keyword arguments:
49         exc -- exception
50         response -- response
51         """
52         super(InvalidResponse, self).__init__(
53             "Invalid response", exc, response)
54
55
56 class CeilometerClient(object):
57     """Ceilometer Client to authenticate and request meters"""
58     def __init__(self, bc_logger):
59         """
60         Keyword arguments:
61         bc_logger - logger instance
62         """
63         self._auth_token = None
64         self._ceilometer_url = None
65         self._meter_list = None
66         self._logger = bc_logger
67
68     def auth_token(self):
69         """Get auth token"""
70         self._auth_server()
71         return self._auth_token
72
73     def get_ceilometer_url(self):
74         """Get Ceilometer URL"""
75         return self._ceilometer_url
76
77     def get_ceil_metrics(self, criteria=None):
78         """Get Ceilometer metrics for given criteria
79
80         Keyword arguments:
81         criteria -- criteria for ceilometer meter list
82         """
83         self._request_meters(criteria)
84         return self._meter_list
85
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']
99                         break
100
101         if self._ceilometer_url is None:
102             self._logger.warning('Ceilometer is not registered in service catalog')
103
104     def _request_meters(self, criteria):
105         """Request meter list values from ceilometer
106
107         Keyword arguments:
108         criteria -- criteria for ceilometer meter list
109         """
110         if criteria is None:
111             url = self._ceilometer_url + ('/v2/samples?limit=400')
112         else:
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)
116         try:
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)
121
122
123 class CSVClient(object):
124     """Client to request CSV meters"""
125     def __init__(self, bc_logger, conf):
126         """
127         Keyword arguments:
128         bc_logger - logger instance
129         conf -- ConfigServer instance
130         """
131         self._logger = bc_logger
132         self.conf = conf
133
134     def get_csv_metrics(self, compute_node, plugin_subdirectories, meter_categories):
135         """Get CSV metrics.
136
137         Keyword arguments:
138         compute_node -- compute node instance
139         plugin_subdirectories -- list of subdirectories of plug-in
140         meter_categories -- categories which will be tested
141
142         Return list of metrics.
143         """
144         stdout = self.conf.execute_command("date '+%Y-%m-%d'", compute_node.get_ip())
145         date = stdout[0].strip()
146         metrics = []
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
155                 values = stdout
156                 if len(values) < 2:
157                     self._logger.error(
158                         'Getting last two CSV entries of meter category '
159                         + '{0} in {1} subdir failed'.format(meter_category, plugin_subdir))
160                 else:
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))
164         return metrics
165
166
167 def _check_logger():
168     """Check whether there is global logger available and if not, define one."""
169     if 'logger' not in globals():
170         global logger
171         logger = logger.Logger("barometercollectd").getLogger()
172
173
174 def _process_result(compute_node, test, result, results_list):
175     """Print test result and append it to results list.
176
177     Keyword arguments:
178     test -- testcase name
179     result -- boolean test result
180     results_list -- results list
181     """
182     if result:
183         logger.info('Compute node {0} test case {1} PASSED.'.format(compute_node, test))
184     else:
185         logger.error('Compute node {0} test case {1} FAILED.'.format(compute_node, test))
186     results_list.append((compute_node, test, result))
187
188
189 def _print_label(label):
190     """Print label on the screen
191
192     Keyword arguments:
193     label -- label string
194     """
195     label = label.strip()
196     length = 70
197     if label != '':
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))
205
206
207 def _print_plugin_label(plugin, node_id):
208     """Print plug-in label.
209
210     Keyword arguments:
211     plugin -- plug-in name
212     node_id -- node ID
213     """
214     _print_label('Node {0}: Plug-in {1} Test case execution'.format(node_id, plugin))
215
216
217 def _print_final_result_of_plugin(plugin, compute_ids, results, out_plugins, out_plugin):
218     """Print final results of plug-in.
219
220     Keyword arguments:
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
226     """
227     print_line = ''
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   |'
234             else:
235                 print_line += ' NOT EX |'
236         elif out_plugin == 'Ceilometer':
237             print_line += ' NOT EX |'
238         else:
239             print_line += ' SKIP   |'
240     return print_line
241
242
243 def print_overall_summary(compute_ids, tested_plugins, results, out_plugins):
244     """Print overall summary table.
245
246     Keyword arguments:
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
251     """
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)) + '+')
259     logger.info(
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 = \
277                         'PASS' if [
278                             plugin for comp_id, plugin,
279                             res in results if comp_id == id and res] else 'FAIL'
280                 else:
281                     out_plugin_result = 'SKIP'
282             output_plugins_line += '| ' + out_plugin_result + '   '
283         logger.info(
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)
292
293
294 def _exec_testcase(
295         test_labels, name, ceilometer_running, compute_node,
296         conf, results, error_plugins):
297     """Execute the testcase.
298
299     Keyword arguments:
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
310     """
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.')],
319         'ovs_events': [(
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}
328     csv_subdirs = {
329         'hugepages': [
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'],
333         'ovs_events': [
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'],
338         'mcelog': [
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']}
342
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)
351     else:
352         plugin_errors = [
353             error for plugin, error, critical in error_plugins if plugin == name and not critical]
354         if plugin_errors:
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:
364             logger.error(
365                 '{} test will not be executed, '.format(name)
366                 + 'following prerequisites failed:')
367             for prerequisite in failed_prerequisites:
368                 logger.error(' * {}'.format(prerequisite))
369         else:
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 ['']))
377             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:
383                 logger.info(
384                     'Test works, but will be reported as failure,'
385                     + 'because of non-critical errors.')
386                 res = False
387             _process_result(compute_node.get_id(), test_labels[name], res, results)
388
389
390 def main(bt_logger=None):
391     """Check each compute node sends ceilometer metrics.
392
393     Keyword arguments:
394     bt_logger -- logger instance
395     """
396     logging.getLogger("paramiko").setLevel(logging.WARNING)
397     logging.getLogger("stevedore").setLevel(logging.WARNING)
398     if bt_logger is None:
399         _check_logger()
400     else:
401         global logger
402         logger = bt_logger
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!')
407         return 1
408     computes = conf.get_computes()
409     if len(computes) == 0:
410         logger.error('No compute nodes found!')
411         return 1
412
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]))
418
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.")
428     else:
429         logger.error("Ceilometer is not running on control node.")
430         logger.info("CSV will be enabled on compute nodes.")
431     compute_ids = []
432     results = []
433     plugin_labels = {
434         'hugepages': 'Hugepages',
435         'mcelog': 'Mcelog',
436         'ovs_events': 'OVS events'}
437     out_plugins = {}
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))
449         else:
450             collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
451             sleep_time = 30
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))
459             else:
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.")
468                 else:
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:
474                     _print_label(
475                         'NODE {}: Enabling Test Plug-in '.format(node_id)
476                         + 'and Test case execution')
477                 error_plugins = []
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))
482                 else:
483                     if plugins_to_enable:
484                         collectd_restarted, collectd_warnings = conf.restart_collectd(compute_node)
485                         sleep_time = 30
486                         logger.info(
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))
494                     else:
495                         if collectd_warnings:
496                             for warning in collectd_warnings:
497                                 logger.warning(warning)
498
499                         for plugin_name in sorted(plugin_labels.keys()):
500                             _exec_testcase(
501                                 plugin_labels, plugin_name, ceilometer_running,
502                                 compute_node, conf, results, error_plugins)
503
504             _print_label('NODE {}: Restoring config file'.format(node_id))
505             conf.restore_config(compute_node)
506
507     print_overall_summary(compute_ids, plugin_labels, results, out_plugins)
508
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')
512         return 1
513     return 0
514
515 if __name__ == '__main__':
516      sys.exit(main())