Merge "Rename to create_test_server in API tests"
diff --git a/HACKING.rst b/HACKING.rst
index 7d995c3..377c647 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -163,3 +163,26 @@
- If the execution of a set of tests is required to be serialized then locking
can be used to perform this. See AggregatesAdminTest in
tempest.api.compute.admin for an example of using locking.
+
+Stress Tests in Tempest
+-----------------------
+Any tempest test case can be flagged as a stress test. With this flag it will
+be automatically discovery and used in the stress test runs. The stress test
+framework itself is a facility to spawn and control worker processes in order
+to find race conditions (see ``tempest/stress/`` for more information). Please
+note that these stress tests can't be used for benchmarking purposes since they
+don't measure any performance characteristics.
+
+Example::
+
+ @stresstest(class_setup_per='process')
+ def test_this_and_that(self):
+ ...
+
+This will flag the test ``test_this_and_that`` as a stress test. The parameter
+``class_setup_per`` gives control when the setUpClass function should be called.
+
+Good candidates for stress tests are:
+
+- Scenario tests
+- API tests that have a wide focus
diff --git a/etc/README.txt b/etc/README.txt
deleted file mode 100644
index c7e5e6e..0000000
--- a/etc/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Copy config.ini.sample to config.ini, and update it to reflect your environment.
diff --git a/etc/TEMPEST_README.txt b/etc/TEMPEST_README.txt
deleted file mode 100644
index 50fa688..0000000
--- a/etc/TEMPEST_README.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-To run:
--rename the /etc/tempest.conf.sample file to tempest.conf
--Set the fields in the file to values relevant to your system
--Set the "authentication" value (basic or keystone_v2 currently supported)
--From the root directory of the project, run "./run_tests.sh" this will
-create the venv to install the project dependencies and run nosetests tempest
-to run all the tests
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 4d02dc5..fc4f9cd 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -153,7 +153,7 @@
[compute-feature-enabled]
# Do we run the Nova V3 API tests?
-api_v3 = true
+api_v3 = false
# Does the Compute API support creation of images?
create_image = true
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index e8ae3b4..43d4ea3 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -15,13 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_int_id
from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
@@ -43,16 +40,38 @@
cls.client = cls.os_adm.flavors_client
admin_client = cls._get_identity_admin_client()
- resp, tenants = admin_client.list_tenants()
- cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
- cls.flavors_client.tenant_name][0]
-
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
cls.vcpus = 1
cls.disk = 10
@attr(type='gate')
+ def test_flavor_access_list_with_private_flavor(self):
+ # Test to list flavor access successfully by querying private flavor
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(len(flavor_access), 1, str(flavor_access))
+ first_flavor = flavor_access[0]
+ self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
+ self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+
+ @attr(type='gate')
def test_flavor_access_add_remove(self):
# Test to add and remove flavor access to a given tenant.
flavor_name = rand_name(self.flavor_name_prefix)
@@ -89,84 +108,6 @@
self.assertEqual(resp.status, 200)
self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_add(self):
- # Test to add flavor access as a user without admin privileges.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.add_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_remove(self):
- # Test to remove flavor access as a user without admin privileges.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- # Add flavor access to a tenant.
- self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
- self.addCleanup(self.client.remove_flavor_access,
- new_flavor['id'], self.tenant_id)
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.remove_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_add_flavor_access_duplicate(self):
- # Create a new flavor.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
-
- # Add flavor access to a tenant.
- self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
- self.addCleanup(self.client.remove_flavor_access,
- new_flavor['id'], self.tenant_id)
-
- # An exception should be raised when adding flavor access to the same
- # tenant
- self.assertRaises(exceptions.Conflict,
- self.client.add_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_remove_flavor_access_not_found(self):
- # Create a new flavor.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
-
- # An exception should be raised when flavor access is not found
- self.assertRaises(exceptions.NotFound,
- self.client.remove_flavor_access,
- new_flavor['id'],
- str(uuid.uuid4()))
-
class FlavorsAdminTestXML(FlavorsAccessTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
new file mode 100644
index 0000000..4d2983c
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -0,0 +1,154 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# 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 uuid
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_int_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavor Access API extension.
+ Add and remove Flavor Access require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
+ if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ admin_client = cls._get_identity_admin_client()
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
+ cls.flavor_name_prefix = 'test_flavor_access_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_access_list_with_public_flavor(self):
+ # Test to list flavor access with exceptions by querying public flavor
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='True')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_flavor_access,
+ new_flavor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_add(self):
+ # Test to add flavor access as a user without admin privileges.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_remove(self):
+ # Test to remove flavor access as a user without admin privileges.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.remove_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_add_flavor_access_duplicate(self):
+ # Create a new flavor.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+
+ # An exception should be raised when adding flavor access to the same
+ # tenant
+ self.assertRaises(exceptions.Conflict,
+ self.client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_flavor_access_not_found(self):
+ # Create a new flavor.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # An exception should be raised when flavor access is not found
+ self.assertRaises(exceptions.NotFound,
+ self.client.remove_flavor_access,
+ new_flavor['id'],
+ str(uuid.uuid4()))
+
+
+class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 8e451a0..22e6cf1 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -32,18 +32,11 @@
super(HostsAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.hosts_client
- def _get_host_name(self):
- resp, hosts = self.client.list_hosts()
- self.assertEqual(200, resp.status)
- self.assertTrue(len(hosts) >= 1)
- hostname = hosts[0]['host_name']
- return hostname
-
@attr(type='gate')
def test_list_hosts(self):
resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
- self.assertTrue(len(hosts) >= 2)
+ self.assertTrue(len(hosts) >= 2, str(hosts))
@attr(type='gate')
def test_list_hosts_with_zone(self):
@@ -77,18 +70,24 @@
@attr(type='gate')
def test_show_host_detail(self):
- hostname = self._get_host_name()
-
- resp, resources = self.client.show_host_detail(hostname)
+ resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
- self.assertTrue(len(resources) >= 1)
- host_resource = resources[0]['resource']
- self.assertIsNotNone(host_resource)
- self.assertIsNotNone(host_resource['cpu'])
- self.assertIsNotNone(host_resource['disk_gb'])
- self.assertIsNotNone(host_resource['memory_mb'])
- self.assertIsNotNone(host_resource['project'])
- self.assertEqual(hostname, host_resource['host'])
+
+ hosts = [host for host in hosts if host['service'] == 'compute']
+ self.assertTrue(len(hosts) >= 1)
+
+ for host in hosts:
+ hostname = host['host_name']
+ resp, resources = self.client.show_host_detail(hostname)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(resources) >= 1)
+ host_resource = resources[0]['resource']
+ self.assertIsNotNone(host_resource)
+ self.assertIsNotNone(host_resource['cpu'])
+ self.assertIsNotNone(host_resource['disk_gb'])
+ self.assertIsNotNone(host_resource['memory_mb'])
+ self.assertIsNotNone(host_resource['project'])
+ self.assertEqual(hostname, host_resource['host'])
class HostsAdminTestXML(HostsAdminTestJSON):
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index b23a5d4..5019003 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -39,7 +39,7 @@
resp, body = self.client.list_instance_actions(self.server_id)
self.assertEqual(200, resp.status)
- self.assertTrue(len(body) == 2)
+ self.assertTrue(len(body) == 2, str(body))
self.assertTrue(any([i for i in body if i['action'] == 'create']))
self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 512d065..2f20cc9 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -106,8 +106,8 @@
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_subnet_id,
router['id'], subnet['id'])
- self.assertTrue('subnet_id' in interface.keys())
- self.assertTrue('port_id' in interface.keys())
+ self.assertIn('subnet_id', interface.keys())
+ self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
@@ -126,8 +126,8 @@
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_port_id,
router['id'], port_body['port']['id'])
- self.assertTrue('subnet_id' in interface.keys())
- self.assertTrue('port_id' in interface.keys())
+ self.assertIn('subnet_id', interface.keys())
+ self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 407c3ec..3389235 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -96,7 +96,7 @@
self.container_name, object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
actual_meta_key = 'x-object-meta-' + meta_key
- self.assertTrue(actual_meta_key in resp)
+ self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='smoke')
@@ -220,7 +220,7 @@
object_name)
self.assertEqual(body, data)
actual_meta_key = 'x-object-meta-' + meta_key
- self.assertTrue(actual_meta_key in resp)
+ self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index d6dd7db..0892eae 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -47,8 +47,7 @@
self.volume_type['id'])
self.assertEqual(200, resp.status)
self.assertIsInstance(body, dict)
- self.assertTrue('spec1' in body, "Incorrect volume type extra"
- " spec returned")
+ self.assertIn('spec1', body)
@attr(type='gate')
def test_volume_type_extra_specs_update(self):
@@ -66,8 +65,7 @@
extra_spec.keys()[0],
extra_spec)
self.assertEqual(200, resp.status)
- self.assertTrue('spec2' in body,
- "Volume type extra spec incorrectly updated")
+ self.assertIn('spec2', body)
self.assertEqual(extra_spec['spec2'], body['spec2'],
"Volume type extra spec incorrectly updated")
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index f12d4bb..30c2c74 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -123,6 +123,23 @@
self.assertEqual(200, resp.status)
self.assertEqual(int(volume['size']), extend_size)
+ @attr(type='gate')
+ def test_reserve_unreserve_volume(self):
+ # Mark volume as reserved.
+ resp, body = self.client.reserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # To get the volume info
+ resp, body = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertIn('attaching', body['status'])
+ # Unmark volume as reserved.
+ resp, body = self.client.unreserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # To get the volume info
+ resp, body = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertIn('available', body['status'])
+
class VolumesActionsTestXML(VolumesActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 9bab9a0..538d5be 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -212,6 +212,31 @@
self.assertRaises(exceptions.NotFound, self.client.extend_volume,
None, extend_size)
+ @attr(type=['negative', 'gate'])
+ def test_reserve_volume_with_nonexistent_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.reserve_volume,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_unreserve_volume_with_nonexistent_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.unreserve_volume,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_reserve_volume_with_negative_volume_status(self):
+ # Mark volume as reserved.
+ resp, body = self.client.reserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # Mark volume which is marked as reserved before
+ self.assertRaises(exceptions.BadRequest,
+ self.client.reserve_volume,
+ self.volume['id'])
+ # Unmark volume as reserved.
+ resp, body = self.client.unreserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+
class VolumesNegativeTestXML(VolumesNegativeTest):
_interface = 'xml'
diff --git a/tempest/config.py b/tempest/config.py
index a629486..76461fb 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -204,7 +204,7 @@
ComputeFeaturesGroup = [
cfg.BoolOpt('api_v3',
- default=True,
+ default=False,
help="If false, skip all nova v3 tests."),
cfg.BoolOpt('disk_config',
default=True,
diff --git a/tempest/openstack/common/excutils.py b/tempest/openstack/common/excutils.py
index db37660..c7bce72 100644
--- a/tempest/openstack/common/excutils.py
+++ b/tempest/openstack/common/excutils.py
@@ -79,7 +79,7 @@
try:
return infunc(*args, **kwargs)
except Exception as exc:
- this_exc_message = unicode(exc)
+ this_exc_message = six.u(str(exc))
if this_exc_message == last_exc_message:
exc_count += 1
else:
diff --git a/tempest/openstack/common/fileutils.py b/tempest/openstack/common/fileutils.py
index 6cf68ba..15530af 100644
--- a/tempest/openstack/common/fileutils.py
+++ b/tempest/openstack/common/fileutils.py
@@ -19,6 +19,7 @@
import contextlib
import errno
import os
+import tempfile
from tempest.openstack.common import excutils
from tempest.openstack.common.gettextutils import _ # noqa
@@ -109,3 +110,30 @@
state at all (for unit tests)
"""
return file(*args, **kwargs)
+
+
+def write_to_tempfile(content, path=None, suffix='', prefix='tmp'):
+ """Create temporary file or use existing file.
+
+ This util is needed for creating temporary file with
+ specified content, suffix and prefix. If path is not None,
+ it will be used for writing content. If the path doesn't
+ exist it'll be created.
+
+ :param content: content for temporary file.
+ :param path: same as parameter 'dir' for mkstemp
+ :param suffix: same as parameter 'suffix' for mkstemp
+ :param prefix: same as parameter 'prefix' for mkstemp
+
+ For example: it can be used in database tests for creating
+ configuration files.
+ """
+ if path:
+ ensure_tree(path)
+
+ (fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix)
+ try:
+ os.write(fd, content)
+ finally:
+ os.close(fd)
+ return path
diff --git a/tempest/openstack/common/gettextutils.py b/tempest/openstack/common/gettextutils.py
index cbf570a..2939ed9 100644
--- a/tempest/openstack/common/gettextutils.py
+++ b/tempest/openstack/common/gettextutils.py
@@ -60,6 +60,8 @@
if USE_LAZY:
return Message(msg, 'tempest')
else:
+ if six.PY3:
+ return _t.gettext(msg)
return _t.ugettext(msg)
@@ -105,13 +107,17 @@
"""
return Message(msg, domain)
- import __builtin__
- __builtin__.__dict__['_'] = _lazy_gettext
+ from six import moves
+ moves.builtins.__dict__['_'] = _lazy_gettext
else:
localedir = '%s_LOCALEDIR' % domain.upper()
- gettext.install(domain,
- localedir=os.environ.get(localedir),
- unicode=True)
+ if six.PY3:
+ gettext.install(domain,
+ localedir=os.environ.get(localedir))
+ else:
+ gettext.install(domain,
+ localedir=os.environ.get(localedir),
+ unicode=True)
class Message(_userString.UserString, object):
@@ -121,8 +127,8 @@
self._msg = msg
self._left_extra_msg = ''
self._right_extra_msg = ''
+ self._locale = None
self.params = None
- self.locale = None
self.domain = domain
@property
@@ -142,8 +148,13 @@
localedir=localedir,
fallback=True)
+ if six.PY3:
+ ugettext = lang.gettext
+ else:
+ ugettext = lang.ugettext
+
full_msg = (self._left_extra_msg +
- lang.ugettext(self._msg) +
+ ugettext(self._msg) +
self._right_extra_msg)
if self.params is not None:
@@ -151,6 +162,33 @@
return six.text_type(full_msg)
+ @property
+ def locale(self):
+ return self._locale
+
+ @locale.setter
+ def locale(self, value):
+ self._locale = value
+ if not self.params:
+ return
+
+ # This Message object may have been constructed with one or more
+ # Message objects as substitution parameters, given as a single
+ # Message, or a tuple or Map containing some, so when setting the
+ # locale for this Message we need to set it for those Messages too.
+ if isinstance(self.params, Message):
+ self.params.locale = value
+ return
+ if isinstance(self.params, tuple):
+ for param in self.params:
+ if isinstance(param, Message):
+ param.locale = value
+ return
+ if isinstance(self.params, dict):
+ for param in self.params.values():
+ if isinstance(param, Message):
+ param.locale = value
+
def _save_dictionary_parameter(self, dict_param):
full_msg = self.data
# look for %(blah) fields in string;
@@ -169,7 +207,7 @@
params[key] = copy.deepcopy(dict_param[key])
except TypeError:
# cast uncopyable thing to unicode string
- params[key] = unicode(dict_param[key])
+ params[key] = six.text_type(dict_param[key])
return params
@@ -188,7 +226,7 @@
try:
self.params = copy.deepcopy(other)
except TypeError:
- self.params = unicode(other)
+ self.params = six.text_type(other)
return self
@@ -197,11 +235,13 @@
return self.data
def __str__(self):
+ if six.PY3:
+ return self.__unicode__()
return self.data.encode('utf-8')
def __getstate__(self):
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
- 'domain', 'params', 'locale']
+ 'domain', 'params', '_locale']
new_dict = self.__dict__.fromkeys(to_copy)
for attr in to_copy:
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
@@ -289,13 +329,21 @@
def get_localized_message(message, user_locale):
- """Gets a localized version of the given message in the given locale."""
+ """Gets a localized version of the given message in the given locale.
+
+ If the message is not a Message object the message is returned as-is.
+ If the locale is None the message is translated to the default locale.
+
+ :returns: the translated message in unicode, or the original message if
+ it could not be translated
+ """
+ translated = message
if isinstance(message, Message):
- if user_locale:
- message.locale = user_locale
- return unicode(message)
- else:
- return message
+ original_locale = message.locale
+ message.locale = user_locale
+ translated = six.text_type(message)
+ message.locale = original_locale
+ return translated
class LocaleHandler(logging.Handler):
diff --git a/tempest/openstack/common/jsonutils.py b/tempest/openstack/common/jsonutils.py
index c568a06..b589545 100644
--- a/tempest/openstack/common/jsonutils.py
+++ b/tempest/openstack/common/jsonutils.py
@@ -46,6 +46,7 @@
import six
+from tempest.openstack.common import gettextutils
from tempest.openstack.common import importutils
from tempest.openstack.common import timeutils
@@ -135,6 +136,8 @@
if convert_datetime and isinstance(value, datetime.datetime):
return timeutils.strtime(value)
+ elif isinstance(value, gettextutils.Message):
+ return value.data
elif hasattr(value, 'iteritems'):
return recursive(dict(value.iteritems()), level=level + 1)
elif hasattr(value, '__iter__'):
diff --git a/tempest/openstack/common/lockutils.py b/tempest/openstack/common/lockutils.py
index a55fd94..8ea8766 100644
--- a/tempest/openstack/common/lockutils.py
+++ b/tempest/openstack/common/lockutils.py
@@ -24,7 +24,6 @@
import time
import weakref
-import fixtures
from oslo.config import cfg
from tempest.openstack.common import fileutils
@@ -242,13 +241,14 @@
def wrap(f):
@functools.wraps(f)
def inner(*args, **kwargs):
- with lock(name, lock_file_prefix, external, lock_path):
- LOG.debug(_('Got semaphore / lock "%(function)s"'),
+ try:
+ with lock(name, lock_file_prefix, external, lock_path):
+ LOG.debug(_('Got semaphore / lock "%(function)s"'),
+ {'function': f.__name__})
+ return f(*args, **kwargs)
+ finally:
+ LOG.debug(_('Semaphore / lock released "%(function)s"'),
{'function': f.__name__})
- return f(*args, **kwargs)
-
- LOG.debug(_('Semaphore / lock released "%(function)s"'),
- {'function': f.__name__})
return inner
return wrap
@@ -276,36 +276,3 @@
"""
return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)
-
-
-class LockFixture(fixtures.Fixture):
- """External locking fixture.
-
- This fixture is basically an alternative to the synchronized decorator with
- the external flag so that tearDowns and addCleanups will be included in
- the lock context for locking between tests. The fixture is recommended to
- be the first line in a test method, like so::
-
- def test_method(self):
- self.useFixture(LockFixture)
- ...
-
- or the first line in setUp if all the test methods in the class are
- required to be serialized. Something like::
-
- class TestCase(testtools.testcase):
- def setUp(self):
- self.useFixture(LockFixture)
- super(TestCase, self).setUp()
- ...
-
- This is because addCleanups are put on a LIFO queue that gets run after the
- test method exits. (either by completing or raising an exception)
- """
- def __init__(self, name, lock_file_prefix=None):
- self.mgr = lock(name, lock_file_prefix, True)
-
- def setUp(self):
- super(LockFixture, self).setUp()
- self.addCleanup(self.mgr.__exit__, None, None, None)
- self.mgr.__enter__()
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 4133c30..5cf8ed6 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -39,6 +39,7 @@
import traceback
from oslo.config import cfg
+import six
from six import moves
from tempest.openstack.common.gettextutils import _ # noqa
@@ -131,7 +132,6 @@
'boto=WARN',
'suds=INFO',
'keystone=INFO',
- 'eventlet.wsgi.server=WARN'
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
@@ -207,6 +207,8 @@
binary = binary or _get_binary_name()
return '%s.log' % (os.path.join(logdir, binary),)
+ return None
+
class BaseLoggerAdapter(logging.LoggerAdapter):
@@ -249,6 +251,13 @@
self.warn(stdmsg, *args, **kwargs)
def process(self, msg, kwargs):
+ # NOTE(mrodden): catch any Message/other object and
+ # coerce to unicode before they can get
+ # to the python logging and possibly
+ # cause string encoding trouble
+ if not isinstance(msg, six.string_types):
+ msg = six.text_type(msg)
+
if 'extra' not in kwargs:
kwargs['extra'] = {}
extra = kwargs['extra']
@@ -260,14 +269,14 @@
extra.update(_dictify_context(context))
instance = kwargs.pop('instance', None)
+ instance_uuid = (extra.get('instance_uuid', None) or
+ kwargs.pop('instance_uuid', None))
instance_extra = ''
if instance:
instance_extra = CONF.instance_format % instance
- else:
- instance_uuid = kwargs.pop('instance_uuid', None)
- if instance_uuid:
- instance_extra = (CONF.instance_uuid_format
- % {'uuid': instance_uuid})
+ elif instance_uuid:
+ instance_extra = (CONF.instance_uuid_format
+ % {'uuid': instance_uuid})
extra.update({'instance': instance_extra})
extra.update({"project": self.project})
diff --git a/tempest/openstack/common/timeutils.py b/tempest/openstack/common/timeutils.py
index 60f02bc..98d877d 100644
--- a/tempest/openstack/common/timeutils.py
+++ b/tempest/openstack/common/timeutils.py
@@ -117,12 +117,15 @@
utcnow.override_time = None
-def set_time_override(override_time=datetime.datetime.utcnow()):
+def set_time_override(override_time=None):
"""Overrides utils.utcnow.
Make it return a constant time or a list thereof, one at a time.
+
+ :param override_time: datetime instance or list thereof. If not
+ given, defaults to the current UTC time.
"""
- utcnow.override_time = override_time
+ utcnow.override_time = override_time or datetime.datetime.utcnow()
def advance_time_delta(timedelta):
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 305a77b..588e5cd 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -122,6 +122,13 @@
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
+
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
post_body = {
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index 12e24d0..d4c456e 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -183,6 +183,13 @@
def _parse_array_access(self, node):
return [xml_to_json(x) for x in node]
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id),
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
doc = Document()
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index eb87cbe..670492a 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -128,6 +128,22 @@
resp, body = self.post(url, post_body, self.headers)
return resp, body
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = {}
+ post_body = json.dumps({'os-reserve': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = {}
+ post_body = json.dumps({'os-unreserve': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index be292a2..0edf7f3 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -266,3 +266,21 @@
if body:
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = Element("os-reserve")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = Element("os-unreserve")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index ae86f6e..20e58d4 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -26,6 +26,16 @@
To activate logging on your console please make sure that you activate `use_stderr`
in tempest.conf or use the default `logging.conf.sample` file.
+Running default stress test set
+-------------------------------
+
+The stress test framework can automatically discover test inside the tempest
+test suite. All test flag with the `@stresstest` decorator will be executed.
+In order to use this discovery you have to be in the tempest root directory
+and execute the following:
+
+ tempest/stress/run_stress.py -a -d 30
+
Running the sample test
-----------------------
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index 12c1c25..ba83cf4 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -17,24 +17,9 @@
import os
import fixtures
-import mox
-import stubout
import testtools
-
-class MoxStubout(fixtures.Fixture):
- """Deal with code around mox and stubout as a fixture."""
-
- def setUp(self):
- super(MoxStubout, self).setUp()
- # emulate some of the mox stuff, we can't use the metaclass
- # because it screws with our generators
- self.mox = mox.Mox()
- self.stubs = stubout.StubOutForTesting()
- self.addCleanup(self.stubs.UnsetAll)
- self.addCleanup(self.stubs.SmartUnsetAll)
- self.addCleanup(self.mox.UnsetStubs)
- self.addCleanup(self.mox.VerifyAll)
+from tempest.openstack.common.fixture import moxstubout
class TestCase(testtools.TestCase):
@@ -50,6 +35,6 @@
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
- mox_fixture = self.useFixture(MoxStubout())
+ mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.mox = mox_fixture.mox
self.stubs = mox_fixture.stubs
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 92d66ae..1bab88a 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -121,9 +121,6 @@
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
- def post_process(self):
- self.get_distro().post_process()
-
def parse_args(self, argv):
"""Parses command-line arguments."""
parser = optparse.OptionParser()
@@ -156,14 +153,6 @@
' requires virtualenv, please install it using your'
' favorite package management tool' % self.project)
- def post_process(self):
- """Any distribution-specific post-processing gets done here.
-
- In particular, this is useful for applying patches to code inside
- the venv.
- """
- pass
-
class Fedora(Distro):
"""This covers all Fedora-based distributions.
@@ -175,10 +164,6 @@
return self.run_command_with_code(['rpm', '-q', pkg],
check_exit_code=False)[1] == 0
- def apply_patch(self, originalfile, patchfile):
- self.run_command(['patch', '-N', originalfile, patchfile],
- check_exit_code=False)
-
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
@@ -187,27 +172,3 @@
self.die("Please install 'python-virtualenv'.")
super(Fedora, self).install_virtualenv()
-
- def post_process(self):
- """Workaround for a bug in eventlet.
-
- This currently affects RHEL6.1, but the fix can safely be
- applied to all RHEL and Fedora distributions.
-
- This can be removed when the fix is applied upstream.
-
- Nova: https://bugs.launchpad.net/nova/+bug/884915
- Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
- RHEL: https://bugzilla.redhat.com/958868
- """
-
- if os.path.exists('contrib/redhat-eventlet.patch'):
- # Install "patch" program if it's not there
- if not self.check_pkg('patch'):
- self.die("Please install 'patch'.")
-
- # Apply the eventlet patch
- self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
- 'site-packages',
- 'eventlet/green/subprocess.py'),
- 'contrib/redhat-eventlet.patch')