Merge "Remove deprecated scenario img_dir option"
diff --git a/REVIEWING.rst b/REVIEWING.rst
index e07e358..4c63aa0 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -160,13 +160,11 @@
When to approve
---------------
* It's OK to hold off on an approval until a subject matter expert reviews it.
-* Every patch needs two +2's before being approved.
-* However, a single Tempest core reviewer can approve patches without waiting
- for another +2 in the following cases:
+* Every patch needs at least single +2's before being approved. A single
+ Tempest core reviewer can approve patches but can always wait for another
+ +2 in any case. Following cases where single +2 can be used without any
+ issue:
- * If a patch has already been approved but requires a trivial rebase to
- merge, then there is no need to wait for a second +2, since the patch has
- already had two +2's.
* If any trivial patch set fixes one of the items below:
* Documentation or code comment typo
@@ -187,7 +185,4 @@
voting ``tempest-tox-plugin-sanity-check`` job) and unblock the
tempest gate
- Note that such a policy should be used judiciously, as we should strive to
- have two +2's on each patch set, prior to approval.
-
.. _example: https://review.opendev.org/#/c/611032/
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index c43e420..c790c5f 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -153,6 +153,11 @@
Pre-Provisioned Credentials are also known as accounts.yaml or accounts file.
+Keystone Scopes & Roles Support in Tempest
+""""""""""""""""""""""""""""""""""""""""""
+For details on scope and roles support in Tempest,
+please refer to :doc:`this document <keystone_scopes_and_roles_support>`
+
Compute
-------
diff --git a/doc/source/contributor/contributing.rst b/doc/source/contributor/contributing.rst
index 9c79a1f..62953ff 100644
--- a/doc/source/contributor/contributing.rst
+++ b/doc/source/contributor/contributing.rst
@@ -43,10 +43,9 @@
Getting Your Patch Merged
~~~~~~~~~~~~~~~~~~~~~~~~~
-All changes proposed to the Tempest require two ``Code-Review +2`` votes from
-Tempest core reviewers before one of the core reviewers can approve the patch by
-giving ``Workflow +1`` vote. More detailed guidelines for reviewers are available
-at :doc:`../REVIEWING`.
+All changes proposed to the Tempest require single ``Code-Review +2`` votes from
+Tempest core reviewers by giving ``Workflow +1`` vote. More detailed guidelines
+for reviewers are available at :doc:`../REVIEWING`.
Project Team Lead Duties
~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/source/data/tempest-blacklisted-plugins-registry.header b/doc/source/data/tempest-non-active-plugins-registry.header
similarity index 67%
rename from doc/source/data/tempest-blacklisted-plugins-registry.header
rename to doc/source/data/tempest-non-active-plugins-registry.header
index 6b6af11..06d8eaa 100644
--- a/doc/source/data/tempest-blacklisted-plugins-registry.header
+++ b/doc/source/data/tempest-non-active-plugins-registry.header
@@ -1,7 +1,7 @@
-Blacklisted Plugins
+Non Active 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
+time (6 months or more). They can be moved out of nonactivelist state once one
of the relevant patches gets merged:
https://review.opendev.org/#/q/topic:tempest-sanity-gate+%28status:open%29
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 66e68ea..2f29cf2 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -105,6 +105,14 @@
tempest_and_plugins_compatible_version_policy
+Keystone Scopes & Roles Support in Tempest
+------------------------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ keystone_scopes_and_roles_support
+
Stable Branch Support Policy
----------------------------
diff --git a/doc/source/keystone_scopes_and_roles_support.rst b/doc/source/keystone_scopes_and_roles_support.rst
new file mode 100644
index 0000000..f446f8c
--- /dev/null
+++ b/doc/source/keystone_scopes_and_roles_support.rst
@@ -0,0 +1,286 @@
+Keystone Scopes & Roles Support in Tempest
+==========================================
+
+OpenStack Keystone supports different scopes in token, refer to the
+`Keystone doc <https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes>`_.
+Along with the scopes, keystone supports default roles, one of which
+is a reader role, for details refer to
+`this keystone document <https://docs.openstack.org/keystone/latest/admin/service-api-protection.html>`_.
+
+Tempest supports those scopes and roles credentials that can be used
+to test APIs under different scope and roles.
+
+Dynamic Credentials
+-------------------
+
+Dynamic credential supports all the below set of personas and allows
+you to generate credentials tailored to a specific persona that you
+can use in your test.
+
+Domain scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+ #. Domain Admin: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['domain_admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_d_admin_client = (
+ cls.os_domain_admin.availability_zone_client)
+
+ #. Domain Member: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['domain_member']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_d_member_client = (
+ cls.os_domain_member.availability_zone_client)
+
+ #. Domain Reader: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['domain_reader']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_d_reader_client = (
+ cls.os_domain_reader.availability_zone_client)
+
+ #. Domain other roles: This is supported and can be requested and used from
+ the test as below:
+
+ You need to use the ``domain`` as the prefix in credentials type, and
+ based on that, Tempest will create test users under 'domain' scope.
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = [['domain_my_role1', 'my_own_role1', 'admin']
+ ['domain_my_role2', 'my_own_role2']]
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_d_role1_client = (
+ cls.os_domain_my_role1.availability_zone_client)
+ cls.az_d_role2_client = (
+ cls.os_domain_my_role2.availability_zone_client)
+
+System scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^
+
+ #. System Admin: This is supported and can be requested and used from the
+ test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['system_admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_s_admin_client = (
+ cls.os_system_admin.availability_zone_client)
+
+ #. System Member: This is supported and can be requested and used from the
+ test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['system_member']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_s_member_client = (
+ cls.os_system_member.availability_zone_client)
+
+ #. System Reader: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['system_reader']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_s_reader_client = (
+ cls.os_system_reader.availability_zone_client)
+
+ #. System other roles: This is supported and can be requested and used from
+ the test as below:
+
+ You need to use the ``system`` as the prefix in credentials type, and
+ based on that, Tempest will create test users under 'project' scope.
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = [['system_my_role1', 'my_own_role1', 'admin']
+ ['system_my_role2', 'my_own_role2']]
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_s_role1_client = (
+ cls.os_system_my_role1.availability_zone_client)
+ cls.az_s_role2_client = (
+ cls.os_system_my_role2.availability_zone_client)
+
+Project scoped personas:
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+ #. Project Admin: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['project_admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_p_admin_client = (
+ cls.os_project_admin.availability_zone_client)
+
+ #. Project Member: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['project_member']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_p_member_client = (
+ cls.os_project_member.availability_zone_client)
+
+ #. Project Reader: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['project_reader']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_p_reader_client = (
+ cls.os_project_reader.availability_zone_client)
+
+ #. Project alternate Admin: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['project_alt_admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_p_alt_admin_client = (
+ cls.os_project_alt_admin.availability_zone_client)
+
+ #. Project alternate Member: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['project_alt_member']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_p_alt_member_client = (
+ cls.os_project_alt_member.availability_zone_client)
+
+ #. Project alternate Reader: This is supported and can be requested and used from
+ the test as below:
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = ['project_alt_reader']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_p_alt_reader_client = (
+ cls.os_project_alt_reader.availability_zone_client)
+
+ #. Project other roles: This is supported and can be requested and used from
+ the test as below:
+
+ You need to use the ``project`` as the prefix in credentials type, and
+ based on that, Tempest will create test users under 'project' scope.
+
+ .. code-block:: python
+
+ class TestDummy(base.DummyBaseTest):
+
+ credentials = [['project_my_role1', 'my_own_role1', 'admin']
+ ['project_my_role2', 'my_own_role2']]
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDummy, cls).setup_clients()
+ cls.az_role1_client = (
+ cls.os_project_my_role1.availability_zone_client)
+ cls.az_role2_client = (
+ cls.os_project_my_role2.availability_zone_client)
+
+Pre-Provisioned Credentials
+---------------------------
+
+Pre-Provisioned credentials support the below set of personas and can be
+used in the test as shown above in the ``Dynamic Credentials`` Section.
+
+* Domain Admin
+* Domain Member
+* Domain Reader
+* System Admin
+* System Member
+* System Reader
+* Project Admin
+* Project Member
+* Project Reader
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index c1981f9..06062c2 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -302,6 +302,10 @@
.. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2
+ * `2.3`_
+
+ .. _2.3: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-kilo
+
* `2.6`_
.. _2.6: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id5
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
index e51b90b..2eaf72f 100644
--- a/doc/source/overview.rst
+++ b/doc/source/overview.rst
@@ -113,7 +113,7 @@
There is also the option to use `stestr`_ directly. For example, from
the workspace dir run::
- $ stestr run --black-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
+ $ stestr run --exclude-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
will run the same set of tests as the default gate jobs. Or you can
use `unittest`_ compatible test runners such as `stestr`_, `pytest`_ etc.
diff --git a/doc/source/stable_branch_support_policy.rst b/doc/source/stable_branch_support_policy.rst
index 87e3ad1..9c2d1ed 100644
--- a/doc/source/stable_branch_support_policy.rst
+++ b/doc/source/stable_branch_support_policy.rst
@@ -20,7 +20,7 @@
testing branches in these phases, it's possible that we'll introduce changes to
Tempest on master which will break support on *Extended Maintenance* phase
branches. When this happens the expectation for those branches is to either
-switch to running Tempest from a tag with support for the branch, or blacklist
+switch to running Tempest from a tag with support for the branch, or exclude
a newly introduced test (if that is the cause of the issue). Tempest will not
be creating stable branches to support *Extended Maintenance* phase branches, as
the burden is on the *Extended Maintenance* phase branche maintainers, not the Tempest
diff --git a/etc/whitelist.yaml b/etc/allow-list.yaml
similarity index 100%
rename from etc/whitelist.yaml
rename to etc/allow-list.yaml
diff --git a/etc/rbac-persona-accounts.yaml.sample b/etc/rbac-persona-accounts.yaml.sample
new file mode 100644
index 0000000..0b59538
--- /dev/null
+++ b/etc/rbac-persona-accounts.yaml.sample
@@ -0,0 +1,108 @@
+- user_domain_name: Default
+ password: password
+ roles:
+ - admin
+ username: tempest-system-admin-1
+ system: all
+- user_domain_name: Default
+ password: password
+ username: tempest-system-member-1
+ roles:
+ - member
+ system: all
+- user_domain_name: Default
+ password: password
+ username: tempest-system-reader-1
+ roles:
+ - reader
+ system: all
+- user_domain_name: Default
+ password: password
+ domain_name: tempest-test-domain
+ username: tempest-domain-admin-1
+ roles:
+ - admin
+- user_domain_name: Default
+ password: password
+ domain_name: tempest-test-domain
+ username: tempest-domain-member-1
+ roles:
+ - member
+- user_domain_name: Default
+ password: password
+ domain_name: tempest-test-domain
+ username: tempest-domain-reader-1
+ roles:
+ - reader
+- user_domain_name: Default
+ password: password
+ project_name: tempest-test-project
+ username: tempest-project-admin-1
+ roles:
+ - admin
+- user_domain_name: Default
+ password: password
+ project_name: tempest-test-project
+ username: tempest-project-member-1
+ roles:
+ - member
+- user_domain_name: Default
+ password: password
+ project_name: tempest-test-project
+ username: tempest-project-reader-1
+ roles:
+ - reader
+- user_domain_name: Default
+ password: password
+ username: tempest-system-admin-2
+ roles:
+ - admin
+ system: all
+- user_domain_name: Default
+ password: password
+ username: tempest-system-member-2
+ roles:
+ - member
+ system: all
+- user_domain_name: Default
+ password: password
+ system: all
+ username: tempest-system-reader-2
+ roles:
+ - reader
+- user_domain_name: Default
+ password: password
+ domain_name: tempest-test-domain
+ username: tempest-domain-admin-2
+ roles:
+ - admin
+- user_domain_name: Default
+ password: password
+ domain_name: tempest-test-domain
+ username: tempest-domain-member-2
+ roles:
+ - member
+- user_domain_name: Default
+ password: password
+ domain_name: tempest-test-domain
+ username: tempest-domain-reader-2
+ roles:
+ - reader
+- user_domain_name: Default
+ password: password
+ project_name: tempest-test-project
+ username: tempest-project-admin-2
+ roles:
+ - admin
+- user_domain_name: Default
+ password: password
+ project_name: tempest-test-project
+ username: tempest-project-member-2
+ roles:
+ - member
+- user_domain_name: Default
+ password: password
+ project_name: tempest-test-project
+ username: tempest-project-reader-2
+ roles:
+ - reader
diff --git a/releasenotes/notes/Add-keystone-v3-OS_FEDERATION-APIs-as-tempest-clients-fe9e10a0fe5f09d4.yaml b/releasenotes/notes/Add-keystone-v3-OS_FEDERATION-APIs-as-tempest-clients-fe9e10a0fe5f09d4.yaml
new file mode 100644
index 0000000..33df7c4
--- /dev/null
+++ b/releasenotes/notes/Add-keystone-v3-OS_FEDERATION-APIs-as-tempest-clients-fe9e10a0fe5f09d4.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ The following tempest clients for keystone v3 OS_FEDERATION API were
+ implemented in this release
+
+ * identity_providers
+ * protocols
+ * mappings
+ * service_providers
diff --git a/releasenotes/notes/Inclusive-jargon-17621346744f0cf4.yaml b/releasenotes/notes/Inclusive-jargon-17621346744f0cf4.yaml
new file mode 100644
index 0000000..089569e
--- /dev/null
+++ b/releasenotes/notes/Inclusive-jargon-17621346744f0cf4.yaml
@@ -0,0 +1,13 @@
+---
+deprecations:
+ - |
+ In this release the following tempest arguments are deprecated and
+ replaced by new ones which are functionally equivalent:
+
+ * --black-regex is replaced by --exclude-regex
+ * --blacklist-file is replaced by --exclude-list
+ * --whitelist-file is replaced by --include-list
+
+ For now Tempest supports both (new and old ones) in order to make the
+ transition for all consumers smoother. However, that's just a temporary
+ case and the old options will be removed soon.
diff --git a/releasenotes/notes/Remove-manager-2e0b0af48f01294a.yaml b/releasenotes/notes/Remove-manager-2e0b0af48f01294a.yaml
new file mode 100644
index 0000000..822df7d
--- /dev/null
+++ b/releasenotes/notes/Remove-manager-2e0b0af48f01294a.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ In this release tempest/manager.py is removed after more than 4 years
+ of deprecation.
diff --git a/releasenotes/notes/add-alt-project-dynamic-creds-1a3bc543e65d9433.yaml b/releasenotes/notes/add-alt-project-dynamic-creds-1a3bc543e65d9433.yaml
new file mode 100644
index 0000000..de81b2b
--- /dev/null
+++ b/releasenotes/notes/add-alt-project-dynamic-creds-1a3bc543e65d9433.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add project alternate admin, member and reader role for dynamic credentials.
diff --git a/releasenotes/notes/add-compute-feature-ide-bus-b63802502c378083.yaml b/releasenotes/notes/add-compute-feature-ide-bus-b63802502c378083.yaml
new file mode 100644
index 0000000..43a0e8c
--- /dev/null
+++ b/releasenotes/notes/add-compute-feature-ide-bus-b63802502c378083.yaml
@@ -0,0 +1,10 @@
+---
+other:
+ - |
+ A new ``[compute-feature-enabled]ide_bus`` config option has been
+ introduced to indicate if the environment supports attaching disks to an
+ instance using an ``IDE`` bus.
+
+ This currently defaults to ``True`` but should be set to ``False`` when
+ using the libvirt OpenStack Nova virt driver *and* the ``q35`` machine type
+ as support for this bus is no longer provided.
diff --git a/releasenotes/notes/add-identity-roles-system-methods-519dc144231993a3.yaml b/releasenotes/notes/add-identity-roles-system-methods-519dc144231993a3.yaml
new file mode 100644
index 0000000..1840c10
--- /dev/null
+++ b/releasenotes/notes/add-identity-roles-system-methods-519dc144231993a3.yaml
@@ -0,0 +1,13 @@
+---
+features:
+ - |
+ Added methods to the identity v3 roles client to support:
+
+ - PUT /v3/system/users/{user}/roles/{role}
+ - GET /v3/system/users/{user}/roles
+ - GET /v3/system/users/{user}/roles/{role}
+ - DELETE /v3/system/users/{user}/roles/{role}
+ - PUT /v3/system/groups/{group}/roles/{role}
+ - GET /v3/system/groups/{group}/roles
+ - GET /v3/system/groups/{group}/roles/{role}
+ - DELETE /v3/system/groups/{group}/roles/{role}
diff --git a/releasenotes/notes/create_loginable_secgroup_rule-73722fd4b4eb12d0.yaml b/releasenotes/notes/create_loginable_secgroup_rule-73722fd4b4eb12d0.yaml
new file mode 100644
index 0000000..e53411d
--- /dev/null
+++ b/releasenotes/notes/create_loginable_secgroup_rule-73722fd4b4eb12d0.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Added public interface create_loginable_secgroup_rule().
+ Since this interface is meant to be used by tempest plugins,
+ It doesn't neccessarily require to be private api.
diff --git a/releasenotes/notes/create_security_group_rule-16d58a8f0f0ff262.yaml b/releasenotes/notes/create_security_group_rule-16d58a8f0f0ff262.yaml
new file mode 100644
index 0000000..3354f65
--- /dev/null
+++ b/releasenotes/notes/create_security_group_rule-16d58a8f0f0ff262.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Added public interface create_security_group_rule().
+ Since this interface is meant to be used by tempest plugins,
+ It doesn't neccessarily require to be private api.
diff --git a/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml b/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml
new file mode 100644
index 0000000..fde6193
--- /dev/null
+++ b/releasenotes/notes/image-client-add-versions-and-tasks-ac289dbfe1c899cc.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Adds a method to images_client to get tasks relevant to a given image. Also adds
+ has_version() method to image versions_client to probe for availability of a given
+ API version.
diff --git a/releasenotes/notes/intermediate-wallaby-release-55a0b31b1dee7b23.yaml b/releasenotes/notes/intermediate-wallaby-release-55a0b31b1dee7b23.yaml
new file mode 100644
index 0000000..cda3b89
--- /dev/null
+++ b/releasenotes/notes/intermediate-wallaby-release-55a0b31b1dee7b23.yaml
@@ -0,0 +1,4 @@
+---
+prelude: >
+ This is an intermediate release during the Wallaby development cycle to
+ make new functionality available to plugins and other consumers.
diff --git a/releasenotes/notes/log_console_output-dae6b8740b5a5821.yaml b/releasenotes/notes/log_console_output-dae6b8740b5a5821.yaml
new file mode 100644
index 0000000..2779b26
--- /dev/null
+++ b/releasenotes/notes/log_console_output-dae6b8740b5a5821.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Added public interface log_console_output().
+ It used to be a private method with name _log_console_output().
+ Since this interface is meant to be used by tempest plugins,
+ It doesn't neccessarily require to be private api.
+
diff --git a/releasenotes/notes/make-create-user-domain-aware-for-v3-creds-client-5054f58e715adc0c.yaml b/releasenotes/notes/make-create-user-domain-aware-for-v3-creds-client-5054f58e715adc0c.yaml
new file mode 100644
index 0000000..8931f09
--- /dev/null
+++ b/releasenotes/notes/make-create-user-domain-aware-for-v3-creds-client-5054f58e715adc0c.yaml
@@ -0,0 +1,9 @@
+---
+fixes:
+ - |
+ [`bug 1613819 <https://bugs.launchpad.net/tempest/+bug/1613819>`_]
+ admin_domain_name and default_credentials_domain_name parameters
+ under [auth] now affect a domain used for creating test users just
+ as they affect it for projects. Previously a domain with an id set
+ to "default" had to be present in order for test user creation to
+ succeed with Keystone v3.
diff --git a/releasenotes/notes/random-bytes-size-limit-ee94a8c6534fe916.yaml b/releasenotes/notes/random-bytes-size-limit-ee94a8c6534fe916.yaml
new file mode 100644
index 0000000..42322e4
--- /dev/null
+++ b/releasenotes/notes/random-bytes-size-limit-ee94a8c6534fe916.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+ - |
+ The ``tempest.lib.common.utils.data_utils.random_bytes()`` helper
+ function will no longer allow a ``size`` of more than 1MiB. Tests
+ generally do not need to generate and use large payloads for
+ feature verification and it is easy to lose track of and duplicate
+ large buffers. The sum total of such errors can become problematic
+ in paralllelized and constrained CI environments.
diff --git a/releasenotes/notes/remove-deprecated-volume-config-options-4b7ea93b88e5b982.yaml b/releasenotes/notes/remove-deprecated-volume-config-options-4b7ea93b88e5b982.yaml
new file mode 100644
index 0000000..f3002f9
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-volume-config-options-4b7ea93b88e5b982.yaml
@@ -0,0 +1,9 @@
+---
+upgrade:
+ - |
+ Deprecated config options to select the Volume API version have been
+ removed. Use ``CONF.volume.catalog_type`` to run volume tests under v3
+ or v2 APIs.
+
+ * ``CONF.volume-feature-enabled.api_v2``
+ * ``CONF.volume-feature-enabled.api_v3``
\ No newline at end of file
diff --git a/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml b/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml
new file mode 100644
index 0000000..ac20340
--- /dev/null
+++ b/releasenotes/notes/remove-old-data-utils-e0966f882f7fe23a.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ The old deprecated ``data-ultis`` from ``tempest.common.utils`` has been
+ removed. If you are still using this, use the stable version
+ of ``data-utils`` from new location ``tempest.lib.common.utils``.
diff --git a/releasenotes/notes/support-for-rbac-new-scope-6ec8164ce1e7288c.yaml b/releasenotes/notes/support-for-rbac-new-scope-6ec8164ce1e7288c.yaml
new file mode 100644
index 0000000..af7df93
--- /dev/null
+++ b/releasenotes/notes/support-for-rbac-new-scope-6ec8164ce1e7288c.yaml
@@ -0,0 +1,13 @@
+---
+prelude: >
+ Support for RBAC new system scope is added in Tempest.
+features:
+ - |
+ Keystone provides the new scoped token called ``system`` which
+ can be used to query the system scoped API operation. Projects
+ are moving towards the policy with new scope types, Keystone, Nova
+ already provide the new policy for RBAC checks. Tempest has added
+ the support to query the system scoped token from keystone to test
+ the new policy.
+ As next step, we will be moving all the Tempest tests on the project's
+ new policy.
diff --git a/releasenotes/notes/support-scope-in-get-roles-dynamic-creds-90bfab163c1c289a.yaml b/releasenotes/notes/support-scope-in-get-roles-dynamic-creds-90bfab163c1c289a.yaml
new file mode 100644
index 0000000..26282f0
--- /dev/null
+++ b/releasenotes/notes/support-scope-in-get-roles-dynamic-creds-90bfab163c1c289a.yaml
@@ -0,0 +1,36 @@
+---
+features:
+ - |
+ Dynamic credentials now support the scope type for specific roles
+ too along with ``admin``, ``member``, ``reader`` role.
+ Test can specify the scope in the prefix of ``cls.credentials`` name.
+ If ``system`` is prefix in ``cls.credentials`` name then creds will
+ be created with scope as ``system``. If ``domain`` is prefix in
+ ``cls.credentials`` name then creds will be created with scope as
+ ``domain`` otherwise default ``project`` scope will be used.
+ For Example::
+
+ credentials = [['my_role', 'role1'], # this will be old style and project scoped
+ ['project_my_role', 'role1'], # this will be project scoped
+ ['domain_my_role', 'role1'], # this will be domain scoped
+ ['system_my_role', 'role1']] # this will be system scoped
+
+ And below is how test can access the credential manager of respective
+ credentials type::
+
+ cls.os_my_role.any_client
+ cls.os_project_my_role.any_client
+ cls.os_domain_my_role.any_client
+ cls.os_system_my_role.any_client
+
+
+ For backward compatibility, we set the credentials manager class attribute
+ in old style form too which is prefix with ``os_roles_*``, example
+ ``cls.os_roles_my_role`` but we recommend to use the new style attribute
+ as shown above.
+issues:
+ - |
+ Scope support for specific role is not yet added for pre-provisioned credentials.
+fixes:
+ - |
+ Fixes the `bug# 1917168 <https://bugs.launchpad.net/tempest/+bug/1917168>`_
diff --git a/releasenotes/notes/system-scope-44244cc955a7825f.yaml b/releasenotes/notes/system-scope-44244cc955a7825f.yaml
new file mode 100644
index 0000000..969a71f
--- /dev/null
+++ b/releasenotes/notes/system-scope-44244cc955a7825f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Adds new personas that can be used to test service policies for all
+ default scopes (project, domain, and system) and roles (reader, member,
+ and admin). Both dynamic credentials and pre-provisioned credentials are
+ supported.
diff --git a/requirements.txt b/requirements.txt
index abc0bce..d84116b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,7 +18,7 @@
PyYAML>=3.12 # MIT
python-subunit>=1.0.0 # Apache-2.0/BSD
stevedore>=1.20.0 # Apache-2.0
-PrettyTable<0.8,>=0.7.1 # BSD
+PrettyTable>=0.7.1 # BSD
urllib3>=1.21.1 # MIT
debtcollector>=1.2.0 # Apache-2.0
unittest2>=1.1.0 # BSD
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 3643edb..0c72b69 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -32,7 +32,11 @@
.. zuul:rolevar:: tempest_test_blacklist
- Specifies a blacklist file to skip tests that are not needed.
+ DEPRECATED option, please use tempest_test_exclude_list instead.
+
+.. zuul:rolevar:: tempest_test_exclude_list
+
+ Specifies an excludelist file to skip tests that are not needed.
Pass a full path to the file.
@@ -44,6 +48,11 @@
.. zuul:rolevar:: tempest_black_regex
:default: ''
+ DEPRECATED option, please use tempest_exclude_regex instead.
+
+.. zuul:rolevar:: tempest_exclude_regex
+ :default: ''
+
A regular expression used to skip the tests.
It works only when used with some specific tox environments
@@ -51,7 +60,7 @@
::
vars:
- tempest_black_regex: (tempest.api.identity).*$
+ tempest_exclude_regex: (tempest.api.identity).*$
.. zuul:rolevar:: tox_extra_args
:default: ''
@@ -72,7 +81,7 @@
.. zuul:rolevar:: stable_constraints_file
:default: ''
- Upper constraints file to be used for stable branch till stable/rocky.
+ Upper constraints file to be used for stable branch till stable/stein.
.. zuul:rolevar:: tempest_tox_environment
:default: ''
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 5867b6c..5f13883 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,9 +1,11 @@
devstack_base_dir: /opt/stack
tempest_test_regex: ''
tox_envlist: smoke
-tempest_black_regex: ''
tox_extra_args: ''
tempest_test_timeout: ''
stable_constraints_file: "{{ devstack_base_dir }}/requirements/upper-constraints.txt"
target_branch: "{{ zuul.branch }}"
tempest_tox_environment: {}
+# NOTE(gmann): external_bridge_mtu shows as undefined for run-tempest role
+# defining default value here to avoid that error.
+external_bridge_mtu: 0
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 1de3725..37026e4 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -25,17 +25,31 @@
target_branch: "{{ zuul.override_checkout }}"
when: zuul.override_checkout is defined
-- name: Use stable branch upper-constraints till stable/rocky
+- name: Use stable branch upper-constraints till stable/stein
set_fact:
# TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
- when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky"]
+ when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
+
+- name: Use Configured upper-constraints for non-master Tempest
+ set_fact:
+ # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
+ tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) | combine({'TOX_CONSTRAINTS_FILE': devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS']}) }}"
+ when:
+ - devstack_localrc is defined
+ - "'TEMPEST_BRANCH' in devstack_localrc"
+ - "'TEMPEST_VENV_UPPER_CONSTRAINTS' in devstack_localrc"
+ - devstack_localrc['TEMPEST_BRANCH'] != 'master'
+ - devstack_localrc['TEMPEST_VENV_UPPER_CONSTRAINTS'] != 'default'
- name: Set OS_TEST_TIMEOUT if requested
set_fact:
tempest_tox_environment: "{{ tempest_tox_environment | combine({'OS_TEST_TIMEOUT': tempest_test_timeout}) }}"
when: tempest_test_timeout != ''
+# TODO(kopecmartin) remove the following 'when block' after all consumers of
+# the role have switched to tempest_test_exclude_list option, until then it's
+# kept here for backward compatibility
- when:
- tempest_test_blacklist is defined
block:
@@ -50,10 +64,42 @@
blacklist_option: "--blacklist-file={{ tempest_test_blacklist|quote }}"
when: blacklist_stat.stat.exists
+- when:
+ - tempest_test_exclude_list is defined
+ block:
+ - name: Check for test exclude list file
+ stat:
+ path: "{{ tempest_test_exclude_list }}"
+ register:
+ exclude_list_stat
+
+ - name: Build exclude list option
+ set_fact:
+ exclude_list_option: "--exclude-list={{ tempest_test_exclude_list|quote }}"
+ when: exclude_list_stat.stat.exists
+
+# TODO(kopecmartin) remove this after all consumers of the role have switched
+# to tempest_exclude_regex option, until then it's kept here for the backward
+# compatibility
+- name: Build exclude regex (old param)
+ set_fact:
+ tempest_test_exclude_regex: "--black-regex={{tempest_black_regex|quote}}"
+ when:
+ - tempest_black_regex is defined
+ - tempest_exclude_regex is not defined
+
+- name: Build exclude regex (new param)
+ set_fact:
+ tempest_test_exclude_regex: "--exclude-regex={{tempest_exclude_regex|quote}}"
+ when:
+ - tempest_black_regex is not defined
+ - tempest_exclude_regex is defined
+
- name: Run Tempest
- command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \
+ command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} \
+ {{blacklist_option|default('')}} {{exclude_list_option|default('')}} \
--concurrency={{tempest_concurrency|default(default_concurrency)}} \
- --black-regex={{tempest_black_regex|quote}}
+ {{tempest_test_exclude_regex|default('')}}
args:
chdir: "{{devstack_base_dir}}/tempest"
register: tempest_run_result
diff --git a/setup.cfg b/setup.cfg
index 18427a2..d885db0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,12 @@
[metadata]
name = tempest
summary = OpenStack Integration Testing
-description-file =
+description_file =
README.rst
author = OpenStack
-author-email = openstack-discuss@lists.openstack.org
-home-page = https://docs.openstack.org/tempest/latest/
-python-requires = >=3.6
+author_email = openstack-discuss@lists.openstack.org
+home_page = https://docs.openstack.org/tempest/latest/
+python_requires = >=3.6
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
@@ -48,4 +48,3 @@
run = tempest.cmd.run:TempestRun
oslo.config.opts =
tempest.config = tempest.config:list_opts
-
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log.py b/tempest/api/compute/admin/test_instance_usage_audit_log.py
index 4dcbb3b..c4c0542 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -14,8 +14,7 @@
# under the License.
import datetime
-
-from six.moves.urllib import parse as urllib
+from urllib import parse as urllib
from tempest.api.compute import base
from tempest.lib import decorators
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
index 84d18c4..c115451 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -14,8 +14,7 @@
# under the License.
import datetime
-
-from six.moves.urllib import parse as urllib
+from urllib import parse as urllib
from tempest.api.compute import base
from tempest.lib import decorators
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 941315e..52ccea7 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -169,8 +169,13 @@
block_migrate_cinder_iscsi,
'Block Live migration not configured for iSCSI')
@utils.services('volume')
- def test_iscsi_volume(self):
- """Test live migrating a server with volume attached"""
+ def test_live_block_migration_with_attached_volume(self):
+ """Test the live-migration of an instance with an attached volume.
+
+ This tests the live-migration of an instance with both a local disk and
+ attach volume. This differs from test_volume_backed_live_migration
+ above that tests live-migration with only an attached volume.
+ """
server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
index 342380e..cf8c560 100644
--- a/tempest/api/compute/admin/test_volume.py
+++ b/tempest/api/compute/admin/test_volume.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.api.compute import base
from tempest.common import waiters
@@ -51,7 +51,7 @@
image = self.image_client.show_image(CONF.compute.image_ref)
image_data = self.image_client.show_image_file(
CONF.compute.image_ref).data
- image_file = six.BytesIO(image_data)
+ image_file = io.BytesIO(image_data)
create_dict = {
'container_format': image['container_format'],
'disk_format': image['disk_format'],
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 235049a..5d6a7d7 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import io
import random
-import six
-
from tempest.api.compute import base
from tempest.common import image as common_image
from tempest.common import utils
@@ -44,7 +43,7 @@
CONF.compute.flavor_ref)['flavor']
min_img_ram = flavor['ram'] + 1
size = random.randint(1024, 4096)
- image_file = six.BytesIO(data_utils.random_bytes(size))
+ image_file = io.BytesIO(data_utils.random_bytes(size))
params = {
'name': data_utils.rand_name('image'),
'container_format': CONF.image.container_formats[0],
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 561265f..ece983d 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.api.compute import base
from tempest.common import image as common_image
@@ -77,7 +77,7 @@
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.glance_client.delete_image,
cls.image_id)
- image_file = six.BytesIO((b'*' * 1024))
+ image_file = io.BytesIO((b'*' * 1024))
if CONF.image_feature_enabled.api_v1:
cls.glance_client.update_image(cls.image_id, data=image_file)
else:
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 0296220..275a26f 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -110,20 +110,30 @@
Creating another server image when first image is being saved is
not allowed.
"""
- # Create first snapshot
- image = self.create_image_from_server(self.server_id)
- self.addCleanup(self._reset_server)
+ try:
+ # Create first snapshot
+ image = self.create_image_from_server(self.server_id)
+ self.addCleanup(self._reset_server)
- # Create second snapshot
- self.assertRaises(lib_exc.Conflict, self.create_image_from_server,
- self.server_id)
+ # Create second snapshot
+ self.assertRaises(lib_exc.Conflict, self.create_image_from_server,
+ self.server_id)
- if api_version_utils.compare_version_header_to_response(
- "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
- image_id = image['image_id']
- else:
- image_id = data_utils.parse_image_id(image.response['location'])
- self.client.delete_image(image_id)
+ if api_version_utils.compare_version_header_to_response(
+ "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
+ image_id = image['image_id']
+ else:
+ image_id = data_utils.parse_image_id(
+ image.response['location'])
+ self.client.delete_image(image_id)
+
+ except lib_exc.TimeoutException as ex:
+ # Test cannot capture the image saving state.
+ # If timeout is reached, we don't need to check state,
+ # since, it wouldn't be a 'SAVING' state atleast and apart from
+ # it, this testcase doesn't have scope for other state transition
+ # Hence, skip the test.
+ raise self.skipException("This test is skipped because " + str(ex))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('084f0cbc-500a-4963-8a4e-312905862581')
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 7930c67..15b8a00 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -15,7 +15,7 @@
import time
-import six
+import io
import testtools
from tempest.api.compute import base
@@ -85,7 +85,7 @@
# Wait 1 second between creation and upload to ensure a delta
# between created_at and updated_at.
time.sleep(1)
- image_file = six.BytesIO((b'*' * 1024))
+ image_file = io.BytesIO((b'*' * 1024))
if CONF.image_feature_enabled.api_v1:
cls.glance_client.update_image(image_id, data=image_file)
else:
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 102792e..ac18442 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -16,7 +16,6 @@
import time
from oslo_log import log
-import six
from tempest.api.compute import base
from tempest.common import compute
@@ -241,7 +240,7 @@
except lib_exc.BadRequest as e:
msg = ('Multiple possible networks found, use a Network ID to be '
'more specific.')
- if not CONF.compute.fixed_network_name and six.text_type(e) == msg:
+ if not CONF.compute.fixed_network_name and str(e) == msg:
raise
else:
ifs.append(iface)
@@ -450,7 +449,7 @@
except lib_exc.BadRequest as e:
msg = ('Multiple possible networks found, use a Network ID to be '
'more specific.')
- if not CONF.compute.fixed_network_name and six.text_type(e) == msg:
+ if not CONF.compute.fixed_network_name and str(e) == msg:
raise
else:
# just to check the response schema
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 6ebdbdb..a9c0e56 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -14,9 +14,9 @@
# under the License.
import struct
+import urllib.parse as urlparse
import six
-import six.moves.urllib.parse as urlparse
import urllib3
from tempest.api.compute import base
@@ -69,7 +69,7 @@
resp = urllib3.PoolManager().request('GET', vnc_url)
# Make sure that the GET request was accepted by the novncproxy
self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the '
- 'initial call: ' + six.text_type(resp.status))
+ 'initial call: ' + str(resp.status))
# Do some basic validation to make sure it is an expected HTML document
resp_data = resp.data.decode()
# This is needed in the case of example: <html lang="en">
@@ -165,11 +165,11 @@
self._websocket.response.startswith(b'HTTP/1.1 101 Switching '
b'Protocols'),
'Incorrect HTTP return status code: {}'.format(
- six.text_type(self._websocket.response)
+ str(self._websocket.response)
)
)
_required_header = 'upgrade: websocket'
- _response = six.text_type(self._websocket.response).lower()
+ _response = str(self._websocket.response).lower()
self.assertIn(
_required_header,
_response,
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 4527aa9..deb21c7 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urlparse
+
from oslo_log import log as logging
-from six.moves.urllib import parse as urlparse
import testtools
from tempest.api.compute import base
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index c222893..a9f8c09 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -158,8 +158,14 @@
self.servers_client, server_id, 'ACTIVE')
-class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
- """Test rescuing server specifying type of device for the rescue disk"""
+class ServerStableDeviceRescueTestIDE(BaseServerStableDeviceRescueTest):
+ """Test rescuing server using an IDE device for the rescue disk"""
+
+ @classmethod
+ def skip_checks(cls):
+ super().skip_checks()
+ if not CONF.compute_feature_enabled.ide_bus:
+ raise cls.skipException("IDE bus not available.")
@decorators.idempotent_id('947004c3-e8ef-47d9-9f00-97b74f9eaf96')
def test_stable_device_rescue_cdrom_ide(self):
@@ -168,6 +174,10 @@
hw_rescue_device='cdrom', hw_rescue_bus='ide')
self._test_stable_device_rescue(server_id, rescue_image_id)
+
+class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
+ """Test rescuing server specifying type of device for the rescue disk"""
+
@decorators.idempotent_id('16865750-1417-4854-bcf7-496e6753c01e')
def test_stable_device_rescue_disk_virtio(self):
"""Test rescuing server with disk and virtio as the rescue disk"""
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index f3a7471..5bbd65c 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
-
from tempest.api.identity import base
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -70,8 +68,8 @@
orig_expires_at = token_auth['token']['expires_at']
orig_user = token_auth['token']['user']
- self.assertIsInstance(token_auth['token']['expires_at'], six.text_type)
- self.assertIsInstance(token_auth['token']['issued_at'], six.text_type)
+ self.assertIsInstance(token_auth['token']['expires_at'], str)
+ self.assertIsInstance(token_auth['token']['issued_at'], str)
self.assertEqual(['password'], token_auth['token']['methods'])
self.assertEqual(user['id'], token_auth['token']['user']['id'])
self.assertEqual(user['name'], token_auth['token']['user']['name'])
@@ -91,7 +89,7 @@
self.assertEqual(orig_expires_at, token_auth['token']['expires_at'],
'Expiration time should match original token')
- self.assertIsInstance(token_auth['token']['issued_at'], six.text_type)
+ self.assertIsInstance(token_auth['token']['issued_at'], str)
self.assertEqual(set(['password', 'token']),
set(token_auth['token']['methods']))
self.assertEqual(orig_user, token_auth['token']['user'],
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index d3dc19a..23e7fd8 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.common import image as common_image
from tempest import config
@@ -113,7 +113,7 @@
cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
def _create_image(self):
- image_file = six.BytesIO(data_utils.random_bytes())
+ image_file = io.BytesIO(data_utils.random_bytes())
image = self.create_image(container_format='bare',
disk_format='raw',
is_public=False,
@@ -134,12 +134,6 @@
def setup_clients(cls):
super(BaseV2ImageTest, cls).setup_clients()
cls.client = cls.os_primary.image_client_v2
- cls.namespaces_client = cls.os_primary.namespaces_client
- cls.resource_types_client = cls.os_primary.resource_types_client
- cls.namespace_properties_client =\
- cls.os_primary.namespace_properties_client
- cls.namespace_objects_client = cls.os_primary.namespace_objects_client
- cls.namespace_tags_client = cls.os_primary.namespace_tags_client
cls.schemas_client = cls.os_primary.schemas_client
cls.versions_client = cls.os_primary.image_versions_client
@@ -204,3 +198,9 @@
def setup_clients(cls):
super(BaseV2ImageAdminTest, cls).setup_clients()
cls.admin_client = cls.os_admin.image_client_v2
+ cls.namespaces_client = cls.os_admin.namespaces_client
+ cls.resource_types_client = cls.os_admin.resource_types_client
+ cls.namespace_properties_client =\
+ cls.os_admin.namespace_properties_client
+ cls.namespace_objects_client = cls.os_admin.namespace_objects_client
+ cls.namespace_tags_client = cls.os_admin.namespace_tags_client
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 595717e..6fd6c4e 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.api.image import base
from tempest.common import image as common_image
@@ -72,7 +72,7 @@
self.assertEqual(val, image.get('properties')[key])
# Now try uploading an image file
- image_file = six.BytesIO(data_utils.random_bytes())
+ image_file = io.BytesIO(data_utils.random_bytes())
body = self.client.update_image(image['id'], data=image_file)['image']
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -204,7 +204,7 @@
Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = six.BytesIO(data_utils.random_bytes(size))
+ image_file = io.BytesIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
image = cls.create_image(name=name,
container_format=container_format,
@@ -306,7 +306,7 @@
disk_format, size):
"""Create a new standard image and return newly-registered image-id"""
- image_file = six.BytesIO(data_utils.random_bytes(size))
+ image_file = io.BytesIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
image = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
index ad68d82..733c778 100644
--- a/tempest/api/image/v2/admin/test_images.py
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.api.image import base
from tempest.common import waiters
@@ -99,7 +99,7 @@
self.assertEqual('queued', image['status'])
file_content = data_utils.random_bytes()
- image_file = six.BytesIO(file_content)
+ image_file = io.BytesIO(file_content)
self.client.store_image_file(image['id'], image_file)
body = self.client.show_image(image['id'])
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_objects.py b/tempest/api/image/v2/admin/test_images_metadefs_namespace_objects.py
similarity index 98%
rename from tempest/api/image/v2/test_images_metadefs_namespace_objects.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespace_objects.py
index 32b81b1..9222920 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_objects.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespace_objects.py
@@ -16,7 +16,7 @@
from tempest.lib import decorators
-class MetadataNamespaceObjectsTest(base.BaseV2ImageTest):
+class MetadataNamespaceObjectsTest(base.BaseV2ImageAdminTest):
"""Test the Metadata definition namespace objects basic functionality"""
def _create_namespace_object(self, namespace):
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py b/tempest/api/image/v2/admin/test_images_metadefs_namespace_properties.py
similarity index 97%
rename from tempest/api/image/v2/test_images_metadefs_namespace_properties.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespace_properties.py
index 1d4f0a6..10dfba1 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespace_properties.py
@@ -15,7 +15,7 @@
from tempest.lib import decorators
-class MetadataNamespacePropertiesTest(base.BaseV2ImageTest):
+class MetadataNamespacePropertiesTest(base.BaseV2ImageAdminTest):
"""Test the Metadata definition namespace property basic functionality"""
@decorators.idempotent_id('b1a3765e-3a5d-4f6d-a3a7-3ca3476ae768')
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py b/tempest/api/image/v2/admin/test_images_metadefs_namespace_tags.py
similarity index 98%
rename from tempest/api/image/v2/test_images_metadefs_namespace_tags.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespace_tags.py
index dc64185..9e88e03 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespace_tags.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespace_tags.py
@@ -16,7 +16,7 @@
from tempest.lib import decorators
-class MetadataNamespaceTagsTest(base.BaseV2ImageTest):
+class MetadataNamespaceTagsTest(base.BaseV2ImageAdminTest):
"""Test the Metadata definition namespace tags basic functionality"""
tags = [
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/admin/test_images_metadefs_namespaces.py
similarity index 98%
rename from tempest/api/image/v2/test_images_metadefs_namespaces.py
rename to tempest/api/image/v2/admin/test_images_metadefs_namespaces.py
index 502949f..0fe49f9 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_namespaces.py
@@ -20,7 +20,7 @@
from tempest.lib import exceptions as lib_exc
-class MetadataNamespacesTest(base.BaseV2ImageTest):
+class MetadataNamespacesTest(base.BaseV2ImageAdminTest):
"""Test the Metadata definition Namespaces basic functionality"""
@decorators.idempotent_id('319b765e-7f3d-4b3d-8b37-3ca3876ee768')
diff --git a/tempest/api/image/v2/test_images_metadefs_resource_types.py b/tempest/api/image/v2/admin/test_images_metadefs_resource_types.py
similarity index 97%
rename from tempest/api/image/v2/test_images_metadefs_resource_types.py
rename to tempest/api/image/v2/admin/test_images_metadefs_resource_types.py
index 6867f2d..e533c43 100644
--- a/tempest/api/image/v2/test_images_metadefs_resource_types.py
+++ b/tempest/api/image/v2/admin/test_images_metadefs_resource_types.py
@@ -17,7 +17,7 @@
from tempest.lib import decorators
-class MetadataResourceTypesTest(base.BaseV2ImageTest):
+class MetadataResourceTypesTest(base.BaseV2ImageAdminTest):
"""Test the Metadata definition resource types basic functionality"""
@decorators.idempotent_id('6f358a4e-5ef0-11e6-a795-080027d0d606')
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index d1f6f98..efa23bb 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import io
import random
-import six
-
from oslo_log import log as logging
from tempest.api.image import base
from tempest.common import waiters
@@ -69,6 +68,23 @@
self.assertEqual('queued', image['status'])
return image
+ def _require_import_method(self, method):
+ if method not in self.available_import_methods:
+ raise self.skipException('Server does not support '
+ '%s import method' % method)
+
+ def _stage_and_check(self):
+ image = self._create_image()
+ # Stage image data
+ file_content = data_utils.random_bytes()
+ image_file = io.BytesIO(file_content)
+ self.client.stage_image_file(image['id'], image_file)
+ # Check image status is 'uploading'
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual('uploading', body['status'])
+ return image['id']
+
@decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2')
def test_image_glance_direct_import(self):
"""Test 'glance-direct' import functionalities
@@ -76,21 +92,29 @@
Create image, stage image data, import image and verify
that import succeeded.
"""
- if 'glance-direct' not in self.available_import_methods:
- raise self.skipException('Server does not support '
- 'glance-direct import method')
- image = self._create_image()
- # Stage image data
- file_content = data_utils.random_bytes()
- image_file = six.BytesIO(file_content)
- self.client.stage_image_file(image['id'], image_file)
- # Check image status is 'uploading'
- body = self.client.show_image(image['id'])
- self.assertEqual(image['id'], body['id'])
- self.assertEqual('uploading', body['status'])
+ self._require_import_method('glance-direct')
+
+ image_id = self._stage_and_check()
# import image from staging to backend
- self.client.image_import(image['id'], method='glance-direct')
- self.client.wait_for_resource_activation(image['id'])
+ resp = self.client.image_import(image_id, method='glance-direct')
+ waiters.wait_for_image_imported_to_stores(self.client, image_id)
+
+ if not self.versions_client.has_version('2.12'):
+ # API is not new enough to support image/tasks API
+ LOG.info('Glance does not support v2.12, so I am unable to '
+ 'validate the image/tasks API.')
+ return
+
+ # Make sure we can access the task and that some of the key
+ # fields look legit.
+ tasks = self.client.show_image_tasks(image_id)
+ self.assertEqual(1, len(tasks['tasks']))
+ task = tasks['tasks'][0]
+ self.assertEqual('success', task['status'])
+ self.assertEqual(resp.response['x-openstack-request-id'],
+ task['request_id'])
+ self.assertEqual('glance-direct',
+ task['input']['import_req']['method']['name'])
@decorators.idempotent_id('f6feb7a4-b04f-4706-a011-206129f83e62')
def test_image_web_download_import(self):
@@ -99,9 +123,8 @@
Create image, import image and verify that import
succeeded.
"""
- if 'web-download' not in self.available_import_methods:
- raise self.skipException('Server does not support '
- 'web-download import method')
+ self._require_import_method('web-download')
+
image = self._create_image()
# Now try to get image details
body = self.client.show_image(image['id'])
@@ -111,7 +134,48 @@
image_uri = CONF.image.http_image
self.client.image_import(image['id'], method='web-download',
image_uri=image_uri)
- self.client.wait_for_resource_activation(image['id'])
+ waiters.wait_for_image_imported_to_stores(self.client, image['id'])
+
+ @decorators.idempotent_id('e04761a1-22af-42c2-b8bc-a34a3f12b585')
+ def test_remote_import(self):
+ """Test image import against a different worker than stage.
+
+ This creates and stages an image against the primary API worker,
+ but then calls import on a secondary worker (if available) to
+ test that distributed image import works (i.e. proxies the import
+ request to the proper worker).
+ """
+ self._require_import_method('glance-direct')
+
+ if not CONF.image.alternate_image_endpoint:
+ raise self.skipException('No image_remote service to test '
+ 'against')
+
+ image_id = self._stage_and_check()
+ # import image from staging to backend, but on the alternate worker
+ self.os_primary.image_client_remote.image_import(
+ image_id, method='glance-direct')
+ waiters.wait_for_image_imported_to_stores(self.client, image_id)
+
+ @decorators.idempotent_id('44d60544-1524-42f7-8899-315301105dd8')
+ def test_remote_delete(self):
+ """Test image delete against a different worker than stage.
+
+ This creates and stages an image against the primary API worker,
+ but then calls delete on a secondary worker (if available) to
+ test that distributed image import works (i.e. proxies the delete
+ request to the proper worker).
+ """
+ self._require_import_method('glance-direct')
+
+ if not CONF.image.alternate_image_endpoint:
+ raise self.skipException('No image_remote service to test '
+ 'against')
+
+ image_id = self._stage_and_check()
+ # delete image from staging to backend, but on the alternate worker
+ self.os_primary.image_client_remote.delete_image(image_id)
+ self.client.wait_for_resource_deletion(image_id)
class MultiStoresImportImagesTest(base.BaseV2ImageTest):
@@ -158,7 +222,7 @@
self.client.stage_image_file(
image['id'],
- six.BytesIO(data_utils.random_bytes()))
+ io.BytesIO(data_utils.random_bytes()))
# Check image status is 'uploading'
body = self.client.show_image(image['id'])
self.assertEqual(image['id'], body['id'])
@@ -240,7 +304,7 @@
# Now try uploading an image file
file_content = data_utils.random_bytes()
- image_file = six.BytesIO(file_content)
+ image_file = io.BytesIO(file_content)
self.client.store_image_file(image['id'], image_file)
# Now try to get image details
@@ -317,7 +381,7 @@
# Upload an image file
content = data_utils.random_bytes()
- image_file = six.BytesIO(content)
+ image_file = io.BytesIO(content)
self.client.store_image_file(image['id'], image_file)
# Deactivate image
@@ -367,7 +431,7 @@
1024 and 4096
"""
size = random.randint(1024, 4096)
- image_file = six.BytesIO(data_utils.random_bytes(size))
+ image_file = io.BytesIO(data_utils.random_bytes(size))
tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
image = cls.create_image(container_format=container_format,
disk_format=disk_format,
@@ -550,7 +614,7 @@
def test_list_images_param_member_status(self):
"""Test listing images by member_status and visibility"""
# Create an image to be shared using default visibility
- image_file = six.BytesIO(data_utils.random_bytes(2048))
+ image_file = io.BytesIO(data_utils.random_bytes(2048))
container_format = CONF.image.container_formats[0]
disk_format = CONF.image.disk_formats[0]
image = self.create_image(container_format=container_format,
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index dc2bb96..a3802a9 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -14,11 +14,16 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from tempest.api.image import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
+CONF = config.CONF
+
class ImagesNegativeTest(base.BaseV2ImageTest):
@@ -114,3 +119,102 @@
self.assertRaises(lib_exc.Forbidden,
self.client.delete_image,
image['id'])
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('a0ae75d4-ce9c-4576-b823-aba04c8acabd')
+ def test_update_image_reserved_property(self):
+ """Attempt to add a reserved property to an image.
+
+ Glance bans some internal-use-only properties such that they will
+ cause a PATCH to fail. Since os_glance_* is banned, we can use a
+ key in that namespace here.
+ """
+ if not CONF.image_feature_enabled.os_glance_reserved:
+ raise self.skipException('os_glance_reserved is not enabled')
+
+ image = self.create_image(name='test',
+ container_format='bare',
+ disk_format='raw')
+ self.assertRaises(lib_exc.Forbidden,
+ self.client.update_image,
+ image['id'], [{'add': '/os_glance_foo',
+ 'value': 'bar'}])
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('e3fb7df8-2742-4143-8976-f1b26870f0a0')
+ def test_create_image_reserved_property(self):
+ """Attempt to create an image with a reserved property.
+
+ Glance bans some internal-use-only properties such that they will
+ cause an image create to fail. Since os_glance_* is banned, we can
+ use a key in that namespace here.
+ """
+ if not CONF.image_feature_enabled.os_glance_reserved:
+ raise self.skipException('os_glance_reserved is not enabled')
+
+ self.assertRaises(lib_exc.Forbidden,
+ self.create_image,
+ name='test',
+ container_format='bare',
+ disk_format='raw',
+ os_glance_foo='bar')
+
+
+class ImportImagesNegativeTest(base.BaseV2ImageTest):
+ """Here we test the import operations for image"""
+
+ @classmethod
+ def skip_checks(cls):
+ super(ImportImagesNegativeTest, cls).skip_checks()
+ if not CONF.image_feature_enabled.import_image:
+ skip_msg = (
+ "%s skipped as image import is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(ImportImagesNegativeTest, cls).resource_setup()
+ cls.available_import_methods = cls.client.info_import()[
+ 'import-methods']['value']
+ if not cls.available_import_methods:
+ raise cls.skipException('Server does not support '
+ 'any import method')
+
+ cls.available_stores = cls.get_available_stores()
+ if not len(cls.available_stores) > 0:
+ raise cls.skipException(
+ 'No stores configured %s' % cls.available_stores)
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('c52f6a77-f522-4411-8dbe-9d14f2b1ba6b')
+ def test_image_web_download_import_with_bad_url(self):
+ """Test 'web-download' import functionalities
+
+ Make sure that web-download with invalid URL fails properly.
+ """
+ if 'web-download' not in self.available_import_methods:
+ raise self.skipException('Server does not support '
+ 'web-download import method')
+ image = self.client.create_image(name='test',
+ container_format='bare',
+ disk_format='raw')
+ # Now try to get image details
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual('queued', body['status'])
+ stores = self.get_available_stores()
+ # import image from web to backend
+ image_uri = 'http://does-not.exist/no/possible/way'
+ self.client.image_import(image['id'], method='web-download',
+ image_uri=image_uri,
+ stores=[stores[0]['id']])
+
+ start_time = int(time.time())
+ while int(time.time()) - start_time < self.client.build_timeout:
+ body = self.client.show_image(image['id'])
+ if body.get('os_glance_failed_import'):
+ # Store ended up in failed list, which is good
+ return
+ time.sleep(self.client.build_interval)
+
+ self.fail('Image never reported failed store')
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index eb31d24..64f6e80 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -15,9 +15,9 @@
from tempest.api.network import base
from tempest.common import utils
-from tempest.common.utils import data_utils
from tempest.common.utils import net_utils
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
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 479578d..63078cd 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -16,7 +16,6 @@
import ipaddress
import netaddr
-import six
import testtools
from tempest.api.network import base_security_groups as sec_base
@@ -234,15 +233,15 @@
# Get two IP addresses
ip_address_1 = None
ip_address_2 = None
- ip_network = ipaddress.ip_network(six.text_type(subnet['cidr']))
+ ip_network = ipaddress.ip_network(str(subnet['cidr']))
for ip in ip_network:
if ip == ip_network.network_address:
continue
if ip_address_1 is None:
- ip_address_1 = six.text_type(ip)
+ ip_address_1 = str(ip)
else:
ip_address_2 = ip_address_1
- ip_address_1 = six.text_type(ip)
+ ip_address_1 = str(ip)
# Make sure these two IP addresses have different substring
if ip_address_1[:-1] != ip_address_2[:-1]:
break
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index e8f3f8b..478a834 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -13,12 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
-
from tempest.common import custom_matchers
from tempest import config
from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
import tempest.test
@@ -50,12 +47,11 @@
_, objlist = container_client.list_container_objects(cont, params)
# delete every object in the container
for obj in objlist:
- test_utils.call_and_ignore_notfound_exc(
- object_client.delete_object, cont, obj['name'])
- # sleep 2 seconds to sync the deletion of the objects
- # in HA deployment
- time.sleep(2)
+ object_client.delete_object(cont, obj['name'])
+ object_client.wait_for_resource_deletion(obj['name'], cont)
+ # Verify resource deletion
container_client.delete_container(cont)
+ container_client.wait_for_resource_deletion(cont)
except lib_exc.NotFound:
pass
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index da8ad66..ffea6f6 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -62,7 +62,7 @@
self.assertIsNotNone(container_list)
for container_name in self.containers:
- self.assertIn(six.text_type(container_name).encode('utf-8'),
+ self.assertIn(str(container_name).encode('utf-8'),
container_list)
@decorators.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565')
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index eb2ef7f..276b826 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -14,8 +14,8 @@
# under the License.
import time
+from urllib import parse as urlparse
-from six.moves.urllib import parse as urlparse
import testtools
from tempest.api.object_storage import base
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index d857d3b..39e895e 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -15,8 +15,7 @@
import hashlib
import hmac
import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
from tempest.api.object_storage import base
from tempest.common import utils
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index 0499eef..971a223 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -15,8 +15,7 @@
import hashlib
import hmac
import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
from tempest.api.object_storage import base
from tempest.common import utils
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index fc9b1a2..93f6fdb 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -13,12 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import hashlib
import random
import re
import time
import zlib
+from oslo_utils.secretutils import md5
from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest import config
@@ -151,8 +151,8 @@
"""Test creating object with Etag"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
- md5 = hashlib.md5(data).hexdigest()
- metadata = {'Etag': md5}
+ create_md5 = md5(data, usedforsecurity=False).hexdigest()
+ metadata = {'Etag': create_md5}
resp, _ = self.object_client.create_object(
self.container_name,
object_name,
@@ -641,7 +641,7 @@
"""Test getting object with if_match"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes(10)
- create_md5 = hashlib.md5(data).hexdigest()
+ create_md5 = md5(data, usedforsecurity=False).hexdigest()
create_metadata = {'Etag': create_md5}
self.object_client.create_object(self.container_name,
object_name,
@@ -681,7 +681,7 @@
"""Test getting object with if_none_match"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
- create_md5 = hashlib.md5(data).hexdigest()
+ create_md5 = md5(data, usedforsecurity=False).hexdigest()
create_metadata = {'Etag': create_md5}
self.object_client.create_object(self.container_name,
object_name,
@@ -689,7 +689,7 @@
metadata=create_metadata)
list_data = data_utils.random_bytes()
- list_md5 = hashlib.md5(list_data).hexdigest()
+ list_md5 = md5(list_data, usedforsecurity=False).hexdigest()
list_metadata = {'If-None-Match': list_md5}
resp, body = self.object_client.get_object(
self.container_name,
@@ -978,8 +978,8 @@
"""
object_name, data = self.create_object(self.container_name)
# local copy is identical, no download
- md5 = hashlib.md5(data).hexdigest()
- headers = {'If-None-Match': md5}
+ object_md5 = md5(data, usedforsecurity=False).hexdigest()
+ headers = {'If-None-Match': object_md5}
url = "%s/%s" % (self.container_name, object_name)
resp, _ = self.object_client.get(url, headers=headers)
self.assertEqual(resp['status'], '304')
@@ -993,8 +993,8 @@
# local copy is different, download
local_data = "something different"
- md5 = hashlib.md5(local_data.encode()).hexdigest()
- headers = {'If-None-Match': md5}
+ other_md5 = md5(local_data.encode(), usedforsecurity=False).hexdigest()
+ headers = {'If-None-Match': other_md5}
resp, _ = self.object_client.get(url, headers=headers)
self.assertHeaders(resp, 'Object', 'GET')
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 664bbc8..0c84357 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import hashlib
-
from oslo_serialization import jsonutils as json
+from oslo_utils.secretutils import md5
from tempest.api.object_storage import base
from tempest.common import utils
from tempest.lib.common.utils import data_utils
@@ -70,10 +69,12 @@
path_object_2 = '/%s/%s' % (self.container_name,
object_name_base_2)
data_manifest = [{'path': path_object_1,
- 'etag': hashlib.md5(self.content).hexdigest(),
+ 'etag': md5(self.content,
+ usedforsecurity=False).hexdigest(),
'size_bytes': data_size},
{'path': path_object_2,
- 'etag': hashlib.md5(self.content).hexdigest(),
+ 'etag': md5(self.content,
+ usedforsecurity=False).hexdigest(),
'size_bytes': data_size}]
return json.dumps(data_manifest)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index 29354b6..e75e22a 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -15,8 +15,7 @@
import hashlib
import hmac
import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
from tempest.api.object_storage import base
from tempest.common import utils
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index bbb4827..4ad8428 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -15,8 +15,7 @@
import hashlib
import hmac
import time
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
from tempest.api.object_storage import base
from tempest.common import utils
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index 0a8b56d..659e2c4 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -64,7 +64,6 @@
class GroupSnapshotsTest(BaseGroupSnapshotsTest):
"""Test group snapshot"""
- _api_version = 3
min_microversion = '3.14'
max_microversion = 'latest'
@@ -253,7 +252,6 @@
class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
"""Test group snapshot with volume microversion greater than 3.18"""
- _api_version = 3
min_microversion = '3.19'
max_microversion = 'latest'
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
index 159c6fb..5c5913e 100644
--- a/tempest/api/volume/admin/test_group_type_specs.py
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -21,7 +21,6 @@
class GroupTypeSpecsTest(base.BaseVolumeAdminTest):
"""Test group type specs"""
- _api_version = 3
min_microversion = '3.11'
max_microversion = 'latest'
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 3993020..a7a5d6f 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -21,7 +21,6 @@
class GroupTypesTest(base.BaseVolumeAdminTest):
"""Test group types"""
- _api_version = 3
min_microversion = '3.11'
max_microversion = 'latest'
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index e67b985..747a194 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -25,7 +25,6 @@
class GroupsTest(base.BaseVolumeAdminTest):
"""Tests of volume groups with microversion greater than 3.12"""
- _api_version = 3
min_microversion = '3.13'
max_microversion = 'latest'
@@ -156,7 +155,6 @@
class GroupsV314Test(base.BaseVolumeAdminTest):
"""Tests of volume groups with microversion greater than 3.13"""
- _api_version = 3
min_microversion = '3.14'
max_microversion = 'latest'
@@ -194,7 +192,6 @@
class GroupsV320Test(base.BaseVolumeAdminTest):
"""Tests of volume groups with microversion greater than 3.19"""
- _api_version = 3
min_microversion = '3.20'
max_microversion = 'latest'
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 096709c..768c129 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -24,7 +24,6 @@
class UserMessagesTest(base.BaseVolumeAdminTest):
"""Test volume messages with microversion greater than 3.2"""
- _api_version = 3
min_microversion = '3.3'
max_microversion = 'latest'
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index c538e60..9f9fc3b 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -20,7 +20,6 @@
from tempest.lib.common import api_version_utils
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
-from tempest.lib import exceptions
import tempest.test
CONF = config.CONF
@@ -33,12 +32,6 @@
# Set this to True in subclasses to create a default network. See
# https://bugs.launchpad.net/tempest/+bug/1844568
create_default_network = False
- _api_version = 2
- # if api_v2 is not enabled while api_v3 is enabled, the volume v2 classes
- # should be transferred to volume v3 classes.
- if (not CONF.volume_feature_enabled.api_v2 and
- CONF.volume_feature_enabled.api_v3):
- _api_version = 3
credentials = ['primary']
@classmethod
@@ -48,17 +41,6 @@
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
- if cls._api_version == 2:
- if not CONF.volume_feature_enabled.api_v2:
- msg = "Volume API v2 is disabled"
- raise cls.skipException(msg)
- elif cls._api_version == 3:
- if not CONF.volume_feature_enabled.api_v3:
- msg = "Volume API v3 is disabled"
- raise cls.skipException(msg)
- else:
- msg = ("Invalid Cinder API version (%s)" % cls._api_version)
- raise exceptions.InvalidConfiguration(msg)
api_version_utils.check_skip_with_microversion(
cls.min_microversion, cls.max_microversion,
@@ -129,6 +111,13 @@
name = data_utils.rand_name(cls.__name__ + '-Volume')
kwargs['name'] = name
+ if CONF.volume.volume_type and 'volume_type' not in kwargs:
+ # If volume_type is not provided in config then no need to
+ # add a volume type and
+ # if volume_type has already been added by child class then
+ # no need to override.
+ kwargs['volume_type'] = CONF.volume.volume_type
+
if CONF.compute.compute_volume_common_az:
kwargs.setdefault('availability_zone',
CONF.compute.compute_volume_common_az)
diff --git a/tempest/api/volume/test_versions.py b/tempest/api/volume/test_versions.py
index e065bdf..578be58 100644
--- a/tempest/api/volume/test_versions.py
+++ b/tempest/api/volume/test_versions.py
@@ -19,8 +19,6 @@
class VersionsTest(base.BaseVolumeTest):
"""Test volume versions"""
- _api_version = 3
-
@decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369')
@decorators.attr(type='smoke')
def test_list_versions(self):
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 2e78114..fff6a44 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -164,7 +164,6 @@
class VolumesBackupsV39Test(base.BaseVolumeTest):
"""Test volumes backup with volume microversion greater than 3.8"""
- _api_version = 3
min_microversion = '3.9'
max_microversion = 'latest'
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 7441f1d..d9790f3 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -77,7 +77,6 @@
# details once that microversion is available in Nova.
credentials = ['primary', 'admin']
- _api_version = 3
# NOTE(mriedem): The minimum required volume API version is 3.42 and the
# minimum required compute API microversion is 2.51, but the compute call
# is implicit - Cinder calls Nova at that microversion, Tempest does not.
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 91728ab..28e41bf 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -143,7 +143,6 @@
class VolumesSummaryTest(base.BaseVolumeTest):
"""Test volume summary"""
- _api_version = 3
min_microversion = '3.12'
max_microversion = 'latest'
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 60f85a4..1d1981c 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -16,8 +16,8 @@
import operator
import random
+from urllib.parse import urlparse
-from six.moves.urllib.parse import urlparse
from testtools import matchers
from tempest.api.volume import base
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 389d3be..35dd0ca 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.api.volume import base
from tempest.common import utils
@@ -50,7 +50,7 @@
self.images_client.delete_image, image['id'])
# Upload image with 1KB data
- image_file = six.BytesIO(data_utils.random_bytes())
+ image_file = io.BytesIO(data_utils.random_bytes())
self.images_client.store_image_file(image['id'], image_file)
waiters.wait_for_image_status(self.images_client,
image['id'], 'active')
diff --git a/tempest/clients.py b/tempest/clients.py
index 6d19a0c..6db43db 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -88,6 +88,14 @@
self.image_v2.NamespacePropertiesClient()
self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
self.image_versions_client = self.image_v2.VersionsClient()
+ # NOTE(danms): If no alternate endpoint is configured,
+ # this client will work the same as the base self.images_client.
+ # If your test needs to know if these are different, check the
+ # config option to see if the alternate_image_endpoint is set.
+ self.image_client_remote = self.image_v2.ImagesClient(
+ service=CONF.image.alternate_image_endpoint,
+ endpoint_type=CONF.image.alternate_image_endpoint_type,
+ region=CONF.image.region)
def _set_compute_clients(self):
self.agents_client = self.compute.AgentsClient()
@@ -231,89 +239,85 @@
def _set_volume_clients(self):
- # if only api_v3 is enabled, all these clients should be available
- if (CONF.volume_feature_enabled.api_v2 or
- CONF.volume_feature_enabled.api_v3):
- self.backups_client_latest = self.volume_v3.BackupsClient()
- self.encryption_types_client_latest = \
- self.volume_v3.EncryptionTypesClient()
- self.snapshot_manage_client_latest = \
- self.volume_v3.SnapshotManageClient()
- self.snapshots_client_latest = self.volume_v3.SnapshotsClient()
- self.volume_capabilities_client_latest = \
- self.volume_v3.CapabilitiesClient()
- self.volume_manage_client_latest = (
- self.volume_v3.VolumeManageClient())
- self.volume_qos_client_latest = self.volume_v3.QosSpecsClient()
- self.volume_services_client_latest = (
- self.volume_v3.ServicesClient())
- self.volume_types_client_latest = self.volume_v3.TypesClient()
- self.volume_hosts_client_latest = self.volume_v3.HostsClient()
- self.volume_quotas_client_latest = self.volume_v3.QuotasClient()
- self.volume_quota_classes_client_latest = \
- self.volume_v3.QuotaClassesClient()
- self.volume_scheduler_stats_client_latest = \
- self.volume_v3.SchedulerStatsClient()
- self.volume_transfers_client_latest = \
- self.volume_v3.TransfersClient()
- self.volume_availability_zone_client_latest = \
- self.volume_v3.AvailabilityZoneClient()
- self.volume_limits_client_latest = self.volume_v3.LimitsClient()
- self.volumes_client_latest = self.volume_v3.VolumesClient()
- self.volumes_extension_client_latest = \
- self.volume_v3.ExtensionsClient()
- self.group_types_client_latest = self.volume_v3.GroupTypesClient()
- self.groups_client_latest = self.volume_v3.GroupsClient()
- self.group_snapshots_client_latest = \
- self.volume_v3.GroupSnapshotsClient()
- self.volume_messages_client_latest = (
- self.volume_v3.MessagesClient())
- self.volume_versions_client_latest = (
- self.volume_v3.VersionsClient())
- self.attachments_client_latest = (
- self.volume_v3.AttachmentsClient())
+ self.backups_client_latest = self.volume_v3.BackupsClient()
+ self.encryption_types_client_latest = \
+ self.volume_v3.EncryptionTypesClient()
+ self.snapshot_manage_client_latest = \
+ self.volume_v3.SnapshotManageClient()
+ self.snapshots_client_latest = self.volume_v3.SnapshotsClient()
+ self.volume_capabilities_client_latest = \
+ self.volume_v3.CapabilitiesClient()
+ self.volume_manage_client_latest = (
+ self.volume_v3.VolumeManageClient())
+ self.volume_qos_client_latest = self.volume_v3.QosSpecsClient()
+ self.volume_services_client_latest = (
+ self.volume_v3.ServicesClient())
+ self.volume_types_client_latest = self.volume_v3.TypesClient()
+ self.volume_hosts_client_latest = self.volume_v3.HostsClient()
+ self.volume_quotas_client_latest = self.volume_v3.QuotasClient()
+ self.volume_quota_classes_client_latest = \
+ self.volume_v3.QuotaClassesClient()
+ self.volume_scheduler_stats_client_latest = \
+ self.volume_v3.SchedulerStatsClient()
+ self.volume_transfers_client_latest = \
+ self.volume_v3.TransfersClient()
+ self.volume_availability_zone_client_latest = \
+ self.volume_v3.AvailabilityZoneClient()
+ self.volume_limits_client_latest = self.volume_v3.LimitsClient()
+ self.volumes_client_latest = self.volume_v3.VolumesClient()
+ self.volumes_extension_client_latest = \
+ self.volume_v3.ExtensionsClient()
+ self.group_types_client_latest = self.volume_v3.GroupTypesClient()
+ self.groups_client_latest = self.volume_v3.GroupsClient()
+ self.group_snapshots_client_latest = \
+ self.volume_v3.GroupSnapshotsClient()
+ self.volume_messages_client_latest = (
+ self.volume_v3.MessagesClient())
+ self.volume_versions_client_latest = (
+ self.volume_v3.VersionsClient())
+ self.attachments_client_latest = (
+ self.volume_v3.AttachmentsClient())
- # TODO(gmann): Below alias for service clients have been
- # deprecated and will be removed in future. Start using the alias
- # defined above with suffix _latest.
- # ****************Deprecated alias start from here***************
- self.backups_v2_client = self.volume_v3.BackupsClient()
- self.encryption_types_v2_client = \
- self.volume_v3.EncryptionTypesClient()
- self.snapshot_manage_v2_client = \
- self.volume_v3.SnapshotManageClient()
- self.snapshots_v2_client = self.volume_v3.SnapshotsClient()
- self.volume_capabilities_v2_client = \
- self.volume_v3.CapabilitiesClient()
- self.volume_manage_v2_client = self.volume_v3.VolumeManageClient()
- self.volume_qos_v2_client = self.volume_v3.QosSpecsClient()
- self.volume_services_v2_client = self.volume_v3.ServicesClient()
- self.volume_types_v2_client = self.volume_v3.TypesClient()
- self.volume_hosts_v2_client = self.volume_v3.HostsClient()
- self.volume_quotas_v2_client = self.volume_v3.QuotasClient()
- self.volume_quota_classes_v2_client = \
- 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_v2_availability_zone_client = \
- self.volume_v3.AvailabilityZoneClient()
- self.volume_v2_limits_client = self.volume_v3.LimitsClient()
- self.volumes_v2_client = self.volume_v3.VolumesClient()
- self.volumes_v2_extension_client = \
- self.volume_v3.ExtensionsClient()
+ # TODO(gmann): Below alias for service clients have been
+ # deprecated and will be removed in future. Start using the alias
+ # defined above with suffix _latest.
+ # ****************Deprecated alias start from here***************
+ self.backups_v2_client = self.volume_v3.BackupsClient()
+ self.encryption_types_v2_client = \
+ self.volume_v3.EncryptionTypesClient()
+ self.snapshot_manage_v2_client = \
+ self.volume_v3.SnapshotManageClient()
+ self.snapshots_v2_client = self.volume_v3.SnapshotsClient()
+ self.volume_capabilities_v2_client = \
+ self.volume_v3.CapabilitiesClient()
+ self.volume_manage_v2_client = self.volume_v3.VolumeManageClient()
+ self.volume_qos_v2_client = self.volume_v3.QosSpecsClient()
+ self.volume_services_v2_client = self.volume_v3.ServicesClient()
+ self.volume_types_v2_client = self.volume_v3.TypesClient()
+ self.volume_hosts_v2_client = self.volume_v3.HostsClient()
+ self.volume_quotas_v2_client = self.volume_v3.QuotasClient()
+ self.volume_quota_classes_v2_client = \
+ 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_v2_availability_zone_client = \
+ self.volume_v3.AvailabilityZoneClient()
+ self.volume_v2_limits_client = self.volume_v3.LimitsClient()
+ self.volumes_v2_client = self.volume_v3.VolumesClient()
+ self.volumes_v2_extension_client = \
+ self.volume_v3.ExtensionsClient()
- if CONF.volume_feature_enabled.api_v3:
- self.backups_v3_client = self.volume_v3.BackupsClient()
- self.group_types_v3_client = self.volume_v3.GroupTypesClient()
- self.groups_v3_client = self.volume_v3.GroupsClient()
- self.group_snapshots_v3_client = \
- self.volume_v3.GroupSnapshotsClient()
- self.snapshots_v3_client = self.volume_v3.SnapshotsClient()
- self.volume_v3_messages_client = self.volume_v3.MessagesClient()
- self.volume_v3_versions_client = self.volume_v3.VersionsClient()
- self.volumes_v3_client = self.volume_v3.VolumesClient()
- # ****************Deprecated alias end here***********************
+ self.backups_v3_client = self.volume_v3.BackupsClient()
+ self.group_types_v3_client = self.volume_v3.GroupTypesClient()
+ self.groups_v3_client = self.volume_v3.GroupsClient()
+ self.group_snapshots_v3_client = \
+ self.volume_v3.GroupSnapshotsClient()
+ self.snapshots_v3_client = self.volume_v3.SnapshotsClient()
+ self.volume_v3_messages_client = self.volume_v3.MessagesClient()
+ self.volume_v3_versions_client = self.volume_v3.VersionsClient()
+ self.volumes_v3_client = self.volume_v3.VolumesClient()
+ # ****************Deprecated alias end here***********************
def _set_object_storage_clients(self):
self.account_client = self.object_storage.AccountClient()
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 84d2492..f2370f3 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_log import log as logging
-from six.moves.urllib import parse as urllib
from tempest import clients
from tempest.common import credentials_factory as credentials
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 6e93d69..3eae552 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import configparser
import os
import shutil
import sys
@@ -19,7 +20,6 @@
from cliff import command
from oslo_config import generator
from oslo_log import log as logging
-from six import moves
from stestr import commands
from tempest.cmd import workspace
@@ -92,7 +92,7 @@
stestr_conf_file.write(stestr_conf)
def get_configparser(self, conf_path):
- config_parse = moves.configparser.ConfigParser()
+ config_parse = configparser.ConfigParser()
config_parse.optionxform = str
# get any existing values if a config file already exists
if os.path.isfile(conf_path):
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 8bebce2..2669ff7 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -22,10 +22,10 @@
* ``--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
+* ``--exclude-regex``: It allows to do simple test exclusion via passing a
+ rejection/exclude regexp
-There are also the ``--blacklist-file`` and ``--whitelist-file`` options that
+There are also the ``--exclude-list`` and ``--include-list`` options that
let you pass a filepath to tempest run with the file format being a line
separated regex, with '#' used to signify the start of a comment on a line.
For example::
@@ -128,6 +128,7 @@
import sys
from cliff import command
+from oslo_log import log
from oslo_serialization import jsonutils as json
from stestr import commands
@@ -141,6 +142,8 @@
CONF = config.CONF
SAVED_STATE_JSON = "saved_state.json"
+LOG = log.getLogger(__name__)
+
class TempestRun(command.Command):
@@ -201,23 +204,71 @@
self._init_state()
regex = self._build_regex(parsed_args)
+
+ # temporary method for parsing deprecated and new stestr options
+ # and showing warning messages in order to make the transition
+ # smoother for all tempest consumers
+ # TODO(kopecmartin) remove this after stestr>=3.1.0 is used
+ # in all supported OpenStack releases
+ def parse_dep(old_o, old_v, new_o, new_v):
+ ret = ''
+ if old_v:
+ LOG.warning("'%s' option is deprecated, use '%s' instead "
+ "which is functionally equivalent. Right now "
+ "Tempest still supports this option for "
+ "backward compatibility, however, it will be "
+ "removed soon.",
+ old_o, new_o)
+ ret = old_v
+ if old_v and new_v:
+ # both options are specified
+ LOG.warning("'%s' and '%s' are specified at the same time, "
+ "'%s' takes precedence over '%s'",
+ new_o, old_o, new_o, old_o)
+ if new_v:
+ ret = new_v
+ return ret
+ ex_regex = parse_dep('--black-regex', parsed_args.black_regex,
+ '--exclude-regex', parsed_args.exclude_regex)
+ ex_list = parse_dep('--blacklist-file', parsed_args.blacklist_file,
+ '--exclude-list', parsed_args.exclude_list)
+ in_list = parse_dep('--whitelist-file', parsed_args.whitelist_file,
+ '--include-list', parsed_args.include_list)
+
return_code = 0
if parsed_args.list_tests:
- return_code = commands.list_command(
- filters=regex, whitelist_file=parsed_args.whitelist_file,
- blacklist_file=parsed_args.blacklist_file,
- black_regex=parsed_args.black_regex)
+ try:
+ return_code = commands.list_command(
+ filters=regex, include_list=in_list,
+ exclude_list=ex_list, exclude_regex=ex_regex)
+ except TypeError:
+ # exclude_list, include_list and exclude_regex are defined only
+ # in stestr >= 3.1.0, this except block catches the case when
+ # tempest is executed with an older stestr
+ return_code = commands.list_command(
+ filters=regex, whitelist_file=in_list,
+ blacklist_file=ex_list, black_regex=ex_regex)
else:
serial = not parsed_args.parallel
- return_code = commands.run_command(
- filters=regex, subunit_out=parsed_args.subunit,
- serial=serial, concurrency=parsed_args.concurrency,
- blacklist_file=parsed_args.blacklist_file,
- whitelist_file=parsed_args.whitelist_file,
- black_regex=parsed_args.black_regex,
- worker_path=parsed_args.worker_file,
- load_list=parsed_args.load_list, combine=parsed_args.combine)
+ params = {
+ 'filters': regex, 'subunit_out': parsed_args.subunit,
+ 'serial': serial, 'concurrency': parsed_args.concurrency,
+ 'worker_path': parsed_args.worker_file,
+ 'load_list': parsed_args.load_list,
+ 'combine': parsed_args.combine
+ }
+ try:
+ return_code = commands.run_command(
+ **params, exclude_list=ex_list,
+ include_list=in_list, exclude_regex=ex_regex)
+ except TypeError:
+ # exclude_list, include_list and exclude_regex are defined only
+ # in stestr >= 3.1.0, this except block catches the case when
+ # tempest is executed with an older stestr
+ return_code = commands.run_command(
+ **params, blacklist_file=ex_list,
+ whitelist_file=in_list, black_regex=ex_regex)
if return_code > 0:
sys.exit(return_code)
return return_code
@@ -271,15 +322,38 @@
help='A normal stestr selection regex used to '
'specify a subset of tests to run')
parser.add_argument('--black-regex', dest='black_regex',
+ help='DEPRECATED: This option is deprecated and '
+ 'will be removed soon, use --exclude-regex '
+ 'which is functionally equivalent. If this '
+ 'is specified at the same time as '
+ '--exclude-regex, this flag will be ignored '
+ 'and --exclude-regex will be used')
+ parser.add_argument('--exclude-regex', dest='exclude_regex',
help='A regex to exclude tests that match it')
parser.add_argument('--whitelist-file', '--whitelist_file',
- help="Path to a whitelist file, this file "
- "contains a separate regex on each "
- "newline.")
+ help='DEPRECATED: This option is deprecated and '
+ 'will be removed soon, use --include-list '
+ 'which is functionally equivalent. If this '
+ 'is specified at the same time as '
+ '--include-list, this flag will be ignored '
+ 'and --include-list will be used')
+ parser.add_argument('--include-list', '--include_list',
+ help="Path to an include file which contains the "
+ "regex for tests to be included in tempest "
+ "run, this file contains a separate regex on "
+ "each newline.")
parser.add_argument('--blacklist-file', '--blacklist_file',
- help='Path to a blacklist file, this file '
- 'contains a separate regex exclude on '
- 'each newline')
+ help='DEPRECATED: This option is deprecated and '
+ 'will be removed soon, use --exclude-list '
+ 'which is functionally equivalent. If this '
+ 'is specified at the same time as '
+ '--exclude-list, this flag will be ignored '
+ 'and --exclude-list will be used')
+ parser.add_argument('--exclude-list', '--exclude_list',
+ help='Path to an exclude file which contains the '
+ 'regex for tests to be excluded in tempest '
+ 'run, this file contains a separate regex on '
+ 'each newline.')
parser.add_argument('--load-list', '--load_list',
help='Path to a non-regex whitelist file, '
'this file contains a separate test '
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 172fbaa..6c36d82 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -40,12 +40,14 @@
subunit-describe-calls will take in either stdin subunit v1 or v2 stream or a
file path which contains either a subunit v1 or v2 stream passed via the
---subunit parameter. This is then parsed checking for details contained in the
-file_bytes of the --non-subunit-name parameter (the default is pythonlogging
-which is what Tempest uses to store logs). By default the OpenStack Kilo
-release port defaults (http://bit.ly/22jpF5P) are used unless a file is
-provided via the --ports option. The resulting output is dumped in JSON output
-to the path provided in the --output-file option.
+``--subunit`` parameter. This is then parsed checking for details contained in
+the file_bytes of the ``--non-subunit-name`` parameter (the default is
+pythonlogging which is what Tempest uses to store logs). By default `the
+OpenStack default ports
+<https://docs.openstack.org/install-guide/firewalls-default-ports.html>`_
+are used unless a file is provided via the ``--ports`` option. The resulting
+output is dumped in JSON output to the path provided in the ``--output-file``
+option.
Ports file JSON structure
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -110,9 +112,8 @@
response_re = re.compile(r'.* Response - Headers: (?P<headers>.*)')
body_re = re.compile(r'.*Body: (?P<body>.*)')
- # Based on newton defaults:
- # http://docs.openstack.org/newton/config-reference/
- # firewalls-default-ports.html
+ # Based on OpenStack default ports:
+ # https://docs.openstack.org/install-guide/firewalls-default-ports.html
services = {
"8776": "Block Storage",
"8774": "Nova",
@@ -131,7 +132,10 @@
"3260": "iSCSI",
"3306": "MySQL",
"5672": "AMQP",
- "8082": "murano"}
+ "8082": "murano",
+ "8778": "Clustering",
+ "8999": "Vitrage",
+ "8989": "Mistral"}
def __init__(self, services=None):
super(UrlParser, self).__init__()
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 235d8e3..0db1ab1 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -202,22 +202,8 @@
not CONF.identity_feature_enabled.api_v3, update)
-def verify_cinder_api_versions(os, update):
- # Check cinder api versions
- versions = _get_api_versions(os, 'cinder')
- if (CONF.volume_feature_enabled.api_v2 !=
- contains_version('v2.', versions)):
- print_and_or_update('api_v2', 'volume-feature-enabled',
- not CONF.volume_feature_enabled.api_v2, update)
- if (CONF.volume_feature_enabled.api_v3 !=
- contains_version('v3.', versions)):
- print_and_or_update('api_v3', 'volume-feature-enabled',
- not CONF.volume_feature_enabled.api_v3, update)
-
-
def verify_api_versions(os, service, update):
verify = {
- 'cinder': verify_cinder_api_versions,
'glance': verify_glance_api_versions,
'keystone': verify_keystone_api_versions,
}
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 42f68f1..a062f6f 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -18,8 +18,7 @@
import ssl
import struct
import textwrap
-
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
from oslo_log import log as logging
from oslo_utils import excutils
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 38881ee..88a16b7 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -13,37 +13,17 @@
# under the License.
import functools
-from functools import partial
import testtools
from tempest import config
from tempest.exceptions import InvalidServiceTag
-from tempest.lib.common.utils import data_utils as lib_data_utils
from tempest.lib import decorators
CONF = config.CONF
-class DataUtils(object):
- def __getattr__(self, attr):
-
- if attr == 'rand_name':
- # NOTE(flwang): This is a proxy to generate a random name that
- # includes a random number and a prefix 'tempest'
- attr_obj = partial(lib_data_utils.rand_name,
- prefix='tempest')
- else:
- attr_obj = getattr(lib_data_utils, attr)
-
- self.__dict__[attr] = attr_obj
- return attr_obj
-
-
-data_utils = DataUtils()
-
-
def get_service_list():
service_list = {
'compute': CONF.service_available.nova,
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index e3c33c7..eaac05e 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -193,26 +193,34 @@
raise lib_exc.TimeoutException(message)
-def wait_for_image_imported_to_stores(client, image_id, stores):
+def wait_for_image_imported_to_stores(client, image_id, stores=None):
"""Waits for an image to be imported to all requested stores.
+ Short circuits to fail if the serer reports failure of any store.
+ If stores is None, just wait for status==active.
+
The client should also have build_interval and build_timeout attributes.
"""
+ exc_cls = lib_exc.TimeoutException
start = int(time.time())
while int(time.time()) - start < client.build_timeout:
image = client.show_image(image_id)
- if image['status'] == 'active' and image['stores'] == stores:
+ if image['status'] == 'active' and (stores is None or
+ image['stores'] == stores):
return
+ if image.get('os_glance_failed_import'):
+ exc_cls = lib_exc.OtherRestClientException
+ break
time.sleep(client.build_interval)
message = ('Image %s failed to import on stores: %s' %
- (image_id, str(image['os_glance_failed_import'])))
+ (image_id, str(image.get('os_glance_failed_import'))))
caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
- raise lib_exc.TimeoutException(message)
+ raise exc_cls(message)
def wait_for_image_copied_to_stores(client, image_id):
diff --git a/tempest/config.py b/tempest/config.py
index 62975be..c409db6 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -636,6 +636,10 @@
"APIs that only worked with that driver "
"have been removed and there's nothing to "
"test after Ussuri."),
+ cfg.BoolOpt('ide_bus',
+ default=True,
+ help='Does the test environment support attaching devices '
+ 'using an IDE bus to the instance?'),
]
@@ -657,6 +661,15 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the image service."),
+ cfg.StrOpt('alternate_image_endpoint',
+ default=None,
+ help="Alternate endpoint name for cross-worker testing"),
+ cfg.StrOpt('alternate_image_endpoint_type',
+ default='publicURL',
+ choices=['public', 'admin', 'internal',
+ 'publicURL', 'adminURL', 'internalURL'],
+ help=("The endpoint type to use for the alternate image "
+ "service.")),
cfg.StrOpt('http_image',
default='http://download.cirros-cloud.net/0.3.1/'
'cirros-0.3.1-x86_64-uec.tar.gz',
@@ -706,6 +719,13 @@
cfg.BoolOpt('import_image',
default=False,
help="Is image import feature enabled"),
+ # NOTE(danms): Starting mid-Wallaby glance began enforcing the
+ # previously-informal requirement that os_glance_* properties are
+ # reserved for internal use. Thus, we can only run these checks
+ # if we know we are on a new enough glance.
+ cfg.BoolOpt('os_glance_reserved',
+ default=False,
+ help="Should we check that os_glance namespace is reserved"),
]
network_group = cfg.OptGroup(name='network',
@@ -965,6 +985,9 @@
default=['BACKEND_1', 'BACKEND_2'],
help='A list of backend names separated by comma. '
'The backend name must be declared in cinder.conf'),
+ cfg.StrOpt('volume_type',
+ default='',
+ help='Volume type to be used while creating volume.'),
cfg.StrOpt('storage_protocol',
default='iSCSI',
help='Backend protocol to target when creating volume types'),
@@ -1036,31 +1059,6 @@
help='A list of enabled volume extensions with a special '
'entry all which indicates every extension is enabled. '
'Empty list indicates all extensions are disabled'),
- cfg.BoolOpt('api_v2',
- default=True,
- help="Is the v2 volume API enabled",
- deprecated_for_removal=True,
- deprecated_reason="The v2 volume API has been deprecated "
- "since Pike release. Now Tempest run all "
- "the volume tests against v2 or v3 API "
- "based on CONF.volume.catalog_type which "
- "makes this config option unusable. If "
- "catalog_type is volumev2, then all the "
- "volume tests will run against v2 API. "
- "Use ``CONF.volume.catalog_type`` to run "
- "the Tempest against volume v2 or v3 API"),
- cfg.BoolOpt('api_v3',
- default=True,
- help="Is the v3 volume API enabled",
- deprecated_for_removal=True,
- deprecated_reason="Tempest run all the volume tests against "
- "v2 or v3 API based on "
- "CONF.volume.catalog_type which makes this "
- "config option unusable. If catalog_type is "
- "volumev3 which is default, then all the "
- "volume tests will run against v3 API. "
- "Use ``CONF.volume.catalog_type`` to run "
- "the Tempest against volume v2 or v3 API"),
cfg.BoolOpt('extend_attached_volume',
default=False,
help='Does the cloud support extending the size of a volume '
@@ -1201,6 +1199,39 @@
help="Whether or not horizon is expected to be available"),
]
+enforce_scope_group = cfg.OptGroup(name="enforce_scope",
+ title="OpenStack Services with "
+ "enforce scope")
+
+
+EnforceScopeGroup = [
+ cfg.BoolOpt('nova',
+ default=False,
+ help='Does the compute service API policies enforce scope? '
+ 'This configuration value should be same as '
+ 'nova.conf: [oslo_policy].enforce_scope option.'),
+ cfg.BoolOpt('neutron',
+ default=False,
+ help='Does the network service API policies enforce scope? '
+ 'This configuration value should be same as '
+ 'neutron.conf: [oslo_policy].enforce_scope option.'),
+ cfg.BoolOpt('glance',
+ default=False,
+ help='Does the Image service API policies enforce scope? '
+ 'This configuration value should be same as '
+ 'glance.conf: [oslo_policy].enforce_scope option.'),
+ cfg.BoolOpt('cinder',
+ default=False,
+ help='Does the Volume service API policies enforce scope? '
+ 'This configuration value should be same as '
+ 'cinder.conf: [oslo_policy].enforce_scope option.'),
+ cfg.BoolOpt('keystone',
+ default=False,
+ help='Does the Identity service API policies enforce scope? '
+ 'This configuration value should be same as '
+ 'keystone.conf: [oslo_policy].enforce_scope option.'),
+]
+
debug_group = cfg.OptGroup(name="debug",
title="Debug System")
@@ -1270,6 +1301,7 @@
(object_storage_feature_group, ObjectStoreFeaturesGroup),
(scenario_group, ScenarioGroup),
(service_available_group, ServiceAvailableGroup),
+ (enforce_scope_group, EnforceScopeGroup),
(debug_group, DebugGroup),
(placement_group, PlacementGroup),
(profiler_group, ProfilerGroup),
@@ -1339,6 +1371,7 @@
'object-storage-feature-enabled']
self.scenario = _CONF.scenario
self.service_available = _CONF.service_available
+ self.enforce_scope = _CONF.enforce_scope
self.debug = _CONF.debug
logging.tempest_set_log_file('tempest.log')
# Setting attributes for plugins
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 9f8c7c5..8bdf98e 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -18,10 +18,9 @@
import copy
import datetime
import re
+from urllib import parse as urlparse
from oslo_log import log as logging
-import six
-from six.moves.urllib import parse as urlparse
from tempest.lib import exceptions
from tempest.lib.services.identity.v2 import token_client as json_v2id
@@ -65,8 +64,7 @@
return url
-@six.add_metaclass(abc.ABCMeta)
-class AuthProvider(object):
+class AuthProvider(object, metaclass=abc.ABCMeta):
"""Provide authentication"""
SCOPES = set(['project'])
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index ff09671..0ae11ca 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -22,10 +22,10 @@
import os
import sys
import unittest
+import urllib.parse as urlparse
import uuid
from oslo_utils import uuidutils
-import six.moves.urllib.parse as urlparse
DECORATOR_MODULE = 'decorators'
DECORATOR_NAME = 'idempotent_id'
diff --git a/tempest/lib/common/cred_client.py b/tempest/lib/common/cred_client.py
index 3af09a0..f13d6d0 100644
--- a/tempest/lib/common/cred_client.py
+++ b/tempest/lib/common/cred_client.py
@@ -13,7 +13,6 @@
import abc
from oslo_log import log as logging
-import six
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
@@ -22,8 +21,7 @@
LOG = logging.getLogger(__name__)
-@six.add_metaclass(abc.ABCMeta)
-class CredsClient(object):
+class CredsClient(object, metaclass=abc.ABCMeta):
"""This class is a wrapper around the identity clients
to provide a single interface for managing credentials in both v2 and v3
@@ -39,11 +37,15 @@
self.projects_client = projects_client
self.roles_client = roles_client
- def create_user(self, username, password, project, email):
+ def create_user(self, username, password, project=None, email=None):
params = {'name': username,
- 'password': password,
- self.project_id_param: project['id'],
- 'email': email}
+ 'password': password}
+ # with keystone v3, a default project is not required
+ if project:
+ params[self.project_id_param] = project['id']
+ # email is not a first-class attribute of a user
+ if email:
+ params['email'] = email
user = self.users_client.create_user(**params)
if 'user' in user:
user = user['user']
@@ -160,6 +162,31 @@
def delete_project(self, project_id):
self.projects_client.delete_project(project_id)
+ def create_domain(self, name, description):
+ domain = self.domains_client.create_domain(
+ name=name, description=description)['domain']
+ return domain
+
+ def delete_domain(self, domain_id):
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
+
+ def create_user(self, username, password, project=None, email=None,
+ domain_id=None):
+ params = {'name': username,
+ 'password': password,
+ 'domain_id': domain_id or self.creds_domain['id']}
+ # with keystone v3, a default project is not required
+ if project:
+ params[self.project_id_param] = project['id']
+ # email is not a first-class attribute of a user
+ if email:
+ params['email'] = email
+ user = self.users_client.create_user(**params)
+ if 'user' in user:
+ user = user['user']
+ return user
+
def get_credentials(
self, user, project, password, domain=None, system=None):
# User, project and domain already include both ID and name here,
@@ -215,6 +242,23 @@
LOG.debug("Role %s already assigned on domain %s for user %s",
role['id'], domain['id'], user['id'])
+ def assign_user_role_on_system(self, user, role_name):
+ """Assign the specified role on the system
+
+ :param user: a user dict
+ :param role_name: name of the role to be assigned
+ """
+ role = self._check_role_exists(role_name)
+ if not role:
+ msg = 'No "%s" role found' % role_name
+ raise lib_exc.NotFound(msg)
+ try:
+ self.roles_client.create_user_role_on_system(
+ user['id'], role['id'])
+ except lib_exc.Conflict:
+ LOG.debug("Role %s already assigned on the system for user %s",
+ role['id'], user['id'])
+
def get_creds_client(identity_client,
projects_client,
diff --git a/tempest/lib/common/cred_provider.py b/tempest/lib/common/cred_provider.py
index 42ed41b..069172a 100644
--- a/tempest/lib/common/cred_provider.py
+++ b/tempest/lib/common/cred_provider.py
@@ -14,14 +14,12 @@
import abc
-import six
from tempest.lib import auth
from tempest.lib import exceptions
-@six.add_metaclass(abc.ABCMeta)
-class CredentialProvider(object):
+class CredentialProvider(object, metaclass=abc.ABCMeta):
def __init__(self, identity_version, name=None,
network_resources=None, credentials_domain=None,
admin_role=None, identity_uri=None):
@@ -60,6 +58,54 @@
return
@abc.abstractmethod
+ def get_system_admin_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_system_member_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_system_reader_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_domain_admin_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_domain_member_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_domain_reader_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_project_admin_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_project_alt_admin_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_project_member_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_project_alt_member_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_project_reader_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_project_alt_reader_creds(self):
+ return
+
+ @abc.abstractmethod
def clear_creds(self):
return
@@ -72,7 +118,7 @@
return
@abc.abstractmethod
- def get_creds_by_roles(self, roles, force_new=False):
+ def get_creds_by_roles(self, roles, force_new=False, scope=None):
return
@abc.abstractmethod
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index d9f4f46..f334c36 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -164,62 +164,101 @@
os.network.PortsClient(),
os.network.SecurityGroupsClient())
- def _create_creds(self, admin=False, roles=None):
+ def _create_creds(self, admin=False, roles=None, scope='project'):
"""Create credentials with random name.
- Creates project and user. When admin flag is True create user
- with admin role. Assign user with additional roles (for example
- _member_) and roles requested by caller.
+ Creates user and role assignments on a project, domain, or system. When
+ the admin flag is True, creates user with the admin role on the
+ resource. If roles are provided, assigns those roles on the resource.
+ Otherwise, assigns the user the 'member' role on the resource.
:param admin: Flag if to assign to the user admin role
:type admin: bool
:param roles: Roles to assign for the user
:type roles: list
+ :param str scope: The scope for the role assignment, may be one of
+ 'project', 'domain', or 'system'.
:return: Readonly Credentials with network resources
+ :raises: Exception if scope is invalid
"""
+ if not roles:
+ roles = []
root = self.name
- project_name = data_utils.rand_name(root, prefix=self.resource_prefix)
- project_desc = project_name + "-desc"
- project = self.creds_client.create_project(
- name=project_name, description=project_desc)
+ cred_params = {
+ 'project': None,
+ 'domain': None,
+ 'system': None
+ }
+ if scope == 'project':
+ project_name = data_utils.rand_name(
+ root, prefix=self.resource_prefix)
+ project_desc = project_name + '-desc'
+ project = self.creds_client.create_project(
+ name=project_name, description=project_desc)
- # NOTE(andreaf) User and project can be distinguished from the context,
- # having the same ID in both makes it easier to match them and debug.
- username = project_name
- user_password = data_utils.rand_password()
- email = data_utils.rand_name(
- root, prefix=self.resource_prefix) + "@example.com"
- user = self.creds_client.create_user(
- username, user_password, project, email)
- role_assigned = False
+ # NOTE(andreaf) User and project can be distinguished from the
+ # context, having the same ID in both makes it easier to match them
+ # and debug.
+ username = project_name + '-project'
+ cred_params['project'] = project
+ elif scope == 'domain':
+ domain_name = data_utils.rand_name(
+ root, prefix=self.resource_prefix)
+ domain_desc = domain_name + '-desc'
+ domain = self.creds_client.create_domain(
+ name=domain_name, description=domain_desc)
+ username = domain_name + '-domain'
+ cred_params['domain'] = domain
+ elif scope == 'system':
+ prefix = data_utils.rand_name(root, prefix=self.resource_prefix)
+ username = prefix + '-system'
+ cred_params['system'] = 'all'
+ else:
+ raise lib_exc.InvalidScopeType(scope=scope)
if admin:
- self.creds_client.assign_user_role(user, project, self.admin_role)
- role_assigned = True
+ username += '-admin'
+ elif roles and len(roles) == 1:
+ username += '-' + roles[0]
+ user_password = data_utils.rand_password()
+ cred_params['password'] = user_password
+ user = self.creds_client.create_user(
+ username, user_password)
+ cred_params['user'] = user
+ roles_to_assign = [r for r in roles]
+ if admin:
+ roles_to_assign.append(self.admin_role)
+ if scope == 'project':
+ self.creds_client.assign_user_role(
+ user, project, self.identity_admin_role)
if (self.identity_version == 'v3' and
self.identity_admin_domain_scope):
self.creds_client.assign_user_role_on_domain(
user, self.identity_admin_role)
# Add roles specified in config file
- for conf_role in self.extra_roles:
- self.creds_client.assign_user_role(user, project, conf_role)
- role_assigned = True
- # Add roles requested by caller
- if roles:
- for role in roles:
- self.creds_client.assign_user_role(user, project, role)
- role_assigned = True
+ roles_to_assign.extend(self.extra_roles)
+ # If there are still no roles, default to 'member'
# NOTE(mtreinish) For a user to have access to a project with v3 auth
# it must beassigned a role on the project. So we need to ensure that
# our newly created user has a role on the newly created project.
- if self.identity_version == 'v3' and not role_assigned:
+ if not roles_to_assign and self.identity_version == 'v3':
+ roles_to_assign = ['member']
try:
self.creds_client.create_user_role('member')
except lib_exc.Conflict:
LOG.warning('member role already exists, ignoring conflict.')
- self.creds_client.assign_user_role(user, project, 'member')
+ for role in roles_to_assign:
+ if scope == 'project':
+ self.creds_client.assign_user_role(user, project, role)
+ elif scope == 'domain':
+ self.creds_client.assign_user_role_on_domain(
+ user, role, domain)
+ elif scope == 'system':
+ self.creds_client.assign_user_role_on_system(user, role)
+ LOG.info("Roles assigned to the user %s are: %s",
+ user['id'], roles_to_assign)
- creds = self.creds_client.get_credentials(user, project, user_password)
+ creds = self.creds_client.get_credentials(**cred_params)
return cred_provider.TestResources(creds)
def _create_network_resources(self, tenant_id):
@@ -303,7 +342,7 @@
tenant_id=tenant_id,
enable_dhcp=self.network_resources['dhcp'],
ip_version=(ipaddress.ip_network(
- six.text_type(subnet_cidr)).version))
+ str(subnet_cidr)).version))
else:
resp_body = self.subnets_admin_client.\
create_subnet(network_id=network_id,
@@ -311,7 +350,7 @@
name=subnet_name,
tenant_id=tenant_id,
ip_version=(ipaddress.ip_network(
- six.text_type(subnet_cidr)).version))
+ str(subnet_cidr)).version))
break
except lib_exc.BadRequest as e:
if 'overlaps with another subnet' not in str(e):
@@ -334,16 +373,38 @@
self.routers_admin_client.add_router_interface(router_id,
subnet_id=subnet_id)
- def get_credentials(self, credential_type):
- if self._creds.get(str(credential_type)):
+ def get_credentials(self, credential_type, scope=None):
+ if not scope and self._creds.get(str(credential_type)):
credentials = self._creds[str(credential_type)]
+ elif scope and (
+ self._creds.get("%s_%s" % (scope, str(credential_type)))):
+ credentials = self._creds["%s_%s" % (scope, str(credential_type))]
else:
- if credential_type in ['primary', 'alt', 'admin']:
+ LOG.debug("Creating new dynamic creds for scope: %s and "
+ "credential_type: %s", scope, credential_type)
+ if scope:
+ if credential_type in [['admin'], ['alt_admin']]:
+ credentials = self._create_creds(
+ admin=True, scope=scope)
+ elif credential_type in [['alt_member'], ['alt_reader']]:
+ cred_type = credential_type[0][4:]
+ if isinstance(cred_type, str):
+ cred_type = [cred_type]
+ credentials = self._create_creds(
+ roles=cred_type, scope=scope)
+ else:
+ credentials = self._create_creds(
+ roles=credential_type, scope=scope)
+ elif credential_type in ['primary', 'alt', 'admin']:
is_admin = (credential_type == 'admin')
credentials = self._create_creds(admin=is_admin)
else:
credentials = self._create_creds(roles=credential_type)
- self._creds[str(credential_type)] = credentials
+ if scope:
+ self._creds["%s_%s" %
+ (scope, str(credential_type))] = credentials
+ else:
+ self._creds[str(credential_type)] = credentials
# Maintained until tests are ported
LOG.info("Acquired dynamic creds:\n"
" credentials: %s", credentials)
@@ -356,28 +417,73 @@
" credentials: %s", credentials)
return credentials
+ # TODO(gmann): Remove this method in favor of get_project_member_creds()
+ # after the deprecation phase.
def get_primary_creds(self):
return self.get_credentials('primary')
+ # TODO(gmann): Remove this method in favor of get_project_admin_creds()
+ # after the deprecation phase.
def get_admin_creds(self):
return self.get_credentials('admin')
+ # TODO(gmann): Replace this method with more appropriate name.
+ # like get_project_alt_member_creds()
def get_alt_creds(self):
return self.get_credentials('alt')
- def get_creds_by_roles(self, roles, force_new=False):
+ def get_system_admin_creds(self):
+ return self.get_credentials(['admin'], scope='system')
+
+ def get_system_member_creds(self):
+ return self.get_credentials(['member'], scope='system')
+
+ def get_system_reader_creds(self):
+ return self.get_credentials(['reader'], scope='system')
+
+ def get_domain_admin_creds(self):
+ return self.get_credentials(['admin'], scope='domain')
+
+ def get_domain_member_creds(self):
+ return self.get_credentials(['member'], scope='domain')
+
+ def get_domain_reader_creds(self):
+ return self.get_credentials(['reader'], scope='domain')
+
+ def get_project_admin_creds(self):
+ return self.get_credentials(['admin'], scope='project')
+
+ def get_project_alt_admin_creds(self):
+ return self.get_credentials(['alt_admin'], scope='project')
+
+ def get_project_member_creds(self):
+ return self.get_credentials(['member'], scope='project')
+
+ def get_project_alt_member_creds(self):
+ return self.get_credentials(['alt_member'], scope='project')
+
+ def get_project_reader_creds(self):
+ return self.get_credentials(['reader'], scope='project')
+
+ def get_project_alt_reader_creds(self):
+ return self.get_credentials(['alt_reader'], scope='project')
+
+ def get_creds_by_roles(self, roles, force_new=False, scope=None):
roles = list(set(roles))
# The roles list as a str will become the index as the dict key for
# the created credentials set in the dynamic_creds dict.
- exist_creds = self._creds.get(str(roles))
+ creds_name = str(roles)
+ if scope:
+ creds_name = "%s_%s" % (scope, str(roles))
+ exist_creds = self._creds.get(creds_name)
# If force_new flag is True 2 cred sets with the same roles are needed
# handle this by creating a separate index for old one to store it
# separately for cleanup
if exist_creds and force_new:
- new_index = str(roles) + '-' + str(len(self._creds))
+ new_index = creds_name + '-' + str(len(self._creds))
self._creds[new_index] = exist_creds
- del self._creds[str(roles)]
- return self.get_credentials(roles)
+ del self._creds[creds_name]
+ return self.get_credentials(roles, scope=scope)
def _clear_isolated_router(self, router_id, router_name):
client = self.routers_admin_client
@@ -472,6 +578,16 @@
except lib_exc.NotFound:
LOG.warning("tenant with name: %s not found for delete",
creds.tenant_name)
+
+ # if cred is domain scoped, delete ephemeral domain
+ # do not delete default domain
+ if (hasattr(creds, 'domain_id') and
+ creds.domain_id != creds.project_domain_id):
+ try:
+ self.creds_client.delete_domain(creds.domain_id)
+ except lib_exc.NotFound:
+ LOG.warning("domain with name: %s not found for delete",
+ creds.domain_name)
self._creds = {}
def is_multi_user(self):
diff --git a/tempest/lib/common/jsonschema_validator.py b/tempest/lib/common/jsonschema_validator.py
index bbf5e89..0ac757d 100644
--- a/tempest/lib/common/jsonschema_validator.py
+++ b/tempest/lib/common/jsonschema_validator.py
@@ -15,7 +15,6 @@
import jsonschema
from oslo_serialization import base64
from oslo_utils import timeutils
-import six
# JSON Schema validator and format checker used for JSON Schema validation
JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
@@ -43,7 +42,7 @@
@jsonschema.FormatChecker.cls_checks('base64')
def _validate_base64_format(instance):
try:
- if isinstance(instance, six.text_type):
+ if isinstance(instance, str):
instance = instance.encode('utf-8')
base64.decode_as_bytes(instance)
except TypeError:
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 641d727..6d948cf 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import hashlib
import os
from oslo_concurrency import lockutils
from oslo_log import log as logging
-import six
+from oslo_utils.secretutils import md5
import yaml
from tempest.lib import auth
@@ -104,15 +103,24 @@
return hash_dict
@classmethod
+ def _append_scoped_role(cls, scope, role, account_hash, hash_dict):
+ key = "%s_%s" % (scope, role)
+ hash_dict['scoped_roles'].setdefault(key, [])
+ hash_dict['scoped_roles'][key].append(account_hash)
+ return hash_dict
+
+ @classmethod
def get_hash_dict(cls, accounts, admin_role,
object_storage_operator_role=None,
object_storage_reseller_admin_role=None):
- hash_dict = {'roles': {}, 'creds': {}, 'networks': {}}
+ hash_dict = {'roles': {}, 'creds': {}, 'networks': {},
+ 'scoped_roles': {}}
# Loop over the accounts read from the yaml file
for account in accounts:
roles = []
types = []
+ scope = None
resources = []
if 'roles' in account:
roles = account.pop('roles')
@@ -120,15 +128,24 @@
types = account.pop('types')
if 'resources' in account:
resources = account.pop('resources')
- temp_hash = hashlib.md5()
+ if 'project_name' in account:
+ scope = 'project'
+ elif 'domain_name' in account:
+ scope = 'domain'
+ elif 'system' in account:
+ scope = 'system'
+ temp_hash = md5(usedforsecurity=False)
account_for_hash = dict((k, v) for (k, v) in account.items()
if k in cls.HASH_CRED_FIELDS)
- temp_hash.update(six.text_type(account_for_hash).encode('utf-8'))
+ temp_hash.update(str(account_for_hash).encode('utf-8'))
temp_hash_key = temp_hash.hexdigest()
hash_dict['creds'][temp_hash_key] = account
for role in roles:
hash_dict = cls._append_role(role, temp_hash_key,
hash_dict)
+ if scope:
+ hash_dict = cls._append_scoped_role(
+ scope, role, temp_hash_key, hash_dict)
# If types are set for the account append the matching role
# subdict with the hash
for type in types:
@@ -201,17 +218,25 @@
'the credentials for this allocation request' % ','.join(names))
raise lib_exc.InvalidCredentials(msg)
- def _get_match_hash_list(self, roles=None):
+ def _get_match_hash_list(self, roles=None, scope=None):
hashes = []
if roles:
# Loop over all the creds for each role in the subdict and generate
# a list of cred lists for each role
for role in roles:
- temp_hashes = self.hash_dict['roles'].get(role, None)
- if not temp_hashes:
- raise lib_exc.InvalidCredentials(
- "No credentials with role: %s specified in the "
- "accounts ""file" % role)
+ if scope:
+ key = "%s_%s" % (scope, role)
+ temp_hashes = self.hash_dict['scoped_roles'].get(key)
+ if not temp_hashes:
+ raise lib_exc.InvalidCredentials(
+ "No credentials matching role: %s, scope: %s "
+ "specified in the accounts file" % (role, scope))
+ else:
+ temp_hashes = self.hash_dict['roles'].get(role, None)
+ if not temp_hashes:
+ raise lib_exc.InvalidCredentials(
+ "No credentials with role: %s specified in the "
+ "accounts file" % role)
hashes.append(temp_hashes)
# Take the list of lists and do a boolean and between each list to
# find the creds which fall under all the specified roles
@@ -239,8 +264,8 @@
temp_creds.pop('password')
return temp_creds
- def _get_creds(self, roles=None):
- useable_hashes = self._get_match_hash_list(roles)
+ def _get_creds(self, roles=None, scope=None):
+ useable_hashes = self._get_match_hash_list(roles, scope)
if not useable_hashes:
msg = 'No users configured for type/roles %s' % roles
raise lib_exc.InvalidCredentials(msg)
@@ -282,6 +307,8 @@
self.remove_hash(_hash)
LOG.info("%s returned allocated creds:\n%s", self.name, clean_creds)
+ # TODO(gmann): Remove this method in favor of get_project_member_creds()
+ # after the deprecation phase.
def get_primary_creds(self):
if self._creds.get('primary'):
return self._creds.get('primary')
@@ -289,6 +316,8 @@
self._creds['primary'] = net_creds
return net_creds
+ # TODO(gmann): Replace this method with more appropriate name.
+ # like get_project_alt_member_creds()
def get_alt_creds(self):
if self._creds.get('alt'):
return self._creds.get('alt')
@@ -296,9 +325,84 @@
self._creds['alt'] = net_creds
return net_creds
- def get_creds_by_roles(self, roles, force_new=False):
+ def get_system_admin_creds(self):
+ if self._creds.get('system_admin'):
+ return self._creds.get('system_admin')
+ system_admin = self._get_creds(['admin'], scope='system')
+ self._creds['system_admin'] = system_admin
+ return system_admin
+
+ def get_system_member_creds(self):
+ if self._creds.get('system_member'):
+ return self._creds.get('system_member')
+ system_member = self._get_creds(['member'], scope='system')
+ self._creds['system_member'] = system_member
+ return system_member
+
+ def get_system_reader_creds(self):
+ if self._creds.get('system_reader'):
+ return self._creds.get('system_reader')
+ system_reader = self._get_creds(['reader'], scope='system')
+ self._creds['system_reader'] = system_reader
+ return system_reader
+
+ def get_domain_admin_creds(self):
+ if self._creds.get('domain_admin'):
+ return self._creds.get('domain_admin')
+ domain_admin = self._get_creds(['admin'], scope='domain')
+ self._creds['domain_admin'] = domain_admin
+ return domain_admin
+
+ def get_domain_member_creds(self):
+ if self._creds.get('domain_member'):
+ return self._creds.get('domain_member')
+ domain_member = self._get_creds(['member'], scope='domain')
+ self._creds['domain_member'] = domain_member
+ return domain_member
+
+ def get_domain_reader_creds(self):
+ if self._creds.get('domain_reader'):
+ return self._creds.get('domain_reader')
+ domain_reader = self._get_creds(['reader'], scope='domain')
+ self._creds['domain_reader'] = domain_reader
+ return domain_reader
+
+ def get_project_admin_creds(self):
+ if self._creds.get('project_admin'):
+ return self._creds.get('project_admin')
+ project_admin = self._get_creds(['admin'], scope='project')
+ self._creds['project_admin'] = project_admin
+ return project_admin
+
+ def get_project_alt_admin_creds(self):
+ # TODO(gmann): Implement alt admin hash.
+ return
+
+ def get_project_member_creds(self):
+ if self._creds.get('project_member'):
+ return self._creds.get('project_member')
+ project_member = self._get_creds(['member'], scope='project')
+ self._creds['project_member'] = project_member
+ return project_member
+
+ def get_project_alt_member_creds(self):
+ # TODO(gmann): Implement alt member hash.
+ return
+
+ def get_project_reader_creds(self):
+ if self._creds.get('project_reader'):
+ return self._creds.get('project_reader')
+ project_reader = self._get_creds(['reader'], scope='project')
+ self._creds['project_reader'] = project_reader
+ return project_reader
+
+ def get_project_alt_reader_creds(self):
+ # TODO(gmann): Implement alt reader hash.
+ return
+
+ def get_creds_by_roles(self, roles, force_new=False, scope=None):
roles = list(set(roles))
- exist_creds = self._creds.get(six.text_type(roles).encode(
+ exist_creds = self._creds.get(str(roles).encode(
'utf-8'), None)
# The force kwarg is used to allocate an additional set of creds with
# the same role list. The index used for the previously allocation
@@ -308,17 +412,19 @@
elif exist_creds and force_new:
# NOTE(andreaf) In py3.x encode returns bytes, and b'' is bytes
# In py2.7 encode returns strings, and b'' is still string
- new_index = six.text_type(roles).encode('utf-8') + b'-' + \
- six.text_type(len(self._creds)).encode('utf-8')
+ new_index = str(roles).encode('utf-8') + b'-' + \
+ str(len(self._creds)).encode('utf-8')
self._creds[new_index] = exist_creds
net_creds = self._get_creds(roles=roles)
- self._creds[six.text_type(roles).encode('utf-8')] = net_creds
+ self._creds[str(roles).encode('utf-8')] = net_creds
return net_creds
def clear_creds(self):
for creds in self._creds.values():
self.remove_credentials(creds)
+ # TODO(gmann): Remove this method in favor of get_project_admin_creds()
+ # after the deprecation phase.
def get_admin_creds(self):
return self.get_creds_by_roles([self.admin_role])
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index a987e03..00f2aeb 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -18,13 +18,13 @@
import email.utils
import re
import time
+import urllib
import jsonschema
from oslo_log import log as logging
from oslo_log import versionutils
from oslo_serialization import jsonutils as json
import six
-from six.moves import urllib
from tempest.lib.common import http
from tempest.lib.common import jsonschema_validator
@@ -418,7 +418,7 @@
def _safe_body(self, body, maxlen=4096):
# convert a structure into a string safely
try:
- text = six.text_type(body)
+ text = str(body)
except UnicodeDecodeError:
# if this isn't actually text, return marker that
return "<BinaryData: removed>"
@@ -890,7 +890,7 @@
return True
return 'exceed' in resp_body.get('message', 'blabla')
- def wait_for_resource_deletion(self, id):
+ def wait_for_resource_deletion(self, id, *args, **kwargs):
"""Waits for a resource to be deleted
This method will loop over is_resource_deleted until either
@@ -903,7 +903,7 @@
"""
start_time = int(time.time())
while True:
- if self.is_resource_deleted(id):
+ if self.is_resource_deleted(id, *args, **kwargs):
return
if int(time.time()) - start_time >= self.build_timeout:
message = ('Failed to delete %(resource_type)s %(id)s within '
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 3a05f27..60107d7 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -14,6 +14,7 @@
# under the License.
+import io
import select
import socket
import time
@@ -67,7 +68,7 @@
self.password = password
if isinstance(pkey, six.string_types):
pkey = paramiko.RSAKey.from_private_key(
- six.StringIO(str(pkey)))
+ io.StringIO(str(pkey)))
self.pkey = pkey
self.look_for_keys = look_for_keys
self.key_filename = key_filename
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 44b55eb..b6671b5 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -169,6 +169,8 @@
:return: size randomly bytes
:rtype: string
"""
+ if size > 1 << 20:
+ raise RuntimeError('Size should be less than 1MiB')
return b''.join([six.int2byte(random.randint(0, 255))
for i in range(size)])
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index ebe2d61..25ff473 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -72,19 +72,13 @@
def decorator(f):
@functools.wraps(f)
def wrapper(*func_args, **func_kwargs):
- skip = False
- msg = ''
- if "condition" in kwargs:
- if kwargs["condition"] is True:
- skip = True
- else:
- skip = True
- if "bug" in kwargs and skip is True:
- bug = kwargs['bug']
+ condition = kwargs.get('condition', True)
+ bug = kwargs.get('bug', None)
+ if bug and condition:
bug_type = kwargs.get('bug_type', 'launchpad')
bug_url = _get_bug_url(bug, bug_type)
- msg = "Skipped until bug: %s is resolved." % bug_url
- raise testtools.TestCase.skipException(msg)
+ raise testtools.TestCase.skipException(
+ "Skipped until bug: %s is resolved." % bug_url)
return f(*func_args, **func_kwargs)
return wrapper
return decorator
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 84b7ee6..abe68d2 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -294,3 +294,7 @@
class ConsistencyGroupSnapshotException(TempestException):
message = ("Consistency group snapshot %(cgsnapshot_id)s failed and is "
"in ERROR status")
+
+
+class InvalidScopeType(TempestException):
+ message = "Invalid scope %(scope)s"
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 90debd9..d328956 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -257,7 +257,7 @@
# class should only be used by tests hosted in Tempest.
@removals.removed_kwarg('client_parameters')
- def __init__(self, credentials, identity_uri, region=None, scope='project',
+ def __init__(self, credentials, identity_uri, region=None, scope=None,
disable_ssl_certificate_validation=True, ca_certs=None,
trace_requests='', client_parameters=None, proxy_url=None):
"""Service Clients provider
@@ -348,6 +348,14 @@
self.ca_certs = ca_certs
self.trace_requests = trace_requests
self.proxy_url = proxy_url
+ if self.credentials.project_id or self.credentials.project_name:
+ scope = 'project'
+ elif self.credentials.system:
+ scope = 'system'
+ elif self.credentials.domain_id or self.credentials.domain_name:
+ scope = 'domain'
+ else:
+ scope = 'project'
# Creates an auth provider for the credentials
self.auth_provider = auth_provider_class(
self.credentials, self.identity_uri, scope=scope,
diff --git a/tempest/lib/services/compute/agents_client.py b/tempest/lib/services/compute/agents_client.py
index 12b3900..bd973dd 100644
--- a/tempest/lib/services/compute/agents_client.py
+++ b/tempest/lib/services/compute/agents_client.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import agents as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/baremetal_nodes_client.py b/tempest/lib/services/compute/baremetal_nodes_client.py
index 3efdbce..83af451 100644
--- a/tempest/lib/services/compute/baremetal_nodes_client.py
+++ b/tempest/lib/services/compute/baremetal_nodes_client.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import baremetal_nodes \
as schema
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index e22b5b2..5282405 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import flavors as schema
from tempest.lib.api_schema.response.compute.v2_1 import flavors_access \
diff --git a/tempest/lib/services/compute/floating_ip_pools_client.py b/tempest/lib/services/compute/floating_ip_pools_client.py
index d3af050..aa065b8 100644
--- a/tempest/lib/services/compute/floating_ip_pools_client.py
+++ b/tempest/lib/services/compute/floating_ip_pools_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/floating_ips_client.py b/tempest/lib/services/compute/floating_ips_client.py
index d7a1a9b..e6b6916 100644
--- a/tempest/lib/services/compute/floating_ips_client.py
+++ b/tempest/lib/services/compute/floating_ips_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/hosts_client.py b/tempest/lib/services/compute/hosts_client.py
index 743b4ec..bbecc3b 100644
--- a/tempest/lib/services/compute/hosts_client.py
+++ b/tempest/lib/services/compute/hosts_client.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import hosts as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
index b252ee9..b6d8d30 100644
--- a/tempest/lib/services/compute/images_client.py
+++ b/tempest/lib/services/compute/images_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import images as schema
from tempest.lib.api_schema.response.compute.v2_45 import images as schemav245
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
index 47cf2d0..9d7b7fc 100644
--- a/tempest/lib/services/compute/keypairs_client.py
+++ b/tempest/lib/services/compute/keypairs_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import keypairs as schemav21
from tempest.lib.api_schema.response.compute.v2_2 import keypairs as schemav22
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
index 812dc96..8a6e62a 100644
--- a/tempest/lib/services/compute/migrations_client.py
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import migrations as schema
from tempest.lib.api_schema.response.compute.v2_23 import migrations \
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index 12e865e..dd796aa 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import quotas as schema
from tempest.lib.api_schema.response.compute.v2_36 import quotas as schemav236
diff --git a/tempest/lib/services/compute/security_groups_client.py b/tempest/lib/services/compute/security_groups_client.py
index 9493144..0bba990 100644
--- a/tempest/lib/services/compute/security_groups_client.py
+++ b/tempest/lib/services/compute/security_groups_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import \
security_groups as schema
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index e82b58f..c36f80a 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -16,9 +16,9 @@
# under the License.
import copy
+from urllib import parse as urllib
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import \
security_groups as security_groups_schema
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index 4e3383f..7d9f3e2 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -14,8 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import services as schema
from tempest.lib.api_schema.response.compute.v2_11 import services \
diff --git a/tempest/lib/services/compute/snapshots_client.py b/tempest/lib/services/compute/snapshots_client.py
index 225eb8d..2e6f7cf 100644
--- a/tempest/lib/services/compute/snapshots_client.py
+++ b/tempest/lib/services/compute/snapshots_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import snapshots as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/tenant_usages_client.py b/tempest/lib/services/compute/tenant_usages_client.py
index a34730c..b47d917 100644
--- a/tempest/lib/services/compute/tenant_usages_client.py
+++ b/tempest/lib/services/compute/tenant_usages_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import tenant_usages
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/compute/volumes_client.py b/tempest/lib/services/compute/volumes_client.py
index 11282ee..52172ed 100644
--- a/tempest/lib/services/compute/volumes_client.py
+++ b/tempest/lib/services/compute/volumes_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.compute.v2_1 import volumes as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v2/identity_client.py b/tempest/lib/services/identity/v2/identity_client.py
index d7526f3..6239ba6 100644
--- a/tempest/lib/services/identity/v2/identity_client.py
+++ b/tempest/lib/services/identity/v2/identity_client.py
@@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v2/roles_client.py b/tempest/lib/services/identity/v2/roles_client.py
index a133fc3..1580c33 100644
--- a/tempest/lib/services/identity/v2/roles_client.py
+++ b/tempest/lib/services/identity/v2/roles_client.py
@@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v2/services_client.py b/tempest/lib/services/identity/v2/services_client.py
index fc51cb4..2a0e5ca 100644
--- a/tempest/lib/services/identity/v2/services_client.py
+++ b/tempest/lib/services/identity/v2/services_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v2/tenants_client.py b/tempest/lib/services/identity/v2/tenants_client.py
index 09618ad..3435835 100644
--- a/tempest/lib/services/identity/v2/tenants_client.py
+++ b/tempest/lib/services/identity/v2/tenants_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v2/users_client.py b/tempest/lib/services/identity/v2/users_client.py
index 72f29be..c3217c9 100644
--- a/tempest/lib/services/identity/v2/users_client.py
+++ b/tempest/lib/services/identity/v2/users_client.py
@@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/access_rules_client.py b/tempest/lib/services/identity/v3/access_rules_client.py
index 4f13e47..c3be5df 100644
--- a/tempest/lib/services/identity/v3/access_rules_client.py
+++ b/tempest/lib/services/identity/v3/access_rules_client.py
@@ -18,8 +18,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/application_credentials_client.py b/tempest/lib/services/identity/v3/application_credentials_client.py
index be2e172..e7f3ac2 100644
--- a/tempest/lib/services/identity/v3/application_credentials_client.py
+++ b/tempest/lib/services/identity/v3/application_credentials_client.py
@@ -18,8 +18,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/credentials_client.py b/tempest/lib/services/identity/v3/credentials_client.py
index 3f4b40e..27f6156 100644
--- a/tempest/lib/services/identity/v3/credentials_client.py
+++ b/tempest/lib/services/identity/v3/credentials_client.py
@@ -17,8 +17,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#credentials
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/domains_client.py b/tempest/lib/services/identity/v3/domains_client.py
index bd32cfc..c1d1980 100644
--- a/tempest/lib/services/identity/v3/domains_client.py
+++ b/tempest/lib/services/identity/v3/domains_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/endpoints_client.py b/tempest/lib/services/identity/v3/endpoints_client.py
index 236b34c..de85388 100644
--- a/tempest/lib/services/identity/v3/endpoints_client.py
+++ b/tempest/lib/services/identity/v3/endpoints_client.py
@@ -17,8 +17,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#service-catalog-and-endpoints
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/groups_client.py b/tempest/lib/services/identity/v3/groups_client.py
index 2cfb24a..6f82067 100644
--- a/tempest/lib/services/identity/v3/groups_client.py
+++ b/tempest/lib/services/identity/v3/groups_client.py
@@ -17,8 +17,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#groups
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/identity_providers_client.py b/tempest/lib/services/identity/v3/identity_providers_client.py
new file mode 100644
index 0000000..af6a245
--- /dev/null
+++ b/tempest/lib/services/identity/v3/identity_providers_client.py
@@ -0,0 +1,92 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class IdentityProvidersClient(rest_client.RestClient):
+
+ def register_identity_provider(self, identity_provider_id, **kwargs):
+ """Register an identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#register-an-identity-provider
+ """
+ post_body = json.dumps({'identity_provider': kwargs})
+ resp, body = self.put(
+ 'OS-FEDERATION/identity_providers/%s' % identity_provider_id,
+ post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_identity_providers(self, **params):
+ """List identity providers.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-identity-providers
+ """
+ url = 'identity_providers'
+ 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 get_identity_provider(self, identity_provider_id):
+ """Get identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-identity-provider
+ """
+ resp, body = self.get(
+ 'OS-FEDERATION/identity_providers/%s' % identity_provider_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_identity_provider(self, identity_provider_id):
+ """Delete identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-identity-provider
+ """
+ resp, body = self.delete(
+ 'OS-FEDERATION/identity_providers/%s' % identity_provider_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_identity_provider(self, identity_provider_id, **kwargs):
+ """Update identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-identity-provider
+ """
+ post_body = json.dumps({'identity_provider': kwargs})
+ resp, body = self.patch(
+ 'OS-FEDERATION/identity_providers/%s' % identity_provider_id,
+ post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/mappings_client.py b/tempest/lib/services/identity/v3/mappings_client.py
new file mode 100644
index 0000000..9ec5384
--- /dev/null
+++ b/tempest/lib/services/identity/v3/mappings_client.py
@@ -0,0 +1,90 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class MappingsClient(rest_client.RestClient):
+
+ def create_mapping(self, mapping_id, **kwargs):
+ """Create a mapping.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#create-a-mapping
+ """
+ post_body = json.dumps({'mapping': kwargs})
+ resp, body = self.put(
+ 'OS-FEDERATION/mappings/%s' % mapping_id, post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_mapping(self, mapping_id):
+ """Get a mapping.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-a-mapping
+ """
+ resp, body = self.get(
+ 'OS-FEDERATION/mappings/%s' % mapping_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_mapping(self, mapping_id, **kwargs):
+ """Update a mapping.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-a-mapping
+ """
+ post_body = json.dumps({'mapping': kwargs})
+ resp, body = self.patch(
+ 'OS-FEDERATION/mappings/%s' % mapping_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_mappings(self, **kwargs):
+ """List mappings.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-mappings
+ """
+ url = 'OS-FEDERATION/mappings'
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_mapping(self, mapping_id):
+ """Delete a mapping.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-mapping
+ """
+ resp, body = self.delete(
+ 'OS-FEDERATION/mappings/%s' % mapping_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
index 722deca..236b224 100644
--- a/tempest/lib/services/identity/v3/oauth_token_client.py
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -18,9 +18,9 @@
import hmac
import random
import time
+from urllib import parse as urlparse
import six
-from six.moves.urllib import parse as urlparse
from oslo_serialization import jsonutils as json
@@ -33,7 +33,7 @@
def _escape(self, s):
"""Escape a unicode string in an OAuth-compatible fashion."""
safe = b'~'
- s = s.encode('utf-8') if isinstance(s, six.text_type) else s
+ s = s.encode('utf-8') if isinstance(s, str) else s
s = urlparse.quote(s, safe)
if isinstance(s, six.binary_type):
s = s.decode('utf-8')
@@ -47,8 +47,8 @@
verifier=None,
http_method='GET'):
"""Generate OAUTH params along with signature."""
- timestamp = six.text_type(int(time.time()))
- nonce = six.text_type(random.getrandbits(64)) + timestamp
+ timestamp = str(int(time.time()))
+ nonce = str(random.getrandbits(64)) + timestamp
oauth_params = [
('oauth_nonce', nonce),
('oauth_timestamp', timestamp),
diff --git a/tempest/lib/services/identity/v3/projects_client.py b/tempest/lib/services/identity/v3/projects_client.py
index b186fba..fffbe7a 100644
--- a/tempest/lib/services/identity/v3/projects_client.py
+++ b/tempest/lib/services/identity/v3/projects_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/protocols_client.py b/tempest/lib/services/identity/v3/protocols_client.py
new file mode 100644
index 0000000..2e0221b
--- /dev/null
+++ b/tempest/lib/services/identity/v3/protocols_client.py
@@ -0,0 +1,96 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class ProtocolsClient(rest_client.RestClient):
+
+ def add_protocol_to_identity_provider(self, idp_id, protocol_id,
+ **kwargs):
+ """Add protocol to identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#add-protocol-to-identity-provider
+ """
+ post_body = json.dumps({'protocol': kwargs})
+ resp, body = self.put(
+ 'OS-FEDERATION/identity_providers/%s/protocols/%s'
+ % (idp_id, protocol_id), post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_protocols_of_identity_provider(self, idp_id, **kwargs):
+ """List protocols of identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-protocols-of-identity-provider
+ """
+ url = 'OS-FEDERATION/identity_providers/%s/protocols' % idp_id
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_protocol_for_identity_provider(self, idp_id, protocol_id):
+ """Get protocol for identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-protocol-for-identity-provider
+ """
+ resp, body = self.get(
+ 'OS-FEDERATION/identity_providers/%s/protocols/%s'
+ % (idp_id, protocol_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_mapping_for_identity_provider(self, idp_id, protocol_id,
+ **kwargs):
+ """Update attribute mapping for identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-attribute-mapping-for-identity-provider
+ """
+ post_body = json.dumps({'protocol': kwargs})
+ resp, body = self.patch(
+ 'OS-FEDERATION/identity_providers/%s/protocols/%s'
+ % (idp_id, protocol_id), post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_protocol_from_identity_provider(self, idp_id, protocol_id):
+ """Delete a protocol from identity provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-protocol-from-identity-provider
+ """
+ resp, body = self.delete(
+ 'OS-FEDERATION/identity_providers/%s/protocols/%s'
+ % (idp_id, protocol_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/regions_client.py b/tempest/lib/services/identity/v3/regions_client.py
index a598c9c..3aed5b8 100644
--- a/tempest/lib/services/identity/v3/regions_client.py
+++ b/tempest/lib/services/identity/v3/regions_client.py
@@ -17,8 +17,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#regions
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/role_assignments_client.py b/tempest/lib/services/identity/v3/role_assignments_client.py
index 51ee8f6..f615709 100644
--- a/tempest/lib/services/identity/v3/role_assignments_client.py
+++ b/tempest/lib/services/identity/v3/role_assignments_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/roles_client.py b/tempest/lib/services/identity/v3/roles_client.py
index e41dc28..4836784 100644
--- a/tempest/lib/services/identity/v3/roles_client.py
+++ b/tempest/lib/services/identity/v3/roles_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/service_providers_client.py b/tempest/lib/services/identity/v3/service_providers_client.py
new file mode 100644
index 0000000..b84cf43
--- /dev/null
+++ b/tempest/lib/services/identity/v3/service_providers_client.py
@@ -0,0 +1,92 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class ServiceProvidersClient(rest_client.RestClient):
+
+ def register_service_provider(self, service_provider_id, **kwargs):
+ """Register a service provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#register-a-service-provider
+ """
+ post_body = json.dumps({'service_provider': kwargs})
+ resp, body = self.put(
+ 'OS-FEDERATION/service_providers/%s' % service_provider_id,
+ post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_service_providers(self, **kwargs):
+ """List service providers.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#list-service-providers
+ """
+ url = 'OS-FEDERATION/service_providers'
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_service_provider(self, service_provider_id):
+ """Get a service provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#get-service-provider
+ """
+ resp, body = self.get(
+ 'OS-FEDERATION/service_providers/%s' % service_provider_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_service_provider(self, service_provider_id):
+ """Delete a service provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-service-provider
+ """
+ resp, body = self.delete(
+ 'OS-FEDERATION/service_providers/%s' % service_provider_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_service_provider(self, service_provider_id, **kwargs):
+ """Update a service provider.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/index.html#update-service-provider
+ """
+ post_body = json.dumps({'service_provider': kwargs})
+ resp, body = self.patch(
+ 'OS-FEDERATION/service_providers/%s' % service_provider_id,
+ post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/services_client.py b/tempest/lib/services/identity/v3/services_client.py
index eb961a5..994df2f 100644
--- a/tempest/lib/services/identity/v3/services_client.py
+++ b/tempest/lib/services/identity/v3/services_client.py
@@ -17,8 +17,9 @@
https://docs.openstack.org/api-ref/identity/v3/index.html#service-catalog-and-endpoints
"""
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/trusts_client.py b/tempest/lib/services/identity/v3/trusts_client.py
index f1cc806..48a7956 100644
--- a/tempest/lib/services/identity/v3/trusts_client.py
+++ b/tempest/lib/services/identity/v3/trusts_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/identity/v3/users_client.py b/tempest/lib/services/identity/v3/users_client.py
index bba02a4..771ffea 100644
--- a/tempest/lib/services/identity/v3/users_client.py
+++ b/tempest/lib/services/identity/v3/users_client.py
@@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/image/v1/images_client.py b/tempest/lib/services/image/v1/images_client.py
index 0e76a63..c9a4a94 100644
--- a/tempest/lib/services/image/v1/images_client.py
+++ b/tempest/lib/services/image/v1/images_client.py
@@ -14,9 +14,9 @@
# under the License.
import functools
+from urllib import parse as urllib
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 import exceptions as lib_exc
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 4713cce..abf427c 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -14,9 +14,9 @@
# under the License.
import functools
+from urllib import parse as urllib
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 import exceptions as lib_exc
@@ -121,6 +121,14 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+ def show_image_tasks(self, image_id):
+ """Show image tasks."""
+ url = 'images/%s/tasks' % image_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
def is_resource_deleted(self, id):
try:
self.show_image(id)
diff --git a/tempest/lib/services/image/v2/namespace_objects_client.py b/tempest/lib/services/image/v2/namespace_objects_client.py
index 0cae816..32f5a2c 100644
--- a/tempest/lib/services/image/v2/namespace_objects_client.py
+++ b/tempest/lib/services/image/v2/namespace_objects_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/image/v2/namespace_tags_client.py b/tempest/lib/services/image/v2/namespace_tags_client.py
index 4315f16..5bca229 100644
--- a/tempest/lib/services/image/v2/namespace_tags_client.py
+++ b/tempest/lib/services/image/v2/namespace_tags_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/image/v2/versions_client.py b/tempest/lib/services/image/v2/versions_client.py
index 1b7f806..98b4fb6 100644
--- a/tempest/lib/services/image/v2/versions_client.py
+++ b/tempest/lib/services/image/v2/versions_client.py
@@ -30,3 +30,13 @@
self.expected_success(300, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+
+ def has_version(self, version):
+ """Return True if a version is supported."""
+ version = 'v%s' % version
+ supported = ['SUPPORTED', 'CURRENT']
+ versions = self.list_versions()
+ for version_struct in versions['versions']:
+ if version_struct['id'] == version:
+ return version_struct['status'] in supported
+ return False
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
index fe8b244..ee87dd4 100644
--- a/tempest/lib/services/network/base.py
+++ b/tempest/lib/services/network/base.py
@@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/object_storage/account_client.py b/tempest/lib/services/object_storage/account_client.py
index 8c15a88..52b2534 100644
--- a/tempest/lib/services/object_storage/account_client.py
+++ b/tempest/lib/services/object_storage/account_client.py
@@ -13,10 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
from xml.etree import ElementTree as etree
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index 027fb1f..6d07ec1 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -13,17 +13,25 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
from xml.etree import ElementTree as etree
import debtcollector.moves
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 import exceptions
class ContainerClient(rest_client.RestClient):
+ def is_resource_deleted(self, container):
+ try:
+ self.list_container_metadata(container)
+ except exceptions.NotFound:
+ return True
+ return False
+
def update_container(self, container_name, **headers):
"""Creates or Updates a container
diff --git a/tempest/lib/services/object_storage/object_client.py b/tempest/lib/services/object_storage/object_client.py
index 1d38153..bb82975 100644
--- a/tempest/lib/services/object_storage/object_client.py
+++ b/tempest/lib/services/object_storage/object_client.py
@@ -14,8 +14,8 @@
# under the License.
import ssl
-from six.moves import http_client as httplib
-from six.moves.urllib import parse as urlparse
+from http import client as httplib
+from urllib import parse as urlparse
from tempest.lib.common import rest_client
from tempest.lib import exceptions
@@ -23,6 +23,13 @@
class ObjectClient(rest_client.RestClient):
+ def is_resource_deleted(self, object_name, container):
+ try:
+ self.get_object(container, object_name)
+ except exceptions.NotFound:
+ return True
+ return False
+
def create_object(self, container, object_name, data,
params=None, metadata=None, headers=None,
chunked=False):
diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py
index b8e91b8..216ac08 100644
--- a/tempest/lib/services/placement/placement_client.py
+++ b/tempest/lib/services/placement/placement_client.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
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
diff --git a/tempest/lib/services/placement/resource_providers_client.py b/tempest/lib/services/placement/resource_providers_client.py
index 56f6409..e6fbcb2 100644
--- a/tempest/lib/services/placement/resource_providers_client.py
+++ b/tempest/lib/services/placement/resource_providers_client.py
@@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
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
@@ -80,3 +81,29 @@
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+
+ def update_resource_providers_inventories(self, rp_uuid, **kwargs):
+ """Update resource providers inventories.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/placement/#update-resource-provider-inventories
+ """
+ url = '/resource_providers/{}/inventories'.format(rp_uuid)
+ data = json.dumps(kwargs)
+ resp, body = self.put(url, data)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_resource_providers_inventories(self, rp_uuid):
+ """Delete resource providers inventories.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/placement/#delete-resource-provider-inventories
+ """
+ url = '/resource_providers/{}/inventories'.format(rp_uuid)
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v1/hosts_client.py b/tempest/lib/services/volume/v1/hosts_client.py
index f344678..2e94274 100644
--- a/tempest/lib/services/volume/v1/hosts_client.py
+++ b/tempest/lib/services/volume/v1/hosts_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v1/quotas_client.py b/tempest/lib/services/volume/v1/quotas_client.py
index 7f191ca..d7c9698 100644
--- a/tempest/lib/services/volume/v1/quotas_client.py
+++ b/tempest/lib/services/volume/v1/quotas_client.py
@@ -12,8 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v1/services_client.py b/tempest/lib/services/volume/v1/services_client.py
index d438a34..957a0e6 100644
--- a/tempest/lib/services/volume/v1/services_client.py
+++ b/tempest/lib/services/volume/v1/services_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v1/snapshots_client.py b/tempest/lib/services/volume/v1/snapshots_client.py
index 7dfdcf2..a478686 100644
--- a/tempest/lib/services/volume/v1/snapshots_client.py
+++ b/tempest/lib/services/volume/v1/snapshots_client.py
@@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
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 import exceptions as lib_exc
diff --git a/tempest/lib/services/volume/v1/types_client.py b/tempest/lib/services/volume/v1/types_client.py
index d434e65..6237fb4 100644
--- a/tempest/lib/services/volume/v1/types_client.py
+++ b/tempest/lib/services/volume/v1/types_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
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 import exceptions as lib_exc
diff --git a/tempest/lib/services/volume/v1/volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
index 2efb0da..9fca800 100644
--- a/tempest/lib/services/volume/v1/volumes_client.py
+++ b/tempest/lib/services/volume/v1/volumes_client.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
import six
-from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index 1df45fa..4bf7ffb 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import backups as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index 4051c06..0f36fc9 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import group_snapshots as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 1dcd508..9de36f4 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import group_types as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index 3d8523d..d1500cf 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import groups as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/hosts_client.py b/tempest/lib/services/volume/v3/hosts_client.py
index 019a852..9c64659 100644
--- a/tempest/lib/services/volume/v3/hosts_client.py
+++ b/tempest/lib/services/volume/v3/hosts_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import hosts as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/quotas_client.py b/tempest/lib/services/volume/v3/quotas_client.py
index 5b1a52c..3f4c4e1 100644
--- a/tempest/lib/services/volume/v3/quotas_client.py
+++ b/tempest/lib/services/volume/v3/quotas_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import quotas as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py
index 8bc82c9..4672da8 100644
--- a/tempest/lib/services/volume/v3/services_client.py
+++ b/tempest/lib/services/volume/v3/services_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import services as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index 8ca2044..ae31ee9 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import snapshots as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/transfers_client.py b/tempest/lib/services/volume/v3/transfers_client.py
index f572f95..36198c3 100644
--- a/tempest/lib/services/volume/v3/transfers_client.py
+++ b/tempest/lib/services/volume/v3/transfers_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import transfers as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/types_client.py b/tempest/lib/services/volume/v3/types_client.py
index 1ebd447..9858d87 100644
--- a/tempest/lib/services/volume/v3/types_client.py
+++ b/tempest/lib/services/volume/v3/types_client.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import volume_types as schema
from tempest.lib.common import rest_client
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index 4ac4112..0bed827 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six.moves.urllib.parse import urljoin
+from urllib.parse import urljoin
from oslo_serialization import jsonutils as json
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index b8535d8..147a79b 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+from urllib import parse as urllib
+
from oslo_serialization import jsonutils as json
import six
-from six.moves.urllib import parse as urllib
from tempest.lib.api_schema.response.volume import volumes as schema
from tempest.lib.common import rest_client
diff --git a/tempest/manager.py b/tempest/manager.py
deleted file mode 100644
index b485ef2..0000000
--- a/tempest/manager.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright 2012 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 oslo_log import log as logging
-
-from tempest import clients as tempest_clients
-from tempest import config
-from tempest.lib.services import clients
-
-CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
-
-class Manager(clients.ServiceClients):
- """Service client manager class for backward compatibility
-
- The former manager.Manager is not a stable interface in Tempest,
- nonetheless it is consumed by a number of plugins already. This class
- exists to provide some grace time for the move to tempest.lib.
- """
-
- def __init__(self, credentials, scope='project'):
- msg = ("tempest.manager.Manager is not a stable interface and as such "
- "it should not be imported directly. It will be removed as "
- "soon as the client manager becomes available in tempest.lib.")
- LOG.warning(msg)
- dscv = CONF.identity.disable_ssl_certificate_validation
- _, uri = tempest_clients.get_auth_provider_class(credentials)
- super(Manager, self).__init__(
- credentials=credentials, scope=scope,
- identity_uri=uri,
- disable_ssl_certificate_validation=dscv,
- ca_certs=CONF.identity.ca_certificates_file,
- trace_requests=CONF.debug.trace_requests)
-
-
-def get_auth_provider(credentials, pre_auth=False, scope='project'):
- """Shim to get_auth_provider in clients.py
-
- get_auth_provider used to be hosted in this module, but it has been
- moved to clients.py now as a more permanent location.
- This module will be removed eventually, and this shim is only
- maintained for the benefit of plugins already consuming this interface.
- """
- msg = ("tempest.manager.get_auth_provider is not a stable interface and "
- "as such it should not imported directly. It will be removed as "
- "the client manager becomes available in tempest.lib.")
- LOG.warning(msg)
- return tempest_clients.get_auth_provider(credentials=credentials,
- pre_auth=pre_auth, scope=scope)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 60f8dfc3..27d30f2 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -18,6 +18,7 @@
import subprocess
import netaddr
+
from oslo_log import log
from oslo_serialization import jsonutils as json
from oslo_utils import netutils
@@ -375,21 +376,35 @@
def create_backup(self, volume_id, name=None, description=None,
force=False, snapshot_id=None, incremental=False,
- container=None):
- """Creates backup
+ container=None, **kwargs):
+ """Creates a backup of the given volume_id or snapshot_id
- This wrapper utility creates backup and waits for backup to be
- in 'available' state.
+ This wrapper utility creates a backup and waits until it is in
+ 'available' state.
+
+ :param volume_id: UUID of the volume to back up
+ :param name: backup name, '$classname-backup' by default
+ :param description: Description of the backup, None by default
+ :param force: boolean whether to backup even if the volume is attached
+ False by default
+ :param snapshot_id: UUID of the source snapshot to back up
+ None by default
+ :param incremental: boolean, False by default
+ :param container: a container name, None by default
+ :param **kwargs: additional parameters per the documentation:
+ https://docs.openstack.org/api-ref/block-storage/v3/
+ #create-a-backup
"""
name = name or data_utils.rand_name(
self.__class__.__name__ + "-backup")
- kwargs = {'name': name,
- 'description': description,
- 'force': force,
- 'snapshot_id': snapshot_id,
- 'incremental': incremental,
- 'container': container}
+ args = {'name': name,
+ 'description': description,
+ 'force': force,
+ 'snapshot_id': snapshot_id,
+ 'incremental': incremental,
+ 'container': container}
+ args.update(kwargs)
backup = self.backups_client.create_backup(volume_id=volume_id,
**kwargs)['backup']
self.addCleanup(self.backups_client.delete_backup, backup['id'])
@@ -397,14 +412,20 @@
backup['id'], 'available')
return backup
- def restore_backup(self, backup_id):
- """Restore backup
+ def restore_backup(self, backup_id, **kwargs):
+ """Restores a backup given by the backup_id
- This wrapper utility restores backup and waits for backup to be
- in 'available' state.
+ This wrapper utility restores a backup and waits until it is in
+ 'available' state.
+
+ :param backup_id: UUID of a backup to restore
+ :param **kwargs: additional parameters per the documentation:
+ https://docs.openstack.org/api-ref/block-storage/v3/
+ #restore-a-backup
"""
- restore = self.backups_client.restore_backup(backup_id)['restore']
+ body = self.backups_client.restore_backup(backup_id, **kwargs)
+ restore = body['restore']
self.addCleanup(self.volumes_client.delete_volume,
restore['volume_id'])
waiters.wait_for_volume_resource_status(self.backups_client,
@@ -431,11 +452,20 @@
server_id, 'ACTIVE')
def create_volume_snapshot(self, volume_id, name=None, description=None,
- metadata=None, force=False):
- """Creates volume
+ metadata=None, force=False, **kwargs):
+ """Creates volume's snapshot
- This wrapper utility creates volume snapshot and waits for backup
- to be in 'available' state.
+ This wrapper utility creates volume snapshot and waits for it until
+ it is in 'available' state.
+
+ :param volume_id: UUID of a volume to create snapshot of
+ :param name: name of the snapshot, '$classname-snapshot' by default
+ :param description: description of the snapshot
+ :param metadata: metadata key and value pairs for the snapshot
+ :param force: whether snapshot even when the volume is attached
+ :param **kwargs: additional parameters per the doc
+ https://docs.openstack.org/api-ref/block-storage/v3/
+ #create-a-snapshot
"""
name = name or data_utils.rand_name(
@@ -445,7 +475,8 @@
force=force,
name=name,
description=description,
- metadata=metadata)['snapshot']
+ metadata=metadata,
+ **kwargs)['snapshot']
self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
snapshot['id'])
@@ -456,7 +487,7 @@
snapshot['id'])['snapshot']
return snapshot
- def _cleanup_volume_type(self, volume_type):
+ def cleanup_volume_type(self, volume_type):
"""Clean up a given volume type.
Ensuring all volumes associated to a type are first removed before
@@ -512,10 +543,10 @@
volume_type = volume_type_resp['volume_type']
self.assertIn('id', volume_type)
- self.addCleanup(self._cleanup_volume_type, volume_type)
+ self.addCleanup(self.cleanup_volume_type, volume_type)
return volume_type
- def _create_loginable_secgroup_rule(self, secgroup_id=None, rulesets=None):
+ def create_loginable_secgroup_rule(self, secgroup_id=None, rulesets=None):
"""Create loginable security group rule by compute clients.
This function will create by default the following rules:
@@ -559,7 +590,7 @@
rules.append(sg_rule)
return rules
- def _create_security_group(self, **kwargs):
+ def create_security_group(self, **kwargs):
"""Create security group and add rules to security group"""
if not kwargs.get('name'):
kwargs['name'] = data_utils.rand_name(self.__class__.__name__)
@@ -575,7 +606,7 @@
secgroup['id'])
# Add rules to the security group
- self._create_loginable_secgroup_rule(secgroup['id'])
+ self.create_loginable_secgroup_rule(secgroup['id'])
return secgroup
def get_remote_client(self, ip_address, username=None, private_key=None,
@@ -655,7 +686,7 @@
LOG.debug("image:%s", image['id'])
return image['id']
- def _log_console_output(self, servers=None, client=None, **kwargs):
+ def log_console_output(self, servers=None, client=None, **kwargs):
"""Console log output"""
if not CONF.compute_feature_enabled.console_output:
LOG.debug('Console output not supported, cannot log')
@@ -791,7 +822,7 @@
'result': 'expected' if result else 'unexpected'
})
if server:
- self._log_console_output([server])
+ self.log_console_output([server])
return result
def check_vm_connectivity(self, ip_address,
@@ -1025,10 +1056,10 @@
if not CONF.service_available.neutron:
raise cls.skipException('Neutron not available')
- def _create_network(self, networks_client=None,
- project_id=None,
- namestart='network-smoke-',
- port_security_enabled=True, **net_dict):
+ def create_network(self, networks_client=None,
+ project_id=None,
+ namestart='network-smoke-',
+ port_security_enabled=True, **net_dict):
if not networks_client:
networks_client = self.networks_client
if not project_id:
@@ -1063,6 +1094,8 @@
:Keyword Arguments:
* *ip_version = ip version of the given network,
+ use_default_subnetpool = default subnetpool to
+ manage IPv6 addresses range.
"""
if not subnets_client:
@@ -1085,51 +1118,73 @@
network_id=ext_net['id'], cidr=cidr)['subnets'])
return len(tenant_subnets + external_subnets) != 0
- ip_version = kwargs.pop('ip_version', 4)
-
- if ip_version == 6:
- tenant_cidr = netaddr.IPNetwork(
- CONF.network.project_network_v6_cidr)
- num_bits = CONF.network.project_network_v6_mask_bits
- else:
- tenant_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
- num_bits = CONF.network.project_network_mask_bits
-
- result = None
- str_cidr = None
- # Repeatedly attempt subnet creation with sequential cidr
- # blocks until an unallocated block is found.
- for subnet_cidr in tenant_cidr.subnet(num_bits):
- str_cidr = str(subnet_cidr)
- if cidr_in_use(str_cidr, project_id=network['project_id']):
- continue
+ def _make_create_subnet_request(namestart, network,
+ ip_version, subnets_client, **kwargs):
subnet = dict(
name=data_utils.rand_name(namestart),
network_id=network['id'],
project_id=network['project_id'],
- cidr=str_cidr,
ip_version=ip_version,
**kwargs
)
+
+ if ip_version == 6:
+ subnet['ipv6_address_mode'] = 'slaac'
+ subnet['ipv6_ra_mode'] = 'slaac'
+
try:
- result = subnets_client.create_subnet(**subnet)
- break
+ return subnets_client.create_subnet(**subnet)
except lib_exc.Conflict as e:
- is_overlapping_cidr = 'overlaps with another subnet' in str(e)
- if not is_overlapping_cidr:
+ if 'overlaps with another subnet' not in str(e):
raise
+
+ result = None
+ str_cidr = None
+
+ use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
+ ip_version = kwargs.pop('ip_version', 4)
+
+ if not use_default_subnetpool:
+
+ if ip_version == 6:
+ tenant_cidr = netaddr.IPNetwork(
+ CONF.network.project_network_v6_cidr)
+ num_bits = CONF.network.project_network_v6_mask_bits
+ else:
+ tenant_cidr = netaddr.IPNetwork(
+ CONF.network.project_network_cidr)
+ num_bits = CONF.network.project_network_mask_bits
+
+ # Repeatedly attempt subnet creation with sequential cidr
+ # blocks until an unallocated block is found.
+ for subnet_cidr in tenant_cidr.subnet(num_bits):
+ str_cidr = str(subnet_cidr)
+ if cidr_in_use(str_cidr, project_id=network['project_id']):
+ continue
+ result = _make_create_subnet_request(
+ namestart, network, ip_version, subnets_client,
+ cidr=str_cidr, **kwargs)
+
+ if result is not None:
+ break
+
+ else:
+ result = _make_create_subnet_request(
+ namestart, network, ip_version, subnets_client,
+ **kwargs)
self.assertIsNotNone(result, 'Unable to allocate tenant network')
subnet = result['subnet']
- self.assertEqual(subnet['cidr'], str_cidr)
+ if str_cidr is not None:
+ self.assertEqual(subnet['cidr'], str_cidr)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
subnets_client.delete_subnet, subnet['id'])
return subnet
- def _get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
+ def get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
if ip_addr and not kwargs.get('fixed_ips'):
kwargs['fixed_ips'] = 'ip_address=%s' % ip_addr
@@ -1166,7 +1221,7 @@
% port_map)
return port_map[0]
- def _get_network_by_name(self, network_name):
+ def get_network_by_name(self, network_name):
net = self.os_admin.networks_client.list_networks(
name=network_name)['networks']
self.assertNotEmpty(net,
@@ -1182,7 +1237,7 @@
if not client:
client = self.floating_ips_client
if not port_id:
- port_id, ip4 = self._get_server_port_id_and_ip4(server)
+ port_id, ip4 = self.get_server_port_id_and_ip4(server)
else:
ip4 = None
@@ -1210,7 +1265,7 @@
This wrapper utility attaches the floating_ip for
the respective port_id of server
"""
- port_id, _ = self._get_server_port_id_and_ip4(server)
+ port_id, _ = self.get_server_port_id_and_ip4(server)
kwargs = dict(port_id=port_id)
floating_ip = self.floating_ips_client.update_floatingip(
floating_ip['id'], **kwargs)['floatingip']
@@ -1280,7 +1335,7 @@
should_connect=should_connect)
except Exception as e:
LOG.exception('Tenant network connectivity check failed')
- self._log_console_output(servers_for_debug)
+ self.log_console_output(servers_for_debug)
self._log_net_info(e)
raise
@@ -1323,25 +1378,25 @@
% (dest, source_host)
else:
msg = "%s is reachable from %s" % (dest, source_host)
- self._log_console_output()
+ self.log_console_output()
self.fail(msg)
- def _create_security_group(self, security_group_rules_client=None,
- project_id=None,
- namestart='secgroup-smoke',
- security_groups_client=None):
+ def create_security_group(self, security_group_rules_client=None,
+ project_id=None,
+ namestart='secgroup-smoke',
+ security_groups_client=None):
if security_group_rules_client is None:
security_group_rules_client = self.security_group_rules_client
if security_groups_client is None:
security_groups_client = self.security_groups_client
if project_id is None:
project_id = security_groups_client.project_id
- secgroup = self._create_empty_security_group(
+ secgroup = self.create_empty_security_group(
namestart=namestart, client=security_groups_client,
project_id=project_id)
# Add rules to the security group
- rules = self._create_loginable_secgroup_rule(
+ rules = self.create_loginable_secgroup_rule(
security_group_rules_client=security_group_rules_client,
secgroup=secgroup,
security_groups_client=security_groups_client)
@@ -1350,8 +1405,8 @@
self.assertEqual(secgroup['id'], rule['security_group_id'])
return secgroup
- def _create_empty_security_group(self, client=None, project_id=None,
- namestart='secgroup-smoke'):
+ def create_empty_security_group(self, client=None, project_id=None,
+ namestart='secgroup-smoke'):
"""Create a security group without rules.
Default rules will be created:
@@ -1382,10 +1437,10 @@
client.delete_security_group, secgroup['id'])
return secgroup
- def _create_security_group_rule(self, secgroup=None,
- sec_group_rules_client=None,
- project_id=None,
- security_groups_client=None, **kwargs):
+ def create_security_group_rule(self, secgroup=None,
+ sec_group_rules_client=None,
+ project_id=None,
+ security_groups_client=None, **kwargs):
"""Create a rule from a dictionary of rule parameters.
Create a rule in a secgroup. if secgroup not defined will search for
@@ -1430,9 +1485,9 @@
return sg_rule
- def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
- secgroup=None,
- security_groups_client=None):
+ def create_loginable_secgroup_rule(self, security_group_rules_client=None,
+ secgroup=None,
+ security_groups_client=None):
"""Create loginable security group rule by neutron clients by default.
This function will create:
@@ -1469,7 +1524,7 @@
for r_direction in ['ingress', 'egress']:
ruleset['direction'] = r_direction
try:
- sg_rule = self._create_security_group_rule(
+ sg_rule = self.create_security_group_rule(
sec_group_rules_client=sec_group_rules_client,
secgroup=secgroup,
security_groups_client=security_groups_client,
@@ -1485,7 +1540,7 @@
return rules
- def _get_router(self, client=None, project_id=None):
+ def get_router(self, client=None, project_id=None, **kwargs):
"""Retrieve a router for the given tenant id.
If a public router has been configured, it will be returned.
@@ -1505,11 +1560,20 @@
body = client.show_router(router_id)
return body['router']
elif network_id:
+ name = kwargs.pop('name', None)
+ if not name:
+ namestart = self.__class__.__name__ + '-router'
+ name = data_utils.rand_name(namestart)
+
+ ext_gw_info = kwargs.pop('external_gateway_info', None)
+ if not ext_gw_info:
+ ext_gw_info = dict(network_id=network_id)
router = client.create_router(
- name=data_utils.rand_name(self.__class__.__name__ + '-router'),
- admin_state_up=True,
+ name=name,
+ admin_state_up=kwargs.get('admin_state_up', True),
project_id=project_id,
- external_gateway_info=dict(network_id=network_id))['router']
+ external_gateway_info=ext_gw_info,
+ **kwargs)['router']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
client.delete_router, router['id'])
return router
@@ -1545,18 +1609,18 @@
if not CONF.compute.fixed_network_name:
m = 'fixed_network_name must be specified in config'
raise lib_exc.InvalidConfiguration(m)
- network = self._get_network_by_name(
+ network = self.get_network_by_name(
CONF.compute.fixed_network_name)
router = None
subnet = None
else:
- network = self._create_network(
+ network = self.create_network(
networks_client=networks_client,
project_id=project_id,
port_security_enabled=port_security_enabled,
**net_dict)
- router = self._get_router(client=routers_client,
- project_id=project_id)
+ router = self.get_router(client=routers_client,
+ project_id=project_id)
subnet_kwargs = dict(network=network,
subnets_client=subnets_client)
# use explicit check because empty list is a valid option
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index fc93a5e..6ee9f28 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -30,8 +30,7 @@
For both LUKS and cryptsetup encryption types, this test performs
the following:
- * Creates an image in Glance
- * Boots an instance from the image
+ * Boots an instance from an image (CONF.compute.image_ref)
* Creates an encryption type (as admin)
* Creates a volume of that encryption type (as a regular user)
* Attaches and detaches the encrypted volume to the instance
@@ -44,10 +43,9 @@
raise cls.skipException('Encrypted volume attach is not supported')
def launch_instance(self):
- image = self.image_create()
keypair = self.create_keypair()
- return self.create_server(image_id=image, key_name=keypair['name'])
+ return self.create_server(key_name=keypair['name'])
def attach_detach_volume(self, server, volume):
attached_volume = self.nova_volume_attach(server, volume)
diff --git a/tempest/scenario/test_minbw_allocation_placement.py b/tempest/scenario/test_minbw_allocation_placement.py
index a9d15bc..8c2752d 100644
--- a/tempest/scenario/test_minbw_allocation_placement.py
+++ b/tempest/scenario/test_minbw_allocation_placement.py
@@ -20,6 +20,7 @@
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
@@ -54,6 +55,8 @@
# https://github.com/openstack/placement/blob/master/placement/
# db/constants.py#L16
PLACEMENT_MAX_INT = 0x7FFFFFFF
+ BANDWIDTH_1 = 1000
+ BANDWIDTH_2 = 2000
@classmethod
def setup_clients(cls):
@@ -61,6 +64,7 @@
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.ports_client = cls.os_primary.ports_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
@@ -78,7 +82,6 @@
def setUp(self):
super(MinBwAllocationPlacementTest, self).setUp()
self._check_if_allocation_is_possible()
- self._create_network_and_qos_policies()
def _create_policy_and_min_bw_rule(self, name_prefix, min_kbps):
policy = self.qos_client.create_qos_policy(
@@ -99,7 +102,7 @@
return policy
- def _create_qos_policies(self):
+ def _create_qos_basic_policies(self):
self.qos_policy_valid = self._create_policy_and_min_bw_rule(
name_prefix='test_policy_valid',
min_kbps=self.SMALLEST_POSSIBLE_BW)
@@ -107,7 +110,20 @@
name_prefix='test_policy_not_valid',
min_kbps=self.PLACEMENT_MAX_INT)
- def _create_network_and_qos_policies(self):
+ def _create_qos_policies_from_life(self):
+ # For tempest-slow the max bandwidth configured is 1000000,
+ # https://opendev.org/openstack/tempest/src/branch/master/
+ # .zuul.yaml#L416-L420
+ self.qos_policy_1 = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_1',
+ min_kbps=self.BANDWIDTH_1
+ )
+ self.qos_policy_2 = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_2',
+ min_kbps=self.BANDWIDTH_2
+ )
+
+ def _create_network_and_qos_policies(self, policy_method):
physnet_name = CONF.network_feature_enabled.qos_placement_physnet
base_segm = \
CONF.network_feature_enabled.provider_net_base_segmentation_id
@@ -123,7 +139,7 @@
'provider:segmentation_id': base_segm
})
- self._create_qos_policies()
+ policy_method()
def _check_if_allocation_is_possible(self):
alloc_candidates = self.placement_client.list_allocation_candidates(
@@ -157,20 +173,29 @@
status=status, ready_wait=False, raise_on_error=False)
return server, port
- def _assert_allocation_is_as_expected(self, allocations, port_id):
- self.assertGreater(len(allocations['allocations']), 0)
+ def _assert_allocation_is_as_expected(self, consumer, port_ids,
+ min_kbps=SMALLEST_POSSIBLE_BW):
+ allocations = self.placement_client.list_allocations(
+ consumer)['allocations']
+ self.assertGreater(len(allocations), 0)
bw_resource_in_alloc = False
- for rp, resources in allocations['allocations'].items():
+ for rp, resources in allocations.items():
if self.INGRESS_RESOURCE_CLASS in resources['resources']:
+ self.assertEqual(
+ min_kbps,
+ resources['resources'][self.INGRESS_RESOURCE_CLASS])
bw_resource_in_alloc = True
allocation_rp = rp
- self.assertTrue(bw_resource_in_alloc)
+ if min_kbps:
+ self.assertTrue(bw_resource_in_alloc)
- # Check binding_profile of the port is not empty and equals with the
- # rp uuid
- port = self.os_admin.ports_client.show_port(port_id)
- self.assertEqual(allocation_rp,
- port['port']['binding:profile']['allocation'])
+ # Check binding_profile of the port is not empty and equals with
+ # the rp uuid
+ for port_id in port_ids:
+ port = self.os_admin.ports_client.show_port(port_id)
+ self.assertEqual(
+ allocation_rp,
+ port['port']['binding:profile']['allocation'])
@decorators.idempotent_id('78625d92-212c-400e-8695-dd51706858b8')
@utils.services('compute', 'network')
@@ -193,11 +218,11 @@
* Create port with invalid QoS policy, and try to boot VM with that,
it should fail.
"""
-
+ self._create_network_and_qos_policies(self._create_qos_basic_policies)
server1, valid_port = self._boot_vm_with_min_bw(
qos_policy_id=self.qos_policy_valid['id'])
- allocations = self.placement_client.list_allocations(server1['id'])
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server1['id'],
+ [valid_port['id']])
server2, not_valid_port = self._boot_vm_with_min_bw(
self.qos_policy_not_valid['id'], status='ERROR')
@@ -228,27 +253,28 @@
* If the VM goes to ACTIVE state check that allocations are as
expected.
"""
+ self._create_network_and_qos_policies(self._create_qos_basic_policies)
server, valid_port = self._boot_vm_with_min_bw(
qos_policy_id=self.qos_policy_valid['id'])
- allocations = self.placement_client.list_allocations(server['id'])
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server['id'],
+ [valid_port['id']])
self.servers_client.migrate_server(server_id=server['id'])
waiters.wait_for_server_status(
client=self.os_primary.servers_client, server_id=server['id'],
status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
- allocations = self.placement_client.list_allocations(server['id'])
# TODO(lajoskatona): Check that the allocations are ok for the
# migration?
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server['id'],
+ [valid_port['id']])
self.servers_client.confirm_resize_server(server_id=server['id'])
waiters.wait_for_server_status(
client=self.os_primary.servers_client, server_id=server['id'],
status='ACTIVE', ready_wait=False, raise_on_error=True)
- allocations = self.placement_client.list_allocations(server['id'])
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server['id'],
+ [valid_port['id']])
@decorators.idempotent_id('c29e7fd3-035d-4993-880f-70819847683f')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
@@ -264,10 +290,11 @@
* If the VM goes to ACTIVE state check that allocations are as
expected.
"""
+ self._create_network_and_qos_policies(self._create_qos_basic_policies)
server, valid_port = self._boot_vm_with_min_bw(
qos_policy_id=self.qos_policy_valid['id'])
- allocations = self.placement_client.list_allocations(server['id'])
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server['id'],
+ [valid_port['id']])
old_flavor = self.flavors_client.show_flavor(
CONF.compute.flavor_ref)['flavor']
@@ -285,15 +312,176 @@
waiters.wait_for_server_status(
client=self.os_primary.servers_client, server_id=server['id'],
status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
- allocations = self.placement_client.list_allocations(server['id'])
# TODO(lajoskatona): Check that the allocations are ok for the
# migration?
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server['id'],
+ [valid_port['id']])
self.servers_client.confirm_resize_server(server_id=server['id'])
waiters.wait_for_server_status(
client=self.os_primary.servers_client, server_id=server['id'],
status='ACTIVE', ready_wait=False, raise_on_error=True)
- allocations = self.placement_client.list_allocations(server['id'])
- self._assert_allocation_is_as_expected(allocations, valid_port['id'])
+ self._assert_allocation_is_as_expected(server['id'],
+ [valid_port['id']])
+
+ @decorators.idempotent_id('79fdaa1c-df62-4738-a0f0-1cff9dc415f6')
+ @utils.services('compute', 'network')
+ def test_qos_min_bw_allocation_update_policy(self):
+ """Test the update of QoS policy on bound port
+
+ Related RFE in neutron: #1882804
+ The scenario is the following:
+ * Have a port with QoS policy and minimum bandwidth rule.
+ * Boot a VM with the port.
+ * Update the port with a new policy with different minimum bandwidth
+ values.
+ * The allocation on placement side should be according to the new
+ rules.
+ """
+ if not utils.is_network_feature_enabled('update_port_qos'):
+ raise self.skipException("update_port_qos feature is not enabled")
+
+ self._create_network_and_qos_policies(
+ self._create_qos_policies_from_life)
+
+ port = self.create_port(
+ self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': port['id']}])
+
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_1)
+
+ self.ports_client.update_port(
+ port['id'],
+ **{'qos_policy_id': self.qos_policy_2['id']})
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_2)
+
+ # I changed my mind
+ self.ports_client.update_port(
+ port['id'],
+ **{'qos_policy_id': self.qos_policy_1['id']})
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_1)
+
+ # bad request....
+ self.qos_policy_not_valid = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_not_valid',
+ min_kbps=self.PLACEMENT_MAX_INT)
+ port_orig = self.ports_client.show_port(port['id'])['port']
+ self.assertRaises(
+ lib_exc.Conflict,
+ self.ports_client.update_port,
+ port['id'], **{'qos_policy_id': self.qos_policy_not_valid['id']})
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_1)
+
+ port_upd = self.ports_client.show_port(port['id'])['port']
+ self.assertEqual(port_orig['qos_policy_id'],
+ port_upd['qos_policy_id'])
+ self.assertEqual(self.qos_policy_1['id'], port_upd['qos_policy_id'])
+
+ @decorators.idempotent_id('9cfc3bb8-f433-4c91-87b6-747cadc8958a')
+ @utils.services('compute', 'network')
+ def test_qos_min_bw_allocation_update_policy_from_zero(self):
+ """Test port without QoS policy to have QoS policy
+
+ This scenario checks if updating a port without QoS policy to
+ have QoS policy with minimum_bandwidth rule succeeds only on
+ controlplane, but placement allocation remains 0.
+ """
+ if not utils.is_network_feature_enabled('update_port_qos'):
+ raise self.skipException("update_port_qos feature is not enabled")
+
+ self._create_network_and_qos_policies(
+ self._create_qos_policies_from_life)
+
+ port = self.create_port(self.prov_network['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': port['id']}])
+
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']], 0)
+
+ self.ports_client.update_port(
+ port['id'], **{'qos_policy_id': self.qos_policy_2['id']})
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']], 0)
+
+ @decorators.idempotent_id('a9725a70-1d28-4e3b-ae0e-450abc235962')
+ @utils.services('compute', 'network')
+ def test_qos_min_bw_allocation_update_policy_to_zero(self):
+ """Test port with QoS policy to remove QoS policy
+
+ In this scenario port with QoS minimum_bandwidth rule update to
+ remove QoS policy results in 0 placement allocation.
+ """
+ if not utils.is_network_feature_enabled('update_port_qos'):
+ raise self.skipException("update_port_qos feature is not enabled")
+
+ self._create_network_and_qos_policies(
+ self._create_qos_policies_from_life)
+
+ port = self.create_port(
+ self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': port['id']}])
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_1)
+
+ self.ports_client.update_port(
+ port['id'],
+ **{'qos_policy_id': None})
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']], 0)
+
+ @decorators.idempotent_id('756ced7f-6f1a-43e7-a851-2fcfc16f3dd7')
+ @utils.services('compute', 'network')
+ def test_qos_min_bw_allocation_update_with_multiple_ports(self):
+ if not utils.is_network_feature_enabled('update_port_qos'):
+ raise self.skipException("update_port_qos feature is not enabled")
+
+ self._create_network_and_qos_policies(
+ self._create_qos_policies_from_life)
+
+ port1 = self.create_port(
+ self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+ port2 = self.create_port(
+ self.prov_network['id'], qos_policy_id=self.qos_policy_2['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': port1['id']}, {'port': port2['id']}])
+ self._assert_allocation_is_as_expected(
+ server1['id'], [port1['id'], port2['id']],
+ self.BANDWIDTH_1 + self.BANDWIDTH_2)
+
+ self.ports_client.update_port(
+ port1['id'],
+ **{'qos_policy_id': self.qos_policy_2['id']})
+ self._assert_allocation_is_as_expected(
+ server1['id'], [port1['id'], port2['id']],
+ 2 * self.BANDWIDTH_2)
+
+ @decorators.idempotent_id('0805779e-e03c-44fb-900f-ce97a790653b')
+ @utils.services('compute', 'network')
+ def test_empty_update(self):
+ if not utils.is_network_feature_enabled('update_port_qos'):
+ raise self.skipException("update_port_qos feature is not enabled")
+
+ self._create_network_and_qos_policies(
+ self._create_qos_policies_from_life)
+
+ port = self.create_port(
+ self.prov_network['id'], qos_policy_id=self.qos_policy_1['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': port['id']}])
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_1)
+ self.ports_client.update_port(
+ port['id'],
+ **{'description': 'foo'})
+ self._assert_allocation_is_as_expected(server1['id'], [port['id']],
+ self.BANDWIDTH_1)
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index fe42583..5201315 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -78,7 +78,7 @@
self.assertEqual(1, disks.count(CONF.compute.volume_device_name))
def create_and_add_security_group_to_server(self, server):
- secgroup = self._create_security_group()
+ secgroup = self.create_security_group()
self.servers_client.add_security_group(server['id'],
name=secgroup['name'])
self.addCleanup(self.servers_client.remove_security_group,
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index dbab212..20d2e80 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -60,7 +60,7 @@
def _setup_server(self, keypair):
security_groups = []
if utils.is_extension_enabled('security-group', 'network'):
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
security_groups = [{'name': security_group['name']}]
network, _, _ = self.create_networks()
server = self.create_server(
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 6c1b3fa..e359c71 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -159,7 +159,7 @@
keypair = self.create_keypair()
self.keypairs[keypair['name']] = keypair
security_groups = [
- {'name': self._create_security_group()['name']}
+ {'name': self.create_security_group()['name']}
]
network = {'uuid': network['id']}
if port_id is not None:
@@ -223,14 +223,14 @@
floating_ip, server = self.floating_ip_tuple
# create a new server for the floating ip
server = self._create_server(self.network)
- port_id, _ = self._get_server_port_id_and_ip4(server)
+ port_id, _ = self.get_server_port_id_and_ip4(server)
floating_ip = self.floating_ips_client.update_floatingip(
floating_ip['id'], port_id=port_id)['floatingip']
self.assertEqual(port_id, floating_ip['port_id'])
self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server)
def _create_new_network(self, create_gateway=False):
- self.new_net = self._create_network()
+ self.new_net = self.create_network()
if create_gateway:
self.new_subnet = self.create_subnet(
network=self.new_net)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 14f24c7..4f5118b 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -66,7 +66,7 @@
def setUp(self):
super(TestGettingAddress, self).setUp()
self.keypair = self.create_keypair()
- self.sec_grp = self._create_security_group()
+ self.sec_grp = self.create_security_group()
def prepare_network(self, address6_mode, n_subnets6=1, dualnet=False):
"""Prepare network
@@ -77,15 +77,15 @@
if dualnet - create IPv6 subnets on a different network
:return: list of created networks
"""
- network = self._create_network()
+ network = self.create_network()
if dualnet:
- network_v6 = self._create_network()
+ network_v6 = self.create_network()
sub4 = self.create_subnet(network=network,
namestart='sub4',
ip_version=4)
- router = self._get_router()
+ router = self.get_router()
self.routers_client.add_router_interface(router['id'],
subnet_id=sub4['id'])
@@ -218,7 +218,7 @@
guest_has_address,
CONF.validation.ping_timeout, 1, ssh, ip)
if not result:
- self._log_console_output(servers=[srv])
+ self.log_console_output(servers=[srv])
self.fail(
'Address %s not configured for instance %s, '
'ip address output is\n%s' %
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 3fc93e4..e078f23 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -197,14 +197,14 @@
tenant.keypair = keypair
def _create_tenant_security_groups(self, tenant):
- access_sg = self._create_empty_security_group(
+ access_sg = self.create_empty_security_group(
namestart='secgroup_access-',
project_id=tenant.creds.project_id,
client=tenant.manager.security_groups_client
)
# don't use default secgroup since it allows in-project traffic
- def_sg = self._create_empty_security_group(
+ def_sg = self.create_empty_security_group(
namestart='secgroup_general-',
project_id=tenant.creds.project_id,
client=tenant.manager.security_groups_client
@@ -217,7 +217,7 @@
direction='ingress',
)
sec_group_rules_client = tenant.manager.security_group_rules_client
- self._create_security_group_rule(
+ self.create_security_group_rule(
secgroup=access_sg,
sec_group_rules_client=sec_group_rules_client,
**ssh_rule)
@@ -385,7 +385,7 @@
remote_group_id=tenant.security_groups['default']['id'],
direction='ingress'
)
- self._create_security_group_rule(
+ self.create_security_group_rule(
secgroup=tenant.security_groups['default'],
security_groups_client=tenant.manager.security_groups_client,
**ruleset
@@ -413,7 +413,7 @@
protocol = ruleset['protocol']
sec_group_rules_client = (
dest_tenant.manager.security_group_rules_client)
- self._create_security_group_rule(
+ self.create_security_group_rule(
secgroup=dest_tenant.security_groups['default'],
sec_group_rules_client=sec_group_rules_client,
**ruleset
@@ -429,7 +429,7 @@
# allow reverse traffic and check
sec_group_rules_client = (
source_tenant.manager.security_group_rules_client)
- self._create_security_group_rule(
+ self.create_security_group_rule(
secgroup=source_tenant.security_groups['default'],
sec_group_rules_client=sec_group_rules_client,
**ruleset
@@ -464,9 +464,9 @@
def _log_console_output_for_all_tenants(self):
for tenant in self.tenants.values():
client = tenant.manager.servers_client
- self._log_console_output(servers=tenant.servers, client=client)
+ self.log_console_output(servers=tenant.servers, client=client)
if tenant.access_point is not None:
- self._log_console_output(
+ self.log_console_output(
servers=[tenant.access_point], client=client)
def _create_protocol_ruleset(self, protocol, port=80):
@@ -534,7 +534,7 @@
new_tenant = self.primary_tenant
# Create empty security group and add icmp rule in it
- new_sg = self._create_empty_security_group(
+ new_sg = self.create_empty_security_group(
namestart='secgroup_new-',
project_id=new_tenant.creds.project_id,
client=new_tenant.manager.security_groups_client)
@@ -543,7 +543,7 @@
direction='ingress',
)
sec_group_rules_client = new_tenant.manager.security_group_rules_client
- self._create_security_group_rule(
+ self.create_security_group_rule(
secgroup=new_sg,
sec_group_rules_client=sec_group_rules_client,
**icmp_rule)
@@ -596,7 +596,7 @@
protocol='icmp',
direction='ingress'
)
- self._create_security_group_rule(
+ self.create_security_group_rule(
secgroup=tenant.security_groups['default'],
**ruleset
)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 8aa729b..990b325 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -37,7 +37,7 @@
@classmethod
def setup_credentials(cls):
- cls.set_network_resources()
+ cls.set_network_resources(network=True, subnet=True)
super(TestServerAdvancedOps, cls).setup_credentials()
@decorators.attr(type='slow')
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 02bc692..4c82d84 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -67,7 +67,10 @@
def verify_metadata(self):
if self.run_ssh and CONF.compute_feature_enabled.metadata_service:
# Verify metadata service
- md_url = 'http://169.254.169.254/latest/meta-data/public-ipv4'
+ if CONF.network.public_network_id:
+ md_url = 'http://169.254.169.254/latest/meta-data/public-ipv4'
+ else:
+ md_url = 'http://169.254.169.254/latest/meta-data/local-ipv4'
def exec_cmd_and_verify_output():
cmd = 'curl ' + md_url
@@ -125,7 +128,7 @@
@utils.services('compute', 'network')
def test_server_basic_ops(self):
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'}
self.instance = self.create_server(
key_name=keypair['name'],
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index ed06898..29612ec 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -76,7 +76,7 @@
cold_migrate=False):
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
security_groups = [{'name': security_group['name']}]
server = self.create_server(
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index a062d40..d04cb9a 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -50,7 +50,7 @@
def test_snapshot_pattern(self):
# prepare for booting an instance
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
# boot an instance and create a timestamp file in it
server = self.create_server(
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index a8e4c30..4b81b9e 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -81,7 +81,7 @@
def test_stamp_pattern(self):
# prepare for booting an instance
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
# boot an instance and create a timestamp file in it
volume = self.create_volume()
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
index 8a8c54e..71e6b53 100644
--- a/tempest/scenario/test_volume_backup_restore.py
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -70,7 +70,7 @@
# Create keypair and security group
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
# Boot a server from the restored backup
bd_map_v2 = [{
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 3b4bbda..5a5cc27 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -64,7 +64,7 @@
LOG.info("Creating keypair and security group")
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
# create an instance from volume
LOG.info("Booting instance 1 from volume")
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index 106500e..57d2a1a 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -100,7 +100,7 @@
def test_volume_retype_attached(self):
LOG.info("Creating keypair and security group")
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
# create volume types
LOG.info("Creating Volume types")
@@ -156,7 +156,7 @@
def test_volume_migrate_attached(self):
LOG.info("Creating keypair and security group")
keypair = self.create_keypair()
- security_group = self._create_security_group()
+ security_group = self.create_security_group()
LOG.info("Creating volume")
# Create a unique volume type to avoid using the backend default
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 9fec548..0d7720e 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -15,9 +15,9 @@
import re
import time
+from urllib import parse as urllib
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest import exceptions
from tempest.lib.common import rest_client
diff --git a/tempest/test.py b/tempest/test.py
index f383bc1..655b9a4 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -38,12 +38,6 @@
CONF = config.CONF
-# TODO(oomichi): This test.idempotent_id should be removed after all projects
-# switch to use decorators.idempotent_id.
-idempotent_id = debtcollector.moves.moved_function(
- decorators.idempotent_id, 'idempotent_id', __name__,
- version='Mitaka', removal_version='?')
-
attr = debtcollector.moves.moved_function(
decorators.attr, 'attr', __name__,
@@ -302,6 +296,7 @@
identity_version = cls.get_identity_version()
# setting force_tenant_isolation to True also needs admin credentials.
if ('admin' in cls.credentials or
+ 'alt_admin' in cls.credentials or
getattr(cls, 'force_tenant_isolation', False)):
if not credentials.is_admin_available(
identity_version=identity_version):
@@ -420,8 +415,18 @@
'alt_manager', 'os_alt', version='Pike',
removal_version='Queens')
elif isinstance(credentials_type, list):
+ scope = 'project'
+ if credentials_type[0].startswith('system'):
+ scope = 'system'
+ elif credentials_type[0].startswith('domain'):
+ scope = 'domain'
manager = cls.get_client_manager(roles=credentials_type[1:],
- force_new=True)
+ force_new=True,
+ scope=scope)
+ setattr(cls, 'os_%s' % credentials_type[0], manager)
+ # TODO(gmann): Setting the old style attribute too for
+ # backward compatibility but at some point we should
+ # remove this.
setattr(cls, 'os_roles_%s' % credentials_type[0], manager)
@classmethod
@@ -663,7 +668,7 @@
@classmethod
def get_client_manager(cls, credential_type=None, roles=None,
- force_new=None):
+ force_new=None, scope=None):
"""Returns an OpenStack client manager
Returns an OpenStack client manager based on either credential_type
@@ -671,6 +676,7 @@
credential_type 'primary'
:param credential_type: string - primary, alt or admin
:param roles: list of roles
+ :param scope: scope for the test user
:returns: the created client manager
:raises skipException: if the requested credentials are not available
@@ -689,7 +695,7 @@
" is not able to provide credentials with the %s role "
"assigned." % (cls.__name__, role))
raise cls.skipException(skip_msg)
- params = dict(roles=roles)
+ params = dict(roles=roles, scope=scope)
if force_new is not None:
params.update(force_new=force_new)
creds = cred_provider.get_creds_by_roles(**params)
@@ -856,7 +862,13 @@
if isinstance(credentials_type, six.string_types):
manager = cls.get_client_manager(credential_type=credentials_type)
elif isinstance(credentials_type, list):
- manager = cls.get_client_manager(roles=credentials_type[1:])
+ scope = 'project'
+ if credentials_type[0].startswith('system'):
+ scope = 'system'
+ elif credentials_type[0].startswith('domain'):
+ scope = 'domain'
+ manager = cls.get_client_manager(roles=credentials_type[1:],
+ scope=scope)
else:
manager = cls.get_client_manager()
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index b20b60e..1d69d9d 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -15,7 +15,6 @@
import abc
from oslo_log import log as logging
-import six
import stevedore
from tempest.lib.common.utils import misc
@@ -24,8 +23,7 @@
LOG = logging.getLogger(__name__)
-@six.add_metaclass(abc.ABCMeta)
-class TempestPlugin(object):
+class TempestPlugin(object, metaclass=abc.ABCMeta):
"""Provide basic hooks for an external plugin
To provide tempest the necessary information to run the plugin.
diff --git a/tempest/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 74d2625..8a1873b 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -15,7 +15,6 @@
from unittest import mock
from oslo_utils import uuidutils
-import six
from tempest.api.compute import base as compute_base
from tempest.common import waiters
@@ -128,9 +127,9 @@
mock.sentinel.server_id, wait_until='active')
# make our assertions
if fault:
- self.assertIn(fault, six.text_type(ex))
+ self.assertIn(fault, str(ex))
else:
- self.assertNotIn(fault, six.text_type(ex))
+ self.assertNotIn(fault, str(ex))
if compute_base.BaseV2ComputeTest.is_requested_microversion_compatible(
'2.35'):
status = 'ACTIVE'
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 3c99bbe..3b5e901 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -21,7 +21,6 @@
from unittest import mock
import fixtures
-import six
from tempest.cmd import run
from tempest.cmd import workspace
@@ -68,6 +67,11 @@
class TestRunReturnCode(base.TestCase):
+
+ exclude_regex = '--exclude-regex'
+ exclude_list = '--exclude-list'
+ include_list = '--include-list'
+
def setUp(self):
super(TestRunReturnCode, self).setUp()
# Setup test dirs
@@ -92,6 +96,14 @@
self.addCleanup(os.chdir, os.path.abspath(os.curdir))
os.chdir(self.directory)
+ def _get_test_list_file(self, content):
+ fd, path = tempfile.mkstemp()
+ self.addCleanup(os.remove, path)
+ test_file = os.fdopen(fd, 'wb', 0)
+ self.addCleanup(test_file.close)
+ test_file.write(content.encode('utf-8'))
+ return path
+
def assertRunExit(self, cmd, expected):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -115,19 +127,23 @@
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_exclude_regex_failing(self):
+ self.assertRunExit(['tempest', 'run',
+ self.exclude_regex, 'failing'], 0)
- def test_tempest_run_blackregex_failing_with_stestr_repository(self):
+ def test_tempest_run_exclude_regex_failing_with_stestr_repository(self):
subprocess.call(['stestr', 'init'])
- self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0)
+ self.assertRunExit(['tempest', 'run',
+ self.exclude_regex, 'failing'], 0)
- def test_tempest_run_blackregex_passing(self):
- self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+ def test_tempest_run_exclude_regex_passing(self):
+ self.assertRunExit(['tempest', 'run',
+ self.exclude_regex, 'passing'], 1)
- def test_tempest_run_blackregex_passing_with_stestr_repository(self):
+ def test_tempest_run_exclude_regex_passing_with_stestr_repository(self):
subprocess.call(['stestr', 'init'])
- self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1)
+ self.assertRunExit(['tempest', 'run',
+ self.exclude_regex, 'passing'], 1)
def test_tempest_run_fails(self):
self.assertRunExit(['tempest', 'run'], 1)
@@ -136,12 +152,12 @@
subprocess.call(['stestr', 'init'])
out, err = self.assertRunExit(['tempest', 'run', '-l'], 0)
tests = out.split()
- tests = sorted([six.text_type(x.rstrip()) for x in tests if x])
+ tests = sorted([str(x.rstrip()) for x in tests if x])
result = [
- six.text_type('tests.test_failing.FakeTestClass.test_pass'),
- six.text_type('tests.test_failing.FakeTestClass.test_pass_list'),
- six.text_type('tests.test_passing.FakeTestClass.test_pass'),
- six.text_type('tests.test_passing.FakeTestClass.test_pass_list'),
+ str('tests.test_failing.FakeTestClass.test_pass'),
+ str('tests.test_failing.FakeTestClass.test_pass_list'),
+ str('tests.test_passing.FakeTestClass.test_pass'),
+ str('tests.test_passing.FakeTestClass.test_pass_list'),
]
# NOTE(mtreinish): on python 3 the subprocess prints b'' around
# stdout.
@@ -149,47 +165,31 @@
self.assertEqual(result, tests)
def test_tempest_run_with_worker_file(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- worker_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(worker_file.close)
- worker_file.write(
- '- worker:\n - passing\n concurrency: 3'.encode('utf-8'))
+ path = self._get_test_list_file(
+ '- worker:\n - passing\n concurrency: 3')
self.assertRunExit(['tempest', 'run', '--worker-file=%s' % path], 0)
- def test_tempest_run_with_whitelist(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- whitelist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(whitelist_file.close)
- whitelist_file.write('passing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path], 0)
+ def test_tempest_run_with_include_list(self):
+ path = self._get_test_list_file('passing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.include_list, path)], 0)
- def test_tempest_run_with_whitelist_regex_include_pass_check_fail(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- whitelist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(whitelist_file.close)
- whitelist_file.write('passing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+ def test_tempest_run_with_include_regex_include_pass_check_fail(self):
+ path = self._get_test_list_file('passing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.include_list, path),
'--regex', 'fail'], 1)
- def test_tempest_run_with_whitelist_regex_include_pass_check_pass(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- whitelist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(whitelist_file.close)
- whitelist_file.write('passing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+ def test_tempest_run_with_include_regex_include_pass_check_pass(self):
+ path = self._get_test_list_file('passing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.include_list, path),
'--regex', 'passing'], 0)
- def test_tempest_run_with_whitelist_regex_include_fail_check_pass(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- whitelist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(whitelist_file.close)
- whitelist_file.write('failing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path,
+ def test_tempest_run_with_include_regex_include_fail_check_pass(self):
+ path = self._get_test_list_file('failing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.include_list, path),
'--regex', 'pass'], 1)
def test_tempest_run_passes_with_config_file(self):
@@ -197,50 +197,75 @@
'--config-file', self.stestr_conf_file,
'--regex', 'passing'], 0)
- def test_tempest_run_with_blacklist_failing(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- blacklist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(blacklist_file.close)
- blacklist_file.write('failing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path], 0)
+ def test_tempest_run_with_exclude_list_failing(self):
+ path = self._get_test_list_file('failing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.exclude_list, path)], 0)
- def test_tempest_run_with_blacklist_passing(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- blacklist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(blacklist_file.close)
- blacklist_file.write('passing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path], 1)
+ def test_tempest_run_with_exclude_list_passing(self):
+ path = self._get_test_list_file('passing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.exclude_list, path)], 1)
- def test_tempest_run_with_blacklist_regex_exclude_fail_check_pass(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- blacklist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(blacklist_file.close)
- blacklist_file.write('failing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path,
+ def test_tempest_run_with_exclude_list_regex_exclude_fail_check_pass(self):
+ path = self._get_test_list_file('failing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.exclude_list, path),
'--regex', 'pass'], 0)
- def test_tempest_run_with_blacklist_regex_exclude_pass_check_pass(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- blacklist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(blacklist_file.close)
- blacklist_file.write('passing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path,
+ def test_tempest_run_with_exclude_list_regex_exclude_pass_check_pass(self):
+ path = self._get_test_list_file('passing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.exclude_list, path),
'--regex', 'pass'], 1)
- def test_tempest_run_with_blacklist_regex_exclude_pass_check_fail(self):
- fd, path = tempfile.mkstemp()
- self.addCleanup(os.remove, path)
- blacklist_file = os.fdopen(fd, 'wb', 0)
- self.addCleanup(blacklist_file.close)
- blacklist_file.write('passing'.encode('utf-8'))
- self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path,
+ def test_tempest_run_with_exclude_list_regex_exclude_pass_check_fail(self):
+ path = self._get_test_list_file('passing')
+ self.assertRunExit(['tempest', 'run',
+ '%s=%s' % (self.exclude_list, path),
'--regex', 'fail'], 1)
+class TestOldArgRunReturnCode(TestRunReturnCode):
+ """A class for testing deprecated but still supported args.
+
+ This class will be removed once we remove the following arguments:
+ * --black-regex
+ * --blacklist-file
+ * --whitelist-file
+ """
+ exclude_regex = '--black-regex'
+ exclude_list = '--blacklist-file'
+ include_list = '--whitelist-file'
+
+ def _test_args_passing(self, args):
+ self.assertRunExit(['tempest', 'run'] + args, 0)
+
+ def test_tempest_run_new_old_arg_comb(self):
+ path = self._get_test_list_file('failing')
+ self._test_args_passing(['--black-regex', 'failing',
+ '--exclude-regex', 'failing'])
+ self._test_args_passing(['--blacklist-file=' + path,
+ '--exclude-list=' + path])
+ path = self._get_test_list_file('passing')
+ self._test_args_passing(['--whitelist-file=' + path,
+ '--include-list=' + path])
+
+ def _test_args_passing_with_stestr_repository(self, args):
+ subprocess.call(['stestr', 'init'])
+ self.assertRunExit(['tempest', 'run'] + args, 0)
+
+ def test_tempest_run_new_old_arg_comb_with_stestr_repository(self):
+ path = self._get_test_list_file('failing')
+ self._test_args_passing_with_stestr_repository(
+ ['--black-regex', 'failing', '--exclude-regex', 'failing'])
+ self._test_args_passing_with_stestr_repository(
+ ['--blacklist-file=' + path, '--exclude-list=' + path])
+ path = self._get_test_list_file('passing')
+ self._test_args_passing_with_stestr_repository(
+ ['--whitelist-file=' + path, '--include-list=' + path])
+
+
class TestConfigPathCheck(base.TestCase):
def setUp(self):
super(TestConfigPathCheck, self).setUp()
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 277e049..a8a4c0f 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -18,12 +18,9 @@
import fixtures
from oslo_serialization import jsonutils as json
-from tempest import clients
from tempest.cmd import init
from tempest.cmd import verify_tempest_config
-from tempest.common import credentials_factory
from tempest import config
-from tempest.lib.common import rest_client
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
from tempest.tests import base
@@ -97,15 +94,15 @@
self.useFixture(fixtures.MockPatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
- fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]}
+ fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
fake_resp = json.dumps(fake_resp)
self.useFixture(fixtures.MockPatch(
'tempest.lib.common.http.ClosingHttp.request',
return_value=(None, fake_resp)))
fake_os = mock.MagicMock()
versions = verify_tempest_config._get_api_versions(fake_os, 'cinder')
- self.assertIn('v1.0', versions)
self.assertIn('v2.0', versions)
+ self.assertIn('v3.0', versions)
def test_get_nova_versions(self):
self.useFixture(fixtures.MockPatchObject(
@@ -145,7 +142,7 @@
self.assertTrue(mock_log_error.called)
def test_verify_api_versions(self):
- api_services = ['cinder', 'glance', 'keystone']
+ api_services = ['glance', 'keystone']
fake_os = mock.MagicMock()
for svc in api_services:
m = 'verify_%s_api_versions' % svc
@@ -154,7 +151,7 @@
verify_mock.assert_called_once_with(fake_os, True)
def test_verify_api_versions_not_implemented(self):
- api_services = ['cinder', 'glance', 'keystone']
+ api_services = ['glance', 'keystone']
fake_os = mock.MagicMock()
for svc in api_services:
m = 'verify_%s_api_versions' % svc
@@ -178,52 +175,6 @@
'identity-feature-enabled',
False, True)
- @mock.patch('tempest.lib.common.http.ClosingHttp.request')
- def test_verify_cinder_api_versions_no_v3(self, mock_request):
- self.useFixture(fixtures.MockPatchObject(
- verify_tempest_config, '_get_unversioned_endpoint',
- return_value='http://fake_endpoint:5000'))
- fake_resp = {'versions': [{'id': 'v2.0'}]}
- fake_resp = json.dumps(fake_resp)
- mock_request.return_value = (None, fake_resp)
- fake_os = mock.MagicMock()
- with mock.patch.object(verify_tempest_config,
- 'print_and_or_update') as print_mock:
- verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
- False, True)
- self.assertEqual(1, print_mock.call_count)
-
- @mock.patch('tempest.lib.common.http.ClosingHttp.request')
- def test_verify_cinder_api_versions_no_v2(self, mock_request):
- self.useFixture(fixtures.MockPatchObject(
- verify_tempest_config, '_get_unversioned_endpoint',
- return_value='http://fake_endpoint:5000'))
- fake_resp = {'versions': [{'id': 'v3.0'}]}
- fake_resp = json.dumps(fake_resp)
- mock_request.return_value = (None, fake_resp)
- fake_os = mock.MagicMock()
- with mock.patch.object(verify_tempest_config,
- 'print_and_or_update') as print_mock:
- verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
- False, True)
- self.assertEqual(1, print_mock.call_count)
-
- @mock.patch('tempest.lib.common.http.ClosingHttp.request')
- def test_verify_cinder_api_versions_no_v1(self, mock_request):
- self.useFixture(fixtures.MockPatchObject(
- verify_tempest_config, '_get_unversioned_endpoint',
- return_value='http://fake_endpoint:5000'))
- fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
- fake_resp = json.dumps(fake_resp)
- mock_request.return_value = (None, fake_resp)
- fake_os = mock.MagicMock()
- with mock.patch.object(verify_tempest_config,
- 'print_and_or_update') as print_mock:
- verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_not_called()
-
def test_verify_glance_version_no_v2_with_v1_1(self):
# This test verifies that wrong config api_v2 = True is detected
class FakeClient(object):
@@ -560,23 +511,24 @@
self.assertEqual([], results['swift']['extensions'])
def test_get_extension_client(self):
- creds = credentials_factory.get_credentials(
- fill_in=False, username='fake_user', project_name='fake_project',
- password='fake_password')
- os = clients.Manager(creds)
- for service in ['nova', 'neutron', 'swift', 'cinder']:
+ fake_os = mock.MagicMock()
+ services = {
+ 'nova': fake_os.compute.ExtensionsClient(),
+ 'neutron': fake_os.network.ExtensionsClient(),
+ 'swift': fake_os.object_storage.CapabilitiesClient(),
+ 'cinder': fake_os.volume_v2.ExtensionsClient(),
+ }
+ for service in services.keys():
extensions_client = verify_tempest_config.get_extension_client(
- os, service)
- self.assertIsInstance(extensions_client, rest_client.RestClient)
+ fake_os, service)
+ self.assertIsInstance(extensions_client, mock.MagicMock)
+ self.assertEqual(extensions_client, services[service])
def test_get_extension_client_sysexit(self):
- creds = credentials_factory.get_credentials(
- fill_in=False, username='fake_user', project_name='fake_project',
- password='fake_password')
- os = clients.Manager(creds)
+ fake_os = mock.MagicMock()
self.assertRaises(SystemExit,
verify_tempest_config.get_extension_client,
- os, 'fakeservice')
+ fake_os, 'fakeservice')
def test_get_config_file(self):
conf_dir = os.path.join(os.getcwd(), 'etc')
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index eae6202..f16d533 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -12,15 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+from io import StringIO
import os
import shutil
import subprocess
import tempfile
from unittest.mock import patch
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
from tempest.cmd import workspace
from tempest.lib.common.utils import data_utils
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
index 45a439c..142bb08 100644
--- a/tempest/tests/common/test_compute.py
+++ b/tempest/tests/common/test_compute.py
@@ -15,7 +15,7 @@
from unittest import mock
-from six.moves.urllib import parse as urlparse
+from urllib import parse as urlparse
from tempest.common import compute
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index ff74877..d64d7b0 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -66,7 +66,7 @@
# Ensure waiter returns before build_timeout
self.assertLess((end_time - start_time), 10)
- def test_wait_for_image_imported_to_stores_timeout(self):
+ def test_wait_for_image_imported_to_stores_failure(self):
time_mock = self.patch('time.time')
client = mock.MagicMock()
client.build_timeout = 2
@@ -77,6 +77,20 @@
'status': 'saving',
'stores': 'fake_store',
'os_glance_failed_import': 'fake_os_glance_failed_import'})
+ self.assertRaises(lib_exc.OtherRestClientException,
+ waiters.wait_for_image_imported_to_stores,
+ client, 'fake_image_id', 'fake_store')
+
+ def test_wait_for_image_imported_to_stores_timeout(self):
+ time_mock = self.patch('time.time')
+ client = mock.MagicMock()
+ client.build_timeout = 2
+ self.patch('time.time', side_effect=[0., 1., 2.])
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ client.show_image.return_value = ({
+ 'status': 'saving',
+ 'stores': 'fake_store'})
self.assertRaises(lib_exc.TimeoutException,
waiters.wait_for_image_imported_to_stores,
client, 'fake_image_id', 'fake_store')
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
index 428e047..5d63dec 100644
--- a/tempest/tests/lib/cmd/test_check_uuid.py
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -13,13 +13,13 @@
import ast
import importlib
import os
+import shutil
import sys
import tempfile
from unittest import mock
-import fixtures
-
from tempest.lib.cmd import check_uuid
+from tempest.lib import decorators
from tempest.tests import base
@@ -40,23 +40,24 @@
return tests_file
def test_fix_argument_no(self):
- temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
- tests_file = self.create_tests_file(temp_dir.path)
-
+ temp_dir = tempfile.mkdtemp(prefix='check-uuid-no', dir=".")
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+ tests_file = self.create_tests_file(temp_dir)
sys.argv = [sys.argv[0]] + ["--package",
- os.path.relpath(temp_dir.path)]
+ os.path.relpath(temp_dir)]
self.assertRaises(SystemExit, check_uuid.run)
with open(tests_file, "r") as f:
self.assertTrue(TestCLInterface.CODE == f.read())
+ @decorators.skip_because(bug='1918316')
def test_fix_argument_yes(self):
- temp_dir = self.useFixture(fixtures.TempDir(rootdir="."))
- tests_file = self.create_tests_file(temp_dir.path)
+ temp_dir = tempfile.mkdtemp(prefix='check-uuid-yes', dir=".")
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+ tests_file = self.create_tests_file(temp_dir)
sys.argv = [sys.argv[0]] + ["--fix", "--package",
- os.path.relpath(temp_dir.path)]
-
+ os.path.relpath(temp_dir)]
check_uuid.run()
with open(tests_file, "r") as f:
self.assertTrue(TestCLInterface.CODE != f.read())
diff --git a/tempest/tests/lib/common/test_api_version_utils.py b/tempest/tests/lib/common/test_api_version_utils.py
index b99e8d4..8d5de09 100644
--- a/tempest/tests/lib/common/test_api_version_utils.py
+++ b/tempest/tests/lib/common/test_api_version_utils.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
import testtools
from tempest.lib.common import api_version_utils
@@ -31,7 +30,7 @@
cfg_max_version)
except testtools.TestCase.skipException as e:
if not expected_skip:
- raise testtools.TestCase.failureException(six.text_type(e))
+ raise testtools.TestCase.failureException(str(e))
def test_version_min_in_range(self):
self._test_version('2.2', '2.10', '2.1', '2.7')
diff --git a/tempest/tests/lib/common/test_cred_client.py b/tempest/tests/lib/common/test_cred_client.py
index b99311c..7ea660b 100644
--- a/tempest/tests/lib/common/test_cred_client.py
+++ b/tempest/tests/lib/common/test_cred_client.py
@@ -111,3 +111,21 @@
self.assertIsNone(ret.project_name)
self.assertEqual(ret.system, {'system': 'all'})
self.assertEqual(ret.domain_name, 'some_domain')
+
+ def test_create_user(self):
+ self.users_client.create_user.return_value = {
+ 'user': 'a_user'
+ }
+ fake_project = {
+ 'id': 'fake_project_id',
+ }
+ res = self.creds_client.create_user('fake_username',
+ 'fake_password',
+ fake_project,
+ 'fake_email')
+ self.assertEqual('a_user', res)
+ self.users_client.create_user.assert_called_once_with(
+ name='fake_username', password='fake_password',
+ project_id=fake_project['id'],
+ email='fake_email',
+ domain_id='fake_domain_id')
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index e9073cc..b4b1b91 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -214,6 +214,56 @@
self.assertEqual(admin_creds.user_id, '1234')
@mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_project_alt_admin_creds(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_roles('1234', 'admin')
+ self._mock_user_create('1234', 'fake_alt_admin_user')
+ self._mock_tenant_create('1234', 'fake_alt_admin')
+
+ user_mock = mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project')
+ user_mock.start()
+ self.addCleanup(user_mock.stop)
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock:
+ alt_admin_creds = creds.get_project_alt_admin_creds()
+ user_mock.assert_has_calls([
+ mock.call('1234', '1234', '1234')])
+ self.assertEqual(alt_admin_creds.username, 'fake_alt_admin_user')
+ self.assertEqual(alt_admin_creds.project_name, 'fake_alt_admin')
+ # Verify IDs
+ self.assertEqual(alt_admin_creds.project_id, '1234')
+ self.assertEqual(alt_admin_creds.user_id, '1234')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_project_alt_member_creds(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_assign_user_role()
+ self._mock_list_role()
+ self._mock_tenant_create('1234', 'fake_alt_member')
+ self._mock_user_create('1234', 'fake_alt_user')
+ alt_member_creds = creds.get_project_alt_member_creds()
+ self.assertEqual(alt_member_creds.username, 'fake_alt_user')
+ self.assertEqual(alt_member_creds.project_name, 'fake_alt_member')
+ # Verify IDs
+ self.assertEqual(alt_member_creds.project_id, '1234')
+ self.assertEqual(alt_member_creds.user_id, '1234')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_project_alt_reader_creds(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_assign_user_role()
+ self._mock_list_roles('1234', 'reader')
+ self._mock_tenant_create('1234', 'fake_alt_reader')
+ self._mock_user_create('1234', 'fake_alt_user')
+ alt_reader_creds = creds.get_project_alt_reader_creds()
+ self.assertEqual(alt_reader_creds.username, 'fake_alt_user')
+ self.assertEqual(alt_reader_creds.project_name, 'fake_alt_reader')
+ # Verify IDs
+ self.assertEqual(alt_reader_creds.project_id, '1234')
+ self.assertEqual(alt_reader_creds.user_id, '1234')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_role_creds(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_list_2_roles()
@@ -242,6 +292,100 @@
self.assertEqual(role_creds.user_id, '1234')
@mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_role_creds_with_project_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+ self._mock_tenant_create('1234', 'fake_role_project')
+
+ user_mock = mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project')
+ user_mock.start()
+ self.addCleanup(user_mock.stop)
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='project')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ args = map(lambda x: x[1], calls)
+ args = list(args)
+ self.assertIn(('1234', '1234', '1234'), args)
+ self.assertIn(('1234', '1234', '12345'), args)
+ self.assertEqual(role_creds.username, 'fake_role_user')
+ self.assertEqual(role_creds.project_name, 'fake_role_project')
+ # Verify IDs
+ self.assertEqual(role_creds.project_id, '1234')
+ self.assertEqual(role_creds.user_id, '1234')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def _test_get_same_role_creds_with_project_scope(self, MockRestClient,
+ scope=None):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+ self._mock_tenant_create('1234', 'fake_role_project')
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope=scope)
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+
+ # Fetch the same creds again
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope=scope)
+ calls = user_mock1.mock_calls
+ # Assert that previously created creds are return and no call to
+ # role creation.
+ self.assertEqual(len(calls), 0)
+ # Check if previously created creds are returned.
+ self.assertEqual(role_creds, role_creds_new)
+
+ def test_get_same_role_creds_with_project_scope(self):
+ self._test_get_same_role_creds_with_project_scope(scope='project')
+
+ def test_get_same_role_creds_with_default_scope(self):
+ self._test_get_same_role_creds_with_project_scope()
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def _test_get_different_role_creds_with_project_scope(
+ self, MockRestClient, scope=None):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+ self._mock_tenant_create('1234', 'fake_role_project')
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope=scope)
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ # Fetch the creds with one role different
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1'], scope=scope)
+ calls = user_mock1.mock_calls
+ # Because one role is different, assert that the role creation
+ # is called with the 1 specified roles
+ self.assertEqual(len(calls), 1)
+ # Check new creds is created for new roles.
+ self.assertNotEqual(role_creds, role_creds_new)
+
+ def test_get_different_role_creds_with_project_scope(self):
+ self._test_get_different_role_creds_with_project_scope(
+ scope='project')
+
+ def test_get_different_role_creds_with_default_scope(self):
+ self._test_get_different_role_creds_with_project_scope()
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_all_cred_cleanup(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -658,6 +802,232 @@
return project_fix
@mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_role_creds_with_system_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_system') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='system')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ args = map(lambda x: x[1], calls)
+ args = list(args)
+ self.assertIn(('1234', '1234'), args)
+ self.assertIn(('1234', '12345'), args)
+ self.assertEqual(role_creds.username, 'fake_role_user')
+ self.assertEqual(role_creds.user_id, '1234')
+ # Verify system scope
+ self.assertEqual(role_creds.system, 'all')
+ # Verify domain is default
+ self.assertEqual(role_creds.domain_id, 'default')
+ self.assertEqual(role_creds.domain_name, 'Default')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_get_same_role_creds_with_system_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_system') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='system')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+
+ # Fetch the same creds again
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_system') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='system')
+ calls = user_mock1.mock_calls
+ # Assert that previously created creds are return and no call to
+ # role creation.
+ self.assertEqual(len(calls), 0)
+ # Verify system scope
+ self.assertEqual(role_creds_new.system, 'all')
+ # Check if previously created creds are returned.
+ self.assertEqual(role_creds, role_creds_new)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_get_different_role_creds_with_system_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_system') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='system')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ # Verify system scope
+ self.assertEqual(role_creds.system, 'all')
+ # Fetch the creds with one role different
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_system') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1'], scope='system')
+ calls = user_mock1.mock_calls
+ # Because one role is different, assert that the role creation
+ # is called with the 1 specified roles
+ self.assertEqual(len(calls), 1)
+ # Verify Scope
+ self.assertEqual(role_creds_new.system, 'all')
+ # Check new creds is created for new roles.
+ self.assertNotEqual(role_creds, role_creds_new)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_role_creds_with_domain_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+
+ domain = {
+ "id": '12',
+ "enabled": True,
+ "name": "TestDomain"
+ }
+
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.cred_client.V3CredsClient.create_domain',
+ return_value=domain))
+
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='domain')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ args = map(lambda x: x[1], calls)
+ args = list(args)
+ self.assertIn((domain['id'], '1234', '1234'), args)
+ self.assertIn((domain['id'], '1234', '12345'), args)
+ self.assertEqual(role_creds.username, 'fake_role_user')
+ self.assertEqual(role_creds.user_id, '1234')
+ # Verify creds are under new created domain
+ self.assertEqual(role_creds.domain_id, domain['id'])
+ self.assertEqual(role_creds.domain_name, domain['name'])
+ # Verify that Scope is None
+ self.assertIsNone(role_creds.system)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_get_same_role_creds_with_domain_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+
+ domain = {
+ "id": '12',
+ "enabled": True,
+ "name": "TestDomain"
+ }
+
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.cred_client.V3CredsClient.create_domain',
+ return_value=domain))
+
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='domain')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ self.assertEqual(role_creds.user_id, '1234')
+ # Verify Scope
+ self.assertIsNone(role_creds.system)
+ # Fetch the same creds again
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='domain')
+ calls = user_mock1.mock_calls
+ # Assert that previously created creds are return and no call to
+ # role creation.
+ self.assertEqual(len(calls), 0)
+ # Verify Scope
+ self.assertIsNone(role_creds_new.system)
+ # Check if previously created creds are returned.
+ self.assertEqual(role_creds, role_creds_new)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_get_different_role_creds_with_domain_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+
+ domain = {
+ "id": '12',
+ "enabled": True,
+ "name": "TestDomain"
+ }
+
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.cred_client.V3CredsClient.create_domain',
+ return_value=domain))
+
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='domain')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ self.assertEqual(role_creds.user_id, '1234')
+ # Verify Scope
+ self.assertIsNone(role_creds.system)
+ # Fetch the same creds again
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1'], scope='domain')
+ calls = user_mock1.mock_calls
+ # Because one role is different, assert that the role creation
+ # is called with the 1 specified roles
+ self.assertEqual(len(calls), 1)
+ # Verify Scope
+ self.assertIsNone(role_creds_new.system)
+ # Check new creds is created for new roles.
+ self.assertNotEqual(role_creds, role_creds_new)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_get_role_creds_with_different_scope(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_2_roles()
+ self._mock_user_create('1234', 'fake_role_user')
+ self._mock_tenant_create('1234', 'fake_role_project')
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_system') as user_mock:
+ role_creds = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='system')
+ calls = user_mock.mock_calls
+ # Assert that the role creation is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ # Verify Scope
+ self.assertEqual(role_creds.system, 'all')
+
+ # Fetch the same role creds but with different scope
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock1:
+ role_creds_new = creds.get_creds_by_roles(
+ roles=['role1', 'role2'], scope='project')
+ calls = user_mock1.mock_calls
+ # Because scope is different, assert that the role creation
+ # is called with the 2 specified roles
+ self.assertEqual(len(calls), 2)
+ # Verify Scope
+ self.assertIsNone(role_creds_new.system)
+ # Check that created creds are different
+ self.assertNotEqual(role_creds, role_creds_new)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_member_role_creation_with_duplicate(self, rest_client_mock):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
creds.creds_client = mock.MagicMock()
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 579363e..fe7fcd2 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -12,17 +12,16 @@
# License for the specific language governing permissions and limitations
# under the License.
-import hashlib
import os
import shutil
from unittest import mock
-import six
import testtools
import fixtures
from oslo_concurrency.fixture import lockutils as lockutils_fixtures
from oslo_config import cfg
+from oslo_utils.secretutils import md5
from tempest import config
from tempest.lib import auth
@@ -106,10 +105,10 @@
hash_fields = (
preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS)
for account in accounts_list:
- hash = hashlib.md5()
+ hash = md5(usedforsecurity=False)
account_for_hash = dict((k, v) for (k, v) in account.items()
if k in hash_fields)
- hash.update(six.text_type(account_for_hash).encode('utf-8'))
+ hash.update(str(account_for_hash).encode('utf-8'))
temp_hash = hash.hexdigest()
hash_list.append(temp_hash)
return hash_list
diff --git a/tempest/tests/lib/services/identity/v3/test_identity_providers_client.py b/tempest/tests/lib/services/identity/v3/test_identity_providers_client.py
new file mode 100644
index 0000000..964c51b
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_identity_providers_client.py
@@ -0,0 +1,142 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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.lib.services.identity.v3 import identity_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestIdentityProvidersClient(base.BaseServiceTest):
+ FAKE_IDENTITY_PROVIDERS_INFO = {
+ "identity_providers": [
+ {
+ "domain_id": "FAKE_DOMAIN_ID",
+ "description": "FAKE IDENTITY PROVIDER",
+ "remote_ids": ["fake_id_1", "fake_id_2"],
+ "enabled": True,
+ "id": "FAKE_ID",
+ "links": {
+ "protocols": "http://example.com/identity/v3/" +
+ "OS-FEDERATION/identity_providers/" +
+ "FAKE_ID/protocols",
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/FAKE_ID"
+ }
+ }
+ ],
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers"
+ }
+ }
+
+ FAKE_IDENTITY_PROVIDER_INFO = {
+ "identity_provider": {
+ "authorization_ttl": None,
+ "domain_id": "FAKE_DOMAIN_ID",
+ "description": "FAKE IDENTITY PROVIDER",
+ "remote_ids": ["fake_id_1", "fake_id_2"],
+ "enabled": True,
+ "id": "ACME",
+ "links": {
+ "protocols": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/FAKE_ID/protocols",
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/FAKE_ID"
+ }
+ }
+ }
+
+ def setUp(self):
+ super(TestIdentityProvidersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = identity_providers_client.IdentityProvidersClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_register_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.register_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_IDENTITY_PROVIDER_INFO,
+ bytes_body,
+ identity_provider_id="FAKE_ID",
+ status=201)
+
+ def _test_list_identity_providers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_identity_providers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_IDENTITY_PROVIDERS_INFO,
+ bytes_body,
+ status=200)
+
+ def _test_get_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_IDENTITY_PROVIDER_INFO,
+ bytes_body,
+ identity_provider_id="FAKE_ID",
+ status=200)
+
+ def _test_delete_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ bytes_body,
+ identity_provider_id="FAKE_ID",
+ status=204)
+
+ def _test_update_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_IDENTITY_PROVIDER_INFO,
+ bytes_body,
+ identity_provider_id="FAKE_ID",
+ status=200)
+
+ def test_register_identity_provider_with_str_body(self):
+ self._test_register_identity_provider()
+
+ def test_register_identity_provider_with_bytes_body(self):
+ self._test_register_identity_provider(bytes_body=True)
+
+ def test_list_identity_providers_with_str_body(self):
+ self._test_list_identity_providers()
+
+ def test_list_identity_providers_with_bytes_body(self):
+ self._test_list_identity_providers(bytes_body=True)
+
+ def test_get_identity_provider_with_str_body(self):
+ self._test_get_identity_provider()
+
+ def test_get_identity_provider_with_bytes_body(self):
+ self._test_get_identity_provider(bytes_body=True)
+
+ def test_delete_identity_provider_with_str_body(self):
+ self._test_delete_identity_provider()
+
+ def test_delete_identity_provider_with_bytes_body(self):
+ self._test_delete_identity_provider(bytes_body=True)
+
+ def test_update_identity_provider_with_str_body(self):
+ self._test_update_identity_provider()
+
+ def test_update_identity_provider_with_bytes_body(self):
+ self._test_update_identity_provider(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_mappings_client.py b/tempest/tests/lib/services/identity/v3/test_mappings_client.py
new file mode 100644
index 0000000..845a3f9
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_mappings_client.py
@@ -0,0 +1,183 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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.lib.services.identity.v3 import mappings_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestMappingsClient(base.BaseServiceTest):
+ FAKE_MAPPING_INFO = {
+ "mapping": {
+ "id": "fake123",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "mappings/fake123"
+ },
+ "rules": [
+ {
+ "local": [
+ {
+ "user": {
+ "name": "{0}"
+ }
+ },
+ {
+ "group": {
+ "id": "0cd5e9"
+ }
+ }
+ ],
+ "remote": [
+ {
+ "type": "UserName"
+ },
+ {
+ "type": "orgPersonType",
+ "not_any_of": [
+ "Contractor",
+ "Guest"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+ FAKE_MAPPINGS_INFO = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/OS-FEDERATION/mappings"
+ },
+ "mappings": [
+ {
+ "id": "fake123",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "mappings/fake123"
+ },
+ "rules": [
+ {
+ "local": [
+ {
+ "user": {
+ "name": "{0}"
+ }
+ },
+ {
+ "group": {
+ "id": "0cd5e9"
+ }
+ }
+ ],
+ "remote": [
+ {
+ "type": "UserName"
+ },
+ {
+ "type": "orgPersonType",
+ "any_one_of": [
+ "Contractor",
+ "SubContractor"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestMappingsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = mappings_client.MappingsClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_create_mapping(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_mapping,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_MAPPING_INFO,
+ bytes_body,
+ mapping_id="fake123",
+ status=201)
+
+ def _test_get_mapping(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_mapping,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_MAPPING_INFO,
+ bytes_body,
+ mapping_id="fake123",
+ status=200)
+
+ def _test_update_mapping(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_mapping,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_MAPPING_INFO,
+ bytes_body,
+ mapping_id="fake123",
+ status=200)
+
+ def _test_list_mappings(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_mappings,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_MAPPINGS_INFO,
+ bytes_body,
+ status=200)
+
+ def _test_delete_mapping(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_mapping,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ bytes_body,
+ mapping_id="fake123",
+ status=204)
+
+ def test_create_mapping_with_str_body(self):
+ self._test_create_mapping()
+
+ def test_create_mapping_with_bytes_body(self):
+ self._test_create_mapping(bytes_body=True)
+
+ def test_get_mapping_with_str_body(self):
+ self._test_get_mapping()
+
+ def test_get_mapping_with_bytes_body(self):
+ self._test_get_mapping(bytes_body=True)
+
+ def test_update_mapping_with_str_body(self):
+ self._test_update_mapping()
+
+ def test_update_mapping_with_bytes_body(self):
+ self._test_update_mapping(bytes_body=True)
+
+ def test_list_mappings_with_str_body(self):
+ self._test_list_mappings()
+
+ def test_list_mappings_with_bytes_body(self):
+ self._test_list_mappings(bytes_body=True)
+
+ def test_delete_mapping_with_str_body(self):
+ self._test_delete_mapping()
+
+ def test_delete_mapping_with_bytes_body(self):
+ self._test_delete_mapping(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_protocols_client.py b/tempest/tests/lib/services/identity/v3/test_protocols_client.py
new file mode 100644
index 0000000..c1d04f4
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_protocols_client.py
@@ -0,0 +1,140 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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.lib.services.identity.v3 import protocols_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestProtocolsClient(base.BaseServiceTest):
+ FAKE_PROTOCOLS_INFO = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/FAKE_ID/protocols"
+ },
+ "protocols": [
+ {
+ "id": "fake_id1",
+ "links": {
+ "identity_provider": "http://example.com/identity/v3/" +
+ "OS-FEDERATION/identity_providers/" +
+ "FAKE_ID",
+ "self": "http://example.com/identity/v3/OS-FEDERATION/"
+ "identity_providers/FAKE_ID/protocols/fake_id1"
+ },
+ "mapping_id": "fake123"
+ }
+ ]
+ }
+
+ FAKE_PROTOCOL_INFO = {
+ "protocol": {
+ "id": "fake_id1",
+ "links": {
+ "identity_provider": "http://example.com/identity/v3/OS-" +
+ "FEDERATION/identity_providers/FAKE_ID",
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/FAKE_ID/protocols/fake_id1"
+ },
+ "mapping_id": "fake123"
+ }
+ }
+
+ def setUp(self):
+ super(TestProtocolsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = protocols_client.ProtocolsClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_add_protocol_to_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_protocol_to_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_PROTOCOL_INFO,
+ bytes_body,
+ idp_id="FAKE_ID",
+ protocol_id="fake_id1",
+ status=201)
+
+ def _test_list_protocols_of_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_protocols_of_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_PROTOCOLS_INFO,
+ bytes_body,
+ idp_id="FAKE_ID",
+ status=200)
+
+ def _test_get_protocol_for_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_protocol_for_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_PROTOCOL_INFO,
+ bytes_body,
+ idp_id="FAKE_ID",
+ protocol_id="fake_id1",
+ status=200)
+
+ def _test_update_mapping_for_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_mapping_for_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_PROTOCOL_INFO,
+ bytes_body,
+ idp_id="FAKE_ID",
+ protocol_id="fake_id1",
+ status=200)
+
+ def _test_delete_protocol_from_identity_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_protocol_from_identity_provider,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ bytes_body,
+ idp_id="FAKE_ID",
+ protocol_id="fake_id1",
+ status=204)
+
+ def test_add_protocol_to_identity_provider_with_str_body(self):
+ self._test_add_protocol_to_identity_provider()
+
+ def test_add_protocol_to_identity_provider_with_bytes_body(self):
+ self._test_add_protocol_to_identity_provider(bytes_body=True)
+
+ def test_list_protocols_of_identity_provider_with_str_body(self):
+ self._test_list_protocols_of_identity_provider()
+
+ def test_list_protocols_of_identity_provider_with_bytes_body(self):
+ self._test_list_protocols_of_identity_provider(bytes_body=True)
+
+ def test_get_protocol_for_identity_provider_with_str_body(self):
+ self._test_get_protocol_for_identity_provider()
+
+ def test_get_protocol_for_identity_provider_with_bytes_body(self):
+ self._test_get_protocol_for_identity_provider(bytes_body=True)
+
+ def test_update_mapping_for_identity_provider_with_str_body(self):
+ self._test_update_mapping_for_identity_provider()
+
+ def test_update_mapping_for_identity_provider_with_bytes_body(self):
+ self._test_update_mapping_for_identity_provider(bytes_body=True)
+
+ def test_delete_protocol_from_identity_provider_with_str_body(self):
+ self._test_delete_protocol_from_identity_provider()
+
+ def test_delete_protocol_from_identity_provider_with_bytes_body(self):
+ self._test_delete_protocol_from_identity_provider(bytes_body=False)
diff --git a/tempest/tests/lib/services/identity/v3/test_service_providers_client.py b/tempest/tests/lib/services/identity/v3/test_service_providers_client.py
new file mode 100644
index 0000000..ec908bc
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_service_providers_client.py
@@ -0,0 +1,157 @@
+# Copyright 2020 Samsung Electronics Co., Ltd
+# 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.lib.services.identity.v3 import service_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServiceProvidersClient(base.BaseServiceTest):
+ FAKE_SERVICE_PROVIDER_INFO = {
+ "service_provider": {
+ "auth_url": "https://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/FAKE_ID/protocols/fake_id1/auth",
+ "description": "Fake Service Provider",
+ "enabled": True,
+ "id": "FAKE_ID",
+ "links": {
+ "self": "https://example.com/identity/v3/OS-FEDERATION/" +
+ "service_providers/FAKE_ID"
+ },
+ "relay_state_prefix": "ss:mem:",
+ "sp_url": "https://example.com/identity/Shibboleth.sso/" +
+ "FAKE_ID1/ECP"
+ }
+ }
+
+ FAKE_SERVICE_PROVIDERS_INFO = {
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "service_providers"
+ },
+ "service_providers": [
+ {
+ "auth_url": "https://example.com/identity/v3/OS-FEDERATION/" +
+ "identity_providers/acme/protocols/saml2/auth",
+ "description": "Stores ACME identities",
+ "enabled": True,
+ "id": "ACME",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "service_providers/ACME"
+ },
+ "relay_state_prefix": "ss:mem:",
+ "sp_url": "https://example.com/identity/Shibboleth.sso/" +
+ "SAML2/ECP"
+ },
+ {
+ "auth_url": "https://other.example.com/identity/v3/" +
+ "OS-FEDERATION/identity_providers/acme/" +
+ "protocols/saml2/auth",
+ "description": "Stores contractor identities",
+ "enabled": False,
+ "id": "ACME-contractors",
+ "links": {
+ "self": "http://example.com/identity/v3/OS-FEDERATION/" +
+ "service_providers/ACME-contractors"
+ },
+ "relay_state_prefix": "ss:mem:",
+ "sp_url": "https://other.example.com/identity/Shibboleth" +
+ ".sso/SAML2/ECP"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestServiceProvidersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = service_providers_client.ServiceProvidersClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_register_service_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.register_service_provider,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_SERVICE_PROVIDER_INFO,
+ bytes_body,
+ service_provider_id="FAKE_ID",
+ status=201)
+
+ def _test_list_service_providers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_service_providers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICE_PROVIDERS_INFO,
+ bytes_body,
+ status=200)
+
+ def _test_get_service_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_service_provider,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICE_PROVIDER_INFO,
+ bytes_body,
+ service_provider_id="FAKE_ID",
+ status=200)
+
+ def _test_delete_service_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_service_provider,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ bytes_body,
+ service_provider_id="FAKE_ID",
+ status=204)
+
+ def _test_update_service_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_service_provider,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_SERVICE_PROVIDER_INFO,
+ bytes_body,
+ service_provider_id="FAKE_ID",
+ status=200)
+
+ def test_register_service_provider_with_str_body(self):
+ self._test_register_service_provider()
+
+ def test_register_service_provider_with_bytes_body(self):
+ self._test_register_service_provider(bytes_body=True)
+
+ def test_list_service_providers_with_str_body(self):
+ self._test_list_service_providers()
+
+ def test_list_service_providers_with_bytes_body(self):
+ self._test_list_service_providers(bytes_body=True)
+
+ def test_get_service_provider_with_str_body(self):
+ self._test_get_service_provider()
+
+ def test_get_service_provider_with_bytes_body(self):
+ self._test_get_service_provider(bytes_body=True)
+
+ def test_delete_service_provider_with_str_body(self):
+ self._test_delete_service_provider()
+
+ def test_delete_service_provider_with_bytes_body(self):
+ self._test_delete_service_provider(bytes_body=True)
+
+ def test_update_service_provider_with_str_body(self):
+ self._test_update_service_provider()
+
+ def test_update_service_provider_with_bytes_body(self):
+ self._test_update_service_provider(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_trusts_client.py b/tempest/tests/lib/services/identity/v3/test_trusts_client.py
index a1ca020..33dca7d 100644
--- a/tempest/tests/lib/services/identity/v3/test_trusts_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_trusts_client.py
@@ -94,6 +94,35 @@
}
}
+ FAKE_LIST_TRUSTS_ROLES = {
+ "roles": [
+ {
+ "id": "c1648e",
+ "links": {
+ "self": "http://example.com/identity/v3/roles/c1648e"
+ },
+ "name": "manager"
+ },
+ {
+ "id": "ed7b78",
+ "links": {
+ "self": "http://example.com/identity/v3/roles/ed7b78"
+ },
+ "name": "member"
+ }
+ ]
+ }
+
+ FAKE_TRUST_ROLE = {
+ "role": {
+ "id": "c1648e",
+ "links": {
+ "self": "http://example.com/identity/v3/roles/c1648e"
+ },
+ "name": "manager"
+ }
+ }
+
def setUp(self):
super(TestTrustsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -123,6 +152,43 @@
self.FAKE_LIST_TRUSTS,
bytes_body)
+ def _test_list_trust_roles(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_trust_roles,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_TRUSTS_ROLES,
+ bytes_body,
+ trust_id="1ff900")
+
+ def test_check_trust_role(self):
+ self.check_service_client_function(
+ self.client.check_trust_role,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ trust_id="1ff900",
+ role_id="ed7b78")
+
+ def _check_show_trust_role(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_trust_role,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TRUST_ROLE,
+ bytes_body,
+ trust_id="1ff900",
+ role_id="ed7b78")
+
+ def test_list_trust_roles_with_str_body(self):
+ self._test_list_trust_roles()
+
+ def test_list_trust_roles_with_bytes_body(self):
+ self._test_list_trust_roles(bytes_body=True)
+
+ def test_check_show_trust_role_with_str_body(self):
+ self._check_show_trust_role()
+
+ def test_check_show_trust_role_with_bytes_body(self):
+ self._check_show_trust_role(bytes_body=True)
+
def test_create_trust_with_str_body(self):
self._test_create_trust()
diff --git a/tempest/tests/lib/services/image/v2/test_images_client.py b/tempest/tests/lib/services/image/v2/test_images_client.py
index fe671bd..5b162f8 100644
--- a/tempest/tests/lib/services/image/v2/test_images_client.py
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
+import io
from tempest.lib.common.utils import data_utils
from tempest.lib.services.image.v2 import images_client
@@ -105,6 +105,44 @@
"first": "/v2/images"
}
+ FAKE_SHOW_IMAGE_TASKS = {
+ "tasks": [
+ {
+ "id": "ee22890e-8948-4ea6-9668-831f973c84f5",
+ "image_id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
+ "request-id": "rrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr",
+ "user": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu",
+ "type": "api_image_import",
+ "status": "processing",
+ "owner": "64f0efc9955145aeb06f297a8a6fe402",
+ "expires_at": None,
+ "created_at": "2020-12-18T05:20:38.000000",
+ "updated_at": "2020-12-18T05:25:39.000000",
+ "deleted_at": None,
+ "deleted": False,
+ "input": {
+ "image_id": "829c729b-ebc4-4cc7-a164-6f43f1149b17",
+ "import_req": {
+ "method": {
+ "name": "copy-image",
+ },
+ "all_stores": True,
+ "all_stores_must_succeed": False,
+ },
+ "backend": [
+ "fast",
+ "cheap",
+ "slow",
+ "reliable",
+ "common",
+ ]
+ },
+ "result": None,
+ "message": "Copied 15 MiB",
+ }
+ ]
+ }
+
FAKE_TAG_NAME = "fake tag"
def setUp(self):
@@ -178,7 +216,7 @@
{}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204)
def test_store_image_file(self):
- data = six.BytesIO(data_utils.random_bytes())
+ data = io.BytesIO(data_utils.random_bytes())
self.check_service_client_function(
self.client.store_image_file,
@@ -230,3 +268,11 @@
def test_list_images_with_bytes_body(self):
self._test_list_images(bytes_body=True)
+
+ def test_show_image_tasks(self):
+ self.check_service_client_function(
+ self.client.show_image_tasks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_IMAGE_TASKS,
+ True,
+ image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8")
diff --git a/tempest/tests/lib/services/image/v2/test_versions_client.py b/tempest/tests/lib/services/image/v2/test_versions_client.py
index 6234b06..98c558a 100644
--- a/tempest/tests/lib/services/image/v2/test_versions_client.py
+++ b/tempest/tests/lib/services/image/v2/test_versions_client.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import fixtures
+
from tempest.lib.services.image.v2 import versions_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -92,3 +94,13 @@
def test_list_versions_with_bytes_body(self):
self._test_list_versions(bytes_body=True)
+
+ def test_has_version(self):
+ mocked_r = self.create_response(self.FAKE_VERSIONS_INFO, False,
+ 300, None)
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.rest_client.RestClient.raw_request',
+ return_value=mocked_r))
+
+ self.assertTrue(self.client.has_version('2.1'))
+ self.assertFalse(self.client.has_version('9.9'))
diff --git a/tempest/tests/lib/services/network/test_ports_client.py b/tempest/tests/lib/services/network/test_ports_client.py
index 20ef3f1..9ca9ac6 100644
--- a/tempest/tests/lib/services/network/test_ports_client.py
+++ b/tempest/tests/lib/services/network/test_ports_client.py
@@ -22,53 +22,126 @@
class TestPortsClient(base.BaseServiceTest):
+ FAKE_CREATE_PORTS = {
+ "port": {
+ "binding:host_id": "4df8d9ff-6f6f-438f-90a1-ef660d4586ad",
+ "binding:profile": {
+ "local_link_information": [
+ {
+ "port_id": "Ethernet3/1",
+ "switch_id": "0a:1b:2c:3d:4e:5f",
+ "switch_info": "switch1"
+ }
+ ]
+ },
+ "binding:vnic_type": "baremetal",
+ "device_id": "d90a13da-be41-461f-9f99-1dbcf438fdf2",
+ "device_owner": "baremetal:none",
+ "dns_domain": "my-domain.org.",
+ "dns_name": "myport",
+ "qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
+ "uplink_status_propagation": False
+ }
+ }
+
FAKE_PORTS = {
"ports": [
{
"admin_state_up": True,
"allowed_address_pairs": [],
+ "created_at": "2016-03-08T20:19:41",
"data_plane_status": None,
"description": "",
"device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
"device_owner": "network:router_gateway",
- "extra_dhcp_opts": [],
+ "dns_assignment": [
+ {
+ "hostname": "myport",
+ "ip_address": "172.24.4.2",
+ "fqdn": "myport.my-domain.org"
+ }
+ ],
+ "dns_domain": "my-domain.org.",
+ "dns_name": "myport",
+ "extra_dhcp_opts": [
+ {
+ "opt_value": "pxelinux.0",
+ "ip_version": 4,
+ "opt_name": "bootfile-name"
+ }
+ ],
"fixed_ips": [
{
"ip_address": "172.24.4.2",
- "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062"
+ "subnet_id":
+ "008ba151-0b8c-4a67-98b5-0d2b87666062"
}
],
"id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b",
+ "ip_allocation": "immediate",
"mac_address": "fa:16:3e:58:42:ed",
"name": "",
"network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3",
"project_id": "",
+ "revision_number": 1,
"security_groups": [],
"status": "ACTIVE",
- "tenant_id": ""
+ "tags": ["tag1,tag2"],
+ "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+ "updated_at": "2016-03-08T20:19:41",
+ "qos_network_policy_id":
+ "174dd0c1-a4eb-49d4-a807-ae80246d82f4",
+ "qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
+ "port_security_enabled": False,
+ "uplink_status_propagation": False
},
{
"admin_state_up": True,
"allowed_address_pairs": [],
+ "created_at": "2016-03-08T20:19:41",
"data_plane_status": None,
"description": "",
"device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824",
"device_owner": "network:router_interface",
- "extra_dhcp_opts": [],
+ "dns_assignment": [
+ {
+ "hostname": "myport2",
+ "ip_address": "10.0.0.1",
+ "fqdn": "myport2.my-domain.org"
+ }
+ ],
+ "dns_domain": "my-domain.org.",
+ "dns_name": "myport2",
+ "extra_dhcp_opts": [
+ {
+ "opt_value": "pxelinux.0",
+ "ip_version": 4,
+ "opt_name": "bootfile-name"
+ }
+ ],
"fixed_ips": [
{
"ip_address": "10.0.0.1",
- "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17"
+ "subnet_id":
+ "288bf4a1-51ba-43b6-9d0a-520e9005db17"
}
],
"id": "f71a6703-d6de-4be1-a91a-a570ede1d159",
+ "ip_allocation": "immediate",
"mac_address": "fa:16:3e:bb:3c:e4",
"name": "",
"network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2",
"project_id": "d397de8a63f341818f198abb0966f6f3",
+ "revision_number": 1,
"security_groups": [],
"status": "ACTIVE",
- "tenant_id": "d397de8a63f341818f198abb0966f6f3"
+ "tags": ["tag1,tag2"],
+ "tenant_id": "d397de8a63f341818f198abb0966f6f3",
+ "updated_at": "2016-03-08T20:19:41",
+ "qos_network_policy_id": None,
+ "qos_policy_id": None,
+ "port_security_enabled": False,
+ "uplink_status_propagation": False
}
]
}
@@ -112,7 +185,7 @@
self.check_service_client_function(
self.ports_client.create_port,
"tempest.lib.common.rest_client.RestClient.post",
- {"port": self.FAKE_PORTS["ports"][0]},
+ self.FAKE_CREATE_PORTS,
bytes_body,
201,
**self.FAKE_PORT1)
diff --git a/tempest/tests/lib/services/placement/test_resource_providers_client.py b/tempest/tests/lib/services/placement/test_resource_providers_client.py
index 11aeaf2..485f584 100644
--- a/tempest/tests/lib/services/placement/test_resource_providers_client.py
+++ b/tempest/tests/lib/services/placement/test_resource_providers_client.py
@@ -48,6 +48,43 @@
FAKE_RESOURCE_PROVIDER_AGGREGATES = {
'aggregates': [FAKE_AGGREGATE_UUID]
}
+ FAKE_RESOURCE_UPDATE_INVENTORIES_RESPONSE = {
+ "inventories": {
+ "MEMORY_MB": {
+ "allocation_ratio": 2.0,
+ "max_unit": 16,
+ "min_unit": 1,
+ "reserved": 0,
+ "step_size": 4,
+ "total": 128
+ },
+ "VCPU": {
+ "allocation_ratio": 10.0,
+ "max_unit": 2147483647,
+ "min_unit": 1,
+ "reserved": 2,
+ "step_size": 1,
+ "total": 64
+ }
+ },
+ "resource_provider_generation": 2
+ }
+ FAKE_RESOURCE_UPDATE_INVENTORIES_REQUEST = {
+ "inventories": {
+ "MEMORY_MB": {
+ "allocation_ratio": 2.0,
+ "max_unit": 16,
+ "step_size": 4,
+ "total": 128
+ },
+ "VCPU": {
+ "allocation_ratio": 10.0,
+ "reserved": 2,
+ "total": 64
+ }
+ },
+ "resource_provider_generation": 1
+ }
def setUp(self):
super(TestResourceProvidersClient, self).setUp()
@@ -102,6 +139,32 @@
def test_list_resource_provider_inventories_with_bytes_body(self):
self._test_list_resource_provider_inventories(bytes_body=True)
+ def _test_update_resource_providers_inventories(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_resource_providers_inventories,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_RESOURCE_UPDATE_INVENTORIES_RESPONSE,
+ to_utf=bytes_body,
+ status=200,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID,
+ **self.FAKE_RESOURCE_UPDATE_INVENTORIES_REQUEST
+ )
+
+ def test_update_resource_providers_inventories_with_str_body(self):
+ self._test_update_resource_providers_inventories()
+
+ def test_update_resource_providers_inventories_with_bytes_body(self):
+ self._test_update_resource_providers_inventories(bytes_body=True)
+
+ def test_delete_resource_providers_inventories(self):
+ self.check_service_client_function(
+ self.client.delete_resource_providers_inventories,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID,
+ )
+
def _test_list_resource_provider_aggregates(self, bytes_body=False):
self.check_service_client_function(
self.client.list_resource_provider_aggregates,
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index e3c17e8..fc93f76 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -16,7 +16,6 @@
import abc
from unittest import mock
-import six
import testtools
from tempest.lib import base as test
@@ -69,8 +68,7 @@
condition=True)
-@six.add_metaclass(abc.ABCMeta)
-class BaseSkipDecoratorTests(object):
+class BaseSkipDecoratorTests(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
index 85048fb..035bdb0 100644
--- a/tempest/tests/lib/test_ssh.py
+++ b/tempest/tests/lib/test_ssh.py
@@ -12,11 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+from io import StringIO
import socket
from unittest import mock
import six
-from six import StringIO
import testtools
from tempest.lib.common import ssh
@@ -30,7 +30,7 @@
SELECT_POLLIN = 1
@mock.patch('paramiko.RSAKey.from_private_key')
- @mock.patch('six.StringIO')
+ @mock.patch('io.StringIO')
def test_pkey_calls_paramiko_RSAKey(self, cs_mock, rsa_mock):
cs_mock.return_value = mock.sentinel.csio
pkey = 'mykey'
@@ -274,7 +274,7 @@
client = ssh.Client('localhost', 'root', timeout=2)
exc = self.assertRaises(exceptions.SSHExecCommandFailed,
client.exec_command, "test")
- self.assertIn('R' + self._utf8_string, six.text_type(exc))
+ self.assertIn('R' + self._utf8_string, str(exc))
def test_exec_command_no_select(self):
gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
diff --git a/tempest/tests/test_base_test.py b/tempest/tests/test_base_test.py
index b154cd5..88c28bf 100644
--- a/tempest/tests/test_base_test.py
+++ b/tempest/tests/test_base_test.py
@@ -109,7 +109,7 @@
test.BaseTestCase.get_tenant_network(credentials_type=creds)
- mock_gcm.assert_called_once_with(roles=['role1'])
+ mock_gcm.assert_called_once_with(roles=['role1'], scope='project')
mock_gprov.assert_called_once_with()
mock_gtn.assert_called_once_with(mock_prov, net_client,
self.fixed_network_name)
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 6018441..1889420 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -19,7 +19,6 @@
from tempest.common import utils
from tempest import config
from tempest import exceptions
-from tempest.lib.common.utils import data_utils
from tempest import test
from tempest.tests import base
from tempest.tests import fake_config
@@ -33,47 +32,6 @@
fake_config.FakePrivate)
-# NOTE: The test module is for tempest.test.idempotent_id.
-# After all projects switch to use decorators.idempotent_id,
-# we can remove tempest.test.idempotent_id as well as this
-# test module
-class TestIdempotentIdDecorator(BaseDecoratorsTest):
-
- def _test_helper(self, _id, **decorator_args):
- @test.idempotent_id(_id)
- def foo():
- """Docstring"""
- pass
-
- return foo
-
- def _test_helper_without_doc(self, _id, **decorator_args):
- @test.idempotent_id(_id)
- def foo():
- pass
-
- return foo
-
- def test_positive(self):
- _id = data_utils.rand_uuid()
- foo = self._test_helper(_id)
- self.assertIn('id-%s' % _id, getattr(foo, '__testtools_attrs'))
- self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
-
- def test_positive_without_doc(self):
- _id = data_utils.rand_uuid()
- foo = self._test_helper_without_doc(_id)
- self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
-
- def test_idempotent_id_not_str(self):
- _id = 42
- self.assertRaises(TypeError, self._test_helper, _id)
-
- def test_idempotent_id_not_valid_uuid(self):
- _id = '42'
- self.assertRaises(ValueError, self._test_helper, _id)
-
-
class TestServicesDecorator(BaseDecoratorsTest):
def _test_services_helper(self, *decorator_args):
class TestFoo(test.BaseTestCase):
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
index 1cc9c9a..fe44ef6 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -16,8 +16,6 @@
import re
import subprocess
-import six
-
from tempest.tests import base
@@ -32,7 +30,7 @@
self.assertEqual(0, p.returncode,
"test discovery failed, one or more files cause an "
"error on import %s" % ids)
- ids = six.text_type(ids).split('\n')
+ ids = str(ids).split('\n')
for test_id in ids:
if re.match(r'(\w+\.){3}\w+', test_id):
if not test_id.startswith('tempest.'):
diff --git a/tempest/tests/test_microversions.py b/tempest/tests/test_microversions.py
index ee6db71..835f51c 100644
--- a/tempest/tests/test_microversions.py
+++ b/tempest/tests/test_microversions.py
@@ -13,7 +13,6 @@
# under the License.
from oslo_config import cfg
-import six
import testtools
from tempest.api.compute import base as compute_base
@@ -75,7 +74,7 @@
self.assertRaises(testtools.TestCase.skipException,
test_class.skip_checks)
except testtools.TestCase.skipException as e:
- raise testtools.TestCase.failureException(six.text_type(e))
+ raise testtools.TestCase.failureException(str(e))
def test_config_version_none_none(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2]
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 72e8b6d..9aeedb3 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -453,6 +453,130 @@
expected_creds[1][1:],
mock_get_client_manager.mock_calls[1][2]['roles'])
+ def test_setup_credentials_with_role_and_system_scope(self):
+ expected_creds = [['system_my_role', 'role1', 'role2']]
+
+ class SystemRoleCredentials(self.parent_test):
+ credentials = expected_creds
+
+ expected_clients = 'clients'
+ with mock.patch.object(
+ SystemRoleCredentials,
+ 'get_client_manager') as mock_get_client_manager:
+ mock_get_client_manager.return_value = expected_clients
+ sys_creds = SystemRoleCredentials()
+ sys_creds.setup_credentials()
+ self.assertTrue(hasattr(sys_creds, 'os_system_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_system_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role)
+ self.assertEqual(1, mock_get_client_manager.call_count)
+ self.assertEqual(
+ expected_creds[0][1:],
+ mock_get_client_manager.mock_calls[0][2]['roles'])
+ self.assertEqual(
+ 'system',
+ mock_get_client_manager.mock_calls[0][2]['scope'])
+
+ def test_setup_credentials_with_multiple_role_and_system_scope(self):
+ expected_creds = [['system_my_role', 'role1', 'role2'],
+ ['system_my_role2', 'role1', 'role2'],
+ ['system_my_role3', 'role3']]
+
+ class SystemRoleCredentials(self.parent_test):
+ credentials = expected_creds
+
+ expected_clients = 'clients'
+ with mock.patch.object(
+ SystemRoleCredentials,
+ 'get_client_manager') as mock_get_client_manager:
+ mock_get_client_manager.return_value = expected_clients
+ sys_creds = SystemRoleCredentials()
+ sys_creds.setup_credentials()
+ self.assertTrue(hasattr(sys_creds, 'os_system_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_system_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_system_my_role2'))
+ self.assertEqual(expected_clients, sys_creds.os_system_my_role2)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role2'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role2)
+ self.assertTrue(hasattr(sys_creds, 'os_system_my_role3'))
+ self.assertEqual(expected_clients, sys_creds.os_system_my_role3)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role3'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role3)
+ self.assertEqual(3, mock_get_client_manager.call_count)
+ self.assertEqual(
+ expected_creds[0][1:],
+ mock_get_client_manager.mock_calls[0][2]['roles'])
+ self.assertEqual(
+ 'system', mock_get_client_manager.mock_calls[0][2]['scope'])
+ self.assertEqual(
+ expected_creds[1][1:],
+ mock_get_client_manager.mock_calls[1][2]['roles'])
+ self.assertEqual(
+ 'system', mock_get_client_manager.mock_calls[1][2]['scope'])
+ self.assertEqual(
+ expected_creds[2][1:],
+ mock_get_client_manager.mock_calls[2][2]['roles'])
+ self.assertEqual(
+ 'system', mock_get_client_manager.mock_calls[2][2]['scope'])
+
+ def test_setup_credentials_with_role_and_multiple_scope(self):
+ expected_creds = [['my_role', 'role1', 'role2'],
+ ['project_my_role', 'role1', 'role2'],
+ ['domain_my_role', 'role1', 'role2'],
+ ['system_my_role', 'role1', 'role2']]
+
+ class SystemRoleCredentials(self.parent_test):
+ credentials = expected_creds
+
+ expected_clients = 'clients'
+ with mock.patch.object(
+ SystemRoleCredentials,
+ 'get_client_manager') as mock_get_client_manager:
+ mock_get_client_manager.return_value = expected_clients
+ sys_creds = SystemRoleCredentials()
+ sys_creds.setup_credentials()
+ self.assertTrue(hasattr(sys_creds, 'os_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_project_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_project_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_project_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_project_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_domain_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_domain_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_domain_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_domain_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_system_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_system_my_role)
+ self.assertTrue(hasattr(sys_creds, 'os_roles_system_my_role'))
+ self.assertEqual(expected_clients, sys_creds.os_roles_system_my_role)
+
+ self.assertEqual(4, mock_get_client_manager.call_count)
+ self.assertEqual(
+ expected_creds[0][1:],
+ mock_get_client_manager.mock_calls[0][2]['roles'])
+ self.assertEqual(
+ 'project', mock_get_client_manager.mock_calls[0][2]['scope'])
+ self.assertEqual(
+ expected_creds[1][1:],
+ mock_get_client_manager.mock_calls[1][2]['roles'])
+ self.assertEqual(
+ 'project', mock_get_client_manager.mock_calls[1][2]['scope'])
+ self.assertEqual(
+ expected_creds[2][1:],
+ mock_get_client_manager.mock_calls[2][2]['roles'])
+ self.assertEqual(
+ 'domain', mock_get_client_manager.mock_calls[2][2]['scope'])
+ self.assertEqual(
+ expected_creds[3][1:],
+ mock_get_client_manager.mock_calls[3][2]['roles'])
+ self.assertEqual(
+ 'system', mock_get_client_manager.mock_calls[3][2]['scope'])
+
def test_setup_class_overwritten(self):
class OverridesSetup(self.parent_test):
diff --git a/tools/check_logs.py b/tools/check_logs.py
index de7e41d..cc74b17 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -17,12 +17,13 @@
import argparse
import gzip
+import io
import os
import re
import sys
+import urllib.request as urlreq
import six
-import six.moves.urllib.request as urlreq
import yaml
# DEVSTACK_GATE_GRENADE is either unset if grenade is not running
@@ -56,39 +57,39 @@
's-proxy'])
-def process_files(file_specs, url_specs, whitelists):
+def process_files(file_specs, url_specs, allow_lists):
regexp = re.compile(r"^.* (ERROR|CRITICAL|TRACE) .*\[.*\-.*\]")
logs_with_errors = []
for (name, filename) in file_specs:
- whitelist = whitelists.get(name, [])
+ allow_list = allow_lists.get(name, [])
with open(filename) as content:
- if scan_content(content, regexp, whitelist):
+ if scan_content(content, regexp, allow_list):
logs_with_errors.append(name)
for (name, url) in url_specs:
- whitelist = whitelists.get(name, [])
+ allow_list = allow_lists.get(name, [])
req = urlreq.Request(url)
req.add_header('Accept-Encoding', 'gzip')
page = urlreq.urlopen(req)
- buf = six.StringIO(page.read())
+ buf = io.StringIO(page.read())
f = gzip.GzipFile(fileobj=buf)
- if scan_content(f.read().splitlines(), regexp, whitelist):
+ if scan_content(f.read().splitlines(), regexp, allow_list):
logs_with_errors.append(name)
return logs_with_errors
-def scan_content(content, regexp, whitelist):
+def scan_content(content, regexp, allow_list):
had_errors = False
for line in content:
if not line.startswith("Stderr:") and regexp.match(line):
- whitelisted = False
- for w in whitelist:
+ allowed = False
+ for w in allow_list:
pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'),
w['message'])
if re.match(pat, line):
- whitelisted = True
+ allowed = True
break
- if not whitelisted or dump_all_errors:
- if not whitelisted:
+ if not allowed or dump_all_errors:
+ if not allowed:
had_errors = True
return had_errors
@@ -105,9 +106,9 @@
print("Must provide exactly one of -d or -u")
return 1
print("Checking logs...")
- WHITELIST_FILE = os.path.join(
+ ALLOW_LIST_FILE = os.path.join(
os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
- "etc", "whitelist.yaml")
+ "etc", "allow-list.yaml")
file_matcher = re.compile(r".*screen-([\w-]+)\.log")
files = []
@@ -132,17 +133,17 @@
if m:
urls_to_process.append((m.group(1), u))
- whitelists = {}
- with open(WHITELIST_FILE) as stream:
+ allow_lists = {}
+ with open(ALLOW_LIST_FILE) as stream:
loaded = yaml.safe_load(stream)
if loaded:
for (name, l) in six.iteritems(loaded):
for w in l:
assert 'module' in w, 'no module in %s' % name
assert 'message' in w, 'no message in %s' % name
- whitelists = loaded
+ allow_lists = loaded
logs_with_errors = process_files(files_to_process, urls_to_process,
- whitelists)
+ allow_lists)
failed = False
if logs_with_errors:
@@ -164,14 +165,14 @@
usage = """
-Find non-white-listed log errors in log files from a devstack-gate run.
+Find non-allow-listed log errors in log files from a devstack-gate run.
Log files will be searched for ERROR or CRITICAL messages. If any
-error messages do not match any of the whitelist entries contained in
-etc/whitelist.yaml, those messages will be printed to the console and
+error messages do not match any of the allow-list entries contained in
+etc/allow-list.yaml, those messages will be printed to the console and
failure will be returned. A file directory containing logs or a url to the
log files of an OpenStack gate job can be provided.
-The whitelist yaml looks like:
+The allow-list yaml looks like:
log-name:
- module: "a.b.c"
@@ -179,7 +180,7 @@
- module: "a.b.c"
message: "regexp"
-repeated for each log file with a whitelist.
+repeated for each log file with an allow-list.
"""
parser = argparse.ArgumentParser(description=usage)
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 618c388..1b5b369 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -32,9 +32,9 @@
# 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 = [
+# TODO(masayukig): Some of these can be removed from NON_ACTIVE_LIST in the
+# future when the patches are merged.
+NON_ACTIVE_LIST = [
'x/gce-api', # It looks gce-api doesn't support python3 yet.
'x/glare', # To avoid sanity-job failure
'x/group-based-policy', # It looks this doesn't support python3 yet.
@@ -52,8 +52,11 @@
'x/tap-as-a-service', # To avoid sanity-job failure
'x/valet', # https://review.opendev.org/#/c/638339/
'x/kingbird', # https://bugs.launchpad.net/kingbird/+bug/1869722
- # vmware-nsx is blacklisted since https://review.opendev.org/#/c/736952
+ # vmware-nsx is excluded since https://review.opendev.org/#/c/736952
'x/vmware-nsx-tempest-plugin',
+ # mogan is unmaintained now, remove from the list when this is merged:
+ # https://review.opendev.org/c/x/mogan/+/767718
+ 'x/mogan',
]
url = 'https://review.opendev.org/projects/'
@@ -86,10 +89,10 @@
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.
+if len(sys.argv) > 1 and sys.argv[1] == 'nonactivelist':
+ for non_active_plugin in NON_ACTIVE_LIST:
+ print(non_active_plugin)
+ # We just need NON_ACTIVE_LIST when we use this `nonactivelist` option.
# So, this exits here.
sys.exit()
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index 33675ed..4430bbf 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -81,17 +81,17 @@
printf "\n\n"
-# Print BLACKLIST
-if [[ -r doc/source/data/tempest-blacklisted-plugins-registry.header ]]; then
- cat doc/source/data/tempest-blacklisted-plugins-registry.header
+# Print NON_ACTIVE_LIST
+if [[ -r doc/source/data/tempest-non-active-plugins-registry.header ]]; then
+ cat doc/source/data/tempest-non-active-plugins-registry.header
fi
-blacklist=$(python tools/generate-tempest-plugins-list.py blacklist)
-name_col_len=$(echo "${blacklist}" | wc -L)
+nonactivelist=$(python tools/generate-tempest-plugins-list.py nonactivelist)
+name_col_len=$(echo "${nonactivelist}" | wc -L)
name_col_len=$(( name_col_len + 20 ))
printf "\n\n"
-print_plugin_table "${blacklist}"
+print_plugin_table "${nonactivelist}"
printf "\n\n"
diff --git a/tools/tempest-integrated-gate-compute-blacklist.txt b/tools/tempest-integrated-gate-compute-exclude-list.txt
similarity index 60%
rename from tools/tempest-integrated-gate-compute-blacklist.txt
rename to tools/tempest-integrated-gate-compute-exclude-list.txt
index 2290751..8805262 100644
--- a/tools/tempest-integrated-gate-compute-blacklist.txt
+++ b/tools/tempest-integrated-gate-compute-exclude-list.txt
@@ -11,9 +11,3 @@
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_backup_restore.TestVolumeBackupRestore.test_volume_backup_restore
-
-# Skip test scenario when creating second image from instance
-# https://bugs.launchpad.net/tripleo/+bug/1881592
-# The test is most likely wrong and may fail if the fists image is create quickly.
-# FIXME: Either fix the test so it won't race or consider if we should cover the scenario at all.
-tempest.api.compute.images.test_images_oneserver_negative.ImagesOneServerNegativeTestJSON.test_create_second_image_when_first_image_is_being_saved
diff --git a/tools/tempest-integrated-gate-networking-blacklist.txt b/tools/tempest-integrated-gate-networking-exclude-list.txt
similarity index 100%
rename from tools/tempest-integrated-gate-networking-blacklist.txt
rename to tools/tempest-integrated-gate-networking-exclude-list.txt
diff --git a/tools/tempest-integrated-gate-object-storage-blacklist.txt b/tools/tempest-integrated-gate-object-storage-exclude-list.txt
similarity index 100%
rename from tools/tempest-integrated-gate-object-storage-blacklist.txt
rename to tools/tempest-integrated-gate-object-storage-exclude-list.txt
diff --git a/tools/tempest-integrated-gate-placement-blacklist.txt b/tools/tempest-integrated-gate-placement-exclude-list.txt
similarity index 100%
rename from tools/tempest-integrated-gate-placement-blacklist.txt
rename to tools/tempest-integrated-gate-placement-exclude-list.txt
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
new file mode 120000
index 0000000..2d691f8
--- /dev/null
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -0,0 +1 @@
+tempest-integrated-gate-storage-exclude-list.txt
\ No newline at end of file
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-exclude-list.txt
similarity index 100%
rename from tools/tempest-integrated-gate-storage-blacklist.txt
rename to tools/tempest-integrated-gate-storage-exclude-list.txt
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index c983da9..106a9c6 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -44,7 +44,7 @@
# retrieve a list of projects having tempest plugins
PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
-BLACKLIST="$(python tools/generate-tempest-plugins-list.py blacklist)"
+NON_ACTIVE_LIST="$(python tools/generate-tempest-plugins-list.py nonactivelist)"
# Function to clone project using zuul-cloner or from git
function clone_project {
@@ -117,8 +117,8 @@
failed_plugin=''
# Perform sanity on all tempest plugin projects
for project in $PROJECT_LIST; do
- # Remove blacklisted tempest plugins
- if ! [[ `echo $BLACKLIST | grep -c $project ` -gt 0 ]]; then
+ # Remove non-active tempest plugins
+ if ! [[ `echo $NON_ACTIVE_LIST | grep -c $project ` -gt 0 ]]; then
plugin_sanity_check $project && passed_plugin+=", $project" || \
failed_plugin+="$project, " > $SANITY_DIR/$project.txt
fi
diff --git a/tox.ini b/tox.ini
index d8e059a..2315163 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist = pep8,py36,py38,bashate,pip-check-reqs
-minversion = 3.1.1
+minversion = 3.18.0
skipsdist = True
ignore_basepython_conflict = True
@@ -26,7 +26,7 @@
passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
usedevelop = True
install_command = pip install {opts} {packages}
-whitelist_externals = *
+allowlist_externals = *
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/requirements.txt
@@ -108,7 +108,7 @@
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
-# FIXME: We can replace it with the `--black-regex` option to exclude tests now.
+# FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
commands =
find . -type f -name "*.pyc" -delete
tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
@@ -132,11 +132,11 @@
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:
+# tests listed in exclude-list 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}
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
[testenv:integrated-compute]
envdir = .tox/tempest
@@ -145,11 +145,11 @@
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:
+# tests listed in exclude-list file:
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-compute-blacklist.txt {posargs}
- tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-compute-blacklist.txt {posargs}
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
[testenv:integrated-placement]
envdir = .tox/tempest
@@ -158,11 +158,11 @@
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:
+# tests listed in exclude-list file:
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-placement-blacklist.txt {posargs}
- tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-placement-blacklist.txt {posargs}
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
[testenv:integrated-storage]
envdir = .tox/tempest
@@ -171,11 +171,11 @@
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:
+# tests listed in exclude-list 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}
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
[testenv:integrated-object-storage]
envdir = .tox/tempest
@@ -184,11 +184,11 @@
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:
+# tests listed in exclude-list file:
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-object-storage-blacklist.txt {posargs}
- tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-object-storage-blacklist.txt {posargs}
+ tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
+ tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
[testenv:full-serial]
envdir = .tox/tempest
@@ -198,7 +198,7 @@
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
-# FIXME: We can replace it with the `--black-regex` option to exclude tests now.
+# FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
commands =
find . -type f -name "*.pyc" -delete
tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs}
@@ -290,12 +290,12 @@
sphinx-apidoc -f -o doc/source/tests/volume tempest/api/volume
rm -rf doc/build
sphinx-build -W -b html doc/source doc/build/html
-whitelist_externals =
+allowlist_externals =
rm
[testenv:pdf-docs]
deps = {[testenv:docs]deps}
-whitelist_externals =
+allowlist_externals =
rm
make
commands =
@@ -369,7 +369,7 @@
rm -rf releasenotes/build
sphinx-build -a -E -W -d releasenotes/build/doctrees \
-b html releasenotes/source releasenotes/build/html
-whitelist_externals = rm
+allowlist_externals = rm
[testenv:bashate]
# if you want to test out some changes you have made to bashate
@@ -377,7 +377,7 @@
# modified bashate tree
deps =
{env:BASHATE_INSTALL_PATH:bashate}
-whitelist_externals = bash
+allowlist_externals = bash
commands = bash -c "find {toxinidir}/tools \
-not \( -type d -name .?\* -prune \) \
-type f \
@@ -406,6 +406,6 @@
[testenv:plugin-sanity-check]
# perform tempest plugin sanity
-whitelist_externals = bash
+allowlist_externals = bash
commands =
bash tools/tempest-plugin-sanity.sh
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 27bbf64..4e62d67 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -11,8 +11,10 @@
vars:
tox_envlist: all
tempest_test_regex: tempest
- devstack_localrc:
- ENABLE_FILE_INJECTION: true
+ # TODO(gmann): Enable File injection tests once nova bug is fixed
+ # https://bugs.launchpad.net/nova/+bug/1882421
+ # devstack_localrc:
+ # ENABLE_FILE_INJECTION: true
- job:
name: tempest-ipv6-only
@@ -60,12 +62,16 @@
- job:
name: tempest-full-py3
parent: devstack-tempest
- # This currently works from stable/pike on.
- # Before stable/pike, legacy version of tempest-full
- # 'legacy-tempest-dsvm-neutron-full' run.
- branches: ^(?!stable/ocata).*$
+ # This job version is with swift disabled on py3
+ # as swift was not ready on py3 until stable/train.
+ branches:
+ - stable/pike
+ - stable/queens
+ - stable/rocky
+ - stable/stein
+ - stable/train
description: |
- Base integration test with Neutron networking and py3.
+ Base integration test with Neutron networking, swift disabled, and py3.
Former names for this job where:
* legacy-tempest-dsvm-py35
* gate-tempest-dsvm-py35
@@ -109,6 +115,25 @@
neutron-qos: true
- job:
+ name: tempest-full-py3
+ parent: devstack-tempest
+ # This job version is with swift enabled on py3
+ # as swift is ready on py3 from stable/ussuri onwards.
+ branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train)).*$
+ description: |
+ Base integration test with Neutron networking, swift enable, and py3.
+ Former names for this job where:
+ * legacy-tempest-dsvm-py35
+ * gate-tempest-dsvm-py35
+ vars:
+ tox_envlist: full
+ devstack_localrc:
+ USE_PYTHON3: true
+ FORCE_CONFIG_DRIVE: true
+ ENABLE_VOLUME_MULTIATTACH: true
+ GLANCE_USE_IMPORT_WORKFLOW: True
+
+- job:
name: tempest-integrated-networking
parent: devstack-tempest
branches: ^(?!stable/ocata).*$
@@ -281,6 +306,12 @@
subnode:
devstack_localrc:
USE_PYTHON3: true
+ devstack_local_conf:
+ post-config:
+ "/$NEUTRON_CORE_PLUGIN_CONF":
+ ovs:
+ bridge_mappings: public:br-ex
+ resource_provider_bandwidths: br-ex:1000000:1000000
- job:
name: tempest-slow
@@ -294,6 +325,8 @@
* legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend
* tempest-scenario-multinode-lvm-multibackend
timeout: 10800
+ # This job runs on stable/stein onwards.
+ branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
vars:
tox_envlist: slow-serial
devstack_localrc:
@@ -314,6 +347,46 @@
ENABLE_VOLUME_MULTIATTACH: true
- job:
+ name: tempest-slow
+ parent: tempest-multinode-full
+ description: |
+ This multinode integration job will run all the tests tagged as slow.
+ It enables the lvm multibackend setup to cover few scenario tests.
+ This job will run only slow tests (API or Scenario) serially.
+
+ Former names for this job were:
+ * legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend
+ * tempest-scenario-multinode-lvm-multibackend
+ timeout: 10800
+ branches:
+ - stable/pike
+ - stable/queens
+ - stable/rocky
+ vars:
+ tox_envlist: slow-serial
+ devstack_localrc:
+ CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
+ ENABLE_VOLUME_MULTIATTACH: true
+ # to avoid https://bugs.launchpad.net/neutron/+bug/1914037
+ # as we couldn't backport the fix to rocky and older releases
+ IPV6_PUBLIC_RANGE: 2001:db8:0:10::/64
+ IPV6_PUBLIC_NETWORK_GATEWAY: 2001:db8:0:10::2
+ IPV6_ROUTER_GW_IP: 2001:db8:0:10::1
+ devstack_plugins:
+ neutron: https://opendev.org/openstack/neutron
+ devstack_services:
+ neutron-placement: true
+ neutron-qos: true
+ tempest_concurrency: 2
+ group-vars:
+ # NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
+ # the controller and subnode prior to Rocky so we have to make sure the
+ # variable is set in both locations.
+ subnode:
+ devstack_localrc:
+ ENABLE_VOLUME_MULTIATTACH: true
+
+- job:
name: tempest-slow-py3
parent: tempest-slow
vars:
@@ -352,7 +425,9 @@
Former name for this job was legacy-tempest-dsvm-neutron-pg-full.
vars:
devstack_localrc:
- ENABLE_FILE_INJECTION: true
+ # TODO(gmann): Enable File injection tests once nova bug is fixed
+ # https://bugs.launchpad.net/nova/+bug/1882421
+ # ENABLE_FILE_INJECTION: true
DATABASE_TYPE: postgresql
- project-template:
@@ -379,11 +454,9 @@
run on Nova gate only.
check:
jobs:
- - grenade
- tempest-integrated-compute
gate:
jobs:
- - grenade
- tempest-integrated-compute
- project-template:
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 5dcd27f..d5b2787 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -8,17 +8,6 @@
- release-notes-jobs-python3
check:
jobs:
- - devstack-tempest:
- files:
- - ^playbooks/
- - ^roles/
- - ^.zuul.yaml$
- - devstack-tempest-ipv6:
- voting: false
- files:
- - ^playbooks/
- - ^roles/
- - ^.zuul.yaml$
- tempest-full-parallel:
# Define list of irrelevant files to use everywhere else
irrelevant-files: &tempest-irrelevant-files
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index 387a94b..fd348cc 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -46,6 +46,8 @@
tox_envlist: full-parallel
run_tempest_cleanup: true
run_tempest_dry_cleanup: true
+ devstack_localrc:
+ DEVSTACK_PARALLEL: True
- job:
name: tempest-full-py3-ipv6