Fix open files
[integration/test.git] / csit / scripts / push_to_elk.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # @License EPL-1.0 <http://spdx.org/licenses/EPL-1.0>
5 ##############################################################################
6 # Copyright (c) 2017 Raghuram Vadapalli, Jaspreet Singh and others.
7 #
8 # All rights reserved. This program and the accompanying materials
9 # are made available under the terms of the Eclipse Public License v1.0
10 # which accompanies this distribution, and is available at
11 # http://www.eclipse.org/legal/epl-v10.html
12 ##############################################################################
13
14 """
15 This script is used to parse logs, construct JSON BODY and push
16 it to ELK DB.
17
18 Usage: python construct_json.py host:port
19
20 JSON body similar to following is constructed from robot files, jenkins environment
21 and plot files available in workspace available post-build.
22 {
23     "project": "opendaylight", <- fix string for ODL project
24     "subject": "test", <- fix string for ODL test
25     "test-type": "performance", <- if there are csv files, otherwise "functional"
26     "jenkins-silo": "releng" <- from Jenkins $SILO
27     "test-name": "openflowplugin-csit-1node-periodic-bulkomatic-perf-daily-only-carbon", <- from Jenkins $JOB_NAME
28     "test-run": 289, <- from Jenkins $BUILD_NUMBER
29     "start-time": "20170612 16:50:04 GMT-07:00",  <- from robot log
30     "duration": "00:01:05.942", <- from robot log
31     "pass-tests": 9, <- from robot log
32     "fail-tests": 0, <- from robot log
33     "plots": {
34         "rate": { <- csv filename
35             "Config DS": 5816.99726601, <- from csv file
36             "OVS Switch": 5757.05238918, <- from csv file
37             "Operational DS": 2654.49139945 <- from csv file
38         },
39         "time": { <- csv filename
40             "Config DS": 17.191, <- from csv file
41             "OVS Switch": 17.37, <- from csv file
42             "Operational DS": 37.672 <- from csv file
43         }
44     }
45 }
46 """
47
48 # stdlib
49 from datetime import datetime
50 import glob
51 import json
52 import os
53 import requests
54 import sys
55 import time
56 import xml.etree.ElementTree as ET
57
58 # 3rd party lib
59 import yaml
60
61 # ELK DB host and port to be passed as ':' separated argument
62
63 if len(sys.argv) > 1:
64     if ':' in sys.argv[1]:
65         ELK_DB_HOST = sys.argv[1].split(':')[0]
66         ELK_DB_PORT = sys.argv[1].split(':')[1]
67 else:
68     print('Usage: python push_to_elk.py host:port')
69     print('Unable to publish data to ELK. Exiting.')
70     sys.exit()
71
72 # Construct json body
73
74 BODY = {}
75
76 ts = time.time()
77 formatted_ts = \
78     datetime.fromtimestamp(ts).strftime('%Y-%m-%dT%H:%M:%S.%fZ')
79 BODY['@timestamp'] = formatted_ts
80
81 # Plots are obtained from csv files (present in archives directory in $WORKSPACE).
82
83 csv_files = glob.glob('archives/*.csv')
84 BODY['project'] = 'opendaylight'
85 BODY['subject'] = 'test'
86
87 # If there are no csv files, then it is a functional test.
88 # Parse csv files and fill perfomance parameter values
89
90 if len(csv_files) == 0:
91     BODY['test-type'] = 'functional'
92 else:
93     BODY['test-type'] = 'performance'
94     BODY['plots'] = {}
95     for f in csv_files:
96         key = (f.split('/')[-1])[:-4]
97         BODY['plots'][key] = {}
98         with open(f) as file:
99             lines = file.readlines()
100         props = lines[0].strip().split(',')
101         vals = lines[1].strip().split(',')
102         BODY['plots'][key][props[0]] = float(vals[0])
103         BODY['plots'][key][props[1]] = float(vals[1])
104         BODY['plots'][key][props[2]] = float(vals[2])
105
106 # Fill the required parameters whose values are obtained from environment.
107
108 BODY['jenkins-silo'] = os.environ['SILO']
109 BODY['test-name'] = os.environ['JOB_NAME']
110 BODY['test-run'] = os.environ['BUILD_NUMBER']
111
112 # Parsing robot log for statistics on no of start-time, pass/fail tests and duration.
113
114 robot_log = os.environ['WORKSPACE'] + '/output.xml'
115 tree = ET.parse(robot_log)
116 BODY['id'] = '{}-{}'.format(os.environ['JOB_NAME'],
117                             os.environ['BUILD_NUMBER'])
118 BODY['start-time'] = tree.getroot().attrib['generated']
119 BODY['pass-tests'] = tree.getroot().find('statistics')[0][1].get('pass')
120 BODY['fail-tests'] = tree.getroot().find('statistics')[0][1].get('fail')
121 endtime = tree.getroot().find('suite').find('status').get('endtime')
122 starttime = tree.getroot().find('suite').find('status').get('starttime')
123 elap_time = datetime.strptime(endtime, '%Y%m%d %H:%M:%S.%f') \
124     - datetime.strptime(starttime, '%Y%m%d %H:%M:%S.%f')
125 BODY['duration'] = str(elap_time)
126
127 # Parse JSON BODY to construct PUT_URL
128
129 PUT_URL_INDEX = '/{}-{}'.format(BODY['project'], BODY['subject'])
130 PUT_URL_TYPE = '/{}'.format(BODY['test-type'])
131 PUT_URL_ID = '/{}-{}'.format(BODY['test-name'], BODY['test-run'])
132 PUT_URL = 'https://{}:{}{}{}{}'.format(ELK_DB_HOST, ELK_DB_PORT, PUT_URL_INDEX, PUT_URL_TYPE, PUT_URL_ID)
133 print(PUT_URL)
134
135 print(json.dumps(BODY, indent=4))
136
137 # Try to send request to ELK DB.
138
139 try:
140     r = requests.put(PUT_URL, json=BODY)
141     print(r.status_code)
142     print(json.dumps(json.loads(r.content), indent=4))
143 except:
144     print('Unable to push data to ElasticSearch')
145
146
147 # Function to convert JSON object to string.
148 # Python puts 'true' as 'True' etc. which need handling.
149
150 def JSONToString(jobj):
151     retval = str(jobj)
152     retval = retval.replace('\'', '"')
153     retval = retval.replace(': ', ':')
154     retval = retval.replace(', ', ',')
155     retval = retval.replace('True', 'true')
156     retval = retval.replace('False', 'false')
157     retval = retval.replace('None', 'null')
158     return retval
159
160
161 # This function takes
162 # testname (string, eg: 'openflowplugin-csit-1node-periodic-bulkomatic-perf-daily-only-carbon'),
163 # fieldlist (list of fields, eg: ['pass-tests', 'failed-tests']),
164 # plotkey (string, eg: 'rate')
165 # as parameters and constructs a visualization object in JSON format
166
167 def getVisualization(testname, fieldlist, plotkey=''):
168     vis = {}
169     vis['title'] = testname
170     if plotkey != '':
171         vis['title'] += '-' + plotkey
172     vis['description'] = 'visualization of ' + plotkey \
173         + ' trends for testplan ' + testname
174     vis['version'] = 1
175     vis['kibanaSavedObjectMeta'] = {'searchSourceJSON': ''}
176     searchSourceJSON = {
177         'index': 'opendaylight-test',
178         'query': {
179             'query_string': {
180                 'analyze_wildcard': True,
181                 'query': '*'
182             }
183         },
184         'filter': [{
185             'meta': {
186                 'index': 'opendaylight-test',
187                 'negate': False,
188                 'disabled': False,
189                 'alias': None,
190                 'type': 'phrase',
191                 'key': 'test-name',
192                 'value': testname,
193             },
194             'query': {
195                 'match': {
196                     'test-name': {
197                         'query': testname,
198                         'type': 'phrase'
199                     }
200                 }
201             },
202             '$state': {
203                 'store': 'appState'
204             }
205         }]
206     }
207
208     vis['kibanaSavedObjectMeta']['searchSourceJSON'] = \
209         JSONToString(searchSourceJSON)
210     vis['uiStateJSON'] = '{"vis":{"legendOpen":true, "colors":{"pass-tests":"#7EB26D","failed-tests":"#E24D42"}}}'
211     visState = {
212         'title': vis['title'],
213         'type': 'area',
214         'params': {
215             'addLegend': True,
216             'addTimeMarker': False,
217             'addTooltip': True,
218             'times': [],
219             'grid': {
220                 'categoryLines': False,
221                 'style': {
222                     'color': '#eee'
223                 }
224             },
225             'legendPosition': 'right',
226             'seriesParams': [],
227             'categoryAxes': [{
228                 'id': 'CategoryAxis-1',
229                 'labels': {'show': True, 'truncate': 100},
230                 'position': 'bottom',
231                 'scale': {'type': 'linear'},
232                 'show': True,
233                 'style': {},
234                 'title': {'text': 'Test run number'},
235                 'type': 'category',
236             }],
237             'valueAxes': [{
238                 'id': 'ValueAxis-1',
239                 'labels': {
240                     'filter': False,
241                     'rotate': 0,
242                     'show': True,
243                     'truncate': 100,
244                 },
245                 'name': 'LeftAxis-1',
246                 'position': 'left',
247                 'scale': {'mode': 'normal', 'type': 'linear'},
248                 'show': True,
249                 'style': {},
250                 'title': {'text': ''},
251                 'type': 'value',
252             }],
253         },
254         'aggs': [{
255             'id': '2',
256             'enabled': True,
257             'type': 'histogram',
258             'schema': 'segment',
259             'params': {
260                 'field': 'test-run',
261                 'interval': 1,
262                 'extended_bounds': {},
263                 'customLabel': 'Test run number',
264             },
265         }],
266         'listeners': {},
267     }
268     if plotkey != '':  # Performance plot
269         visState['type'] = 'line'
270     for field in fieldlist:
271         seriesParam = {
272             'show': True,
273             'mode': 'normal',
274             'type': 'area',
275             'drawLinesBetweenPoints': True,
276             'showCircles': True,
277             'interpolate': 'linear',
278             'lineWidth': 2,
279             'data': {
280                 'id': str(len(visState['params']['seriesParams']) + 1) + '-' + vis['title'],
281                 'label': field.split('.')[-1]
282             },
283             'valueAxis': 'ValueAxis-1',
284         }
285         if plotkey != '':  # Performance plot
286             seriesParam['type'] = 'line'
287         agg = {
288             'id': str(len(visState['params']['seriesParams']) + 1) + '-' + vis['title'],
289             'enabled': True,
290             'type': 'sum',
291             'schema': 'metric',
292             'params': {
293                 'field': field,
294                 'customLabel': field.split('.')[-1]
295             },
296         }
297
298         visState['params']['seriesParams'].append(seriesParam)
299         visState['aggs'].append(agg)
300
301     vis['visState'] = JSONToString(visState)
302     return vis
303
304
305 vis_ids = []
306 if BODY['test-type'] == 'performance':
307
308     # Create visualizations for performance tests
309     # One visualization for one plot
310
311     for key in BODY['plots']:
312         fieldlist = []
313         for subkey in BODY['plots'][key]:
314             fieldlist.append('plots.' + key + '.' + subkey)
315         vis = getVisualization(BODY['test-name'], fieldlist, key)
316         vis_ids.append(BODY['test-name'] + '-' + key)
317         PUT_URL = \
318             'https://{}:{}/.kibana/visualization/{}-{}'.format(ELK_DB_HOST, ELK_DB_PORT, BODY['test-name'], key)
319         print(PUT_URL)
320         print(json.dumps(vis, indent=4))
321         try:
322             r = requests.put(PUT_URL, json=vis)
323             print(r.status_code)
324             print(json.dumps(json.loads(r.content), indent=4))
325         except:
326             print('Unable to push visualization to Kibana')
327
328 vis = getVisualization(BODY['test-name'],
329                        ['pass-tests', 'failed-tests'])
330 vis_ids.append(BODY['test-name'])
331 PUT_URL = 'https://{}:{}/.kibana/visualization/{}'.format(ELK_DB_HOST, ELK_DB_PORT, BODY['test-name'])
332 print(PUT_URL)
333 print(json.dumps(vis, indent=4))
334 try:
335     r = requests.put(PUT_URL, json=vis)
336     print(r.status_code)
337     print(json.dumps(json.loads(r.content), indent=4))
338 except:
339     print('Unable to push dashboard to Kibana')
340
341 # Create dashboard and add above created visualizations to it
342
343 DASHBOARD_NAME = BODY['test-name'].split('-')[0]
344 dashboard = {}
345 dashboard['title'] = DASHBOARD_NAME
346 dashboard['description'] = 'Dashboard for visualizing ' \
347     + DASHBOARD_NAME
348 dashboard['uiStateJSON'] = '{}'
349 dashboard['optionsJSON'] = '{"darkTheme":false}'
350 dashboard['version'] = 1
351 dashboard['timeRestore'] = False
352 dashboard['kibanaSavedObjectMeta'] = {
353     'searchSourceJSON': '{"filter":[{"query":{"query_string":{"query":"*","analyze_wildcard":true}}}],'
354     '"highlightAll":true,"version":true}'
355 }
356
357 # Check if visualizations already present in dashboard. If present, don't add, else, add at end
358
359 GET_URL = 'https://{}:{}/.kibana/dashboard/{}'.format(ELK_DB_HOST, ELK_DB_PORT, DASHBOARD_NAME)
360 r = requests.get(GET_URL)
361 response = json.loads(r.content)
362 dashboard_found = response['found']
363 vis_ids_present = set()
364 panelsJSON = []
365
366 print json.dumps(response, indent=4)
367 if dashboard_found:
368     panelsJSON = yaml.safe_load(response['_source']['panelsJSON'])
369     for vis in panelsJSON:
370         vis_ids_present.add(vis['id'])
371
372 size_x = 6
373 size_y = 3
374 xpos = (len(vis_ids_present) % 2) * 6 + 1
375 ypos = (len(vis_ids_present) / 2) * 3 + 1
376 for (i, vis_id) in enumerate(vis_ids):
377     if (not dashboard_found or vis_id not in vis_ids_present):
378         panelJSON = {
379             'size_x': size_x,
380             'size_y': size_y,
381             'panelIndex': len(vis_ids_present) + i,
382             'type': 'visualization',
383             'id': vis_id,
384             'col': xpos,
385             'row': ypos,
386         }
387         xpos += size_x
388         if xpos > 12:
389             xpos = 1
390             ypos += size_y
391         panelsJSON.append(panelJSON)
392     else:
393         print('visualization ' + vis_id + ' already present in dashboard')
394
395 dashboard['panelsJSON'] = JSONToString(panelsJSON)
396 PUT_URL = 'https://{}:{}/.kibana/dashboard/{}'.format(ELK_DB_HOST, ELK_DB_PORT, DASHBOARD_NAME)
397 print(PUT_URL)
398 print(json.dumps(dashboard, indent=4))
399 r = requests.put(PUT_URL, json=dashboard)
400 print(r.status_code)
401 print(json.dumps(json.loads(r.content), indent=4))