Merge "Create discovery option for stress tests"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 703d92a..d39ef70 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -390,3 +390,11 @@
 heat = false
 # Whether or not horizon is expected to be available
 horizon = True
+
+[stress]
+# Maximum number of instances to create during test
+max_instances = 32
+# Time (in seconds) between log file error checks
+log_check_interval = 60
+# The default number of threads created while stress test
+default_thread_number_per_action=4
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 2a1e2c4..9fa86b6 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -18,6 +18,7 @@
 from tempest.api.volume.base import BaseVolumeTest
 from tempest.common.utils.data_utils import rand_name
 from tempest.test import attr
+from tempest.test import stresstest
 
 
 class VolumesActionsTest(BaseVolumeTest):
@@ -52,6 +53,7 @@
 
         super(VolumesActionsTest, cls).tearDownClass()
 
+    @stresstest(class_setup_per='process')
     @attr(type='smoke')
     def test_attach_detach_volume_to_instance(self):
         # Volume is attached and detached successfully from an instance
@@ -65,6 +67,7 @@
         self.assertEqual(202, resp.status)
         self.client.wait_for_volume_status(self.volume['id'], 'available')
 
+    @stresstest(class_setup_per='process')
     @attr(type='gate')
     def test_get_volume_attachment(self):
         # Verify that a volume's attachment information is retrieved
diff --git a/tempest/config.py b/tempest/config.py
index e0ac843..3b09b5e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -519,7 +519,10 @@
                help='regexp for list of log files.'),
     cfg.StrOpt('log_check_interval',
                default=60,
-               help='time between log file error checks.')
+               help='time (in seconds) between log file error checks.'),
+    cfg.StrOpt('default_thread_number_per_action',
+               default=4,
+               help='The number of threads created while stress test.')
 ]
 
 
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index efc57a9..5171da2 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -102,6 +102,8 @@
     """
     logfiles = admin_manager.config.stress.target_logfiles
     log_check_interval = int(admin_manager.config.stress.log_check_interval)
+    default_thread_num = int(admin_manager.config.stress.
+                             default_thread_number_per_action)
     if logfiles:
         controller = admin_manager.config.stress.target_controller
         computes = _get_compute_nodes(controller)
@@ -112,7 +114,7 @@
             manager = admin_manager
         else:
             manager = clients.Manager()
-        for p_number in xrange(test.get('threads', 1)):
+        for p_number in xrange(test.get('threads', default_thread_num)):
             if test.get('use_isolated_tenants', False):
                 username = rand_name("stress_user")
                 tenant_name = rand_name("stress_tenant")
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index 32e3ae0..aab2afd 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -19,13 +19,52 @@
 import argparse
 import json
 import sys
+from testtools.testsuite import iterate_tests
+from unittest import loader
+
+
+def discover_stress_tests(path="./", filter_attr=None):
+    """Discovers all tempest tests and create action out of them
+    """
+
+    tests = []
+    testloader = loader.TestLoader()
+    list = testloader.discover(path)
+    for func in (iterate_tests(list)):
+        try:
+            method_name = getattr(func, '_testMethodName')
+            full_name = "%s.%s.%s" % (func.__module__,
+                                      func.__class__.__name__,
+                                      method_name)
+            test_func = getattr(func, method_name)
+            # NOTE(mkoderer): this contains a list of all type attributes
+            attrs = getattr(test_func, "__testtools_attrs")
+        except Exception:
+            next
+        if 'stress' in attrs:
+            if filter_attr is not None and not filter_attr in attrs:
+                continue
+            class_setup_per = getattr(test_func, "st_class_setup_per")
+
+            action = {'action':
+                      "tempest.stress.actions.unit_test.UnitTest",
+                      'kwargs': {"test_method": full_name,
+                                 "class_setup_per": class_setup_per
+                                 }
+                      }
+            tests.append(action)
+    return tests
 
 
 def main(ns):
     # NOTE(mkoderer): moved import to make "-h" possible without OpenStack
     from tempest.stress import driver
     result = 0
-    tests = json.load(open(ns.tests, 'r'))
+    if not ns.all:
+        tests = json.load(open(ns.tests, 'r'))
+    else:
+        tests = discover_stress_tests(filter_attr=ns.type)
+
     if ns.serial:
         for test in tests:
             step_result = driver.stress_openstack([test],
@@ -49,7 +88,13 @@
                     default=False, help="Stop on first error.")
 parser.add_argument('-n', '--number', type=int,
                     help="How often an action is executed for each process.")
-parser.add_argument('tests', help="Name of the file with test description.")
+group = parser.add_mutually_exclusive_group(required=True)
+group.add_argument('-a', '--all', action='store_true',
+                   help="Execute all stress tests")
+parser.add_argument('-T', '--type',
+                    help="Filters tests of a certain type (e.g. gate)")
+group.add_argument('-t', "--tests", nargs='?',
+                   help="Name of the file with test description.")
 
 if __name__ == "__main__":
     sys.exit(main(parser.parse_args()))
diff --git a/tempest/stress/stressaction.py b/tempest/stress/stressaction.py
index 3719841..6284cef 100644
--- a/tempest/stress/stressaction.py
+++ b/tempest/stress/stressaction.py
@@ -60,6 +60,8 @@
 
         while self.max_runs is None or (shared_statistic['runs'] <
                                         self.max_runs):
+            self.logger.debug("Trigger new run (run %d)" %
+                              shared_statistic['runs'])
             try:
                 self.run()
             except Exception:
diff --git a/tempest/test.py b/tempest/test.py
index 68cedf0..8b3cfa6 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -57,6 +57,27 @@
     return decorator
 
 
+def stresstest(*args, **kwargs):
+    """Add stress test decorator
+
+    For all functions with this decorator a attr stress will be
+    set automatically.
+
+    @param class_setup_per: allowed values are application, process, action
+           ``application``: once in the stress job lifetime
+           ``process``: once in the worker process lifetime
+           ``action``: on each action
+    """
+    def decorator(f):
+        if 'class_setup_per' in kwargs:
+            setattr(f, "st_class_setup_per", kwargs['class_setup_per'])
+        else:
+            setattr(f, "st_class_setup_per", 'process')
+        attr(type='stress')(f)
+        return f
+    return decorator
+
+
 # there is a mis-match between nose and testtools for older pythons.
 # testtools will set skipException to be either
 # unittest.case.SkipTest, unittest2.case.SkipTest or an internal skip
diff --git a/tox.ini b/tox.ini
index a101a9e..c60e19e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -78,7 +78,7 @@
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
 commands =
-    python -m tempest/stress/run_stress tempest/stress/etc/stress-tox-job.json -d 3600
+    python -m tempest/stress/run_stress -a -d 3600
 
 [testenv:venv]
 commands = {posargs}