Merge "Adds tests for tags in boto (EC2 API)"
diff --git a/.testr.conf b/.testr.conf
index a0262d8..9a72d29 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,3 +2,4 @@
 test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION
 test_id_option=--load-list $IDFILE
 test_list_option=--list
+group_regex=([^\.]*\.)*
diff --git a/run_tests.sh b/run_tests.sh
index 366564e..d5081c7 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -11,6 +11,7 @@
   echo "  -u, --update             Update the virtual environment with any newer package versions"
   echo "  -s, --smoke              Only run smoke tests"
   echo "  -w, --whitebox           Only run whitebox tests"
+  echo "  -t, --with-testr         Run using testr instead of nose"
   echo "  -c, --nova-coverage      Enable Nova coverage collection"
   echo "  -C, --config             Config file location"
   echo "  -p, --pep8               Just run pep8"
@@ -26,6 +27,7 @@
 just_pep8=0
 venv=.venv
 with_venv=tools/with_venv.sh
+with_testr=0
 always_venv=0
 never_venv=0
 no_site_packages=0
@@ -37,7 +39,7 @@
 logging=0
 logging_config=etc/logging.conf
 
-if ! options=$(getopt -o VNnfuswcphdSC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@")
+if ! options=$(getopt -o VNnfuswtcphdSC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,with-testr,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@")
 then
     # parse error
     usage
@@ -60,6 +62,7 @@
     -p|--pep8) let just_pep8=1;;
     -s|--smoke) noseargs="$noseargs --attr=type=smoke";;
     -w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
+    -t|--with-testr) with_testr=1;;
     -S|--stdout) noseargs="$noseargs -s";;
     -l|--logging) logging=1;;
     -L|--logging-config) logging_config=$2; shift;;
@@ -105,8 +108,20 @@
   noseargs="$noseargs tempest"
 fi
 
+function testr_init {
+  if [ ! -d .testrepository ]; then
+      ${wrapper} testr init
+  fi
+}
+
 function run_tests {
-  ${wrapper} $NOSETESTS
+  if [ $with_testr -eq 1 ]; then
+      testr_init
+      ${wrapper} find . -type f -name "*.pyc" -delete
+      ${wrapper} testr run --parallel $noseargs
+  else
+      ${wrapper} $NOSETESTS
+  fi
 }
 
 function run_pep8 {
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index a74bb68..f9b4346 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -89,26 +89,6 @@
                           '!@#$%^&*()', name, meta)
 
     @attr(type=['negative', 'gate'])
-    def test_create_image_when_server_is_terminating(self):
-        # Return an error when creating image of server that is terminating
-        resp, server = self.create_server(wait_until='ACTIVE')
-        self.servers_client.delete_server(server['id'])
-
-        snapshot_name = rand_name('test-snap-')
-        self.assertRaises(exceptions.Duplicate, self.client.create_image,
-                          server['id'], snapshot_name)
-
-    @attr(type=['negative', 'gate'])
-    def test_create_image_when_server_is_rebooting(self):
-        # Return error when creating an image of server that is rebooting
-        resp, server = self.create_server(wait_until='ACTIVE')
-        self.servers_client.reboot(server['id'], 'HARD')
-
-        snapshot_name = rand_name('test-snap-')
-        self.assertRaises(exceptions.Duplicate, self.client.create_image,
-                          server['id'], snapshot_name)
-
-    @attr(type=['negative', 'gate'])
     def test_create_image_specify_uuid_35_characters_or_less(self):
         # Return an error if Image ID passed is 35 characters or less
         snapshot_name = rand_name('test-snap-')
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index db9bdc1..c03c43e 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -59,8 +59,9 @@
         if num_servers > 0:
             username = cls.os.username
             tenant_name = cls.os.tenant_name
-            msg = ("User/tenant %(username)s/%(tenant_name)s already have "
-                   "existing server instances. Skipping test.") % locals()
+            msg = ("User/tenant %(u)s/%(t)s already have "
+                   "existing server instances. Skipping test." %
+                   {'u': username, 't': tenant_name})
             raise cls.skipException(msg)
 
         resp, body = cls.alt_client.list_servers()
@@ -69,8 +70,9 @@
         if num_servers > 0:
             username = cls.alt_manager.username
             tenant_name = cls.alt_manager.tenant_name
-            msg = ("Alt User/tenant %(username)s/%(tenant_name)s already have "
-                   "existing server instances. Skipping test.") % locals()
+            msg = ("Alt User/tenant %(u)s/%(t)s already have "
+                   "existing server instances. Skipping test." %
+                   {'u': username, 't': tenant_name})
             raise cls.skipException(msg)
 
         # The following servers are created for use
diff --git a/tempest/clients.py b/tempest/clients.py
index 5efce98..2154f8b 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -274,8 +274,9 @@
 
         if None in (self.username, self.password, self.tenant_name):
             msg = ("Missing required credentials. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
+                   "username: %(u)s, password: %(p)s, "
+                   "tenant_name: %(t)s" %
+                   {'u': username, 'p': password, 't': tenant_name})
             raise exceptions.InvalidConfiguration(msg)
 
         self.auth_url = self.config.identity.uri
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index cd33a22..4045430 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -125,11 +125,12 @@
                 conn.request(method, conn_url, **kwargs)
             resp = conn.getresponse()
         except socket.gaierror as e:
