Unit tests as stress tests

Creating a special stressaction, which is able to call an
arbitrary existing unit test as stress action.

The setUpClass() and tearDownClass() can happen in every action,
 or just once per thread or once per stress application run,
 it is configure able option.

Change-Id: Ia5432a4d9d749aa7618e098d8eeedd70d00b0b6d
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"}
+  }
+]