Merge "API tests: Don't depend on template versions >mitaka"
diff --git a/.zuul.yaml b/.zuul.yaml
index 61c94c3..c9aea1b 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,3 +1,20 @@
+- job:
+ name: heat-functional-convg-queens
+ parent: heat-functional-convg-mysql-lbaasv2
+ override-checkout: stable/queens
+
+- job:
+ name: heat-functional-orig-queens
+ parent: heat-functional-orig-mysql-lbaasv2
+ override-checkout: stable/queens
+
+- job:
+ name: heat-functional-convg-queens-py35
+ parent: heat-functional-convg-mysql-lbaasv2-py35
+ override-checkout: stable/queens
+
+
+
- project:
check:
jobs:
@@ -5,6 +22,9 @@
- heat-functional-convg-mysql-lbaasv2
- heat-functional-convg-mysql-lbaasv2-non-apache
- heat-functional-convg-mysql-lbaasv2-py35
+ - heat-functional-convg-queens
+ - heat-functional-convg-queens-py35
+ - heat-functional-orig-queens
gate:
jobs:
- heat-functional-orig-mysql-lbaasv2
diff --git a/heat_tempest_plugin/common/test.py b/heat_tempest_plugin/common/test.py
index dbd2e27..429d4ae 100644
--- a/heat_tempest_plugin/common/test.py
+++ b/heat_tempest_plugin/common/test.py
@@ -34,6 +34,7 @@
LOG = logging.getLogger(__name__)
_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
+_resource_types = None
def call_until_true(duration, sleep_for, func, *args, **kwargs):
@@ -86,6 +87,45 @@
return skipper(test_method)
+def requires_resource_type(resource_type):
+ '''Decorator for tests requiring a resource type.
+
+ The decorated test will be skipped when the resource type is not available.
+ '''
+ def decorator(test_method):
+ conf = getattr(config.CONF, 'heat_plugin', None)
+ if not conf or conf.auth_url is None:
+ return test_method
+
+ global _resource_types
+ if not _resource_types:
+ manager = clients.ClientManager(conf)
+ obj_rtypes = manager.orchestration_client.resource_types.list()
+ _resource_types = list(t.resource_type for t in obj_rtypes)
+ rtype_available = resource_type and resource_type in _resource_types
+ skipper = testtools.skipUnless(
+ rtype_available,
+ "%s resource type not available, skipping test." % resource_type)
+ return skipper(test_method)
+ return decorator
+
+
+def requires_feature(feature):
+ '''Decorator for tests requring specific feature.
+
+ The decorated test will be skipped when a specific feature is disabled.
+ '''
+ def decorator(test_method):
+ features_group = getattr(config.CONF, 'heat_features_enabled', None)
+ if not features_group:
+ return test_method
+ feature_enabled = config.CONF.heat_features_enabled.get(feature, False)
+ skipper = testtools.skipUnless(feature_enabled,
+ "%s - Feature not enabled." % feature)
+ return skipper(test_method)
+ return decorator
+
+
class HeatIntegrationTest(testtools.testcase.WithAttributes,
testscenarios.WithScenarios,
testtools.TestCase):
@@ -713,13 +753,13 @@
time.sleep(build_interval)
def check_autoscale_complete(self, stack_id, expected_num, parent_stack,
- policy):
+ group_name):
res_list = self.client.resources.list(stack_id)
all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
'CREATE_COMPLETE')
for res in res_list)
all_res = len(res_list) == expected_num
if all_res and all_res_complete:
- metadata = self.client.resources.metadata(parent_stack, policy)
+ metadata = self.client.resources.metadata(parent_stack, group_name)
return not metadata.get('scaling_in_progress')
return False
diff --git a/heat_tempest_plugin/config.py b/heat_tempest_plugin/config.py
index d658a98..c77bb51 100644
--- a/heat_tempest_plugin/config.py
+++ b/heat_tempest_plugin/config.py
@@ -153,10 +153,23 @@
cfg.StrOpt('heat_config_notify_script',
default=('heat-config-notify'),
help="Path to the script heat-config-notify"),
+ cfg.StrOpt('hidden_stack_tag',
+ default='data-processing-cluster',
+ help="Tag to be considered as hidden for stack tags tests"),
+]
+heat_features_group = cfg.OptGroup(
+ name='heat_features_enabled',
+ title="Enabled Orchestration Service Features")
+
+HeatFeaturesGroup = [
+ cfg.BoolOpt('stack_cancel',
+ default=False,
+ help="If false, skip stack cancel tests")
]
def list_opts():
yield heat_group.name, HeatGroup
+ yield heat_features_group.name, HeatFeaturesGroup
yield service_available_group.name, ServiceAvailableGroup
diff --git a/heat_tempest_plugin/plugin.py b/heat_tempest_plugin/plugin.py
index acf4fc7..6926691 100644
--- a/heat_tempest_plugin/plugin.py
+++ b/heat_tempest_plugin/plugin.py
@@ -34,7 +34,11 @@
heat_config.ServiceAvailableGroup)
config.register_opt_group(conf, heat_config.heat_group,
heat_config.HeatGroup)
+ config.register_opt_group(conf, heat_config.heat_features_group,
+ heat_config.HeatFeaturesGroup)
def get_opt_lists(self):
return [(heat_config.heat_group.name,
- heat_config.HeatGroup)]
+ heat_config.HeatGroup),
+ (heat_config.heat_features_group.name,
+ heat_config.HeatFeaturesGroup)]
diff --git a/heat_tempest_plugin/services/clients.py b/heat_tempest_plugin/services/clients.py
index 4567968..3999cee 100644
--- a/heat_tempest_plugin/services/clients.py
+++ b/heat_tempest_plugin/services/clients.py
@@ -15,7 +15,6 @@
from cinderclient import client as cinder_client
from gnocchiclient import client as gnocchi_client
from heatclient import client as heat_client
-from keystoneauth1 import exceptions as kc_exceptions
from keystoneauth1.identity.generic import password
from keystoneauth1 import session
from neutronclient.v2_0 import client as neutron_client
@@ -104,23 +103,19 @@
def _get_orchestration_client(self):
endpoint = os.environ.get('HEAT_URL')
if os.environ.get('OS_NO_CLIENT_AUTH') == 'True':
- token = None
+ session = None
else:
- token = self.identity_client.auth_token
- try:
- if endpoint is None:
- endpoint = self.identity_client.get_endpoint_url(
- 'orchestration', region=self.conf.region,
- endpoint_type=self.conf.endpoint_type)
- except kc_exceptions.EndpointNotFound:
- return None
- else:
- return heat_client.Client(
- self.HEATCLIENT_VERSION,
- endpoint,
- token=token,
- username=self._username(),
- password=self._password())
+ session = self.identity_client.session
+
+ return heat_client.Client(
+ self.HEATCLIENT_VERSION,
+ endpoint,
+ session=session,
+ endpoint_type='publicURL',
+ service_type='orchestration',
+ region_name=self.conf.region,
+ username=self._username(),
+ password=self._password())
def _get_identity_client(self):
user_domain_id = self.conf.user_domain_id
diff --git a/heat_tempest_plugin/tests/api/test_heat_api.py b/heat_tempest_plugin/tests/api/test_heat_api.py
index 0a44621..804f445 100644
--- a/heat_tempest_plugin/tests/api/test_heat_api.py
+++ b/heat_tempest_plugin/tests/api/test_heat_api.py
@@ -13,16 +13,19 @@
"""A test module to exercise the Heat API with gabbi. """
+import keystoneauth1
import os
+import sys
import unittest
from gabbi import driver
-from six.moves.urllib import parse as urlparse
+from oslo_log import log as logging
from tempest import config
from heat_tempest_plugin.common import test
from heat_tempest_plugin.services import clients
+LOG = logging.getLogger(__name__)
TESTS_DIR = 'gabbits'
@@ -30,16 +33,45 @@
"""Provide a TestSuite to the discovery process."""
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
+ endpoint = None
conf = config.CONF.heat_plugin
- if conf.auth_url is None:
- # It's not configured, let's not load tests
- return
- manager = clients.ClientManager(conf)
- endpoint = manager.identity_client.get_endpoint_url(
- 'orchestration', region=conf.region, endpoint_type=conf.endpoint_type)
- host = urlparse.urlparse(endpoint).hostname
- os.environ['OS_TOKEN'] = manager.identity_client.auth_token
- os.environ['PREFIX'] = test.rand_name('api')
+ if conf.auth_url:
+ try:
+ manager = clients.ClientManager(conf)
+ endpoint = manager.identity_client.get_endpoint_url(
+ 'orchestration', region=conf.region,
+ endpoint_type=conf.endpoint_type)
+ os.environ['OS_TOKEN'] = manager.identity_client.auth_token
+ os.environ['PREFIX'] = test.rand_name('api')
+
+ # Catch the authentication exceptions that can happen if one of the
+ # following conditions occur:
+ # 1. conf.auth_url IP/port is incorrect or keystone not available
+ # (ConnectFailure)
+ # 2. conf.auth_url is malformed (BadRequest, UnknownConnectionError,
+ # EndpointNotFound, NotFound, or DiscoveryFailure)
+ # 3. conf.username/password is incorrect (Unauthorized)
+ # 4. conf.project_name is missing/incorrect (EmptyCatalog)
+ # These exceptions should not prevent a test list from being returned,
+ # so just issue a warning log and move forward with test listing.
+ except (keystoneauth1.exceptions.http.BadRequest,
+ keystoneauth1.exceptions.http.Unauthorized,
+ keystoneauth1.exceptions.http.NotFound,
+ keystoneauth1.exceptions.catalog.EmptyCatalog,
+ keystoneauth1.exceptions.catalog.EndpointNotFound,
+ keystoneauth1.exceptions.discovery.DiscoveryFailure,
+ keystoneauth1.exceptions.connection.UnknownConnectionError,
+ keystoneauth1.exceptions.connection.ConnectFailure):
+ LOG.warn("Keystone auth exception: %s: %s" % (sys.exc_info()[0],
+ sys.exc_info()[1]))
+ # Clear the auth_url, as there is no point in tempest trying
+ # to authenticate later with mis-configured or unreachable endpoint
+ conf.auth_url = None
+
+ except Exception:
+ LOG.error("Fatal exception: %s: %s" % (sys.exc_info()[0],
+ sys.exc_info()[1]))
+ raise
def register_test_case_id(test_case):
tempest_id = test_case.test_data.get('desc')
@@ -60,7 +92,8 @@
else:
register_test_case_id(test_case)
- api_tests = driver.build_tests(test_dir, loader, host=host,
- url=endpoint, test_loader_name=__name__)
+ api_tests = driver.build_tests(test_dir, loader, url=endpoint, host="",
+ test_loader_name=__name__)
+
register_test_suite_ids(api_tests)
return api_tests
diff --git a/heat_tempest_plugin/tests/functional/templates/lb_member.yaml b/heat_tempest_plugin/tests/functional/templates/lb_member.yaml
new file mode 100644
index 0000000..0afa754
--- /dev/null
+++ b/heat_tempest_plugin/tests/functional/templates/lb_member.yaml
@@ -0,0 +1,61 @@
+heat_template_version: pike
+parameters:
+ image:
+ type: string
+ flavor:
+ type: string
+ network:
+ type: string
+ sec_group:
+ type: string
+ pool:
+ type: string
+ app_port:
+ type: number
+ timeout:
+ type: number
+ default: 120
+ subnet:
+ type: string
+
+resources:
+ server:
+ type: OS::Nova::Server
+ properties:
+ image: {get_param: image}
+ flavor: {get_param: flavor}
+ networks:
+ - network: {get_param: network}
+ security_groups:
+ - {get_param: sec_group}
+ user_data_format: RAW
+ user_data:
+ str_replace:
+ template: |
+ #! /bin/sh -v
+ Body=$(hostname)
+ Response="HTTP/1.1 200 OK\r\nContent-Length: ${#Body}\r\n\r\n$Body"
+ wc_notify --data-binary '{"status": "SUCCESS"}'
+ while true ; do echo -e $Response | nc -llp PORT; done
+ params:
+ PORT: {get_param: app_port}
+ wc_notify: { get_attr: [handle, curl_cli]}
+
+ handle:
+ type: OS::Heat::WaitConditionHandle
+
+ waiter:
+ type: OS::Heat::WaitCondition
+ depends_on: server
+ properties:
+ timeout: {get_param: timeout}
+ handle: {get_resource: handle}
+
+ pool_member:
+ type: OS::Octavia::PoolMember
+ depends_on: waiter
+ properties:
+ address: {get_attr: [server, networks, {get_param: network}, 0]}
+ pool: {get_param: pool}
+ protocol_port: {get_param: app_port}
+ subnet: {get_param: subnet}
diff --git a/heat_tempest_plugin/tests/functional/templates/octavia_lbaas.yaml b/heat_tempest_plugin/tests/functional/templates/octavia_lbaas.yaml
new file mode 100644
index 0000000..d20ae60
--- /dev/null
+++ b/heat_tempest_plugin/tests/functional/templates/octavia_lbaas.yaml
@@ -0,0 +1,86 @@
+heat_template_version: pike
+parameters:
+ app_port:
+ type: number
+ default: 8080
+ flavor:
+ type: string
+ default: m1.nano
+ image:
+ type: string
+ default: cirros-0.3.5-x86_64-disk
+ lb_port:
+ type: number
+ default: 80
+ network:
+ type: string
+ default: heat-net
+ subnet:
+ type: string
+ default: heat-subnet
+ member_count:
+ type: number
+ default: 1
+ lb_algorithm:
+ type: string
+ default: ROUND_ROBIN
+
+resources:
+ sec_group:
+ type: OS::Neutron::SecurityGroup
+ properties:
+ rules:
+ - remote_ip_prefix: 0.0.0.0/0
+ protocol: tcp
+ port_range_min: {get_param: app_port}
+ port_range_max: {get_param: app_port}
+
+ pool_members:
+ type: OS::Heat::ResourceGroup
+ properties:
+ count: {get_param: member_count}
+ resource_def:
+ type: OS::Test::PoolMember
+ properties:
+ image: {get_param: image}
+ flavor: {get_param: flavor}
+ pool: {get_resource: pool}
+ app_port: {get_param: app_port}
+ network: {get_param: network}
+ sec_group: {get_resource: sec_group}
+ subnet: {get_param: subnet}
+
+ monitor:
+ type: OS::Octavia::HealthMonitor
+ properties:
+ delay: 3
+ type: HTTP
+ timeout: 3
+ max_retries: 3
+ pool: {get_resource: pool}
+
+ pool:
+ type: OS::Octavia::Pool
+ properties:
+ lb_algorithm: {get_param: lb_algorithm}
+ protocol: HTTP
+ listener: {get_resource: listener}
+
+ listener:
+ type: OS::Octavia::Listener
+ properties:
+ loadbalancer: {get_resource: loadbalancer}
+ protocol: HTTP
+ protocol_port: {get_param: lb_port}
+
+ loadbalancer:
+ type: OS::Octavia::LoadBalancer
+ properties:
+ vip_subnet: {get_param: subnet}
+outputs:
+ loadbalancer:
+ value: {get_attr: [loadbalancer, show]}
+ pool:
+ value: {get_attr: [pool, show]}
+ listener:
+ value: {get_attr: [listener, show]}
diff --git a/heat_tempest_plugin/tests/functional/test_create_update_neutron_trunk.py b/heat_tempest_plugin/tests/functional/test_create_update_neutron_trunk.py
index ff5bcaf..bdcb58e 100644
--- a/heat_tempest_plugin/tests/functional/test_create_update_neutron_trunk.py
+++ b/heat_tempest_plugin/tests/functional/test_create_update_neutron_trunk.py
@@ -17,6 +17,7 @@
from tempest.lib import decorators
+from heat_tempest_plugin.common import test
from heat_tempest_plugin.tests.functional import functional_base
@@ -72,6 +73,7 @@
'''
+@test.requires_resource_type('OS::Neutron::Trunk')
class UpdateTrunkTest(functional_base.FunctionalTestsBase):
@staticmethod
diff --git a/heat_tempest_plugin/tests/functional/test_octavia_lbaas.py b/heat_tempest_plugin/tests/functional/test_octavia_lbaas.py
new file mode 100644
index 0000000..b1e500c
--- /dev/null
+++ b/heat_tempest_plugin/tests/functional/test_octavia_lbaas.py
@@ -0,0 +1,101 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib import decorators
+
+from heat_tempest_plugin.common import test
+from heat_tempest_plugin.tests.functional import functional_base
+
+
+@test.requires_resource_type('OS::Octavia::LoadBalancer')
+class LoadBalancerTest(functional_base.FunctionalTestsBase):
+ def setUp(self):
+ super(LoadBalancerTest, self).setUp()
+ self.template_name = 'octavia_lbaas.yaml'
+ self.member_template_name = 'lb_member.yaml'
+ self.sub_dir = 'templates'
+
+ def _create_stack(self):
+ self.parameters = {
+ 'flavor': self.conf.minimal_instance_type,
+ 'image': self.conf.minimal_image_ref,
+ 'network': self.conf.fixed_network_name,
+ 'subnet': self.conf.fixed_subnet_name
+ }
+ member_template = self._load_template(
+ __file__, self.member_template_name, self.sub_dir
+ )
+ self.files = {'lb_member.yaml': member_template}
+ self.env = {'resource_registry': {
+ 'OS::Test::PoolMember': 'lb_member.yaml'}}
+
+ self.template = self._load_template(__file__, self.template_name,
+ self.sub_dir)
+ return self.stack_create(template=self.template,
+ parameters=self.parameters,
+ files=self.files,
+ environment=self.env)
+
+ @decorators.idempotent_id('5d2c4452-4433-4438-899c-7711c01d3c50')
+ def test_create_update_loadbalancer(self):
+ stack_identifier = self._create_stack()
+ stack = self.client.stacks.get(stack_identifier)
+ output = self._stack_output(stack, 'loadbalancer')
+ self.assertEqual('ONLINE', output['operating_status'])
+ self.parameters['lb_algorithm'] = 'SOURCE_IP'
+
+ self.update_stack(stack_identifier,
+ template=self.template,
+ parameters=self.parameters,
+ files=self.files,
+ environment=self.env)
+ stack = self.client.stacks.get(stack_identifier)
+
+ output = self._stack_output(stack, 'loadbalancer')
+ self.assertEqual('ONLINE', output['operating_status'])
+ output = self._stack_output(stack, 'pool')
+ self.assertEqual('SOURCE_IP', output['lb_algorithm'])
+
+ @decorators.idempotent_id('970e91af-1be8-4990-837b-66f9b5aff2b9')
+ def test_add_delete_poolmember(self):
+ stack_identifier = self._create_stack()
+ stack = self.client.stacks.get(stack_identifier)
+ output = self._stack_output(stack, 'loadbalancer')
+ self.assertEqual('ONLINE', output['operating_status'])
+ output = self._stack_output(stack, 'pool')
+ self.assertEqual(1, len(output['members']))
+ # add pool member
+ self.parameters['member_count'] = 2
+ self.update_stack(stack_identifier,
+ template=self.template,
+ parameters=self.parameters,
+ files=self.files,
+ environment=self.env)
+ stack = self.client.stacks.get(stack_identifier)
+
+ output = self._stack_output(stack, 'loadbalancer')
+ self.assertEqual('ONLINE', output['operating_status'])
+ output = self._stack_output(stack, 'pool')
+ self.assertEqual(2, len(output['members']))
+ # delete pool member
+ self.parameters['member_count'] = 1
+ self.update_stack(stack_identifier,
+ template=self.template,
+ parameters=self.parameters,
+ files=self.files,
+ environment=self.env)
+ stack = self.client.stacks.get(stack_identifier)
+
+ output = self._stack_output(stack, 'loadbalancer')
+ self.assertEqual('ONLINE', output['operating_status'])
+ output = self._stack_output(stack, 'pool')
+ self.assertEqual(1, len(output['members']))
diff --git a/heat_tempest_plugin/tests/functional/test_os_wait_condition.py b/heat_tempest_plugin/tests/functional/test_os_wait_condition.py
index dc78341..603b5e5 100644
--- a/heat_tempest_plugin/tests/functional/test_os_wait_condition.py
+++ b/heat_tempest_plugin/tests/functional/test_os_wait_condition.py
@@ -57,11 +57,11 @@
wc_notify --data-binary ''{"status": "SUCCESS", "reason":
"signal4", "data": "data4"}''
- # check signals with the same number
+ # check signals with the same ID
- wc_notify --data-binary ''{"status": "SUCCESS", "id": "5"}''
+ wc_notify --data-binary ''{"status": "SUCCESS", "id": "test5"}''
- wc_notify --data-binary ''{"status": "SUCCESS", "id": "5"}''
+ wc_notify --data-binary ''{"status": "SUCCESS", "id": "test5"}''
# loop for 20 signals without reasons and data
diff --git a/heat_tempest_plugin/tests/functional/test_stack_tags.py b/heat_tempest_plugin/tests/functional/test_stack_tags.py
index 7d3d52a..55c262d 100644
--- a/heat_tempest_plugin/tests/functional/test_stack_tags.py
+++ b/heat_tempest_plugin/tests/functional/test_stack_tags.py
@@ -72,7 +72,7 @@
@decorators.idempotent_id('5ed79584-0684-4f9c-ae8e-44a8f874ec79')
def test_hidden_stack(self):
# Stack create with hidden stack tag
- tags = 'foo,hidden'
+ tags = 'foo,%s' % self.conf.hidden_stack_tag
self.stack_create(
template=self.template,
tags=tags)
diff --git a/heat_tempest_plugin/tests/scenario/test_autoscaling_lb.py b/heat_tempest_plugin/tests/scenario/test_autoscaling_lb.py
index b8ffa1f..23e27c7 100644
--- a/heat_tempest_plugin/tests/scenario/test_autoscaling_lb.py
+++ b/heat_tempest_plugin/tests/scenario/test_autoscaling_lb.py
@@ -106,7 +106,7 @@
test.call_until_true(self.conf.build_timeout,
self.conf.build_interval,
self.check_autoscale_complete,
- asg.physical_resource_id, 2, sid, 'scale_up')
+ asg.physical_resource_id, 2, sid, 'asg')
# Check number of distinctive responses, must now be 2
self.check_num_responses(lb_url, 2)
diff --git a/heat_tempest_plugin/tests/scenario/test_autoscaling_lbv2.py b/heat_tempest_plugin/tests/scenario/test_autoscaling_lbv2.py
index 52957f5..c3bda78 100644
--- a/heat_tempest_plugin/tests/scenario/test_autoscaling_lbv2.py
+++ b/heat_tempest_plugin/tests/scenario/test_autoscaling_lbv2.py
@@ -106,7 +106,7 @@
test.call_until_true(self.conf.build_timeout,
self.conf.build_interval,
self.check_autoscale_complete,
- asg.physical_resource_id, 2, sid, 'scale_up')
+ asg.physical_resource_id, 2, sid, 'asg')
# Check number of distinctive responses, must now be 2
self.check_num_responses(lb_url, 2)
diff --git a/heat_tempest_plugin/tests/scenario/test_server_signal.py b/heat_tempest_plugin/tests/scenario/test_server_signal.py
index a43c74a..1823087 100644
--- a/heat_tempest_plugin/tests/scenario/test_server_signal.py
+++ b/heat_tempest_plugin/tests/scenario/test_server_signal.py
@@ -29,6 +29,7 @@
'key_name': self.keypair_name,
'flavor': flavor,
'image': image,
+ 'public_net': self.conf.floating_network_name,
'timeout': self.conf.build_timeout,
'user_data_format': user_data_format
}
diff --git a/requirements.txt b/requirements.txt
index d5dbc36..43abf78 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,8 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT
keystoneauth1>=3.4.0 # Apache-2.0
-oslo.config>=5.1.0 # Apache-2.0
+oslo.config>=5.2.0 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
oslo.messaging>=5.29.0 # Apache-2.0
os-collect-config>=5.0.0 # Apache-2.0
@@ -18,5 +17,5 @@
testtools>=2.2.0 # MIT
testscenarios>=0.4 # Apache-2.0/BSD
tempest>=17.1.0 # Apache-2.0
-gabbi>=1.35.0 # Apache-2.0
+gabbi>=1.42.1 # Apache-2.0
kombu!=4.0.2,>=4.0.0 # BSD
diff --git a/test-requirements.txt b/test-requirements.txt
index 90882be..f6c0a00 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,4 +5,4 @@
# Hacking already pins down pep8, pyflakes and flake8
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
openstackdocstheme>=1.18.1 # Apache-2.0
-sphinx!=1.6.6,>=1.6.2 # BSD
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD