Add ping test in aclrecovery suite
[integration/test.git] / csit / scripts / generate_visState.py
1 # SPDX-License-Identifier: EPL-1.0
2 ##############################################################################
3 # Copyright (c) 2018 The Linux Foundation and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Eclipse Public License v1.0
7 # which accompanies this distribution, and is available at
8 # http://www.eclipse.org/legal/epl-v10.html
9 ##############################################################################
10 import yaml
11 from copy import deepcopy as dc
12
13 import json
14
15 # Pretty Printer
16
17
18 def p(x):
19     print(json.dumps(x, indent=4, sort_keys=True))
20
21
22 class visState:
23     # viState template
24     def __init__(self):
25         self.content = {
26             'title': None,
27             'type': None,
28             'params': {
29                 'type': None,
30                 'grid': {
31                     'categoryLines': False,
32                     'style': {
33                         'color': '#eee'
34                     }
35                 },
36                 'categoryAxes': None,
37                 'valueAxes': None,
38                 'seriesParams': None,
39                 'addTooltip': True,
40                 'addLegend': True,
41                 'legendPosition': 'right',
42                 'times': [],
43                 'addTimeMarker': False
44             },
45             'aggs': None
46         }
47
48     def create(self, config):
49         temp = self.content
50         temp['title'] = config['title']
51         temp['type'] = temp['params']['type'] = config['type']
52
53         cat = categoryAxes()
54         temp['params']['categoryAxes'] = [dc(
55             cat.create()) for i in range(config['num_cat_axes'])]
56
57         val = ValueAxes()
58         temp['params']['valueAxes'] = [dc(val.create(position=i['position'],
59                                                      title=i['title']))
60                                        for _, i in
61                                        config['value_axes'].items()]
62
63         agg = aggs()
64
65         temp['aggs'] = \
66             [dc(agg.create(id=i,
67                            field=config['aggs'][i]['field'],
68                            custom_label=config['aggs'][i]['custom_label'],
69                            schema=config['aggs'][i]['schema']))
70              for i in range(1, len(config['aggs']) + 1)]
71
72         temp['params']['seriesParams'] = [seriesParams(i['data_type'],
73                                                        i['mode'],
74                                                        i['label'],
75                                                        i['agg_id'],
76                                                        i['value_axis'])
77                                           .create()
78                                           for _, i in
79                                           config['seriesParams'].items()]
80
81         return temp
82
83
84 class categoryAxes:
85     def __init__(self):
86         self.content = {
87             'id': None,
88             'type': 'category',
89             'position': 'bottom',
90             'show': True,
91             'style': {},
92             'scale': {
93                 'type': 'linear'
94             },
95             'labels': {
96                 'show': True,
97                 'truncate': 100
98             },
99             'title': {}
100         }
101         self.counter = 0
102
103     # Category axes are named as CategoryAxis-i
104     def create(self):
105         self.counter += 1
106         temp = dc(self.content)
107         temp['id'] = 'CategoryAxis-{}'.format(self.counter)
108         return temp
109
110
111 class ValueAxes:
112     def __init__(self):
113         self.content = {
114             'id': None,
115             'name': None,
116             'type': 'value',
117             'position': 'left',
118             'show': True,
119             'style': {},
120             'scale': {
121                 'type': 'linear',
122                 'mode': 'normal'
123             },
124             'labels': {
125                 'show': True,
126                 'rotate': 0,
127                 'filter': False,
128                 'truncate': 100
129             },
130             'title': {
131                 'text': None
132             }
133         }
134         self.counter = 0
135
136     def create(self, position='left', title='Value'):
137         self.counter += 1
138         temp = dc(self.content)
139         temp['id'] = 'ValueAxis-{}'.format(self.counter)
140         if position == 'left':
141             temp['name'] = 'LeftAxis-{}'.format(self.counter)
142         elif position == 'right':
143             temp['name'] = 'RightAxis-{}'.format(self.counter)
144         else:
145             # raise ValueError('Not one of left or right')
146             # assuming default
147             temp['name'] = 'LeftAxis-{}'.format(self.counter)
148
149         temp['title']['text'] = title
150
151         return temp
152
153 # 'seriesParams' are the ones that actually show up in the plots.
154 # They point to a data source a.k.a 'aggs' (short for aggregation)
155 # to get their data.
156
157
158 class seriesParams:
159     def __init__(self, data_type, mode, label, agg_id, value_axis):
160         self.content = {
161             'show': True,
162             'type': data_type,
163             'mode': mode,
164             'data': {
165                 'label': label,
166                 'id': str(agg_id)  # the id of the aggregation they point to
167             },
168             'valueAxis': 'ValueAxis-{}'.format(value_axis),
169             'drawLinesBetweenPoints': True,
170             'showCircles': True
171         }
172
173     def create(self):
174         return self.content
175
176 # 'aggs' or aggregation refers to collection of values. They are the data
177 # source which are used by seriesParams. and as expected they take 'field'
178 # as the nested name of the key.
179 #
180 # Example, if your value is in {
181 #  'perfomance': {
182 #  'plots': {
183 #         'rate': myval,
184 #          ...
185 #        }
186 #   },
187 #   then I would have to use, 'performance.plots.rate' as the 'field' for aggs
188 # the 'schema' of an agg is 'metric' which are to be
189 # plotted in the Y-axis and 'segment' for the ones in X-axis
190
191
192 class aggs:
193     def __init__(self):
194         self.content = {
195             'id': None,
196             'enabled': True,
197             'type': None,
198             'schema': None,
199             'params': {
200                 'field': None,
201                 'customLabel': None
202             }
203         }
204         self.counter = 0
205
206     def create(self, id, field, custom_label, schema):
207         temp = dc(self.content)
208         temp['id'] = id
209         temp['params']['field'] = field
210         temp['params']['customLabel'] = custom_label
211         temp['schema'] = schema
212         if schema == 'metric':
213             temp['type'] = 'max'
214             return temp
215         elif schema == 'segment':
216             temp['type'] = 'terms'
217             temp['params']['size'] = 20  # default
218             temp['params']['order'] = 'asc'
219             temp['params']['orderBy'] = '_term'
220         return temp
221
222
223 # 'series' actually combines and simplifies both 'seriesParams' and 'aggs'
224 # Both 'seriesParams' and 'aggs' support 'default' to set default values
225
226 # generate takes both the template config and project specific config and
227 # parses and organizes as much info available from that and
228 # generates an intermediate format first which
229 # contains all necessary info to deterministically create the visState to
230 # be sent to Kibana. Hence, any error occuring in the visualizaton side
231 # must first be checked by looking at the intermediate format.
232
233 def generate(dash_config, viz_config):
234
235     format = {
236         "type": None,
237         "value_axes": {},
238         "seriesParams": {},
239         "index_pattern": None,
240         "desc": None,
241         "id": None,
242         "aggs": {},
243         "title": None,
244         "num_cat_axes": None
245     }
246
247     value_axes_format = {
248         "index": {
249             "position": None,
250             "title": None
251         }
252     }
253
254     seriesParams_format = {
255         "index": {
256             "value_axis": None,
257             "data_type": None,
258             "mode": None,
259             "label": None,
260             "agg_id": None
261         }
262     }
263
264     aggs_format = {
265         "index": {
266             "custom_label": None,
267             "field": None,
268             "schema": None
269         }
270     }
271
272     # all general description must be present in either of the config files
273     for config in [viz_config, dash_config]:
274         general_fields = ['type', 'index_pattern',
275                           'num_cat_axes', 'title', 'desc', 'id']
276         for i in general_fields:
277             try:
278                 format[i] = config[i]
279             except KeyError as e:
280                 pass
281
282     # setting any default values if available
283     mappings = {'value_axes': value_axes_format,
284                 'seriesParams': seriesParams_format, 'aggs': aggs_format}
285     for index, container in mappings.items():
286         try:
287             default_values = viz_config[index]['default']
288             for i in default_values:
289                 container['index'][i] = default_values[i]
290         except Exception:
291             pass
292
293     ####################################################################
294     # Extract 'value_axes', 'seriesParams' or 'aggs' if present in viz_config
295     value_axes_counter = 1
296     for m in viz_config['value_axes']:
297         if m != "default":
298             temp = dc(value_axes_format)
299             temp[str(value_axes_counter)] = temp['index']
300             for i in ['position', 'title']:
301                 try:
302                     temp[str(value_axes_counter)
303                          ][i] = viz_config['value_axes'][m][i]
304                 except KeyError:
305                     pass
306             format['value_axes'].update(temp)
307             value_axes_counter += 1
308
309     seriesParams_fields = ['value_axis',
310                            'data_type', 'mode', 'label', 'agg_id']
311     try:
312         for m in viz_config['seriesParams']:
313             if m != 'default':
314                 temp = dc(seriesParams_format)
315                 temp[m] = temp['index']
316                 for i in seriesParams_fields:
317                     try:
318                         temp[m][i] = viz_config['seriesParams'][m][i]
319                     except KeyError:
320                         pass
321                 format['seriesParams'].update(temp)
322     except KeyError:
323         pass
324
325     agg_counter = 1
326     try:
327         for m in viz_config['aggs']:
328             if m != 'default':
329                 temp = dc(aggs_format)
330                 temp[m] = temp['index']
331                 for i in ['field', 'custom_label', 'schema']:
332                     try:
333                         temp[m][i] = viz_config['aggs'][m][i]
334                     except KeyError:
335                         pass
336                 format['aggs'].update(temp)
337     except KeyError:
338         pass
339     ####################################################################
340
341     # collect 'series' from both the configs
342     configs = []
343     try:
344         viz_config['series']
345         configs.append(viz_config)
346     except KeyError:
347         pass
348
349     try:
350         dash_config['y-axis']['series']
351         configs.append(dash_config['y-axis'])
352     except KeyError:
353         pass
354
355     ########################################################################
356     # Extract 'series' from either of the configs
357     for config in configs:
358         try:
359             value_axes_counter = 1
360             for key in config['value_axes']:
361
362                 value_axes_temp = dc(value_axes_format)
363                 value_axes_temp[str(value_axes_counter)
364                                 ] = value_axes_temp['index']
365
366                 for index in ['position', 'title']:
367                     try:
368                         value_axes_temp[str(
369                             value_axes_counter)][index] = \
370                             config['value_axes'][key][index]
371                     except KeyError as e:
372                         pass
373                 format['value_axes'].update(value_axes_temp)
374                 value_axes_counter += 1
375
376         except KeyError as e:
377             pass
378
379         try:
380             for key in config['series']:
381                 try:
382                     # check if this key is present or not
383                     config['series'][key]['not_in_seriesParams']
384                 except KeyError:
385                     seriesParams_temp = dc(seriesParams_format)
386                     seriesParams_temp[key] = seriesParams_temp['index']
387                     for index in ['value_axis', 'data_type', 'mode', 'label']:
388                         try:
389                             seriesParams_temp[key][index] = \
390                                 config['series'][key][index]
391                         except KeyError as e:
392                             pass
393                     seriesParams_temp[key]['agg_id'] = key
394                     format['seriesParams'].update(seriesParams_temp)
395                 finally:
396                     agg_temp = dc(aggs_format)
397                     agg_temp[key] = agg_temp['index']
398                     for index in ['field', 'schema']:
399                         try:
400                             agg_temp[key][index] = config['series'][key][index]
401                         except KeyError as e:
402                             pass
403                     agg_temp[key]['custom_label'] = \
404                         config['series'][key]['label']
405                     format['aggs'].update(agg_temp)
406         except KeyError as e:
407             print("required fields are empty!")
408
409     ##########################################################################
410
411     # to remove the default template index
412     for i in ['value_axes', 'seriesParams', 'aggs']:
413         try:
414             format[i].pop('index')
415         except KeyError:
416             # print("No default index found")
417             pass
418
419     missing = config_validator(format)
420     if len(missing):
421         raise ValueError('Missing required field values :-', *missing)
422
423     p(format)
424
425     vis = visState()
426     generated_visState = vis.create(format)
427
428     # checking incase there are None values
429     # in the format indicating missing fields
430
431     missing = config_validator(generated_visState)
432     if len(missing):
433         raise ValueError('required fields are missing values! ', *missing)
434     return format, generated_visState
435
436
437 # Check the generated format if it contains any key with None
438 # as it's value which indicates incomplete information
439
440 def config_validator(val, missing=[]):
441     for key, value in val.items():
442         if isinstance(value, dict):
443             config_validator(value)
444         if value is None:
445             missing.append(key)
446     return missing
447
448
449 if __name__ == '__main__':
450     with open('viz_config.yaml', 'r') as f:
451         viz_config = yaml.safe_load(f)
452
453     with open('dash_config.yaml', 'r') as f:
454         dash_config = yaml.safe_load(f)
455
456     generate(dash_config['dashboard']['viz'][2],
457              viz_config['opendaylight-test-performance'])
458     # generate(dash_config['dashboard']['viz'][3],viz_config['opendaylight-test-performance'])
459     # generate(dash_config['dashboard']['viz'][1],viz_config['opendaylight-test-feature'])