Merge "Unit tests as stress tests"
diff --git a/tempest/stress/actions/unit_test.py b/tempest/stress/actions/unit_test.py
new file mode 100644
index 0000000..95cc1bc
--- /dev/null
+++ b/tempest/stress/actions/unit_test.py
@@ -0,0 +1,79 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.openstack.common import importutils
+import tempest.stress.stressaction as stressaction
+
+
+class SetUpClassRunTime(object):
+
+    process = 'process'
+    action = 'action'
+    application = 'application'
+
+    allowed = set((process, action, application))
+
+    @classmethod
+    def validate(cls, name):
+        if name not in cls.allowed:
+            raise KeyError("\'%s\' not a valid option" % name)
+
+
+class UnitTest(stressaction.StressAction):
+    """This is a special action for running existing unittests as stress test.
+       You need to pass ``test_method`` and ``class_setup_per``
+       using ``kwargs`` in the JSON descriptor;
+       ``test_method`` should be the fully qualified name of a unittest,
+       ``class_setup_per`` should be one from:
+           ``application``: once in the stress job lifetime
+           ``process``: once in the worker process lifetime
+           ``action``: on each action
+       Not all combination working in every case.
+    """
+
+    def setUp(self, **kwargs):
+        method = kwargs['test_method'].split('.')
+        self.test_method = method.pop()
+        self.klass = importutils.import_class('.'.join(method))
+        # valid options are 'process', 'application' , 'action'
+        self.class_setup_per = kwargs.get('class_setup_per',
+                                          SetUpClassRunTime.process)
+        SetUpClassRunTime.validate(self.class_setup_per)
+
+        if self.class_setup_per == SetUpClassRunTime.application:
+            self.klass.setUpClass()
+        self.setupclass_called = False
+
+    def run_core(self):
+        res = self.klass(self.test_method).run()
+        if res.errors:
+            raise RuntimeError(res.errors)
+
+    def run(self):
+        if self.class_setup_per != SetUpClassRunTime.application:
+            if (self.class_setup_per == SetUpClassRunTime.action
+                or self.setupclass_called is False):
+                self.klass.setUpClass()
+                self.setupclass_called = True
+
+            self.run_core()
+
+            if (self.class_setup_per == SetUpClassRunTime.action):
+                self.klass.tearDownClass()
+        else:
+            self.run_core()
+
+    def tearDown(self):
+        if self.class_setup_per != SetUpClassRunTime.action:
+            self.klass.tearDownClass()
diff --git a/tempest/stress/etc/sample-unit-test.json b/tempest/stress/etc/sample-unit-test.json
new file mode 100644
index 0000000..b388bfe
--- /dev/null
+++ b/tempest/stress/etc/sample-unit-test.json
@@ -0,0 +1,8 @@
+[{"action": "tempest.stress.actions.unit_test.UnitTest",
+  "threads": 8,
+  "use_admin": false,
+  "use_isolated_tenants": false,
+  "kwargs": {"test_method": "tempest.cli.simple_read_only.test_glance.SimpleReadOnlyGlanceClientTest.test_glance_fake_action",
+             "class_setup_per": "process"}
+  }
+]