-            message = "Error finding address for %(url)s: %(e)s" % locals()
+            message = ("Error finding address for %(url)s: %(e)s" %
+                       {'url': url, 'e': e})
             raise exc.EndpointNotFound(message)
         except (socket.error, socket.timeout) as e:
-            endpoint = self.endpoint
-            message = "Error communicating with %(endpoint)s %(e)s" % locals()
+            message = ("Error communicating with %(endpoint)s %(e)s" %
+                       {'endpoint': self.endpoint, 'e': e})
             raise exc.TimeoutException(message)
 
         body_iter = ResponseBodyIterator(resp)
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index fd5d3d0..de2bf43 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -1,3 +1,17 @@
+# 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.
+
 import re
 import time
 
diff --git a/tempest/config.py b/tempest/config.py
index 2e56628..6e6488b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -569,7 +569,7 @@
         LOG.info("Using tempest config file %s" % path)
 
         if not os.path.exists(path):
-            msg = "Config file %(path)s not found" % locals()
+            msg = "Config file %s not found" % path
             print(RuntimeError(msg), file=sys.stderr)
         else:
             config_files.append(path)
diff --git a/tempest/manager.py b/tempest/manager.py
index 4a447f3..187e2c6 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -65,6 +65,15 @@
         self.config = tempest.config.TempestConfig()
         self.client_attr_names = []
 
+    # we do this everywhere, have it be part of the super class
+    def _validate_credentials(self, username, password, tenant_name):
+        if None in (username, password, tenant_name):
+            msg = ("Missing required credentials. "
+                   "username: %(u)s, password: %(p)s, "
+                   "tenant_name: %(t)s" %
+                   {'u': username, 'p': password, 't': tenant_name})
+            raise exceptions.InvalidConfiguration(msg)
+
 
 class FuzzClientManager(Manager):
 
@@ -103,11 +112,7 @@
         password = password or self.config.identity.password
         tenant_name = tenant_name or self.config.identity.tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index fe6fbf5..f968411 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -32,7 +32,6 @@
 from tempest.common import log as logging
 from tempest.common import ssh
 from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
 import tempest.manager
 import tempest.test
 
@@ -76,11 +75,7 @@
         if not tenant_name:
             tenant_name = self.config.identity.tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials for compute client. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
@@ -131,11 +126,7 @@
         if not tenant_name:
             tenant_name = self.config.identity.admin_tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials for identity client. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
@@ -157,11 +148,7 @@
         password = self.config.identity.admin_password
         tenant_name = self.config.identity.admin_tenant_name
 
-        if None in (username, password, tenant_name):
-            msg = ("Missing required credentials for network client. "
-                   "username: %(username)s, password: %(password)s, "
-                   "tenant_name: %(tenant_name)s") % locals()
-            raise exceptions.InvalidConfiguration(msg)
+        self._validate_credentials(username, password, tenant_name)
 
         auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index a216b55..90e64e7 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -1,3 +1,17 @@
+# 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.
+
 import httplib2
 import json
 
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index c4fe6b1..446a674 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -1,4 +1,19 @@
+# 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.
+
 import json
+
 from tempest.common.rest_client import RestClient
 
 
diff --git a/tempest/stress/etc/stress-tox-job.json b/tempest/stress/etc/stress-tox-job.json
index 7c5626d..159794b 100644
--- a/tempest/stress/etc/stress-tox-job.json
+++ b/tempest/stress/etc/stress-tox-job.json
@@ -1,10 +1,10 @@
-[{"action": "tempest.stress.actions.create_destroy_server.create_destroy",
+[{"action": "tempest.stress.actions.create_destroy_server.CreateDestroyServerTest",
   "threads": 8,
   "use_admin": false,
   "use_isolated_tenants": false,
   "kwargs": {}
   },
-  {"action": "tempest.stress.actions.volume_create_delete.create_delete",
+  {"action": "tempest.stress.actions.volume_create_delete.CreateDeleteTest",
   "threads": 4,
   "use_admin": false,
   "use_isolated_tenants": false,
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index 06dee0f..109f334 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -19,10 +19,10 @@
 import argparse
 import json
 
-from tempest.stress import driver
-
 
 def main(ns):
+    #NOTE(kodererm): moved import to make "-h" possible without OpenStack
+    from tempest.stress import driver
     tests = json.load(open(ns.tests, 'r'))
     if ns.serial:
         for test in tests:
diff --git a/tox.ini b/tox.ini
index ae1b11e..04b845a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,24 +3,16 @@
 
 [testenv]
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
+         LANG=en_US.UTF-8
+         LANGUAGE=en_US:en
+         LC_ALL=C
 
 [testenv:all]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
 commands =
-  nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-all.xml -sv tempest
+  python setup.py testr --slowest
+
 
 [testenv:full]
 sitepackages = True
@@ -34,6 +26,12 @@
 commands =
   nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
 
+[testenv:testr-full]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+commands =
+  python setup.py testr --slowest --testr-args='tempest.api tempest.scenario tempest.thirdparty tempest.cli'
+
 [testenv:smoke]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
@@ -46,7 +44,6 @@
 commands =
    nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --attr=type=smoke --xunit-file=nosetests-smoke.xml tempest
 
-
 [testenv:coverage]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}