Merge "Don't check console output if not available"
diff --git a/.zuul.yaml b/.zuul.yaml
index e646d9b..ec6c59a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -13,6 +13,82 @@
     run: playbooks/devstack-tempest.yaml
     post-run: playbooks/post-tempest.yaml
 
+- job:
+    name: tempest-tox-plugin-sanity-check
+    parent: tox
+    description: |
+      Run tempest plugin sanity check script using tox.
+    nodeset: ubuntu-xenial
+    vars:
+      tox_envlist: plugin-sanity-check
+    voting: false
+    timeout: 5000
+    required-projects:
+      - openstack/almanach
+      - openstack/aodh
+      - openstack/barbican-tempest-plugin
+      - openstack/ceilometer
+      - openstack/cinder
+      - openstack/congress
+      - openstack/designate-tempest-plugin
+      - openstack/ec2-api
+      - openstack/freezer
+      - openstack/freezer-api
+      - openstack/freezer-tempest-plugin
+      - openstack/gce-api
+      - openstack/glare
+      - openstack/heat
+      - openstack/intel-nfv-ci-tests
+      - openstack/ironic
+      - openstack/ironic-inspector
+      - openstack/keystone-tempest-plugin
+      - openstack/kingbird
+      - openstack/kuryr-tempest-plugin
+      - openstack/magnum
+      - openstack/magnum-tempest-plugin
+      - openstack/manila
+      - openstack/manila-tempest-plugin
+      - openstack/mistral
+      - openstack/mogan
+      - openstack/monasca-api
+      - openstack/monasca-log-api
+      - openstack/murano
+      - openstack/networking-bgpvpn
+      - openstack/networking-cisco
+      - openstack/networking-fortinet
+      - openstack/networking-generic-switch
+      - openstack/networking-l2gw
+      - openstack/networking-midonet
+      - openstack/networking-plumgrid
+      - openstack/networking-sfc
+      - openstack/neutron
+      - openstack/neutron-dynamic-routing
+      - openstack/neutron-fwaas
+      - openstack/neutron-lbaas
+      - openstack/neutron-tempest-plugin
+      - openstack/neutron-vpnaas
+      - openstack/nova-lxd
+      - openstack/novajoin-tempest-plugin
+      - openstack/octavia
+      - openstack/oswin-tempest-plugin
+      - openstack/panko
+      - openstack/patrole
+      - openstack/qinling
+      - openstack/requirements
+      - openstack/sahara-tests
+      - openstack/senlin
+      - openstack/senlin-tempest-plugin
+      - openstack/tap-as-a-service
+      - openstack/tempest-horizon
+      - openstack/trio2o
+      - openstack/trove
+      - openstack/valet
+      - openstack/vitrage
+      - openstack/vmware-nsx-tempest-plugin
+      - openstack/watcher-tempest-plugin
+      - openstack/zaqar-tempest-plugin
+      - openstack/zun-tempest-plugin
+
 - project:
     name: openstack/tempest
     check:
@@ -22,3 +98,4 @@
               - ^playbooks/
               - ^roles/
               - ^.zuul.yaml$
+        - tempest-tox-plugin-sanity-check
diff --git a/playbooks/post-tempest.yaml b/playbooks/post-tempest.yaml
index c0b9dd4..820e4f6 100644
--- a/playbooks/post-tempest.yaml
+++ b/playbooks/post-tempest.yaml
@@ -3,12 +3,20 @@
   vars:
     logs_root: "{{ devstack_base_dir|default('/opt/stack') }}"
     stage_dir: "{{ devstack_base_dir|default('/opt/stack') }}"
+    test_results_stage_name: 'test_results'
   roles:
+    - role: process-test-results
+      test_results_dir: '{{ logs_root }}/tempest'
+      tox_envdir: tempest
+    - role: process-stackviz
     - role: stage-output
       zuul_copy_output:
         { '{{ logs_root }}/tempest/etc/tempest.conf': 'logs',
           '{{ logs_root }}/tempest/etc/accounts.yaml': 'logs',
-          '{{ logs_root }}/tempest/tempest.log': 'logs' }
+          '{{ logs_root }}/tempest/tempest.log': 'logs',
+          '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': 'logs',
+          '{{ stage_dir }}/{{ test_results_stage_name }}.html': 'logs',
+          '{{ stage_dir }}/stackviz': 'logs' }
       extensions_to_txt:
         - conf
         - log
