Merge "Adding missing test cases of tempest workspace"
diff --git a/.zuul.yaml b/.zuul.yaml
index 9af79a6..8576455 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,7 +1,6 @@
- job:
name: devstack-tempest
parent: devstack
- nodeset: openstack-single-node
description: |
Base Tempest job.
@@ -53,7 +52,6 @@
- job:
name: devstack-tempest-ipv6
parent: devstack-ipv6
- nodeset: openstack-single-node
description: |
Base Tempest IPv6 job.
required-projects:
@@ -196,7 +194,7 @@
- job:
name: tempest-multinode-full
parent: devstack-tempest
- nodeset: openstack-two-node
+ nodeset: openstack-two-node-bionic
# Until the devstack changes are backported, only run this on master
branches:
- master
@@ -224,6 +222,13 @@
LIVE_MIGRATION_AVAILABLE: true
USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
+- job:
+ name: tempest-multinode-full-py3
+ parent: tempest-multinode-full
+ vars:
+ devstack_localrc:
+ USE_PYTHON3: true
+
- nodeset:
name: openstack-bionic-node
nodes:
@@ -234,16 +239,6 @@
nodes:
- controller
-- job:
- name: tempest-full-py36
- parent: tempest-full-py3
- nodeset: openstack-bionic-node
- branches:
- - master
- description: |
- Base integration test with Neutron networking and py36.
- voting: false
-
- nodeset:
name: openstack-opensuse150-node
nodes:
@@ -284,28 +279,47 @@
tempest_concurrency: 2
- job:
+ name: tempest-slow-py3
+ parent: tempest-slow
+ vars:
+ devstack_localrc:
+ USE_PYTHON3: true
+ devstack_services:
+ s-account: false
+ s-container: false
+ s-object: false
+ s-proxy: false
+ # without Swift, c-bak cannot run (in the Gate at least)
+ c-bak: false
+
+- job:
name: tempest-full-rocky
parent: tempest-full
+ nodeset: openstack-single-node-xenial
override-checkout: stable/rocky
- job:
name: tempest-full-rocky-py3
parent: tempest-full-py3
+ nodeset: openstack-single-node-xenial
override-checkout: stable/rocky
- job:
name: tempest-full-queens
parent: tempest-full
+ nodeset: openstack-single-node-xenial
override-checkout: stable/queens
- job:
name: tempest-full-queens-py3
parent: tempest-full-py3
+ nodeset: openstack-single-node-xenial
override-checkout: stable/queens
- job:
name: tempest-full-pike
parent: tempest-full
+ nodeset: openstack-single-node-xenial
override-checkout: stable/pike
- job:
@@ -456,7 +470,7 @@
templates:
- check-requirements
- integrated-gate
- - integrated-gate-py35
+ - integrated-gate-py3
- openstack-cover-jobs
- openstack-python-jobs
- openstack-python35-jobs
@@ -492,8 +506,6 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-py36:
- irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3-ipv6:
voting: false
irrelevant-files: *tempest-irrelevant-files
@@ -509,6 +521,8 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-multinode-full-py3:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-tox-plugin-sanity-check:
irrelevant-files:
- ^(test-|)requirements.txt$
@@ -522,7 +536,7 @@
# tools/ is not here since this relies on a script in tools/.
- tempest-slow:
irrelevant-files: *tempest-irrelevant-files
- - nova-cells-v1:
+ - tempest-slow-py3:
irrelevant-files: *tempest-irrelevant-files
- nova-live-migration:
voting: false
@@ -531,6 +545,8 @@
irrelevant-files: *tempest-irrelevant-files
- neutron-grenade:
irrelevant-files: *tempest-irrelevant-files
+ - grenade-py3:
+ irrelevant-files: *tempest-irrelevant-files
- devstack-plugin-ceph-tempest:
voting: false
irrelevant-files: *tempest-irrelevant-files
@@ -565,7 +581,7 @@
jobs:
- nova-multiattach:
irrelevant-files: *tempest-irrelevant-files
- - tempest-slow:
+ - tempest-slow-py3:
irrelevant-files: *tempest-irrelevant-files
- neutron-grenade-multinode:
irrelevant-files: *tempest-irrelevant-files
@@ -573,6 +589,8 @@
irrelevant-files: *tempest-irrelevant-files
- neutron-grenade:
irrelevant-files: *tempest-irrelevant-files
+ - grenade-py3:
+ irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
- tempest-cinder-v2-api:
@@ -583,6 +601,8 @@
irrelevant-files: *tempest-irrelevant-files
- neutron-tempest-dvr-ha-multinode-full:
irrelevant-files: *tempest-irrelevant-files
+ - nova-cells-v1:
+ irrelevant-files: *tempest-irrelevant-files
- legacy-tempest-dsvm-nova-v20-api:
irrelevant-files: *tempest-irrelevant-files
- legacy-tempest-dsvm-lvm-multibackend:
diff --git a/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml b/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml
new file mode 100644
index 0000000..21b74a6
--- /dev/null
+++ b/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml
@@ -0,0 +1,17 @@
+---
+features:
+ - |
+ Add basic read-only Placement client to Tempest to make possible the
+ testing of the placement based bandwidth allocation feature.
+ The following API calls are available for tempest from now:
+
+ * GET /allocation_candidates
+ * GET /allocations/{consumer_uuid}
+
+ Add new config group ``placement``, with the config options:
+
+ * ``endpoint_type`` to use for communication with placement service.
+ * ``catalog_type`` of the placement service.
+ * ``region`` as the placement region name to use.
+ * ``min_microversion`` and ``max_microversion`` as the range between
+ placement API requests are sent.
diff --git a/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml b/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml
new file mode 100644
index 0000000..931d689
--- /dev/null
+++ b/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ Add a new config setting ``immutable_user_source`` in the
+ ``[identity-feature-enabled]`` group that defaults to false.
+ This setting, combined with the usage of the ``@testtools.skipIf()``
+ decorator, will allow tests that require user creation, deletion,
+ or modification to skip instead of failing in environments that
+ are LDAP-backed. In such environments, the user source is read-only,
+ so this feature flag is needed to allow such tests to gracefully skip
+ without having to blacklist them.
diff --git a/setup.cfg b/setup.cfg
index 96ee7ea..fcbe956 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,7 +4,7 @@
description-file =
README.rst
author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/tempest/latest/
classifier =
Intended Audience :: Information Technology
diff --git a/tempest/README.rst b/tempest/README.rst
index a5f4a92..b345032 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -9,7 +9,7 @@
OpenStack clouds.
As such Tempest tests come in many flavors, each with their own rules
-and guidelines. Below is the overview of the Tempest respository structure
+and guidelines. Below is the overview of the Tempest repository structure
to make this clear.
.. code-block:: console
diff --git a/tempest/api/compute/admin/test_keypairs_v210.py b/tempest/api/compute/admin/test_keypairs_v210.py
index 24ea8a1..40ed532 100644
--- a/tempest/api/compute/admin/test_keypairs_v210.py
+++ b/tempest/api/compute/admin/test_keypairs_v210.py
@@ -56,7 +56,7 @@
self.assertEqual(first_keyname, keypair_detail['name'])
self.assertEqual(user_id, keypair_detail['user_id'],
"The fetched keypair is not for requested user!")
- # Create a admin keypair
+ # Create an admin keypair
admin_keypair = self.create_keypair(keypair_type='ssh',
client=self.client)
admin_keypair.pop('private_key', None)
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 5a60dc6..b1a7c52 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -50,8 +50,6 @@
# a subnet so the instance being migrated has a single port, but
# we need that to make sure we are properly updating the port
# host bindings during the live migration.
- # TODO(mriedem): SSH validation before and after the instance is
- # live migrated would be a nice test wrinkle addition.
cls.set_network_resources(network=True, subnet=True)
super(LiveMigrationTestBase, cls).setup_credentials()
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 5cd98f4..bebc8c5 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -105,7 +105,7 @@
asserts the servers are in the group and on different hosts.
"""
hosts = self._create_servers_with_group('anti-affinity')
- hostnames = hosts.values()
+ hostnames = list(hosts.values())
self.assertNotEqual(hostnames[0], hostnames[1],
'Servers are on the same host: %s' % hosts)
@@ -120,6 +120,6 @@
asserts the servers are in the group and on same host.
"""
hosts = self._create_servers_with_group('affinity')
- hostnames = hosts.values()
+ hostnames = list(hosts.values())
self.assertEqual(hostnames[0], hostnames[1],
'Servers are on the different hosts: %s' % hosts)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 09dd409..624a99e 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -457,7 +457,7 @@
else:
msg = ('When validation.connect_method equals floating, '
'validation_resources cannot be None')
- raise exceptions.InvalidParam(invalid_param=msg)
+ raise lib_exc.InvalidParam(invalid_param=msg)
elif CONF.validation.connect_method == 'fixed':
addresses = server['addresses'][CONF.validation.network_for_ssh]
for address in addresses:
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 05c2a28..bea23d9 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -256,8 +256,8 @@
if not (CONF.auth.use_dynamic_credentials and
CONF.auth.create_isolated_networks and
not CONF.network.shared_physical_network):
- raise self.skipException("Only owner network supports "
- "creating interface by fixed ip.")
+ raise self.skipException("Only owner network supports "
+ "creating interface by fixed ip.")
server, ifs = self._create_server_get_interfaces()
interface_count = len(ifs)
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 18a78f0..f0915de 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -80,7 +80,7 @@
@decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
def test_list_servers_status_non_existing(self):
# When invalid status is specified, up to microversion 2.37,
- # an empty list is returnd, and starting from microversion 2.38,
+ # an empty list is returned, and starting from microversion 2.38,
# a 400 error is returned in that case.
if self.is_requested_microversion_compatible('2.37'):
body = self.client.list_servers(status='non_existing_status')
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 302a0e5..a79cbc3 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -9,6 +9,8 @@
# 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 testtools
+
from tempest.api.identity import base
from tempest import clients
from tempest import config
@@ -32,6 +34,10 @@
self.domains_client.update_domain(domain_id, enabled=False)
self.domains_client.delete_domain(domain_id)
+ @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.')
@decorators.idempotent_id('d6110661-6a71-49a7-a453-b5e26640ff6d')
def test_default_project_id(self):
# create a domain
diff --git a/tempest/api/identity/admin/v3/test_domain_configuration.py b/tempest/api/identity/admin/v3/test_domain_configuration.py
index c4e0622..c0b18ca 100644
--- a/tempest/api/identity/admin/v3/test_domain_configuration.py
+++ b/tempest/api/identity/admin/v3/test_domain_configuration.py
@@ -21,6 +21,10 @@
class DomainConfigurationTestJSON(base.BaseIdentityV3AdminTest):
+ # NOTE: force_tenant_isolation is true in the base class by default but
+ # overridden to false here to allow test execution for clouds using the
+ # pre-provisioned credentials provider.
+ force_tenant_isolation = False
custom_config = {
"identity": {
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 56f7d32..b3c68fb 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -20,6 +20,10 @@
class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+ # NOTE: force_tenant_isolation is true in the base class by default but
+ # overridden to false here to allow test execution for clouds using the
+ # pre-provisioned credentials provider.
+ force_tenant_isolation = False
@decorators.attr(type=['negative', 'gate'])
@decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
diff --git a/tempest/api/identity/admin/v3/test_endpoint_groups.py b/tempest/api/identity/admin/v3/test_endpoint_groups.py
index eef93c2..625568d 100644
--- a/tempest/api/identity/admin/v3/test_endpoint_groups.py
+++ b/tempest/api/identity/admin/v3/test_endpoint_groups.py
@@ -20,6 +20,10 @@
class EndPointGroupsTest(base.BaseIdentityV3AdminTest):
+ # NOTE: force_tenant_isolation is true in the base class by default but
+ # overridden to false here to allow test execution for clouds using the
+ # pre-provisioned credentials provider.
+ force_tenant_isolation = False
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/identity/admin/v3/test_oauth_consumers.py b/tempest/api/identity/admin/v3/test_oauth_consumers.py
index 062cce5..7a85f84 100644
--- a/tempest/api/identity/admin/v3/test_oauth_consumers.py
+++ b/tempest/api/identity/admin/v3/test_oauth_consumers.py
@@ -21,6 +21,10 @@
class OAUTHConsumersV3Test(base.BaseIdentityV3AdminTest):
+ # NOTE: force_tenant_isolation is true in the base class by default but
+ # overridden to false here to allow test execution for clouds using the
+ # pre-provisioned credentials provider.
+ force_tenant_isolation = False
def _create_consumer(self):
"""Creates a consumer with a random description."""
diff --git a/tempest/api/identity/admin/v3/test_project_tags.py b/tempest/api/identity/admin/v3/test_project_tags.py
index d05173b..b7878a8 100644
--- a/tempest/api/identity/admin/v3/test_project_tags.py
+++ b/tempest/api/identity/admin/v3/test_project_tags.py
@@ -25,6 +25,10 @@
class IdentityV3ProjectTagsTest(base.BaseIdentityV3AdminTest):
+ # NOTE: force_tenant_isolation is true in the base class by default but
+ # overridden to false here to allow test execution for clouds using the
+ # pre-provisioned credentials provider.
+ force_tenant_isolation = False
@decorators.idempotent_id('7c123aac-999d-416a-a0fb-84b915ab10de')
@testtools.skipUnless(CONF.identity_feature_enabled.project_tags,
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 33a9c8c..12f1d4a 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -22,6 +22,22 @@
class ProjectsNegativeTestJSON(base.BaseIdentityV3AdminTest):
@decorators.attr(type=['negative'])
+ @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
+ def test_project_delete_by_unauthorized_user(self):
+ # Non-admin user should not be able to delete a project
+ project = self.setup_test_project()
+ self.assertRaises(
+ lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
+ project['id'])
+
+
+class ProjectsNegativeStaticTestJSON(base.BaseIdentityV3AdminTest):
+ # NOTE: force_tenant_isolation is true in the base class by default but
+ # overridden to false here to allow test execution for clouds using the
+ # pre-provisioned credentials provider.
+ force_tenant_isolation = False
+
+ @decorators.attr(type=['negative'])
@decorators.idempotent_id('24c49279-45dd-4155-887a-cb738c2385aa')
def test_list_projects_by_unauthorized_user(self):
# Non-admin user should not be able to list projects
@@ -63,15 +79,6 @@
self.projects_client.create_project, project_name)
@decorators.attr(type=['negative'])
- @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97')
- def test_project_delete_by_unauthorized_user(self):
- # Non-admin user should not be able to delete a project
- project = self.setup_test_project()
- self.assertRaises(
- lib_exc.Forbidden, self.non_admin_projects_client.delete_project,
- project['id'])
-
- @decorators.attr(type=['negative'])
@decorators.idempotent_id('7965b581-60c1-43b7-8169-95d4ab7fc6fb')
def test_delete_non_existent_project(self):
# Attempt to delete a non existent project should fail
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 8ae43d6..5f1b58d 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -42,10 +42,10 @@
user = self.create_test_user(password=user_password)
# Create a couple projects
- project1_name = data_utils.rand_name(name='project')
+ project1_name = data_utils.rand_name(name=self.__class__.__name__)
project1 = self.setup_test_project(name=project1_name)
- project2_name = data_utils.rand_name(name='project')
+ project2_name = data_utils.rand_name(name=self.__class__.__name__)
project2 = self.setup_test_project(name=project2_name)
self.addCleanup(self.projects_client.delete_project, project2['id'])
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 83b3c30..54a5ab7 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -49,7 +49,8 @@
def create_trustor_and_roles(self):
# create a project that trusts will be granted on
- trustor_project_name = data_utils.rand_name(name='project')
+ trustor_project_name = data_utils.rand_name(
+ name=self.__class__.__name__)
project = self.projects_client.create_project(
trustor_project_name,
domain_id=CONF.identity.default_domain_id)['project']
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 3813568..8955a93 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -28,6 +28,14 @@
class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
+ @classmethod
+ def skip_checks(cls):
+ super(UsersV3TestJSON, cls).skip_checks()
+ if CONF.identity_feature_enabled.immutable_user_source:
+ raise cls.skipException('Skipped because environment has an '
+ 'immutable user source and solely '
+ 'provides read-only access to users.')
+
@decorators.idempotent_id('b537d090-afb9-4519-b95d-270b0708e87e')
def test_user_update(self):
# Test case to check if updating of user attributes is successful.
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index aa57daf..c938cee 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -185,7 +185,7 @@
for disk_fmt in disk_fmts]
for (container_fmt, disk_fmt) in all_pairs[:6]:
- LOG.debug("Creating an image"
+ LOG.debug("Creating an image "
"(Container format: %s, Disk format: %s).",
container_fmt, disk_fmt)
cls._create_standard_image(container_fmt, disk_fmt)
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 30ed176..eaf477c 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -48,11 +48,6 @@
agent.pop('configurations', None)
self.assertIn(self.agent, agents)
- @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
- def test_list_agents_non_admin(self):
- body = self.agents_client.list_agents()
- self.assertEmpty(body["agents"])
-
@decorators.idempotent_id('869bc8e8-0fda-4a30-9b71-f8a7cf58ca9f')
def test_show_agent(self):
body = self.admin_agents_client.show_agent(self.agent['id'])
@@ -95,4 +90,4 @@
non_existent_id = data_utils.rand_uuid()
self.assertRaises(
lib_exc.NotFound,
- self.agents_client.delete_agent, non_existent_id)
+ self.admin_agents_client.delete_agent, non_existent_id)
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index a7355f3..6ce86fb 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -111,7 +111,8 @@
def _verify_gateway_port(self, router_id):
list_body = self.admin_ports_client.list_ports(
network_id=CONF.network.public_network_id,
- device_id=router_id)
+ device_id=router_id,
+ device_owner="network:router_gateway")
self.assertEqual(len(list_body['ports']), 1)
gw_port = list_body['ports'][0]
fixed_ips = gw_port['fixed_ips']
diff --git a/tempest/api/network/test_agent_management_negative.py b/tempest/api/network/test_agent_management_negative.py
new file mode 100644
index 0000000..d1c02ce
--- /dev/null
+++ b/tempest/api/network/test_agent_management_negative.py
@@ -0,0 +1,28 @@
+# Copyright 2018 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.lib import decorators
+
+
+class AgentManagementNegativeTest(base.BaseNetworkTest):
+
+ @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
+ @decorators.attr(type=['negative'])
+ def test_list_agents_non_admin(self):
+ """Validate that non-admin user cannot list agents."""
+ # Listing agents requires admin_only permissions.
+ body = self.agents_client.list_agents()
+ self.assertEmpty(body["agents"])
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 399954c..3ab2909 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -167,7 +167,7 @@
self._clean_network()
self.assertNotEqual(eui_ip, real_ip,
('Real port IP %s equal to EUI-64 %s when '
- 'ipv6_ra_mode=Off and ipv6_address_mode=Off,'
+ 'ipv6_ra_mode=Off and ipv6_address_mode=Off, '
'but shall be taken from fixed IPs') % (
real_ip, eui_ip))
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index 915aeec..5c7ab15 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -45,13 +45,6 @@
cls.addClassResourceCleanup(cls.admin_quotas_client.update_quota_set,
cls.demo_tenant_id, **cleanup_quota_set)
- cls.shared_quota_set = {'gigabytes': 2 * CONF.volume.volume_size,
- 'volumes': 1}
-
- cls.admin_quotas_client.update_quota_set(
- cls.demo_tenant_id,
- **cls.shared_quota_set)
-
# NOTE(gfidente): no need to delete in tearDown as
# they are created using utility wrapper methods.
cls.volume = cls.create_volume()
@@ -59,6 +52,8 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('bf544854-d62a-47f2-a681-90f7a47d86b6')
def test_quota_volumes(self):
+ self.admin_quotas_client.update_quota_set(self.demo_tenant_id,
+ volumes=1, gigabytes=-1)
self.assertRaises(lib_exc.OverLimit,
self.volumes_client.create_volume,
size=CONF.volume.volume_size)
@@ -66,17 +61,18 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('2dc27eee-8659-4298-b900-169d71a91374')
def test_quota_volume_gigabytes(self):
- # NOTE(gfidente): quota set needs to be changed for this test
- # or we may be limited by the volumes or snaps quota number, not by
- # actual gigs usage; next line ensures shared set is restored.
- self.addCleanup(self.admin_quotas_client.update_quota_set,
- self.demo_tenant_id,
- **self.shared_quota_set)
- new_quota_set = {'gigabytes': CONF.volume.volume_size,
- 'volumes': 2, 'snapshots': 1}
self.admin_quotas_client.update_quota_set(
- self.demo_tenant_id,
- **new_quota_set)
+ self.demo_tenant_id, gigabytes=CONF.volume.volume_size, volumes=-1)
self.assertRaises(lib_exc.OverLimit,
self.volumes_client.create_volume,
- size=CONF.volume.volume_size)
+ size=CONF.volume.volume_size * 2)
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('d321dc21-d8c6-401f-95fe-49f4845f1a6d')
+ def test_volume_extend_gigabytes_quota_deviation(self):
+ self.admin_quotas_client.update_quota_set(
+ self.demo_tenant_id, gigabytes=CONF.volume.volume_size)
+ self.assertRaises(lib_exc.OverLimit,
+ self.volumes_client.extend_volume,
+ self.volume['id'],
+ new_size=CONF.volume.volume_size * 2)
diff --git a/tempest/clients.py b/tempest/clients.py
index 204ce08..e5d5be1 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -82,10 +82,8 @@
self.schemas_client = self.image_v2.SchemasClient()
self.namespace_properties_client = \
self.image_v2.NamespacePropertiesClient()
- self.namespace_tags_client = \
- self.image_v2.NamespaceTagsClient()
- self.image_versions_client = \
- self.image_v2.VersionsClient()
+ self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
+ self.image_versions_client = self.image_v2.VersionsClient()
def _set_compute_clients(self):
self.agents_client = self.compute.AgentsClient()
@@ -284,8 +282,7 @@
self.volume_v3.QuotaClassesClient()
self.volume_scheduler_stats_v2_client = \
self.volume_v3.SchedulerStatsClient()
- self.volume_transfers_v2_client = \
- self.volume_v3.TransfersClient()
+ self.volume_transfers_v2_client = self.volume_v3.TransfersClient()
self.volume_v2_availability_zone_client = \
self.volume_v3.AvailabilityZoneClient()
self.volume_v2_limits_client = self.volume_v3.LimitsClient()
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 6c2fee8..d25d3ca 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -365,11 +365,11 @@
catalog_type = getattr(cfg, 'catalog_type', None)
if not catalog_type:
continue
- else:
- if cfgname == 'identity':
- # Keystone is a required service for tempest
- continue
- if catalog_type not in services:
+ if cfgname == 'identity':
+ # Keystone is a required service for tempest
+ continue
+ if catalog_type not in services:
+ try:
if getattr(CONF.service_available, codename_match[cfgname]):
print('Endpoint type %s not found either disable service '
'%s or fix the catalog_type in the config file' % (
@@ -377,7 +377,13 @@
if update:
change_option(codename_match[cfgname],
'service_available', False)
- else:
+ except KeyError:
+ print('%s is a third party plugin, cannot be verified '
+ 'automatically, but it is suggested that it is set to '
+ 'False because %s service is not available ' % (
+ cfgname, catalog_type))
+ else:
+ try:
if not getattr(CONF.service_available,
codename_match[cfgname]):
print('Endpoint type %s is available, service %s should be'
@@ -391,6 +397,11 @@
avail_services.append(codename_match[cfgname])
else:
avail_services.append(codename_match[cfgname])
+ except KeyError:
+ print('%s is a third party plugin, cannot be verified '
+ 'automatically, but it is suggested that it is set to '
+ 'True because %s service is available ' % (
+ cfgname, catalog_type))
return avail_services
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 375113d..1489e60 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -168,10 +168,19 @@
'imageRef': image_id,
'size': CONF.volume.volume_size}
volume = volumes_client.create_volume(**params)
- waiters.wait_for_volume_resource_status(volumes_client,
- volume['volume']['id'],
- 'available')
-
+ try:
+ waiters.wait_for_volume_resource_status(volumes_client,
+ volume['volume']['id'],
+ 'available')
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ try:
+ volumes_client.delete_volume(volume['volume']['id'])
+ volumes_client.wait_for_resource_deletion(
+ volume['volume']['id'])
+ except Exception as exc:
+ LOG.exception("Deleting volume %s failed, exception %s",
+ volume['volume']['id'], exc)
bd_map_v2 = [{
'uuid': volume['volume']['id'],
'source_type': 'volume',
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 52ccfa9..49d9742 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -98,6 +98,7 @@
def get_nic_name_by_ip(self, address):
cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
nic = self.exec_command(cmd)
+ LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
return nic.strip().strip(":").split('@')[0].lower()
def get_dns_servers(self):
diff --git a/tempest/config.py b/tempest/config.py
index 18c9d9d..e431754 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -101,7 +101,7 @@
deprecated_group='identity'),
cfg.StrOpt('admin_domain_name',
default='Default',
- help="Admin domain name for authentication (Keystone V3)."
+ help="Admin domain name for authentication (Keystone V3). "
"The same domain applies to user and project",
deprecated_group='identity'),
]
@@ -191,7 +191,7 @@
help="The number of passwords for a user that must be unique "
"before an old password can be reused. This only takes "
"effect when identity-feature-enabled.security_compliance "
- "is set to 'True'."
+ "is set to 'True'. "
"This config option corresponds to keystone.conf: "
"security_compliance.unique_last_password_count, whose "
"default value is 0 meaning disabling this feature. "
@@ -262,7 +262,13 @@
cfg.BoolOpt('application_credentials',
default=False,
help='Does the environment have application credentials '
- 'enabled?')
+ 'enabled?'),
+ cfg.BoolOpt('immutable_user_source',
+ default=False,
+ help='Set to True if the environment has a read-only '
+ 'user source. This will skip all tests that attempt to '
+ 'create, delete, or modify users. This should not be set '
+ 'to True if using dynamic credentials')
]
compute_group = cfg.OptGroup(name='compute',
@@ -359,6 +365,38 @@
"with format 'X.Y' or string 'latest'"),
]
+placement_group = cfg.OptGroup(name='placement',
+ title='Placement Service Options')
+
+PlacementGroup = [
+ cfg.StrOpt('endpoint_type',
+ default='public',
+ choices=['public', 'admin', 'internal'],
+ help="The endpoint type to use for the placement service."),
+ cfg.StrOpt('catalog_type',
+ default='placement',
+ help="Catalog type of the Placement service."),
+ cfg.StrOpt('region',
+ default='RegionOne',
+ help="The placement region name to use. If empty, the value "
+ "of [identity]/region is used instead. If no such region "
+ "is found in the service catalog, the first region found "
+ "is used."),
+ cfg.StrOpt('min_microversion',
+ default=None,
+ help="Lower version of the test target microversion range. "
+ "The format is 'X.Y', where 'X' and 'Y' are int values. "
+ "Valid values are string with format 'X.Y' or string "
+ "'latest'"),
+ cfg.StrOpt('max_microversion',
+ default=None,
+ help="Upper version of the test target microversion range. "
+ "The format is 'X.Y', where 'X' and 'Y' are int values. "
+ "Valid values are string with format 'X.Y' or string "
+ "'latest'"),
+]
+
+
compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
title="Enabled Compute Service Features")
@@ -1090,6 +1128,7 @@
(scenario_group, ScenarioGroup),
(service_available_group, ServiceAvailableGroup),
(debug_group, DebugGroup),
+ (placement_group, PlacementGroup),
(None, DefaultGroup)
]
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 82fcd0b..ac40eef 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -233,8 +233,8 @@
if self._is_test_case(module, node))
for node in test_cases:
for subnode in filter(self._is_test_method, node.body):
- test_name = '%s.%s' % (node.name, subnode.name)
- tests[module_name]['tests'][test_name] = subnode
+ test_name = '%s.%s' % (node.name, subnode.name)
+ tests[module_name]['tests'][test_name] = subnode
return tests
@staticmethod
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index ec46caf..e612bd1 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -374,7 +374,7 @@
return self.request('COPY', url, extra_headers, headers)
def get_versions(self):
- """Get the versions on a endpoint from the keystone catalog
+ """Get the versions on an endpoint from the keystone catalog
This method will make a GET request on the baseurl from the keystone
catalog to return a list of API versions. It is expected that a GET
@@ -526,7 +526,7 @@
if (resp.status == 205 and
0 != len(set(resp.keys()) - set(('status',)) -
self.response_header_lc - self.general_header_lc)):
- raise exceptions.ResponseWithEntity()
+ raise exceptions.ResponseWithEntity()
# NOTE(afazekas)
# Now the swift sometimes (delete not empty container)
# returns with non json error response, we can create new rest class
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 438d73e..3483c51 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -50,8 +50,7 @@
(e.g. 'prefixfoo-namebar-154876201')
:rtype: string
"""
- randbits = str(random.randint(1, 0x7fffffff))
- rand_name = randbits
+ rand_name = str(random.randint(1, 0x7fffffff))
if name:
rand_name = name + '-' + rand_name
if prefix:
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 833cfd6..90debd9 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -18,7 +18,6 @@
import importlib
import inspect
import sys
-import warnings
from debtcollector import removals
from oslo_log import log as logging
@@ -32,9 +31,9 @@
from tempest.lib.services import image
from tempest.lib.services import network
from tempest.lib.services import object_storage
+from tempest.lib.services import placement
from tempest.lib.services import volume
-warnings.simplefilter("once")
LOG = logging.getLogger(__name__)
@@ -46,6 +45,7 @@
"""
return {
'compute': compute,
+ 'placement': placement,
'identity.v2': identity.v2,
'identity.v3': identity.v3,
'image.v1': image.v1,
diff --git a/tempest/lib/services/identity/v3/inherited_roles_client.py b/tempest/lib/services/identity/v3/inherited_roles_client.py
index 691c7fd..3949437 100644
--- a/tempest/lib/services/identity/v3/inherited_roles_client.py
+++ b/tempest/lib/services/identity/v3/inherited_roles_client.py
@@ -114,8 +114,7 @@
def check_user_has_flag_on_inherited_to_project(
self, project_id, user_id, role_id):
- """Checks whether a user has a role assignment"""
- """with the inherited_to_projects flag on a project."""
+ """Check if user has an inherited project role on project"""
resp, body = self.head(
"OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
% (project_id, user_id, role_id))
@@ -142,8 +141,7 @@
def check_group_has_flag_on_inherited_to_project(
self, project_id, group_id, role_id):
- """Checks whether a group has a role assignment"""
- """with the inherited_to_projects flag on a project."""
+ """Check if group has an inherited project role on project"""
resp, body = self.head(
"OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
% (project_id, group_id, role_id))
diff --git a/tempest/lib/services/placement/__init__.py b/tempest/lib/services/placement/__init__.py
new file mode 100644
index 0000000..5c20c57
--- /dev/null
+++ b/tempest/lib/services/placement/__init__.py
@@ -0,0 +1,18 @@
+# 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.placement.placement_client import \
+ PlacementClient
+
+__all__ = ['PlacementClient']
diff --git a/tempest/lib/services/placement/base_placement_client.py b/tempest/lib/services/placement/base_placement_client.py
new file mode 100644
index 0000000..505a515
--- /dev/null
+++ b/tempest/lib/services/placement/base_placement_client.py
@@ -0,0 +1,43 @@
+# 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.common import api_version_utils
+from tempest.lib.common import rest_client
+
+PLACEMENT_MICROVERSION = None
+
+
+class BasePlacementClient(rest_client.RestClient):
+
+ api_microversion_header_name = 'OpenStack-API-Version'
+ version_header_value = 'placement %s'
+
+ def get_headers(self):
+ headers = super(BasePlacementClient, self).get_headers()
+ if PLACEMENT_MICROVERSION:
+ headers[self.api_microversion_header_name] = \
+ self.version_header_value % PLACEMENT_MICROVERSION
+ return headers
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+ resp, resp_body = super(BasePlacementClient, self).request(
+ method, url, extra_headers, headers, body, chunked)
+ if (PLACEMENT_MICROVERSION and
+ PLACEMENT_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+ api_version_utils.assert_version_header_matches_request(
+ self.api_microversion_header_name,
+ self.version_header_value % PLACEMENT_MICROVERSION,
+ resp)
+ return resp, resp_body
diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py
new file mode 100644
index 0000000..2c6d919
--- /dev/null
+++ b/tempest/lib/services/placement/placement_client.py
@@ -0,0 +1,50 @@
+# 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_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.placement import base_placement_client
+
+
+class PlacementClient(base_placement_client.BasePlacementClient):
+
+ def list_allocation_candidates(self, **params):
+ """List allocation candidates.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/placement/#list-allocation-candidates
+ """
+ url = '/allocation_candidates'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_allocations(self, consumer_uuid):
+ """List all allocation records for the consumer.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/placement/#list-allocations
+ """
+ url = '/allocations/%s' % consumer_uuid
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e94ce3d..4be2b29 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -230,6 +230,32 @@
self.assertNotEqual(src_host, dst_host)
+ @decorators.idempotent_id('03fd1562-faad-11e7-9ea0-fa163e65f5ce')
+ @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
+ 'Live migration is not available.')
+ @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+ 'Less than 2 compute nodes, skipping multinode '
+ 'tests.')
+ @decorators.attr(type='slow')
+ @utils.services('compute', 'network')
+ def test_server_connectivity_live_migration(self):
+ keypair = self.create_keypair()
+ server = self._setup_server(keypair)
+ floating_ip = self._setup_network(server, keypair)
+ self._wait_server_status_and_check_network_connectivity(
+ server, keypair, floating_ip)
+
+ block_migration = (CONF.compute_feature_enabled.
+ block_migration_for_live_migration)
+ self.admin_servers_client.live_migrate_server(
+ server['id'], host=None, block_migration=block_migration,
+ disk_over_commit=False)
+ waiters.wait_for_server_status(self.servers_client,
+ server['id'], 'ACTIVE')
+
+ self._wait_server_status_and_check_network_connectivity(
+ server, keypair, floating_ip)
+
@decorators.skip_because(bug='1788403')
@decorators.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index c11070c..7992585 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -579,10 +579,17 @@
initial_dns_server = '1.2.3.4'
alt_dns_server = '9.8.7.6'
- # renewal should be immediate.
- # Timeouts are suggested by salvatore-orlando in
+ # Original timeouts are suggested by salvatore-orlando in
# https://bugs.launchpad.net/neutron/+bug/1412325/comments/3
- renew_delay = CONF.network.build_interval
+ #
+ # Compared to that renew_delay was increased, because
+ # busybox's udhcpc accepts SIGUSR1 as a renew request. Internally
+ # it goes into RENEW_REQUESTED state. If it receives a 2nd SIGUSR1
+ # signal while in that state then it calls the deconfig script
+ # ("/sbin/cirros-dhcpc deconfig" in sufficiently new cirros versions)
+ # which leads to the address being transiently deconfigured which
+ # for our case is unwanted.
+ renew_delay = 3 * CONF.network.build_interval
renew_timeout = CONF.network.build_timeout
self._setup_network_and_servers(dns_nameservers=[initial_dns_server])
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index e159cdc..5091841 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -108,6 +108,27 @@
subprocess.call(['stestr', 'init'])
self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0)
+ def test_tempest_run_failing(self):
+ self.assertRunExit(['tempest', 'run', '--regex', 'failing'], 1)
+
+ def test_tempest_run_failing_with_stestr_repository(self):
+ subprocess.call(['stestr', 'init'])
+ self.assertRunExit(['tempest', 'run', '--regex', 'failing'], 1)
+
+ def test_tempest_run_blackregex_failing(self):
+ self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0)
+
+ def test_tempest_run_blackregex_failing_with_stestr_repository(self):
+ subprocess.call(['stestr', 'init'])
+ self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0)
+
+ def test_tempest_run_blackregex_passing(self):
+ self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+
+ def test_tempest_run_blackregex_passing_with_stestr_repository(self):
+ subprocess.call(['stestr', 'init'])
+ self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+
def test_tempest_run_fails(self):
self.assertRunExit(['tempest', 'run'], 1)
diff --git a/tempest/tests/files/setup.cfg b/tempest/tests/files/setup.cfg
index bd68708..a81d31e 100644
--- a/tempest/tests/files/setup.cfg
+++ b/tempest/tests/files/setup.cfg
@@ -3,7 +3,7 @@
version = 1
summary = Fake Project for testing wrapper scripts
author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/tempest/latest/
classifier =
Intended Audience :: Information Technology
diff --git a/tempest/tests/lib/common/test_http.py b/tempest/tests/lib/common/test_http.py
index 336ef4a..a19153f 100644
--- a/tempest/tests/lib/common/test_http.py
+++ b/tempest/tests/lib/common/test_http.py
@@ -170,9 +170,6 @@
class TestClosingHttpRedirects(base.TestCase):
- def setUp(self):
- super(TestClosingHttpRedirects, self).setUp()
-
def test_redirect_default(self):
connection = http.ClosingHttp()
self.assertTrue(connection.follow_redirects)
@@ -183,9 +180,6 @@
class TestClosingProxyHttpRedirects(base.TestCase):
- def setUp(self):
- super(TestClosingProxyHttpRedirects, self).setUp()
-
def test_redirect_default(self):
connection = http.ClosingProxyHttp(proxy_url=PROXY_URL)
self.assertTrue(connection.follow_redirects)
diff --git a/tempest/tests/lib/services/placement/__init__.py b/tempest/tests/lib/services/placement/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/placement/__init__.py
diff --git a/tempest/tests/lib/services/placement/test_placement_client.py b/tempest/tests/lib/services/placement/test_placement_client.py
new file mode 100644
index 0000000..1396a85
--- /dev/null
+++ b/tempest/tests/lib/services/placement/test_placement_client.py
@@ -0,0 +1,89 @@
+# 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.placement import placement_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestPlacementClient(base.BaseServiceTest):
+ FAKE_ALLOCATION_CANDIDATES = {
+ 'allocation_requests': [
+ {'allocations': {
+ 'rp-uuid': {'resources': {'VCPU': 42}}
+ }}
+ ],
+ 'provider_summaries': {
+ 'rp-uuid': {
+ 'resources': {
+ 'VCPU': {'used': 0, 'capacity': 64},
+ 'MEMORY_MB': {'capacity': 11196, 'used': 0},
+ 'DISK_GB': {'capacity': 19, 'used': 0}
+ },
+ 'traits': ["HW_CPU_X86_SVM"],
+ }
+ }
+ }
+
+ FAKE_ALLOCATIONS = {
+ 'allocations': {
+ 'rp-uuid-1': {
+ 'resources': {
+ 'NET_BW_IGR_KILOBIT_PER_SEC': 1
+ },
+ 'generation': 14
+ },
+ 'rp-uuid2': {
+ 'resources': {
+ 'MEMORY_MB': 256,
+ 'VCPU': 1
+ },
+ 'generation': 9
+ }
+ }
+ }
+
+ def setUp(self):
+ super(TestPlacementClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = placement_client.PlacementClient(
+ fake_auth, 'placement', 'regionOne')
+
+ def _test_list_allocation_candidates(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_allocation_candidates,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ALLOCATION_CANDIDATES,
+ to_utf=bytes_body,
+ **{'resources1': 'NET_BW_IGR_KILOBIT_PER_SEC:1'})
+
+ def test_list_allocation_candidates_with_str_body(self):
+ self._test_list_allocation_candidates()
+
+ def test_list_allocation_candidates_with_bytes_body(self):
+ self._test_list_allocation_candidates(bytes_body=True)
+
+ def _test_list_allocations(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_allocations,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ALLOCATIONS,
+ to_utf=bytes_body,
+ **{'consumer_uuid': 'foo-bar'})
+
+ def test_list_allocations_with_str_body(self):
+ self._test_list_allocations()
+
+ def test_list_allocations_with_bytes_body(self):
+ self._test_list_allocations(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_types_client.py b/tempest/tests/lib/services/volume/v3/test_types_client.py
new file mode 100644
index 0000000..7021a3f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_types_client.py
@@ -0,0 +1,281 @@
+# 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.volume.v3 import types_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTypesClient(base.BaseServiceTest):
+ FAKE_CREATE_VOLUME_TYPE = {
+ 'volume_type': {
+ 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+ 'name': 'vol-type-001',
+ 'description': 'volume type 0001',
+ 'is_public': True,
+ 'os-volume-type-access:is_public': True,
+ 'extra_specs': {
+ 'volume_backend_name': 'rbd'
+ }
+ }
+ }
+
+ FAKE_DEFAULT_VOLUME_TYPE_INFO = {
+ 'volume_type': {
+ 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+ 'qos_specs_id': None,
+ 'name': 'volume-type-test',
+ 'description': 'default volume type',
+ 'is_public': True,
+ 'os-volume-type-access:is_public': True,
+ 'extra_specs': {
+ 'volume_backend_name': 'rbd'
+ }
+ }
+ }
+
+ FAKE_UPDATE_VOLUME_TYPE = {
+ 'volume_type': {
+ 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+ 'qos_specs_id': None,
+ 'name': 'volume-type-test',
+ 'description': 'default volume type',
+ 'os-volume-type-access:is_public': True,
+ 'is_public': True,
+ 'extra_specs': {
+ 'volume_backend_name': 'rbd'
+ }
+ }
+ }
+
+ FAKE_VOLUME_TYPES = {
+ 'volume_types': [
+ {
+ 'name': 'volume_type01',
+ 'qos_specs_id': None,
+ 'extra_specs': {
+ 'volume_backend_name': 'lvmdriver-1'
+ },
+ 'os-volume-type-access:is_public': True,
+ 'is_public': True,
+ 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff',
+ 'description': None
+ },
+ {
+ 'name': 'volume_type02',
+ 'qos_specs_id': None,
+ 'extra_specs': {
+ 'volume_backend_name': 'lvmdriver-1'
+ },
+ 'os-volume-type-access:is_public': True,
+ 'is_public': True,
+ 'id': '8eb69a46-df97-4e41-9586-9a40a7533803',
+ 'description': None
+ }
+ ]
+ }
+
+ FAKE_VOLUME_TYPE_EXTRA_SPECS = {
+ 'extra_specs': {
+ 'capabilities': 'gpu'
+ }
+ }
+
+ FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS = {
+ 'capabilities': 'gpu'
+ }
+
+ FAKE_VOLUME_TYPE_ACCESS = {
+ 'volume_type_access': [{
+ 'volume_type_id': '3c67e124-39ad-4ace-a507-8bb7bf510c26',
+ 'project_id': 'f270b245cb11498ca4031deb7e141cfa'
+ }]
+ }
+
+ def setUp(self):
+ super(TestTypesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = types_client.TypesClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_list_volume_types(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_volume_types,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_TYPES,
+ bytes_body)
+
+ def _test_show_volume_type(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_type,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_DEFAULT_VOLUME_TYPE_INFO,
+ to_utf=bytes_body,
+ volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff")
+
+ def _test_create_volume_type(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_volume_type,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_VOLUME_TYPE,
+ to_utf=bytes_body,
+ name='volume-type-test')
+
+ def _test_delete_volume_type(self):
+ self.check_service_client_function(
+ self.client.delete_volume_type,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+ def _test_list_volume_types_extra_specs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_volume_types_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+ to_utf=bytes_body,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+ def _test_show_volume_type_extra_specs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_type_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+ extra_specs_name='capabilities',
+ to_utf=bytes_body)
+
+ def _test_create_volume_type_extra_specs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_volume_type_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+ volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff",
+ extra_specs=self.FAKE_VOLUME_TYPE_EXTRA_SPECS,
+ to_utf=bytes_body)
+
+ def _test_delete_volume_type_extra_specs(self):
+ self.check_service_client_function(
+ self.client.delete_volume_type_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+ extra_spec_name='volume_backend_name')
+
+ def _test_update_volume_type(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_volume_type,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_VOLUME_TYPE,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+ to_utf=bytes_body,
+ name='update-volume-type-test',
+ description='test update volume type description')
+
+ def _test_update_volume_type_extra_specs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_volume_type_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+ extra_spec_name='capabilities',
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff',
+ extra_specs=self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS,
+ to_utf=bytes_body)
+
+ def _test_add_type_access(self):
+ self.check_service_client_function(
+ self.client.add_type_access,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, status=202,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+ def _test_remove_type_access(self):
+ self.check_service_client_function(
+ self.client.remove_type_access,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, status=202,
+ volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff')
+
+ def _test_list_type_access(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_type_access,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_TYPE_ACCESS,
+ volume_type_id='3c67e124-39ad-4ace-a507-8bb7bf510c26',
+ to_utf=bytes_body)
+
+ def test_list_volume_types_with_str_body(self):
+ self._test_list_volume_types()
+
+ def test_list_volume_types_with_bytes_body(self):
+ self._test_list_volume_types(bytes_body=True)
+
+ def test_show_volume_type_with_str_body(self):
+ self._test_show_volume_type()
+
+ def test_show_volume_type_with_bytes_body(self):
+ self._test_show_volume_type(bytes_body=True)
+
+ def test_create_volume_type_str_body(self):
+ self._test_create_volume_type()
+
+ def test_create_volume_type_with_bytes_body(self):
+ self._test_create_volume_type(bytes_body=True)
+
+ def test_list_volume_types_extra_specs_with_str_body(self):
+ self._test_list_volume_types_extra_specs()
+
+ def test_list_volume_types_extra_specs_with_bytes_body(self):
+ self._test_list_volume_types_extra_specs(bytes_body=True)
+
+ def test_show_volume_type_extra_specs_with_str_body(self):
+ self._test_show_volume_type_extra_specs()
+
+ def test_show_volume_type_extra_specs_with_bytes_body(self):
+ self._test_show_volume_type_extra_specs(bytes_body=True)
+
+ def test_create_volume_type_extra_specs_with_str_body(self):
+ self._test_create_volume_type_extra_specs()
+
+ def test_create_volume_type_extra_specs_with_bytes_body(self):
+ self._test_create_volume_type_extra_specs(bytes_body=True)
+
+ def test_delete_volume_type_extra_specs(self):
+ self._test_delete_volume_type_extra_specs()
+
+ def test_update_volume_type_with_str_body(self):
+ self._test_update_volume_type()
+
+ def test_update_volume_type_with_bytes_body(self):
+ self._test_update_volume_type(bytes_body=True)
+
+ def test_delete_volume_type(self):
+ self._test_delete_volume_type()
+
+ def test_update_volume_type_extra_specs_with_str_body(self):
+ self._test_update_volume_type_extra_specs()
+
+ def test_update_volume_type_extra_specs_with_bytes_body(self):
+ self._test_update_volume_type_extra_specs(bytes_body=True)
+
+ def test_add_type_access(self):
+ self._test_add_type_access()
+
+ def test_remove_type_access(self):
+ self._test_remove_type_access()
+
+ def test_list_type_access_with_str_body(self):
+ self._test_list_type_access()
+
+ def test_list_type_access_with_bytes_body(self):
+ self._test_list_type_access(bytes_body=True)
diff --git a/tox.ini b/tox.ini
index 4d3c622..4068054 100644
--- a/tox.ini
+++ b/tox.ini
@@ -48,6 +48,10 @@
coverage xml -o cover/coverage.xml
coverage report
+[testenv:debug]
+basepython = python3
+commands = oslo_debug_helper -t tempest/tests {posargs}
+
[testenv:all]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}