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] = {}
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])
106 # Fill the required parameters whose values are obtained from environment.
108 BODY['jenkins-silo'] = os.environ['SILO']
109 BODY['test-name'] = os.environ['JOB_NAME']
110 BODY['test-run'] = os.environ['BUILD_NUMBER']
112 # Parsing robot log for statistics on no of start-time, pass/fail tests and duration.
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)
127 # Parse JSON BODY to construct PUT_URL
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)
135 print(json.dumps(BODY, indent=4))
137 # Try to send request to ELK DB.
140 r = requests.put(PUT_URL, json=BODY)
142 print(json.dumps(json.loads(r.content), indent=4))
144 print('Unable to push data to ElasticSearch')
147 # Function to convert JSON object to string.
148 # Python puts 'true' as 'True' etc. which need handling.
150 def JSONToString(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')
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
167 def getVisualization(testname, fieldlist, plotkey=''):
169 vis['title'] = testname
171 vis['title'] += '-' + plotkey
172 vis['description'] = 'visualization of ' + plotkey \
173 + ' trends for testplan ' + testname
175 vis['kibanaSavedObjectMeta'] = {'searchSourceJSON': ''}
177 'index': 'opendaylight-test',
180 'analyze_wildcard': True,
186 'index': 'opendaylight-test',
208 vis['kibanaSavedObjectMeta']['searchSourceJSON'] = \
209 JSONToString(searchSourceJSON)
210 vis['uiStateJSON'] = '{"vis":{"legendOpen":true, "colors":{"pass-tests":"#7EB26D","failed-tests":"#E24D42"}}}'
212 'title': vis['title'],
216 'addTimeMarker': False,
220 'categoryLines': False,
225 'legendPosition': 'right',
228 'id': 'CategoryAxis-1',
229 'labels': {'show': True, 'truncate': 100},
230 'position': 'bottom',
231 'scale': {'type': 'linear'},
234 'title': {'text': 'Test run number'},
245 'name': 'LeftAxis-1',
247 'scale': {'mode': 'normal', 'type': 'linear'},
250 'title': {'text': ''},
262 'extended_bounds': {},
263 'customLabel': 'Test run number',
268 if plotkey != '': # Performance plot
269 visState['type'] = 'line'
270 for field in fieldlist:
275 'drawLinesBetweenPoints': True,
277 'interpolate': 'linear',
280 'id': str(len(visState['params']['seriesParams']) + 1) + '-' + vis['title'],
281 'label': field.split('.')[-1]
283 'valueAxis': 'ValueAxis-1',
285 if plotkey != '': # Performance plot
286 seriesParam['type'] = 'line'
288 'id': str(len(visState['params']['seriesParams']) + 1) + '-' + vis['title'],
294 'customLabel': field.split('.')[-1]
298 visState['params']['seriesParams'].append(seriesParam)
299 visState['aggs'].append(agg)
301 vis['visState'] = JSONToString(visState)
306 if BODY['test-type'] == 'performance':
308 # Create visualizations for performance tests
309 # One visualization for one plot
311 for key in BODY['plots']:
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)
318 'https://{}:{}/.kibana/visualization/{}-{}'.format(ELK_DB_HOST, ELK_DB_PORT, BODY['test-name'], key)
320 print(json.dumps(vis, indent=4))
322 r = requests.put(PUT_URL, json=vis)
324 print(json.dumps(json.loads(r.content), indent=4))
326 print('Unable to push visualization to Kibana')
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'])
333 print(json.dumps(vis, indent=4))
335 r = requests.put(PUT_URL, json=vis)
337 print(json.dumps(json.loads(r.content), indent=4))
339 print('Unable to push dashboard to Kibana')
341 # Create dashboard and add above created visualizations to it
343 DASHBOARD_NAME = BODY['test-name'].split('-')[0]
345 dashboard['title'] = DASHBOARD_NAME
346 dashboard['description'] = 'Dashboard for visualizing ' \
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}'
357 # Check if visualizations already present in dashboard. If present, don't add, else, add at end
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()
366 print json.dumps(response, indent=4)
368 panelsJSON = yaml.safe_load(response['_source']['panelsJSON'])
369 for vis in panelsJSON:
370 vis_ids_present.add(vis['id'])
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):
381 'panelIndex': len(vis_ids_present) + i,
382 'type': 'visualization',
391 panelsJSON.append(panelJSON)
393 print('visualization ' + vis_id + ' already present in dashboard')
395 dashboard['panelsJSON'] = JSONToString(panelsJSON)
396 PUT_URL = 'https://{}:{}/.kibana/dashboard/{}'.format(ELK_DB_HOST, ELK_DB_PORT, DASHBOARD_NAME)
398 print(json.dumps(dashboard, indent=4))
399 r = requests.put(PUT_URL, json=dashboard)
401 print(json.dumps(json.loads(r.content), indent=4))