Merge "Revert "Skip test_server_connectivity_cold_migration_revert until fixed""
diff --git a/.zuul.yaml b/.zuul.yaml
index 462501e..42911a3 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -172,6 +172,41 @@
c-bak: false
- job:
+ name: tempest-integrated-networking
+ parent: devstack-tempest
+ branches: ^(?!stable/ocata).*$
+ description: |
+ This job runs integration tests for networking. This is subset of
+ 'tempest-full' job and run only Neutron and Nova related tests.
+ This is meant to be run on neutron gate only.
+ vars:
+ tox_envlist: integrated-network
+ devstack_localrc:
+ USE_PYTHON3: true
+ FORCE_CONFIG_DRIVE: true
+ devstack_services:
+ s-account: false
+ s-container: false
+ s-object: false
+ s-proxy: false
+ c-bak: false
+
+- job:
+ name: tempest-integrated-storage
+ parent: devstack-tempest
+ branches: ^(?!stable/ocata).*$
+ description: |
+ This job runs integration tests for image & block storage. This is
+ subset of 'tempest-full' job and run Cinder, Glance, Swift and Nova
+ related tests. This is meant to be run on Cinder and Glance gate only.
+ vars:
+ tox_envlist: integrated-storage
+ devstack_localrc:
+ USE_PYTHON3: true
+ FORCE_CONFIG_DRIVE: true
+ ENABLE_VOLUME_MULTIATTACH: true
+
+- job:
name: tempest-full-py3-ipv6
parent: devstack-tempest-ipv6
# This currently works from stable/pike on.
@@ -273,6 +308,21 @@
devstack_localrc:
CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
ENABLE_VOLUME_MULTIATTACH: true
+ devstack_plugins:
+ neutron: https://opendev.org/openstack/neutron
+ devstack_services:
+ neutron-placement: true
+ neutron-qos: true
+ devstack_local_conf:
+ post-config:
+ "/$NEUTRON_CORE_PLUGIN_CONF":
+ ovs:
+ bridge_mappings: public:br-ex
+ resource_provider_bandwidths: br-ex:1000000:1000000
+ test-config:
+ $TEMPEST_CONFIG:
+ network-feature-enabled:
+ qos_placement_physnet: public
tempest_concurrency: 2
group-vars:
# NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
@@ -389,7 +439,6 @@
- opendev.org/openstack/monasca-log-api
- opendev.org/openstack/monasca-tempest-plugin
- opendev.org/openstack/murano-tempest-plugin
- - opendev.org/x/networking-ansible
- opendev.org/openstack/networking-bgpvpn
- opendev.org/x/networking-cisco
- opendev.org/x/networking-fortinet
@@ -418,6 +467,7 @@
- opendev.org/openstack/senlin-tempest-plugin
- opendev.org/openstack/solum-tempest-plugin
- opendev.org/x/tap-as-a-service
+ - opendev.org/x/tap-as-a-service-tempest-plugin
- opendev.org/openstack/telemetry-tempest-plugin
- opendev.org/openstack/tempest-horizon
- opendev.org/x/tobiko
@@ -428,6 +478,7 @@
- opendev.org/openstack/vitrage-tempest-plugin
- opendev.org/x/vmware-nsx-tempest-plugin
- opendev.org/openstack/watcher-tempest-plugin
+ - opendev.org/x/whitebox-tempest-plugin
- opendev.org/openstack/zaqar-tempest-plugin
- opendev.org/openstack/zun-tempest-plugin
@@ -482,6 +533,37 @@
ENABLE_FILE_INJECTION: true
DATABASE_TYPE: postgresql
+- project-template:
+ name: integrated-gate-networking
+ description: |
+ Run the python3 Tempest network integration tests (Nova and Neutron related)
+ in check and gate for the neutron integrated gate. This is meant to be
+ run on neutron gate only.
+ check:
+ jobs:
+ - grenade-py3
+ - tempest-integrated-networking
+ gate:
+ jobs:
+ - grenade-py3
+ - tempest-integrated-networking
+
+- project-template:
+ name: integrated-gate-storage
+ description: |
+ Run the python3 Tempest image & block storage integration tests
+ (Cinder, Glance, Swift and Nova related) in check and gate
+ for the neutron integrated gate. This is meant to be
+ run on Cinder and Glance gate only.
+ check:
+ jobs:
+ - grenade-py3
+ - tempest-integrated-storage
+ gate:
+ jobs:
+ - grenade-py3
+ - tempest-integrated-storage
+
- project:
templates:
- check-requirements
diff --git a/README.rst b/README.rst
index e8206ee..841fae6 100644
--- a/README.rst
+++ b/README.rst
@@ -119,6 +119,17 @@
will run the same set of tests as the default gate jobs. Or you can
use `unittest`_ compatible test runners such as `testr`_, `pytest`_ etc.
+ Tox also contains several existing job configurations. For example::
+
+ $ tox -e full
+
+ which will run the same set of tests as the OpenStack gate. (it's exactly how
+ the gate invokes Tempest) Or::
+
+ $ tox -e smoke
+
+ to run the tests tagged as smoke.
+
.. _unittest: https://docs.python.org/3/library/unittest.html
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
.. _stestr: https://stestr.readthedocs.org/en/latest/MANUAL.html
@@ -270,14 +281,3 @@
To run one single test serially ::
$ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
-
-Tox also contains several existing job configurations. For example::
-
- $ tox -e full
-
-which will run the same set of tests as the OpenStack gate. (it's exactly how
-the gate invokes Tempest) Or::
-
- $ tox -e smoke
-
-to run the tests tagged as smoke.
diff --git a/doc/source/data/tempest-blacklisted-plugins-registry.header b/doc/source/data/tempest-blacklisted-plugins-registry.header
new file mode 100644
index 0000000..6b6af11
--- /dev/null
+++ b/doc/source/data/tempest-blacklisted-plugins-registry.header
@@ -0,0 +1,7 @@
+Blacklisted Plugins
+===================
+
+List of Tempest plugin projects that are stale or unmaintained for a long
+time (6 months or more). They can be moved out of blacklist state once one
+of the relevant patches gets merged:
+https://review.opendev.org/#/q/topic:tempest-sanity-gate+%28status:open%29
diff --git a/releasenotes/notes/QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml b/releasenotes/notes/QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml
new file mode 100644
index 0000000..b66ea3a
--- /dev/null
+++ b/releasenotes/notes/QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml
@@ -0,0 +1,25 @@
+---
+features:
+ - |
+ Add ``qos-policies`` and ``qos-minimum-bandwidth-rule`` clients
+ to Tempest to make possible the testing of the placement based
+ bandwidth allocation feature.
+ The following API calls are available for tempest from now:
+
+ ``QoS policies`` client:
+
+ * GET /qos/policies
+ * POST /qos/policies
+ * GET /qos/policies/{policy_id}
+ * PUT /qos/policies/{policy_id}
+ * DELETE /qos/policies/{policy_id}
+
+
+ ``QoS minimum bandwidth rules`` client:
+
+ * GET qos/policies/{policy_id}/minimum_bandwidth_rules
+ * POST /qos/policies/{policy_id}/minimum_bandwidth_rules
+ * GET qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
+ * PUT qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
+ * DELETE /qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
+
diff --git a/releasenotes/notes/bug-1647999-7aeda50a8d082d4c.yaml b/releasenotes/notes/bug-1647999-7aeda50a8d082d4c.yaml
new file mode 100644
index 0000000..384f916
--- /dev/null
+++ b/releasenotes/notes/bug-1647999-7aeda50a8d082d4c.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ A new parameter, compute/compute_volume_common_az is introduced to
+ specify availability zone where tempest creates instances and volumes
+ for scenario tests, to allow us to run scenario tests in the deployment
+ which has multiple availability zones and cinder/cross_az_attach in
+ nova.conf is set to False.
diff --git a/releasenotes/notes/config_image_certificate_compute_feature-c56efb520d54aff5.yaml b/releasenotes/notes/config_image_certificate_compute_feature-c56efb520d54aff5.yaml
new file mode 100644
index 0000000..8475f50
--- /dev/null
+++ b/releasenotes/notes/config_image_certificate_compute_feature-c56efb520d54aff5.yaml
@@ -0,0 +1,8 @@
+---
+other:
+ - |
+ New configuration options ``[compute]/certified_image_ref`` and
+ ``[compute]/certified_image_trusted_certs`` have been introduced. These
+ are required in order to run the ``ServerShowV263Test`` test and allow a
+ signed image with the required img_signature_* properties set along
+ with a list of trusted certificates to be used during the test.
diff --git a/releasenotes/notes/remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml b/releasenotes/notes/remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml
new file mode 100644
index 0000000..fa21afd
--- /dev/null
+++ b/releasenotes/notes/remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml
@@ -0,0 +1,8 @@
+upgrade:
+ - |
+ Remove deprecated config option ``endpoint_type`` from
+ ``identity`` group. Use ``v2_public_endpoint_type`` from
+ ``identity`` group instead.
+ Remove deprecated config option ``tenant_isolation_domain_name``
+ from ``auth`` group. Use ``default_credentials_domain_name`` from
+ ``auth`` group instead.
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index e71e642..aaf7a5a 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -487,6 +487,9 @@
kwargs['display_name'] = vol_name
if image_ref is not None:
kwargs['imageRef'] = image_ref
+ if CONF.compute.compute_volume_common_az:
+ kwargs.setdefault('availability_zone',
+ CONF.compute.compute_volume_common_az)
volume = cls.volumes_client.create_volume(**kwargs)['volume']
cls.addClassResourceCleanup(
cls.volumes_client.wait_for_resource_deletion, volume['id'])
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 3a474e6..235049a 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -70,9 +70,7 @@
self.assertEqual(min_img_ram, image['min_ram'])
# Try to create server with flavor of insufficient ram size
- self.assertRaisesRegex(lib_exc.BadRequest,
- "Flavor's memory is too small for "
- "requested image",
- self.create_test_server,
- image_id=image['id'],
- flavor=flavor['id'])
+ self.assertRaises(lib_exc.BadRequest,
+ self.create_test_server,
+ image_id=image['id'],
+ flavor=flavor['id'])
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index eeb58d6..3789aa0 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -15,6 +15,7 @@
import time
+from oslo_log import log
import six
from tempest.api.compute import base
@@ -30,6 +31,8 @@
CONF = config.CONF
+LOG = log.getLogger(__name__)
+
class AttachInterfacesTestBase(base.BaseV2ComputeTest):
@@ -364,10 +367,34 @@
self.servers_client.add_fixed_ip(server['id'], networkId=network_id)
# Wait for the ips count to increase by one.
+ def _get_server_floating_ips():
+ _floating_ips = []
+ _server = self.os_primary.servers_client.show_server(
+ server['id'])['server']
+ for _ip_set in _server['addresses']:
+ for _ip in _server['addresses'][_ip_set]:
+ if _ip['OS-EXT-IPS:type'] == 'floating':
+ _floating_ips.append(_ip['addr'])
+ return _floating_ips
+
def _wait_for_ip_increase():
_addresses = self.os_primary.servers_client.list_addresses(
server['id'])['addresses']
- return len(list(_addresses.values())[0]) == original_ip_count + 1
+ _ips = [addr['addr'] for addr in list(_addresses.values())[0]]
+ LOG.debug("Wait for IP increase. All IPs still associated to "
+ "the server %(id)s: %(ips)s",
+ {'id': server['id'], 'ips': _ips})
+ if len(_ips) == original_ip_count + 1:
+ return True
+ elif len(_ips) == original_ip_count:
+ return False
+ # If not, lets remove any floating IP from the list and check again
+ _fips = _get_server_floating_ips()
+ _ips = [_ip for _ip in _ips if _ip not in _fips]
+ LOG.debug("Wait for IP increase. Fixed IPs still associated to "
+ "the server %(id)s: %(ips)s",
+ {'id': server['id'], 'ips': _ips})
+ return len(_ips) == original_ip_count + 1
if not test_utils.call_until_true(
_wait_for_ip_increase, CONF.compute.build_timeout,
@@ -394,7 +421,19 @@
def _wait_for_ip_decrease():
_addresses = self.os_primary.servers_client.list_addresses(
server['id'])['addresses']
- return len(list(_addresses.values())[0]) == original_ip_count
+ _ips = [addr['addr'] for addr in list(_addresses.values())[0]]
+ LOG.debug("Wait for IP decrease. All IPs still associated to "
+ "the server %(id)s: %(ips)s",
+ {'id': server['id'], 'ips': _ips})
+ if len(_ips) == original_ip_count:
+ return True
+ # If not, lets remove any floating IP from the list and check again
+ _fips = _get_server_floating_ips()
+ _ips = [_ip for _ip in _ips if _ip not in _fips]
+ LOG.debug("Wait for IP decrease. Fixed IPs still associated to "
+ "the server %(id)s: %(ips)s",
+ {'id': server['id'], 'ips': _ips})
+ return len(_ips) == original_ip_count
if not test_utils.call_until_true(
_wait_for_ip_decrease, CONF.compute.build_timeout,
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index daf6a06..50ffb21 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -16,6 +16,7 @@
import struct
import six
+import six.moves.urllib.parse as urlparse
import urllib3
from tempest.api.compute import base
@@ -73,8 +74,9 @@
'initial call: ' + six.text_type(resp.status))
# Do some basic validation to make sure it is an expected HTML document
resp_data = resp.data.decode()
- self.assertIn('<html>', resp_data,
- 'Not a valid html document in the response.')
+ # This is needed in the case of example: <html lang="en">
+ self.assertRegex(resp_data, '<html.*>',
+ 'Not a valid html document in the response.')
self.assertIn('</html>', resp_data,
'Not a valid html document in the response.')
# Just try to make sure we got JavaScript back for noVNC, since we
@@ -204,7 +206,18 @@
type='novnc')['console']
self.assertEqual('novnc', body['type'])
# Do the WebSockify HTTP Request to novncproxy with a bad token
- url = body['url'].replace('token=', 'token=bad')
+ parts = urlparse.urlparse(body['url'])
+ qparams = urlparse.parse_qs(parts.query)
+ if 'path' in qparams:
+ qparams['path'] = urlparse.unquote(qparams['path'][0]).replace(
+ 'token=', 'token=bad')
+ elif 'token' in qparams:
+ qparams['token'] = 'bad' + qparams['token'][0]
+ new_query = urlparse.urlencode(qparams)
+ new_parts = urlparse.ParseResult(parts.scheme, parts.netloc,
+ parts.path, parts.params, new_query,
+ parts.fragment)
+ url = urlparse.urlunparse(new_parts)
self._websocket = compute.create_websocket(url)
# Make sure the novncproxy rejected the connection and closed it
data = self._websocket.receive_frame()
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index f6c3e73..d47ff51 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -417,10 +417,7 @@
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
# Make sure everything still looks OK.
server = self.client.show_server(self.server_id)['server']
- # The flavor id is not returned in the server response after
- # microversion 2.46 so handle that gracefully.
- if server['flavor'].get('id'):
- self.assertEqual(self.flavor_ref, server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref, server['flavor'])
attached_volumes = server['os-extended-volumes:volumes_attached']
self.assertEqual(1, len(attached_volumes))
self.assertEqual(volume['id'], attached_volumes[0]['id'])
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index e8b1161..76d65dd 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -186,10 +186,17 @@
min_microversion = '2.63'
max_microversion = 'latest'
+ @testtools.skipUnless(CONF.compute.certified_image_ref,
+ '``[compute]/certified_image_ref`` required to test '
+ 'image certificate validation.')
+ @testtools.skipUnless(CONF.compute.certified_image_trusted_certs,
+ '``[compute]/certified_image_trusted_certs`` '
+ 'required to test image certificate validation.')
@decorators.idempotent_id('71b8e3d5-11d2-494f-b917-b094a4afed3c')
def test_show_update_rebuild_list_server(self):
- trusted_certs = ['test-cert-1', 'test-cert-2']
+ trusted_certs = CONF.compute.certified_image_trusted_certs
server = self.create_test_server(
+ image_id=CONF.compute.certified_image_ref,
trusted_image_certificates=trusted_certs,
wait_until='ACTIVE')
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 2cd8906..366d6a0 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -44,11 +44,14 @@
cls.addClassResourceCleanup(
cls.services_client.delete_service, service['id'])
- region = data_utils.rand_name('region')
+ region_name = data_utils.rand_name('region')
url = data_utils.rand_url()
endpoint = cls.client.create_endpoint(
service_id=cls.service_ids[i], interface=interfaces[i],
- url=url, region=region, enabled=True)['endpoint']
+ url=url, region=region_name, enabled=True)['endpoint']
+ region = cls.regions_client.show_region(region_name)['region']
+ cls.addClassResourceCleanup(
+ cls.regions_client.delete_region, region['id'])
cls.addClassResourceCleanup(
cls.client.delete_endpoint, endpoint['id'])
cls.setup_endpoint_ids.append(endpoint['id'])
@@ -108,17 +111,19 @@
@decorators.idempotent_id('0e2446d2-c1fd-461b-a729-b9e73e3e3b37')
def test_create_list_show_delete_endpoint(self):
- region = data_utils.rand_name('region')
+ region_name = data_utils.rand_name('region')
url = data_utils.rand_url()
interface = 'public'
endpoint = self.client.create_endpoint(service_id=self.service_ids[0],
interface=interface,
- url=url, region=region,
+ url=url, region=region_name,
enabled=True)['endpoint']
+ region = self.regions_client.show_region(region_name)['region']
+ self.addCleanup(self.regions_client.delete_region, region['id'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.client.delete_endpoint, endpoint['id'])
# Asserting Create Endpoint response body
- self.assertEqual(region, endpoint['region'])
+ self.assertEqual(region_name, endpoint['region'])
self.assertEqual(url, endpoint['url'])
# Checking if created endpoint is present in the list of endpoints
@@ -133,7 +138,7 @@
self.assertEqual(self.service_ids[0], fetched_endpoint['service_id'])
self.assertEqual(interface, fetched_endpoint['interface'])
self.assertEqual(url, fetched_endpoint['url'])
- self.assertEqual(region, fetched_endpoint['region'])
+ self.assertEqual(region_name, fetched_endpoint['region'])
self.assertEqual(True, fetched_endpoint['enabled'])
# Deleting the endpoint created in this method
@@ -161,28 +166,33 @@
self.addCleanup(self.services_client.delete_service, service2['id'])
# Creating an endpoint so as to check update endpoint with new values
- region1 = data_utils.rand_name('region')
+ region1_name = data_utils.rand_name('region')
url1 = data_utils.rand_url()
interface1 = 'public'
endpoint_for_update = (
self.client.create_endpoint(service_id=self.service_ids[0],
interface=interface1,
- url=url1, region=region1,
+ url=url1, region=region1_name,
enabled=True)['endpoint'])
- self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
+ region1 = self.regions_client.show_region(region1_name)['region']
+ self.addCleanup(self.regions_client.delete_region, region1['id'])
# Updating endpoint with new values
- region2 = data_utils.rand_name('region')
+ region2_name = data_utils.rand_name('region')
url2 = data_utils.rand_url()
interface2 = 'internal'
endpoint = self.client.update_endpoint(endpoint_for_update['id'],
service_id=service2['id'],
interface=interface2,
- url=url2, region=region2,
+ url=url2, region=region2_name,
enabled=False)['endpoint']
+ region2 = self.regions_client.show_region(region2_name)['region']
+ self.addCleanup(self.regions_client.delete_region, region2['id'])
+ self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
+
# Asserting if the attributes of endpoint are updated
self.assertEqual(service2['id'], endpoint['service_id'])
self.assertEqual(interface2, endpoint['interface'])
self.assertEqual(url2, endpoint['url'])
- self.assertEqual(region2, endpoint['region'])
+ self.assertEqual(region2_name, endpoint['region'])
self.assertEqual(False, endpoint['enabled'])
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 4c3eb1c..164b577 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -70,14 +70,16 @@
def _assert_update_raises_bad_request(self, enabled):
# Create an endpoint
- region1 = data_utils.rand_name('region')
+ region1_name = data_utils.rand_name('region')
url1 = data_utils.rand_url()
interface1 = 'public'
endpoint_for_update = (
self.client.create_endpoint(service_id=self.service_id,
interface=interface1,
- url=url1, region=region1,
+ url=url1, region=region1_name,
enabled=True)['endpoint'])
+ region1 = self.regions_client.show_region(region1_name)['region']
+ self.addCleanup(self.regions_client.delete_region, region1['id'])
self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
self.assertRaises(lib_exc.BadRequest, self.client.update_endpoint,
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 158dfb3..2eea860 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -15,6 +15,8 @@
import time
+import testtools
+
from tempest.api.identity import base
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -78,6 +80,10 @@
self.non_admin_users_client.auth_provider.set_auth()
@decorators.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
+ @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+ 'Skipped because environment has an '
+ 'immutable user source and solely '
+ 'provides read-only access to users.')
def test_user_update_own_password(self):
old_pass = self.creds.password
old_token = self.non_admin_users_client.token
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 13b5161..d4e7612 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -77,6 +77,10 @@
self.non_admin_users_client.auth_provider.set_auth()
@decorators.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
+ @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+ 'Skipped because environment has an '
+ 'immutable user source and solely '
+ 'provides read-only access to users.')
def test_user_update_own_password(self):
old_pass = self.creds.password
old_token = self.non_admin_client.token
@@ -102,6 +106,10 @@
@testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
'Security compliance not available.')
@decorators.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516')
+ @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+ 'Skipped because environment has an '
+ 'immutable user source and solely '
+ 'provides read-only access to users.')
def test_password_history_check_self_service_api(self):
old_pass = self.creds.password
new_pass1 = data_utils.rand_password()
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 64fe29a..1bfd075 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -124,6 +124,10 @@
name = data_utils.rand_name(cls.__name__ + '-Volume')
kwargs['name'] = name
+ if CONF.compute.compute_volume_common_az:
+ kwargs.setdefault('availability_zone',
+ CONF.compute.compute_volume_common_az)
+
volume = cls.volumes_client.create_volume(**kwargs)['volume']
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.delete_volume, cls.volumes_client,
diff --git a/tempest/clients.py b/tempest/clients.py
index 0506646..f7a83be 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -69,6 +69,8 @@
self.network_versions_client = self.network.NetworkVersionsClient()
self.service_providers_client = self.network.ServiceProvidersClient()
self.tags_client = self.network.TagsClient()
+ self.qos_client = self.network.QosClient()
+ self.qos_min_bw_client = self.network.QosMinimumBandwidthRulesClient()
def _set_image_clients(self):
if CONF.service_available.glance:
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index e6db2e9..f0d7264 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -94,6 +94,8 @@
class TempestCleanup(command.Command):
+ GOT_EXCEPTIONS = []
+
def take_action(self, parsed_args):
try:
self.init(parsed_args)
@@ -103,6 +105,8 @@
LOG.exception("Failure during cleanup")
traceback.print_exc()
raise
+ if self.GOT_EXCEPTIONS:
+ raise Exception(self.GOT_EXCEPTIONS)
def init(self, parsed_args):
cleanup_service.init_conf()
@@ -159,7 +163,8 @@
'is_dry_run': is_dry_run,
'saved_state_json': self.json_data,
'is_preserve': is_preserve,
- 'is_save_state': is_save_state}
+ 'is_save_state': is_save_state,
+ 'got_exceptions': self.GOT_EXCEPTIONS}
for service in self.global_services:
svc = service(admin_mgr, **kwargs)
svc.run()
@@ -200,7 +205,8 @@
'saved_state_json': self.json_data,
'is_preserve': is_preserve,
'is_save_state': False,
- 'project_id': project_id}
+ 'project_id': project_id,
+ 'got_exceptions': self.GOT_EXCEPTIONS}
for service in self.project_services:
svc = service(mgr, **kwargs)
svc.run()
@@ -300,7 +306,8 @@
'is_dry_run': False,
'saved_state_json': data,
'is_preserve': False,
- 'is_save_state': True}
+ 'is_save_state': True,
+ 'got_exceptions': self.GOT_EXCEPTIONS}
for service in self.global_services:
svc = service(admin_mgr, **kwargs)
svc.run()
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 104958a..ccceb34 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -22,6 +22,7 @@
from tempest.common import utils
from tempest.common.utils import net_info
from tempest import config
+from tempest.lib import exceptions
LOG = logging.getLogger(__name__)
CONF = config.CONF
@@ -127,12 +128,23 @@
pass
def run(self):
- if self.is_dry_run:
- self.dry_run()
- elif self.is_save_state:
- self.save_state()
- else:
- self.delete()
+ try:
+ if self.is_dry_run:
+ self.dry_run()
+ elif self.is_save_state:
+ self.save_state()
+ else:
+ self.delete()
+ except exceptions.NotImplemented as exc:
+ # Many OpenStack services use extensions logic to implement the
+ # features or resources. Tempest cleanup tries to clean up the test
+ # resources without having much logic of extensions checks etc.
+ # If any of the extension is missing then, service will return
+ # NotImplemented error.
+ msg = ("Got NotImplemented error in %s, full exception: %s" %
+ (str(self.__class__), str(exc)))
+ LOG.exception(msg)
+ self.got_exceptions.append(msg)
class SnapshotService(BaseService):
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 77d4496..f9ca2c7 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -19,11 +19,11 @@
==============
Tempest run has several options:
- * **--regex/-r**: This is a selection regex like what stestr uses. It will run
- any tests that match on re.match() with the regex
- * **--smoke/-s**: Run all the tests tagged as smoke
- * **--black-regex**: It allows to do simple test exclusion via passing a
- rejection/black regexp
+* ``--regex/-r``: This is a selection regex like what stestr uses. It will run
+ any tests that match on re.match() with the regex
+* ``--smoke/-s``: Run all the tests tagged as smoke
+* ``--black-regex``: It allows to do simple test exclusion via passing a
+ rejection/black regexp
There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
let you pass a filepath to tempest run with the file format being a line
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 1489e60..cd85ede 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -167,6 +167,9 @@
params = {'name': volume_name,
'imageRef': image_id,
'size': CONF.volume.volume_size}
+ if CONF.compute.compute_volume_common_az:
+ params.setdefault('availability_zone',
+ CONF.compute.compute_volume_common_az)
volume = volumes_client.create_volume(**params)
try:
waiters.wait_for_volume_resource_status(volumes_client,
@@ -193,6 +196,9 @@
# to be specified.
image_id = ''
+ if CONF.compute.compute_volume_common_az:
+ kwargs.setdefault('availability_zone',
+ CONF.compute.compute_volume_common_az)
body = clients.servers_client.create_server(name=name, imageRef=image_id,
flavorRef=flavor,
**kwargs)
@@ -392,8 +398,11 @@
def _upgrade(self, url):
"""Upgrade the HTTP connection to a WebSocket and verify."""
- # The real request goes to the /websockify URI always
- reqdata = 'GET /websockify HTTP/1.1\r\n'
+ # It is possible to pass the path as a query parameter in the request,
+ # so use it if present
+ qparams = urlparse.parse_qs(url.query)
+ path = qparams['path'][0] if 'path' in qparams else '/websockify'
+ reqdata = 'GET %s HTTP/1.1\r\n' % path
reqdata += 'Host: %s' % url.hostname
# Add port only if we have one specified
if url.port:
@@ -402,7 +411,7 @@
reqdata += '\r\n'
# Tell the HTTP Server to Upgrade the connection to a WebSocket
reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
- # The token=xxx is sent as a Cookie not in the URI
+ # The token=xxx is sent as a Cookie not in the URI for noVNC < v1.1.0
reqdata += 'Cookie: %s\r\n' % url.query
# Use a hard-coded WebSocket key since a test program
reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 77ec0f8..11f3bf9 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -121,7 +121,9 @@
'/'.join((server_status, str(task_state))),
time.time() - start_time)
if server_status == 'ERROR' and not ignore_error:
- raise lib_exc.DeleteErrorException(resource_id=server_id)
+ raise lib_exc.DeleteErrorException(
+ "Server %s failed to delete and is in ERROR status" %
+ server_id)
if int(time.time()) - start_time >= client.build_timeout:
raise lib_exc.TimeoutException
@@ -202,6 +204,8 @@
resource_name=resource_name, resource_id=resource_id)
if resource_name == 'volume' and resource_status == 'error_restoring':
raise exceptions.VolumeRestoreErrorException(volume_id=resource_id)
+ if resource_status == 'error_extending' and resource_status != status:
+ raise exceptions.VolumeExtendErrorException(volume_id=resource_id)
if int(time.time()) - start >= client.build_timeout:
message = ('%s %s failed to reach %s status (current %s) '
diff --git a/tempest/config.py b/tempest/config.py
index 6830148..c50ebbe 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -69,10 +69,7 @@
cfg.StrOpt('default_credentials_domain_name',
default='Default',
help="Default domain used when getting v3 credentials. "
- "This is the name keystone uses for v2 compatibility.",
- deprecated_opts=[cfg.DeprecatedOpt(
- 'tenant_isolation_domain_name',
- group='auth')]),
+ "This is the name keystone uses for v2 compatibility."),
cfg.BoolOpt('create_isolated_networks',
default=True,
help="If use_dynamic_credentials is set to True and Neutron "
@@ -139,9 +136,7 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The public endpoint type to use for OpenStack Identity "
- "(Keystone) API v2",
- deprecated_opts=[cfg.DeprecatedOpt('endpoint_type',
- group='identity')]),
+ "(Keystone) API v2"),
cfg.StrOpt('v3_endpoint_type',
default='adminURL',
choices=['public', 'admin', 'internal',
@@ -276,6 +271,17 @@
help="Valid secondary image reference to be used in tests. "
"This is a required option, but if only one image is "
"available duplicate the value of image_ref above"),
+ cfg.StrOpt('certified_image_ref',
+ help="Valid image reference to be used in image certificate "
+ "validation tests when enabled. This image must also "
+ "have the required img_signature_* properties set. "
+ "Additional details available within the following Nova "
+ "documentation: https://docs.openstack.org/nova/latest/"
+ "user/certificate-validation.html"),
+ cfg.ListOpt('certified_image_trusted_certs',
+ help="A list of trusted certificates to be used when the "
+ "image certificate validation compute feature is "
+ "enabled."),
cfg.StrOpt('flavor_ref',
default="1",
help="Valid primary flavor to use in tests."),
@@ -357,6 +363,19 @@
"If both values are not specified, Tempest avoids tests "
"which require a microversion. Valid values are string "
"with format 'X.Y' or string 'latest'"),
+ cfg.StrOpt('compute_volume_common_az',
+ default=None,
+ help='AZ to be used for Cinder and Nova. Set this parameter '
+ 'when the cloud has nova.conf: cinder.cross_az_attach '
+ 'set to false. Which means volumes attached to an '
+ 'instance must be in the same availability zone in Cinder '
+ 'as the instance availability zone in Nova. Set the '
+ 'common availability zone in this config which will be '
+ 'used to boot an instance as well as creating a volume. '
+ 'NOTE: If that AZ is not in Cinder (or '
+ 'allow_availability_zone_fallback=False in cinder.conf), '
+ 'the volume create request will fail and the instance '
+ 'will fail the build request.'),
]
placement_group = cfg.OptGroup(name='placement',
@@ -516,9 +535,8 @@
default=True,
help='Enable special configuration drive with metadata.'),
cfg.ListOpt('scheduler_enabled_filters',
- default=["RetryFilter", "AvailabilityZoneFilter",
- "ComputeFilter", "ComputeCapabilitiesFilter",
- "ImagePropertiesFilter",
+ default=["AvailabilityZoneFilter", "ComputeFilter",
+ "ComputeCapabilitiesFilter", "ImagePropertiesFilter",
"ServerGroupAntiAffinityFilter",
"ServerGroupAffinityFilter"],
help="A list of enabled filters that Nova will accept as "
@@ -731,7 +749,13 @@
help="Does the test environment support port security?"),
cfg.BoolOpt('floating_ips',
default=True,
- help='Does the test environment support floating_ips')
+ help='Does the test environment support floating_ips'),
+ cfg.StrOpt('qos_placement_physnet', default=None,
+ help='Name of the physnet for placement based minimum '
+ 'bandwidth allocation.'),
+ cfg.StrOpt('provider_net_base_segmentation_id', default=3000,
+ help='Base segmentation ID to create provider networks. '
+ 'This value will be increased in case of conflict.')
]
validation_group = cfg.OptGroup(name='validation',
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
old mode 100644
new mode 100755
index a430d5d..c05e7a6
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -42,6 +42,11 @@
message = "Volume %(volume_id)s failed to restore and is in ERROR status"
+class VolumeExtendErrorException(exceptions.TempestException):
+ message = ("Volume %(volume_id)s failed to extend and "
+ "is in error_extending status")
+
+
class StackBuildErrorException(exceptions.TempestException):
message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
"due to '%(stack_status_reason)s'")
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index 419e593..69f178e 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -21,6 +21,9 @@
MeteringLabelsClient
from tempest.lib.services.network.networks_client import NetworksClient
from tempest.lib.services.network.ports_client import PortsClient
+from tempest.lib.services.network.qos_client import QosClient
+from tempest.lib.services.network.qos_minimum_bandwidth_rules_client import \
+ QosMinimumBandwidthRulesClient
from tempest.lib.services.network.quotas_client import QuotasClient
from tempest.lib.services.network.routers_client import RoutersClient
from tempest.lib.services.network.security_group_rules_client import \
@@ -37,6 +40,7 @@
__all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient',
'MeteringLabelRulesClient', 'MeteringLabelsClient',
'NetworksClient', 'NetworkVersionsClient', 'PortsClient',
- 'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient',
- 'SecurityGroupsClient', 'ServiceProvidersClient',
- 'SubnetpoolsClient', 'SubnetsClient', 'TagsClient']
+ 'QosClient', 'QosMinimumBandwidthRulesClient', 'QuotasClient',
+ 'RoutersClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient',
+ 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient',
+ 'TagsClient']
diff --git a/tempest/lib/services/network/qos_client.py b/tempest/lib/services/network/qos_client.py
new file mode 100644
index 0000000..bcd1066
--- /dev/null
+++ b/tempest/lib/services/network/qos_client.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2019 Ericsson
+#
+# 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.services.network import base
+
+
+class QosClient(base.BaseNetworkClient):
+
+ def create_qos_policy(self, **kwargs):
+ """Creates a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#create-qos-policy
+ """
+ uri = '/qos/policies'
+ post_data = {'policy': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_qos_policy(self, qos_policy_id, **kwargs):
+ """Updates a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#update-qos-policy
+ """
+ uri = '/qos/policies/%s' % qos_policy_id
+ post_data = {'policy': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_qos_policy(self, qos_policy_id, **fields):
+ """Show details of a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#show-qos-policy-details
+ """
+ uri = '/qos/policies/%s' % qos_policy_id
+ return self.show_resource(uri, **fields)
+
+ def delete_qos_policy(self, qos_policy_id):
+ """Deletes a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#delete-qos-policy
+ """
+ uri = '/qos/policies/%s' % qos_policy_id
+ return self.delete_resource(uri)
+
+ def list_qos_policies(self, **filters):
+ """Lists QoS policies.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#list-qos-policies
+ """
+ uri = '/qos/policies'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py b/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py
new file mode 100644
index 0000000..4f4ee3f
--- /dev/null
+++ b/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2019 Ericsson
+#
+# 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.services.network import base
+
+
+class QosMinimumBandwidthRulesClient(base.BaseNetworkClient):
+
+ def create_minimum_bandwidth_rule(self, qos_policy_id, **kwargs):
+ """Creates a minimum bandwidth rule for a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#create-minimum-bandwidth-rule
+ """
+ uri = '/qos/policies/%s/minimum_bandwidth_rules' % qos_policy_id
+ post_data = {'minimum_bandwidth_rule': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_minimum_bandwidth_rule(self, qos_policy_id, rule_id, **kwargs):
+ """Updates a minimum bandwidth rule.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#update-minimum-bandwidth-rule
+ """
+ uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+ qos_policy_id, rule_id)
+ post_data = {'minimum_bandwidth_rule': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_minimum_bandwidth_rule(self, qos_policy_id, rule_id, **fields):
+ """Show details of a minimum bandwidth rule.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#show-minimum-bandwidth-rule-details
+ """
+ uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+ qos_policy_id, rule_id)
+ return self.show_resource(uri, **fields)
+
+ def delete_minimum_bandwidth_rule(self, qos_policy_id, rule_id):
+ """Deletes a minimum bandwidth rule for a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#delete-minimum-bandwidth-rule
+ """
+ uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+ qos_policy_id, rule_id)
+ return self.delete_resource(uri)
+
+ def list_minimum_bandwidth_rules(self, qos_policy_id, **filters):
+ """Lists all minimum bandwidth rules for a QoS policy.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#list-minimum-bandwidth-rules-for-qos-policy
+ """
+ uri = '/qos/policies/%s/minimum_bandwidth_rules' % qos_policy_id
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 2dbdd11..a93c76e 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -212,7 +212,9 @@
except lib_exc.NotFound:
return True
if volume["volume"]["status"] == "error_deleting":
- raise lib_exc.DeleteErrorException(resource_id=id)
+ raise lib_exc.DeleteErrorException(
+ "Volume %s failed to delete and is in error_deleting status" %
+ volume['id'])
return False
@property
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 87d7e76..1252f09 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -274,6 +274,10 @@
tenant_network = self.get_tenant_network()
+ if CONF.compute.compute_volume_common_az:
+ kwargs.setdefault('availability_zone',
+ CONF.compute.compute_volume_common_az)
+
body, _ = compute.create_test_server(
clients,
tenant_network=tenant_network,
@@ -307,6 +311,11 @@
'imageRef': imageRef,
'volume_type': volume_type,
'size': size}
+
+ if CONF.compute.compute_volume_common_az:
+ kwargs.setdefault('availability_zone',
+ CONF.compute.compute_volume_common_az)
+
volume = self.volumes_client.create_volume(**kwargs)['volume']
self.addCleanup(self.volumes_client.wait_for_resource_deletion,
@@ -826,13 +835,15 @@
def _create_network(self, networks_client=None,
tenant_id=None,
namestart='network-smoke-',
- port_security_enabled=True):
+ port_security_enabled=True, **net_dict):
if not networks_client:
networks_client = self.networks_client
if not tenant_id:
tenant_id = networks_client.tenant_id
name = data_utils.rand_name(namestart)
network_kwargs = dict(name=name, tenant_id=tenant_id)
+ if net_dict:
+ network_kwargs.update(net_dict)
# Neutron disables port security by default so we have to check the
# config before trying to create the network with port_security_enabled
if CONF.network_feature_enabled.port_security:
@@ -1257,7 +1268,7 @@
def create_networks(self, networks_client=None,
routers_client=None, subnets_client=None,
tenant_id=None, dns_nameservers=None,
- port_security_enabled=True):
+ port_security_enabled=True, **net_dict):
"""Create a network with a subnet connected to a router.
The baremetal driver is a special case since all nodes are
@@ -1265,6 +1276,11 @@
:param tenant_id: id of tenant to create resources in.
:param dns_nameservers: list of dns servers to send to subnet.
+ :param port_security_enabled: whether or not port_security is enabled
+ :param net_dict: a dict containing experimental network information in
+ a form like this: {'provider:network_type': 'vlan',
+ 'provider:physical_network': 'foo',
+ 'provider:segmentation_id': '42'}
:returns: network, subnet, router
"""
if CONF.network.shared_physical_network:
@@ -1284,7 +1300,8 @@
network = self._create_network(
networks_client=networks_client,
tenant_id=tenant_id,
- port_security_enabled=port_security_enabled)
+ port_security_enabled=port_security_enabled,
+ **net_dict)
router = self._get_router(client=routers_client,
tenant_id=tenant_id)
subnet_kwargs = dict(network=network,
diff --git a/tempest/scenario/test_minbw_allocation_placement.py b/tempest/scenario/test_minbw_allocation_placement.py
new file mode 100644
index 0000000..e7085f6
--- /dev/null
+++ b/tempest/scenario/test_minbw_allocation_placement.py
@@ -0,0 +1,195 @@
+# Copyright (c) 2019 Ericsson
+#
+# 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 oslo_log import log as logging
+
+from tempest.common import utils
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.scenario import manager
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class MinBwAllocationPlacementTest(manager.NetworkScenarioTest):
+ credentials = ['primary', 'admin']
+ required_extensions = ['port-resource-request',
+ 'qos',
+ 'qos-bw-minimum-ingress']
+ # The feature QoS minimum bandwidth allocation in Placement API depends on
+ # Granular resource requests to GET /allocation_candidates and Support
+ # allocation candidates with nested resource providers features in
+ # Placement (see: https://specs.openstack.org/openstack/nova-specs/specs/
+ # stein/approved/bandwidth-resource-provider.html#rest-api-impact) and this
+ # means that the minimum placement microversion is 1.29
+ placement_min_microversion = '1.29'
+ placement_max_microversion = 'latest'
+
+ # Nova rejects to boot VM with port which has resource_request field, below
+ # microversion 2.72
+ compute_min_microversion = '2.72'
+ compute_max_microversion = 'latest'
+
+ INGRESS_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC"
+ INGRESS_DIRECTION = 'ingress'
+
+ SMALLEST_POSSIBLE_BW = 1
+ # For any realistic inventory value (that is inventory != MAX_INT) an
+ # allocation candidate request of MAX_INT is expected to be rejected, see:
+ # https://github.com/openstack/placement/blob/master/placement/
+ # db/constants.py#L16
+ PLACEMENT_MAX_INT = 0x7FFFFFFF
+
+ @classmethod
+ def setup_clients(cls):
+ super(MinBwAllocationPlacementTest, cls).setup_clients()
+ cls.placement_client = cls.os_admin.placement_client
+ cls.networks_client = cls.os_admin.networks_client
+ cls.subnets_client = cls.os_admin.subnets_client
+ cls.routers_client = cls.os_adm.routers_client
+ cls.qos_client = cls.os_admin.qos_client
+ cls.qos_min_bw_client = cls.os_admin.qos_min_bw_client
+
+ @classmethod
+ def skip_checks(cls):
+ super(MinBwAllocationPlacementTest, cls).skip_checks()
+ if not CONF.network_feature_enabled.qos_placement_physnet:
+ msg = "Skipped as no physnet is available in config for " \
+ "placement based QoS allocation."
+ raise cls.skipException(msg)
+
+ def _create_policy_and_min_bw_rule(self, name_prefix, min_kbps):
+ policy = self.qos_client.create_qos_policy(
+ name=data_utils.rand_name(name_prefix),
+ shared=True)['policy']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.qos_client.delete_qos_policy, policy['id'])
+ rule = self.qos_min_bw_client.create_minimum_bandwidth_rule(
+ policy['id'],
+ **{
+ 'min_kbps': min_kbps,
+ 'direction': self.INGRESS_DIRECTION
+ })['minimum_bandwidth_rule']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.qos_min_bw_client.delete_minimum_bandwidth_rule, policy['id'],
+ rule['id'])
+
+ return policy
+
+ def _create_qos_policies(self):
+ self.qos_policy_valid = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_valid',
+ min_kbps=self.SMALLEST_POSSIBLE_BW)
+ self.qos_policy_not_valid = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_not_valid',
+ min_kbps=self.PLACEMENT_MAX_INT)
+
+ def _create_network_and_qos_policies(self):
+ physnet_name = CONF.network_feature_enabled.qos_placement_physnet
+ base_segm = \
+ CONF.network_feature_enabled.provider_net_base_segmentation_id
+
+ self.prov_network, _, _ = self.create_networks(
+ networks_client=self.networks_client,
+ routers_client=self.routers_client,
+ subnets_client=self.subnets_client,
+ **{
+ 'shared': True,
+ 'provider:network_type': 'vlan',
+ 'provider:physical_network': physnet_name,
+ 'provider:segmentation_id': base_segm
+ })
+
+ self._create_qos_policies()
+
+ def _check_if_allocation_is_possible(self):
+ alloc_candidates = self.placement_client.list_allocation_candidates(
+ resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
+ self.SMALLEST_POSSIBLE_BW))
+ if len(alloc_candidates['provider_summaries']) == 0:
+ self.fail('No allocation candidates are available for %s:%s' %
+ (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
+
+ # Just to be sure check with impossible high (placement max_int),
+ # allocation
+ alloc_candidates = self.placement_client.list_allocation_candidates(
+ resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
+ self.PLACEMENT_MAX_INT))
+ if len(alloc_candidates['provider_summaries']) != 0:
+ self.fail('For %s:%s there should be no available candidate!' %
+ (self.INGRESS_RESOURCE_CLASS, self.PLACEMENT_MAX_INT))
+
+ @decorators.idempotent_id('78625d92-212c-400e-8695-dd51706858b8')
+ @decorators.attr(type='slow')
+ @utils.services('compute', 'network')
+ def test_qos_min_bw_allocation_basic(self):
+ """"Basic scenario with QoS min bw allocation in placement.
+
+ Steps:
+ * Create prerequisites:
+ ** VLAN type provider network with subnet.
+ ** valid QoS policy with minimum bandwidth rule with min_kbps=1
+ (This is a simplification to skip the checks in placement for
+ detecting the resource provider tree and inventories, as if
+ bandwidth resource is available 1 kbs will be available).
+ ** invalid QoS policy with minimum bandwidth rule with
+ min_kbs=max integer from placement (this is a simplification again
+ to avoid detection of RP tress and inventories, as placement will
+ reject such big allocation).
+ * Create port with valid QoS policy, and boot VM with that, it should
+ pass.
+ * Create port with invalid QoS policy, and try to boot VM with that,
+ it should fail.
+ """
+
+ self._check_if_allocation_is_possible()
+
+ self._create_network_and_qos_policies()
+
+ valid_port = self.create_port(
+ self.prov_network['id'], qos_policy_id=self.qos_policy_valid['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': valid_port['id']}])
+ allocations = self.placement_client.list_allocations(server1['id'])
+
+ self.assertGreater(len(allocations['allocations']), 0)
+ bw_resource_in_alloc = False
+ for rp, resources in allocations['allocations'].items():
+ if self.INGRESS_RESOURCE_CLASS in resources['resources']:
+ bw_resource_in_alloc = True
+ self.assertTrue(bw_resource_in_alloc)
+
+ # boot another vm with max int bandwidth
+ not_valid_port = self.create_port(
+ self.prov_network['id'],
+ qos_policy_id=self.qos_policy_not_valid['id'])
+ server2 = self.create_server(
+ wait_until=None,
+ networks=[{'port': not_valid_port['id']}])
+ waiters.wait_for_server_status(
+ client=self.os_primary.servers_client, server_id=server2['id'],
+ status='ERROR', ready_wait=False, raise_on_error=False)
+ allocations = self.placement_client.list_allocations(server2['id'])
+
+ self.assertEqual(0, len(allocations['allocations']))
+ server2 = self.servers_client.show_server(server2['id'])
+ self.assertIn('fault', server2['server'])
+ self.assertIn('No valid host', server2['server']['fault']['message'])
diff --git a/tempest/tests/cmd/test_cleanup.py b/tempest/tests/cmd/test_cleanup.py
index b47da0b..1618df9 100644
--- a/tempest/tests/cmd/test_cleanup.py
+++ b/tempest/tests/cmd/test_cleanup.py
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import mock
+
from tempest.cmd import cleanup
from tempest.tests import base
@@ -24,3 +26,17 @@
test_saved_json = 'tempest/tests/cmd/test_saved_state_json.json'
# test if the file is loaded without any issues/exceptions
c._load_json(test_saved_json)
+
+ @mock.patch('tempest.cmd.cleanup.TempestCleanup.init')
+ @mock.patch('tempest.cmd.cleanup.TempestCleanup._cleanup')
+ def test_take_action_got_exception(self, mock_cleanup, mock_init):
+ c = cleanup.TempestCleanup(None, None, 'test')
+ c.GOT_EXCEPTIONS.append('exception')
+ mock_cleanup.return_value = True
+ mock_init.return_value = True
+ try:
+ c.take_action(mock.Mock())
+ except Exception as exc:
+ self.assertEqual(str(exc), '[\'exception\']')
+ return
+ assert False
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index 3262b1c..de0dbec 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -19,6 +19,7 @@
from tempest import clients
from tempest.cmd import cleanup_service
from tempest import config
+from tempest.lib import exceptions
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests.lib import fake_credentials
@@ -27,13 +28,24 @@
class TestBaseService(base.TestCase):
+ class TestException(cleanup_service.BaseService):
+ def delete(self):
+ raise exceptions.NotImplemented
+
+ def dry_run(self):
+ raise exceptions.NotImplemented
+
+ def save_state(self):
+ raise exceptions.NotImplemented
+
def test_base_service_init(self):
kwargs = {'data': {'data': 'test'},
'is_dry_run': False,
'saved_state_json': {'saved': 'data'},
'is_preserve': False,
'is_save_state': True,
- 'tenant_id': 'project_id'}
+ 'tenant_id': 'project_id',
+ 'got_exceptions': []}
base = cleanup_service.BaseService(kwargs)
self.assertEqual(base.data, kwargs['data'])
self.assertFalse(base.is_dry_run)
@@ -41,6 +53,28 @@
self.assertFalse(base.is_preserve)
self.assertTrue(base.is_save_state)
self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id'])
+ self.assertEqual(base.got_exceptions, kwargs['got_exceptions'])
+
+ def test_not_implemented_ex(self):
+ kwargs = {'data': {'data': 'test'},
+ 'is_dry_run': False,
+ 'saved_state_json': {'saved': 'data'},
+ 'is_preserve': False,
+ 'is_save_state': False,
+ 'tenant_id': 'project_id',
+ 'got_exceptions': []}
+ base = self.TestException(kwargs)
+ # delete
+ base.run()
+ self.assertEqual(len(base.got_exceptions), 1)
+ # save_state
+ base.save_state = True
+ base.run()
+ self.assertEqual(len(base.got_exceptions), 2)
+ # dry_run
+ base.is_dry_run = True
+ base.run()
+ self.assertEqual(len(base.got_exceptions), 3)
class MockFunctionsBase(base.TestCase):
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 0e00d94..8997a4c 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -49,7 +49,7 @@
args = mock.Mock(spec=argparse.Namespace)
setattr(args, 'smoke', False)
setattr(args, 'regex', '')
- self.assertIsNone(None, self.run_cmd._build_regex(args))
+ self.assertIsNone(self.run_cmd._build_regex(args))
def test__build_regex_smoke(self):
args = mock.Mock(spec=argparse.Namespace)
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
old mode 100644
new mode 100755
index d56e8a4..02e1c99
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -73,6 +73,25 @@
mock.call(volume_id)])
mock_sleep.assert_called_once_with(1)
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_volume_status_error_extending(self, mock_sleep):
+ # Tests that the wait method raises VolumeExtendErrorException if
+ # the volume status is 'error_extending'.
+ client = mock.Mock(spec=volumes_client.VolumesClient,
+ resource_type="volume",
+ build_interval=1)
+ volume1 = {'volume': {'status': 'extending'}}
+ volume2 = {'volume': {'status': 'error_extending'}}
+ mock_show = mock.Mock(side_effect=(volume1, volume2))
+ client.show_volume = mock_show
+ volume_id = '7532b91e-aa0a-4e06-b3e5-20c0c5ee1caa'
+ self.assertRaises(exceptions.VolumeExtendErrorException,
+ waiters.wait_for_volume_resource_status,
+ client, volume_id, 'available')
+ mock_show.assert_has_calls([mock.call(volume_id),
+ mock.call(volume_id)])
+ mock_sleep.assert_called_once_with(1)
+
class TestInterfaceWaiters(base.TestCase):
diff --git a/tempest/tests/lib/services/network/test_qos_client.py b/tempest/tests/lib/services/network/test_qos_client.py
new file mode 100644
index 0000000..b04b847
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_qos_client.py
@@ -0,0 +1,139 @@
+# Copyright (c) 2019 Ericsson
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import qos_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQosClient(base.BaseServiceTest):
+
+ FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616"
+
+ FAKE_QOS_POLICY_REQUEST = {
+ 'name': 'foo',
+ 'shared': True
+ }
+
+ FAKE_QOS_POLICY_RESPONSE = {
+ 'policy': {
+ "name": "10Mbit",
+ "description": "This policy limits the ports to 10Mbit max.",
+ "rules": [],
+ "id": FAKE_QOS_POLICY_ID,
+ "is_default": False,
+ "project_id": "8d4c70a21fed4aeba121a1a429ba0d04",
+ "revision_number": 1,
+ "tenant_id": "8d4c70a21fed4aeba121a1a429ba0d04",
+ "created_at": "2018-04-03T21:26:39Z",
+ "updated_at": "2018-04-03T21:26:39Z",
+ "shared": False,
+ "tags": ["tag1,tag2"]
+ }
+ }
+
+ FAKE_QOS_POLICIES = {
+ 'policies': [
+ FAKE_QOS_POLICY_RESPONSE['policy']
+ ]
+ }
+
+ def setUp(self):
+ super(TestQosClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.qos_client = qos_client.QosClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_create_qos_policy(self, bytes_body=False):
+ self.check_service_client_function(
+ self.qos_client.create_qos_policy,
+ "tempest.lib.common.rest_client.RestClient.post",
+ self.FAKE_QOS_POLICY_RESPONSE,
+ bytes_body,
+ 201,
+ **self.FAKE_QOS_POLICY_REQUEST)
+
+ def _test_list_qos_policies(self, bytes_body=False):
+ self.check_service_client_function(
+ self.qos_client.list_qos_policies,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_QOS_POLICIES,
+ bytes_body,
+ 200)
+
+ def _test_show_qos_policy(self, bytes_body=False):
+ self.check_service_client_function(
+ self.qos_client.show_qos_policy,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_QOS_POLICY_RESPONSE,
+ bytes_body,
+ 200,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID)
+
+ def _test_update_qos_polcy(self, bytes_body=False):
+ update_kwargs = {
+ "name": "100Mbit",
+ "description": "This policy limits the ports to 100Mbit max.",
+ "shared": True
+ }
+
+ resp_body = {
+ "policy": copy.deepcopy(
+ self.FAKE_QOS_POLICY_RESPONSE['policy']
+ )
+ }
+ resp_body["policy"].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.qos_client.update_qos_policy,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID,
+ **update_kwargs)
+
+ def test_create_qos_policy_with_str_body(self):
+ self._test_create_qos_policy()
+
+ def test_create_qos_policy_with_bytes_body(self):
+ self._test_create_qos_policy(bytes_body=True)
+
+ def test_update_qos_policy_with_str_body(self):
+ self._test_update_qos_polcy()
+
+ def test_update_qos_policy_with_bytes_body(self):
+ self._test_update_qos_polcy(bytes_body=True)
+
+ def test_show_qos_policy_with_str_body(self):
+ self._test_show_qos_policy()
+
+ def test_show_qos_policy_with_bytes_body(self):
+ self._test_show_qos_policy(bytes_body=True)
+
+ def test_delete_qos_policy(self):
+ self.check_service_client_function(
+ self.qos_client.delete_qos_policy,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID)
+
+ def test_list_qos_policies_with_str_body(self):
+ self._test_list_qos_policies()
+
+ def test_list_qos_policies_with_bytes_body(self):
+ self._test_list_qos_policies(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py b/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py
new file mode 100644
index 0000000..8234dda
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2019 Ericsson
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.network import qos_minimum_bandwidth_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQosMinimumBandwidthRulesClient(base.BaseServiceTest):
+
+ FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616"
+ FAKE_MIN_BW_RULE_ID = "e758c89e-1297-11e9-a6cf-cf46a71e6699"
+
+ FAKE_MIN_BW_RULE_REQUEST = {
+ 'qos_policy_id': FAKE_QOS_POLICY_ID,
+ 'min_kbps': 1000,
+ 'direction': 'ingress'
+ }
+
+ FAKE_MIN_BW_RULE_RESPONSE = {
+ 'minimum_bandwidth_rule': {
+ 'id': FAKE_MIN_BW_RULE_ID,
+ 'min_kbps': 10000,
+ 'direction': 'egress'
+ }
+ }
+
+ FAKE_MIN_BW_RULES = {
+ 'bandwidth_limit_rules': [
+ FAKE_MIN_BW_RULE_RESPONSE['minimum_bandwidth_rule']
+ ]
+ }
+
+ def setUp(self):
+ super(TestQosMinimumBandwidthRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.qos_min_bw_client = qos_minimum_bandwidth_rules_client.\
+ QosMinimumBandwidthRulesClient(fake_auth, "network", "regionOne")
+
+ def _test_create_minimum_bandwidth_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.qos_min_bw_client.create_minimum_bandwidth_rule,
+ "tempest.lib.common.rest_client.RestClient.post",
+ self.FAKE_MIN_BW_RULE_RESPONSE,
+ bytes_body,
+ 201,
+ **self.FAKE_MIN_BW_RULE_REQUEST
+ )
+
+ def _test_list_minimum_bandwidth_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.qos_min_bw_client.list_minimum_bandwidth_rules,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_MIN_BW_RULES,
+ bytes_body,
+ 200,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID
+ )
+
+ def _test_show_minimum_bandwidth_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.qos_min_bw_client.show_minimum_bandwidth_rule,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_MIN_BW_RULE_RESPONSE,
+ bytes_body,
+ 200,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID,
+ rule_id=self.FAKE_MIN_BW_RULE_ID
+ )
+
+ def _test_update_qos_polcy(self, bytes_body=False):
+ update_kwargs = {
+ "min_kbps": "20000"
+ }
+
+ resp_body = {
+ "minimum_bandwidth_rule": copy.deepcopy(
+ self.FAKE_MIN_BW_RULE_RESPONSE['minimum_bandwidth_rule']
+ )
+ }
+ resp_body["minimum_bandwidth_rule"].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.qos_min_bw_client.update_minimum_bandwidth_rule,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID,
+ rule_id=self.FAKE_MIN_BW_RULE_ID,
+ **update_kwargs)
+
+ def test_create_minimum_bandwidth_rule_with_str_body(self):
+ self._test_create_minimum_bandwidth_rule()
+
+ def test_create_minimum_bandwidth_rule_with_bytes_body(self):
+ self._test_create_minimum_bandwidth_rule(bytes_body=True)
+
+ def test_update_minimum_bandwidth_rule_with_str_body(self):
+ self._test_update_qos_polcy()
+
+ def test_update_minimum_bandwidth_rule_with_bytes_body(self):
+ self._test_update_qos_polcy(bytes_body=True)
+
+ def test_show_minimum_bandwidth_rule_with_str_body(self):
+ self._test_show_minimum_bandwidth_rule()
+
+ def test_show_minimum_bandwidth_rule_with_bytes_body(self):
+ self._test_show_minimum_bandwidth_rule(bytes_body=True)
+
+ def test_delete_minimum_bandwidth_rule(self):
+ self.check_service_client_function(
+ self.qos_min_bw_client.delete_minimum_bandwidth_rule,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ qos_policy_id=self.FAKE_QOS_POLICY_ID,
+ rule_id=self.FAKE_MIN_BW_RULE_ID)
+
+ def test_list_minimum_bandwidth_rule_with_str_body(self):
+ self._test_list_minimum_bandwidth_rules()
+
+ def test_list_minimum_bandwidth_rule_with_bytes_body(self):
+ self._test_list_minimum_bandwidth_rules(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
index c788181..87bd379 100644
--- a/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
@@ -30,6 +30,15 @@
}
}
+ UPDATE_ENCRYPTION_TYPE = {
+ "encryption": {
+ "key_size": 64,
+ "provider": "LuksEncryptor",
+ "control_location": "front-end",
+ "cipher": "aes-xts-plain64"
+ }
+ }
+
FAKE_INFO_ENCRYPTION_TYPE = {
"encryption": {
"name": "FakeEncryptionType",
@@ -50,10 +59,8 @@
def setUp(self):
super(TestEncryptionTypesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = encryption_types_client.EncryptionTypesClient(fake_auth,
- 'volume',
- 'regionOne'
- )
+ self.client = encryption_types_client.EncryptionTypesClient(
+ fake_auth, 'volume', 'regionOne')
def _test_create_encryption(self, bytes_body=False):
self.check_service_client_function(
@@ -101,3 +108,16 @@
{},
volume_type_id="cbc36478b0bd8e67e89",
status=202)
+
+ def test_update_encryption_type_with_str_body(self):
+ self._test_update_encryption_type()
+
+ def test_update_encryption_type_with_bytes_body(self):
+ self._test_update_encryption_type(bytes_body=True)
+
+ def _test_update_encryption_type(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_encryption_type,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.UPDATE_ENCRYPTION_TYPE,
+ bytes_body, volume_type_id="cbc36478b0bd8e67e89")
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 746cb34..c18f109 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -25,6 +25,7 @@
import json
import re
+import sys
try:
# For Python 3.0 and later
@@ -35,6 +36,27 @@
import urllib2 as urllib
from urllib2 import HTTPError
+# List of projects having tempest plugin stale or unmaintained for a long time
+# (6 months or more)
+# TODO(masayukig): Some of these can be removed from BLACKLIST in the future
+# when the patches are merged.
+BLACKLIST = [
+ 'openstack/barbican-tempest-plugin',
+ # https://review.opendev.org/#/c/634631/
+ 'x/gce-api', # It looks gce-api doesn't support python3 yet.
+ 'x/intel-nfv-ci-tests', # https://review.opendev.org/#/c/634640/
+ 'openstack/networking-generic-switch',
+ # https://review.opendev.org/#/c/634846/
+ 'openstack/networking-l2gw-tempest-plugin',
+ # https://review.opendev.org/#/c/635093/
+ 'openstack/networking-midonet', # https://review.opendev.org/#/c/635096/
+ 'x/networking-plumgrid', # https://review.opendev.org/#/c/635096/
+ 'x/networking-spp', # https://review.opendev.org/#/c/635098/
+ 'openstack/neutron-dynamic-routing',
+ # https://review.opendev.org/#/c/637718/
+ 'openstack/neutron-vpnaas', # https://review.opendev.org/#/c/637719/
+ 'x/valet', # https://review.opendev.org/#/c/638339/
+]
url = 'https://review.opendev.org/projects/'
@@ -47,15 +69,10 @@
'''
-def is_in_openstack_namespace(proj):
- return proj.startswith('openstack/')
-
# Rather than returning a 404 for a nonexistent file, cgit delivers a
# 0-byte response to a GET request. It also does not provide a
# Content-Length in a HEAD response, so the way we tell if a file exists
# is to check the length of the entire GET response body.
-
-
def has_tempest_plugin(proj):
try:
r = urllib.urlopen(
@@ -64,6 +81,8 @@
except HTTPError as err:
if err.code == 404:
return False
+ # We should not ignore non 404 errors.
+ raise err
p = re.compile(r'^tempest\.test_plugins', re.M)
if p.findall(r.read().decode('utf-8')):
return True
@@ -71,24 +90,45 @@
False
+if len(sys.argv) > 1 and sys.argv[1] == 'blacklist':
+ for black_plugin in BLACKLIST:
+ print(black_plugin)
+ # We just need BLACKLIST when we use this `blacklist` option.
+ # So, this exits here.
+ sys.exit()
+
r = urllib.urlopen(url)
# Gerrit prepends 4 garbage octets to the JSON, in order to counter
# cross-site scripting attacks. Therefore we must discard it so the
# json library won't choke.
content = r.read().decode('utf-8')[4:]
-projects = sorted(filter(is_in_openstack_namespace, json.loads(content)))
+projects = sorted(json.loads(content))
-# Retrieve projects having no deb, puppet, ui or spec namespace as those
+# Retrieve projects having no deployment tool repo (such as deb,
+# puppet, ansible, etc.), infra repos, ui or spec namespace as those
# namespaces do not contains tempest plugins.
projects_list = [i for i in projects if not (
+ i.startswith('openstack-dev/') or
+ i.startswith('openstack-infra/') or
+ i.startswith('openstack/ansible-') or
+ i.startswith('openstack/charm-') or
+ i.startswith('openstack/cookbook-openstack-') or
+ i.startswith('openstack/devstack-') or
+ i.startswith('openstack/fuel-') or
i.startswith('openstack/deb-') or
i.startswith('openstack/puppet-') or
+ i.startswith('openstack/openstack-ansible-') or
+ i.startswith('x/deb-') or
+ i.startswith('x/fuel-') or
+ i.startswith('x/python-') or
+ i.startswith('zuul/') or
i.endswith('-ui') or
i.endswith('-specs'))]
found_plugins = list(filter(has_tempest_plugin, projects_list))
-# Every element of the found_plugins list begins with "openstack/".
-# We drop those initial 10 octets when printing the list.
+# We have tempest plugins not only in 'openstack/' namespace but also the
+# other name spaces such as 'airship/', 'x/', etc.
+# So, we print all of them here.
for project in found_plugins:
- print(project[10:])
+ print(project)
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index b4e5430..6e473b7 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -61,20 +61,37 @@
printf " ===\n"
}
+function print_plugin_table() {
+ title_underline ${name_col_len}
+ printf "%-3s %-${name_col_len}s %s\n" "SR" "Plugin Name" "URL"
+ title_underline ${name_col_len}
+
+ i=0
+ for plugin in $1; do
+ i=$((i+1))
+ giturl="https://opendev.org/openstack/${plugin}"
+ printf "%-3s %-${name_col_len}s %s\n" "$i" "${plugin}" "${giturl}"
+ done
+
+ title_underline ${name_col_len}
+}
+
printf "\n\n"
-title_underline ${name_col_len}
-printf "%-3s %-${name_col_len}s %s\n" "SR" "Plugin Name" "URL"
-title_underline ${name_col_len}
+print_plugin_table "${sorted_plugins}"
-i=0
-for plugin in ${sorted_plugins}; do
- i=$((i+1))
- giturl="https://opendev.org/openstack/${plugin}"
- gitlink="https://opendev.org/openstack/${plugin}"
- printf "%-3s %-${name_col_len}s %s\n" "$i" "${plugin}" "\`${giturl} <${gitlink}>\`__"
-done
+printf "\n\n"
-title_underline ${name_col_len}
+# Print BLACKLIST
+if [[ -r doc/source/data/tempest-blacklisted-plugins-registry.header ]]; then
+ cat doc/source/data/tempest-blacklisted-plugins-registry.header
+fi
+
+blacklist=$(python tools/generate-tempest-plugins-list.py blacklist)
+name_col_len=$(echo "${blacklist}" | wc -L)
+name_col_len=$(( name_col_len + 20 ))
+
+printf "\n\n"
+print_plugin_table "${blacklist}"
printf "\n\n"
diff --git a/tools/tempest-integrated-gate-networking-blacklist.txt b/tools/tempest-integrated-gate-networking-blacklist.txt
new file mode 100644
index 0000000..9566f69
--- /dev/null
+++ b/tools/tempest-integrated-gate-networking-blacklist.txt
@@ -0,0 +1,17 @@
+# This file includes the backlist of tests which need to be
+# skipped for Integrated-gate-networking template.
+
+# Skip Cinder, Glance, keystone and Swift API tests.
+tempest.api.volume
+tempest.api.image
+tempest.api.object_storage
+tempest.api.identity
+
+# Skip Cinder, Glance and Swift only scenario tests.
+tempest.scenario.test_encrypted_cinder_volumes.TestEncryptedCinderVolumes.test_encrypted_cinder_volumes_luks
+tempest.scenario.test_encrypted_cinder_volumes.TestEncryptedCinderVolumes.test_encrypted_cinder_volumes_cryptsetup
+tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_basic_ops
+tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_acl_anonymous_download
+tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
+tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
+tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
new file mode 100644
index 0000000..3900f96
--- /dev/null
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -0,0 +1,13 @@
+# This file includes the backlist of tests which need to be
+# skipped for Integrated-gate-storage template. Integrated-gate-storage template
+# needs to run only Cinder, Glance, Swift and Nova related tests and rest all
+# tests will be skipped by below list.
+
+# Skip network, keystone API tests.
+tempest.api.network
+tempest.api.identity
+
+# Skip network only scenario tests.
+tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps.test_network_advanced_server_ops
+tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_network_basic_ops
+tempest.scenario.test_network_v6.TestGettingAddress.test_security_groups_basic_ops
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index b291fcc..b652369 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -43,49 +43,19 @@
# retrieve a list of projects having tempest plugins
PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
-# List of projects having tempest plugin stale or unmaintained for a long time
-# (6 months or more)
-# TODO(masayukig): Some of these can be removed from BLACKLIST in the future.
-# barbican-tempest-plugin: https://review.opendev.org/#/c/634631/
-# cyborg-tempest-plugin: https://review.opendev.org/659687
-# intel-nfv-ci-tests: https://review.opendev.org/#/c/634640/
-# networking-ansible: https://review.opendev.org/#/c/634647/
-# networking-generic-switch: https://review.opendev.org/#/c/634846/
-# networking-l2gw-tempest-plugin: https://review.opendev.org/#/c/635093/
-# networking-midonet: https://review.opendev.org/#/c/635096/
-# networking-plumgrid: https://review.opendev.org/#/c/635096/
-# networking-spp: https://review.opendev.org/#/c/635098/
-# neutron-dynamic-routing: https://review.opendev.org/#/c/637718/
-# neutron-vpnaas: https://review.opendev.org/#/c/637719/
-# nova-lxd: https://review.opendev.org/#/c/638334/
-# valet: https://review.opendev.org/#/c/638339/
-BLACKLIST="
-barbican-tempest-plugin
-cyborg-tempest-plugin
-intel-nfv-ci-tests
-networking-ansible
-networking-generic-switch
-networking-l2gw-tempest-plugin
-networking-midonet
-networking-plumgrid
-networking-spp
-neutron-dynamic-routing
-neutron-vpnaas
-nova-lxd
-valet
-"
+BLACKLIST="$(python tools/generate-tempest-plugins-list.py blacklist)"
# Function to clone project using zuul-cloner or from git
function clone_project() {
if [ -e /usr/zuul-env/bin/zuul-cloner ]; then
/usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \
https://opendev.org \
- openstack/"$1"
+ "$1"
elif [ -e /usr/bin/git ]; then
- /usr/bin/git clone https://opendev.org/openstack/"$1" \
- openstack/"$1"
+ /usr/bin/git clone https://opendev.org/"$1" \
+ "$1"
fi
}
@@ -103,10 +73,10 @@
# Function to install project
function install_project() {
- "$TVENV" pip install "$SANITY_DIR"/openstack/"$1"
+ "$TVENV" pip install "$SANITY_DIR"/"$1"
# Check for test-requirements.txt file in a project then install it.
- if [ -e "$SANITY_DIR"/openstack/"$1"/test-requirements.txt ]; then
- "$TVENV" pip install -r "$SANITY_DIR"/openstack/"$1"/test-requirements.txt
+ if [ -e "$SANITY_DIR"/"$1"/test-requirements.txt ]; then
+ "$TVENV" pip install -r "$SANITY_DIR"/"$1"/test-requirements.txt
fi
}
@@ -124,7 +94,7 @@
# Remove the sanity workspace in case of remaining
rm -fr "$SANITY_DIR"/tempest_sanity
# Remove the project directory after sanity run
- rm -fr "$SANITY_DIR"/openstack/"$1"
+ rm -fr "$SANITY_DIR"/"$1"
return $retval
}
@@ -151,8 +121,10 @@
fi
done
+echo "Passed Plugins: $passed_plugin"
+echo "Failed Plugins: $failed_plugin"
+
# Check for failed status
if [[ -n $failed_plugin ]]; then
- echo "Failed Plugins: $failed_plugin"
exit 1
fi
diff --git a/tox.ini b/tox.ini
index 48a2baa..087a298 100644
--- a/tox.ini
+++ b/tox.ini
@@ -62,7 +62,7 @@
deps = {[tempestenv]deps}
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex {posargs}
+ tempest run --regex {posargs:''}
[testenv:all-plugin]
# DEPRECATED
@@ -82,7 +82,7 @@
echo "WARNING: The all-plugin env is deprecated and will be removed"
echo "WARNING Please use the 'all' environment for Tempest plugins."
find . -type f -name "*.pyc" -delete
- tempest run --regex {posargs}
+ tempest run --regex {posargs:''}
[testenv:all-site-packages]
sitepackages = True
@@ -93,7 +93,7 @@
deps = {[tempestenv]deps}
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex {posargs}
+ tempest run --regex {posargs:''}
[testenv:full]
envdir = .tox/tempest
@@ -118,6 +118,30 @@
find . -type f -name "*.pyc" -delete
tempest run --regex '(^tempest\.scenario.*)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
+[testenv:integrated-network]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select which tests to run and exclude the slow tag and
+# tests listed in blacklist file:
+commands =
+ find . -type f -name "*.pyc" -delete
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-networking-blacklist.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-networking-blacklist.txt {posargs}
+
+[testenv:integrated-storage]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select which tests to run and exclude the slow tag and
+# tests listed in blacklist file:
+commands =
+ find . -type f -name "*.pyc" -delete
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-storage-blacklist.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-storage-blacklist.txt {posargs}
+
[testenv:full-serial]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}