Merge "dovetail: chown for output results file"
authorMatthewLi <matthew.lijun@huawei.com>
Fri, 30 Sep 2016 09:22:41 +0000 (09:22 +0000)
committerGerrit Code Review <gerrit@172.30.200.206>
Fri, 30 Sep 2016 09:22:42 +0000 (09:22 +0000)
20 files changed:
jjb-sandbox/releng/releng-sandbox-jobs.yml [new file with mode: 0644]
jjb-sandbox/releng/verify-sandbox-jobs.sh [new file with mode: 0755]
jjb/fastpathmetrics/fastpathmetrics.yml
jjb/fuel/fuel-plugin-verify-jobs.yml [new file with mode: 0644]
jjb/opnfv/opnfv-docker.sh
jjb/releng/releng-ci-jobs.yml
prototypes/bifrost/playbooks/roles/bifrost-prepare-for-test-dynamic/defaults/main.yml [deleted file]
prototypes/bifrost/scripts/test-bifrost-deployment.sh
utils/test/dashboard/dashboard/common/elastic_access.py
utils/test/dashboard/dashboard/conf/config.py
utils/test/dashboard/dashboard/elastic2kibana/main.py
utils/test/dashboard/dashboard/elastic2kibana/templates/visualization.json [new file with mode: 0644]
utils/test/dashboard/dashboard/mongo2elastic/main.py
utils/test/dashboard/etc/config.ini
utils/test/reporting/css/default.css [moved from utils/test/reporting/functest/default.css with 79% similarity]
utils/test/reporting/functest/reporting-status.py
utils/test/reporting/functest/reportingConf.py
utils/test/reporting/functest/template/index-status-tmpl.html
utils/test/reporting/js/gauge.js [new file with mode: 0644]
utils/test/reporting/js/trend.js [new file with mode: 0644]

diff --git a/jjb-sandbox/releng/releng-sandbox-jobs.yml b/jjb-sandbox/releng/releng-sandbox-jobs.yml
new file mode 100644 (file)
index 0000000..ee35f42
--- /dev/null
@@ -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 (executable)
index 0000000..8f67e74
--- /dev/null
@@ -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
index 504e07f..40df385 100644 (file)
@@ -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 (file)
index 0000000..affc705
--- /dev/null
@@ -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."
index c5edf7c..f56de7f 100644 (file)
@@ -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:"
index ac323a3..2d88449 100644 (file)
@@ -2,7 +2,6 @@
     name: builder-jobs
     jobs:
         - 'builder-verify-jjb'
-        - 'builder-sandbox'
         - 'builder-merge'
         - 'artifacts-api'
 
                 git pull
                 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'
 
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 (file)
index 69eb787..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
----
-node_ssh_pause: 10
-wait_timeout: 1900
-multinode_testing: false
index fb49afc..773697e 100755 (executable)
@@ -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
index b454e9a..8c6494d 100644 (file)
@@ -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)
index 2e0f1ca..b868999 100644 (file)
@@ -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)
index 38a49b6..f16879b 100644 (file)
@@ -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 (file)
index 0000000..d51d417
--- /dev/null
@@ -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 }}
+}
+
index 82b01e4..76efb14 100644 (file)
@@ -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()
index b94ac7b..1e67bd8 100644 (file)
@@ -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
similarity index 79%
rename from utils/test/reporting/functest/default.css
rename to utils/test/reporting/css/default.css
index 897c3b1..7da5e27 100644 (file)
@@ -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;
+}
index 90699bd..9df6996 100755 (executable)
@@ -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" %
index e1c4b61..1c9a2ac 100644 (file)
@@ -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
index 67c2349..2beb912 100644 (file)
@@ -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">
             <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 (file)
index 0000000..4cad16c
--- /dev/null
@@ -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 (file)
index 0000000..ec48e75
--- /dev/null
@@ -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;
+}