diff options
20 files changed, 775 insertions, 213 deletions
diff --git a/jjb-sandbox/releng/releng-sandbox-jobs.yml b/jjb-sandbox/releng/releng-sandbox-jobs.yml new file mode 100644 index 000000000..ee35f4299 --- /dev/null +++ b/jjb-sandbox/releng/releng-sandbox-jobs.yml @@ -0,0 +1,77 @@ +- project: + name: 'releng-sandbox-jobs' + jobs: + - 'releng-deploy-sandbox' + - 'releng-clear-jenkins-jobs' + + project: 'releng' + +- job-template: + name: 'releng-deploy-sandbox' + + parameters: + - project-parameter: + project: '{project}' + - gerrit-parameter: + branch: 'master' + scm: + - gerrit-trigger-scm: + credentials-id: '{ssh-credentials}' + refspec: '$GERRIT_REFSPEC' + choosing-strategy: 'gerrit' + + triggers: + - gerrit: + trigger-on: + - patchset-created-event: + exclude-drafts: 'false' + exclude-trivial-rebase: 'false' + exclude-no-code-change: 'false' + - draft-published-event + - comment-added-contains-event: + comment-contains-value: 'redeploy' + projects: + - project-compare-type: 'ANT' + project-pattern: 'releng' + branches: + - branch-compare-type: 'ANT' + branch-pattern: '**/master' + file-paths: + - compare-type: ANT + pattern: jjb-sandbox/** + - compare-type: ANT + pattern: utils/** + + builders: + - shell: + !include-raw-escape: verify-sandbox-jobs.sh + - shell: | + #! /bin/bash + jenkins-jobs update -r jjb-sandbox + + publishers: + - archive-artifacts: + artifacts: 'job_output/*' + +- job-template: + name: 'releng-clear-jenkins-jobs' + + parameters: + - project-parameter: + project: '{project}' + - gerrit-parameter: + branch: 'master' + + scm: + - gerrit-trigger-scm: + credentials-id: '{ssh-credentials}' + refspec: '' + choosing-strategy: 'default' + + triggers: + - timed: '@weekly' + + builders: + - shell: | + #! /bin/bash + jenkins-jobs delete -r -p jjb-sandbox -x jjb-sandbox/releng diff --git a/jjb-sandbox/releng/verify-sandbox-jobs.sh b/jjb-sandbox/releng/verify-sandbox-jobs.sh new file mode 100755 index 000000000..8f67e742b --- /dev/null +++ b/jjb-sandbox/releng/verify-sandbox-jobs.sh @@ -0,0 +1,21 @@ +#! /bin/bash +# SPDX-license-identifier: Apache-2.0 +############################################################################## +# Copyright (c) 2016 Linux Foundation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +#test for non-ascii characters, these can pass the test and end up breaking things in production +for x in $(find . -name *\.yml); do + + if LC_ALL=C grep -q '[^[:print:][:space:]]' "$x"; then + echo "file "$x" contains non-ascii characters" + exit 1 + fi + +done + +jenkins-jobs test -r jjb/releng-defaults.yaml:jjb/releng-macros.yaml:jjb/opnfv/installer-params.yml:jjb/opnfv/slave-params.yml:jjb-sandbox \ + -o job_output diff --git a/jjb/fastpathmetrics/fastpathmetrics.yml b/jjb/fastpathmetrics/fastpathmetrics.yml index 504e07f25..40df385d8 100644 --- a/jjb/fastpathmetrics/fastpathmetrics.yml +++ b/jjb/fastpathmetrics/fastpathmetrics.yml @@ -18,7 +18,7 @@ gs-pathname: '' disabled: false - colorado: - branch: '{stream}' + branch: 'stable/{stream}' gs-pathname: '/{stream}' disabled: false diff --git a/jjb/fuel/fuel-plugin-verify-jobs.yml b/jjb/fuel/fuel-plugin-verify-jobs.yml new file mode 100644 index 000000000..affc705f3 --- /dev/null +++ b/jjb/fuel/fuel-plugin-verify-jobs.yml @@ -0,0 +1,226 @@ +- project: + name: 'fuel-plugin-verify-jobs' + + project: 'fuel-plugin' + + installer: 'fuel' +##################################### +# branch definitions +##################################### + stream: + - master: + branch: '{stream}' + gs-pathname: '' + disabled: false +##################################### +# patch verification phases +##################################### + phase: + - 'build': + slave-label: 'opnfv-build-ubuntu' + - 'test': + slave-label: 'opnfv-build-ubuntu' +##################################### +# jobs +##################################### + jobs: + - 'fuel-verify-plugin-{stream}' + - 'fuel-verify-plugin-{phase}-{stream}' +##################################### +# job templates +##################################### +- job-template: + name: 'fuel-verify-plugin-{stream}' + + project-type: multijob + + disabled: '{obj:disabled}' + + concurrent: true + + properties: + - throttle: + enabled: true + max-total: 4 + option: 'project' + + # the url to plugin repo can essentially become a variable if + # the plugin name is injected to env by gerrit plugin + scm: + - git: + url: 'https://git.openstack.org/openstack/fuel-plugin-bgpvpn' + refspec: '$GERRIT_REFSPEC' + branches: + - 'origin/$GERRIT_BRANCH' + skip-tag: true + choosing-strategy: 'gerrit' + timeout: 10 + wipe-workspace: true + + wrappers: + - ssh-agent-credentials: + users: + - '{ssh-credentials}' + - timeout: + timeout: 360 + fail: true + + triggers: + - gerrit: + server-name: 'review.openstack.org' + silent-start: false + skip-vote: + successful: true + failed: true + unstable: true + notbuilt: true + escape-quotes: true + trigger-on: + - patchset-created-event: + exclude-drafts: 'false' + exclude-trivial-rebase: 'false' + exclude-no-code-change: 'false' + - comment-added-contains-event: + comment-contains-value: 'recheck' + - comment-added-contains-event: + comment-contains-value: 'reverify' + projects: + - project-compare-type: 'PLAIN' + project-pattern: 'openstack/fuel-plugin-bgpvpn' + branches: + - branch-compare-type: 'ANT' + branch-pattern: '**/{branch}' + forbidden-file-paths: + - compare-type: ANT + pattern: 'README.md|.gitignore|.gitreview' + readable-message: true + + parameters: + - project-parameter: + project: '{project}' + - gerrit-parameter: + branch: '{branch}' + - 'opnfv-build-defaults' + - 'fuel-verify-plugin-defaults': + gs-pathname: '{gs-pathname}' + + builders: + - description-setter: + description: "Built on $NODE_NAME" + - multijob: + name: build + condition: SUCCESSFUL + projects: + - name: 'fuel-verify-plugin-build-{stream}' + current-parameters: false + predefined-parameters: | + GERRIT_BRANCH=$GERRIT_BRANCH + GERRIT_REFSPEC=$GERRIT_REFSPEC + GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER + GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE + node-parameters: false + kill-phase-on: FAILURE + abort-all-job: true + - multijob: + name: test + condition: SUCCESSFUL + projects: + - name: 'fuel-verify-plugin-test-{stream}' + current-parameters: false + predefined-parameters: | + GERRIT_BRANCH=$GERRIT_BRANCH + GERRIT_REFSPEC=$GERRIT_REFSPEC + GERRIT_CHANGE_NUMBER=$GERRIT_CHANGE_NUMBER + GERRIT_CHANGE_COMMIT_MESSAGE=$GERRIT_CHANGE_COMMIT_MESSAGE + node-parameters: false + kill-phase-on: FAILURE + abort-all-job: true + +- job-template: + name: 'fuel-verify-plugin-{phase}-{stream}' + + disabled: '{obj:disabled}' + + concurrent: true + + properties: + - throttle: + enabled: true + max-total: 6 + option: 'project' + - build-blocker: + use-build-blocker: true + blocking-jobs: + - 'fuel-verify-plugin-test-.*' + block-level: 'NODE' + + # the url to plugin repo can essentially become a variable if + # the plugin name is injected to env by gerrit plugin + scm: + - git: + url: 'https://git.openstack.org/openstack/fuel-plugin-bgpvpn' + refspec: '$GERRIT_REFSPEC' + branches: + - 'origin/$GERRIT_BRANCH' + skip-tag: true + choosing-strategy: 'gerrit' + timeout: 10 + wipe-workspace: true + + wrappers: + - ssh-agent-credentials: + users: + - '{ssh-credentials}' + - timeout: + timeout: 360 + fail: true + parameters: + - project-parameter: + project: '{project}' + - gerrit-parameter: + branch: '{branch}' + - '{slave-label}-defaults' + - '{installer}-defaults' + - 'fuel-verify-plugin-defaults': + gs-pathname: '{gs-pathname}' + + builders: + - description-setter: + description: "Built on $NODE_NAME" + - 'fuel-verify-plugin-{phase}-macro' +##################################### +# builder macros +##################################### +- builder: + name: 'fuel-verify-plugin-build-macro' + builders: + - shell: | + #!/bin/bash + + echo "Not activated!" + +- builder: + name: 'fuel-verify-plugin-test-macro' + builders: + - shell: | + #!/bin/bash + + echo "Not activated!" +##################################### +# parameter macros +##################################### +- parameter: + name: 'fuel-verify-plugin-defaults' + parameters: + - string: + name: BUILD_DIRECTORY + default: $WORKSPACE/build_output + description: "Directory where the build artifact will be located upon the completion of the build." + - string: + name: CACHE_DIRECTORY + default: $HOME/opnfv/cache/$INSTALLER_TYPE + description: "Directory where the cache to be used during the build is located." + - string: + name: GS_URL + default: artifacts.opnfv.org/$PROJECT{gs-pathname} + description: "URL to Google Storage." diff --git a/jjb/opnfv/opnfv-docker.sh b/jjb/opnfv/opnfv-docker.sh index c5edf7cc3..f56de7f60 100644 --- a/jjb/opnfv/opnfv-docker.sh +++ b/jjb/opnfv/opnfv-docker.sh @@ -59,7 +59,7 @@ if [[ "$UPDATE_LATEST_STABLE" == "true" ]]; then echo "ERROR: The image $DOCKER_REPO_NAME with tag $STABLE_TAG does not exist." exit 1 fi - docker tag -f $DOCKER_REPO_NAME:$STABLE_TAG $DOCKER_REPO_NAME:latest_stable + docker tag $DOCKER_REPO_NAME:$STABLE_TAG $DOCKER_REPO_NAME:latest_stable echo "Pushing $DOCKER_REPO_NAME:latest_stable ..." docker push $DOCKER_REPO_NAME:latest_stable exit 0 @@ -119,7 +119,7 @@ else fi echo "Creating tag '$DOCKER_TAG'..." -docker tag -f $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG $DOCKER_REPO_NAME:$DOCKER_TAG +docker tag $DOCKER_REPO_NAME:$DOCKER_BRANCH_TAG $DOCKER_REPO_NAME:$DOCKER_TAG # list the images echo "Available images are:" diff --git a/jjb/releng/releng-ci-jobs.yml b/jjb/releng/releng-ci-jobs.yml index ac323a3c1..2d88449e8 100644 --- a/jjb/releng/releng-ci-jobs.yml +++ b/jjb/releng/releng-ci-jobs.yml @@ -2,7 +2,6 @@ name: builder-jobs jobs: - 'builder-verify-jjb' - - 'builder-sandbox' - 'builder-merge' - 'artifacts-api' @@ -102,55 +101,6 @@ jenkins-jobs update -r --delete-old jjb/ - job-template: - name: 'builder-sandbox' - - # Upload all jjb jobs to sandbox instance, excluding jobs jjb - # builder jobs - - parameters: - - project-parameter: - project: '{project}' - - gerrit-parameter: - branch: 'master' - - scm: - - gerrit-trigger-scm: - credentials-id: '{ssh-credentials}' - refspec: '' - choosing-strategy: 'default' - - triggers: - - gerrit: - trigger-on: - - change-merged-event - - comment-added-contains-event: - comment-contains-value: 'remerge' - projects: - - project-compare-type: 'ANT' - project-pattern: 'releng' - branches: - - branch-compare-type: 'ANT' - branch-pattern: '**/sandbox' - file-paths: - - compare-type: ANT - pattern: jjb/** - - compare-type: ANT - pattern: utils/** - - builders: - - shell: - !include-raw: verify-releng.sh - - shell: | - #!/bin/bash - source /opt/virtualenv/jenkins-job-builder/bin/activate - cd /opt/jenkins-ci/releng - git pull - cp /etc/jenkins_jobs/jenkins_jobs.ini jenkins_sandbox.ini - sed -i 's/url=.*/url=https:\/\/sandbox.opnfv.org\//g' jenkins_sandbox.ini - jenkins-jobs --conf jenkins_sandbox.ini update -r -x jjb/releng --delete-old jjb - rm -f jenkins_sandbox.ini - -- job-template: name: 'artifacts-api' # Generate and upload the JSON file to used for artifacts site diff --git a/prototypes/bifrost/playbooks/roles/bifrost-prepare-for-test-dynamic/defaults/main.yml b/prototypes/bifrost/playbooks/roles/bifrost-prepare-for-test-dynamic/defaults/main.yml deleted file mode 100644 index 69eb787e7..000000000 --- a/prototypes/bifrost/playbooks/roles/bifrost-prepare-for-test-dynamic/defaults/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -node_ssh_pause: 10 -wait_timeout: 1900 -multinode_testing: false diff --git a/prototypes/bifrost/scripts/test-bifrost-deployment.sh b/prototypes/bifrost/scripts/test-bifrost-deployment.sh index fb49afc42..773697efe 100755 --- a/prototypes/bifrost/scripts/test-bifrost-deployment.sh +++ b/prototypes/bifrost/scripts/test-bifrost-deployment.sh @@ -18,6 +18,7 @@ ENABLE_VENV="false" USE_DHCP="false" USE_VENV="false" BUILD_IMAGE=true +PROVISION_WAIT_TIMEOUT=${PROVISION_WAIT_TIMEOUT:-2400} # Set defaults for ansible command-line options to drive the different # tests. @@ -114,7 +115,8 @@ ${ANSIBLE} -vvvv \ -e download_ipa=${DOWNLOAD_IPA} \ -e create_ipa_image=${CREATE_IPA_IMAGE} \ -e write_interfaces_file=${WRITE_INTERFACES_FILE} \ - -e ipv4_gateway=192.168.122.1 + -e ipv4_gateway=192.168.122.1 \ + -e wait_timeout=${PROVISION_WAIT_TIMEOUT} EXITCODE=$? if [ $EXITCODE != 0 ]; then diff --git a/utils/test/dashboard/dashboard/common/elastic_access.py b/utils/test/dashboard/dashboard/common/elastic_access.py index b454e9a12..8c6494d39 100644 --- a/utils/test/dashboard/dashboard/common/elastic_access.py +++ b/utils/test/dashboard/dashboard/common/elastic_access.py @@ -22,14 +22,9 @@ def delete_docs(url, creds=None, body=None): return _request('DELETE', url, creds=creds, body=body) -def publish_docs(docs, creds, to): - json_docs = json.dumps(docs) - if to == 'stdout': - print json_docs - return 200, None - else: - result = _post(to, creds=creds, body=json_docs) - return result.status, result.data +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): @@ -48,25 +43,3 @@ def get_docs(url, creds=None, body=None, field='_source'): for hit in res_data['hits']['hits']: docs.append(hit[field]) return 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_docs(elastic_url, creds, body) 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 38a49b63f..f16879b93 100644 --- a/utils/test/dashboard/dashboard/elastic2kibana/main.py +++ b/utils/test/dashboard/dashboard/elastic2kibana/main.py @@ -27,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): @@ -46,21 +49,21 @@ 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_title + self._visualization_title = self._kibana_visualizations[0].vis_state_title def _publish_visualizations(self): for visualization in self._kibana_visualizations: url = urlparse.urljoin(base_elastic_url, '/.kibana/visualization/{}'.format(visualization.id)) logger.debug("publishing visualization '{}'".format(url)) # logger.error("_publish_visualization: %s" % visualization) - elastic_access.publish_docs(visualization, es_creds, url) + elastic_access.publish_docs(url, es_creds, visualization) def _construct_panels(self): size_x = 6 @@ -138,37 +141,16 @@ 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_docs(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 VisualizationBuilder(object): +class VisStateBuilder(object): def __init__(self, visualization): - super(VisualizationBuilder, self).__init__() + super(VisStateBuilder, self).__init__() self.visualization = visualization def build(self): @@ -184,15 +166,12 @@ class VisualizationBuilder(object): }) index += 1 - env = Environment(loader=PackageLoader('elastic2kibana', 'templates')) - env.filters['jsonify'] = json.dumps 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 @@ -208,32 +187,35 @@ class KibanaVisualization(dict): :return: """ - super(KibanaVisualization, self).__init__() - vis = VisualizationBuilder(visualization).build() - self.vis_title = vis['title'] - self['title'] = '{} {} {} {} {} {}'.format(project_name, - case_name, - self.vis_title, - installer, - pod, - scenario) - self.id = self['title'].replace(' ', '-').replace('/', '-') - self['visState'] = json.dumps(vis, separators=(',', ':')) - self['uiStateJSON'] = "{}" - self['description'] = "Kibana visualization for project_name '{}', case_name '{}', metric '{}', installer '{}'," \ - " pod '{}' and scenario '{}'".format(project_name, - case_name, - self.vis_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): 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 82b01e4b3..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_docs(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/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; +} |