2 # -*- coding: utf-8 -*-
4 # @License EPL-1.0 <http://spdx.org/licenses/EPL-1.0>
5 ##############################################################################
6 # Copyright (c) 2017 Raghuram Vadapalli, Jaspreet Singh and others.
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 ##############################################################################
15 This script is used to parse logs, construct JSON BODY and push
18 Usage: python construct_json.py host:port
20 JSON body similar to following is constructed from robot files, jenkins environment
21 and plot files available in workspace available post-build.
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
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
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
49 from datetime import datetime
56 import xml.etree.ElementTree as ET
61 # ELK DB host and port to be passed as ':' separated argument
64 if ':' in sys.argv[1]:
65 ELK_DB_HOST = sys.argv[1].split(':')[0]
66 ELK_DB_PORT = sys.argv[1].split(':')[1]
68 print('Usage: python push_to_elk.py host:port')
69 print('Unable to publish data to ELK. Exiting.')
78 datetime.fromtimestamp(ts).strftime('%Y-%m-%dT%H:%M:%S.%fZ')
79 BODY['@timestamp'] = formatted_ts
81 # Plots are obtained from csv files (present in archives directory in $WORKSPACE).
83 csv_files = glob.glob('archives/*.csv')
84 BODY['project'] = 'opendaylight'
85 BODY['subject'] = 'test'
87 # If there are no csv files, then it is a functional test.
88 # Parse csv files and fill perfomance parameter values
90 if len(csv_files) == 0:
91 BODY['test-type'] = 'functional'
93 BODY['test-type'] = 'performance'
96 key = (f.split('/')[-1])[:-4]
97 BODY['plots'][key] = {}
98 lines = open(f).readlines()
99 props = lines[0].strip().split(',')
100 vals = lines[1].strip().split(',')
101 BODY['plots'][key][props[0]] = float(vals[0])
102 BODY['plots'][key][props[1]] = float(vals[1])
103 BODY['plots'][key][props[2]] = float(vals[2])
105 # Fill the required parameters whose values are obtained from environment.
107 BODY['jenkins-silo'] = os.environ['SILO']
108 BODY['test-name'] = os.environ['JOB_NAME']
109 BODY['test-run'] = os.environ['BUILD_NUMBER']
111 # Parsing robot log for statistics on no of start-time, pass/fail tests and duration.
113 robot_log = os.environ['WORKSPACE'] + '/output.xml'
114 tree = ET.parse(robot_log)
115 BODY['id'] = '{}-{}'.format(os.environ['JOB_NAME'],
116 os.environ['BUILD_NUMBER'])
117 BODY['start-time'] = tree.getroot().attrib['generated']
118 BODY['pass-tests'] = tree.getroot().find('statistics')[0][1].get('pass')
119 BODY['fail-tests'] = tree.getroot().find('statistics')[0][1].get('fail')
120 endtime = tree.getroot().find('suite').find('status').get('endtime')
121 starttime = tree.getroot().find('suite').find('status').get('starttime')
122 elap_time = datetime.strptime(endtime, '%Y%m%d %H:%M:%S.%f') \
123 - datetime.strptime(starttime, '%Y%m%d %H:%M:%S.%f')
124 BODY['duration'] = str(elap_time)
126 # Parse JSON BODY to construct PUT_URL
128 PUT_URL_INDEX = '/{}-{}'.format(BODY['project'], BODY['subject'])
129 PUT_URL_TYPE = '/{}'.format(BODY['test-type'])
130 PUT_URL_ID = '/{}-{}'.format(BODY['test-name'], BODY['test-run'])
131 PUT_URL = 'https://{}:{}{}{}{}'.format(ELK_DB_HOST, ELK_DB_PORT, PUT_URL_INDEX, PUT_URL_TYPE, PUT_URL_ID)
134 print(json.dumps(BODY, indent=4))
136 # Try to send request to ELK DB.
139 r = requests.put(PUT_URL, json=BODY)
141 print(json.dumps(json.loads(r.content), indent=4))
143 print('Unable to push data to ElasticSearch')
146 # Function to convert JSON object to string.
147 # Python puts 'true' as 'True' etc. which need handling.
149 def JSONToString(jobj):
151 retval = retval.replace('\'', '"')
152 retval = retval.replace(': ', ':')
153 retval = retval.replace(', ', ',')
154 retval = retval.replace('True', 'true')
155 retval = retval.replace('False', 'false')
156 retval = retval.replace('None', 'null')
160 # This function takes
161 # testname (string, eg: 'openflowplugin-csit-1node-periodic-bulkomatic-perf-daily-only-carbon'),
162 # fieldlist (list of fields, eg: ['pass-tests', 'failed-tests']),
163 # plotkey (string, eg: 'rate')
164 # as parameters and constructs a visualization object in JSON format
166 def getVisualization(testname, fieldlist, plotkey=''):
168 vis['title'] = testname
170 vis['title'] += '-' + plotkey
171 vis['description'] = 'visualization of ' + plotkey \
172 + ' trends for testplan ' + testname
174 vis['kibanaSavedObjectMeta'] = {'searchSourceJSON': ''}
176 'index': 'opendaylight-test',
179 'analyze_wildcard': True,
185 'index': 'opendaylight-test',
207 vis['kibanaSavedObjectMeta']['searchSourceJSON'] = \
208 JSONToString(searchSourceJSON)
209 vis['uiStateJSON'] = '{"vis":{"legendOpen":true, "colors":{"pass-tests":"#7EB26D","failed-tests":"#E24D42"}}}'
211 'title': vis['title'],
215 'addTimeMarker': False,
219 'categoryLines': False,
224 'legendPosition': 'right',
227 'id': 'CategoryAxis-1',
228 'labels': {'show': True, 'truncate': 100},
229 'position': 'bottom',
230 'scale': {'type': 'linear'},
233 'title': {'text': 'Test run number'},
244 'name': 'LeftAxis-1',
246 'scale': {'mode': 'normal', 'type': 'linear'},
249 'title': {'text': ''},
261 'extended_bounds': {},
262 'customLabel': 'Test run number',
267 if plotkey != '': # Performance plot
268 visState['type'] = 'line'
269 for field in fieldlist:
274 'drawLinesBetweenPoints': True,
276 'interpolate': 'linear',
279 'id': str(len(visState['params']['seriesParams']) + 1) + '-' + vis['title'],
280 'label': field.split('.')[-1]
282 'valueAxis': 'ValueAxis-1',
284 if plotkey != '': # Performance plot
285 seriesParam['type'] = 'line'
287 'id': str(len(visState['params']['seriesParams']) + 1) + '-' + vis['title'],
293 'customLabel': field.split('.')[-1]
297 visState['params']['seriesParams'].append(seriesParam)
298 visState['aggs'].append(agg)
300 vis['visState'] = JSONToString(visState)
305 if BODY['test-type'] == 'performance':
307 # Create visualizations for performance tests
308 # One visualization for one plot
310 for key in BODY['plots']:
312 for subkey in BODY['plots'][key]:
313 fieldlist.append('plots.' + key + '.' + subkey)
314 vis = getVisualization(BODY['test-name'], fieldlist, key)
315 vis_ids.append(BODY['test-name'] + '-' + key)
317 'https://{}:{}/.kibana/visualization/{}-{}'.format(ELK_DB_HOST, ELK_DB_PORT, BODY['test-name'], key)
319 print(json.dumps(vis, indent=4))
321 r = requests.put(PUT_URL, json=vis)
323 print(json.dumps(json.loads(r.content), indent=4))
325 print('Unable to push visualization to Kibana')
327 vis = getVisualization(BODY['test-name'],
328 ['pass-tests', 'failed-tests'])
329 vis_ids.append(BODY['test-name'])
330 PUT_URL = 'https://{}:{}/.kibana/visualization/{}'.format(ELK_DB_HOST, ELK_DB_PORT, BODY['test-name'])
332 print(json.dumps(vis, indent=4))
334 r = requests.put(PUT_URL, json=vis)
336 print(json.dumps(json.loads(r.content), indent=4))
338 print('Unable to push dashboard to Kibana')
340 # Create dashboard and add above created visualizations to it
342 DASHBOARD_NAME = BODY['test-name'].split('-')[0]
344 dashboard['title'] = DASHBOARD_NAME
345 dashboard['description'] = 'Dashboard for visualizing ' \
347 dashboard['uiStateJSON'] = '{}'
348 dashboard['optionsJSON'] = '{"darkTheme":false}'
349 dashboard['version'] = 1
350 dashboard['timeRestore'] = False
351 dashboard['kibanaSavedObjectMeta'] = {
352 'searchSourceJSON': '{"filter":[{"query":{"query_string":{"query":"*","analyze_wildcard":true}}}],'
353 '"highlightAll":true,"version":true}'
356 # Check if visualizations already present in dashboard. If present, don't add, else, add at end
358 GET_URL = 'https://{}:{}/.kibana/dashboard/{}'.format(ELK_DB_HOST, ELK_DB_PORT, DASHBOARD_NAME)
359 r = requests.get(GET_URL)
360 response = json.loads(r.content)
361 dashboard_found = response['found']
362 vis_ids_present = set()
365 print json.dumps(response, indent=4)
367 panelsJSON = yaml.safe_load(response['_source']['panelsJSON'])
368 for vis in panelsJSON:
369 vis_ids_present.add(vis['id'])
373 xpos = (len(vis_ids_present) % 2) * 6 + 1
374 ypos = (len(vis_ids_present) / 2) * 3 + 1
375 for (i, vis_id) in enumerate(vis_ids):
376 if (not dashboard_found or vis_id not in vis_ids_present):
380 'panelIndex': len(vis_ids_present) + i,
381 'type': 'visualization',
390 panelsJSON.append(panelJSON)
392 print('visualization ' + vis_id + ' already present in dashboard')
394 dashboard['panelsJSON'] = JSONToString(panelsJSON)
395 PUT_URL = 'https://{}:{}/.kibana/dashboard/{}'.format(ELK_DB_HOST, ELK_DB_PORT, DASHBOARD_NAME)
397 print(json.dumps(dashboard, indent=4))
398 r = requests.put(PUT_URL, json=dashboard)
400 print(json.dumps(json.loads(r.content), indent=4))