Merge "Fix the default value of stage_dir"
diff --git a/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml b/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml
new file mode 100644
index 0000000..6efe7e6
--- /dev/null
+++ b/releasenotes/notes/add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add show default quotas API to network quotas_client library.
+ This feature enables the possibility to show default network quotas for
+ a specified project.
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index acb0d90..87ce39d 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -24,7 +24,7 @@
"""Tests Nova Networks API that usually requires admin privileges.
API docs:
- http://developer.openstack.org/api-ref-compute-v2-ext.html#ext-os-networks
+ https://developer.openstack.org/api-ref/compute/#networks-os-networks-deprecated
"""
@classmethod
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 83fabdb..9759be7 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -185,11 +185,12 @@
cls.request_microversion)
v2_37_version = api_version_request.APIVersionRequest('2.37')
+ tenant_network = cls.get_tenant_network()
# NOTE(snikitin): since microversion v2.37 'networks' field is required
- if request_version >= v2_37_version and 'networks' not in kwargs:
+ if (request_version >= v2_37_version and 'networks' not in kwargs and
+ not tenant_network):
kwargs['networks'] = 'none'
- tenant_network = cls.get_tenant_network()
body, servers = compute.create_test_server(
cls.os_primary,
validatable,
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index cf4236d..57a28bf 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -80,6 +80,10 @@
non_default_quotas = self.admin_quotas_client.list_quotas()
for q in non_default_quotas['quotas']:
self.assertNotEqual(project_id, q['tenant_id'])
+ quota_set = self.admin_quotas_client.show_quotas(project_id)['quota']
+ default_quotas = self.admin_quotas_client.show_default_quotas(
+ project_id)['quota']
+ self.assertEqual(default_quotas, quota_set)
@decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb')
def test_quotas(self):
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 42bfcd6..6f9daa8 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -38,7 +38,6 @@
def setup_credentials(cls):
super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
- cls.alt_client = cls.os_alt.volumes_client_latest
@classmethod
def setup_clients(cls):
@@ -150,7 +149,8 @@
self.demo_tenant_id, params={'usage': True})['quota_set']
alt_quota = self.admin_quotas_client.show_quota_set(
- self.alt_client.tenant_id, params={'usage': True})['quota_set']
+ self.os_alt.volumes_client_latest.tenant_id,
+ params={'usage': True})['quota_set']
# Creates a volume transfer
transfer = self.transfer_client.create_volume_transfer(
@@ -164,14 +164,15 @@
# Verify volume transferred is available
waiters.wait_for_volume_resource_status(
- self.alt_client, volume['id'], 'available')
+ self.os_alt.volumes_client_latest, volume['id'], 'available')
# List of tenants quota usage post transfer
new_primary_quota = self.admin_quotas_client.show_quota_set(
self.demo_tenant_id, params={'usage': True})['quota_set']
new_alt_quota = self.admin_quotas_client.show_quota_set(
- self.alt_client.tenant_id, params={'usage': True})['quota_set']
+ self.os_alt.volumes_client_latest.tenant_id,
+ params={'usage': True})['quota_set']
# Verify tenants quota usage was updated
self.assertEqual(primary_quota['volumes']['in_use'] -
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index e93bcb5..b64face 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -27,11 +27,6 @@
credentials = ['primary', 'alt', 'admin']
- @classmethod
- def setup_clients(cls):
- super(VolumeTypesAccessTest, cls).setup_clients()
- cls.alt_client = cls.os_alt.volumes_client_latest
-
@decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
def test_volume_type_access_add(self):
# Creating a NON public volume type
@@ -70,10 +65,11 @@
# Adding volume type access for alt tenant
self.admin_volume_types_client.add_type_access(
- volume_type['id'], project=self.alt_client.tenant_id)
+ volume_type['id'],
+ project=self.os_alt.volumes_client_latest.tenant_id)
self.addCleanup(self.admin_volume_types_client.remove_type_access,
volume_type['id'],
- project=self.alt_client.tenant_id)
+ project=self.os_alt.volumes_client_latest.tenant_id)
# List tenant access for the given volume type
type_access_list = self.admin_volume_types_client.list_type_access(
@@ -88,5 +84,5 @@
# Validating the permitted tenants are the expected tenants
self.assertIn(self.volumes_client.tenant_id,
map(operator.itemgetter('project_id'), type_access_list))
- self.assertIn(self.alt_client.tenant_id,
+ self.assertIn(self.os_alt.volumes_client_latest.tenant_id,
map(operator.itemgetter('project_id'), type_access_list))
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index ea3bb5a..0a6b79d 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -101,20 +101,12 @@
cls.min_microversion,
CONF.volume.min_microversion))
- cls.snapshots = []
- cls.volumes = []
cls.image_ref = CONF.compute.image_ref
cls.flavor_ref = CONF.compute.flavor_ref
cls.build_interval = CONF.volume.build_interval
cls.build_timeout = CONF.volume.build_timeout
@classmethod
- def resource_cleanup(cls):
- cls.clear_snapshots()
- cls.clear_volumes()
- super(BaseVolumeTest, cls).resource_cleanup()
-
- @classmethod
def create_volume(cls, wait_until='available', **kwargs):
"""Wrapper utility that returns a test volume.
@@ -133,7 +125,9 @@
kwargs['name'] = name
volume = cls.volumes_client.create_volume(**kwargs)['volume']
- cls.volumes.append(volume)
+ cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+ cls.delete_volume, cls.volumes_client,
+ volume['id'])
waiters.wait_for_volume_resource_status(cls.volumes_client,
volume['id'], wait_until)
return volume
@@ -147,7 +141,8 @@
snapshot = cls.snapshots_client.create_snapshot(
volume_id=volume_id, **kwargs)['snapshot']
- cls.snapshots.append(snapshot['id'])
+ cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+ cls.delete_snapshot, snapshot['id'])
waiters.wait_for_volume_resource_status(cls.snapshots_client,
snapshot['id'], 'available')
return snapshot
@@ -176,14 +171,13 @@
client.delete_volume(volume_id)
client.wait_for_resource_deletion(volume_id)
- def delete_snapshot(self, snapshot_id, snapshots_client=None):
+ @classmethod
+ def delete_snapshot(cls, snapshot_id, snapshots_client=None):
"""Delete snapshot by the given client"""
if snapshots_client is None:
- snapshots_client = self.snapshots_client
+ snapshots_client = cls.snapshots_client
snapshots_client.delete_snapshot(snapshot_id)
snapshots_client.wait_for_resource_deletion(snapshot_id)
- if snapshot_id in self.snapshots:
- self.snapshots.remove(snapshot_id)
def attach_volume(self, server_id, volume_id):
"""Attach a volume to a server"""
@@ -197,31 +191,6 @@
self.addCleanup(self.servers_client.detach_volume, server_id,
volume_id)
- @classmethod
- def clear_volumes(cls):
- for volume in cls.volumes:
- try:
- cls.volumes_client.delete_volume(volume['id'])
- except Exception:
- pass
-
- for volume in cls.volumes:
- try:
- cls.volumes_client.wait_for_resource_deletion(volume['id'])
- except Exception:
- pass
-
- @classmethod
- def clear_snapshots(cls):
- for snapshot in cls.snapshots:
- test_utils.call_and_ignore_notfound_exc(
- cls.snapshots_client.delete_snapshot, snapshot)
-
- for snapshot in cls.snapshots:
- test_utils.call_and_ignore_notfound_exc(
- cls.snapshots_client.wait_for_resource_deletion,
- snapshot)
-
def create_server(self, wait_until='ACTIVE', **kwargs):
name = kwargs.pop(
'name',
@@ -303,26 +272,13 @@
cls.os_admin.volume_scheduler_stats_v2_client
@classmethod
- def resource_setup(cls):
- super(BaseVolumeAdminTest, cls).resource_setup()
-
- cls.qos_specs = []
- cls.volume_types = []
-
- @classmethod
- def resource_cleanup(cls):
- cls.clear_qos_specs()
- super(BaseVolumeAdminTest, cls).resource_cleanup()
- cls.clear_volume_types()
-
- @classmethod
def create_test_qos_specs(cls, name=None, consumer=None, **kwargs):
"""create a test Qos-Specs."""
name = name or data_utils.rand_name(cls.__name__ + '-QoS')
consumer = consumer or 'front-end'
qos_specs = cls.admin_volume_qos_client.create_qos(
name=name, consumer=consumer, **kwargs)['qos_specs']
- cls.qos_specs.append(qos_specs['id'])
+ cls.addClassResourceCleanup(cls.clear_qos_spec, qos_specs['id'])
return qos_specs
@classmethod
@@ -331,7 +287,7 @@
name = name or data_utils.rand_name(cls.__name__ + '-volume-type')
volume_type = cls.admin_volume_types_client.create_volume_type(
name=name, **kwargs)['volume_type']
- cls.volume_types.append(volume_type['id'])
+ cls.addClassResourceCleanup(cls.clear_volume_type, volume_type['id'])
return volume_type
def create_group_type(self, name=None, **kwargs):
@@ -345,22 +301,18 @@
return group_type
@classmethod
- def clear_qos_specs(cls):
- for qos_id in cls.qos_specs:
- test_utils.call_and_ignore_notfound_exc(
- cls.admin_volume_qos_client.delete_qos, qos_id)
+ def clear_qos_spec(cls, qos_id):
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_qos_client.delete_qos, qos_id)
- for qos_id in cls.qos_specs:
- test_utils.call_and_ignore_notfound_exc(
- cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id)
@classmethod
- def clear_volume_types(cls):
- for vol_type in cls.volume_types:
- test_utils.call_and_ignore_notfound_exc(
- cls.admin_volume_types_client.delete_volume_type, vol_type)
+ def clear_volume_type(cls, vol_type_id):
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_types_client.delete_volume_type, vol_type_id)
- for vol_type in cls.volume_types:
- test_utils.call_and_ignore_notfound_exc(
- cls.admin_volume_types_client.wait_for_resource_deletion,
- vol_type)
+ test_utils.call_and_ignore_notfound_exc(
+ cls.admin_volume_types_client.wait_for_resource_deletion,
+ vol_type_id)
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 1e240b8..552b231 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -40,7 +40,7 @@
backup_id)['restore']
# Delete backup
- self.addCleanup(self.volumes_client.delete_volume,
+ self.addCleanup(self.delete_volume, self.volumes_client,
restored_volume['volume_id'])
self.assertEqual(backup_id, restored_volume['backup_id'])
waiters.wait_for_volume_resource_status(self.backups_client,
@@ -59,8 +59,7 @@
"vol-meta2": "value2",
"vol-meta3": "value3"}
volume = self.create_volume(metadata=metadata)
- self.addCleanup(self.volumes_client.delete_volume,
- volume['id'])
+ self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
# Create a backup
backup_name = data_utils.rand_name(
@@ -109,8 +108,7 @@
"""
# Create a server
volume = self.create_volume()
- self.addCleanup(self.volumes_client.delete_volume,
- volume['id'])
+ self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
server = self.create_server()
# Attach volume to instance
self.attach_volume(server['id'], volume['id'])
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index b73bdf2..54052ae 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -80,11 +80,6 @@
# is implicit - Cinder calls Nova at that microversion, Tempest does not.
min_microversion = '3.42'
- @classmethod
- def setup_clients(cls):
- super(VolumesExtendAttachedTest, cls).setup_clients()
- cls.admin_servers_client = cls.os_admin.servers_client
-
def _find_extend_volume_instance_action(self, server_id):
actions = self.servers_client.list_instance_actions(
server_id)['instanceActions']
@@ -95,7 +90,7 @@
def _find_extend_volume_instance_action_finish_event(self, action):
# This has to be called by an admin client otherwise
# the events don't show up.
- action = self.admin_servers_client.show_instance_action(
+ action = self.os_admin.servers_client.show_instance_action(
action['instance_uuid'], action['request_id'])['instanceAction']
for event in action['events']:
if (event['event'] == 'compute_extend_volume' and
diff --git a/tempest/lib/services/compute/quota_classes_client.py b/tempest/lib/services/compute/quota_classes_client.py
index 0fe9868..64e06f4 100644
--- a/tempest/lib/services/compute/quota_classes_client.py
+++ b/tempest/lib/services/compute/quota_classes_client.py
@@ -35,8 +35,9 @@
def update_quota_class_set(self, quota_class_id, **kwargs):
"""Update the quota class's limits for one or more resources.
- # NOTE: Current api-site doesn't contain this API description.
- # LP: https://bugs.launchpad.net/nova/+bug/1602400
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#create-or-update-quotas-for-quota-class
"""
post_body = json.dumps({'quota_class_set': kwargs})
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index daf4bc0..12df895 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -28,8 +28,8 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref-compute-v2.1.html/#show-a-quota
- http://developer.openstack.org/api-ref-compute-v2.1.html/#show-the-detail-of-quota
+ https://developer.openstack.org/api-ref/compute/#show-a-quota
+ https://developer.openstack.org/api-ref/compute/#show-the-detail-of-quota
"""
params = {}
@@ -49,7 +49,10 @@
return rest_client.ResponseBody(resp, body)
def show_default_quota_set(self, tenant_id):
- """List the default quota set for a tenant."""
+ """List the default quota set for a tenant.
+
+ https://developer.openstack.org/api-ref/compute/#list-default-quotas-for-tenant
+ """
url = 'os-quota-sets/%s/defaults' % tenant_id
resp, body = self.get(url)
@@ -79,7 +82,10 @@
return rest_client.ResponseBody(resp, body)
def delete_quota_set(self, tenant_id):
- """Delete the tenant's quota set."""
+ """Delete the tenant's quota set.
+
+ https://developer.openstack.org/api-ref/compute/#revert-quotas-to-defaults
+ """
resp, body = self.delete('os-quota-sets/%s' % tenant_id)
self.validate_response(schema.delete_quota, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 598d5a6..09bccab 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -126,7 +126,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref-compute-v2.1.html#showServer
+ https://developer.openstack.org/api-ref/compute/#show-server-details
"""
resp, body = self.get("servers/%s" % server_id)
body = json.loads(body)
@@ -321,7 +321,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://developer.openstack.org/api-ref/compute/#create-or-replace-metadata-items
+ https://developer.openstack.org/api-ref/compute/#replace-metadata-items
"""
if no_metadata_field:
post_body = ""
@@ -338,7 +338,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://developer.openstack.org/api-ref/compute/#update-metadata-items
+ https://developer.openstack.org/api-ref/compute/#create-or-update-metadata-items
"""
post_body = json.dumps({'metadata': meta})
resp, body = self.post('servers/%s/metadata' % server_id,
@@ -609,9 +609,7 @@
For a full list of available parameters, please refer to the official
API reference:
- TODO (markus_z) The api-ref for that isn't yet available, update this
- here when the docs in Nova are updated. The old API is at
- http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action
+ https://developer.openstack.org/api-ref/compute/#create-remote-console
"""
param = {
'remote_console': {
@@ -722,7 +720,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action
+ https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action-deprecated
"""
return self.action(server_id, "os-getVNCConsole",
schema.get_vnc_console, **kwargs)
@@ -732,7 +730,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action
+ https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action-deprecated
"""
return self.action(server_id, 'addFixedIp', **kwargs)
@@ -741,7 +739,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action
+ https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action-deprecated
"""
return self.action(server_id, 'removeFixedIp', **kwargs)
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index fdd3d6b..f23af88 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -41,3 +41,8 @@
def list_quotas(self, **filters):
uri = '/quotas'
return self.list_resources(uri, **filters)
+
+ def show_default_quotas(self, tenant_id):
+ """List default quotas for a project."""
+ uri = '/quotas/%s/default' % tenant_id
+ return self.show_resource(uri)
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
index d40d2d9..733b1ac 100644
--- a/tempest/lib/services/volume/v2/quota_classes_client.py
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -26,8 +26,9 @@
def show_quota_class_set(self, quota_class_id):
"""List quotas for a quota class.
- TODO: Current api-site doesn't contain this API description.
- LP: https://bugs.launchpad.net/nova/+bug/1602400
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-quota-classes
"""
url = 'os-quota-class-sets/%s' % quota_class_id
resp, body = self.get(url)
@@ -38,8 +39,9 @@
def update_quota_class_set(self, quota_class_id, **kwargs):
"""Update quotas for a quota class.
- TODO: Current api-site doesn't contain this API description.
- LP: https://bugs.launchpad.net/nova/+bug/1602400
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/index.html#update-quota-classes
"""
url = 'os-quota-class-sets/%s' % quota_class_id
put_body = json.dumps({'quota_class_set': kwargs})
diff --git a/tempest/tests/lib/services/network/test_quotas_client.py b/tempest/tests/lib/services/network/test_quotas_client.py
index e76bc9c..5a09911 100644
--- a/tempest/tests/lib/services/network/test_quotas_client.py
+++ b/tempest/tests/lib/services/network/test_quotas_client.py
@@ -38,6 +38,20 @@
]
}
+ FAKE_PROJECT_QUOTAS = {
+ "quota": {
+ "floatingip": 50,
+ "network": 10,
+ "port": 50,
+ "rbac_policy": -1,
+ "router": 10,
+ "security_group": 10,
+ "security_group_rule": 100,
+ "subnet": 10,
+ "subnetpool": -1
+ }
+ }
+
FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978"
def setUp(self):
@@ -58,7 +72,16 @@
self.check_service_client_function(
self.quotas_client.show_quotas,
"tempest.lib.common.rest_client.RestClient.get",
- {"quota": self.FAKE_QUOTAS["quotas"][0]},
+ self.FAKE_PROJECT_QUOTAS,
+ bytes_body,
+ 200,
+ tenant_id=self.FAKE_QUOTA_TENANT_ID)
+
+ def _test_show_default_quotas(self, bytes_body=False):
+ self.check_service_client_function(
+ self.quotas_client.show_default_quotas,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_PROJECT_QUOTAS,
bytes_body,
200,
tenant_id=self.FAKE_QUOTA_TENANT_ID)
@@ -67,7 +90,7 @@
self.check_service_client_function(
self.quotas_client.update_quotas,
"tempest.lib.common.rest_client.RestClient.put",
- {"quota": self.FAKE_QUOTAS["quotas"][0]},
+ self.FAKE_PROJECT_QUOTAS,
bytes_body,
200,
tenant_id=self.FAKE_QUOTA_TENANT_ID)
@@ -92,6 +115,12 @@
def test_show_quotas_with_bytes_body(self):
self._test_show_quotas(bytes_body=True)
+ def test_show_default_quotas_with_str_body(self):
+ self._test_show_default_quotas()
+
+ def test_show_default_quotas_with_bytes_body(self):
+ self._test_show_default_quotas(bytes_body=True)
+
def test_update_quotas_with_str_body(self):
self._test_update_quotas()
diff --git a/tox.ini b/tox.ini
index c0df8a6..892b6f4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -123,6 +123,10 @@
tempest run --serial --regex '\[.*\bsmoke\b.*\]' {posargs}
[testenv:venv]
+deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/doc/requirements.txt
commands = {posargs}
[testenv:venv-tempest]