diff --git a/roles/process-stackviz/README.rst b/roles/process-stackviz/README.rst
new file mode 100644
index 0000000..b05326d
--- /dev/null
+++ b/roles/process-stackviz/README.rst
@@ -0,0 +1,22 @@
+Generate stackviz report.
+
+Generate stackviz report using subunit and dstat data, using
+the stackviz archive embedded in test images.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: stage_dir
+   :default: /opt/stack/logs
+
+   The stage directory where the input data can be found and
+   the output will be produced.
+
+.. zuul:rolevar:: test_results_stage_name
+   :default: test_results
+
+   The name of the subunit file to be used as input.
diff --git a/roles/process-stackviz/defaults/main.yaml b/roles/process-stackviz/defaults/main.yaml
new file mode 100644
index 0000000..b1eb8d9
--- /dev/null
+++ b/roles/process-stackviz/defaults/main.yaml
@@ -0,0 +1,3 @@
+devstack_base_dir: /opt/stack
+stage_dir: /opt/stack/
+test_results_stage_name: test_results
diff --git a/roles/process-stackviz/tasks/main.yaml b/roles/process-stackviz/tasks/main.yaml
new file mode 100644
index 0000000..09de606
--- /dev/null
+++ b/roles/process-stackviz/tasks/main.yaml
@@ -0,0 +1,63 @@
+- name: Check if stackviz archive exists
+  stat:
+    path: "/opt/cache/files/stackviz-latest.tar.gz"
+  register: stackviz_archive
+
+- debug:
+    msg: "Stackviz archive could not be found in /opt/cache/files/stackviz-latest.tar.gz"
+  when: not stackviz_archive.stat.exists
+
+- name: Check if subunit data exists
+  stat:
+    path: "{{ stage_dir }}/{{ test_results_stage_name }}.subunit"
+  register: subunit_input
+
+- debug:
+    msg: "Subunit file could not be found at {{ stage_dir }}/{{ test_results_stage_name }}.subunit"
+  when: not subunit_input.stat.exists
+
+- name: Install stackviz
+  pip:
+    name: "file://{{ stackviz_archive.stat.path }}"
+    virtualenv: /tmp/stackviz
+    extra_args: -U
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Deploy stackviz static html+js
+  command: cp -pR /tmp/stackviz/share/stackviz-html {{ stage_dir }}/stackviz
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Check if dstat data exists
+  stat:
+    path: "{{ devstack_base_dir }}/logs/dstat-csv.log"
+  register: dstat_input
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Run stackviz with dstat
+  shell: |
+    cat {{ subunit_input.stat.path }} | \
+      /tmp/stackviz/bin/stackviz-export \
+        --dstat "{{ devstack_base_dir }}/logs/dstat-csv.log" \
+        --env --stdin \
+        {{ stage_dir }}/stackviz/data
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+    - dstat_input.stat.exists
+
+- name: Run stackviz without dstat
+  shell: |
+    cat {{ subunit_input.stat.path }} | \
+      /tmp/stackviz/bin/stackviz-export \
+        --env --stdin \
+        {{ stage_dir }}/stackviz/data
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+    - not dstat_input.stat.exists
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 705814c..9ee8858 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -434,7 +434,7 @@
             # the compute API will return a 400 response.
             if volume['status'] == 'in-use':
                 self.servers_client.detach_volume(server['id'], volume['id'])
-        except exceptions.NotFound:
+        except lib_exc.NotFound:
             # Ignore 404s on detach in case the server is deleted or the volume
             # is already detached.
             pass
diff --git a/tempest/config.py b/tempest/config.py
index 0743220..b14d4fd 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -227,7 +227,8 @@
                 help="A list of enabled identity extensions with a special "
                      "entry all which indicates every extension is enabled. "
                      "Empty list indicates all extensions are disabled. "
-                     "To get the list of extensions run: 'keystone discover'"),
+                     "To get the list of extensions run: "
+                     "'openstack extension list --identity'"),
     # TODO(rodrigods): This is a feature flag for bug 1590578 which is fixed
     # in Newton and Ocata. This option can be removed after Mitaka is end of
     # life.
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index f82f707..ef1003b 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -31,7 +31,7 @@
     """
     def decorator(f):
         @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
+        def wrapper(*func_args, **func_kwargs):
             skip = False
             if "condition" in kwargs:
                 if kwargs["condition"] is True:
@@ -43,7 +43,7 @@
                     raise ValueError('bug must be a valid bug number')
                 msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
                 raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
+            return f(*func_args, **func_kwargs)
         return wrapper
     return decorator
 
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 44bf840..8b4f913 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -120,3 +120,8 @@
         failed_plugin+=", $project"
     fi
 done
+
+# Check for failed status
+if [[ -n $failed_plugin ]]; then
+    exit 1
+fi