Merge "Remove deprecated TYPE in rest_client"
diff --git a/bindep.txt b/bindep.txt
index 6a28348..8914ade 100644
--- a/bindep.txt
+++ b/bindep.txt
@@ -7,5 +7,7 @@
gcc [platform:dpkg]
python-dev [platform:dpkg]
python-devel [platform:rpm]
+python3-dev [platform:dpkg]
+python3-devel [platform:rpm]
openssl-devel [platform:rpm]
libssl-dev [platform:dpkg]
diff --git a/releasenotes/notes/add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml b/releasenotes/notes/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
similarity index 100%
rename from releasenotes/notes/add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
rename to releasenotes/notes/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml
diff --git a/releasenotes/notes/add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml b/releasenotes/notes/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml
similarity index 100%
rename from releasenotes/notes/add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml
rename to releasenotes/notes/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml
diff --git a/releasenotes/notes/add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml b/releasenotes/notes/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml
similarity index 100%
rename from releasenotes/notes/add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml
rename to releasenotes/notes/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml
diff --git a/releasenotes/notes/add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml b/releasenotes/notes/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml
similarity index 100%
rename from releasenotes/notes/add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml
rename to releasenotes/notes/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml
diff --git a/releasenotes/notes/add-content-type-without-spaces-b2c9b91b257814f3.yaml b/releasenotes/notes/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
similarity index 100%
rename from releasenotes/notes/add-content-type-without-spaces-b2c9b91b257814f3.yaml
rename to releasenotes/notes/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml
diff --git a/releasenotes/notes/add-list-auth-project-client-5905076d914a3943.yaml b/releasenotes/notes/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml
similarity index 100%
rename from releasenotes/notes/add-list-auth-project-client-5905076d914a3943.yaml
rename to releasenotes/notes/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml
diff --git a/releasenotes/notes/add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml b/releasenotes/notes/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml
similarity index 100%
rename from releasenotes/notes/add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml
rename to releasenotes/notes/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml
diff --git a/releasenotes/notes/add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml b/releasenotes/notes/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml
similarity index 100%
rename from releasenotes/notes/add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml
rename to releasenotes/notes/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml
diff --git a/releasenotes/notes/add-list-version-to-identity-client-944cb7396088a575.yaml b/releasenotes/notes/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml
similarity index 100%
rename from releasenotes/notes/add-list-version-to-identity-client-944cb7396088a575.yaml
rename to releasenotes/notes/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml
diff --git a/releasenotes/notes/add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml b/releasenotes/notes/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml
similarity index 100%
rename from releasenotes/notes/add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml
rename to releasenotes/notes/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml
diff --git a/releasenotes/notes/add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml b/releasenotes/notes/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml
similarity index 100%
rename from releasenotes/notes/add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml
rename to releasenotes/notes/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml
diff --git a/releasenotes/notes/add-tempest-lib-remote-client-adbeb3f42a36910b.yaml b/releasenotes/notes/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml
similarity index 100%
rename from releasenotes/notes/add-tempest-lib-remote-client-adbeb3f42a36910b.yaml
rename to releasenotes/notes/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml
diff --git a/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml b/releasenotes/notes/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
similarity index 100%
rename from releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml
rename to releasenotes/notes/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml
diff --git a/releasenotes/notes/add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml b/releasenotes/notes/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
similarity index 100%
rename from releasenotes/notes/add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
rename to releasenotes/notes/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
diff --git a/releasenotes/notes/add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml b/releasenotes/notes/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
similarity index 100%
rename from releasenotes/notes/add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
rename to releasenotes/notes/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
diff --git a/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml b/releasenotes/notes/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
similarity index 100%
rename from releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml
rename to releasenotes/notes/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml
diff --git a/releasenotes/notes/deprecate-deactivate_image-config-7a282c471937bbcb.yaml b/releasenotes/notes/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-deactivate_image-config-7a282c471937bbcb.yaml
rename to releasenotes/notes/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml
diff --git a/releasenotes/notes/deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml b/releasenotes/notes/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml
rename to releasenotes/notes/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml
diff --git a/releasenotes/notes/deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml b/releasenotes/notes/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml
rename to releasenotes/notes/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml
diff --git a/releasenotes/notes/deprecate-resources-prefix-option-ad490c0a30a0266b.yaml b/releasenotes/notes/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
rename to releasenotes/notes/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
diff --git a/releasenotes/notes/deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml b/releasenotes/notes/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
rename to releasenotes/notes/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
diff --git a/releasenotes/notes/deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml b/releasenotes/notes/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml
rename to releasenotes/notes/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml
diff --git a/releasenotes/notes/deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml b/releasenotes/notes/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml
similarity index 100%
rename from releasenotes/notes/deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml
rename to releasenotes/notes/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml
diff --git a/releasenotes/notes/dreprecate_client_parameters-cb8d069e62957f7e.yaml b/releasenotes/notes/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml
similarity index 100%
rename from releasenotes/notes/dreprecate_client_parameters-cb8d069e62957f7e.yaml
rename to releasenotes/notes/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml
diff --git a/releasenotes/notes/fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml b/releasenotes/notes/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml
similarity index 100%
rename from releasenotes/notes/fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml
rename to releasenotes/notes/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml
diff --git a/releasenotes/notes/remove-call_until_true-of-test-de9c13bc8f969921.yaml b/releasenotes/notes/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml
similarity index 100%
rename from releasenotes/notes/remove-call_until_true-of-test-de9c13bc8f969921.yaml
rename to releasenotes/notes/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml
diff --git a/releasenotes/notes/remove-cinder-v1-api-tests-71e266b8d55d475f.yaml b/releasenotes/notes/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml
similarity index 100%
rename from releasenotes/notes/remove-cinder-v1-api-tests-71e266b8d55d475f.yaml
rename to releasenotes/notes/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml
diff --git a/releasenotes/notes/remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml b/releasenotes/notes/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
similarity index 100%
rename from releasenotes/notes/remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
rename to releasenotes/notes/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml
diff --git a/releasenotes/notes/remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml b/releasenotes/notes/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
similarity index 100%
rename from releasenotes/notes/remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
rename to releasenotes/notes/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml
diff --git a/releasenotes/notes/remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml b/releasenotes/notes/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml
similarity index 100%
rename from releasenotes/notes/remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml
rename to releasenotes/notes/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml
diff --git a/releasenotes/notes/remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml b/releasenotes/notes/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml
similarity index 100%
rename from releasenotes/notes/remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml
rename to releasenotes/notes/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml
diff --git a/releasenotes/notes/remove-sahara-service-available-44a642aa9c634ab4.yaml b/releasenotes/notes/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml
similarity index 100%
rename from releasenotes/notes/remove-sahara-service-available-44a642aa9c634ab4.yaml
rename to releasenotes/notes/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml
diff --git a/releasenotes/notes/remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml b/releasenotes/notes/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml
similarity index 100%
rename from releasenotes/notes/remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml
rename to releasenotes/notes/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml
diff --git a/releasenotes/notes/use-keystone-v3-api-935860d30ddbb8e9.yaml b/releasenotes/notes/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml
similarity index 100%
rename from releasenotes/notes/use-keystone-v3-api-935860d30ddbb8e9.yaml
rename to releasenotes/notes/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml
diff --git a/releasenotes/notes/volume-transfers-client-e5ed3f5464c0cdc0.yaml b/releasenotes/notes/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml
similarity index 100%
rename from releasenotes/notes/volume-transfers-client-e5ed3f5464c0cdc0.yaml
rename to releasenotes/notes/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml
diff --git a/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml b/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml
new file mode 100644
index 0000000..18fd5ad
--- /dev/null
+++ b/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new boolean config option ``serial_console`` is added to the section
+ ``compute-feature-enabled``. If enabled, tests, which validate the
+ behavior of Nova's *serial console* feature (an alternative to VNC,
+ RDP, SPICE) can be executed.
diff --git a/releasenotes/notes/add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yaml b/releasenotes/notes/add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yaml
new file mode 100644
index 0000000..5653681
--- /dev/null
+++ b/releasenotes/notes/add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add a new client to handle the domain configuration feature from the
+ identity v3 API.
diff --git a/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml b/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml
new file mode 100644
index 0000000..a0156a0
--- /dev/null
+++ b/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add force detach volume feature API to v2 volumes_client library.
+ This feature enables the possibility to force a volume to detach, and
+ roll back an unsuccessful detach operation after you disconnect the volume.
diff --git a/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml b/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml
new file mode 100644
index 0000000..69320fb
--- /dev/null
+++ b/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Defines the identity v3 OS-EP-FILTER extension API client.
+ This client manages associations between endpoints, projects
+ along with groups.
diff --git a/releasenotes/notes/add-kwargs-to-delete-vol-of-vol-client-1ecde75beb62933c.yaml b/releasenotes/notes/add-kwargs-to-delete-vol-of-vol-client-1ecde75beb62933c.yaml
new file mode 100644
index 0000000..b8c9dfc
--- /dev/null
+++ b/releasenotes/notes/add-kwargs-to-delete-vol-of-vol-client-1ecde75beb62933c.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ The ``delete_volume`` method of the ``VolumesClient`` class
+ now has an additional ``**params`` argument that enables passing
+ additional information in the query string of the HTTP request.
+
diff --git a/releasenotes/notes/add-volume-backup-force-delete-af0156651a0cbf7f.yaml b/releasenotes/notes/add-volume-backup-force-delete-af0156651a0cbf7f.yaml
new file mode 100644
index 0000000..71bbfcb
--- /dev/null
+++ b/releasenotes/notes/add-volume-backup-force-delete-af0156651a0cbf7f.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ As in the [doc]:
+ https://developer.openstack.org/api-ref/block-storage/v3/
+ #force-delete-a-backup.
+
+ * Force-deletes a backup(v2)
+
diff --git a/releasenotes/notes/api_v2_admin_flag-dea5ca9bc2ce63bc.yaml b/releasenotes/notes/api_v2_admin_flag-dea5ca9bc2ce63bc.yaml
new file mode 100644
index 0000000..0c33b69
--- /dev/null
+++ b/releasenotes/notes/api_v2_admin_flag-dea5ca9bc2ce63bc.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new configuration flag api_v2_admin is introduced in the identity
+ feature flag group to allow for enabling/disabling all identity v2
+ admin tests. The new flag only applies when the existing api_v2 flag
+ is set to True
diff --git a/releasenotes/notes/deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.yaml b/releasenotes/notes/deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.yaml
new file mode 100644
index 0000000..dc4ed27
--- /dev/null
+++ b/releasenotes/notes/deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.yaml
@@ -0,0 +1,10 @@
+---
+deprecations:
+ - |
+ Image APIs in compute are deprecated, Image native APIs are recommended.
+ And Glance v1 APIs are deprecated and v2 APIs are current. Image client
+ compute_images_client and Glance v1 APIs are removed in volume tests.
+upgrade:
+ - |
+ Swith to use Glance v2 APIs in volume tests, by adding the Glance v2 client
+ images_client.
diff --git a/releasenotes/notes/deprecate-config-forbid_global_implied_dsr-e64cfa66e6e3ded5.yaml b/releasenotes/notes/deprecate-config-forbid_global_implied_dsr-e64cfa66e6e3ded5.yaml
new file mode 100644
index 0000000..2b63402
--- /dev/null
+++ b/releasenotes/notes/deprecate-config-forbid_global_implied_dsr-e64cfa66e6e3ded5.yaml
@@ -0,0 +1,5 @@
+---
+deprecations:
+ - The config option ``forbid_global_implied_dsr`` from the ``IdentityFeature``
+ group is now deprecated. This feature flag was introduced to support
+ testing of old OpenStack versions which are not supported anymore.
diff --git a/setup.cfg b/setup.cfg
index b2035bc..b292970 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -39,7 +39,11 @@
cleanup = tempest.cmd.cleanup:TempestCleanup
list-plugins = tempest.cmd.list_plugins:TempestListPlugins
verify-config = tempest.cmd.verify_tempest_config:TempestVerifyConfig
- workspace = tempest.cmd.workspace:TempestWorkspace
+ workspace_register = tempest.cmd.workspace:TempestWorkspaceRegister
+ workspace_rename = tempest.cmd.workspace:TempestWorkspaceRename
+ workspace_move = tempest.cmd.workspace:TempestWorkspaceMove
+ workspace_remove = tempest.cmd.workspace:TempestWorkspaceRemove
+ workspace_list = tempest.cmd.workspace:TempestWorkspaceList
run = tempest.cmd.run:TempestRun
oslo.config.opts =
tempest.config = tempest.config:list_opts
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 4d0f12a..8344103 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from oslo_log import log as logging
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@@ -175,6 +178,80 @@
self.assertEqual(volume_id1, volume_id2)
+class LiveBlockMigrationRemoteConsolesV26TestJson(LiveBlockMigrationTestJSON):
+ min_microversion = '2.6'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('6190af80-513e-4f0f-90f2-9714e84955d7')
+ @testtools.skipUnless(CONF.compute_feature_enabled.serial_console,
+ 'Serial console not supported.')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ 'DifferentHostFilter is not available.')
+ def test_live_migration_serial_console(self):
+ """Test the live-migration of an instance which has a serial console
+
+ The serial console feature of an instance uses ports on the host.
+ These ports need to be updated when they are already in use by
+ another instance on the target host. This test checks if this
+ update behavior is correctly done, by connecting to the serial
+ consoles of the instances before and after the live migration.
+ """
+ server01_id = self.create_test_server(wait_until='ACTIVE')['id']
+ hints = {'different_host': server01_id}
+ server02_id = self.create_test_server(scheduler_hints=hints,
+ wait_until='ACTIVE')['id']
+ host01_id = self._get_host_for_server(server01_id)
+ host02_id = self._get_host_for_server(server02_id)
+ self.assertNotEqual(host01_id, host02_id)
+
+ # At this step we have 2 instances on different hosts, both with
+ # serial consoles, both with port 10000 (the default value).
+ # https://bugs.launchpad.net/nova/+bug/1455252 describes the issue
+ # when live-migrating in such a scenario.
+
+ self._verify_console_interaction(server01_id)
+ self._verify_console_interaction(server02_id)
+
+ self._migrate_server_to(server01_id, host02_id)
+ waiters.wait_for_server_status(self.servers_client,
+ server01_id, 'ACTIVE')
+ self.assertEqual(host02_id, self._get_host_for_server(server01_id))
+ self._verify_console_interaction(server01_id)
+ # At this point, both instances have a valid serial console
+ # connection, which means the ports got updated.
+
+ def _verify_console_interaction(self, server_id):
+ body = self.servers_client.get_remote_console(server_id,
+ console_type='serial',
+ protocol='serial')
+ console_url = body['remote_console']['url']
+ data = "test_live_migration_serial_console"
+ console_output = ''
+ t = 0.0
+ interval = 0.1
+
+ ws = compute.create_websocket(console_url)
+ try:
+ # NOTE (markus_z): It can take a long time until the terminal
+ # of the instance is available for interaction. Hence the
+ # long timeout value.
+ while data not in console_output and t <= 120.0:
+ try:
+ ws.send_frame(data)
+ recieved = ws.receive_frame()
+ console_output += recieved
+ except Exception:
+ # In case we had an issue with send/receive on the
+ # websocket connection, we create a new one.
+ ws = compute.create_websocket(console_url)
+ time.sleep(interval)
+ t += interval
+ finally:
+ ws.close()
+ self.assertIn(data, console_output)
+
+
class LiveAutoBlockMigrationV225TestJSON(LiveBlockMigrationTestJSON):
min_microversion = '2.25'
max_microversion = 'latest'
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index b2c33e2..91e9684 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -82,9 +82,9 @@
self.assertEqual(min_img_ram, image['min_ram'])
# Try to create server with flavor of insufficient ram size
- self.assertRaisesRegexp(lib_exc.BadRequest,
- "Flavor's memory is too small for "
- "requested image",
- self.create_test_server,
- image_id=image['id'],
- flavor=flavor['id'])
+ self.assertRaisesRegex(lib_exc.BadRequest,
+ "Flavor's memory is too small for "
+ "requested image",
+ self.create_test_server,
+ image_id=image['id'],
+ flavor=flavor['id'])
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index cce9856..96983b0 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute.floating_ips import base
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -99,3 +101,27 @@
self.assertRaises((lib_exc.NotFound, lib_exc.BadRequest),
self.client.associate_floating_ip_to_server,
'', self.server_id)
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('58a80596-ffb2-11e6-9393-fa163e4fa634')
+ @test.services('network')
+ @testtools.skipUnless(CONF.network.public_network_id,
+ 'The public_network_id option must be specified.')
+ def test_associate_ip_to_server_with_floating_ip(self):
+ # The VM have one port
+ # Associate floating IP A to the VM
+ # Associate floating IP B which is from same pool with floating IP A
+ # to the VM, should raise BadRequest exception
+ body = self.client.create_floating_ip(
+ pool=CONF.network.public_network_id)['floating_ip']
+ self.addCleanup(self.client.delete_floating_ip, body['id'])
+ self.client.associate_floating_ip_to_server(body['ip'], self.server_id)
+ self.addCleanup(self.client.disassociate_floating_ip_from_server,
+ body['ip'], self.server_id)
+
+ body = self.client.create_floating_ip(
+ pool=CONF.network.public_network_id)['floating_ip']
+ self.addCleanup(self.client.delete_floating_ip, body['id'])
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.associate_floating_ip_to_server,
+ body['ip'], self.server_id)
diff --git a/tempest/api/identity/admin/v3/test_domain_configuration.py b/tempest/api/identity/admin/v3/test_domain_configuration.py
new file mode 100644
index 0000000..f731697
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_domain_configuration.py
@@ -0,0 +1,184 @@
+# Copyright 2017 AT&T Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+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
+
+
+class DomainConfigurationTestJSON(base.BaseIdentityV3AdminTest):
+
+ custom_config = {
+ "identity": {
+ "driver": "ldap"
+ },
+ "ldap": {
+ "url": "ldap://myldap.com:389/",
+ "user_tree_dn": "ou=Users,dc=my_new_root,dc=org"
+ }
+ }
+
+ @classmethod
+ def setup_clients(cls):
+ super(DomainConfigurationTestJSON, cls).setup_clients()
+ cls.client = cls.domain_config_client
+
+ @classmethod
+ def resource_setup(cls):
+ super(DomainConfigurationTestJSON, cls).resource_setup()
+ cls.group = cls.groups_client.create_group(
+ name=data_utils.rand_name('group'),
+ description=data_utils.rand_name('group-desc'))['group']
+
+ @classmethod
+ def resource_cleanup(cls):
+ cls.groups_client.delete_group(cls.group['id'])
+ super(DomainConfigurationTestJSON, cls).resource_cleanup()
+
+ def _create_domain_and_config(self, config):
+ domain = self.setup_test_domain()
+ config = self.client.create_domain_config(domain['id'], **config)[
+ 'config']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_domain_config, domain['id'])
+ return domain, config
+
+ @decorators.idempotent_id('11a02bf0-6f94-4380-b3b0-c8dc18fc0d22')
+ def test_show_default_group_config_and_options(self):
+ # The API supports only the identity and ldap groups. For the ldap
+ # group, a valid value is url or user_tree_dn. For the identity group,
+ # a valid value is driver.
+
+ # Check that the default config has the identity and ldap groups.
+ config = self.client.show_default_config_settings()['config']
+ self.assertIsInstance(config, dict)
+ self.assertIn('identity', config)
+ self.assertIn('ldap', config)
+
+ # Check that the identity group is correct.
+ identity_config = self.client.show_default_group_config('identity')[
+ 'config']
+
+ self.assertIsInstance(identity_config, dict)
+ self.assertIn('identity', identity_config)
+ self.assertIn('driver', identity_config['identity'])
+ self.assertIn('list_limit', identity_config['identity'])
+
+ # Show each option for the default domain and identity group.
+ for config_opt_name in ['driver', 'list_limit']:
+ retrieved_config_opt = self.client.show_default_group_option(
+ 'identity', config_opt_name)['config']
+ self.assertIn(config_opt_name, retrieved_config_opt)
+
+ # Check that the ldap group is correct.
+ ldap_config = self.client.show_default_group_config('ldap')['config']
+
+ self.assertIsInstance(ldap_config, dict)
+ self.assertIn('ldap', ldap_config)
+
+ # Several valid options exist for ldap group.
+ valid_options = ldap_config['ldap'].keys()
+
+ # Show each option for the default domain and ldap group.
+ for config_opt_name in valid_options:
+ retrieved_config_opt = self.client.show_default_group_option(
+ 'ldap', config_opt_name)['config']
+ self.assertIn(config_opt_name, retrieved_config_opt)
+
+ @decorators.idempotent_id('9e3ff13c-f597-4f01-9377-d6c06c2a1477')
+ def test_create_domain_config_and_show_config_groups_and_options(self):
+ domain, created_config = self._create_domain_and_config(
+ self.custom_config)
+
+ # Check that the entire configuration is correct.
+ self.assertEqual(self.custom_config, created_config)
+
+ # Check that each configuration group is correct.
+ for group_name in self.custom_config.keys():
+ group_cfg = self.client.show_domain_group_config(
+ domain['id'], group_name)['config']
+ self.assertIn(group_name, group_cfg)
+ self.assertEqual(self.custom_config[group_name],
+ group_cfg[group_name])
+
+ # Check that each configuration option is correct.
+ for opt_name in self.custom_config[group_name].keys():
+ group_opt = self.client.show_domain_group_option_config(
+ domain['id'], group_name, opt_name)['config']
+ self.assertIn(opt_name, group_opt)
+ self.assertEqual(self.custom_config[group_name][opt_name],
+ group_opt[opt_name])
+
+ @decorators.idempotent_id('7161023e-5dd0-4612-9da0-1bac6ac30b63')
+ def test_create_update_and_delete_domain_config(self):
+ domain, created_config = self._create_domain_and_config(
+ self.custom_config)
+
+ new_config = created_config
+ new_config['ldap']['url'] = data_utils.rand_url()
+
+ # Check that the altered configuration is reflected in updated_config.
+ updated_config = self.client.update_domain_config(
+ domain['id'], **new_config)['config']
+ self.assertEqual(new_config, updated_config)
+
+ # Check that showing the domain config shows the altered configuration.
+ retrieved_config = self.client.show_domain_config(domain['id'])[
+ 'config']
+ self.assertEqual(new_config, retrieved_config)
+
+ # Check that deleting a configuration works.
+ self.client.delete_domain_config(domain['id'])
+ self.assertRaises(lib_exc.NotFound, self.client.show_domain_config,
+ domain['id'])
+
+ @decorators.idempotent_id('c7510fa2-6661-4170-9c6b-4783a80651e9')
+ def test_create_update_and_delete_domain_config_groups_and_opts(self):
+ domain, _ = self._create_domain_and_config(self.custom_config)
+
+ # Check that updating configuration groups work.
+ new_driver = data_utils.rand_name('driver')
+ new_limit = data_utils.rand_int_id(0, 100)
+ new_group_config = {'identity': {'driver': new_driver,
+ 'list_limit': new_limit}}
+
+ updated_config = self.client.update_domain_group_config(
+ domain['id'], 'identity', **new_group_config)['config']
+
+ self.assertEqual(new_driver, updated_config['identity']['driver'])
+ self.assertEqual(new_limit, updated_config['identity']['list_limit'])
+
+ # Check that updating individual configuration group options work.
+ new_driver = data_utils.rand_name('driver')
+
+ updated_config = self.client.update_domain_group_option_config(
+ domain['id'], 'identity', 'driver', driver=new_driver)['config']
+
+ self.assertEqual(new_driver, updated_config['identity']['driver'])
+
+ # Check that deleting individual configuration group options work.
+ self.client.delete_domain_group_option_config(
+ domain['id'], 'identity', 'driver')
+ self.assertRaises(lib_exc.NotFound,
+ self.client.show_domain_group_option_config,
+ domain['id'], 'identity', 'driver')
+
+ # Check that deleting configuration groups work.
+ self.client.delete_domain_group_config(domain['id'], 'identity')
+ self.assertRaises(lib_exc.NotFound,
+ self.client.show_domain_group_config,
+ domain['id'], 'identity')
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 86a5764..9683e93 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -31,10 +31,7 @@
# One of those domains will be disabled
cls.setup_domains = list()
for i in range(3):
- domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('domain'),
- description=data_utils.rand_name('domain-desc'),
- enabled=i < 2)['domain']
+ domain = cls.create_domain(enabled=i < 2)
cls.setup_domains.append(domain)
@classmethod
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 61b4fa2..1a0b851 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -25,11 +25,7 @@
@decorators.attr(type=['negative', 'gate'])
@decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
def test_delete_active_domain(self):
- d_name = data_utils.rand_name('domain')
- d_desc = data_utils.rand_name('domain-desc')
- domain = self.domains_client.create_domain(
- name=d_name,
- description=d_desc)['domain']
+ domain = self.create_domain()
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index f630f74..49b6585 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -31,9 +31,7 @@
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_name('pass-')
- cls.domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('domain-'),
- description=data_utils.rand_name('domain-desc-'))['domain']
+ cls.domain = cls.create_domain()
cls.project = cls.projects_client.create_project(
data_utils.rand_name('project-'),
description=data_utils.rand_name('project-desc-'),
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index ac56fc6..adb467c 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -38,9 +38,7 @@
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
cls.u_password = data_utils.rand_password()
- cls.domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('domain'),
- description=data_utils.rand_name('domain-desc'))['domain']
+ cls.domain = cls.create_domain()
cls.project = cls.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'),
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 339b4bb..0a163fc 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -232,10 +232,12 @@
# For example, when creating a trust, we will set the expiry time of
# the trust to 2015-02-17T17:34:01.907051Z. However, if we make a GET
# request on the trust, the response will contain the time rounded up
- # to 2015-02-17T17:34:02.000000Z. That is why we shouldn't set flag
- # "subsecond" to True when we invoke timeutils.isotime(...) to avoid
- # problems with rounding.
- expires_str = timeutils.isotime(at=expires_at)
+ # to 2015-02-17T17:34:02.000000Z. That is why we set microsecond to
+ # 0 when we invoke isoformat to avoid problems with rounding.
+ expires_at = expires_at.replace(microsecond=0)
+ # NOTE(ekhugen) Python datetime does not support military timezones
+ # since we used UTC we'll add the Z so our compare works.
+ expires_str = expires_at.isoformat() + 'Z'
trust = self.create_trust(expires=expires_str)
self.validate_trust(trust, expires=expires_str)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index aff3238..10121d9 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -127,6 +127,12 @@
force_tenant_isolation = True
@classmethod
+ def skip_checks(cls):
+ super(BaseIdentityV2AdminTest, cls).skip_checks()
+ if not CONF.identity_feature_enabled.api_v2_admin:
+ raise cls.skipException('Identity v2 admin not available')
+
+ @classmethod
def setup_clients(cls):
super(BaseIdentityV2AdminTest, cls).setup_clients()
cls.client = cls.os_adm.identity_client
@@ -216,6 +222,8 @@
cls.projects_client = cls.os_adm.projects_client
cls.role_assignments = cls.os_admin.role_assignments_client
cls.oauth_consumers_client = cls.os_adm.oauth_consumers_client
+ cls.domain_config_client = cls.os_adm.domain_config_client
+ cls.endpoint_filter_client = cls.os_adm.endpoint_filter_client
if CONF.identity.admin_domain_scope:
# NOTE(andreaf) When keystone policy requires it, the identity
# admin clients for these tests shall use 'domain' scoped tokens.
@@ -229,11 +237,13 @@
cls.users_client.update_user(user['id'], name=user_name, enabled=False)
@classmethod
- def create_domain(cls):
+ def create_domain(cls, **kwargs):
"""Create a domain."""
- domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('test_domain'),
- description=data_utils.rand_name('desc'))['domain']
+ if 'name' not in kwargs:
+ kwargs['name'] = data_utils.rand_name('test_domain')
+ if 'description' not in kwargs:
+ kwargs['description'] = data_utils.rand_name('desc')
+ domain = cls.domains_client.create_domain(**kwargs)['domain']
return domain
def delete_domain(self, domain_id):
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 7f291e9..acff7cd 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -14,7 +14,12 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
from tempest.lib import decorators
+from tempest import test
+
+CONF = config.CONF
class VolumesActionsTest(base.BaseVolumeAdminTest):
@@ -60,3 +65,36 @@
def test_volume_force_delete_when_volume_is_maintenance(self):
# test force delete when status of volume is maintenance
self._create_reset_and_force_delete_temp_volume('maintenance')
+
+ @decorators.idempotent_id('d38285d9-929d-478f-96a5-00e66a115b81')
+ @test.services('compute')
+ def test_force_detach_volume(self):
+ # Create a server and a volume
+ server_id = self.create_server(wait_until='ACTIVE')['id']
+ volume_id = self.create_volume()['id']
+
+ # Attach volume
+ self.volumes_client.attach_volume(
+ volume_id,
+ instance_uuid=server_id,
+ mountpoint='/dev/%s' % CONF.compute.volume_device_name)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume_id, 'in-use')
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.volumes_client, volume_id, 'available')
+ self.addCleanup(self.volumes_client.detach_volume, volume_id)
+ attachment = self.volumes_client.show_volume(
+ volume_id)['volume']['attachments'][0]
+
+ # Reset volume's status to error
+ self.admin_volume_client.reset_volume_status(volume_id, status='error')
+
+ # Force detach volume
+ self.admin_volume_client.force_detach_volume(
+ volume_id, connector=None,
+ attachment_id=attachment['attachment_id'])
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume_id, 'available')
+ vol_info = self.volumes_client.show_volume(volume_id)['volume']
+ self.assertIn('attachments', vol_info)
+ self.assertEmpty(vol_info['attachments'])
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index afc3281..a6f9246 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -121,7 +121,7 @@
'available')
@decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
- def test_volume_backup_reset_status(self):
+ def test_volume_backup_reset_status_force_delete(self):
# Create a volume
volume = self.create_volume()
# Create a backup
@@ -136,3 +136,6 @@
status="error")
waiters.wait_for_volume_resource_status(self.admin_backups_client,
backup['id'], 'error')
+ # Force delete a backup volume when backup is in error state.
+ self.admin_backups_client.force_delete_backup(backup['id'])
+ self.admin_backups_client.wait_for_resource_deletion(backup['id'])
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 767b3c7..d8b503d 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -65,7 +65,9 @@
def setup_clients(cls):
super(BaseVolumeTest, cls).setup_clients()
cls.servers_client = cls.os.servers_client
- cls.compute_images_client = cls.os.compute_images_client
+
+ if CONF.service_available.glance:
+ cls.images_client = cls.os.image_client_v2
cls.snapshots_client = cls.os.snapshots_v2_client
cls.volumes_client = cls.os.volumes_v2_client
@@ -113,9 +115,8 @@
kwargs['size'] = CONF.volume.volume_size
if 'imageRef' in kwargs:
- image = cls.compute_images_client.show_image(
- kwargs['imageRef'])['image']
- min_disk = image.get('minDisk')
+ image = cls.images_client.show_image(kwargs['imageRef'])
+ min_disk = image['min_disk']
kwargs['size'] = max(kwargs['size'], min_disk)
if 'name' not in kwargs:
@@ -142,7 +143,8 @@
snapshot['id'], 'available')
return snapshot
- def create_backup(self, volume_id, backup_client=None, **kwargs):
+ def create_backup(self, volume_id, backup_client=None,
+ wait_until="available", **kwargs):
"""Wrapper utility that returns a test backup."""
if backup_client is None:
backup_client = self.backups_client
@@ -152,9 +154,12 @@
backup = backup_client.create_backup(
volume_id=volume_id, **kwargs)['backup']
- self.addCleanup(backup_client.delete_backup, backup['id'])
- waiters.wait_for_volume_resource_status(backup_client, backup['id'],
- 'available')
+
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ backup_client.delete_backup, backup['id'])
+ waiters.wait_for_volume_resource_status(backup_client,
+ backup['id'],
+ wait_until)
return backup
# NOTE(afazekas): these create_* and clean_* could be defined
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 1e05f22..2ed2a06 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -19,7 +19,6 @@
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
from tempest import test
CONF = config.CONF
@@ -28,20 +27,6 @@
class VolumesActionsTest(base.BaseVolumeTest):
@classmethod
- def setup_clients(cls):
- super(VolumesActionsTest, cls).setup_clients()
- if CONF.service_available.glance:
- # Check if glance v1 is available to determine which client to use.
- if CONF.image_feature_enabled.api_v1:
- cls.image_client = cls.os.image_client
- elif CONF.image_feature_enabled.api_v2:
- cls.image_client = cls.os.image_client_v2
- else:
- raise exceptions.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
-
- @classmethod
def resource_setup(cls):
super(VolumesActionsTest, cls).resource_setup()
@@ -121,9 +106,9 @@
disk_format=CONF.volume.disk_format)['os-volume_upload_image']
image_id = body["image_id"]
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.image_client.delete_image,
+ self.images_client.delete_image,
image_id)
- waiters.wait_for_image_status(self.image_client, image_id, 'active')
+ waiters.wait_for_image_status(self.images_client, image_id, 'active')
waiters.wait_for_volume_resource_status(self.volumes_client,
self.volume['id'], 'available')
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 1d9b846..712254e 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -124,9 +124,8 @@
@decorators.idempotent_id('54a01030-c7fc-447c-86ee-c1182beae638')
@test.services('image')
def test_volume_create_get_update_delete_from_image(self):
- image = self.compute_images_client.show_image(
- CONF.compute.image_ref)['image']
- min_disk = image.get('minDisk')
+ image = self.images_client.show_image(CONF.compute.image_ref)
+ min_disk = image['min_disk']
disk_size = max(min_disk, CONF.volume.volume_size)
self._volume_create_get_update_delete(
imageRef=CONF.compute.image_ref, size=disk_size)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 5ba72c3..0516c69 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -13,12 +13,19 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
+
from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest import test
+CONF = config.CONF
+
class VolumesNegativeTest(base.BaseVolumeTest):
@@ -263,3 +270,32 @@
self.volumes_client.list_volumes(detail=True,
params=params)['volumes']
self.assertEqual(0, len(fetched_volume))
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('5b810c91-0ad1-47ce-aee8-615f789be78f')
+ @test.services('image')
+ def test_create_volume_from_image_with_decreasing_size(self):
+ # Create image
+ image_name = data_utils.rand_name(self.__class__.__name__ + "-image")
+ image = self.images_client.create_image(
+ name=image_name,
+ container_format=CONF.image.container_formats[0],
+ disk_format=CONF.image.disk_formats[0],
+ visibility='private',
+ min_disk=CONF.volume.volume_size + 1)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.images_client.delete_image, image['id'])
+
+ # Upload image with 1KB data
+ image_file = six.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')
+
+ # Note(jeremyZ): To shorten the test time (uploading a big size image
+ # is time-consuming), here just consider the scenario that volume size
+ # is smaller than the min_disk of image.
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.create_volume,
+ size=CONF.volume.volume_size,
+ imageRef=image['id'])
diff --git a/tempest/clients.py b/tempest/clients.py
index dbaacda..e000a74 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -229,6 +229,10 @@
**params_v3)
self.oauth_consumers_client = self.identity_v3.OAUTHConsumerClient(
**params_v3)
+ self.domain_config_client = self.identity_v3.DomainConfigurationClient(
+ **params_v3)
+ self.endpoint_filter_client = \
+ self.identity_v3.EndPointsFilterClient(**params_v3)
# Token clients do not use the catalog. They only need default_params.
# They read auth_url, so they should only be set if the corresponding
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index d2dc00d..96d2300 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -52,8 +52,8 @@
import sys
from cliff import command
+from cliff import lister
from oslo_concurrency import lockutils
-import prettytable
import yaml
from tempest import config
@@ -154,76 +154,97 @@
self.workspaces = yaml.safe_load(f) or {}
-class TempestWorkspace(command.Command):
- def take_action(self, parsed_args):
- self.manager = WorkspaceManager(parsed_args.workspace_path)
- if getattr(parsed_args, 'register', None):
- self.manager.register_new_workspace(
- parsed_args.name, parsed_args.path)
- elif getattr(parsed_args, 'rename', None):
- self.manager.rename_workspace(
- parsed_args.old_name, parsed_args.new_name)
- elif getattr(parsed_args, 'move', None):
- self.manager.move_workspace(
- parsed_args.name, parsed_args.path)
- elif getattr(parsed_args, 'remove', None):
- self.manager.remove_workspace(
- parsed_args.name)
- else:
- self._print_workspaces()
- sys.exit(0)
+def add_global_arguments(parser):
+ parser.add_argument(
+ '--workspace-path', required=False, default=None,
+ help="The path to the workspace file, the default is "
+ "~/.tempest/workspace.yaml")
+ return parser
+
+class TempestWorkspaceRegister(command.Command):
def get_description(self):
- return 'Tempest workspace actions'
+ return ('Registers a new tempest workspace via a given '
+ '--name and --path')
def get_parser(self, prog_name):
- parser = super(TempestWorkspace, self).get_parser(prog_name)
-
- parser.add_argument(
- '--workspace-path', required=False, default=None,
- help="The path to the workspace file, the default is "
- "~/.tempest/workspace.yaml")
-
- subparsers = parser.add_subparsers()
-
- list_parser = subparsers.add_parser(
- 'list', help='Outputs the name and path of all known tempest '
- 'workspaces')
- list_parser.set_defaults(list=True)
-
- register_parser = subparsers.add_parser(
- 'register', help='Registers a new tempest workspace via a given '
- '--name and --path')
- register_parser.add_argument('--name', required=True)
- register_parser.add_argument('--path', required=True)
- register_parser.set_defaults(register=True)
-
- update_parser = subparsers.add_parser(
- 'rename', help='Renames a tempest workspace from --old-name to '
- '--new-name')
- update_parser.add_argument('--old-name', required=True)
- update_parser.add_argument('--new-name', required=True)
- update_parser.set_defaults(rename=True)
-
- move_parser = subparsers.add_parser(
- 'move', help='Changes the path of a given tempest workspace '
- '--name to --path')
- move_parser.add_argument('--name', required=True)
- move_parser.add_argument('--path', required=True)
- move_parser.set_defaults(move=True)
-
- remove_parser = subparsers.add_parser(
- 'remove', help='Deletes the entry for a given tempest workspace '
- '--name')
- remove_parser.add_argument('--name', required=True)
- remove_parser.set_defaults(remove=True)
+ parser = super(TempestWorkspaceRegister, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--name', required=True)
+ parser.add_argument('--path', required=True)
return parser
- def _print_workspaces(self):
- output = prettytable.PrettyTable(["Name", "Path"])
- if self.manager.list_workspaces() is not None:
- for name, path in self.manager.list_workspaces().items():
- output.add_row([name, path])
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.register_new_workspace(parsed_args.name, parsed_args.path)
+ sys.exit(0)
- print(output)
+
+class TempestWorkspaceRename(command.Command):
+ def get_description(self):
+ return 'Renames a tempest workspace from --old-name to --new-name'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceRename, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--old-name', required=True)
+ parser.add_argument('--new-name', required=True)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.rename_workspace(
+ parsed_args.old_name, parsed_args.new_name)
+ sys.exit(0)
+
+
+class TempestWorkspaceMove(command.Command):
+ def get_description(self):
+ return 'Changes the path of a given tempest workspace --name to --path'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceMove, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--name', required=True)
+ parser.add_argument('--path', required=True)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.move_workspace(parsed_args.name, parsed_args.path)
+ sys.exit(0)
+
+
+class TempestWorkspaceRemove(command.Command):
+ def get_description(self):
+ return 'Deletes the entry for a given tempest workspace --name'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceRemove, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--name', required=True)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.remove_workspace(parsed_args.name)
+ sys.exit(0)
+
+
+class TempestWorkspaceList(lister.Lister):
+ def get_description(self):
+ return 'Outputs the name and path of all known tempest workspaces'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceList, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ return (("Name", "Path"),
+ ((n, p) for n, p in self.manager.list_workspaces().items()))
diff --git a/tempest/config.py b/tempest/config.py
index 00c69b0..6a198d7 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -208,6 +208,10 @@
cfg.BoolOpt('api_v2',
default=True,
help='Is the v2 identity API enabled'),
+ cfg.BoolOpt('api_v2_admin',
+ default=True,
+ help="Is the v2 identity admin API available? This setting "
+ "only applies if api_v2 is set to True."),
cfg.BoolOpt('api_v3',
default=True,
help='Is the v3 identity API enabled'),
@@ -223,7 +227,11 @@
cfg.BoolOpt('forbid_global_implied_dsr',
default=False,
help='Does the environment forbid global roles implying '
- 'domain specific ones?'),
+ 'domain specific ones?',
+ deprecated_for_removal=True,
+ deprecated_reason="This feature flag was introduced to "
+ "support testing of old OpenStack versions, "
+ "which are not supported anymore"),
cfg.BoolOpt('security_compliance',
default=False,
help='Does the environment have the security compliance '
@@ -384,9 +392,9 @@
"migration"),
cfg.BoolOpt('block_migrate_cinder_iscsi',
default=False,
- help="Does the test environment block migration support "
- "cinder iSCSI volumes. Note, libvirt doesn't support this, "
- "see https://bugs.launchpad.net/nova/+bug/1398999"),
+ help="Does the test environment support block migration with "
+ "Cinder iSCSI volumes. Note: libvirt >= 1.2.17 is required "
+ "to support this if using the libvirt compute driver."),
cfg.BoolOpt('vnc_console',
default=False,
help='Enable VNC console. This configuration value should '
@@ -399,6 +407,11 @@
default=False,
help='Enable RDP console. This configuration value should '
'be same as [nova.rdp]->enabled in nova.conf'),
+ cfg.BoolOpt('serial_console',
+ default=False,
+ help='Enable serial console. This configuration value '
+ 'should be the same as [nova.serial_console]->enabled '
+ 'in nova.conf'),
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '
diff --git a/tempest/lib/api_schema/response/compute/v2_6/__init__.py b/tempest/lib/api_schema/response/compute/v2_6/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_6/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
new file mode 100644
index 0000000..29b3e86
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -0,0 +1,48 @@
+# Copyright 2016 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_3 import servers
+
+list_servers = copy.deepcopy(servers.list_servers)
+get_server = copy.deepcopy(servers.get_server)
+list_servers_detail = copy.deepcopy(servers.list_servers_detail)
+
+# NOTE: The consolidated remote console API got introduced with v2.6
+# with bp/consolidate-console-api. See Nova commit 578bafeda
+get_remote_consoles = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'remote_console': {
+ 'type': 'object',
+ 'properties': {
+ 'protocol': {'enum': ['vnc', 'rdp', 'serial', 'spice']},
+ 'type': {'enum': ['novnc', 'xpvnc', 'rdp-html5',
+ 'spice-html5', 'serial']},
+ 'url': {
+ 'type': 'string',
+ 'format': 'uri'
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['protocol', 'type', 'url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['remote_console']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index 470190c..e260e48 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,7 +14,7 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_3 import servers
+from tempest.lib.api_schema.response.compute.v2_6 import servers
list_servers = copy.deepcopy(servers.list_servers)
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
index 07b811d..87806b7 100755
--- a/tempest/lib/cmd/skip_tracker.py
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -34,10 +34,14 @@
LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
LOG = logging.getLogger(__name__)
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))
+TESTDIR = os.path.join(BASEDIR, 'tempest')
+
def parse_args():
parser = argparse.ArgumentParser()
- parser.add_argument('test_path', help='Path of test dir')
+ parser.add_argument('test_path', nargs='?', default=TESTDIR,
+ help='Path of test dir')
return parser.parse_args()
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 0d355a1..ff65b25 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -27,6 +27,7 @@
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
+from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -37,7 +38,8 @@
schema_versions_info = [
{'min': None, 'max': '2.2', 'schema': schema},
- {'min': '2.3', 'max': '2.8', 'schema': schemav23},
+ {'min': '2.3', 'max': '2.5', 'schema': schemav23},
+ {'min': '2.6', 'max': '2.8', 'schema': schemav26},
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
@@ -598,6 +600,29 @@
return self.action(server_id, 'os-getConsoleOutput',
schema.get_console_output, **kwargs)
+ def get_remote_console(self, server_id, console_type, protocol, **kwargs):
+ """Get a remote console.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ TODO (markus_z) The api-ref for that isn't yet available, update this
+ here when the docs in Nova are updated. The old API is at
+ http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action
+ """
+ param = {
+ 'remote_console': {
+ 'type': console_type,
+ 'protocol': protocol,
+ }
+ }
+ post_body = json.dumps(param)
+ resp, body = self.post("servers/%s/remote-consoles" % server_id,
+ post_body)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.get_remote_consoles, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
def list_virtual_interfaces(self, server_id):
"""List the virtual interfaces used in an instance."""
resp, body = self.get('/'.join(['servers', server_id,
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index 1489b50..6f498d9 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -14,7 +14,11 @@
from tempest.lib.services.identity.v3.credentials_client import \
CredentialsClient
+from tempest.lib.services.identity.v3.domain_configuration_client \
+ import DomainConfigurationClient
from tempest.lib.services.identity.v3.domains_client import DomainsClient
+from tempest.lib.services.identity.v3.endpoint_filter_client import \
+ EndPointsFilterClient
from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
from tempest.lib.services.identity.v3.groups_client import GroupsClient
from tempest.lib.services.identity.v3.identity_client import IdentityClient
@@ -34,9 +38,9 @@
from tempest.lib.services.identity.v3.users_client import UsersClient
from tempest.lib.services.identity.v3.versions_client import VersionsClient
-__all__ = ['CredentialsClient', 'DomainsClient', 'EndPointsClient',
- 'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
+__all__ = ['CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
+ 'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
+ 'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
'PoliciesClient', 'ProjectsClient', 'RegionsClient',
'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
- 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient',
- 'OAUTHConsumerClient']
+ 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/domain_configuration_client.py b/tempest/lib/services/identity/v3/domain_configuration_client.py
new file mode 100644
index 0000000..d57f2d4
--- /dev/null
+++ b/tempest/lib/services/identity/v3/domain_configuration_client.py
@@ -0,0 +1,188 @@
+# Copyright 2017 AT&T Corporation
+#
+# 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 tempest.lib.common import rest_client
+
+
+class DomainConfigurationClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def show_default_config_settings(self):
+ """Show default configuration settings.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-default-configuration-settings
+ """
+ url = 'domains/config/default'
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_default_group_config(self, group):
+ """Show default configuration for a group.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-default-configuration-for-a-group
+ """
+ url = 'domains/config/%s/default' % group
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_default_group_option(self, group, option):
+ """Show default option for a group.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-default-option-for-a-group
+ """
+ url = 'domains/config/%s/%s/default' % (group, option)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_domain_group_option_config(self, domain_id, group, option):
+ """Show domain group option configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-group-option-configuration
+ """
+ url = 'domains/%s/config/%s/%s' % (domain_id, group, option)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_domain_group_option_config(self, domain_id, group, option,
+ **kwargs):
+ """Update domain group option configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain-group-option-configuration
+ """
+ url = 'domains/%s/config/%s/%s' % (domain_id, group, option)
+ resp, body = self.patch(url, json.dumps({'config': kwargs}))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_domain_group_option_config(self, domain_id, group, option):
+ """Delete domain group option configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain-group-option-configuration
+ """
+ url = 'domains/%s/config/%s/%s' % (domain_id, group, option)
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_domain_group_config(self, domain_id, group):
+ """Shows details for a domain group configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-group-configuration
+ """
+ url = 'domains/%s/config/%s' % (domain_id, group)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_domain_group_config(self, domain_id, group, **kwargs):
+ """Update domain group configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain-group-configuration
+ """
+ url = 'domains/%s/config/%s' % (domain_id, group)
+ resp, body = self.patch(url, json.dumps({'config': kwargs}))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_domain_group_config(self, domain_id, group):
+ """Delete domain group configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain-group-configuration
+ """
+ url = 'domains/%s/config/%s' % (domain_id, group)
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_domain_config(self, domain_id, **kwargs):
+ """Create domain configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#create-domain-configuration
+ """
+ url = 'domains/%s/config' % domain_id
+ resp, body = self.put(url, json.dumps({'config': kwargs}))
+ self.expected_success([200, 201], resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_domain_config(self, domain_id):
+ """Show domain configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-configuration
+ """
+ url = 'domains/%s/config' % domain_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_domain_config(self, domain_id, **kwargs):
+ """Update domain configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain-configuration
+ """
+ url = 'domains/%s/config' % domain_id
+ resp, body = self.patch(url, json.dumps({'config': kwargs}))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_domain_config(self, domain_id):
+ """Delete domain configuration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain-configuration
+ """
+ url = 'domains/%s/config' % domain_id
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/endpoint_filter_client.py b/tempest/lib/services/identity/v3/endpoint_filter_client.py
new file mode 100644
index 0000000..a8cd722
--- /dev/null
+++ b/tempest/lib/services/identity/v3/endpoint_filter_client.py
@@ -0,0 +1,68 @@
+# Copyright 2017 AT&T Corp.
+# 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.
+
+"""
+https://developer.openstack.org/api-ref/identity/v3-ext/#os-ep-filter-api
+"""
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class EndPointsFilterClient(rest_client.RestClient):
+ api_version = "v3"
+ ep_filter = "OS-EP-FILTER"
+
+ def list_projects_for_endpoint(self, endpoint_id):
+ """List all projects that are associated with the endpoint."""
+ resp, body = self.get(self.ep_filter + '/endpoints/%s/projects' %
+ endpoint_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_endpoint_to_project(self, project_id, endpoint_id):
+ """Add association between project and endpoint. """
+ body = None
+ resp, body = self.put(
+ self.ep_filter + '/projects/%s/endpoints/%s' %
+ (project_id, endpoint_id), body)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_endpoint_in_project(self, project_id, endpoint_id):
+ """Check association of Project with Endpoint."""
+ resp, body = self.head(
+ self.ep_filter + '/projects/%s/endpoints/%s' %
+ (project_id, endpoint_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_endpoints_in_project(self, project_id):
+ """List Endpoints associated with Project."""
+ resp, body = self.get(self.ep_filter + '/projects/%s/endpoints'
+ % project_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_endpoint_from_project(self, project_id, endpoint_id):
+ """Delete association between project and endpoint."""
+ resp, body = self.delete(
+ self.ep_filter + '/projects/%s/endpoints/%s'
+ % (project_id, endpoint_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index 2b5e82d..197d57e 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -55,6 +55,14 @@
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
+ def force_delete_backup(self, backup_id):
+ """Force delete a backup volume."""
+ post_body = json.dumps({'os-force_delete': {}})
+ url = 'backups/%s/action' % backup_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp)
+
def show_backup(self, backup_id):
"""Returns the details of a single backup."""
url = "backups/%s" % backup_id
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index c67ddfb..8b5c96f 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -118,11 +118,16 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def delete_volume(self, volume_id, cascade=False):
- """Deletes the Specified Volume."""
+ def delete_volume(self, volume_id, **params):
+ """Deletes the Specified Volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume
+ """
url = 'volumes/%s' % volume_id
- if cascade:
- url += '?cascade=True'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
resp, body = self.delete(url)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
@@ -281,6 +286,19 @@
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
+ def force_detach_volume(self, volume_id, **kwargs):
+ """Force detach a volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#force-detach-volume
+ """
+ post_body = json.dumps({'os-force_detach': kwargs})
+ url = 'volumes/%s/action' % volume_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_volume_image_metadata(self, volume_id, **kwargs):
"""Update image metadata for the volume.
diff --git a/tempest/test.py b/tempest/test.py
index 2ebdc60..e63e08f 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -455,7 +455,9 @@
"""Returns a credentials provider
If no credential provider exists yet creates one.
- It uses self.identity_version if defined, or the configuration value
+ It always use the configuration value from identity.auth_version,
+ since we always want to provision accounts with the current version
+ of the identity API.
"""
if (not hasattr(cls, '_creds_provider') or not cls._creds_provider or
not cls._creds_provider.name == cls.__name__):
@@ -464,8 +466,7 @@
cls._creds_provider = credentials.get_credentials_provider(
name=cls.__name__, network_resources=cls.network_resources,
- force_tenant_isolation=force_tenant_isolation,
- identity_version=cls.get_identity_version())
+ force_tenant_isolation=force_tenant_isolation)
return cls._creds_provider
@classmethod
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index 9f75962..1206e3f 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -92,6 +92,31 @@
:return option_list: A list of tuples with the group name and options
in that group.
:rtype: list
+
+ Example::
+
+ # Config options are defined in a config.py module
+ service_option = cfg.BoolOpt(
+ "my_service", default=True,
+ help="Whether or not my service is available")
+
+ my_service_group = cfg.OptGroup(name="my-service",
+ title="My service options")
+ my_service_features_group = cfg.OptGroup(
+ name="my-service-features",
+ title="My service available features")
+
+ MyServiceGroup = [<list of options>]
+ MyServiceFeaturesGroup = [<list of options>]
+
+ # Plugin is implemented in a plugin.py module
+ from my_plugin import config as my_config
+
+ def get_opt_lists(self, conf):
+ return [
+ (my_service_group.name, MyServiceGroup),
+ (my_service_features_group.name, MyServiceFeaturesGroup)
+ ]
"""
return []
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index 6ca4d42..dc6c0c8 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -47,23 +47,25 @@
self.assertEqual(return_code, expected, msg)
def test_run_workspace_list(self):
- cmd = ['tempest', 'workspace', '--workspace-path',
- self.store_file, 'list']
+ cmd = ['tempest', 'workspace', 'list',
+ '--workspace-path', self.store_file]
self._run_cmd_gets_return_code(cmd, 0)
def test_run_workspace_register(self):
name = data_utils.rand_uuid()
path = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, path, ignore_errors=True)
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'register', '--name', name, '--path', path]
+ cmd = ['tempest', 'workspace', 'register',
+ '--workspace-path', self.store_file,
+ '--name', name, '--path', path]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNotNone(self.workspace_manager.get_workspace(name))
def test_run_workspace_rename(self):
new_name = data_utils.rand_uuid()
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'rename', "--old-name", self.name, '--new-name', new_name]
+ cmd = ['tempest', 'workspace', 'rename',
+ '--workspace-path', self.store_file,
+ '--old-name', self.name, '--new-name', new_name]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
@@ -71,15 +73,17 @@
def test_run_workspace_move(self):
new_path = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'move', '--name', self.name, '--path', new_path]
+ cmd = ['tempest', 'workspace', 'move',
+ '--workspace-path', self.store_file,
+ '--name', self.name, '--path', new_path]
self._run_cmd_gets_return_code(cmd, 0)
self.assertEqual(
self.workspace_manager.get_workspace(self.name), new_path)
def test_run_workspace_remove(self):
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'remove', '--name', self.name]
+ cmd = ['tempest', 'workspace', 'remove',
+ '--workspace-path', self.store_file,
+ '--name', self.name]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index a277dfe..a857329 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -1168,3 +1168,34 @@
tag=self.FAKE_TAGS[0],
status=204,
to_utf=bytes_body)
+
+
+class TestServersClientMinV26(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestServersClientMinV26, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = servers_client.ServersClient(fake_auth, 'compute',
+ 'regionOne')
+ base_compute_client.COMPUTE_MICROVERSION = '2.6'
+ self.server_id = "920eaac8-a284-4fd1-9c2c-b30f0181b125"
+
+ def tearDown(self):
+ super(TestServersClientMinV26, self).tearDown()
+ base_compute_client.COMPUTE_MICROVERSION = None
+
+ def test_get_remote_consoles(self):
+ self.check_service_client_function(
+ self.client.get_remote_console,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {
+ 'remote_console': {
+ 'protocol': 'serial',
+ 'type': 'serial',
+ 'url': 'ws://127.0.0.1:6083/?token=IllAllowIt'
+ }
+ },
+ server_id=self.server_id,
+ console_type='serial',
+ protocol='serial',
+ )
diff --git a/tempest/tests/lib/services/identity/v3/test_domain_configuration_client.py b/tempest/tests/lib/services/identity/v3/test_domain_configuration_client.py
new file mode 100644
index 0000000..72e5bd2
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_domain_configuration_client.py
@@ -0,0 +1,217 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# 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 domain_configuration_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestDomainConfigurationClient(base.BaseServiceTest):
+
+ FAKE_CONFIG_SETTINGS = {
+ "config": {
+ "identity": {
+ "driver": "ldap"
+ },
+ "ldap": {
+ "url": "ldap://localhost",
+ "user": "",
+ "suffix": "cn=example,cn=com",
+ }
+ }
+ }
+
+ FAKE_DOMAIN_ID = '07ef7d04-2941-4bee-8551-f79f08a021de'
+
+ def setUp(self):
+ super(TestDomainConfigurationClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = domain_configuration_client.DomainConfigurationClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_show_default_config_settings(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_default_config_settings,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CONFIG_SETTINGS,
+ bytes_body)
+
+ def _test_show_default_group_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_default_group_config,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CONFIG_SETTINGS['config']['ldap'],
+ bytes_body,
+ group='ldap')
+
+ def _test_show_default_group_option(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_default_group_option,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'driver': 'ldap'},
+ bytes_body,
+ group='identity',
+ option='driver')
+
+ def _test_show_domain_group_option_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_domain_group_option_config,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'driver': 'ldap'},
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID,
+ group='identity',
+ option='driver')
+
+ def _test_update_domain_group_option_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_domain_group_option_config,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_CONFIG_SETTINGS,
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID,
+ group='identity',
+ option='driver',
+ url='http://myldap/my_other_root')
+
+ def _test_show_domain_group_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_domain_group_config,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CONFIG_SETTINGS['config']['ldap'],
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID,
+ group='ldap')
+
+ def _test_update_domain_group_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_domain_group_config,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_CONFIG_SETTINGS['config']['ldap'],
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID,
+ group='ldap',
+ **self.FAKE_CONFIG_SETTINGS['config'])
+
+ def _test_create_domain_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_domain_config,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_CONFIG_SETTINGS,
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID,
+ status=201)
+
+ def _test_show_domain_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_domain_config,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CONFIG_SETTINGS,
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID)
+
+ def _test_update_domain_config(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_domain_config,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_CONFIG_SETTINGS,
+ bytes_body,
+ domain_id=self.FAKE_DOMAIN_ID)
+
+ def test_show_default_config_settings_with_str_body(self):
+ self._test_show_default_config_settings()
+
+ def test_show_default_config_settings_with_bytes_body(self):
+ self._test_show_default_config_settings(bytes_body=True)
+
+ def test_show_default_group_config_with_str_body(self):
+ self._test_show_default_group_config()
+
+ def test_show_default_group_config_with_bytes_body(self):
+ self._test_show_default_group_config(bytes_body=True)
+
+ def test_show_default_group_option_with_str_body(self):
+ self._test_show_default_group_option()
+
+ def test_show_default_group_option_with_bytes_body(self):
+ self._test_show_default_group_option(bytes_body=True)
+
+ def test_show_domain_group_option_config_with_str_body(self):
+ self._test_show_domain_group_option_config()
+
+ def test_show_domain_group_option_config_with_bytes_body(self):
+ self._test_show_domain_group_option_config(bytes_body=True)
+
+ def test_update_domain_group_option_config_with_str_body(self):
+ self._test_update_domain_group_option_config()
+
+ def test_update_domain_group_option_config_with_bytes_body(self):
+ self._test_update_domain_group_option_config(bytes_body=True)
+
+ def test_delete_domain_group_option_config(self):
+ self.check_service_client_function(
+ self.client.delete_domain_group_option_config,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ domain_id=self.FAKE_DOMAIN_ID,
+ group='identity',
+ option='driver')
+
+ def test_show_domain_group_config_with_str_body(self):
+ self._test_show_domain_group_config()
+
+ def test_show_domain_group_config_with_bytes_body(self):
+ self._test_show_domain_group_config(bytes_body=True)
+
+ def test_test_update_domain_group_config_with_str_body(self):
+ self._test_update_domain_group_config()
+
+ def test_update_domain_group_config_with_bytes_body(self):
+ self._test_update_domain_group_config(bytes_body=True)
+
+ def test_delete_domain_group_config(self):
+ self.check_service_client_function(
+ self.client.delete_domain_group_config,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ domain_id=self.FAKE_DOMAIN_ID,
+ group='identity')
+
+ def test_create_domain_config_with_str_body(self):
+ self._test_create_domain_config()
+
+ def test_create_domain_config_with_bytes_body(self):
+ self._test_create_domain_config(bytes_body=True)
+
+ def test_show_domain_config_with_str_body(self):
+ self._test_show_domain_config()
+
+ def test_show_domain_config_with_bytes_body(self):
+ self._test_show_domain_config(bytes_body=True)
+
+ def test_update_domain_config_with_str_body(self):
+ self._test_update_domain_config()
+
+ def test_update_domain_config_with_bytes_body(self):
+ self._test_update_domain_config(bytes_body=True)
+
+ def test_delete_domain_config(self):
+ self.check_service_client_function(
+ self.client.delete_domain_config,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ domain_id=self.FAKE_DOMAIN_ID)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
new file mode 100644
index 0000000..7faf6a0
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
@@ -0,0 +1,165 @@
+# Copyright 2017 AT&T Corp.
+# 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 endpoint_filter_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndPointsFilterClient(base.BaseServiceTest):
+ FAKE_LIST_PROJECTS_FOR_ENDPOINTS = {
+ "projects": [
+ {
+ "domain_id": "1777c7",
+ "enabled": True,
+ "id": "1234ab1",
+ "type": "compute",
+ "links": {
+ "self": "http://example.com/identity/v3/projects/1234ab1"
+ },
+ "name": "Project 1",
+ "description": "Project 1 description",
+ },
+ {
+ "domain_id": "1777c7",
+ "enabled": True,
+ "id": "5678cd2",
+ "type": "compute",
+ "links": {
+ "self": "http://example.com/identity/v3/projects/5678cd2"
+ },
+ "name": "Project 2",
+ "description": "Project 2 description",
+ }
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/endpoints/\
+ u6ay5u/projects",
+ "previous": None,
+ "next": None
+ }
+ }
+
+ FAKE_LIST_ENDPOINTS_FOR_PROJECTS = {
+ "endpoints": [
+ {
+ "id": "u6ay5u",
+ "interface": "public",
+ "url": "http://example.com/identity/",
+ "region": "north",
+ "links": {
+ "self": "http://example.com/identity/v3/endpoints/u6ay5u"
+ },
+ "service_id": "5um4r",
+ },
+ {
+ "id": "u6ay5u",
+ "interface": "internal",
+ "url": "http://example.com/identity/",
+ "region": "south",
+ "links": {
+ "self": "http://example.com/identity/v3/endpoints/u6ay5u"
+ },
+ "service_id": "5um4r",
+ },
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/projects/\
+ 1234ab1/endpoints",
+ "previous": None,
+ "next": None
+ }
+ }
+
+ def setUp(self):
+ super(TestEndPointsFilterClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = endpoint_filter_client.EndPointsFilterClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_add_endpoint_to_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_endpoint_to_project,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ status=204,
+ project_id=3,
+ endpoint_id=4)
+
+ def _test_check_endpoint_in_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_endpoint_in_project,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ bytes_body,
+ status=204,
+ project_id=3,
+ endpoint_id=4)
+
+ def _test_list_projects_for_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_projects_for_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_PROJECTS_FOR_ENDPOINTS,
+ bytes_body,
+ status=200,
+ endpoint_id=3)
+
+ def _test_list_endpoints_in_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints_in_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINTS_FOR_PROJECTS,
+ bytes_body,
+ status=200,
+ project_id=4)
+
+ def _test_delete_endpoint_from_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_endpoint_from_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ bytes_body,
+ status=204,
+ project_id=3,
+ endpoint_id=4)
+
+ def test_add_endpoint_to_project_with_str_body(self):
+ self._test_add_endpoint_to_project()
+
+ def test_add_endpoint_to_project_with_bytes_body(self):
+ self._test_add_endpoint_to_project(bytes_body=True)
+
+ def test_check_endpoint_in_project_with_str_body(self):
+ self._test_check_endpoint_in_project()
+
+ def test_check_endpoint_in_project_with_bytes_body(self):
+ self._test_check_endpoint_in_project(bytes_body=True)
+
+ def test_list_projects_for_endpoint_with_str_body(self):
+ self._test_list_projects_for_endpoint()
+
+ def test_list_projects_for_endpoint_with_bytes_body(self):
+ self._test_list_projects_for_endpoint(bytes_body=True)
+
+ def test_list_endpoints_in_project_with_str_body(self):
+ self._test_list_endpoints_in_project()
+
+ def test_list_endpoints_in_project_with_bytes_body(self):
+ self._test_list_endpoints_in_project(bytes_body=True)
+
+ def test_delete_endpoint_from_project(self):
+ self._test_delete_endpoint_from_project()
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
new file mode 100644
index 0000000..498b963
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
@@ -0,0 +1,52 @@
+# Copyright 2017 FiberHome Telecommunication Technologies 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.volume.v2 import volumes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestVolumesClient(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestVolumesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = volumes_client.VolumesClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_force_detach_volume(self, bytes_body=False):
+ kwargs = {
+ 'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
+ 'connector': {
+ 'initiator': 'iqn.2017-04.org.fake:01'
+ }
+ }
+
+ self.check_service_client_function(
+ self.client.force_detach_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ to_utf=bytes_body,
+ status=202,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ **kwargs
+ )
+
+ def test_force_detach_volume_with_str_body(self):
+ self._test_force_detach_volume()
+
+ def test_force_detach_volume_with_bytes_body(self):
+ self._test_force_detach_volume(bytes_body=True)
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index 9735f77..44f5874 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -20,127 +20,10 @@
is fixed but a skip is still in the Tempest test code
"""
-import os
-import re
-
-from launchpadlib import launchpad
-from oslo_log import log as logging
-
-BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-TESTDIR = os.path.join(BASEDIR, 'tempest')
-LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
-
-LOG = logging.getLogger(__name__)
-
-
-def info(msg, *args, **kwargs):
- LOG.info(msg, *args, **kwargs)
-
-
-def debug(msg, *args, **kwargs):
- LOG.debug(msg, *args, **kwargs)
-
-
-def find_skips(start=TESTDIR):
- """Find skipped tests
-
- Returns a list of tuples (method, bug) that represent
- test methods that have been decorated to skip because of
- a particular bug.
- """
- results = {}
- debug("Searching in %s", start)
- for root, _dirs, files in os.walk(start):
- for name in files:
- if name.startswith('test_') and name.endswith('py'):
- path = os.path.join(root, name)
- debug("Searching in %s", path)
- temp_result = find_skips_in_file(path)
- for method_name, bug_no in temp_result:
- if results.get(bug_no):
- result_dict = results.get(bug_no)
- if result_dict.get(name):
- result_dict[name].append(method_name)
- else:
- result_dict[name] = [method_name]
- results[bug_no] = result_dict
- else:
- results[bug_no] = {name: [method_name]}
- return results
-
-
-def find_skips_in_file(path):
- """Return the skip tuples in a test file"""
- BUG_RE = re.compile(r'\s*@.*skip_because\(bug=[\'"](\d+)[\'"]')
- DEF_RE = re.compile(r'\s*def (\w+)\(')
- bug_found = False
- results = []
- with open(path, 'rb') as content:
- lines = content.readlines()
- for x, line in enumerate(lines):
- if not bug_found:
- res = BUG_RE.match(line)
- if res:
- bug_no = int(res.group(1))
- debug("Found bug skip %s on line %d", bug_no, x + 1)
- bug_found = True
- else:
- res = DEF_RE.match(line)
- if res:
- method = res.group(1)
- debug("Found test method %s skips for bug %d",
- method, bug_no)
- results.append((method, bug_no))
- bug_found = False
- return results
-
-
-def get_results(result_dict):
- results = []
- for bug_no in result_dict:
- for method in result_dict[bug_no]:
- results.append((method, bug_no))
- return results
+from tempest.lib.cmd import skip_tracker
if __name__ == '__main__':
- results = find_skips()
- unique_bugs = sorted(set([bug for (method, bug) in get_results(results)]))
- unskips = []
- duplicates = []
- info("Total bug skips found: %d", len(results))
- info("Total unique bugs causing skips: %d", len(unique_bugs))
- lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
- 'production',
- LPCACHEDIR)
- for bug_no in unique_bugs:
- bug = lp.bugs[bug_no]
- duplicate = bug.duplicate_of_link
- if duplicate is not None:
- dup_id = duplicate.split('/')[-1]
- duplicates.append((bug_no, dup_id))
- for task in bug.bug_tasks:
- info("Bug #%7s (%12s - %12s)", bug_no,
- task.importance, task.status)
- if task.status in ('Fix Released', 'Fix Committed'):
- unskips.append(bug_no)
-
- for bug_id, dup_id in duplicates:
- if bug_id not in unskips:
- dup_bug = lp.bugs[dup_id]
- for task in dup_bug.bug_tasks:
- info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)",
- bug_id, dup_id, task.importance, task.status)
- if task.status in ('Fix Released', 'Fix Committed'):
- unskips.append(bug_id)
-
- unskips = sorted(set(unskips))
- if unskips:
- print("The following bugs have been fixed and the corresponding skips")
- print("should be removed from the test cases:")
- print()
- for bug in unskips:
- message = " %7s in " % bug
- locations = ["%s" % x for x in results[bug].keys()]
- message += " and ".join(locations)
- print(message)
+ print("DEPRECATED: `skip_tracker.py` is already deprecated, "
+ "use `skip-tracker` command instead.")
+ skip_tracker.main()