summaryrefslogtreecommitdiffstats
path: root/utils/test
diff options
context:
space:
mode:
Diffstat (limited to 'utils/test')
-rw-r--r--utils/test/dashboard/dashboard/common/elastic_access.py79
-rw-r--r--utils/test/dashboard/dashboard/conf/config.py12
-rw-r--r--utils/test/dashboard/dashboard/elastic2kibana/main.py174
-rw-r--r--utils/test/dashboard/dashboard/elastic2kibana/templates/duration.json45
-rw-r--r--utils/test/dashboard/dashboard/elastic2kibana/templates/success_percentage.json45
-rw-r--r--utils/test/dashboard/dashboard/elastic2kibana/templates/tests_failures.json45
-rw-r--r--utils/test/dashboard/dashboard/elastic2kibana/templates/visualization.json32
-rw-r--r--utils/test/dashboard/dashboard/mongo2elastic/main.py49
-rw-r--r--utils/test/dashboard/etc/config.ini4
-rw-r--r--utils/test/dashboard/kibana_cleanup.py4
-rw-r--r--utils/test/reporting/css/default.css (renamed from utils/test/reporting/functest/default.css)27
-rwxr-xr-xutils/test/reporting/functest/reporting-status.py7
-rw-r--r--utils/test/reporting/functest/reportingConf.py1
-rw-r--r--utils/test/reporting/functest/template/index-status-tmpl.html96
-rw-r--r--utils/test/reporting/js/gauge.js165
-rw-r--r--utils/test/reporting/js/trend.js68
16 files changed, 626 insertions, 227 deletions
diff --git a/utils/test/dashboard/dashboard/common/elastic_access.py b/utils/test/dashboard/dashboard/common/elastic_access.py
index e90a17fa3..8c6494d39 100644
--- a/utils/test/dashboard/dashboard/common/elastic_access.py
+++ b/utils/test/dashboard/dashboard/common/elastic_access.py
@@ -5,60 +5,41 @@ import urllib3
http = urllib3.PoolManager()
-def delete_request(url, creds, body=None):
+def _request(method, url, creds=None, body=None):
headers = urllib3.make_headers(basic_auth=creds)
- http.request('DELETE', url, headers=headers, body=body)
+ return http.request(method, url, headers=headers, body=body)
-def publish_json(json_ojb, creds, to):
- json_dump = json.dumps(json_ojb)
- if to == 'stdout':
- print json_dump
- return 200, None
- else:
- headers = urllib3.make_headers(basic_auth=creds)
- result = http.request('POST', to, headers=headers, body=json_dump)
- return result.status, result.data
+def _post(url, creds=None, body=None):
+ return _request('POST', url, creds=creds, body=body)
-def _get_nr_of_hits(elastic_json):
- return elastic_json['hits']['total']
+def _get(url, creds=None, body=None):
+ return json.loads(_request('GET', url, creds=creds, body=body).data)
-def get_elastic_docs(elastic_url, creds, body=None, field = '_source'):
+def delete_docs(url, creds=None, body=None):
+ return _request('DELETE', url, creds=creds, body=body)
- # 1. get the number of results
- headers = urllib3.make_headers(basic_auth=creds)
- elastic_json = json.loads(http.request('GET', elastic_url + '/_search?size=0', headers=headers, body=body).data)
- print elastic_json
- nr_of_hits = _get_nr_of_hits(elastic_json)
-
- # 2. get all results
- elastic_json = json.loads(http.request('GET', elastic_url + '/_search?size={}'.format(nr_of_hits), headers=headers, body=body).data)
-
- elastic_docs = []
- for hit in elastic_json['hits']['hits']:
- elastic_docs.append(hit[field])
- return elastic_docs
-
-
-def get_elastic_docs_by_days(elastic_url, creds, days):
- if days == 0:
- body = '''{
- "query": {
- "match_all": {}
- }
- }'''
- elif days > 0:
- body = '''{{
- "query" : {{
- "range" : {{
- "start_date" : {{
- "gte" : "now-{}d"
- }}
- }}
- }}
- }}'''.format(days)
- else:
- raise Exception('Update days must be non-negative')
- return get_elastic_docs(elastic_url, creds, body)
+
+def publish_docs(url, creds=None, body=None):
+ result = _post(url, creds=creds, body=(json.dumps(body)))
+ return result.status, result.data
+
+
+def _get_docs_nr(url, creds=None, body=None):
+ res_data = _get('{}/_search?size=0'.format(url), creds=creds, body=body)
+ print type(res_data), res_data
+ return res_data['hits']['total']
+
+
+def get_docs(url, creds=None, body=None, field='_source'):
+
+ docs_nr = _get_docs_nr(url, creds=creds, body=body)
+ res_data = _get('{}/_search?size={}'.format(url, docs_nr),
+ creds=creds, body=body)
+
+ docs = []
+ for hit in res_data['hits']['hits']:
+ docs.append(hit[field])
+ return docs
diff --git a/utils/test/dashboard/dashboard/conf/config.py b/utils/test/dashboard/dashboard/conf/config.py
index 2e0f1cabb..b868999a2 100644
--- a/utils/test/dashboard/dashboard/conf/config.py
+++ b/utils/test/dashboard/dashboard/conf/config.py
@@ -25,7 +25,6 @@ class APIConfig:
self._default_config_location = "../etc/config.ini"
self.elastic_url = 'http://localhost:9200'
self.elastic_creds = None
- self.destination = 'elasticsearch'
self.kibana_url = None
self.is_js = True
self.js_path = None
@@ -67,7 +66,6 @@ class APIConfig:
# Linking attributes to keys from file with their sections
obj.elastic_url = obj._get_str_parameter("elastic", "url")
obj.elastic_creds = obj._get_str_parameter("elastic", "creds")
- obj.destination = obj._get_str_parameter("output", "destination")
obj.kibana_url = obj._get_str_parameter("kibana", "url")
obj.is_js = obj._get_bool_parameter("kibana", "js")
obj.js_path = obj._get_str_parameter("kibana", "js_path")
@@ -77,12 +75,10 @@ class APIConfig:
def __str__(self):
return "elastic_url = %s \n" \
"elastic_creds = %s \n" \
- "destination = %s \n" \
"kibana_url = %s \n" \
"is_js = %s \n" \
"js_path = %s \n" % (self.elastic_url,
- self.elastic_creds,
- self.destination,
- self.kibana_url,
- self.is_js,
- self.js_path)
+ self.elastic_creds,
+ self.kibana_url,
+ self.is_js,
+ self.js_path)
diff --git a/utils/test/dashboard/dashboard/elastic2kibana/main.py b/utils/test/dashboard/dashboard/elastic2kibana/main.py
index c1cbc308e..f16879b93 100644
--- a/utils/test/dashboard/dashboard/elastic2kibana/main.py
+++ b/utils/test/dashboard/dashboard/elastic2kibana/main.py
@@ -3,8 +3,10 @@ import json
import urlparse
import argparse
+from jinja2 import PackageLoader, Environment
-from common import logger_utils, elastic_access
+from common import elastic_access
+from common import logger_utils
from conf import testcases
from conf.config import APIConfig
@@ -25,6 +27,9 @@ es_creds = CONF.elastic_creds
_installers = {'fuel', 'apex', 'compass', 'joid'}
+env = Environment(loader=PackageLoader('elastic2kibana', 'templates'))
+env.filters['jsonify'] = json.dumps
+
class KibanaDashboard(dict):
def __init__(self, project_name, case_name, family, installer, pod, scenarios, visualization):
@@ -44,12 +49,12 @@ class KibanaDashboard(dict):
def _create_visualizations(self):
for scenario in self.scenarios:
- self._kibana_visualizations.append(KibanaVisualization(self.project_name,
- self.case_name,
- self.installer,
- self.pod,
- scenario,
- self.visualization))
+ self._kibana_visualizations.append(Visualization(self.project_name,
+ self.case_name,
+ self.installer,
+ self.pod,
+ scenario,
+ self.visualization))
self._visualization_title = self._kibana_visualizations[0].vis_state_title
@@ -57,7 +62,8 @@ class KibanaDashboard(dict):
for visualization in self._kibana_visualizations:
url = urlparse.urljoin(base_elastic_url, '/.kibana/visualization/{}'.format(visualization.id))
logger.debug("publishing visualization '{}'".format(url))
- elastic_access.publish_json(visualization, es_creds, url)
+ # logger.error("_publish_visualization: %s" % visualization)
+ elastic_access.publish_docs(url, es_creds, visualization)
def _construct_panels(self):
size_x = 6
@@ -135,98 +141,37 @@ class KibanaDashboard(dict):
def _publish(self):
url = urlparse.urljoin(base_elastic_url, '/.kibana/dashboard/{}'.format(self.id))
logger.debug("publishing dashboard '{}'".format(url))
- elastic_access.publish_json(self, es_creds, url)
+ elastic_access.publish_docs(url, es_creds, self)
def publish(self):
self._publish_visualizations()
self._publish()
-class KibanaSearchSourceJSON(dict):
- """
- "filter": [
- {"match": {"installer": {"query": installer, "type": "phrase"}}},
- {"match": {"project_name": {"query": project_name, "type": "phrase"}}},
- {"match": {"case_name": {"query": case_name, "type": "phrase"}}}
- ]
- """
-
- def __init__(self, project_name, case_name, installer, pod, scenario):
- super(KibanaSearchSourceJSON, self).__init__()
- self["filter"] = [
- {"match": {"project_name": {"query": project_name, "type": "phrase"}}},
- {"match": {"case_name": {"query": case_name, "type": "phrase"}}},
- {"match": {"installer": {"query": installer, "type": "phrase"}}},
- {"match": {"scenario": {"query": scenario, "type": "phrase"}}}
- ]
- if pod != 'all':
- self["filter"].append({"match": {"pod_name": {"query": pod, "type": "phrase"}}})
-
-
-class VisualizationState(dict):
+class VisStateBuilder(object):
def __init__(self, visualization):
- super(VisualizationState, self).__init__()
- name = visualization.get('name')
- fields = visualization.get('fields')
-
- if name == 'tests_failures':
- mode = 'grouped'
- metric_type = 'sum'
- self['type'] = 'histogram'
- else:
- # duration or success_percentage
- mode = 'stacked'
- metric_type = 'avg'
- self['type'] = 'line'
-
- self['params'] = {
- "shareYAxis": True,
- "addTooltip": True,
- "addLegend": True,
- "smoothLines": False,
- "scale": "linear",
- "interpolate": "linear",
- "mode": mode,
- "times": [],
- "addTimeMarker": False,
- "defaultYExtents": False,
- "setYExtents": False,
- "yAxis": {}
- }
+ super(VisStateBuilder, self).__init__()
+ self.visualization = visualization
- self['aggs'] = []
+ def build(self):
+ name = self.visualization.get('name')
+ fields = self.visualization.get('fields')
- i = 1
+ aggs = []
+ index = 1
for field in fields:
- self['aggs'].append({
- "id": str(i),
- "type": metric_type,
- "schema": "metric",
- "params": {
- "field": field.get('field')
- }
- })
- i += 1
-
- self['aggs'].append({
- "id": str(i),
- "type": 'date_histogram',
- "schema": "segment",
- "params": {
- "field": "start_date",
- "interval": "auto",
- "customInterval": "2h",
- "min_doc_count": 1,
- "extended_bounds": {}
- }
+ aggs.append({
+ "id": index,
+ "field": field.get("field")
})
+ index += 1
- self['listeners'] = {}
- self['title'] = ' '.join(['{} {}'.format(x['type'], x['params']['field']) for x in self['aggs']
- if x['schema'] == 'metric'])
+ template = env.get_template('{}.json'.format(name))
+ vis = template.render(aggs=aggs)
+ return json.loads(vis)
-class KibanaVisualization(dict):
+class Visualization(object):
def __init__(self, project_name, case_name, installer, pod, scenario, visualization):
"""
We need two things
@@ -242,32 +187,35 @@ class KibanaVisualization(dict):
:return:
"""
- super(KibanaVisualization, self).__init__()
- vis_state = VisualizationState(visualization)
- self.vis_state_title = vis_state['title']
- self['title'] = '{} {} {} {} {} {}'.format(project_name,
- case_name,
- self.vis_state_title,
- installer,
- pod,
- scenario)
- self.id = self['title'].replace(' ', '-').replace('/', '-')
- self['visState'] = json.dumps(vis_state, separators=(',', ':'))
- self['uiStateJSON'] = "{}"
- self['description'] = "Kibana visualization for project_name '{}', case_name '{}', data '{}', installer '{}'," \
- " pod '{}' and scenario '{}'".format(project_name,
- case_name,
- self.vis_state_title,
- installer,
- pod,
- scenario)
- self['scenario'] = 1
- self['kibanaSavedObjectMeta'] = {"searchSourceJSON": json.dumps(KibanaSearchSourceJSON(project_name,
- case_name,
- installer,
- pod,
- scenario),
- separators=(',', ':'))}
+ super(Visualization, self).__init__()
+ visState = VisStateBuilder(visualization).build()
+ self.vis_state_title = visState['title']
+
+ vis = {
+ "visState": json.dumps(visState),
+ "filters": {
+ "project_name": project_name,
+ "case_name": case_name,
+ "installer": installer,
+ "metric": self.vis_state_title,
+ "pod_name": pod,
+ "scenario": scenario
+ }
+ }
+
+ template = env.get_template('visualization.json')
+
+ self.visualization = json.loads(template.render(vis=vis))
+ self._dumps(['visState', 'description', 'uiStateJSON'])
+ self._dumps_2deeps('kibanaSavedObjectMeta', 'searchSourceJSON')
+ self.id = self.visualization['title'].replace(' ', '-').replace('/', '-')
+
+ def _dumps(self, items):
+ for key in items:
+ self.visualization[key] = json.dumps(self.visualization[key])
+
+ def _dumps_2deeps(self, key1, key2):
+ self.visualization[key1][key2] = json.dumps(self.visualization[key1][key2])
def _get_pods_and_scenarios(project_name, case_name, installer):
@@ -286,7 +234,7 @@ def _get_pods_and_scenarios(project_name, case_name, installer):
}
})
- elastic_data = elastic_access.get_elastic_docs(urlparse.urljoin(base_elastic_url, '/test_results/mongo2elastic'),
+ elastic_data = elastic_access.get_docs(urlparse.urljoin(base_elastic_url, '/test_results/mongo2elastic'),
es_creds, query_json)
pods_and_scenarios = {}
diff --git a/utils/test/dashboard/dashboard/elastic2kibana/templates/duration.json b/utils/test/dashboard/dashboard/elastic2kibana/templates/duration.json
new file mode 100644
index 000000000..f50a668db
--- /dev/null
+++ b/utils/test/dashboard/dashboard/elastic2kibana/templates/duration.json
@@ -0,0 +1,45 @@
+{% set aggs = aggs|default([]) -%}
+
+{
+ "title": "duration",
+ "type": "line",
+ "listeners": {},
+ "params": {
+ "addLegend": true,
+ "shareYAxis": true,
+ "addTooltip": true,
+ "smoothLines": false,
+ "scale": "linear",
+ "interpolate": "linear",
+ "times": [],
+ "addTimeMarker": false,
+ "defaultYExtents": false,
+ "setYExtents": false,
+ "yAxis": {},
+ "mode": "stacked"
+ },
+ "aggs": [
+ {% for agg in aggs %}
+ {
+ "id": {{agg.id }},
+ "type": "avg",
+ "schema": "metric",
+ "params": {
+ "field": "{{agg.field}}"
+ }
+ },
+ {% endfor %}
+ {
+ "id": {{ aggs|length + 1 }},
+ "type": "date_histogram",
+ "schema": "segment",
+ "params": {
+ "field": "start_date",
+ "interval": "auto",
+ "customInterval": "2h",
+ "min_doc_count": 1,
+ "extended_bounds": {}
+ }
+ }
+ ]
+}
diff --git a/utils/test/dashboard/dashboard/elastic2kibana/templates/success_percentage.json b/utils/test/dashboard/dashboard/elastic2kibana/templates/success_percentage.json
new file mode 100644
index 000000000..993070844
--- /dev/null
+++ b/utils/test/dashboard/dashboard/elastic2kibana/templates/success_percentage.json
@@ -0,0 +1,45 @@
+{% set aggs = aggs|default([]) -%}
+
+{
+ "title": "success_percentage",
+ "type": "line",
+ "listeners": {},
+ "params": {
+ "addLegend": true,
+ "shareYAxis": true,
+ "addTooltip": true,
+ "smoothLines": false,
+ "scale": "linear",
+ "interpolate": "linear",
+ "times": [],
+ "addTimeMarker": false,
+ "defaultYExtents": false,
+ "setYExtents": false,
+ "yAxis": {},
+ "mode": "stacked"
+ },
+ "aggs": [
+ {% for agg in aggs %}
+ {
+ "id": {{agg.id }},
+ "type": "avg",
+ "schema": "metric",
+ "params": {
+ "field": "{{agg.field}}"
+ }
+ },
+ {% endfor %}
+ {
+ "id": {{ aggs|length + 1 }},
+ "type": "date_histogram",
+ "schema": "segment",
+ "params": {
+ "field": "start_date",
+ "interval": "auto",
+ "customInterval": "2h",
+ "min_doc_count": 1,
+ "extended_bounds": {}
+ }
+ }
+ ]
+}
diff --git a/utils/test/dashboard/dashboard/elastic2kibana/templates/tests_failures.json b/utils/test/dashboard/dashboard/elastic2kibana/templates/tests_failures.json
new file mode 100644
index 000000000..01f9ba89e
--- /dev/null
+++ b/utils/test/dashboard/dashboard/elastic2kibana/templates/tests_failures.json
@@ -0,0 +1,45 @@
+{% set aggs = aggs|default([]) -%}
+
+{
+ "title": "tests_failures",
+ "type": "histogram",
+ "listeners": {},
+ "params": {
+ "addLegend": true,
+ "shareYAxis": true,
+ "addTooltip": true,
+ "smoothLines": false,
+ "scale": "linear",
+ "interpolate": "linear",
+ "times": [],
+ "addTimeMarker": false,
+ "defaultYExtents": false,
+ "setYExtents": false,
+ "yAxis": {},
+ "mode": "grouped"
+ },
+ "aggs": [
+ {% for agg in aggs %}
+ {
+ "id": {{agg.id }},
+ "type": "sum",
+ "schema": "metric",
+ "params": {
+ "field": "{{agg.field}}"
+ }
+ },
+ {% endfor %}
+ {
+ "id": {{ aggs|length + 1 }},
+ "type": "date_histogram",
+ "schema": "segment",
+ "params": {
+ "field": "start_date",
+ "interval": "auto",
+ "customInterval": "2h",
+ "min_doc_count": 1,
+ "extended_bounds": {}
+ }
+ }
+ ]
+}
diff --git a/utils/test/dashboard/dashboard/elastic2kibana/templates/visualization.json b/utils/test/dashboard/dashboard/elastic2kibana/templates/visualization.json
new file mode 100644
index 000000000..d51d4174e
--- /dev/null
+++ b/utils/test/dashboard/dashboard/elastic2kibana/templates/visualization.json
@@ -0,0 +1,32 @@
+{% set vis = vis|default({}) -%}
+
+
+{
+ "description": "Kibana visualization for {{ vis.filters }}",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": {
+ "filter": [
+ {% for key, value in vis.filters.iteritems() if key != "metric" %}
+ {% if not (key == "pod_name" and value == "all") %}
+ {
+ "match": {
+ "{{ key }}": {
+ "query": "{{ value }}",
+ "type": "phrase"
+ }
+ }
+ }
+ {% if not loop.last %}
+ ,
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+ ]
+ }
+ },
+ "scenario": 1,
+ "title": "{{vis.filters.project_name}} {{vis.filters.case_name}} {{vis.filters.installer}} {{vis.filters.metric}} {{vis.filters.pod_name}} {{vis.filters.scenario}}",
+ "uiStateJSON": {},
+ "visState": {{ vis.visState }}
+}
+
diff --git a/utils/test/dashboard/dashboard/mongo2elastic/main.py b/utils/test/dashboard/dashboard/mongo2elastic/main.py
index 25b5320d7..76efb14f0 100644
--- a/utils/test/dashboard/dashboard/mongo2elastic/main.py
+++ b/utils/test/dashboard/dashboard/mongo2elastic/main.py
@@ -38,12 +38,12 @@ tmp_docs_file = './mongo-{}.json'.format(uuid.uuid4())
class DocumentPublisher:
- def __init__(self, doc, fmt, exist_docs, creds, to):
+ def __init__(self, doc, fmt, exist_docs, creds, elastic_url):
self.doc = doc
self.fmt = fmt
self.creds = creds
self.exist_docs = exist_docs
- self.to = to
+ self.elastic_url = elastic_url
self.is_formatted = True
def format(self):
@@ -64,7 +64,7 @@ class DocumentPublisher:
self._publish()
def _publish(self):
- status, data = elastic_access.publish_json(self.doc, self.creds, self.to)
+ status, data = elastic_access.publish_docs(self.elastic_url, self.creds, self.doc)
if status > 300:
logger.error('Publish record[{}] failed, due to [{}]'
.format(self.doc, json.loads(data)['error']['reason']))
@@ -163,14 +163,13 @@ class DocumentPublisher:
class DocumentsPublisher:
- def __init__(self, project, case, fmt, days, elastic_url, creds, to):
+ def __init__(self, project, case, fmt, days, elastic_url, creds):
self.project = project
self.case = case
self.fmt = fmt
self.days = days
self.elastic_url = elastic_url
self.creds = creds
- self.to = to
self.existed_docs = []
def export(self):
@@ -200,7 +199,36 @@ class DocumentsPublisher:
exit(-1)
def get_existed_docs(self):
- self.existed_docs = elastic_access.get_elastic_docs_by_days(self.elastic_url, self.creds, self.days)
+ if self.days == 0:
+ body = '''{{
+ "query": {{
+ "bool": {{
+ "must": [
+ {{ "match": {{ "project_name": "{}" }} }},
+ {{ "match": {{ "case_name": "{}" }} }}
+ ]
+ }}
+ }}
+ }}'''.format(self.project, self.case)
+ elif self.days > 0:
+ body = '''{{
+ "query": {{
+ "bool": {{
+ "must": [
+ {{ "match": {{ "project_name": "{}" }} }},
+ {{ "match": {{ "case_name": "{}" }} }}
+ ],
+ "filter": {{
+ "range": {{
+ "start_date": {{ "gte": "now-{}d" }}
+ }}
+ }}
+ }}
+ }}
+ }}'''.format(self.project, self.case, self.days)
+ else:
+ raise Exception('Update days must be non-negative')
+ self.existed_docs = elastic_access.get_docs(self.elastic_url, self.creds, body)
return self
def publish(self):
@@ -211,7 +239,7 @@ class DocumentsPublisher:
self.fmt,
self.existed_docs,
self.creds,
- self.to).format().publish()
+ self.elastic_url).format().publish()
finally:
fdocs.close()
self._remove()
@@ -223,13 +251,9 @@ class DocumentsPublisher:
def main():
base_elastic_url = urlparse.urljoin(CONF.elastic_url, '/test_results/mongo2elastic')
- to = CONF.destination
days = args.latest_days
es_creds = CONF.elastic_creds
- if to == 'elasticsearch':
- to = base_elastic_url
-
for project, case_dicts in testcases.testcases_yaml.items():
for case_dict in case_dicts:
case = case_dict.get('name')
@@ -239,5 +263,4 @@ def main():
fmt,
days,
base_elastic_url,
- es_creds,
- to).export().get_existed_docs().publish()
+ es_creds).export().get_existed_docs().publish()
diff --git a/utils/test/dashboard/etc/config.ini b/utils/test/dashboard/etc/config.ini
index b94ac7b4f..1e67bd822 100644
--- a/utils/test/dashboard/etc/config.ini
+++ b/utils/test/dashboard/etc/config.ini
@@ -4,10 +4,6 @@
url = http://localhost:9200
creds =
-[output]
-# elasticsearch or console
-destination = elasticsearch
-
[kibana]
url = http://10.63.243.17:5601/app/kibana
js = true
diff --git a/utils/test/dashboard/kibana_cleanup.py b/utils/test/dashboard/kibana_cleanup.py
index 9ce4994f5..ee0190049 100644
--- a/utils/test/dashboard/kibana_cleanup.py
+++ b/utils/test/dashboard/kibana_cleanup.py
@@ -14,10 +14,10 @@ logger.addHandler(file_handler)
def delete_all(url, es_creds):
- ids = elastic_access.get_elastic_docs(url, es_creds, body=None, field='_id')
+ ids = elastic_access.get_docs(url, es_creds, body=None, field='_id')
for id in ids:
del_url = '/'.join([url, id])
- elastic_access.delete_request(del_url, es_creds)
+ elastic_access.delete_docs(del_url, es_creds)
if __name__ == '__main__':
diff --git a/utils/test/reporting/functest/default.css b/utils/test/reporting/css/default.css
index 897c3b12b..7da5e277a 100644
--- a/utils/test/reporting/functest/default.css
+++ b/utils/test/reporting/css/default.css
@@ -75,3 +75,30 @@ h2 {
font-weight: bold;
color:rgb(128, 128, 128)
}
+
+#power-gauge g.arc {
+ fill: steelblue;
+}
+
+#power-gauge g.pointer {
+ fill: #e85116;
+ stroke: #b64011;
+}
+
+#power-gauge g.label text {
+ text-anchor: middle;
+ font-size: 14px;
+ font-weight: bold;
+ fill: #666;
+}
+
+#power-gauge path {
+
+}
+
+.axis path,
+.axis line {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
diff --git a/utils/test/reporting/functest/reporting-status.py b/utils/test/reporting/functest/reporting-status.py
index 90699bd61..9df699629 100755
--- a/utils/test/reporting/functest/reporting-status.py
+++ b/utils/test/reporting/functest/reporting-status.py
@@ -184,8 +184,13 @@ for version in conf.versions:
scenario_criteria = conf.MAX_SCENARIO_CRITERIA
s_score = str(scenario_score) + "/" + str(scenario_criteria)
- s_score_percent = float(
+ s_score_percent = 0.0
+ try:
+ s_score_percent = float(
scenario_score) / float(scenario_criteria) * 100
+ except:
+ logger.error("cannot calculate the score percent")
+
s_status = "KO"
if scenario_score < scenario_criteria:
logger.info(">>>> scenario not OK, score = %s/%s" %
diff --git a/utils/test/reporting/functest/reportingConf.py b/utils/test/reporting/functest/reportingConf.py
index e1c4b61a8..1c9a2ac9f 100644
--- a/utils/test/reporting/functest/reportingConf.py
+++ b/utils/test/reporting/functest/reportingConf.py
@@ -13,7 +13,6 @@ installers = ["apex", "compass", "fuel", "joid"]
# list of test cases declared in testcases.yaml but that must not be
# taken into account for the scoring
blacklist = ["ovno", "security_scan"]
-# versions = ["brahmaputra", "master"]
versions = ["master", "colorado"]
PERIOD = 10
MAX_SCENARIO_CRITERIA = 50
diff --git a/utils/test/reporting/functest/template/index-status-tmpl.html b/utils/test/reporting/functest/template/index-status-tmpl.html
index 67c23491a..2beb9128e 100644
--- a/utils/test/reporting/functest/template/index-status-tmpl.html
+++ b/utils/test/reporting/functest/template/index-status-tmpl.html
@@ -3,17 +3,65 @@
<meta charset="utf-8">
<!-- Bootstrap core CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
- <link href="default.css" rel="stylesheet">
+ <link href="../../../css/default.css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
- <script type="text/javascript">
- $(document).ready(function (){
- $(".btn-more").click(function() {
- $(this).hide();
- $(this).parent().find(".panel-default").show();
+ <script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
+ <script type="text/javascript" src="../../../js/gauge.js"></script>
+ <script type="text/javascript" src="../../../js/trend.js"></script>
+ <script>
+ function onDocumentReady() {
+ // Gauge management
+ {% for scenario in scenario_stats.iteritems() -%}
+ var gaugeScenario{{loop.index}} = gauge('#gaugeScenario{{loop.index}}');
+ {%- endfor %}
+
+ // assign success rate to the gauge
+ function updateReadings() {
+ {% for scenario,iteration in scenario_stats.iteritems() -%}
+ gaugeScenario{{loop.index}}.update({{scenario_results[scenario].getScorePercent()}});
+ {%- endfor %}
+ }
+ updateReadings();
+ }
+
+ // trend line management
+ d3.csv("./scenario_history.txt", function(data) {
+ // ***************************************
+ // Create the trend line
+ {% for scenario,iteration in scenario_stats.iteritems() -%}
+ // for scenario {{scenario}}
+ // Filter results
+ var trend{{loop.index}} = data.filter(function(row) {
+ return row["scenario"]=="{{scenario}}" && row["installer"]=="{{installer}}";
+ })
+ // Parse the date
+ trend{{loop.index}}.forEach(function(d) {
+ d.date = parseDate(d.date);
+ d.score = +d.score
});
- })
- </script>
+ // Draw the trend line
+ var mytrend = trend("#trend_svg{{loop.index}}",trend{{loop.index}})
+ // ****************************************
+ {%- endfor %}
+ });
+ if ( !window.isLoaded ) {
+ window.addEventListener("load", function() {
+ onDocumentReady();
+ }, false);
+ } else {
+ onDocumentReady();
+ }
+</script>
+<script type="text/javascript">
+$(document).ready(function (){
+ $(".btn-more").click(function() {
+ $(this).hide();
+ $(this).parent().find(".panel-default").show();
+ });
+})
+</script>
+
</head>
<body>
<div class="container">
@@ -40,41 +88,17 @@
<div class="panel-heading"><h4><b>List of last scenarios ({{version}}) run over the last {{period}} days </b></h4></div>
<table class="table">
<tr>
- <th width="60%">Scenario</th>
+ <th width="40%">Scenario</th>
<th width="20%">Status</th>
+ <th width="20%">Trend</th>
<th width="10%">Score</th>
<th width="10%">Iteration</th>
</tr>
{% for scenario,iteration in scenario_stats.iteritems() -%}
<tr class="tr-ok">
<td><a href={{scenario_results[scenario].getUrlLastRun()}}>{{scenario}}</a></td>
- <td>{%if scenario_results[scenario].getScorePercent() < 8.3 -%}
- <img src="../../img/gauge_0.png">
- {%elif scenario_results[scenario].getScorePercent() < 16.7 -%}
- <img src="../../img/gauge_8.3.png">
- {%elif scenario_results[scenario].getScorePercent() < 25 -%}
- <img src="../../img/gauge_16.7.png">
- {%elif scenario_results[scenario].getScorePercent() < 33.3 -%}
- <img src="../../img/gauge_25.png">
- {%elif scenario_results[scenario].getScorePercent() < 41.7 -%}
- <img src="../../img/gauge_33.3.png">
- {%elif scenario_results[scenario].getScorePercent() < 50 -%}
- <img src="../../img/gauge_41.7.png">
- {%elif scenario_results[scenario].getScorePercent() < 58.3 -%}
- <img src="../../img/gauge_50.png">
- {%elif scenario_results[scenario].getScorePercent() < 66.7 -%}
- <img src="../../img/gauge_58.3.png">
- {%elif scenario_results[scenario].getScorePercent() < 75 -%}
- <img src="../../img/gauge_66.7.png">
- {%elif scenario_results[scenario].getScorePercent() < 83.3 -%}
- <img src="../../img/gauge_75.png">
- {%elif scenario_results[scenario].getScorePercent() < 91.7 -%}
- <img src="../../img/gauge_83.3.png">
- {%elif scenario_results[scenario].getScorePercent() < 100 -%}
- <img src="../../img/gauge_91.7.png">
- {%- else -%}
- <img src="../../img/gauge_100.png">
- {%- endif %}</td>
+ <td><div id="gaugeScenario{{loop.index}}"></div></td>
+ <td><div id="trend_svg{{loop.index}}"></div></td>
<td>{{scenario_results[scenario].getScore()}}</td>
<td>{{iteration}}</td>
</tr>
diff --git a/utils/test/reporting/js/gauge.js b/utils/test/reporting/js/gauge.js
new file mode 100644
index 000000000..4cad16c61
--- /dev/null
+++ b/utils/test/reporting/js/gauge.js
@@ -0,0 +1,165 @@
+// ******************************************
+// Gauge for reporting
+// Each scenario has a score
+// We use a gauge to indicate the trust level
+// ******************************************
+var gauge = function(container) {
+ var that = {};
+ var config = {
+ size : 150,
+ clipWidth : 250,
+ clipHeight : 100,
+ ringInset : 20,
+ ringWidth : 40,
+
+ pointerWidth : 7,
+ pointerTailLength : 5,
+ pointerHeadLengthPercent : 0.8,
+
+ minValue : 0,
+ maxValue : 100,
+
+ minAngle : -90,
+ maxAngle : 90,
+
+ transitionMs : 4000,
+
+ majorTicks : 7,
+ labelFormat : d3.format(',g'),
+ labelInset : 10,
+
+ arcColorFn : d3.interpolateHsl(d3.rgb('#ff0000'), d3.rgb('#00ff00'))
+ };
+
+
+var range = undefined;
+var r = undefined;
+var pointerHeadLength = undefined;
+var value = 0;
+
+var svg = undefined;
+var arc = undefined;
+var scale = undefined;
+var ticks = undefined;
+var tickData = undefined;
+var pointer = undefined;
+
+var donut = d3.layout.pie();
+
+function deg2rad(deg) {
+ return deg * Math.PI / 180;
+}
+
+function newAngle(d) {
+ var ratio = scale(d);
+ var newAngle = config.minAngle + (ratio * range);
+ return newAngle;
+}
+
+function configure() {
+ range = config.maxAngle - config.minAngle;
+ r = config.size / 2;
+ pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);
+
+ // a linear scale that maps domain values to a percent from 0..1
+ scale = d3.scale.linear()
+ .range([0,1])
+ .domain([config.minValue, config.maxValue]);
+
+ ticks = scale.ticks(config.majorTicks);
+ tickData = d3.range(config.majorTicks).map(function() {return 1/config.majorTicks;});
+
+ arc = d3.svg.arc()
+ .innerRadius(r - config.ringWidth - config.ringInset)
+ .outerRadius(r - config.ringInset)
+ .startAngle(function(d, i) {
+ var ratio = d * i;
+ return deg2rad(config.minAngle + (ratio * range));
+ })
+ .endAngle(function(d, i) {
+ var ratio = d * (i+1);
+ return deg2rad(config.minAngle + (ratio * range));
+ });
+}
+that.configure = configure;
+
+function centerTranslation() {
+ return 'translate('+r +','+ r +')';
+}
+
+function isRendered() {
+ return (svg !== undefined);
+}
+that.isRendered = isRendered;
+
+function render(newValue) {
+ svg = d3.select(container)
+ .append('svg:svg')
+ .attr('class', 'gauge')
+ .attr('width', config.clipWidth)
+ .attr('height', config.clipHeight);
+
+ var centerTx = centerTranslation();
+
+ var arcs = svg.append('g')
+ .attr('class', 'arc')
+ .attr('transform', centerTx);
+
+ arcs.selectAll('path')
+ .data(tickData)
+ .enter().append('path')
+ .attr('fill', function(d, i) {
+ return config.arcColorFn(d * i);
+ })
+ .attr('d', arc);
+
+ var lg = svg.append('g')
+ .attr('class', 'label')
+ .attr('transform', centerTx);
+ lg.selectAll('text')
+ .data(ticks)
+ .enter().append('text')
+ .attr('transform', function(d) {
+ var ratio = scale(d);
+ var newAngle = config.minAngle + (ratio * range);
+ return 'rotate(' +newAngle +') translate(0,' +(config.labelInset - r) +')';
+ })
+ .text(config.labelFormat);
+
+ var lineData = [ [config.pointerWidth / 2, 0],
+ [0, -pointerHeadLength],
+ [-(config.pointerWidth / 2), 0],
+ [0, config.pointerTailLength],
+ [config.pointerWidth / 2, 0] ];
+ var pointerLine = d3.svg.line().interpolate('monotone');
+ var pg = svg.append('g').data([lineData])
+ .attr('class', 'pointer')
+ .attr('transform', centerTx);
+
+ pointer = pg.append('path')
+ .attr('d', pointerLine/*function(d) { return pointerLine(d) +'Z';}*/ )
+ .attr('transform', 'rotate(' +config.minAngle +')');
+
+ update(newValue === undefined ? 0 : newValue);
+}
+that.render = render;
+
+function update(newValue, newConfiguration) {
+ if ( newConfiguration !== undefined) {
+ configure(newConfiguration);
+ }
+ var ratio = scale(newValue);
+ var newAngle = config.minAngle + (ratio * range);
+ pointer.transition()
+ .duration(config.transitionMs)
+ .ease('elastic')
+ .attr('transform', 'rotate(' +newAngle +')');
+}
+that.update = update;
+
+configure();
+
+render();
+
+return that;
+};
diff --git a/utils/test/reporting/js/trend.js b/utils/test/reporting/js/trend.js
new file mode 100644
index 000000000..ec48e75ef
--- /dev/null
+++ b/utils/test/reporting/js/trend.js
@@ -0,0 +1,68 @@
+// ******************************************
+// Trend line for reporting
+// based on scenario_history.txt
+// where data looks like
+// date,scenario,installer,detail,score
+// 2016-09-22 13:12,os-nosdn-fdio-noha,apex,4/12,33.0
+// 2016-09-22 13:13,os-odl_l2-fdio-noha,apex,12/15,80.0
+// 2016-09-22 13:13,os-odl_l2-sfc-noha,apex,18/24,75.0
+// .....
+// ******************************************
+// Set the dimensions of the canvas / graph
+var trend_margin = {top: 20, right: 30, bottom: 50, left: 40},
+ trend_width = 300 - trend_margin.left - trend_margin.right,
+ trend_height = 130 - trend_margin.top - trend_margin.bottom;
+
+// Parse the date / time
+var parseDate = d3.time.format("%Y-%m-%d %H:%M").parse;
+
+// Set the ranges
+var trend_x = d3.time.scale().range([0, trend_width]);
+var trend_y = d3.scale.linear().range([trend_height, 0]);
+
+// Define the axes
+var trend_xAxis = d3.svg.axis().scale(trend_x)
+ .orient("bottom").ticks(2).tickFormat(d3.time.format("%m-%d"));
+
+var trend_yAxis = d3.svg.axis().scale(trend_y)
+ .orient("left").ticks(2);
+
+// Define the line
+var valueline = d3.svg.line()
+ .x(function(d) { return trend_x(d.date); })
+ .y(function(d) { return trend_y(d.score); });
+
+var trend = function(container, trend_data) {
+
+ var trend_svg = d3.select(container)
+ .append("svg")
+ .attr("width", trend_width + trend_margin.left + trend_margin.right)
+ .attr("height", trend_height + trend_margin.top + trend_margin.bottom)
+ .append("g")
+ .attr("transform",
+ "translate(" + trend_margin.left + "," + trend_margin.top + ")");
+
+ // Scale the range of the data
+ trend_x.domain(d3.extent(trend_data, function(d) { return d.date; }));
+ trend_y.domain([0, d3.max(trend_data, function(d) { return d.score; })]);
+
+ // Add the X Axis
+ trend_svg.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + trend_height + ")")
+ .call(trend_xAxis);
+
+ // Add the Y Axis
+ trend_svg.append("g")
+ .attr("class", "y axis")
+ .call(trend_yAxis);
+
+ // Add the valueline path.
+ trend_svg.append("path")
+ .attr("class", "line")
+ .attr("d", valueline(trend_data))
+ .attr("stroke", "steelblue")
+ .attr("fill", "none");
+
+ return trend;
+}