Merge "Addresses Expect: 100-continue client behavior"
diff --git a/HACKING.rst b/HACKING.rst
index b66fa24..d3ac5c6 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -132,16 +132,16 @@
Set-up is split in a series of steps (setup stages), which can be overwritten
by test classes. Set-up stages are:
-- `skip_checks`
-- `setup_credentials`
-- `setup_clients`
-- `resource_setup`
+ - `skip_checks`
+ - `setup_credentials`
+ - `setup_clients`
+ - `resource_setup`
Tear-down is also split in a series of steps (teardown stages), which are
stacked for execution only if the corresponding setup stage had been
reached during the setup phase. Tear-down stages are:
-- `clear_credentials` (defined in the base test class)
-- `resource_cleanup`
+ - `clear_credentials` (defined in the base test class)
+ - `resource_cleanup`
Skipping Tests
--------------
diff --git a/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml b/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
new file mode 100644
index 0000000..9baf035
--- /dev/null
+++ b/releasenotes/notes/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+ This release is marking the end of Kilo release support in Tempest
+other:
+ - OpenStack Releases Supported after this release are **Liberty**
+ and **Mitaka**
+
+ The release under current development as of this tag is Newton,
+ meaning that every Tempest commit is also tested against master during
+ the Newton cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Newton (or future releases)
+ cloud.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index b617b22..2c22408 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -5,6 +5,7 @@
.. toctree::
:maxdepth: 1
+ v12.0.0
v11.0.0
v10.0.0
unreleased
diff --git a/releasenotes/source/v12.0.0.rst b/releasenotes/source/v12.0.0.rst
new file mode 100644
index 0000000..0bc8343
--- /dev/null
+++ b/releasenotes/source/v12.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v12.0.0 Release Notes
+=====================
+
+.. release-notes:: 12.0.0 Release Notes
+ :version: 12.0.0
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index f6ea3a4..a9e9644 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -49,7 +49,7 @@
@test.idempotent_id('c6ddbadb-c94e-4500-b12f-8ffc43843ff8')
def test_list_hosts_with_nonexistent_zone(self):
# If send the request with a nonexistent zone, the request will be
- # successful and no hosts will be retured
+ # successful and no hosts will be returned
hosts = self.client.list_hosts(zone='xxx')['hosts']
self.assertEqual(0, len(hosts))
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 2907e26..b1f0755 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -185,7 +185,7 @@
# increment all of the values for updating the default quota class
for quota, default in six.iteritems(body):
# NOTE(sdague): we need to increment a lot, otherwise
- # there is a real chance that we go from -1 (unlimitted)
+ # there is a real chance that we go from -1 (unlimited)
# to a very small number which causes issues.
body[quota] = default + 100
LOG.debug("update limits for the default quota class set")
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 814a876..1bbde98 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute import base
from tempest import config
from tempest import test
@@ -34,6 +36,9 @@
server_id)['server']['OS-EXT-SRV-ATTR:host']
@test.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("SameHostFilter"),
+ 'SameHostFilter is not available.')
def test_create_servers_on_same_host(self):
server01 = self.create_test_server(wait_until='ACTIVE')['id']
@@ -45,6 +50,9 @@
self.assertEqual(host01, host02)
@test.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ 'DifferentHostFilter is not available.')
def test_create_servers_on_different_hosts(self):
server01 = self.create_test_server(wait_until='ACTIVE')['id']
@@ -56,6 +64,9 @@
self.assertNotEqual(host01, host02)
@test.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ 'DifferentHostFilter is not available.')
def test_create_servers_on_different_hosts_with_list_of_servers(self):
server01 = self.create_test_server(wait_until='ACTIVE')['id']
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 87f3c86..c05045e 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -119,7 +119,9 @@
self.get_server_ip(self.server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.client)
self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
@test.idempotent_id('ac1ad47f-984b-4441-9274-c9079b7a0666')
@@ -131,7 +133,9 @@
self.get_server_ip(self.server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.client)
self.assertTrue(linux_client.hostname_equals_servername(self.name))
@test.idempotent_id('ed20d3fb-9d1f-4329-b160-543fbd5d9811')
@@ -322,7 +326,9 @@
self.get_server_ip(server_no_eph_disk),
self.ssh_user,
admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server_no_eph_disk,
+ servers_client=self.client)
partition_num = len(linux_client.get_partitions().split('\n'))
# Explicit server deletion necessary for Juno compatibility
@@ -340,7 +346,9 @@
self.get_server_ip(server_with_eph_disk),
self.ssh_user,
admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server_with_eph_disk,
+ servers_client=self.client)
partition_num_emph = len(linux_client.get_partitions().split('\n'))
self.assertEqual(partition_num + 1, partition_num_emph)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index f3aa16a..f01657b 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -91,7 +91,9 @@
linux_client = remote_client.RemoteClient(
self.get_server_ip(server),
self.ssh_user,
- new_password)
+ new_password,
+ server=server,
+ servers_client=self.client)
linux_client.validate_authentication()
def _test_reboot_server(self, reboot_type):
@@ -102,7 +104,9 @@
self.get_server_ip(server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
boot_time = linux_client.get_boot_time()
self.client.reboot_server(self.server_id, type=reboot_type)
@@ -114,7 +118,9 @@
self.get_server_ip(server),
self.ssh_user,
self.password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
new_boot_time = linux_client.get_boot_time()
self.assertTrue(new_boot_time > boot_time,
'%s > %s' % (new_boot_time, boot_time))
@@ -183,7 +189,9 @@
self.get_server_ip(rebuilt_server),
self.ssh_user,
password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=rebuilt_server,
+ servers_client=self.client)
linux_client.validate_authentication()
@test.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d')
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index 74d34a2..baa4f9a 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -66,7 +66,9 @@
linux_client = remote_client.RemoteClient(
self.get_server_ip(server),
self.ssh_user, password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
self.assertEqual(file_contents,
linux_client.exec_command(
'sudo cat %s' % file_path))
@@ -130,7 +132,9 @@
linux_client = remote_client.RemoteClient(
self.get_server_ip(server),
self.ssh_user, password,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.client)
for i in person:
self.assertEqual(base64.b64decode(i['contents']),
linux_client.exec_command(
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 37423a3..fa3fdfe 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -84,6 +84,19 @@
self.volume['id'], 'available')
if shelve_server:
+ # NOTE(andreaf) If we are going to shelve a server, we should
+ # check first whether the server is ssh-able. Otherwise we won't
+ # be able to distinguish failures introduced by shelve from
+ # pre-existing ones. Also it's good to wait for cloud-init to be
+ # done and sshd server to be running before shelving to avoid
+ # breaking the VM
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(self.server),
+ self.image_ssh_user,
+ self.admin_pass,
+ self.validation_resources['keypair']['private_key'])
+ linux_client.validate_authentication()
+ # If validation went ok, shelve the server
compute.shelve_server(self.servers_client, self.server['id'])
# Attach the volume to the server
@@ -116,7 +129,9 @@
self.get_server_ip(self.server),
self.image_ssh_user,
self.admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
partitions = linux_client.get_partitions()
self.assertIn(self.device, partitions)
@@ -135,7 +150,9 @@
self.get_server_ip(self.server),
self.image_ssh_user,
self.admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
partitions = linux_client.get_partitions()
self.assertNotIn(self.device, partitions)
@@ -179,7 +196,9 @@
self.get_server_ip(self.server['id']),
self.image_ssh_user,
self.admin_pass,
- self.validation_resources['keypair']['private_key'])
+ self.validation_resources['keypair']['private_key'],
+ server=self.server,
+ servers_client=self.servers_client)
command = 'grep vd /proc/partitions | wc -l'
nb_partitions = linux_client.exec_command(command).strip()
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index 990e429..f709c91 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -27,7 +27,7 @@
# ensure that the backing file for the volume group that Nova uses
# has space for at least 3 1G volumes!
# If you are running a Devstack environment, ensure that the
- # VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc
+ # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/database/flavors/test_flavors.py b/tempest/api/database/flavors/test_flavors.py
index bdb4383..bb7a0a4 100644
--- a/tempest/api/database/flavors/test_flavors.py
+++ b/tempest/api/database/flavors/test_flavors.py
@@ -27,6 +27,7 @@
@test.attr(type='smoke')
@test.idempotent_id('c94b825e-0132-4686-8049-8a4a2bc09525')
+ @decorators.skip_because(bug='1567134')
def test_get_db_flavor(self):
# The expected flavor details should be returned
flavor = (self.client.show_db_flavor(self.db_flavor_ref)
@@ -38,6 +39,7 @@
@test.attr(type='smoke')
@test.idempotent_id('685025d6-0cec-4673-8a8d-995cb8e0d3bb')
+ @decorators.skip_because(bug='1567134')
def test_list_db_flavors(self):
flavor = (self.client.show_db_flavor(self.db_flavor_ref)
['flavor'])
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 1a84d06..cad22f3 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -32,7 +32,7 @@
if container_format in a_formats and container_format != disk_format:
msg = ("The container format and the disk format don't match. "
- "Contaiter format: %(container)s, Disk format: %(disk)s." %
+ "Container format: %(container)s, Disk format: %(disk)s." %
{'container': container_format, 'disk': disk_format})
raise exceptions.InvalidConfiguration(message=msg)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 3654b2e..46b068b 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -364,6 +364,23 @@
self._verify_router_interface(router['id'], subnet02['id'],
interface02['port_id'])
+ @test.idempotent_id('96522edf-b4b5-45d9-8443-fa11c26e6eff')
+ def test_router_interface_port_update_with_fixed_ip(self):
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ router = self._create_router(data_utils.rand_name('router-'))
+ fixed_ip = [{'subnet_id': subnet['id']}]
+ interface = self._add_router_interface_with_subnet_id(router['id'],
+ subnet['id'])
+ self.assertIn('port_id', interface)
+ self.assertIn('subnet_id', interface)
+ port = self.ports_client.show_port(interface['port_id'])
+ self.assertEqual(port['port']['id'], interface['port_id'])
+ router_port = self.ports_client.update_port(port['port']['id'],
+ fixed_ips=fixed_ip)
+ self.assertEqual(subnet['id'],
+ router_port['port']['fixed_ips'][0]['subnet_id'])
+
def _verify_router_interface(self, router_id, subnet_id, port_id):
show_port_body = self.ports_client.show_port(port_id)
interface_port = show_port_body['port']
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 866e676..6707121 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -14,6 +14,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
@@ -34,6 +35,9 @@
cls.name_field = cls.special_fields['name_field']
cls.descrip_field = cls.special_fields['descrip_field']
+ # Create 2 snapshots
+ for _ in xrange(2):
+ cls.create_snapshot(cls.volume_origin['id'])
def _detach(self, volume_id):
"""Detach volume."""
@@ -58,6 +62,14 @@
('details' if with_detail else '', key)
self.assertEqual(params[key], snap[key], msg)
+ def _list_snapshots_by_param_limit(self, limit, expected_elements):
+ """list snapshots by limit param"""
+ # Get snapshots list using limit parameter
+ fetched_snap_list = self.snapshots_client.list_snapshots(
+ limit=limit)['snapshots']
+ # Validating filtered snapshots length equals to expected_elements
+ self.assertEqual(expected_elements, len(fetched_snap_list))
+
@test.idempotent_id('b467b54c-07a4-446d-a1cf-651dedcc3ff1')
@test.services('compute')
def test_snapshot_create_with_volume_in_use(self):
@@ -178,6 +190,25 @@
self.volumes_client.wait_for_resource_deletion(volume['id'])
self.cleanup_snapshot(snapshot)
+ @test.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896')
+ def test_snapshot_list_param_limit(self):
+ # List returns limited elements
+ self._list_snapshots_by_param_limit(limit=1, expected_elements=1)
+
+ @test.idempotent_id('a1427f61-420e-48a5-b6e3-0b394fa95400')
+ def test_snapshot_list_param_limit_equals_infinite(self):
+ # List returns all elements when request limit exceeded
+ # snapshots number
+ snap_list = self.snapshots_client.list_snapshots()['snapshots']
+ self._list_snapshots_by_param_limit(limit=100000,
+ expected_elements=len(snap_list))
+
+ @decorators.skip_because(bug='1540893')
+ @test.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a')
+ def test_snapshot_list_param_limit_equals_zero(self):
+ # List returns zero elements
+ self._list_snapshots_by_param_limit(limit=0, expected_elements=0)
+
def cleanup_snapshot(self, snapshot):
# Delete the snapshot
self.snapshots_client.delete_snapshot(snapshot['id'])
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 9a3a4ed..633b9e9 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -85,6 +85,10 @@
parser = super(TempestInit, self).get_parser(prog_name)
parser.add_argument('dir', nargs='?', default=os.getcwd())
parser.add_argument('--config-dir', '-c', default=None)
+ parser.add_argument('--show-global-config-dir', '-s',
+ action='store_true', dest='show_global_dir',
+ help="Print the global config dir location, "
+ "then exit")
return parser
def generate_testr_conf(self, local_path):
@@ -156,4 +160,7 @@
def take_action(self, parsed_args):
config_dir = parsed_args.config_dir or get_tempest_default_config_dir()
+ if parsed_args.show_global_dir:
+ print("Global config dir is located at: %s" % config_dir)
+ sys.exit(0)
self.create_working_dir(parsed_args.dir, config_dir)
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 3ef04f5..39fed63 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -16,6 +16,7 @@
import argparse
import os
+import re
import sys
import traceback
@@ -77,9 +78,16 @@
not CONF.image_feature_enabled.api_v2, update)
+def _remove_version_project(url_path):
+ # The regex matches strings like /v2.0, /v3/, /v2.1/project-id/
+ return re.sub(r'/v\d+(\.\d+)?(/[^/]+)?', '', url_path)
+
+
def _get_unversioned_endpoint(base_url):
endpoint_parts = urlparse.urlparse(base_url)
- endpoint = endpoint_parts.scheme + '://' + endpoint_parts.netloc
+ new_path = _remove_version_project(endpoint_parts.path)
+ endpoint_parts = endpoint_parts._replace(path=new_path)
+ endpoint = urlparse.urlunparse(endpoint_parts)
return endpoint
@@ -89,7 +97,9 @@
'keystone': os.identity_client,
'cinder': os.volumes_client,
}
- client_dict[service].skip_path()
+ if service != 'keystone':
+ # Since keystone may be listening on a path, do not remove the path.
+ client_dict[service].skip_path()
endpoint = _get_unversioned_endpoint(client_dict[service].base_url)
http = tempest.lib.common.http.ClosingHttp(
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index d374be4..a63af38 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -154,6 +154,20 @@
return cred_provider.TestResources(creds)
def _create_network_resources(self, tenant_id):
+ """The function creates network resources in the given tenant.
+
+ The function checks if network_resources class member is empty,
+ In case it is, it will create a network, a subnet and a router for
+ the tenant according to the given tenant id parameter.
+ Otherwise it will create a network resource according
+ to the values from network_resources dict.
+
+ :param tenant_id: The tenant id to create resources for.
+ :type tenant_id: str
+ :raises: InvalidConfiguration, Exception
+ :returns: network resources(network,subnet,router)
+ :rtype: tuple
+ """
network = None
subnet = None
router = None
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 3a51f2e..0647edb 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -169,7 +169,7 @@
expected_result = generator_result[2]
element = path.pop()
if len(path) > 0:
- schema_snip = reduce(dict.get, path, schema)
+ schema_snip = six.moves.reduce(dict.get, path, schema)
schema_snip[element] = invalid_snippet
else:
schema[element] = invalid_snippet
diff --git a/tempest/common/preprov_creds.py b/tempest/common/preprov_creds.py
index f3df387..51f723b 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/common/preprov_creds.py
@@ -219,9 +219,9 @@
else:
hashes = self.hash_dict['creds'].keys()
# NOTE(mtreinish): admin is a special case because of the increased
- # privlege set which could potentially cause issues on tests where that
- # is not expected. So unless the admin role isn't specified do not
- # allocate admin.
+ # privilege set which could potentially cause issues on tests where
+ # that is not expected. So unless the admin role isn't specified do
+ # not allocate admin.
admin_hashes = self.hash_dict['roles'].get(self.admin_role,
None)
if ((not roles or self.admin_role not in roles) and
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 3a215a0..3f573b7 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -12,6 +12,8 @@
import netaddr
import re
+import six
+import sys
import time
from oslo_log import log as logging
@@ -19,6 +21,7 @@
from tempest import config
from tempest import exceptions
from tempest.lib.common import ssh
+from tempest.lib.common.utils import misc as misc_utils
import tempest.lib.exceptions
CONF = config.CONF
@@ -26,16 +29,61 @@
LOG = logging.getLogger(__name__)
+def debug_ssh(function):
+ """Decorator to generate extra debug info in case off SSH failure"""
+ def wrapper(self, *args, **kwargs):
+ try:
+ return function(self, *args, **kwargs)
+ except tempest.lib.exceptions.SSHTimeout:
+ try:
+ original_exception = sys.exc_info()
+ caller = misc_utils.find_test_caller() or "not found"
+ if self.server:
+ msg = 'Caller: %s. Timeout trying to ssh to server %s'
+ LOG.debug(msg, caller, self.server)
+ if self.log_console and self.servers_client:
+ try:
+ msg = 'Console log for server %s: %s'
+ console_log = (
+ self.servers_client.get_console_output(
+ self.server['id'])['output'])
+ LOG.debug(msg, self.server['id'], console_log)
+ except Exception:
+ msg = 'Could not get console_log for server %s'
+ LOG.debug(msg, self.server['id'])
+ # re-raise the original ssh timeout exception
+ six.reraise(*original_exception)
+ finally:
+ # Delete the traceback to avoid circular references
+ _, _, trace = original_exception
+ del trace
+ return wrapper
+
+
class RemoteClient(object):
- def __init__(self, ip_address, username, password=None, pkey=None):
+ def __init__(self, ip_address, username, password=None, pkey=None,
+ server=None, servers_client=None):
+ """Executes commands in a VM over ssh
+
+ :param ip_address: IP address to ssh to
+ :param username: ssh username
+ :param password: ssh password (optional)
+ :param pkey: ssh public key (optional)
+ :param server: server dict, used for debugging purposes
+ :param servers_client: servers client, used for debugging purposes
+ """
+ self.server = server
+ self.servers_client = servers_client
ssh_timeout = CONF.validation.ssh_timeout
connect_timeout = CONF.validation.connect_timeout
+ self.log_console = CONF.compute_feature_enabled.console_output
self.ssh_client = ssh.Client(ip_address, username, password,
ssh_timeout, pkey=pkey,
channel_timeout=connect_timeout)
+ @debug_ssh
def exec_command(self, cmd):
# Shell options below add more clearness on failures,
# path is extended for some non-cirros guest oses (centos7)
@@ -43,6 +91,7 @@
LOG.debug("Remote command: %s" % cmd)
return self.ssh_client.exec_command(cmd)
+ @debug_ssh
def validate_authentication(self):
"""Validate ssh connection and authentication
diff --git a/tempest/config.py b/tempest/config.py
index a09080d..3e0f28f 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,6 +15,7 @@
from __future__ import print_function
+import functools
import logging as std_logging
import os
import tempfile
@@ -22,6 +23,7 @@
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
+import testtools
from tempest.test_discover import plugins
@@ -1385,3 +1387,72 @@
CONF = TempestConfigProxy()
+
+
+def skip_unless_config(*args):
+ """Decorator to raise a skip if a config opt doesn't exist or is False
+
+ :param str group: The first arg, the option group to check
+ :param str name: The second arg, the option name to check
+ :param str msg: Optional third arg, the skip msg to use if a skip is raised
+ :raises testtools.TestCaseskipException: If the specified config option
+ doesn't exist or it exists and evaluates to False
+ """
+ def decorator(f):
+ group = args[0]
+ name = args[1]
+
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ if not hasattr(CONF, group):
+ msg = "Config group %s doesn't exist" % group
+ raise testtools.TestCase.skipException(msg)
+
+ conf_group = getattr(CONF, group)
+ if not hasattr(conf_group, name):
+ msg = "Config option %s.%s doesn't exist" % (group,
+ name)
+ raise testtools.TestCase.skipException(msg)
+
+ value = getattr(conf_group, name)
+ if not value:
+ if len(args) == 3:
+ msg = args[2]
+ else:
+ msg = "Config option %s.%s is false" % (group,
+ name)
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
+def skip_if_config(*args):
+ """Raise a skipException if a config exists and is True
+
+ :param str group: The first arg, the option group to check
+ :param str name: The second arg, the option name to check
+ :param str msg: Optional third arg, the skip msg to use if a skip is raised
+ :raises testtools.TestCase.skipException: If the specified config option
+ exists and evaluates to True
+ """
+ def decorator(f):
+ group = args[0]
+ name = args[1]
+
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ if hasattr(CONF, group):
+ conf_group = getattr(CONF, group)
+ if hasattr(conf_group, name):
+ value = getattr(conf_group, name)
+ if value:
+ if len(args) == 3:
+ msg = args[2]
+ else:
+ msg = "Config option %s.%s is false" % (group,
+ name)
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
index b7d6a24..f35b14c 100755
--- a/tempest/lib/cmd/skip_tracker.py
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -48,7 +48,7 @@
def find_skips(start):
- """Find the entire list of skiped tests.
+ """Find the entire list of skipped tests.
Returns a list of tuples (method, bug) that represent
test methods that have been decorated to skip because of
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 01b6477..9605479 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -39,7 +39,7 @@
def rand_name(name='', prefix=None):
- """Generate a random name that inclues a random number
+ """Generate a random name that includes a random number
:param str name: The name that you want to include
:param str prefix: The prefix that you want to include
@@ -81,7 +81,7 @@
def rand_url():
- """Generate a random url that inclues a random number
+ """Generate a random url that includes a random number
:return: a random url. The format is 'https://url-<random number>.com'.
(e.g. 'https://url-154876201.com')
@@ -121,6 +121,18 @@
return ':'.join(["%02x" % x for x in mac])
+def rand_infiniband_guid_address():
+ """Generate an Infiniband GUID address
+
+ :return: an random Infiniband GUID address
+ :rtype: string
+ """
+ guid = []
+ for i in range(8):
+ guid.append("%02x" % random.randint(0x00, 0xff))
+ return ':'.join(guid)
+
+
def parse_image_id(image_ref):
"""Return the image id from a given image ref
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 0472eda..8e4eca1 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -61,7 +61,7 @@
post_body = {'server': body}
if hints:
- post_body = dict(post_body.items() + hints.items())
+ post_body.update(hints)
post_body = json.dumps(post_body)
resp, body = self.post('servers', post_body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 988ee1a..956fe88 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -29,7 +29,7 @@
from tempest import exceptions
from tempest.lib.common.utils import misc as misc_utils
from tempest.lib import exceptions as lib_exc
-from tempest.services.network import resources as net_resources
+from tempest.scenario import network_resources
import tempest.test
CONF = config.CONF
@@ -648,7 +648,7 @@
"""
if CONF.validation.connect_method == 'floating':
# The tests calling this method don't have a floating IP
- # and can't make use of the validattion resources. So the
+ # and can't make use of the validation resources. So the
# method is creating the floating IP there.
return self.create_floating_ip(server)['ip']
elif CONF.validation.connect_method == 'fixed':
@@ -697,7 +697,7 @@
tenant_id = networks_client.tenant_id
name = data_utils.rand_name(namestart)
result = networks_client.create_network(name=name, tenant_id=tenant_id)
- network = net_resources.DeletableNetwork(
+ network = network_resources.DeletableNetwork(
networks_client=networks_client, routers_client=routers_client,
**result['network'])
self.assertEqual(network.name, name)
@@ -790,7 +790,7 @@
if not is_overlapping_cidr:
raise
self.assertIsNotNone(result, 'Unable to allocate tenant network')
- subnet = net_resources.DeletableSubnet(
+ subnet = network_resources.DeletableSubnet(
subnets_client=subnets_client,
routers_client=routers_client, **result['subnet'])
self.assertEqual(subnet.cidr, str_cidr)
@@ -807,8 +807,8 @@
network_id=network_id,
**kwargs)
self.assertIsNotNone(result, 'Unable to allocate port')
- port = net_resources.DeletablePort(ports_client=client,
- **result['port'])
+ port = network_resources.DeletablePort(ports_client=client,
+ **result['port'])
self.addCleanup(self.delete_wrapper, port.delete)
return port
@@ -838,7 +838,7 @@
net = self._list_networks(name=network_name)
self.assertNotEqual(len(net), 0,
"Unable to get network by name: %s" % network_name)
- return net_resources.AttributeDict(net[0])
+ return network_resources.AttributeDict(net[0])
def create_floating_ip(self, thing, external_network_id=None,
port_id=None, client=None):
@@ -857,7 +857,7 @@
tenant_id=thing['tenant_id'],
fixed_ip_address=ip4
)
- floating_ip = net_resources.DeletableFloatingIp(
+ floating_ip = network_resources.DeletableFloatingIp(
client=client,
**result['floatingip'])
self.addCleanup(self.delete_wrapper, floating_ip.delete)
@@ -878,8 +878,8 @@
def check_floating_ip_status(self, floating_ip, status):
"""Verifies floatingip reaches the given status
- :param floating_ip: net_resources.DeletableFloatingIp floating IP to
- to check status
+ :param floating_ip: network_resources.DeletableFloatingIp floating
+ IP to check status
:param status: target status
:raises: AssertionError if status doesn't match
"""
@@ -991,7 +991,7 @@
description=sg_desc)
sg_dict['tenant_id'] = tenant_id
result = client.create_security_group(**sg_dict)
- secgroup = net_resources.DeletableSecurityGroup(
+ secgroup = network_resources.DeletableSecurityGroup(
client=client, routers_client=self.routers_client,
**result['security_group']
)
@@ -1016,8 +1016,8 @@
]
msg = "No default security group for tenant %s." % (tenant_id)
self.assertTrue(len(sgs) > 0, msg)
- return net_resources.DeletableSecurityGroup(client=client,
- **sgs[0])
+ return network_resources.DeletableSecurityGroup(client=client,
+ **sgs[0])
def _create_security_group_rule(self, secgroup=None,
sec_group_rules_client=None,
@@ -1055,7 +1055,7 @@
ruleset.update(kwargs)
sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
- sg_rule = net_resources.DeletableSecurityGroupRule(
+ sg_rule = network_resources.DeletableSecurityGroupRule(
client=sec_group_rules_client,
**sg_rule['security_group_rule']
)
@@ -1135,7 +1135,7 @@
network_id = CONF.network.public_network_id
if router_id:
body = client.show_router(router_id)
- return net_resources.AttributeDict(**body['router'])
+ return network_resources.AttributeDict(**body['router'])
elif network_id:
router = self._create_router(client, tenant_id)
router.set_gateway(network_id)
@@ -1154,8 +1154,8 @@
result = client.create_router(name=name,
admin_state_up=True,
tenant_id=tenant_id)
- router = net_resources.DeletableRouter(routers_client=client,
- **result['router'])
+ router = network_resources.DeletableRouter(routers_client=client,
+ **result['router'])
self.assertEqual(router.name, name)
self.addCleanup(self.delete_wrapper, router.delete)
return router
diff --git a/tempest/services/network/resources.py b/tempest/scenario/network_resources.py
similarity index 98%
rename from tempest/services/network/resources.py
rename to tempest/scenario/network_resources.py
index 329c54d..667476f 100644
--- a/tempest/services/network/resources.py
+++ b/tempest/scenario/network_resources.py
@@ -76,7 +76,7 @@
"""Waits for a network resource to reach a status
@param fetch: the callable to be used to query the resource status
- @type fecth: callable that takes no parameters and returns the resource
+ @type fetch: callable that takes no parameters and returns the resource
@param status: the status that the resource has to reach
@type status: String
"""
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index c23a78f..b9fdd18 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -24,7 +24,7 @@
from tempest import config
from tempest import exceptions
from tempest.scenario import manager
-from tempest.services.network import resources as net_resources
+from tempest.scenario import network_resources
from tempest import test
CONF = config.CONF
@@ -269,8 +269,9 @@
"Old port: %s. Number of new ports: %d" % (
CONF.network.build_timeout, old_port,
len(self.new_port_list)))
- new_port = net_resources.DeletablePort(ports_client=self.ports_client,
- **self.new_port_list[0])
+ new_port = network_resources.DeletablePort(
+ ports_client=self.ports_client,
+ **self.new_port_list[0])
def check_new_nic():
new_nic_list = self._get_server_nics(ssh_client)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 4b932ce..504d72b 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -31,7 +31,7 @@
"""The test suite for server advanced operations
This test case stresses some advanced server instance operations:
- * Resizing an instance
+ * Resizing a volume-backed instance
* Sequence suspend resume
"""
@@ -50,10 +50,10 @@
@test.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
- @test.services('compute')
- def test_resize_server_confirm(self):
+ @test.services('compute', 'volume')
+ def test_resize_volume_backed_server_confirm(self):
# We create an instance for use in this test
- instance = self.create_server(wait_until='ACTIVE')
+ instance = self.create_server(wait_until='ACTIVE', volume_backed=True)
instance_id = instance['id']
resize_flavor = CONF.compute.flavor_ref_alt
LOG.debug("Resizing instance %s from flavor %s to flavor %s",
diff --git a/tempest/test.py b/tempest/test.py
index b32beaa..4bc0ae2 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -350,11 +350,14 @@
@classmethod
def setup_credentials(cls):
- """Allocate credentials and the client managers from them.
+ """Allocate credentials and create the client managers from them.
- A test class that requires network resources must override
- setup_credentials and defined the required resources before super
- is invoked.
+ For every element of credentials param function creates tenant/user,
+ Then it creates client manager for that credential.
+
+ Network related tests must override this function with
+ set_network_resources() method, otherwise it will create
+ network resources(network resources are created in a later step).
"""
for credentials_type in cls.credentials:
# This may raise an exception in case credentials are not available
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 3b09673..70cbf87 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -12,28 +12,56 @@
# License for the specific language governing permissions and limitations
# under the License.
-
import mock
from oslo_serialization import jsonutils as json
from oslotest import mockpatch
from tempest.cmd import verify_tempest_config
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest.tests import base
from tempest.tests import fake_config
class TestGetAPIVersions(base.TestCase):
+ def test_remove_version_project(self):
+ f = verify_tempest_config._remove_version_project
+ self.assertEqual('/', f('/v2.1/%s/' % data_utils.rand_uuid_hex()))
+ self.assertEqual('', f('/v2.1/tenant_id'))
+ self.assertEqual('', f('/v3'))
+ self.assertEqual('/', f('/v3/'))
+ self.assertEqual('/something/', f('/something/v2.1/tenant_id/'))
+ self.assertEqual('/something', f('/something/v2.1/tenant_id'))
+ self.assertEqual('/something', f('/something/v3'))
+ self.assertEqual('/something/', f('/something/v3/'))
+ self.assertEqual('/', f('/')) # http://localhost/
+ self.assertEqual('', f('')) # http://localhost
+
def test_url_grab_versioned_nova_nossl(self):
base_url = 'http://127.0.0.1:8774/v2/'
endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
- self.assertEqual('http://127.0.0.1:8774', endpoint)
+ self.assertEqual('http://127.0.0.1:8774/', endpoint)
def test_url_grab_versioned_nova_ssl(self):
base_url = 'https://127.0.0.1:8774/v3/'
endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
- self.assertEqual('https://127.0.0.1:8774', endpoint)
+ self.assertEqual('https://127.0.0.1:8774/', endpoint)
+
+ def test_get_unversioned_endpoint_base(self):
+ base_url = 'https://127.0.0.1:5000/'
+ endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
+ self.assertEqual('https://127.0.0.1:5000/', endpoint)
+
+ def test_get_unversioned_endpoint_subpath(self):
+ base_url = 'https://127.0.0.1/identity/v3'
+ endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
+ self.assertEqual('https://127.0.0.1/identity', endpoint)
+
+ def test_get_unversioned_endpoint_subpath_trailing_solidus(self):
+ base_url = 'https://127.0.0.1/identity/v3/'
+ endpoint = verify_tempest_config._get_unversioned_endpoint(base_url)
+ self.assertEqual('https://127.0.0.1/identity/', endpoint)
class TestDiscovery(base.TestCase):
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index e9146bc..7d625cf 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
import time
from oslo_config import cfg
@@ -19,10 +20,39 @@
from tempest.common.utils.linux import remote_client
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
from tempest.tests import fake_config
+SERVER = {
+ 'id': 'server_uuid',
+ 'name': 'fake_server',
+ 'status': 'ACTIVE'
+}
+
+BROKEN_SERVER = {
+ 'id': 'broken_server_uuid',
+ 'name': 'broken_server',
+ 'status': 'ERROR'
+}
+
+
+class FakeServersClient(object):
+
+ CONSOLE_OUTPUT = "Console output for %s"
+
+ def get_console_output(self, server_id):
+ status = 'ERROR'
+ for s in SERVER, BROKEN_SERVER:
+ if s['id'] == server_id:
+ status = s['status']
+ if status == 'ERROR':
+ raise lib_exc.BadRequest('Server in ERROR state')
+ else:
+ return dict(output=self.CONSOLE_OUTPUT % server_id)
+
+
class TestRemoteClient(base.TestCase):
def setUp(self):
super(TestRemoteClient, self).setUp()
@@ -155,3 +185,78 @@
self.conn.set_nic_state(nic, "down")
self._assert_exec_called_with(
'sudo ip link set %s down' % nic)
+
+
+class TestRemoteClientWithServer(base.TestCase):
+
+ server = SERVER
+
+ def setUp(self):
+ super(TestRemoteClientWithServer, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.patchobject(config, 'TempestConfigPrivate',
+ fake_config.FakePrivate)
+ cfg.CONF.set_default('ip_version_for_ssh', 4, group='validation')
+ cfg.CONF.set_default('network_for_ssh', 'public',
+ group='validation')
+ cfg.CONF.set_default('connect_timeout', 1, group='validation')
+ cfg.CONF.set_default('console_output', True,
+ group='compute-feature-enabled')
+
+ self.conn = remote_client.RemoteClient(
+ '127.0.0.1', 'user', 'pass',
+ server=self.server, servers_client=FakeServersClient())
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.ssh.Client._get_ssh_connection',
+ side_effect=lib_exc.SSHTimeout(host='127.0.0.1',
+ user='user',
+ password='pass')))
+ self.log = self.useFixture(fixtures.FakeLogger(
+ name='tempest.common.utils.linux.remote_client',
+ level='DEBUG'))
+
+ def test_validate_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.validate_authentication)
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ 'TestRemoteClientWithServer:test_validate_debug_ssh_console',
+ self.server)
+ self.assertIn(msg, self.log.output)
+ self.assertIn('Console output for', self.log.output)
+
+ def test_exec_command_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.exec_command, 'fake command')
+ self.assertIn('fake command', self.log.output)
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ 'TestRemoteClientWithServer:test_exec_command_debug_ssh_console',
+ self.server)
+ self.assertIn(msg, self.log.output)
+ self.assertIn('Console output for', self.log.output)
+
+
+class TestRemoteClientWithBrokenServer(TestRemoteClientWithServer):
+
+ server = BROKEN_SERVER
+
+ def test_validate_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.validate_authentication)
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ 'TestRemoteClientWithBrokenServer:test_validate_debug_ssh_console',
+ self.server)
+ self.assertIn(msg, self.log.output)
+ msg = 'Could not get console_log for server %s' % self.server['id']
+ self.assertIn(msg, self.log.output)
+
+ def test_exec_command_debug_ssh_console(self):
+ self.assertRaises(lib_exc.SSHTimeout,
+ self.conn.exec_command, 'fake command')
+ self.assertIn('fake command', self.log.output)
+ caller = ":".join(['TestRemoteClientWithBrokenServer',
+ 'test_exec_command_debug_ssh_console'])
+ msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
+ caller, self.server)
+ self.assertIn(msg, self.log.output)
+ msg = 'Could not get console_log for server %s' % self.server['id']
+ self.assertIn(msg, self.log.output)
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index f435461..f9e1f44 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -93,6 +93,15 @@
actual2 = data_utils.rand_int_id()
self.assertNotEqual(actual, actual2)
+ def test_rand_infiniband_guid_address(self):
+ actual = data_utils.rand_infiniband_guid_address()
+ self.assertIsInstance(actual, str)
+ self.assertRegex(actual, "^([0-9a-f][0-9a-f]:){7}"
+ "[0-9a-f][0-9a-f]$")
+
+ actual2 = data_utils.rand_infiniband_guid_address()
+ self.assertNotEqual(actual, actual2)
+
def test_rand_mac_address(self):
actual = data_utils.rand_mac_address()
self.assertIsInstance(actual, str)
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 7c9579b..8c5d861 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -246,3 +246,96 @@
self.assertIn("test_fake_negative", dir(obj))
obj.test_fake_negative()
mock.assert_called_once_with(self.FakeNegativeJSONTest._schema)
+
+
+class TestConfigDecorators(BaseDecoratorsTest):
+ def setUp(self):
+ super(TestConfigDecorators, self).setUp()
+ cfg.CONF.set_default('nova', True, 'service_available')
+ cfg.CONF.set_default('glance', False, 'service_available')
+
+ def _assert_skip_message(self, func, skip_msg):
+ try:
+ func()
+ self.fail()
+ except testtools.TestCase.skipException as skip_exc:
+ self.assertEqual(skip_exc.args[0], skip_msg)
+
+ def _test_skip_unless_config(self, expected_to_skip=True, *decorator_args):
+
+ class TestFoo(test.BaseTestCase):
+ @config.skip_unless_config(*decorator_args)
+ def test_bar(self):
+ return 0
+
+ t = TestFoo('test_bar')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ if (len(decorator_args) >= 3):
+ # decorator_args[2]: skip message specified
+ self._assert_skip_message(t.test_bar, decorator_args[2])
+ else:
+ try:
+ self.assertEqual(t.test_bar(), 0)
+ except testtools.TestCase.skipException:
+ # We caught a skipException but we didn't expect to skip
+ # this test so raise a hard test failure instead.
+ raise testtools.TestCase.failureException(
+ "Not supposed to skip")
+
+ def _test_skip_if_config(self, expected_to_skip=True,
+ *decorator_args):
+
+ class TestFoo(test.BaseTestCase):
+ @config.skip_if_config(*decorator_args)
+ def test_bar(self):
+ return 0
+
+ t = TestFoo('test_bar')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ if (len(decorator_args) >= 3):
+ # decorator_args[2]: skip message specified
+ self._assert_skip_message(t.test_bar, decorator_args[2])
+ else:
+ try:
+ self.assertEqual(t.test_bar(), 0)
+ except testtools.TestCase.skipException:
+ # We caught a skipException but we didn't expect to skip
+ # this test so raise a hard test failure instead.
+ raise testtools.TestCase.failureException(
+ "Not supposed to skip")
+
+ def test_skip_unless_no_group(self):
+ self._test_skip_unless_config(True, 'fake_group', 'an_option')
+
+ def test_skip_unless_no_option(self):
+ self._test_skip_unless_config(True, 'service_available',
+ 'not_an_option')
+
+ def test_skip_unless_false_option(self):
+ self._test_skip_unless_config(True, 'service_available', 'glance')
+
+ def test_skip_unless_false_option_msg(self):
+ self._test_skip_unless_config(True, 'service_available', 'glance',
+ 'skip message')
+
+ def test_skip_unless_true_option(self):
+ self._test_skip_unless_config(False,
+ 'service_available', 'nova')
+
+ def test_skip_if_no_group(self):
+ self._test_skip_if_config(False, 'fake_group', 'an_option')
+
+ def test_skip_if_no_option(self):
+ self._test_skip_if_config(False, 'service_available', 'not_an_option')
+
+ def test_skip_if_false_option(self):
+ self._test_skip_if_config(False, 'service_available', 'glance')
+
+ def test_skip_if_true_option(self):
+ self._test_skip_if_config(True, 'service_available', 'nova')
+
+ def test_skip_if_true_option_msg(self):
+ self._test_skip_if_config(True, 'service_available', 'nova',
+ 'skip message')
diff --git a/tox.ini b/tox.ini
index e371406..44162fd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -35,6 +35,7 @@
commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
[testenv:all]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
# 'all' includes slow tests
setenv =
@@ -68,6 +69,7 @@
bash tools/pretty_tox.sh '{posargs}'
[testenv:full]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -78,6 +80,7 @@
bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
[testenv:full-serial]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -88,6 +91,7 @@
bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
[testenv:smoke]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -96,6 +100,7 @@
bash tools/pretty_tox.sh '\[.*\bsmoke\b.*\] {posargs}'
[testenv:smoke-serial]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -107,6 +112,7 @@
bash tools/pretty_tox_serial.sh '\[.*\bsmoke\b.*\] {posargs}'
[testenv:stress]
+envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
@@ -116,6 +122,13 @@
[testenv:venv]
commands = {posargs}
+[testenv:venv-tempest]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+commands = {posargs}
+
[testenv:docs]
commands =
python setup.py build_sphinx {posargs}