Merge "Report original request ID on failed shelve"
diff --git a/.coveragerc b/.coveragerc
index 449e62c..398755b 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,4 +1,4 @@
[run]
branch = True
source = tempest
-omit = tempest/tests/*,tempest/scenario/test_*.py,tempest/api/*
+omit = tempest/tests/*,tempest/scenario/test_*.py,tempest/api/*,tempest/serial_tests/*
diff --git a/bindep.txt b/bindep.txt
index 7d34939..3eb3bbd 100644
--- a/bindep.txt
+++ b/bindep.txt
@@ -5,7 +5,6 @@
libffi-devel [platform:rpm]
gcc [platform:rpm]
gcc [platform:dpkg]
-python-devel [platform:rpm]
python3-dev [platform:dpkg]
python3-devel [platform:rpm]
openssl-devel [platform:rpm]
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index 3d221c9..89f0f90 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -9,10 +9,10 @@
Tempest master supports the below OpenStack Releases:
+* 2023.2
+* 2023.1
* Zed
* Yoga
-* Xena
-* Wallaby
For older OpenStack Release:
@@ -35,3 +35,5 @@
* Python 3.8
* Python 3.9
+* Python 3.10
+* Python 3.11
diff --git a/playbooks/enable-fips.yaml b/playbooks/enable-fips.yaml
deleted file mode 100644
index c8f042d..0000000
--- a/playbooks/enable-fips.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-- hosts: all
- tasks:
- - include_role:
- name: enable-fips
diff --git a/releasenotes/notes/2023.2-intermediate-release-8725d48b96854dce.yaml b/releasenotes/notes/2023.2-intermediate-release-8725d48b96854dce.yaml
new file mode 100644
index 0000000..7d3d3c4
--- /dev/null
+++ b/releasenotes/notes/2023.2-intermediate-release-8725d48b96854dce.yaml
@@ -0,0 +1,5 @@
+---
+prelude: >
+ This is an intermediate release during the 2023.2 development cycle to
+ make scenario tests server SSHABLE functionality available to plugins
+ and other consumers.
diff --git a/releasenotes/notes/add-image-task-apis-as-tempest-clients-228ccba01f59cbf3.yaml b/releasenotes/notes/add-image-task-apis-as-tempest-clients-228ccba01f59cbf3.yaml
new file mode 100644
index 0000000..cb99a29
--- /dev/null
+++ b/releasenotes/notes/add-image-task-apis-as-tempest-clients-228ccba01f59cbf3.yaml
@@ -0,0 +1,54 @@
+---
+features:
+ - |
+ The following ``tasks_client`` tempest client for glance v2 image
+ task API is implemented in this release.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/releasenotes/notes/add-keystone-config-opt-minimum-password-age-426e9d225f743137.yaml b/releasenotes/notes/add-keystone-config-opt-minimum-password-age-426e9d225f743137.yaml
new file mode 100644
index 0000000..06f993e
--- /dev/null
+++ b/releasenotes/notes/add-keystone-config-opt-minimum-password-age-426e9d225f743137.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Adding a new config option `[identity]/user_minimum_password_age`
+ which allows to specify the number of days that a password must
+ be used before the user can change it. For this option to take
+ effect, identity-feature-enabled.security_compliance must be set
+ to True.
diff --git a/releasenotes/notes/add-volume-detach-libs-2cbb3ca924aed0ac.yaml b/releasenotes/notes/add-volume-detach-libs-2cbb3ca924aed0ac.yaml
new file mode 100644
index 0000000..30127b3
--- /dev/null
+++ b/releasenotes/notes/add-volume-detach-libs-2cbb3ca924aed0ac.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add delete_attachment to the v3 AttachmentsClient and terminate_connection
+ to the v3 VolumesClient.
diff --git a/releasenotes/notes/backup-driver-config-option-5f12e71c75b7f01b.yaml b/releasenotes/notes/backup-driver-config-option-5f12e71c75b7f01b.yaml
new file mode 100644
index 0000000..0126a06
--- /dev/null
+++ b/releasenotes/notes/backup-driver-config-option-5f12e71c75b7f01b.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new tempest.conf option called backup_driver was added to tempest to
+ indicate which backup driver is used by Cinder. This option allows us
+ to create a resource properly and avoid cleanup issues when Swift is used as the
+ backup driver.
diff --git a/releasenotes/notes/end-of-support-of-xena-2e747cff7f8bc48a.yaml b/releasenotes/notes/end-of-support-of-xena-2e747cff7f8bc48a.yaml
new file mode 100644
index 0000000..39f6866
--- /dev/null
+++ b/releasenotes/notes/end-of-support-of-xena-2e747cff7f8bc48a.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+ This is an intermediate release during the 2023.2 development cycle to
+ mark the end of support for EM Xena release in Tempest.
+ After this release, Tempest will support below OpenStack Releases:
+
+ * 2023.1
+ * Zed
+ * Yoga
+
+ Current development of Tempest is for OpenStack 2023.2 development
+ cycle.
diff --git a/releasenotes/notes/remove-glance-v1-api-tests-5a39d3ea4b6bd71e.yaml b/releasenotes/notes/remove-glance-v1-api-tests-5a39d3ea4b6bd71e.yaml
new file mode 100644
index 0000000..dc36ac0
--- /dev/null
+++ b/releasenotes/notes/remove-glance-v1-api-tests-5a39d3ea4b6bd71e.yaml
@@ -0,0 +1,8 @@
+---
+prelude: >
+ Glance v1 APIs were removed in Rocky release and last
+ supported release for v1 was Queens. Tempest master does
+ not support the Rocky or Queens release so we removed
+ the Glance v1 tests, config option, and its service clients.
+ If you would like to test the v1 APIs then you can use the old
+ Tempest version.
diff --git a/releasenotes/notes/remove-nova-network-tests-f694bcd30a97a4ca.yaml b/releasenotes/notes/remove-nova-network-tests-f694bcd30a97a4ca.yaml
new file mode 100644
index 0000000..6ee5691
--- /dev/null
+++ b/releasenotes/notes/remove-nova-network-tests-f694bcd30a97a4ca.yaml
@@ -0,0 +1,11 @@
+---
+prelude: >
+ Tempest remove the nova-network tests and service clients.
+ The nova-network was removed from Rocky release and current
+ Tempest master does not support the Rocky release. Below are
+ the service clients have been removed:
+
+ * floating_ip_pools_client
+ * floating_ips_bulk_client
+ * fixed_ips_client
+ * list_virtual_interfaces
diff --git a/releasenotes/notes/tempest-2023-2-release-e913af3f36762afb.yaml b/releasenotes/notes/tempest-2023-2-release-e913af3f36762afb.yaml
new file mode 100644
index 0000000..1147465
--- /dev/null
+++ b/releasenotes/notes/tempest-2023-2-release-e913af3f36762afb.yaml
@@ -0,0 +1,17 @@
+---
+prelude: |
+ This release is to tag Tempest for OpenStack 2023.2 release.
+ This release marks the start of 2023.2 release support in Tempest.
+ After this release, Tempest will support below OpenStack Releases:
+
+ * 2023.2
+ * 2023.1
+ * Zed
+ * Yoga
+
+ Current development of Tempest is for OpenStack 2024.1 development
+ cycle. Every Tempest commit is also tested against master during
+ the 2024.1 cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a 2024.1 (or future release)
+ cloud.
+ To be on safe side, use this tag to test the OpenStack 2023.2 release.
diff --git a/releasenotes/notes/update-v3-entrypoint-29d56c902439cc03.yaml b/releasenotes/notes/update-v3-entrypoint-29d56c902439cc03.yaml
new file mode 100644
index 0000000..363e59f
--- /dev/null
+++ b/releasenotes/notes/update-v3-entrypoint-29d56c902439cc03.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ Update default value of config option ``CONF.identity.v3_entrypoint_type``
+ from adminURL to public. This was deprecated in Q release, and was missed.
+ The default entrypoint used by tempest should be the public one.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 882413f..989d3b5 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,9 @@
:maxdepth: 1
unreleased
+ v36.0.0
+ v35.0.0
+ v34.2.0
v34.0.0
v33.0.0
v32.0.0
diff --git a/releasenotes/source/v34.2.0.rst b/releasenotes/source/v34.2.0.rst
new file mode 100644
index 0000000..386cf71
--- /dev/null
+++ b/releasenotes/source/v34.2.0.rst
@@ -0,0 +1,6 @@
+=====================
+v34.2.0 Release Notes
+=====================
+
+.. release-notes:: 34.2.0 Release Notes
+ :version: 34.2.0
diff --git a/releasenotes/source/v35.0.0.rst b/releasenotes/source/v35.0.0.rst
new file mode 100644
index 0000000..3e5dcbd
--- /dev/null
+++ b/releasenotes/source/v35.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v35.0.0 Release Notes
+=====================
+
+.. release-notes:: 35.0.0 Release Notes
+ :version: 35.0.0
diff --git a/releasenotes/source/v36.0.0.rst b/releasenotes/source/v36.0.0.rst
new file mode 100644
index 0000000..7cf477d
--- /dev/null
+++ b/releasenotes/source/v36.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v36.0.0 Release Notes
+=====================
+
+.. release-notes:: 36.0.0 Release Notes
+ :version: 36.0.0
diff --git a/roles/run-tempest-26/tasks/main.yaml b/roles/run-tempest-26/tasks/main.yaml
index 7423bfb..7ad5c99 100644
--- a/roles/run-tempest-26/tasks/main.yaml
+++ b/roles/run-tempest-26/tasks/main.yaml
@@ -17,7 +17,7 @@
- name: Limit max concurrency when more than 3 vcpus are available
set_fact:
- default_concurrency: "{{ num_cores|int // 2 }}"
+ default_concurrency: "{{ num_cores|int - 2 }}"
when: num_cores|int > 3
- name: Override target branch
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 3fb494f..3d78557 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -17,7 +17,7 @@
- name: Limit max concurrency when more than 3 vcpus are available
set_fact:
- default_concurrency: "{{ num_cores|int // 2 }}"
+ default_concurrency: "{{ num_cores|int - 2 }}"
when: num_cores|int > 3
- name: Override target branch
diff --git a/setup.cfg b/setup.cfg
index beaf9b4..bb1ced5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -18,6 +18,7 @@
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
+ Programming Language :: Python :: 3.11
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
diff --git a/tempest/api/compute/admin/test_assisted_volume_snapshots.py b/tempest/api/compute/admin/test_assisted_volume_snapshots.py
new file mode 100644
index 0000000..b7be796
--- /dev/null
+++ b/tempest/api/compute/admin/test_assisted_volume_snapshots.py
@@ -0,0 +1,70 @@
+# 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.compute import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+CONF = config.CONF
+
+
+class VolumesAssistedSnapshotsTest(base.BaseV2ComputeAdminTest):
+ """Test volume assisted snapshots"""
+
+ create_default_network = True
+
+ # TODO(gmann): Remove the admin access to service user
+ # once nova change the default of this API to service
+ # role. To merge the nova changing the policy default
+ # we need to use token with admin as well as service
+ # role and later we can use only service token.
+ credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesAssistedSnapshotsTest, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(VolumesAssistedSnapshotsTest, cls).setup_clients()
+ cls.assisted_v_client = (
+ cls.os_service_user.assisted_volume_snapshots_client)
+ cls.volumes_client = cls.os_admin.volumes_client_latest
+ cls.servers_client = cls.os_admin.servers_client
+
+ @decorators.idempotent_id('8aee84a3-1b1f-42e4-9b00-613931ccac24')
+ def test_volume_assisted_snapshot_create_delete(self):
+ """Test create/delete volume assisted snapshot"""
+ volume = self.create_volume()
+ self.addCleanup(self.delete_volume, volume['id'])
+ validation_resources = self.get_class_validation_resources(
+ self.os_primary)
+ server = self.create_test_server(
+ validatable=True,
+ validation_resources=validation_resources,
+ wait_until='SSHABLE'
+ )
+ # Attach created volume to server
+ self.attach_volume(server, volume)
+ snapshot_id = data_utils.rand_uuid()
+ snapshot = self.assisted_v_client.create_assisted_volume_snapshot(
+ volume_id=volume['id'], snapshot_id=snapshot_id,
+ type='qcow2', new_file='new_file')['snapshot']
+ self.assisted_v_client.delete_assisted_volume_snapshot(
+ volume_id=volume['id'], snapshot_id=snapshot['id'])
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
deleted file mode 100644
index 9de3da9..0000000
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright 2013 IBM 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.api.compute import base
-from tempest.common import utils
-from tempest import config
-from tempest.lib import decorators
-
-CONF = config.CONF
-
-
-class FixedIPsTestJson(base.BaseV2ComputeAdminTest):
- """Test fixed ips API"""
-
- @classmethod
- def skip_checks(cls):
- super(FixedIPsTestJson, cls).skip_checks()
- if CONF.service_available.neutron:
- msg = ("%s skipped as neutron is available" % cls.__name__)
- raise cls.skipException(msg)
- if not utils.get_service_list()['network']:
- raise cls.skipException("network service not enabled.")
-
- @classmethod
- def setup_clients(cls):
- super(FixedIPsTestJson, cls).setup_clients()
- cls.client = cls.os_admin.fixed_ips_client
-
- @classmethod
- def resource_setup(cls):
- super(FixedIPsTestJson, cls).resource_setup()
- server = cls.create_test_server(wait_until='ACTIVE')
- server = cls.servers_client.show_server(server['id'])['server']
- cls.ip = None
- for ip_set in server['addresses']:
- for ip in server['addresses'][ip_set]:
- if ip['OS-EXT-IPS:type'] == 'fixed':
- cls.ip = ip['addr']
- break
- if cls.ip:
- break
- if cls.ip is None:
- raise cls.skipException("No fixed ip found for server: %s"
- % server['id'])
-
- @decorators.idempotent_id('16b7d848-2f7c-4709-85a3-2dfb4576cc52')
- def test_list_fixed_ip_details(self):
- """Test getting fixed ip details"""
- fixed_ip = self.client.show_fixed_ip(self.ip)
- self.assertEqual(fixed_ip['fixed_ip']['address'], self.ip)
-
- @decorators.idempotent_id('5485077b-7e46-4cec-b402-91dc3173433b')
- def test_set_reserve(self):
- """Test reserving fixed ip"""
- self.client.reserve_fixed_ip(self.ip, reserve="None")
-
- @decorators.idempotent_id('7476e322-b9ff-4710-bf82-49d51bac6e2e')
- def test_set_unreserve(self):
- """Test unreserving fixed ip"""
- self.client.reserve_fixed_ip(self.ip, unreserve="None")
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
deleted file mode 100644
index 1629faa..0000000
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright 2013 NEC 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.compute import base
-from tempest.common import utils
-from tempest import config
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-CONF = config.CONF
-
-
-class FixedIPsNegativeTestJson(base.BaseV2ComputeAdminTest):
- """Negative tests of fixed ips API"""
-
- @classmethod
- def skip_checks(cls):
- super(FixedIPsNegativeTestJson, cls).skip_checks()
- if CONF.service_available.neutron:
- msg = ("%s skipped as neutron is available" % cls.__name__)
- raise cls.skipException(msg)
- if not utils.get_service_list()['network']:
- raise cls.skipException("network service not enabled.")
-
- @classmethod
- def setup_clients(cls):
- super(FixedIPsNegativeTestJson, cls).setup_clients()
- cls.client = cls.os_admin.fixed_ips_client
- cls.non_admin_client = cls.fixed_ips_client
-
- @classmethod
- def resource_setup(cls):
- super(FixedIPsNegativeTestJson, cls).resource_setup()
- server = cls.create_test_server(wait_until='ACTIVE')
- server = cls.servers_client.show_server(server['id'])['server']
- cls.ip = None
- for ip_set in server['addresses']:
- for ip in server['addresses'][ip_set]:
- if ip['OS-EXT-IPS:type'] == 'fixed':
- cls.ip = ip['addr']
- break
- if cls.ip:
- break
- if cls.ip is None:
- raise cls.skipException("No fixed ip found for server: %s"
- % server['id'])
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('9f17f47d-daad-4adc-986e-12370c93e407')
- def test_list_fixed_ip_details_with_non_admin_user(self):
- """Test listing fixed ip with detail by non-admin user is forbidden"""
- self.assertRaises(lib_exc.Forbidden,
- self.non_admin_client.show_fixed_ip, self.ip)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('ce60042c-fa60-4836-8d43-1c8e3359dc47')
- def test_set_reserve_with_non_admin_user(self):
- """Test reserving fixed ip by non-admin user is forbidden"""
- self.assertRaises(lib_exc.Forbidden,
- self.non_admin_client.reserve_fixed_ip,
- self.ip, reserve="None")
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('f1f7a35b-0390-48c5-9803-5f27461439db')
- def test_set_unreserve_with_non_admin_user(self):
- """Test unreserving fixed ip by non-admin user is forbidden"""
- self.assertRaises(lib_exc.Forbidden,
- self.non_admin_client.reserve_fixed_ip,
- self.ip, unreserve="None")
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('f51cf464-7fc5-4352-bc3e-e75cfa2cb717')
- def test_set_reserve_with_invalid_ip(self):
- """Test reserving invalid fixed ip should fail"""
- # NOTE(maurosr): since this exercises the same code snippet, we do it
- # only for reserve action
- # NOTE(eliqiao): in Juno, the exception is NotFound, but in master, we
- # change the error code to BadRequest, both exceptions should be
- # accepted by tempest
- self.assertRaises((lib_exc.NotFound, lib_exc.BadRequest),
- self.client.reserve_fixed_ip,
- "my.invalid.ip", reserve="None")
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('fd26ef50-f135-4232-9d32-281aab3f9176')
- def test_fixed_ip_with_invalid_action(self):
- """Test operating fixed ip with invalid action should fail"""
- self.assertRaises(lib_exc.BadRequest,
- self.client.reserve_fixed_ip,
- self.ip, invalid_action="None")
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
deleted file mode 100644
index 786c7f0..0000000
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright 2014 NEC Technologies India 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.
-
-import netaddr
-
-from tempest.api.compute import base
-from tempest.common import utils
-from tempest import config
-from tempest.lib.common.utils import test_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-CONF = config.CONF
-
-
-# TODO(stephenfin): Remove this test class once the nova queens branch goes
-# into extended maintenance mode.
-class FloatingIPsBulkAdminTestJSON(base.BaseV2ComputeAdminTest):
- """Tests Floating IPs Bulk APIs that require admin privileges.
-
- API documentation - http://docs.openstack.org/api/openstack-compute/2/
- content/ext-os-floating-ips-bulk.html
- """
- max_microversion = '2.35'
- depends_on_nova_network = True
-
- @classmethod
- def setup_clients(cls):
- super(FloatingIPsBulkAdminTestJSON, cls).setup_clients()
- cls.client = cls.os_admin.floating_ips_bulk_client
-
- @classmethod
- def resource_setup(cls):
- super(FloatingIPsBulkAdminTestJSON, cls).resource_setup()
- cls.ip_range = CONF.validation.floating_ip_range
- cls.verify_unallocated_floating_ip_range(cls.ip_range)
-
- @classmethod
- def verify_unallocated_floating_ip_range(cls, ip_range):
- # Verify whether configure floating IP range is not already allocated.
- body = cls.client.list_floating_ips_bulk()['floating_ip_info']
- allocated_ips_list = map(lambda x: x['address'], body)
- for ip_addr in netaddr.IPNetwork(ip_range).iter_hosts():
- if str(ip_addr) in allocated_ips_list:
- msg = ("Configured unallocated floating IP range is already "
- "allocated. Configure the correct unallocated range "
- "as 'floating_ip_range'")
- raise exceptions.InvalidConfiguration(msg)
- return
-
- @decorators.idempotent_id('2c8f145f-8012-4cb8-ac7e-95a587f0e4ab')
- @utils.services('network')
- def test_create_list_delete_floating_ips_bulk(self):
- """Creating, listing and deleting the Floating IPs Bulk"""
- pool = 'test_pool'
- # NOTE(GMann): Reserving the IP range but those are not attached
- # anywhere. Using the below mentioned interface which is not ever
- # expected to be used. Clean Up has been done for created IP range
- interface = 'eth0'
- body = (self.client.create_floating_ips_bulk(self.ip_range,
- pool,
- interface)
- ['floating_ips_bulk_create'])
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.client.delete_floating_ips_bulk, self.ip_range)
- self.assertEqual(self.ip_range, body['ip_range'])
- ips_list = self.client.list_floating_ips_bulk()['floating_ip_info']
- self.assertNotEmpty(ips_list)
- for ip in netaddr.IPNetwork(self.ip_range).iter_hosts():
- self.assertIn(str(ip), map(lambda x: x['address'], ips_list))
- body = (self.client.delete_floating_ips_bulk(self.ip_range)
- ['floating_ips_bulk_delete'])
- self.assertEqual(self.ip_range, body)
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 30f3388..0d79570 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -68,7 +68,9 @@
"""Showing nova host details"""
hosts = self.client.list_hosts()['hosts']
- hosts = [host for host in hosts if host['service'] == 'compute']
+ hosts = [host for host in hosts if (
+ host['service'] == 'compute' and
+ not host['host_name'].endswith('-ironic'))]
self.assertNotEmpty(hosts)
for host in hosts:
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index f7c0dd9..d68334f 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -258,6 +258,8 @@
port = self.ports_client.show_port(port_id)['port']
return port['status'] == 'ACTIVE'
+ @decorators.unstable_test(bug='2024160')
+ @decorators.unstable_test(bug='2033887')
@decorators.attr(type='multinode')
@decorators.idempotent_id('0022c12e-a482-42b0-be2d-396b5f0cffe3')
@utils.requires_ext(service='network', extension='trunk')
@@ -303,13 +305,17 @@
min_microversion = '2.6'
max_microversion = 'latest'
+ @classmethod
+ def skip_checks(cls):
+ super(LiveMigrationRemoteConsolesV26Test, cls).skip_checks()
+ if not CONF.compute_feature_enabled.serial_console:
+ skip_msg = ("Serial console not supported.")
+ raise cls.skipException(skip_msg)
+ if not compute.is_scheduler_filter_enabled("DifferentHostFilter"):
+ raise cls.skipException("DifferentHostFilter is not available.")
+
@decorators.attr(type='multinode')
@decorators.idempotent_id('6190af80-513e-4f0f-90f2-9714e84955d7')
- @testtools.skipUnless(CONF.compute_feature_enabled.serial_console,
- 'Serial console not supported.')
- @testtools.skipUnless(
- compute.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
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index fb6376e..d7fb62d 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -64,5 +64,5 @@
configured_network = CONF.compute.fixed_network_name
self.assertIn(configured_network, [x['label'] for x in networks])
else:
- network_labels = [x['label'] for x in networks]
- self.assertNotEmpty(network_labels)
+ raise self.skipException(
+ "Environment has no known-for-sure existing network.")
diff --git a/tempest/api/compute/admin/test_server_external_events.py b/tempest/api/compute/admin/test_server_external_events.py
index 1c5c295..d867a39 100644
--- a/tempest/api/compute/admin/test_server_external_events.py
+++ b/tempest/api/compute/admin/test_server_external_events.py
@@ -19,6 +19,13 @@
class ServerExternalEventsTest(base.BaseV2ComputeAdminTest):
"""Test server external events test"""
+ # TODO(gmann): Remove the admin access to service user
+ # once nova change the default of this API to service
+ # role. To merge the nova changing the policy default
+ # we need to use token with admin as well as service
+ # role and later we can use only service token.
+ credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
+
@decorators.idempotent_id('6bbf4723-61d2-4372-af55-7ba27f1c9ba6')
def test_create_server_external_events(self):
"""Test create a server and add some external events"""
@@ -29,7 +36,7 @@
"server_uuid": server_id,
}
]
- client = self.os_admin.server_external_events_client
+ client = self.os_service_user.server_external_events_client
events_resp = client.create_server_external_events(
events=events)['events'][0]
self.assertEqual(server_id, events_resp['server_uuid'])
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index bc00f8c..321078c 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -25,6 +25,8 @@
class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Servers API using admin privileges"""
+ create_default_network = True
+
@classmethod
def setup_clients(cls):
super(ServersAdminTestJSON, cls).setup_clients()
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index 36148c5..9576b74 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -13,7 +13,6 @@
import time
from tempest.api.compute import base
-from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@@ -33,6 +32,8 @@
@classmethod
def skip_checks(cls):
super(TestVolumeSwapBase, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
if not CONF.compute_feature_enabled.swap_volume:
raise cls.skipException("Swapping volumes is not supported.")
@@ -81,7 +82,6 @@
# so it's marked as such.
@decorators.attr(type='slow')
@decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
- @utils.services('volume')
def test_volume_swap(self):
"""Test swapping of volume attached to server with admin user
@@ -183,7 +183,6 @@
# multiple computes but that would just side-step the underlying bug.
@decorators.skip_because(bug='1807723',
condition=CONF.compute.min_compute_nodes > 1)
- @utils.services('volume')
def test_volume_swap_with_multiattach(self):
"""Test swapping volume attached to multiple servers
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 260d4e0..d02532d 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -51,6 +51,9 @@
super(BaseV2ComputeTest, cls).skip_checks()
if not CONF.service_available.nova:
raise cls.skipException("Nova is not available")
+ if cls.create_default_network and not CONF.service_available.neutron:
+ raise cls.skipException("Neutron is not available")
+
api_version_utils.check_skip_with_microversion(
cls.min_microversion, cls.max_microversion,
CONF.compute.min_microversion, CONF.compute.max_microversion)
@@ -79,7 +82,6 @@
cls.flavors_client = cls.os_primary.flavors_client
cls.compute_images_client = cls.os_primary.compute_images_client
cls.extensions_client = cls.os_primary.extensions_client
- cls.floating_ip_pools_client = cls.os_primary.floating_ip_pools_client
cls.floating_ips_client = cls.os_primary.compute_floating_ips_client
cls.keypairs_client = cls.os_primary.keypairs_client
cls.security_group_rules_client = (
@@ -94,7 +96,6 @@
cls.snapshots_extensions_client =\
cls.os_primary.snapshots_extensions_client
cls.interfaces_client = cls.os_primary.interfaces_client
- cls.fixed_ips_client = cls.os_primary.fixed_ips_client
cls.availability_zone_client = cls.os_primary.availability_zone_client
cls.agents_client = cls.os_primary.agents_client
cls.aggregates_client = cls.os_primary.aggregates_client
@@ -112,43 +113,11 @@
cls.attachments_client = cls.os_primary.attachments_client_latest
cls.snapshots_client = cls.os_primary.snapshots_client_latest
if CONF.service_available.glance:
- if CONF.image_feature_enabled.api_v1:
- cls.images_client = cls.os_primary.image_client
- elif CONF.image_feature_enabled.api_v2:
+ if CONF.image_feature_enabled.api_v2:
cls.images_client = cls.os_primary.image_client_v2
else:
raise lib_exc.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
- cls._check_depends_on_nova_network()
-
- @classmethod
- def _check_depends_on_nova_network(cls):
- # Since nova-network APIs were removed from Nova in the Rocky release,
- # determine, based on the max version from the version document, if
- # the compute API is >Queens and if so, skip tests that rely on
- # nova-network.
- if not getattr(cls, 'depends_on_nova_network', False):
- return
- versions = cls.versions_client.list_versions()['versions']
- # Find the v2.1 version which will tell us our max version for the
- # compute API we're testing against.
- for version in versions:
- if version['id'] == 'v2.1':
- max_version = api_version_request.APIVersionRequest(
- version['version'])
- break
- else:
- LOG.warning(
- 'Unable to determine max v2.1 compute API version: %s',
- versions)
- return
-
- # The max compute API version in Queens is 2.60 so we cap
- # at that version.
- queens = api_version_request.APIVersionRequest('2.60')
- if max_version > queens:
- raise cls.skipException('nova-network is gone')
+ 'api_v2 must be True in [image-feature-enabled].')
@classmethod
def resource_setup(cls):
@@ -326,18 +295,18 @@
body['id'])
return body
- def wait_for(self, condition):
+ def wait_for(self, condition, *args):
"""Repeatedly calls condition() until a timeout."""
start_time = int(time.time())
while True:
try:
- condition()
+ condition(*args)
except Exception:
pass
else:
return
if int(time.time()) - start_time >= self.build_timeout:
- condition()
+ condition(*args)
return
time.sleep(self.build_interval)
@@ -462,9 +431,11 @@
self, server_id, new_flavor_id, wait_until='ACTIVE', **kwargs
):
"""resize and confirm_resize an server, waits for it to be ACTIVE."""
- self.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
- waiters.wait_for_server_status(self.servers_client, server_id,
- 'VERIFY_RESIZE')
+ body = self.servers_client.resize_server(
+ server_id, new_flavor_id, **kwargs)
+ waiters.wait_for_server_status(
+ self.servers_client, server_id, 'VERIFY_RESIZE',
+ request_id=body.response['x-openstack-request-id'])
self.servers_client.confirm_resize_server(server_id)
waiters.wait_for_server_status(
@@ -522,6 +493,8 @@
"""Create a volume and wait for it to become 'available'.
:param image_ref: Specify an image id to create a bootable volume.
+ :param wait_for_available: Wait until the volume becomes available
+ before returning
:param kwargs: other parameters to create volume.
:returns: The available volume.
"""
@@ -532,6 +505,7 @@
kwargs['display_name'] = vol_name
if image_ref is not None:
kwargs['imageRef'] = image_ref
+ wait = kwargs.pop('wait_for_available', True)
if CONF.volume.volume_type and 'volume_type' not in kwargs:
# If volume_type is not provided in config then no need to
# add a volume type and
@@ -547,8 +521,9 @@
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.volumes_client.delete_volume,
volume['id'])
- waiters.wait_for_volume_resource_status(cls.volumes_client,
- volume['id'], 'available')
+ if wait:
+ waiters.wait_for_volume_resource_status(cls.volumes_client,
+ volume['id'], 'available')
return volume
def _detach_volume(self, server, volume):
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 5d6a7d7..22b71fc 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -17,7 +17,6 @@
import random
from tempest.api.compute import base
-from tempest.common import image as common_image
from tempest.common import utils
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -48,23 +47,15 @@
'name': data_utils.rand_name('image'),
'container_format': CONF.image.container_formats[0],
'disk_format': CONF.image.disk_formats[0],
- 'min_ram': min_img_ram
+ 'min_ram': min_img_ram,
+ 'visibility': 'private'
}
- if CONF.image_feature_enabled.api_v1:
- params.update({'is_public': False})
- params = {'headers': common_image.image_meta_to_headers(**params)}
- else:
- params.update({'visibility': 'private'})
-
image = self.images_client.create_image(**params)
image = image['image'] if 'image' in image else image
self.addCleanup(self.images_client.delete_image, image['id'])
- if CONF.image_feature_enabled.api_v1:
- self.images_client.update_image(image['id'], data=image_file)
- else:
- self.images_client.store_image_file(image['id'], data=image_file)
+ self.images_client.store_image_file(image['id'], data=image_file)
self.assertEqual(min_img_ram, image['min_ram'])
diff --git a/tempest/api/compute/floating_ips/base.py b/tempest/api/compute/floating_ips/base.py
index 262a3c1..d6c302d 100644
--- a/tempest/api/compute/floating_ips/base.py
+++ b/tempest/api/compute/floating_ips/base.py
@@ -41,4 +41,3 @@
def setup_clients(cls):
super(BaseFloatingIPsTest, cls).setup_clients()
cls.client = cls.floating_ips_client
- cls.pools_client = cls.floating_ip_pools_client
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 6bfee95..fcbea2f 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -66,10 +66,3 @@
self.assertEqual(floating_ip_fixed_ip,
body['fixed_ip'])
self.assertEqual(floating_ip_id, body['id'])
-
- @decorators.idempotent_id('df389fc8-56f5-43cc-b290-20eda39854d3')
- def test_list_floating_ip_pools(self):
- """Test listing floating ip pools"""
- floating_ip_pools = self.pools_client.list_floating_ip_pools()
- self.assertNotEmpty(floating_ip_pools['floating_ip_pools'],
- "Expected floating IP Pools. Got zero.")
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index ece983d..f630bc8 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -16,7 +16,6 @@
import io
from tempest.api.compute import base
-from tempest.common import image as common_image
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -42,17 +41,11 @@
@classmethod
def setup_clients(cls):
super(ImagesMetadataTestJSON, cls).setup_clients()
- # Check if glance v1 is available to determine which client to use. We
- # prefer glance v1 for the compute API tests since the compute image
- # API proxy was written for glance v1.
- if CONF.image_feature_enabled.api_v1:
- cls.glance_client = cls.os_primary.image_client
- elif CONF.image_feature_enabled.api_v2:
+ if CONF.image_feature_enabled.api_v2:
cls.glance_client = cls.os_primary.image_client_v2
else:
raise exceptions.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
+ 'api_v2 must be True in [image-feature-enabled].')
cls.client = cls.compute_images_client
@classmethod
@@ -63,13 +56,9 @@
params = {
'name': data_utils.rand_name('image'),
'container_format': 'bare',
- 'disk_format': 'raw'
+ 'disk_format': 'raw',
+ 'visibility': 'private'
}
- if CONF.image_feature_enabled.api_v1:
- params.update({'is_public': False})
- params = {'headers': common_image.image_meta_to_headers(**params)}
- else:
- params.update({'visibility': 'private'})
body = cls.glance_client.create_image(**params)
body = body['image'] if 'image' in body else body
@@ -78,10 +67,7 @@
cls.glance_client.delete_image,
cls.image_id)
image_file = io.BytesIO((b'*' * 1024))
- if CONF.image_feature_enabled.api_v1:
- cls.glance_client.update_image(cls.image_id, data=image_file)
- else:
- cls.glance_client.store_image_file(cls.image_id, data=image_file)
+ cls.glance_client.store_image_file(cls.image_id, data=image_file)
waiters.wait_for_image_status(cls.client, cls.image_id, 'ACTIVE')
def setUp(self):
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index b9806c7..33a59ae 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -14,10 +14,13 @@
# under the License.
from tempest.api.compute import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
+CONF = config.CONF
+
class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest):
"""Negative tests of image metadata
@@ -28,6 +31,13 @@
max_microversion = '2.38'
@classmethod
+ def skip_checks(cls):
+ super(ImagesMetadataNegativeTestJSON, cls).skip_checks()
+ if not CONF.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
def setup_clients(cls):
super(ImagesMetadataNegativeTestJSON, cls).setup_clients()
cls.client = cls.compute_images_client
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 23f8326..2b859da 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -24,6 +24,8 @@
class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
"""Test server images API"""
+ create_default_network = True
+
@classmethod
def resource_setup(cls):
super(ImagesOneServerTestJSON, cls).resource_setup()
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 15b8a00..c6eff9b 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -19,7 +19,6 @@
import testtools
from tempest.api.compute import base
-from tempest.common import image as common_image
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -46,17 +45,11 @@
def setup_clients(cls):
super(ListImageFiltersTestJSON, cls).setup_clients()
cls.client = cls.compute_images_client
- # Check if glance v1 is available to determine which client to use. We
- # prefer glance v1 for the compute API tests since the compute image
- # API proxy was written for glance v1.
- if CONF.image_feature_enabled.api_v1:
- cls.glance_client = cls.os_primary.image_client
- elif CONF.image_feature_enabled.api_v2:
+ if CONF.image_feature_enabled.api_v2:
cls.glance_client = cls.os_primary.image_client_v2
else:
raise exceptions.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
+ 'api_v2 must be True in [image-feature-enabled].')
@classmethod
def resource_setup(cls):
@@ -66,14 +59,9 @@
params = {
'name': data_utils.rand_name(cls.__name__ + '-image'),
'container_format': 'bare',
- 'disk_format': 'raw'
+ 'disk_format': 'raw',
+ 'visibility': 'private'
}
- if CONF.image_feature_enabled.api_v1:
- params.update({'is_public': False})
- params = {'headers':
- common_image.image_meta_to_headers(**params)}
- else:
- params.update({'visibility': 'private'})
body = cls.glance_client.create_image(**params)
body = body['image'] if 'image' in body else body
@@ -86,10 +74,7 @@
# between created_at and updated_at.
time.sleep(1)
image_file = io.BytesIO((b'*' * 1024))
- if CONF.image_feature_enabled.api_v1:
- cls.glance_client.update_image(image_id, data=image_file)
- else:
- cls.glance_client.store_image_file(image_id, data=image_file)
+ cls.glance_client.store_image_file(image_id, data=image_file)
waiters.wait_for_image_status(cls.client, image_id, 'ACTIVE')
body = cls.client.show_image(image_id)['image']
return body
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index efecd6c..9b6bf84 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -295,8 +295,8 @@
def test_reassign_port_between_servers(self):
"""Tests reassigning port between servers
- 1. Create a port in Neutron.
- 2. Create two servers in Nova.
+ 1. Create two servers in Nova.
+ 2. Create a port in Neutron.
3. Attach the port to the first server.
4. Detach the port from the first server.
5. Attach the port to the second server.
@@ -304,11 +304,6 @@
"""
network = self.get_tenant_network()
network_id = network['id']
- port = self.ports_client.create_port(
- network_id=network_id,
- name=data_utils.rand_name(self.__class__.__name__))
- port_id = port['port']['id']
- self.addCleanup(self.ports_client.delete_port, port_id)
# NOTE(artom) We create two servers one at a time because
# create_test_server doesn't support multiple validatable servers.
@@ -318,12 +313,21 @@
def _create_validatable_server():
_, servers = compute.create_test_server(
self.os_primary, tenant_network=network,
- wait_until='ACTIVE', validatable=True,
+ validatable=True,
validation_resources=validation_resources)
return servers[0]
+ # NOTE(danms): We create these with no waiters because we will wait
+ # for them to be validatable (i.e. SSHABLE) below. That way some of
+ # the server creation overlap each other and with create_port.
servers = [_create_validatable_server(), _create_validatable_server()]
+ port = self.ports_client.create_port(
+ network_id=network_id,
+ name=data_utils.rand_name(self.__class__.__name__))
+ port_id = port['port']['id']
+ self.addCleanup(self.ports_client.delete_port, port_id)
+
# add our cleanups for the servers since we bypassed the base class
for server in servers:
self.addCleanup(self.delete_server, server['id'])
@@ -332,7 +336,9 @@
# NOTE(mgoddard): Get detailed server to ensure addresses are
# present in fixed IP case.
server = self.servers_client.show_server(server['id'])['server']
- self._wait_for_validation(server, validation_resources)
+ compute.wait_for_ssh_or_ping(server, self.os_primary, network,
+ True, validation_resources,
+ 'SSHABLE', True)
# attach the port to the server
iface = self.interfaces_client.create_interface(
server['id'], port_id=port_id)['interfaceAttachment']
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index e1e7fda..a181839 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -34,13 +34,13 @@
LOG = logging.getLogger(__name__)
-class ServerActionsTestJSON(base.BaseV2ComputeTest):
+class ServerActionsBase(base.BaseV2ComputeTest):
"""Test server actions"""
def setUp(self):
# NOTE(afazekas): Normally we use the same server with all test cases,
# but if it has an issue, we build a new one
- super(ServerActionsTestJSON, self).setUp()
+ super().setUp()
# Check if the server is in a clean state after test
try:
self.validation_resources = self.get_class_validation_resources(
@@ -73,7 +73,7 @@
self.server_id, validatable=True, wait_until='SSHABLE')
def tearDown(self):
- super(ServerActionsTestJSON, self).tearDown()
+ super(ServerActionsBase, self).tearDown()
# NOTE(zhufl): Because server_check_teardown will raise Exception
# which will prevent other cleanup steps from being executed, so
# server_check_teardown should be called after super's tearDown.
@@ -82,51 +82,19 @@
@classmethod
def setup_credentials(cls):
cls.prepare_instance_network()
- super(ServerActionsTestJSON, cls).setup_credentials()
+ super(ServerActionsBase, cls).setup_credentials()
@classmethod
def setup_clients(cls):
- super(ServerActionsTestJSON, cls).setup_clients()
+ super(ServerActionsBase, cls).setup_clients()
cls.client = cls.servers_client
@classmethod
def resource_setup(cls):
- super(ServerActionsTestJSON, cls).resource_setup()
+ super(ServerActionsBase, cls).resource_setup()
cls.server_id = cls.recreate_server(None, validatable=True,
wait_until='SSHABLE')
- @decorators.idempotent_id('6158df09-4b82-4ab3-af6d-29cf36af858d')
- @testtools.skipUnless(CONF.compute_feature_enabled.change_password,
- 'Change password not available.')
- def test_change_server_password(self):
- """Test changing server's password
-
- The server's password should be set to the provided password and
- the user can authenticate with the new password.
- """
- # Since this test messes with the password and makes the
- # server unreachable, it should create its own server
- newserver = self.create_test_server(
- validatable=True,
- validation_resources=self.validation_resources,
- wait_until='ACTIVE')
- self.addCleanup(self.delete_server, newserver['id'])
- # The server's password should be set to the provided password
- new_password = 'Newpass1234'
- self.client.change_password(newserver['id'], adminPass=new_password)
- waiters.wait_for_server_status(self.client, newserver['id'], 'ACTIVE')
-
- if CONF.validation.run_validation:
- # Verify that the user can authenticate with the new password
- server = self.client.show_server(newserver['id'])['server']
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(server, self.validation_resources),
- self.ssh_user,
- new_password,
- server=server,
- servers_client=self.client)
- linux_client.validate_authentication()
-
def _test_reboot_server(self, reboot_type):
if CONF.validation.run_validation:
# Get the time the server was last rebooted,
@@ -159,68 +127,23 @@
self.assertGreater(new_boot_time, boot_time,
'%s > %s' % (new_boot_time, boot_time))
- @decorators.attr(type='smoke')
- @decorators.idempotent_id('2cb1baf6-ac8d-4429-bf0d-ba8a0ba53e32')
- def test_reboot_server_hard(self):
- """Test hard rebooting server
-
- The server should be power cycled.
- """
- self._test_reboot_server('HARD')
-
- @decorators.idempotent_id('1d1c9104-1b0a-11e7-a3d4-fa163e65f5ce')
- def test_remove_server_all_security_groups(self):
- """Test removing all security groups from server"""
- server = self.create_test_server(wait_until='ACTIVE')
-
- # Remove all Security group
- self.client.remove_security_group(
- server['id'], name=server['security_groups'][0]['name'])
-
- # Verify all Security group
- server = self.client.show_server(server['id'])['server']
- self.assertNotIn('security_groups', server)
-
- def _rebuild_server_and_check(self, image_ref, server):
- rebuilt_server = (self.client.rebuild_server(server['id'], image_ref)
- ['server'])
- if CONF.validation.run_validation:
- tenant_network = self.get_tenant_network()
- compute.wait_for_ssh_or_ping(
- server, self.os_primary, tenant_network,
- True, self.validation_resources, "SSHABLE", True)
- else:
- waiters.wait_for_server_status(self.client, server['id'],
- 'ACTIVE')
-
- msg = ('Server was not rebuilt to the original image. '
- 'The original image: {0}. The current image: {1}'
- .format(image_ref, rebuilt_server['image']['id']))
- self.assertEqual(image_ref, rebuilt_server['image']['id'], msg)
-
- def _test_rebuild_server(self):
+ def _test_rebuild_server(self, server_id):
# Get the IPs the server has before rebuilding it
- original_addresses = (self.client.show_server(self.server_id)['server']
+ original_addresses = (self.client.show_server(server_id)['server']
['addresses'])
# The server should be rebuilt using the provided image and data
meta = {'rebuild': 'server'}
new_name = data_utils.rand_name(self.__class__.__name__ + '-server')
password = 'rebuildPassw0rd'
rebuilt_server = self.client.rebuild_server(
- self.server_id,
+ server_id,
self.image_ref_alt,
name=new_name,
metadata=meta,
adminPass=password)['server']
- # If the server was rebuilt on a different image, restore it to the
- # original image once the test ends
- if self.image_ref_alt != self.image_ref:
- self.addCleanup(self._rebuild_server_and_check, self.image_ref,
- rebuilt_server)
-
# Verify the properties in the initial response are correct
- self.assertEqual(self.server_id, rebuilt_server['id'])
+ self.assertEqual(server_id, rebuilt_server['id'])
rebuilt_image_id = rebuilt_server['image']['id']
self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
@@ -250,86 +173,6 @@
servers_client=self.client)
linux_client.validate_authentication()
- @decorators.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c')
- def test_rebuild_server(self):
- """Test rebuilding server
-
- The server should be rebuilt using the provided image and data.
- """
- self._test_rebuild_server()
-
- @decorators.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d')
- def test_rebuild_server_in_stop_state(self):
- """Test rebuilding server in stop state
-
- The server in stop state should be rebuilt using the provided
- image and remain in SHUTOFF state.
- """
- server = self.client.show_server(self.server_id)['server']
- old_image = server['image']['id']
- new_image = (self.image_ref_alt
- if old_image == self.image_ref else self.image_ref)
- self.client.stop_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF')
- rebuilt_server = (self.client.rebuild_server(self.server_id, new_image)
- ['server'])
- # If the server was rebuilt on a different image, restore it to the
- # original image once the test ends
- if self.image_ref_alt != self.image_ref:
- self.addCleanup(self._rebuild_server_and_check, old_image, server)
-
- # Verify the properties in the initial response are correct
- self.assertEqual(self.server_id, rebuilt_server['id'])
- rebuilt_image_id = rebuilt_server['image']['id']
- self.assertEqual(new_image, rebuilt_image_id)
- self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
-
- # Verify the server properties after the rebuild completes
- waiters.wait_for_server_status(self.client,
- rebuilt_server['id'], 'SHUTOFF')
- server = self.client.show_server(rebuilt_server['id'])['server']
- rebuilt_image_id = server['image']['id']
- self.assertEqual(new_image, rebuilt_image_id)
-
- self.client.start_server(self.server_id)
-
- # NOTE(mriedem): Marked as slow because while rebuild and volume-backed is
- # common, we don't actually change the image (you can't with volume-backed
- # rebuild) so this isn't testing much outside normal rebuild
- # (and it's slow).
- @decorators.attr(type='slow')
- @decorators.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
- @utils.services('volume')
- def test_rebuild_server_with_volume_attached(self):
- """Test rebuilding server with volume attached
-
- The volume should be attached to the instance after rebuild.
- """
- # create a new volume and attach it to the server
- volume = self.create_volume()
-
- server = self.client.show_server(self.server_id)['server']
- self.attach_volume(server, volume)
-
- # run general rebuild test
- self._test_rebuild_server()
-
- # make sure the volume is attached to the instance after rebuild
- vol_after_rebuild = self.volumes_client.show_volume(volume['id'])
- vol_after_rebuild = vol_after_rebuild['volume']
- self.assertEqual('in-use', vol_after_rebuild['status'])
- self.assertEqual(self.server_id,
- vol_after_rebuild['attachments'][0]['server_id'])
- if CONF.validation.run_validation:
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(server, self.validation_resources),
- self.ssh_alt_user,
- password=None,
- pkey=self.validation_resources['keypair']['private_key'],
- server=server,
- servers_client=self.client)
- linux_client.validate_authentication()
-
def _test_resize_server_confirm(self, server_id, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
@@ -358,6 +201,82 @@
# NOTE(mriedem): tearDown requires the server to be started.
self.client.start_server(server_id)
+ def _get_output(self, server_id):
+ output = self.client.get_console_output(
+ server_id, length=3)['output']
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 3)
+
+ def _validate_url(self, url):
+ valid_scheme = ['http', 'https']
+ parsed_url = urlparse.urlparse(url)
+ self.assertNotEqual('None', parsed_url.port)
+ self.assertNotEqual('None', parsed_url.hostname)
+ self.assertIn(parsed_url.scheme, valid_scheme)
+
+
+class ServerActionsTestJSON(ServerActionsBase):
+ @decorators.idempotent_id('6158df09-4b82-4ab3-af6d-29cf36af858d')
+ @testtools.skipUnless(CONF.compute_feature_enabled.change_password,
+ 'Change password not available.')
+ def test_change_server_password(self):
+ """Test changing server's password
+
+ The server's password should be set to the provided password and
+ the user can authenticate with the new password.
+ """
+ # Since this test messes with the password and makes the
+ # server unreachable, it should create its own server
+ newserver = self.create_test_server(
+ validatable=True,
+ validation_resources=self.validation_resources,
+ wait_until='ACTIVE')
+ self.addCleanup(self.delete_server, newserver['id'])
+ # The server's password should be set to the provided password
+ new_password = 'Newpass1234'
+ self.client.change_password(newserver['id'], adminPass=new_password)
+ waiters.wait_for_server_status(self.client, newserver['id'], 'ACTIVE')
+
+ if CONF.validation.run_validation:
+ # Verify that the user can authenticate with the new password
+ server = self.client.show_server(newserver['id'])['server']
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server, self.validation_resources),
+ self.ssh_user,
+ new_password,
+ server=server,
+ servers_client=self.client)
+ linux_client.validate_authentication()
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('2cb1baf6-ac8d-4429-bf0d-ba8a0ba53e32')
+ def test_reboot_server_hard(self):
+ """Test hard rebooting server
+
+ The server should be power cycled.
+ """
+ self._test_reboot_server('HARD')
+
+ @decorators.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c')
+ def test_rebuild_server(self):
+ """Test rebuilding server
+
+ The server should be rebuilt using the provided image and data.
+ """
+ tenant_network = self.get_tenant_network()
+ _, servers = compute.create_test_server(
+ self.os_primary,
+ wait_until='ACTIVE',
+ tenant_network=tenant_network)
+ server = servers[0]
+
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.client, server['id'])
+ self.addCleanup(self.client.delete_server, server['id'])
+
+ self._test_rebuild_server(server_id=server['id'])
+
@decorators.idempotent_id('1499262a-9328-4eda-9068-db1ac57498d2')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@@ -365,6 +284,194 @@
"""Test resizing server and then confirming"""
self._test_resize_server_confirm(self.server_id, stop=False)
+ @decorators.idempotent_id('c03aab19-adb1-44f5-917d-c419577e9e68')
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ def test_resize_server_revert(self):
+ """Test resizing server and then reverting
+
+ The server's RAM and disk space should return to its original
+ values after a resize is reverted.
+ """
+
+ self.client.resize_server(self.server_id, self.flavor_ref_alt)
+ # NOTE(zhufl): Explicitly delete the server to get a new one for later
+ # tests. Avoids resize down race issues.
+ self.addCleanup(self.delete_server, self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id,
+ 'VERIFY_RESIZE')
+
+ self.client.revert_resize_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+
+ server = self.client.show_server(self.server_id)['server']
+ self.assert_flavor_equal(self.flavor_ref, server['flavor'])
+
+ @decorators.idempotent_id('4b8867e6-fffa-4d54-b1d1-6fdda57be2f3')
+ @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
+ 'Console output not supported.')
+ def test_get_console_output(self):
+ """Test getting console output for a server
+
+ Should be able to GET the console output for a given server_id and
+ number of lines.
+ """
+
+ # This reboot is necessary for outputting some console log after
+ # creating an instance backup. If an instance backup, the console
+ # log file is truncated and we cannot get any console log through
+ # "console-log" API.
+ # The detail is https://bugs.launchpad.net/nova/+bug/1251920
+ self.reboot_server(self.server_id, type='HARD')
+ self.wait_for(self._get_output, self.server_id)
+
+ @decorators.idempotent_id('bd61a9fd-062f-4670-972b-2d6c3e3b9e73')
+ @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+ 'Pause is not available.')
+ def test_pause_unpause_server(self):
+ """Test pausing and unpausing server"""
+ self.client.pause_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED')
+ self.client.unpause_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+
+ @decorators.idempotent_id('0d8ee21e-b749-462d-83da-b85b41c86c7f')
+ @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
+ 'Suspend is not available.')
+ def test_suspend_resume_server(self):
+ """Test suspending and resuming server"""
+ self.client.suspend_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id,
+ 'SUSPENDED')
+ self.client.resume_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+
+ @decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
+ def test_stop_start_server(self):
+ """Test stopping and starting server"""
+ self.client.stop_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF')
+ self.client.start_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+
+ @decorators.idempotent_id('80a8094c-211e-440a-ab88-9e59d556c7ee')
+ def test_lock_unlock_server(self):
+ """Test locking and unlocking server
+
+ Lock the server, and trying to stop it will fail because locked
+ server is not allowed to be stopped by non-admin user.
+ Then unlock the server, now the server can be stopped and started.
+ """
+ # Lock the server,try server stop(exceptions throw),unlock it and retry
+ self.client.lock_server(self.server_id)
+ self.addCleanup(self.client.unlock_server, self.server_id)
+ server = self.client.show_server(self.server_id)['server']
+ self.assertEqual(server['status'], 'ACTIVE')
+ # Locked server is not allowed to be stopped by non-admin user
+ self.assertRaises(lib_exc.Conflict,
+ self.client.stop_server, self.server_id)
+ self.client.unlock_server(self.server_id)
+ self.client.stop_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF')
+ self.client.start_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+
+
+class ServerActionsTestOtherA(ServerActionsBase):
+ @decorators.idempotent_id('1d1c9104-1b0a-11e7-a3d4-fa163e65f5ce')
+ def test_remove_server_all_security_groups(self):
+ """Test removing all security groups from server"""
+ server = self.create_test_server(wait_until='ACTIVE')
+
+ # Remove all Security group
+ self.client.remove_security_group(
+ server['id'], name=server['security_groups'][0]['name'])
+
+ # Verify all Security group
+ server = self.client.show_server(server['id'])['server']
+ self.assertNotIn('security_groups', server)
+
+ @decorators.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d')
+ def test_rebuild_server_in_stop_state(self):
+ """Test rebuilding server in stop state
+
+ The server in stop state should be rebuilt using the provided
+ image and remain in SHUTOFF state.
+ """
+ tenant_network = self.get_tenant_network()
+ _, servers = compute.create_test_server(
+ self.os_primary,
+ wait_until='ACTIVE',
+ tenant_network=tenant_network)
+ server = servers[0]
+
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.client, server['id'])
+ self.addCleanup(self.client.delete_server, server['id'])
+ server = self.client.show_server(server['id'])['server']
+ old_image = server['image']['id']
+ new_image = (self.image_ref_alt
+ if old_image == self.image_ref else self.image_ref)
+ self.client.stop_server(server['id'])
+ waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF')
+ rebuilt_server = (self.client.rebuild_server(server['id'], new_image)
+ ['server'])
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(server['id'], rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+ self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
+
+ # Verify the server properties after the rebuild completes
+ waiters.wait_for_server_status(self.client,
+ rebuilt_server['id'], 'SHUTOFF')
+ server = self.client.show_server(rebuilt_server['id'])['server']
+ rebuilt_image_id = server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+
+ # NOTE(mriedem): Marked as slow because while rebuild and volume-backed is
+ # common, we don't actually change the image (you can't with volume-backed
+ # rebuild) so this isn't testing much outside normal rebuild
+ # (and it's slow).
+ @decorators.attr(type='slow')
+ @decorators.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
+ @utils.services('volume')
+ def test_rebuild_server_with_volume_attached(self):
+ """Test rebuilding server with volume attached
+
+ The volume should be attached to the instance after rebuild.
+ """
+ # create a new volume and attach it to the server
+ volume = self.create_volume(wait_for_available=False)
+ network = self.get_tenant_network()
+ validation_resources = self.get_test_validation_resources(
+ self.os_primary)
+ _, servers = compute.create_test_server(
+ self.os_primary, tenant_network=network,
+ validatable=True,
+ validation_resources=validation_resources,
+ wait_until='SSHABLE')
+ server = servers[0]
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.client, server['id'])
+ self.addCleanup(self.client.delete_server, server['id'])
+
+ server = self.client.show_server(server['id'])['server']
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ self.attach_volume(server, volume)
+
+ # run general rebuild test
+ self._test_rebuild_server(server_id=server['id'])
+
+ # make sure the volume is attached to the instance after rebuild
+ vol_after_rebuild = self.volumes_client.show_volume(volume['id'])
+ vol_after_rebuild = vol_after_rebuild['volume']
+ self.assertEqual('in-use', vol_after_rebuild['status'])
+ self.assertEqual(server['id'],
+ vol_after_rebuild['attachments'][0]['server_id'])
+
@decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
@decorators.related_bug('1728603')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
@@ -402,6 +509,8 @@
servers_client=self.client)
linux_client.validate_authentication()
+
+class ServerActionsTestOtherB(ServerActionsBase):
@decorators.idempotent_id('138b131d-66df-48c9-a171-64f45eb92962')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@@ -409,29 +518,6 @@
"""Test resizing a stopped server and then confirming"""
self._test_resize_server_confirm(self.server_id, stop=True)
- @decorators.idempotent_id('c03aab19-adb1-44f5-917d-c419577e9e68')
- @testtools.skipUnless(CONF.compute_feature_enabled.resize,
- 'Resize not available.')
- def test_resize_server_revert(self):
- """Test resizing server and then reverting
-
- The server's RAM and disk space should return to its original
- values after a resize is reverted.
- """
-
- self.client.resize_server(self.server_id, self.flavor_ref_alt)
- # NOTE(zhufl): Explicitly delete the server to get a new one for later
- # tests. Avoids resize down race issues.
- self.addCleanup(self.delete_server, self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id,
- 'VERIFY_RESIZE')
-
- self.client.revert_resize_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-
- server = self.client.show_server(self.server_id)['server']
- self.assert_flavor_equal(self.flavor_ref, server['flavor'])
-
@decorators.idempotent_id('fbbf075f-a812-4022-bc5c-ccb8047eef12')
@decorators.related_bug('1737599')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
@@ -483,17 +569,11 @@
# create the first and the second backup
- # Check if glance v1 is available to determine which client to use. We
- # prefer glance v1 for the compute API tests since the compute image
- # API proxy was written for glance v1.
- if CONF.image_feature_enabled.api_v1:
- glance_client = self.os_primary.image_client
- elif CONF.image_feature_enabled.api_v2:
+ if CONF.image_feature_enabled.api_v2:
glance_client = self.os_primary.image_client_v2
else:
raise lib_exc.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
+ 'api_v2 must be True in [image-feature-enabled].')
backup1 = data_utils.rand_name('backup-1')
resp = self.client.create_backup(self.server_id,
@@ -549,16 +629,9 @@
'sort_key': 'created_at',
'sort_dir': 'asc'
}
- if CONF.image_feature_enabled.api_v1:
- for key, value in properties.items():
- params['property-%s' % key] = value
- image_list = glance_client.list_images(
- detail=True,
- **params)['images']
- else:
- # Additional properties are flattened in glance v2.
- params.update(properties)
- image_list = glance_client.list_images(params)['images']
+ # Additional properties are flattened in glance v2.
+ params.update(properties)
+ image_list = glance_client.list_images(params)['images']
self.assertEqual(2, len(image_list))
self.assertEqual((backup1, backup2),
@@ -582,11 +655,7 @@
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
glance_client.wait_for_resource_deletion(image1_id)
oldest_backup_exist = False
- if CONF.image_feature_enabled.api_v1:
- image_list = glance_client.list_images(
- detail=True, **params)['images']
- else:
- image_list = glance_client.list_images(params)['images']
+ image_list = glance_client.list_images(params)['images']
self.assertEqual(2, len(image_list),
'Unexpected number of images for '
'v2:test_create_backup; was the oldest backup not '
@@ -595,31 +664,6 @@
self.assertEqual((backup2, backup3),
(image_list[0]['name'], image_list[1]['name']))
- def _get_output(self):
- output = self.client.get_console_output(
- self.server_id, length=3)['output']
- self.assertTrue(output, "Console output was empty.")
- lines = len(output.split('\n'))
- self.assertEqual(lines, 3)
-
- @decorators.idempotent_id('4b8867e6-fffa-4d54-b1d1-6fdda57be2f3')
- @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
- 'Console output not supported.')
- def test_get_console_output(self):
- """Test getting console output for a server
-
- Should be able to GET the console output for a given server_id and
- number of lines.
- """
-
- # This reboot is necessary for outputting some console log after
- # creating an instance backup. If an instance backup, the console
- # log file is truncated and we cannot get any console log through
- # "console-log" API.
- # The detail is https://bugs.launchpad.net/nova/+bug/1251920
- self.reboot_server(self.server_id, type='HARD')
- self.wait_for(self._get_output)
-
@decorators.idempotent_id('89104062-69d8-4b19-a71b-f47b7af093d7')
@testtools.skipUnless(CONF.compute_feature_enabled.console_output,
'Console output not supported.')
@@ -643,6 +687,7 @@
self.wait_for(_check_full_length_console_log)
+ @decorators.skip_because(bug='2028851')
@decorators.idempotent_id('5b65d4e7-4ecd-437c-83c0-d6b79d927568')
@testtools.skipUnless(CONF.compute_feature_enabled.console_output,
'Console output not supported.')
@@ -661,28 +706,7 @@
self.client.stop_server(temp_server_id)
waiters.wait_for_server_status(self.client, temp_server_id, 'SHUTOFF')
- self.wait_for(self._get_output)
-
- @decorators.idempotent_id('bd61a9fd-062f-4670-972b-2d6c3e3b9e73')
- @testtools.skipUnless(CONF.compute_feature_enabled.pause,
- 'Pause is not available.')
- def test_pause_unpause_server(self):
- """Test pausing and unpausing server"""
- self.client.pause_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED')
- self.client.unpause_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-
- @decorators.idempotent_id('0d8ee21e-b749-462d-83da-b85b41c86c7f')
- @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
- 'Suspend is not available.')
- def test_suspend_resume_server(self):
- """Test suspending and resuming server"""
- self.client.suspend_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id,
- 'SUSPENDED')
- self.client.resume_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+ self.wait_for(self._get_output, temp_server_id)
@decorators.idempotent_id('77eba8e0-036e-4635-944b-f7a8f3b78dc9')
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
@@ -692,34 +716,26 @@
"""Test shelving and unshelving server"""
if CONF.image_feature_enabled.api_v2:
glance_client = self.os_primary.image_client_v2
- elif CONF.image_feature_enabled.api_v1:
- glance_client = self.os_primary.image_client
else:
raise lib_exc.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
+ 'api_v2 must be True in [image-feature-enabled].')
compute.shelve_server(self.client, self.server_id,
force_shelve_offload=True)
- def _unshelve_server():
- server_info = self.client.show_server(self.server_id)['server']
- if 'SHELVED' in server_info['status']:
- self.client.unshelve_server(self.server_id)
- self.addCleanup(_unshelve_server)
-
server = self.client.show_server(self.server_id)['server']
image_name = server['name'] + '-shelved'
params = {'name': image_name}
- if CONF.image_feature_enabled.api_v2:
- images = glance_client.list_images(params)['images']
- elif CONF.image_feature_enabled.api_v1:
- images = glance_client.list_images(
- detail=True, **params)['images']
+ images = glance_client.list_images(params)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_name, images[0]['name'])
- self.client.unshelve_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+ body = self.client.unshelve_server(self.server_id)
+ waiters.wait_for_server_status(
+ self.client,
+ self.server_id,
+ "ACTIVE",
+ request_id=body.response["x-openstack-request-id"],
+ )
glance_client.wait_for_resource_deletion(images[0]['id'])
@decorators.idempotent_id('8cf9f450-a871-42cf-9bef-77eba189c0b0')
@@ -737,43 +753,6 @@
compute.shelve_server(self.client, server['id'],
force_shelve_offload=True)
- @decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
- def test_stop_start_server(self):
- """Test stopping and starting server"""
- self.client.stop_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF')
- self.client.start_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-
- @decorators.idempotent_id('80a8094c-211e-440a-ab88-9e59d556c7ee')
- def test_lock_unlock_server(self):
- """Test locking and unlocking server
-
- Lock the server, and trying to stop it will fail because locked
- server is not allowed to be stopped by non-admin user.
- Then unlock the server, now the server can be stopped and started.
- """
- # Lock the server,try server stop(exceptions throw),unlock it and retry
- self.client.lock_server(self.server_id)
- self.addCleanup(self.client.unlock_server, self.server_id)
- server = self.client.show_server(self.server_id)['server']
- self.assertEqual(server['status'], 'ACTIVE')
- # Locked server is not allowed to be stopped by non-admin user
- self.assertRaises(lib_exc.Conflict,
- self.client.stop_server, self.server_id)
- self.client.unlock_server(self.server_id)
- self.client.stop_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF')
- self.client.start_server(self.server_id)
- waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-
- def _validate_url(self, url):
- valid_scheme = ['http', 'https']
- parsed_url = urlparse.urlparse(url)
- self.assertNotEqual('None', parsed_url.port)
- self.assertNotEqual('None', parsed_url.hostname)
- self.assertIn(parsed_url.scheme, valid_scheme)
-
@decorators.idempotent_id('c6bc11bf-592e-4015-9319-1c98dc64daf5')
@testtools.skipUnless(CONF.compute_feature_enabled.vnc_console,
'VNC Console feature is disabled.')
@@ -804,9 +783,15 @@
min_microversion = '2.47'
+ @classmethod
+ def skip_checks(cls):
+ if not CONF.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ super(ServersAaction247Test, cls).skip_checks()
+
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
'Snapshotting not available, backup not possible.')
- @utils.services('image')
@decorators.idempotent_id('252a4bdd-6366-4dae-9994-8c30aa660f23')
def test_create_backup(self):
server = self.create_test_server(wait_until='ACTIVE')
@@ -817,3 +802,114 @@
backup_type='daily',
rotation=2,
name=backup1)
+
+
+class ServerActionsV293TestJSON(base.BaseV2ComputeTest):
+
+ min_microversion = '2.93'
+ volume_min_microversion = '3.68'
+
+ @classmethod
+ def skip_checks(cls):
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
+ return super().skip_checks()
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.prepare_instance_network()
+ super(ServerActionsV293TestJSON, cls).setup_credentials()
+
+ @classmethod
+ def resource_setup(cls):
+ super(ServerActionsV293TestJSON, cls).resource_setup()
+ cls.server_id = cls.recreate_server(None, volume_backed=True,
+ validatable=True)
+
+ @decorators.idempotent_id('6652dab9-ea24-4c93-ab5a-93d79c3041cf')
+ def test_rebuild_volume_backed_server(self):
+ """Test rebuilding a volume backed server"""
+ self.validation_resources = self.get_class_validation_resources(
+ self.os_primary)
+ server = self.servers_client.show_server(self.server_id)['server']
+ volume_id = server['os-extended-volumes:volumes_attached'][0]['id']
+ volume_before_rebuild = self.volumes_client.show_volume(volume_id)
+ image_before_rebuild = (
+ volume_before_rebuild['volume']
+ ['volume_image_metadata']['image_id'])
+ # Verify that image inside volume is our initial image before rebuild
+ self.assertEqual(self.image_ref, image_before_rebuild)
+
+ # Authentication is attempted in the following order of priority:
+ # 1.The key passed in, if one was passed in.
+ # 2.Any key we can find through an SSH agent (if allowed).
+ # 3.Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
+ # ~/.ssh/ (if allowed).
+ # 4.Plain username/password auth, if a password was given.
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server, self.validation_resources),
+ self.ssh_user,
+ password=None,
+ pkey=self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
+ output = linux_client.exec_command('touch test_file')
+ # No output means success
+ self.assertEqual('', output.strip())
+
+ # The server should be rebuilt using the provided image and data
+ meta = {'rebuild': 'server'}
+ new_name = data_utils.rand_name(self.__class__.__name__ + '-server')
+ password = 'rebuildPassw0rd'
+ rebuilt_server = self.servers_client.rebuild_server(
+ server['id'],
+ self.image_ref_alt,
+ name=new_name,
+ metadata=meta,
+ adminPass=password)['server']
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(server['id'], rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']
+ # Since it is a volume backed server, image id will remain empty
+ self.assertEqual('', rebuilt_image_id)
+ self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
+
+ # Verify the server properties after the rebuild completes
+ waiters.wait_for_server_status(self.servers_client,
+ rebuilt_server['id'], 'ACTIVE')
+ server = self.servers_client.show_server(
+ rebuilt_server['id'])['server']
+ volume_id = server['os-extended-volumes:volumes_attached'][0]['id']
+ volume_after_rebuild = self.volumes_client.show_volume(volume_id)
+ image_after_rebuild = (
+ volume_after_rebuild['volume']
+ ['volume_image_metadata']['image_id'])
+
+ self.assertEqual(new_name, server['name'])
+ # Verify that volume ID remains same before and after rebuild
+ self.assertEqual(volume_before_rebuild['volume']['id'],
+ volume_after_rebuild['volume']['id'])
+ # Verify that image inside volume is our final image after rebuild
+ self.assertEqual(self.image_ref_alt, image_after_rebuild)
+
+ # Authentication is attempted in the following order of priority:
+ # 1.The key passed in, if one was passed in.
+ # 2.Any key we can find through an SSH agent (if allowed).
+ # 3.Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
+ # ~/.ssh/ (if allowed).
+ # 4.Plain username/password auth, if a password was given.
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(rebuilt_server, self.validation_resources),
+ self.ssh_alt_user,
+ password,
+ self.validation_resources['keypair']['private_key'],
+ server=rebuilt_server,
+ servers_client=self.servers_client)
+ linux_client.validate_authentication()
+ e = self.assertRaises(lib_exc.SSHExecCommandFailed,
+ linux_client.exec_command,
+ 'cat test_file')
+ # If we rebuilt the boot volume, then we should not find
+ # the file we touched.
+ self.assertIn('No such file or directory', str(e))
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index 5a3f5d0..978a9da 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.api.compute import base
-from tempest.common import utils
from tempest.lib import decorators
@@ -35,7 +34,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('6eb718c0-02d9-4d5e-acd1-4e0c269cef39')
- @utils.services('network')
def test_list_server_addresses(self):
"""Test listing server address
@@ -52,7 +50,6 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('87bbc374-5538-4f64-b673-2b0e4443cc30')
- @utils.services('network')
def test_list_server_addresses_by_network(self):
"""Test listing server addresses filtered by network addresses
diff --git a/tempest/api/compute/servers/test_server_addresses_negative.py b/tempest/api/compute/servers/test_server_addresses_negative.py
index e7444d2..bb21594 100644
--- a/tempest/api/compute/servers/test_server_addresses_negative.py
+++ b/tempest/api/compute/servers/test_server_addresses_negative.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.api.compute import base
-from tempest.common import utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -35,7 +34,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('02c3f645-2d2e-4417-8525-68c0407d001b')
- @utils.services('network')
def test_list_server_addresses_invalid_server_id(self):
"""List addresses request should fail if server id not in system"""
self.assertRaises(lib_exc.NotFound, self.client.list_addresses,
@@ -43,7 +41,6 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a2ab5144-78c0-4942-a0ed-cc8edccfd9ba')
- @utils.services('network')
def test_list_server_addresses_by_network_neg(self):
"""List addresses by network should fail if network name not valid"""
self.assertRaises(lib_exc.NotFound,
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 716ecda..97c2774 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -239,13 +239,15 @@
# after unrescue the server. Due to that we need to make
# server SSHable before it try to detach, more details are
# in bug#1960346
+ volume = self.create_volume(wait_for_available=False)
validation_resources = self.get_class_validation_resources(
self.os_primary)
server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio', validatable=True,
validation_resources=validation_resources, wait_until="SSHABLE")
server = self.servers_client.show_server(server['id'])['server']
- volume = self.create_volume()
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
self.attach_volume(server, volume)
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'in-use')
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 4f85048..bd383d3 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -508,10 +508,7 @@
server = self.client.show_server(self.server_id)['server']
image_name = server['name'] + '-shelved'
- if CONF.image_feature_enabled.api_v1:
- kwargs = {'name': image_name}
- else:
- kwargs = {'params': {'name': image_name}}
+ kwargs = {'params': {'name': image_name}}
images = self.images_client.list_images(**kwargs)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_name, images[0]['name'])
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
deleted file mode 100644
index b2e02c5..0000000
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import netaddr
-import testtools
-
-from tempest.api.compute import base
-from tempest.common import utils
-from tempest import config
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-CONF = config.CONF
-
-
-# TODO(mriedem): Remove this test class once the nova queens branch goes into
-# extended maintenance mode.
-class VirtualInterfacesTestJSON(base.BaseV2ComputeTest):
- """Test virtual interfaces API with compute microversion less than 2.44"""
-
- max_microversion = '2.43'
-
- depends_on_nova_network = True
-
- create_default_network = True
-
- @classmethod
- def setup_clients(cls):
- super(VirtualInterfacesTestJSON, cls).setup_clients()
- cls.client = cls.servers_client
-
- @classmethod
- def resource_setup(cls):
- super(VirtualInterfacesTestJSON, cls).resource_setup()
- cls.server = cls.create_test_server(wait_until='ACTIVE')
-
- @decorators.idempotent_id('96c4e2ef-5e4d-4d7f-87f5-fed6dca18016')
- @utils.services('network')
- def test_list_virtual_interfaces(self):
- """Test listing virtual interfaces of a server"""
- if CONF.service_available.neutron:
- with testtools.ExpectedException(exceptions.BadRequest):
- self.client.list_virtual_interfaces(self.server['id'])
- else:
- output = self.client.list_virtual_interfaces(self.server['id'])
- virt_ifaces = output['virtual_interfaces']
- self.assertNotEmpty(virt_ifaces,
- 'Expected virtual interfaces, got 0 '
- 'interfaces.')
- for virt_iface in virt_ifaces:
- mac_address = virt_iface['mac_address']
- self.assertTrue(netaddr.valid_mac(mac_address),
- "Invalid mac address detected. mac address: %s"
- % mac_address)
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
deleted file mode 100644
index 5667281..0000000
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.compute import base
-from tempest.common import utils
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-
-# TODO(mriedem): Remove this test class once the nova queens branch goes into
-# extended maintenance mode.
-class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest):
- """Negative tests of virtual interfaces API
-
- Negative tests of virtual interfaces API for compute microversion less
- than 2.44.
- """
-
- max_microversion = '2.43'
-
- depends_on_nova_network = True
-
- @classmethod
- def setup_credentials(cls):
- # For this test no network resources are needed
- cls.set_network_resources()
- super(VirtualInterfacesNegativeTestJSON, cls).setup_credentials()
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('64ebd03c-1089-4306-93fa-60f5eb5c803c')
- @utils.services('network')
- def test_list_virtual_interfaces_invalid_server_id(self):
- """Test listing virtual interfaces of an invalid server should fail"""
- invalid_server_id = data_utils.rand_uuid()
- self.assertRaises(lib_exc.NotFound,
- self.servers_client.list_virtual_interfaces,
- invalid_server_id)
diff --git a/tempest/api/compute/test_tenant_networks.py b/tempest/api/compute/test_tenant_networks.py
index 17f4b80..da28b9b 100644
--- a/tempest/api/compute/test_tenant_networks.py
+++ b/tempest/api/compute/test_tenant_networks.py
@@ -14,8 +14,11 @@
from tempest.api.compute import base
from tempest.common import utils
+from tempest import config
from tempest.lib import decorators
+CONF = config.CONF
+
class ComputeTenantNetworksTest(base.BaseV2ComputeTest):
"""Test compute tenant networks API with microversion less than 2.36"""
@@ -23,6 +26,14 @@
max_microversion = '2.35'
@classmethod
+ def skip_checks(cls):
+ super(ComputeTenantNetworksTest, cls).skip_checks()
+ if not CONF.service_available.neutron:
+ skip_msg = (
+ "%s skipped as Neutron is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
def resource_setup(cls):
super(ComputeTenantNetworksTest, cls).resource_setup()
cls.client = cls.os_primary.tenant_networks_client
diff --git a/tempest/api/identity/v3/test_access_rules.py b/tempest/api/identity/v3/test_access_rules.py
index 608eb59..64a6959 100644
--- a/tempest/api/identity/v3/test_access_rules.py
+++ b/tempest/api/identity/v3/test_access_rules.py
@@ -17,6 +17,7 @@
from tempest.api.identity import base
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
@@ -37,10 +38,6 @@
super(AccessRulesV3Test, cls).resource_setup()
cls.user_id = cls.os_primary.credentials.user_id
cls.project_id = cls.os_primary.credentials.project_id
-
- def setUp(self):
- super(AccessRulesV3Test, self).setUp()
- ac = self.non_admin_app_creds_client
access_rules = [
{
"path": "/v2.1/servers/*/ips",
@@ -48,11 +45,15 @@
"service": "compute"
}
]
- self.app_cred = ac.create_application_credential(
- self.user_id,
+ cls.ac = cls.non_admin_app_creds_client
+ cls.app_cred = cls.ac.create_application_credential(
+ cls.user_id,
name=data_utils.rand_name('application_credential'),
access_rules=access_rules
)['application_credential']
+ cls.addClassResourceCleanup(
+ cls.ac.delete_application_credential,
+ cls.user_id, cls.app_cred['id'])
@decorators.idempotent_id('2354c498-5119-4ba5-9f0d-44f16f78fb0e')
def test_list_access_rules(self):
@@ -67,18 +68,33 @@
@decorators.idempotent_id('278757e9-e193-4bf8-adf2-0b0a229a17d0')
def test_delete_access_rule(self):
- access_rule_id = self.app_cred['access_rules'][0]['id']
- app_cred_id = self.app_cred['id']
+ access_rules = [
+ {
+ "path": "/v2.1/servers/*/ips",
+ "method": "GET",
+ "service": "monitoring"
+ }
+ ]
+ app_cred = self.ac.create_application_credential(
+ self.user_id,
+ name=data_utils.rand_name('application_credential'),
+ access_rules=access_rules
+ )['application_credential']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.ac.delete_application_credential,
+ self.user_id, app_cred['id'])
+ access_rule_id = app_cred['access_rules'][0]['id']
self.assertRaises(
lib_exc.Forbidden,
self.non_admin_access_rules_client.delete_access_rule,
self.user_id,
access_rule_id)
- self.non_admin_app_creds_client.delete_application_credential(
- self.user_id, app_cred_id)
+ self.ac.delete_application_credential(
+ self.user_id, app_cred['id'])
ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
- self.assertEqual(1, len(ar['access_rules']))
+ self.assertIn(access_rule_id, [x['id'] for x in ar['access_rules']])
self.non_admin_access_rules_client.delete_access_rule(
self.user_id, access_rule_id)
ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
- self.assertEqual(0, len(ar['access_rules']))
+ self.assertNotIn(access_rule_id, [x['id'] for x in ar['access_rules']])
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index dc6dd4a..53814ad 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -31,6 +31,12 @@
"""Test identity user password"""
@classmethod
+ def skip_checks(cls):
+ super(IdentityV3UsersTest, cls).skip_checks()
+ if not CONF.identity_feature_enabled.security_compliance:
+ raise cls.skipException("Security compliance not available.")
+
+ @classmethod
def resource_setup(cls):
super(IdentityV3UsersTest, cls).resource_setup()
cls.creds = cls.os_primary.credentials
@@ -77,13 +83,15 @@
time.sleep(1)
self.non_admin_users_client.auth_provider.set_auth()
- @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
- 'Security compliance not available.')
@decorators.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an '
'immutable user source and solely '
'provides read-only access to users.')
+ @testtools.skipIf(CONF.identity.user_minimum_password_age > 0,
+ 'Skipped because password cannot '
+ 'be changed immediately, resulting '
+ 'in failed password update.')
def test_user_update_own_password(self):
"""Test updating user's own password"""
old_pass = self.creds.password
@@ -107,13 +115,15 @@
user_id=self.user_id,
password=old_pass)
- @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
- 'Security compliance not available.')
@decorators.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516')
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an '
'immutable user source and solely '
'provides read-only access to users.')
+ @testtools.skipIf(CONF.identity.user_minimum_password_age > 0,
+ 'Skipped because password cannot '
+ 'be changed immediately, resulting '
+ 'in failed password update.')
def test_password_history_check_self_service_api(self):
"""Test checking password changing history"""
old_pass = self.creds.password
@@ -142,8 +152,6 @@
# A different password can be set
self._update_password(original_password=new_pass1, password=new_pass2)
- @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
- 'Security compliance not available.')
@decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
def test_user_account_lockout(self):
"""Test locking out user account after failure attempts"""
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 23e7fd8..7bae712 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import io
+import time
-from tempest.common import image as common_image
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
@@ -22,6 +21,7 @@
import tempest.test
CONF = config.CONF
+BAD_REQUEST_RETRIES = 3
class BaseImageTest(tempest.test.BaseTestCase):
@@ -54,17 +54,7 @@
name = data_utils.rand_name(cls.__name__ + "-image")
kwargs['name'] = name
- params = cls._get_create_params(**kwargs)
- if data:
- # NOTE: On glance v1 API, the data should be passed on
- # a header. Then here handles the data separately.
- params['data'] = data
-
- image = cls.client.create_image(**params)
- # Image objects returned by the v1 client have the image
- # data inside a dict that is keyed against 'image'.
- if 'image' in image:
- image = image['image']
+ image = cls.client.create_image(**kwargs)
cls.created_images.append(image['id'])
cls.addClassResourceCleanup(cls.client.wait_for_resource_deletion,
image['id'])
@@ -72,54 +62,6 @@
cls.client.delete_image, image['id'])
return image
- @classmethod
- def _get_create_params(cls, **kwargs):
- return kwargs
-
-
-class BaseV1ImageTest(BaseImageTest):
-
- @classmethod
- def skip_checks(cls):
- super(BaseV1ImageTest, cls).skip_checks()
- if not CONF.image_feature_enabled.api_v1:
- msg = "Glance API v1 not supported"
- raise cls.skipException(msg)
-
- @classmethod
- def setup_clients(cls):
- super(BaseV1ImageTest, cls).setup_clients()
- cls.client = cls.os_primary.image_client
-
- @classmethod
- def _get_create_params(cls, **kwargs):
- return {'headers': common_image.image_meta_to_headers(**kwargs)}
-
-
-class BaseV1ImageMembersTest(BaseV1ImageTest):
-
- credentials = ['primary', 'alt']
-
- @classmethod
- def setup_clients(cls):
- super(BaseV1ImageMembersTest, cls).setup_clients()
- cls.image_member_client = cls.os_primary.image_member_client
- cls.alt_image_member_client = cls.os_alt.image_member_client
- cls.alt_img_cli = cls.os_alt.image_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseV1ImageMembersTest, cls).resource_setup()
- cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
-
- def _create_image(self):
- image_file = io.BytesIO(data_utils.random_bytes())
- image = self.create_image(container_format='bare',
- disk_format='raw',
- is_public=False,
- data=image_file)
- return image['id']
-
class BaseV2ImageTest(BaseImageTest):
@@ -159,6 +101,82 @@
pass
return stores
+ def _update_image_with_retries(self, image, patch):
+ # NOTE(danms): If glance was unable to fetch the remote image via
+ # HTTP, it will return BadRequest. Because this can be transient in
+ # CI, we try this a few times before we agree that it has failed
+ # for a reason worthy of failing the test.
+ for i in range(BAD_REQUEST_RETRIES):
+ try:
+ self.client.update_image(image, patch)
+ break
+ except exceptions.BadRequest:
+ if i + 1 == BAD_REQUEST_RETRIES:
+ raise
+ else:
+ time.sleep(1)
+
+ def check_set_location(self):
+ image = self.client.create_image(container_format='bare',
+ disk_format='raw')
+
+ # Locations should be empty when there is no data
+ self.assertEqual('queued', image['status'])
+ self.assertEqual([], image['locations'])
+
+ # Add a new location
+ new_loc = {'metadata': {'foo': 'bar'},
+ 'url': CONF.image.http_image}
+ self._update_image_with_retries(image['id'], [
+ dict(add='/locations/-', value=new_loc)])
+
+ # The image should now be active, with one location that looks
+ # like we expect
+ image = self.client.show_image(image['id'])
+ self.assertEqual(1, len(image['locations']),
+ 'Image should have one location but has %i' % (
+ len(image['locations'])))
+ self.assertEqual(new_loc['url'], image['locations'][0]['url'])
+ self.assertEqual('bar', image['locations'][0]['metadata'].get('foo'))
+ if 'direct_url' in image:
+ self.assertEqual(image['direct_url'], image['locations'][0]['url'])
+
+ # If we added the location directly, the image goes straight
+ # to active and no hashing is done
+ self.assertEqual('active', image['status'])
+ self.assertIsNone(None, image['os_hash_algo'])
+ self.assertIsNone(None, image['os_hash_value'])
+
+ return image
+
+ def check_set_multiple_locations(self):
+ image = self.check_set_location()
+
+ new_loc = {'metadata': {'speed': '88mph'},
+ 'url': '%s#new' % CONF.image.http_image}
+ self._update_image_with_retries(image['id'],
+ [dict(add='/locations/-',
+ value=new_loc)])
+
+ # The image should now have two locations and the last one
+ # (locations are ordered) should have the new URL.
+ image = self.client.show_image(image['id'])
+ self.assertEqual(2, len(image['locations']),
+ 'Image should have two locations but has %i' % (
+ len(image['locations'])))
+ self.assertEqual(new_loc['url'], image['locations'][1]['url'])
+
+ # The image should still be active and still have no hashes
+ self.assertEqual('active', image['status'])
+ self.assertIsNone(None, image['os_hash_algo'])
+ self.assertIsNone(None, image['os_hash_value'])
+
+ # The direct_url should still match the first location
+ if 'direct_url' in image:
+ self.assertEqual(image['direct_url'], image['locations'][0]['url'])
+
+ return image
+
class BaseV2MemberImageTest(BaseV2ImageTest):
diff --git a/tempest/api/image/v1/__init__.py b/tempest/api/image/v1/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/image/v1/__init__.py
+++ /dev/null
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
deleted file mode 100644
index 5e2c8af..0000000
--- a/tempest/api/image/v1/test_image_members.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2013 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.
-
-
-from tempest.api.image import base
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-
-class ImageMembersTest(base.BaseV1ImageMembersTest):
- """Test image members"""
-
- @decorators.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad')
- def test_add_image_member(self):
- """Test adding member for image"""
- image = self._create_image()
- self.image_member_client.create_image_member(image, self.alt_tenant_id)
- body = self.image_member_client.list_image_members(image)
- members = body['members']
- members = [member['member_id'] for member in members]
- self.assertIn(self.alt_tenant_id, members)
- # get image as alt user
- self.alt_img_cli.show_image(image)
-
- @decorators.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9')
- def test_get_shared_images(self):
- """Test getting shared images"""
- image = self._create_image()
- self.image_member_client.create_image_member(image, self.alt_tenant_id)
- share_image = self._create_image()
- self.image_member_client.create_image_member(share_image,
- self.alt_tenant_id)
- body = self.image_member_client.list_shared_images(
- self.alt_tenant_id)
- images = body['shared_images']
- images = [img['image_id'] for img in images]
- self.assertIn(share_image, images)
- self.assertIn(image, images)
-
- @decorators.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138')
- def test_remove_member(self):
- """Test removing member from image"""
- image_id = self._create_image()
- self.image_member_client.create_image_member(image_id,
- self.alt_tenant_id)
- self.image_member_client.delete_image_member(image_id,
- self.alt_tenant_id)
- body = self.image_member_client.list_image_members(image_id)
- members = body['members']
- self.assertEmpty(members)
- self.assertRaises(
- lib_exc.NotFound, self.alt_img_cli.show_image, image_id)
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
deleted file mode 100644
index 4e3c27c..0000000
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright 2013 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.
-
-from tempest.api.image import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-
-class ImageMembersNegativeTest(base.BaseV1ImageMembersTest):
- """Negative tests of image members"""
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('147a9536-18e3-45da-91ea-b037a028f364')
- def test_add_member_with_non_existing_image(self):
- """Add member with non existing image"""
- non_exist_image = data_utils.rand_uuid()
- self.assertRaises(lib_exc.NotFound,
- self.image_member_client.create_image_member,
- non_exist_image, self.alt_tenant_id)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('e1559f05-b667-4f1b-a7af-518b52dc0c0f')
- def test_delete_member_with_non_existing_image(self):
- """Delete member with non existing image"""
- non_exist_image = data_utils.rand_uuid()
- self.assertRaises(lib_exc.NotFound,
- self.image_member_client.delete_image_member,
- non_exist_image, self.alt_tenant_id)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('f5720333-dd69-4194-bb76-d2f048addd56')
- def test_delete_member_with_non_existing_tenant(self):
- """Delete member from image with non existing tenant"""
- image_id = self._create_image()
- non_exist_tenant = data_utils.rand_uuid_hex()
- self.assertRaises(lib_exc.NotFound,
- self.image_member_client.delete_image_member,
- image_id, non_exist_tenant)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('f25f89e4-0b6c-453b-a853-1f80b9d7ef26')
- def test_get_image_without_membership(self):
- """Get image without membership
-
- Image is hidden from another tenants.
- """
- image_id = self._create_image()
- self.assertRaises(lib_exc.NotFound,
- self.alt_img_cli.show_image,
- image_id)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
deleted file mode 100644
index 6fd6c4e..0000000
--- a/tempest/api/image/v1/test_images.py
+++ /dev/null
@@ -1,341 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import io
-
-from tempest.api.image import base
-from tempest.common import image as common_image
-from tempest.common import waiters
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-CONF = config.CONF
-
-
-def get_container_and_disk_format():
- a_formats = ['ami', 'ari', 'aki']
-
- container_format = CONF.image.container_formats[0]
-
- # In v1, If container_format is one of ['ami', 'ari', 'aki'], then
- # disk_format must be same with container_format.
- # If they are of different item sequence in tempest.conf, such as:
- # container_formats = ami,ari,aki,bare
- # disk_formats = ari,ami,aki,vhd
- # we can select one in disk_format list that is same with container_format.
- if container_format in a_formats:
- if container_format in CONF.image.disk_formats:
- disk_format = container_format
- else:
- msg = ("The container format and the disk format don't match. "
- "Container format: %(container)s, Disk format: %(disk)s." %
- {'container': container_format, 'disk':
- CONF.image.disk_formats})
- raise exceptions.InvalidConfiguration(msg)
- else:
- disk_format = CONF.image.disk_formats[0]
-
- return container_format, disk_format
-
-
-class CreateRegisterImagesTest(base.BaseV1ImageTest):
- """Here we test the registration and creation of images."""
-
- @decorators.idempotent_id('3027f8e6-3492-4a11-8575-c3293017af4d')
- def test_register_then_upload(self):
- """Register, then upload an image"""
- properties = {'prop1': 'val1'}
- container_format, disk_format = get_container_and_disk_format()
- image = self.create_image(name='New Name',
- container_format=container_format,
- disk_format=disk_format,
- is_public=False,
- properties=properties)
- self.assertEqual('New Name', image.get('name'))
- self.assertFalse(image.get('is_public'))
- self.assertEqual('queued', image.get('status'))
- for key, val in properties.items():
- self.assertEqual(val, image.get('properties')[key])
-
- # Now try uploading an image file
- image_file = io.BytesIO(data_utils.random_bytes())
- body = self.client.update_image(image['id'], data=image_file)['image']
- self.assertIn('size', body)
- self.assertEqual(1024, body.get('size'))
-
- @decorators.idempotent_id('69da74d9-68a9-404b-9664-ff7164ccb0f5')
- def test_register_remote_image(self):
- """Register a new remote image"""
- container_format, disk_format = get_container_and_disk_format()
- body = self.create_image(name='New Remote Image',
- container_format=container_format,
- disk_format=disk_format, is_public=False,
- location=CONF.image.http_image,
- properties={'key1': 'value1',
- 'key2': 'value2'})
- self.assertEqual('New Remote Image', body.get('name'))
- self.assertFalse(body.get('is_public'))
- self.assertEqual('active', body.get('status'))
- properties = body.get('properties')
- self.assertEqual(properties['key1'], 'value1')
- self.assertEqual(properties['key2'], 'value2')
-
- @decorators.idempotent_id('6d0e13a7-515b-460c-b91f-9f4793f09816')
- def test_register_http_image(self):
- """Register a new image from an http image path url"""
- container_format, disk_format = get_container_and_disk_format()
- image = self.create_image(name='New Http Image',
- container_format=container_format,
- disk_format=disk_format, is_public=False,
- copy_from=CONF.image.http_image)
- self.assertEqual('New Http Image', image.get('name'))
- self.assertFalse(image.get('is_public'))
- waiters.wait_for_image_status(self.client, image['id'], 'active')
- self.client.show_image(image['id'])
-
- @decorators.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
- def test_register_image_with_min_ram(self):
- """Register an image with min ram"""
- container_format, disk_format = get_container_and_disk_format()
- properties = {'prop1': 'val1'}
- body = self.create_image(name='New_image_with_min_ram',
- container_format=container_format,
- disk_format=disk_format,
- is_public=False,
- min_ram=40,
- properties=properties)
- self.assertEqual('New_image_with_min_ram', body.get('name'))
- self.assertFalse(body.get('is_public'))
- self.assertEqual('queued', body.get('status'))
- self.assertEqual(40, body.get('min_ram'))
- for key, val in properties.items():
- self.assertEqual(val, body.get('properties')[key])
- self.client.delete_image(body['id'])
-
-
-class ListImagesTest(base.BaseV1ImageTest):
- """Here we test the listing of image information"""
-
- @classmethod
- def skip_checks(cls):
- super(ListImagesTest, cls).skip_checks()
- if (len(CONF.image.container_formats) < 2 or
- len(CONF.image.disk_formats) < 2):
- skip_msg = ("%s skipped as multiple container formats "
- "or disk formats are not available." % cls.__name__)
- raise cls.skipException(skip_msg)
-
- @classmethod
- def resource_setup(cls):
- super(ListImagesTest, cls).resource_setup()
- # We add a few images here to test the listing functionality of
- # the images API
- a_formats = ['ami', 'ari', 'aki']
-
- (cls.container_format,
- container_format_alt) = CONF.image.container_formats[:2]
- cls.disk_format, cls.disk_format_alt = CONF.image.disk_formats[:2]
- if cls.container_format in a_formats:
- cls.disk_format = cls.container_format
- if container_format_alt in a_formats:
- cls.disk_format_alt = container_format_alt
-
- img1 = cls._create_remote_image('one', cls.container_format,
- cls.disk_format)
- img2 = cls._create_remote_image('two', container_format_alt,
- cls.disk_format_alt)
- img3 = cls._create_remote_image('dup', cls.container_format,
- cls.disk_format)
- img4 = cls._create_remote_image('dup', cls.container_format,
- cls.disk_format)
- img5 = cls._create_standard_image('1', container_format_alt,
- cls.disk_format_alt, 42)
- img6 = cls._create_standard_image('2', container_format_alt,
- cls.disk_format_alt, 142)
- img7 = cls._create_standard_image('33', cls.container_format,
- cls.disk_format, 142)
- img8 = cls._create_standard_image('33', cls.container_format,
- cls.disk_format, 142)
- cls.created_set = set(cls.created_images)
- # same container format
- cls.same_container_format_set = set((img1, img3, img4, img7, img8))
- # same disk format
- cls.same_disk_format_set = set((img2, img5, img6))
-
- # 1x with size 42
- cls.size42_set = set((img5,))
- # 3x with size 142
- cls.size142_set = set((img6, img7, img8,))
- # dup named
- cls.dup_set = set((img3, img4))
-
- @classmethod
- def _create_remote_image(cls, name, container_format, disk_format):
- """Create a new remote image and return newly-registered image-id"""
-
- name = 'New Remote Image %s' % name
- location = CONF.image.http_image
- image = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- is_public=False,
- location=location)
- return image['id']
-
- @classmethod
- def _create_standard_image(cls, name, container_format,
- disk_format, size):
- """Create a new standard image and return newly-registered image-id
-
- Note that the size of the new image is a random number between
- 1024 and 4096
- """
- image_file = io.BytesIO(data_utils.random_bytes(size))
- name = 'New Standard Image %s' % name
- image = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- is_public=False, data=image_file)
- return image['id']
-
- @decorators.idempotent_id('246178ab-3b33-4212-9a4b-a7fe8261794d')
- def test_index_no_params(self):
- """Simple test to see all fixture images returned"""
- images_list = self.client.list_images()['images']
- image_list = [image['id'] for image in images_list]
- for image_id in self.created_images:
- self.assertIn(image_id, image_list)
-
- @decorators.idempotent_id('f1755589-63d6-4468-b098-589820eb4031')
- def test_index_disk_format(self):
- """Test listing images by disk format"""
- images_list = self.client.list_images(
- disk_format=self.disk_format_alt)['images']
- for image in images_list:
- self.assertEqual(image['disk_format'], self.disk_format_alt)
- result_set = set(map(lambda x: x['id'], images_list))
- self.assertTrue(self.same_disk_format_set <= result_set)
- self.assertFalse(self.created_set - self.same_disk_format_set <=
- result_set)
-
- @decorators.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b')
- def test_index_container_format(self):
- """Test listing images by container format"""
- images_list = self.client.list_images(
- container_format=self.container_format)['images']
- for image in images_list:
- self.assertEqual(image['container_format'], self.container_format)
- result_set = set(map(lambda x: x['id'], images_list))
- self.assertTrue(self.same_container_format_set <= result_set)
- self.assertFalse(self.created_set - self.same_container_format_set <=
- result_set)
-
- @decorators.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14')
- def test_index_max_size(self):
- """Test listing images by max size"""
- images_list = self.client.list_images(size_max=42)['images']
- for image in images_list:
- self.assertLessEqual(image['size'], 42)
- result_set = set(map(lambda x: x['id'], images_list))
- self.assertTrue(self.size42_set <= result_set)
- self.assertFalse(self.created_set - self.size42_set <= result_set)
-
- @decorators.idempotent_id('6ffc16d0-4cbf-4401-95c8-4ac63eac34d8')
- def test_index_min_size(self):
- """Test listing images by min size"""
- images_list = self.client.list_images(size_min=142)['images']
- for image in images_list:
- self.assertGreaterEqual(image['size'], 142)
- result_set = set(map(lambda x: x['id'], images_list))
- self.assertTrue(self.size142_set <= result_set)
- self.assertFalse(self.size42_set <= result_set)
-
- @decorators.idempotent_id('e5dc26d9-9aa2-48dd-bda5-748e1445da98')
- def test_index_status_active_detail(self):
- """Test listing active images sorting by size in descending order"""
- images_list = self.client.list_images(detail=True,
- status='active',
- sort_key='size',
- sort_dir='desc')['images']
- top_size = images_list[0]['size'] # We have non-zero sized images
- for image in images_list:
- size = image['size']
- self.assertLessEqual(size, top_size)
- top_size = size
- self.assertEqual(image['status'], 'active')
-
- @decorators.idempotent_id('097af10a-bae8-4342-bff4-edf89969ed2a')
- def test_index_name(self):
- """Test listing images by its name"""
- images_list = self.client.list_images(
- detail=True,
- name='New Remote Image dup')['images']
- result_set = set(map(lambda x: x['id'], images_list))
- for image in images_list:
- self.assertEqual(image['name'], 'New Remote Image dup')
- self.assertTrue(self.dup_set <= result_set)
- self.assertFalse(self.created_set - self.dup_set <= result_set)
-
-
-class UpdateImageMetaTest(base.BaseV1ImageTest):
- """Test image metadata"""
-
- @classmethod
- def resource_setup(cls):
- super(UpdateImageMetaTest, cls).resource_setup()
- container_format, disk_format = get_container_and_disk_format()
- cls.image_id = cls._create_standard_image('1', container_format,
- disk_format, 42)
-
- @classmethod
- def _create_standard_image(cls, name, container_format,
- disk_format, size):
- """Create a new standard image and return newly-registered image-id"""
-
- image_file = io.BytesIO(data_utils.random_bytes(size))
- name = 'New Standard Image %s' % name
- image = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- is_public=False, data=image_file,
- properties={'key1': 'value1'})
- return image['id']
-
- @decorators.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
- def test_list_image_metadata(self):
- """Test listing image metadata"""
- # All metadata key/value pairs for an image should be returned
- resp = self.client.check_image(self.image_id)
- resp_metadata = common_image.get_image_meta_from_headers(resp)
- expected = {'key1': 'value1'}
- self.assertEqual(expected, resp_metadata['properties'])
-
- @decorators.idempotent_id('d6d7649c-08ce-440d-9ea7-e3dda552f33c')
- def test_update_image_metadata(self):
- """Test updating image metadata"""
- # The metadata for the image should match the updated values
- req_metadata = {'key1': 'alt1', 'key2': 'value2'}
- resp = self.client.check_image(self.image_id)
- metadata = common_image.get_image_meta_from_headers(resp)
- self.assertEqual(metadata['properties'], {'key1': 'value1'})
- metadata['properties'].update(req_metadata)
- headers = common_image.image_meta_to_headers(
- properties=metadata['properties'])
- self.client.update_image(self.image_id, headers=headers)
- resp = self.client.check_image(self.image_id)
- resp_metadata = common_image.get_image_meta_from_headers(resp)
- self.assertEqual(req_metadata, resp_metadata['properties'])
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
deleted file mode 100644
index 2af1288..0000000
--- a/tempest/api/image/v1/test_images_negative.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# Copyright 2013 IBM 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.api.image import base
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-
-class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest):
- """Here are negative tests for the deletion and creation of images."""
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('036ede36-6160-4463-8c01-c781eee6369d')
- def test_register_with_invalid_container_format(self):
- """Create image with invalid container format
-
- Negative tests for invalid data supplied to POST /images
- """
- self.assertRaises(lib_exc.BadRequest, self.client.create_image,
- headers={'x-image-meta-name': 'test',
- 'x-image-meta-container_format': 'wrong',
- 'x-image-meta-disk_format': 'vhd'})
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('993face5-921d-4e84-aabf-c1bba4234a67')
- def test_register_with_invalid_disk_format(self):
- """Create image with invalid disk format"""
- self.assertRaises(lib_exc.BadRequest, self.client.create_image,
- headers={'x-image-meta-name': 'test',
- 'x-image-meta-container_format': 'bare',
- 'x-image-meta-disk_format': 'wrong'})
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('ec652588-7e3c-4b67-a2f2-0fa96f57c8fc')
- def test_delete_non_existent_image(self):
- """Return an error while trying to delete a non-existent image"""
-
- non_existent_image_id = data_utils.rand_uuid()
- self.assertRaises(lib_exc.NotFound, self.client.delete_image,
- non_existent_image_id)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('04f72aa3-fcec-45a3-81a3-308ef7cc82bc')
- def test_delete_image_blank_id(self):
- """Return an error while trying to delete an image with blank Id"""
- self.assertRaises(lib_exc.NotFound, self.client.delete_image, '')
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('950e5054-a3c7-4dee-ada5-e576f1087abd')
- def test_delete_image_non_hex_string_id(self):
- """Return an error while trying to delete an image with non hex id"""
- invalid_image_id = data_utils.rand_uuid()[:-1] + "j"
- self.assertRaises(lib_exc.NotFound, self.client.delete_image,
- invalid_image_id)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('4ed757cd-450c-44b1-9fd1-c819748c650d')
- def test_delete_image_negative_image_id(self):
- """Return an error while trying to delete an image with negative id"""
- self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1)
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('a4a448ab-3db2-4d2d-b9b2-6a1271241dfe')
- def test_delete_image_id_over_character_limit(self):
- """Return an error while trying to delete image with id over limit"""
- overlimit_image_id = data_utils.rand_uuid() + "1"
- self.assertRaises(lib_exc.NotFound, self.client.delete_image,
- overlimit_image_id)
diff --git a/tempest/api/image/v2/admin/test_image_task.py b/tempest/api/image/v2/admin/test_image_task.py
new file mode 100644
index 0000000..9439e91
--- /dev/null
+++ b/tempest/api/image/v2/admin/test_image_task.py
@@ -0,0 +1,140 @@
+# Copyright 2023 Red Hat, Inc.
+# 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.image import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class ImageTaskCreate(base.BaseV2ImageAdminTest):
+ """Test image task operations"""
+
+ @classmethod
+ def skip_checks(cls):
+ # TODO(msava): Add additional skipcheck with task conversion_format and
+ # glance ceph backend then will be available
+ # in tempest image service config options.
+ super(ImageTaskCreate, cls).skip_checks()
+ if not CONF.image.http_image:
+ skip_msg = ("%s skipped as http_image is not available " %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(ImageTaskCreate, cls).resource_setup()
+
+ @staticmethod
+ def _prepare_image_tasks_param(type="import",
+ disk_format=['qcow2'],
+ image_from_format=['qcow2'],
+ image_location=CONF.image.http_image):
+ # TODO(msava): Need to add additional disk formats then
+ # task conversion_format and glance Ceph backend will be
+ # available in image service options
+ """Prepare image task params.
+ By default, will create task type 'import'
+
+ The same index is used for both params and creates a task
+ :param type Type of the task.
+ :param disk_format: Each format in the list is a different task.
+ :param image_from_format: Each format in the list is a different task.
+ :param image_location Location to import image from.
+ :return: A list with all task.
+ """
+ i = 0
+ tasks = list()
+ while i < len(disk_format):
+ image_name = data_utils.rand_name("task_image")
+ image_property = {"container_format": "bare",
+ "disk_format": disk_format[0],
+ "visibility": "public",
+ "name": image_name
+ }
+ task = {
+ "type": type,
+ "input": {
+ "image_properties": image_property,
+ "import_from_format": image_from_format[0],
+ "import_from": image_location
+ }
+ }
+ tasks.append(task)
+ i += 1
+ return tasks
+
+ def _verify_disk_format(self, task_body):
+ expected_disk_format = \
+ task_body['input']['image_properties']['disk_format']
+ image_id = task_body['result']['image_id']
+ observed_disk_format = self.admin_client.show_image(
+ image_id)['disk_format']
+ # If glance backend storage is Ceph glance will convert
+ # image to raw format.
+ # TODO(msava): Need to change next lines once task conversion_format
+ # and glance ceph backend will be available in image service options
+ if observed_disk_format == 'raw':
+ return
+ self.assertEqual(observed_disk_format, expected_disk_format,
+ message="Expected disk format not match ")
+
+ @decorators.skip_because(bug='2030527')
+ @decorators.idempotent_id('669d5387-0340-4abf-b62d-7cc89f539c8c')
+ def test_image_tasks_create(self):
+ """Test task type 'import' image """
+
+ # Prepare params for task type 'import'
+ tasks = self._prepare_image_tasks_param()
+
+ # Create task type 'import'
+ body = self.os_admin.tasks_client.create_task(**tasks[0])
+ task_id = body['id']
+ task_body = waiters.wait_for_tasks_status(self.os_admin.tasks_client,
+ task_id, 'success')
+ self.addCleanup(self.admin_client.delete_image,
+ task_body['result']['image_id'])
+ task_image_id = task_body['result']['image_id']
+ waiters.wait_for_image_status(self.client, task_image_id, 'active')
+ self._verify_disk_format(task_body)
+
+ # Verify disk format
+ image_body = self.client.show_image(task_image_id)
+ task_disk_format = \
+ task_body['input']['image_properties']['disk_format']
+ image_disk_format = image_body['disk_format']
+ self.assertEqual(
+ image_disk_format, task_disk_format,
+ message="Image Disc format %s not match to expected %s"
+ % (image_disk_format, task_disk_format))
+
+ @decorators.idempotent_id("ad6450c6-7060-4ee7-a2d1-41c2604b446c")
+ @decorators.attr(type=['negative'])
+ def test_task_create_fake_image_location(self):
+ http_fake_url = ''.join(
+ ["http://", data_utils.rand_name('dummy-img-file'), ".qcow2"])
+ task = self._prepare_image_tasks_param(
+ image_from_format=['qcow2'],
+ disk_format=['qcow2'],
+ image_location=http_fake_url)
+ body = self.os_admin.tasks_client.create_task(**task[0])
+ task_observed = \
+ waiters.wait_for_tasks_status(self.os_admin.tasks_client,
+ body['id'], 'failure')
+ task_observed = task_observed['status']
+ self.assertEqual(task_observed, 'failure')
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
index 733c778..ce50c5d 100644
--- a/tempest/api/image/v2/admin/test_images.py
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -20,6 +20,7 @@
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
@@ -59,6 +60,24 @@
self.assertNotEqual(created_image_info['owner'],
updated_image_info['owner'])
+ @decorators.idempotent_id('f6ab4aa0-035e-4664-9f2d-c57c6df50605')
+ def test_list_public_image(self):
+ """Test create image as admin and list public image as none admin"""
+ name = data_utils.rand_name(self.__class__.__name__ + '-Image')
+ image = self.admin_client.create_image(
+ name=name,
+ container_format='bare',
+ visibility='public',
+ disk_format='raw')
+ waiters.wait_for_image_status(self.admin_client, image['id'], 'queued')
+ created_image = self.admin_client.show_image(image['id'])
+ self.assertEqual(image['id'], created_image['id'])
+ self.addCleanup(self.admin_client.delete_image, image['id'])
+
+ images_list = self.client.list_images()['images']
+ fetched_images_id = [img['id'] for img in images_list]
+ self.assertIn(image['id'], fetched_images_id)
+
class ImportCopyImagesTest(base.BaseV2ImageAdminTest):
"""Test the import copy-image operations"""
@@ -120,3 +139,40 @@
self.assertEqual(0, len(failed_stores),
"Failed to copy the following stores: %s" %
str(failed_stores))
+
+
+class ImageLocationsAdminTest(base.BaseV2ImageAdminTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(ImageLocationsAdminTest, cls).skip_checks()
+ if not CONF.image_feature_enabled.manage_locations:
+ skip_msg = (
+ "%s skipped as show_multiple_locations is not available" % (
+ cls.__name__))
+ raise cls.skipException(skip_msg)
+
+ @decorators.idempotent_id('8a648de4-b745-4c28-a7b5-20de1c3da4d2')
+ def test_delete_locations(self):
+ image = self.check_set_multiple_locations()
+ expected_remaining_loc = image['locations'][1]
+
+ self.admin_client.update_image(image['id'], [
+ dict(remove='/locations/0')])
+
+ # The image should now have only the one location we did not delete
+ image = self.client.show_image(image['id'])
+ self.assertEqual(1, len(image['locations']),
+ 'Image should have one location but has %i' % (
+ len(image['locations'])))
+ self.assertEqual(expected_remaining_loc['url'],
+ image['locations'][0]['url'])
+
+ # The direct_url should now be the last remaining location
+ if 'direct_url' in image:
+ self.assertEqual(image['direct_url'], image['locations'][0]['url'])
+
+ # Removing the last location should be disallowed
+ self.assertRaises(lib_exc.Forbidden,
+ self.admin_client.update_image, image['id'], [
+ dict(remove='/locations/0')])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index b723977..977ad82 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -16,19 +16,18 @@
import io
import random
-import time
from oslo_log import log as logging
from tempest.api.image 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
CONF = config.CONF
LOG = logging.getLogger(__name__)
-BAD_REQUEST_RETRIES = 3
class ImportImagesTest(base.BaseV2ImageTest):
@@ -735,6 +734,30 @@
body = self.schemas_client.show_schema(schema)
self.assertEqual("images", body['name'])
+ @decorators.idempotent_id('d43f3efc-da4c-4af9-b636-868f0c6acedb')
+ def test_list_hidden_image(self):
+ image = self.client.create_image(os_hidden=True)
+ image = image['image'] if 'image' in image else image
+ self.addCleanup(self.client.wait_for_resource_deletion, image['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_image, image['id'])
+ images_list = self.client.list_images()['images']
+ fetched_images_id = [img['id'] for img in images_list]
+ self.assertNotIn(image['id'], fetched_images_id)
+
+ @decorators.idempotent_id('fdb96b81-257b-42ac-978b-ddeefa3760e4')
+ def test_list_update_hidden_image(self):
+ image = self.create_image()
+ images_list = self.client.list_images()['images']
+ fetched_images_id = [img['id'] for img in images_list]
+ self.assertIn(image['id'], fetched_images_id)
+
+ self.client.update_image(image['id'],
+ [dict(replace='/os_hidden', value=True)])
+ images_list = self.client.list_images()['images']
+ fetched_images_id = [img['id'] for img in images_list]
+ self.assertNotIn(image['id'], fetched_images_id)
+
class ListSharedImagesTest(base.BaseV2ImageTest):
"""Here we test the listing of a shared image information"""
@@ -808,89 +831,13 @@
return image
- def _check_set_location(self):
- image = self.client.create_image(container_format='bare',
- disk_format='raw')
-
- # Locations should be empty when there is no data
- self.assertEqual('queued', image['status'])
- self.assertEqual([], image['locations'])
-
- # Add a new location
- new_loc = {'metadata': {'foo': 'bar'},
- 'url': CONF.image.http_image}
- self._update_image_with_retries(image['id'], [
- dict(add='/locations/-', value=new_loc)])
-
- # The image should now be active, with one location that looks
- # like we expect
- image = self.client.show_image(image['id'])
- self.assertEqual(1, len(image['locations']),
- 'Image should have one location but has %i' % (
- len(image['locations'])))
- self.assertEqual(new_loc['url'], image['locations'][0]['url'])
- self.assertEqual('bar', image['locations'][0]['metadata'].get('foo'))
- if 'direct_url' in image:
- self.assertEqual(image['direct_url'], image['locations'][0]['url'])
-
- # If we added the location directly, the image goes straight
- # to active and no hashing is done
- self.assertEqual('active', image['status'])
- self.assertIsNone(None, image['os_hash_algo'])
- self.assertIsNone(None, image['os_hash_value'])
-
- return image
-
@decorators.idempotent_id('37599b8a-d5c0-4590-aee5-73878502be15')
def test_set_location(self):
- self._check_set_location()
-
- def _update_image_with_retries(self, image, patch):
- # NOTE(danms): If glance was unable to fetch the remote image via
- # HTTP, it will return BadRequest. Because this can be transient in
- # CI, we try this a few times before we agree that it has failed
- # for a reason worthy of failing the test.
- for i in range(BAD_REQUEST_RETRIES):
- try:
- self.client.update_image(image, patch)
- break
- except lib_exc.BadRequest:
- if i + 1 == BAD_REQUEST_RETRIES:
- raise
- else:
- time.sleep(1)
-
- def _check_set_multiple_locations(self):
- image = self._check_set_location()
-
- new_loc = {'metadata': {'speed': '88mph'},
- 'url': '%s#new' % CONF.image.http_image}
- self._update_image_with_retries(image['id'],
- [dict(add='/locations/-',
- value=new_loc)])
-
- # The image should now have two locations and the last one
- # (locations are ordered) should have the new URL.
- image = self.client.show_image(image['id'])
- self.assertEqual(2, len(image['locations']),
- 'Image should have two locations but has %i' % (
- len(image['locations'])))
- self.assertEqual(new_loc['url'], image['locations'][1]['url'])
-
- # The image should still be active and still have no hashes
- self.assertEqual('active', image['status'])
- self.assertIsNone(None, image['os_hash_algo'])
- self.assertIsNone(None, image['os_hash_value'])
-
- # The direct_url should still match the first location
- if 'direct_url' in image:
- self.assertEqual(image['direct_url'], image['locations'][0]['url'])
-
- return image
+ self.check_set_location()
@decorators.idempotent_id('bf6e0009-c039-4884-b498-db074caadb10')
def test_replace_location(self):
- image = self._check_set_multiple_locations()
+ image = self.check_set_multiple_locations()
original_locs = image['locations']
# Replacing with the exact thing should work
@@ -927,31 +874,6 @@
len(image['locations'])))
self.assertEqual(original_locs, image['locations'])
- @decorators.idempotent_id('8a648de4-b745-4c28-a7b5-20de1c3da4d2')
- def test_delete_locations(self):
- image = self._check_set_multiple_locations()
- expected_remaining_loc = image['locations'][1]
-
- self.client.update_image(image['id'], [
- dict(remove='/locations/0')])
-
- # The image should now have only the one location we did not delete
- image = self.client.show_image(image['id'])
- self.assertEqual(1, len(image['locations']),
- 'Image should have one location but has %i' % (
- len(image['locations'])))
- self.assertEqual(expected_remaining_loc['url'],
- image['locations'][0]['url'])
-
- # The direct_url should now be the last remaining location
- if 'direct_url' in image:
- self.assertEqual(image['direct_url'], image['locations'][0]['url'])
-
- # Removing the last location should be disallowed
- self.assertRaises(lib_exc.Forbidden,
- self.client.update_image, image['id'], [
- dict(remove='/locations/0')])
-
@decorators.idempotent_id('a9a20396-8399-4b36-909d-564949be098f')
def test_set_location_bad_scheme(self):
image = self.client.create_image(container_format='bare',
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index 3c0efee..b4bfc61 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -31,6 +31,15 @@
@classmethod
def resource_setup(cls):
super(DHCPAgentSchedulersTestJSON, cls).resource_setup()
+ # NOTE(slaweq): In some cases (like default ML2/OVN deployment)
+ # extension is enabled but there may not be any DHCP agent available.
+ # In such case those tests should be also skipped.
+ dhcp_agents = cls.admin_agents_client.list_agents(
+ agent_type="DHCP Agent")['agents']
+ if not dhcp_agents:
+ msg = ("At least one DHCP agent is required to be running in "
+ "the environment for those tests.")
+ raise cls.skipException(msg)
# Create a network and make sure it will be hosted by a
# dhcp agent: this is done by creating a regular port
cls.network = cls.create_network()
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
deleted file mode 100644
index 1ce9f47..0000000
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Copyright 2015 Cloudwatt
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.network import base
-from tempest.common import identity
-from tempest.common import utils
-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 QuotasNegativeTest(base.BaseAdminNetworkTest):
- """Tests the following operations in the Neutron API:
-
- set network quota and exceed this quota
-
- v2.0 of the API is assumed.
- It is also assumed that the per-project quota extension API is configured
- in /etc/neutron/neutron.conf as follows:
-
- quota_driver = neutron.db.quota.driver.DbQuotaDriver
- """
-
- @classmethod
- def skip_checks(cls):
- super(QuotasNegativeTest, cls).skip_checks()
- if not utils.is_extension_enabled('quotas', 'network'):
- msg = "quotas extension not enabled."
- raise cls.skipException(msg)
-
- def setUp(self):
- super(QuotasNegativeTest, self).setUp()
- name = data_utils.rand_name('test_project_')
- description = data_utils.rand_name('desc_')
- self.creds_client = identity.identity_utils(self.os_admin)
- self.project = self.creds_client.create_project(
- name=name, description=description)
- self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
- self.project['id'])
-
- def tearDown(self):
- super(QuotasNegativeTest, self).tearDown()
- self.credentials_provider.cleanup_default_secgroup(
- self.os_admin.security_groups_client, self.project['id'])
-
- @decorators.attr(type=['negative'])
- @decorators.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf')
- def test_network_quota_exceeding(self):
- """Test creating network when exceeding network quota will fail"""
- # Set the network quota to two
- self.admin_quotas_client.update_quotas(self.project['id'], network=2)
-
- # Create two networks
- n1 = self.admin_networks_client.create_network(
- project_id=self.project['id'])
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.admin_networks_client.delete_network,
- n1['network']['id'])
- n2 = self.admin_networks_client.create_network(
- project_id=self.project['id'])
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.admin_networks_client.delete_network,
- n2['network']['id'])
-
- # Try to create a third network while the quota is two
- with self.assertRaisesRegex(
- lib_exc.Conflict,
- r"Quota exceeded for resources: \['network'\].*"):
- n3 = self.admin_networks_client.create_network(
- project_id=self.project['id'])
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.admin_networks_client.delete_network,
- n3['network']['id'])
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
deleted file mode 100644
index d8db298..0000000
--- a/tempest/api/network/admin/test_quotas.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import testtools
-
-from tempest.api.network import base
-from tempest.common import identity
-from tempest.common import utils
-from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
-from tempest.lib import decorators
-
-
-class QuotasTest(base.BaseAdminNetworkTest):
- """Tests the following operations in the Neutron API:
-
- list quotas for projects who have non-default quota values
- show quotas for a specified project
- update quotas for a specified project
- reset quotas to default values for a specified project
-
- v2.0 of the API is assumed.
- It is also assumed that the per-project quota extension API is configured
- in /etc/neutron/neutron.conf as follows:
-
- quota_driver = neutron.db.quota.driver.DbQuotaDriver
- """
-
- @classmethod
- def skip_checks(cls):
- super(QuotasTest, cls).skip_checks()
- if not utils.is_extension_enabled('quotas', 'network'):
- msg = "quotas extension not enabled."
- raise cls.skipException(msg)
-
- def _check_quotas(self, new_quotas):
- # Add a project to conduct the test
- project = data_utils.rand_name('test_project_')
- description = data_utils.rand_name('desc_')
- project = identity.identity_utils(self.os_admin).create_project(
- name=project, description=description)
- project_id = project['id']
- self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
- project_id)
-
- # Change quotas for project
- quota_set = self.admin_quotas_client.update_quotas(
- project_id, **new_quotas)['quota']
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.admin_quotas_client.reset_quotas, project_id)
- for key, value in new_quotas.items():
- self.assertEqual(value, quota_set[key])
-
- # Confirm our project is listed among projects with non default quotas
- non_default_quotas = self.admin_quotas_client.list_quotas()
- found = False
- for qs in non_default_quotas['quotas']:
- if qs['project_id'] == project_id:
- found = True
- self.assertTrue(found)
-
- # Confirm from API quotas were changed as requested for project
- quota_set = self.admin_quotas_client.show_quotas(project_id)
- quota_set = quota_set['quota']
- for key, value in new_quotas.items():
- self.assertEqual(value, quota_set[key])
-
- # Reset quotas to default and confirm
- self.admin_quotas_client.reset_quotas(project_id)
- non_default_quotas = self.admin_quotas_client.list_quotas()
- for q in non_default_quotas['quotas']:
- self.assertNotEqual(project_id, q['project_id'])
- quota_set = self.admin_quotas_client.show_quotas(project_id)['quota']
- default_quotas = self.admin_quotas_client.show_default_quotas(
- project_id)['quota']
- self.assertEqual(default_quotas, quota_set)
-
- @decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb')
- def test_quotas(self):
- """Test update/list/show/reset of network quotas"""
- new_quotas = {'network': 0, 'port': 0}
- self._check_quotas(new_quotas)
-
- @testtools.skipUnless(utils.is_extension_enabled(
- 'quota_details', 'network'), 'Quota details extension not enabled.')
- @decorators.idempotent_id('7b05ec5f-bf44-43cb-b28f-ddd72a824288')
- def test_show_quota_details(self):
- """Test showing network quota details"""
- # Show quota details for an existing project
- quota_details = self.admin_quotas_client.show_quota_details(
- self.admin_quotas_client.tenant_id)['quota']
- expected_keys = ['used', 'limit', 'reserved']
- for resource_type in quota_details:
- for key in expected_keys:
- self.assertIn(key, quota_details[resource_type])
diff --git a/tempest/api/network/test_service_providers.py b/tempest/api/network/test_service_providers.py
index 5af5244..e203a2c 100644
--- a/tempest/api/network/test_service_providers.py
+++ b/tempest/api/network/test_service_providers.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.network import base
from tempest.common import utils
from tempest.lib import decorators
@@ -20,10 +18,14 @@
class ServiceProvidersTest(base.BaseNetworkTest):
"""Test network service providers"""
+ @classmethod
+ def skip_checks(cls):
+ super(ServiceProvidersTest, cls).skip_checks()
+ if not utils.is_extension_enabled('service-type', 'network'):
+ skip_msg = ("service-type extension not enabled.")
+ raise cls.skipException(skip_msg)
+
@decorators.idempotent_id('2cbbeea9-f010-40f6-8df5-4eaa0c918ea6')
- @testtools.skipUnless(
- utils.is_extension_enabled('service-type', 'network'),
- 'service-type extension not enabled.')
def test_service_providers_list(self):
"""Test listing network service providers"""
body = self.service_providers_client.list_service_providers()
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 7107dc4..8adbe7d 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -15,7 +15,10 @@
import time
+from oslo_log import log
+
from tempest.common import custom_matchers
+from tempest.common import object_storage
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -23,35 +26,7 @@
import tempest.test
CONF = config.CONF
-
-
-def delete_containers(containers, container_client, object_client):
- """Remove containers and all objects in them.
-
- The containers should be visible from the container_client given.
- Will not throw any error if the containers don't exist.
-
- :param containers: List of containers(or string of a container)
- to be deleted
- :param container_client: Client to be used to delete containers
- :param object_client: Client to be used to delete objects
- """
- if isinstance(containers, str):
- containers = [containers]
-
- for cont in containers:
- try:
- params = {'limit': 9999, 'format': 'json'}
- _, objlist = container_client.list_container_objects(cont, params)
- # delete every object in the container
- for obj in objlist:
- object_client.delete_object(cont, obj['name'])
- object_client.wait_for_resource_deletion(obj['name'], cont)
- # Verify resource deletion
- container_client.delete_container(cont)
- container_client.wait_for_resource_deletion(cont)
- except lib_exc.NotFound:
- pass
+LOG = log.getLogger(__name__)
class BaseObjectTest(tempest.test.BaseTestCase):
@@ -141,7 +116,8 @@
container_client = cls.container_client
if object_client is None:
object_client = cls.object_client
- delete_containers(cls.containers, container_client, object_client)
+ object_storage.delete_containers(cls.containers, container_client,
+ object_client)
def assertHeaders(self, resp, target, method):
"""Check the existence and the format of response headers"""
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 687fe57..0ecae85 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -16,6 +16,7 @@
import tempfile
from tempest.api.object_storage import base
+from tempest.common import object_storage
from tempest.common import utils
from tempest.lib import decorators
@@ -30,8 +31,9 @@
def tearDown(self):
# NOTE(andreaf) BulkTests needs to cleanup containers after each
# test is executed.
- base.delete_containers(self.containers, self.container_client,
- self.object_client)
+ object_storage.delete_containers(self.containers,
+ self.container_client,
+ self.object_client)
super(BulkTest, self).tearDown()
def _create_archive(self):
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 4966ec4..b7a413e 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -18,6 +18,7 @@
from tempest.api.object_storage import base
from tempest.common import custom_matchers
+from tempest.common import object_storage
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -43,7 +44,7 @@
for i in range(ord('a'), ord('f') + 1):
name = data_utils.rand_name(name='%s-' % bytes((i,)))
cls.container_client.update_container(name)
- cls.addClassResourceCleanup(base.delete_containers,
+ cls.addClassResourceCleanup(object_storage.delete_containers,
[name],
cls.container_client,
cls.object_client)
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 85e6ddb..347c79e 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -41,6 +41,7 @@
super(ObjectACLsNegativeTest, self).setUp()
self.container_name = data_utils.rand_name(name='TestContainer')
self.container_client.update_container(self.container_name)
+ self.containers.append(self.container_name)
@classmethod
def resource_cleanup(cls):
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index b31ff76..9b1d3c7 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -19,6 +19,7 @@
import testtools
from tempest.api.object_storage import base
+from tempest.common import object_storage
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -74,7 +75,7 @@
(cls.container_client_alt, cls.object_client_alt)
for cont_name, client in cls.clients.items():
client[0].create_container(cont_name)
- cls.addClassResourceCleanup(base.delete_containers,
+ cls.addClassResourceCleanup(object_storage.delete_containers,
cont_name,
client[0],
client[1])
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 7d5bd26..61b9136 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -1016,9 +1016,10 @@
super(PublicObjectTest, self).setUp()
self.container_name = data_utils.rand_name(name='TestContainer')
self.container_client.update_container(self.container_name)
+ self.containers.append(self.container_name)
def tearDown(self):
- self.delete_containers([self.container_name])
+ self.delete_containers()
super(PublicObjectTest, self).tearDown()
@decorators.idempotent_id('07c9cf95-c0d4-4b49-b9c8-0ef2c9b27193')
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index b64b172..2a1f63e 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.object_storage import base
+from tempest.common import object_storage
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -53,7 +54,7 @@
# create container
vers_container_name = data_utils.rand_name(name='TestVersionContainer')
resp, _ = self.container_client.update_container(vers_container_name)
- self.addCleanup(base.delete_containers,
+ self.addCleanup(object_storage.delete_containers,
[vers_container_name],
self.container_client,
self.object_client)
@@ -65,7 +66,7 @@
resp, _ = self.container_client.update_container(
base_container_name,
**headers)
- self.addCleanup(base.delete_containers,
+ self.addCleanup(object_storage.delete_containers,
[base_container_name],
self.container_client,
self.object_client)
diff --git a/tempest/api/volume/admin/test_encrypted_volumes_extend.py b/tempest/api/volume/admin/test_encrypted_volumes_extend.py
index e85a00d..4506389 100644
--- a/tempest/api/volume/admin/test_encrypted_volumes_extend.py
+++ b/tempest/api/volume/admin/test_encrypted_volumes_extend.py
@@ -14,7 +14,6 @@
from tempest.api.volume import base
from tempest.api.volume import test_volumes_extend as extend
-from tempest.common import utils
from tempest import config
from tempest.lib import decorators
@@ -25,23 +24,25 @@
base.BaseVolumeAdminTest):
"""Tests extending the size of an attached encrypted volume."""
+ @classmethod
+ def skip_checks(cls):
+ super(EncryptedVolumesExtendAttachedTest, cls).skip_checks()
+ if not CONF.service_available.nova:
+ skip_msg = ("%s skipped as Nova is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ if not CONF.volume_feature_enabled.extend_attached_encrypted_volume:
+ raise cls.skipException(
+ "Attached encrypted volume extend is disabled.")
+
@decorators.idempotent_id('e93243ec-7c37-4b5b-a099-ebf052c13216')
- @testtools.skipUnless(
- CONF.volume_feature_enabled.extend_attached_encrypted_volume,
- "Attached encrypted volume extend is disabled.")
- @utils.services('compute')
def test_extend_attached_encrypted_volume_luksv1(self):
"""LUKs v1 decrypts and extends through libvirt."""
volume = self.create_encrypted_volume(encryption_provider="luks")
self._test_extend_attached_volume(volume)
@decorators.idempotent_id('381a2a3a-b2f4-4631-a910-720881f2cc2f')
- @testtools.skipUnless(
- CONF.volume_feature_enabled.extend_attached_encrypted_volume,
- "Attached encrypted volume extend is disabled.")
@testtools.skipIf(CONF.volume.storage_protocol == 'ceph',
'Ceph only supports LUKSv2 if doing host attach.')
- @utils.services('compute')
def test_extend_attached_encrypted_volume_luksv2(self):
"""LUKs v2 decrypts and extends through os-brick."""
volume = self.create_encrypted_volume(encryption_provider="luks2")
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index ab0aa38..478bd16 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -31,6 +32,8 @@
managed by Cinder from a storage back end to Cinder
"""
+ create_default_network = True
+
@classmethod
def skip_checks(cls):
super(SnapshotManageAdminTest, cls).skip_checks()
@@ -46,8 +49,7 @@
"it should be a list of two elements")
raise exceptions.InvalidConfiguration(msg)
- @decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
- def test_unmanage_manage_snapshot(self):
+ def _test_unmanage_manage_snapshot(self, attached_volume=False):
"""Test unmanaging and managing volume snapshot"""
# Create a volume
volume = self.create_volume()
@@ -55,6 +57,13 @@
# Create a snapshot
snapshot = self.create_snapshot(volume_id=volume['id'])
+ if attached_volume:
+ # Create a server
+ server = self.create_server(wait_until='SSHABLE')
+ # Attach volume to instance
+ self.attach_volume(server['id'], volume['id'],
+ wait_for_detach=False)
+
# Unmanage the snapshot
# Unmanage snapshot function works almost the same as delete snapshot,
# but it does not delete the snapshot data
@@ -100,3 +109,17 @@
self.assertEqual(snapshot['size'], new_snapshot_info['size'])
for key in ['volume_id', 'name', 'description', 'metadata']:
self.assertEqual(snapshot_ref[key], new_snapshot_info[key])
+
+ @decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
+ def test_unmanage_manage_snapshot(self):
+ self._test_unmanage_manage_snapshot()
+
+ @decorators.idempotent_id('7c735385-e953-4198-8534-68137f72dbdc')
+ @utils.services('compute')
+ def test_snapshot_manage_with_attached_volume(self):
+ """Test manage a snapshot with an attached volume.
+
+ The case validates manage snapshot operation while
+ the parent volume is attached to an instance.
+ """
+ self._test_unmanage_manage_snapshot(attached_volume=True)
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index ecddfba..b6e9f32 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -83,7 +83,7 @@
server_id = self.create_server()['id']
volume_id = self.create_volume()['id']
- # Attach volume
+ # Request Cinder to map & export volume (it's not attached to instance)
self.volumes_client.attach_volume(
volume_id,
instance_uuid=server_id,
@@ -101,7 +101,9 @@
waiters.wait_for_volume_resource_status(self.volumes_client,
volume_id, 'error')
- # Force detach volume
+ # The force detach volume calls works because the volume is not really
+ # connected to the instance (it is safe), otherwise it would be
+ # rejected for security reasons (bug #2004555).
self.admin_volume_client.force_detach_volume(
volume_id, connector=None,
attachment_id=attachment['attachment_id'])
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 9ba9949..b9e1e58 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.common import compute
+from tempest.common import object_storage
from tempest.common import waiters
from tempest import config
from tempest.lib.common import api_version_utils
@@ -42,6 +43,10 @@
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ if cls.create_default_network and not CONF.service_available.neutron:
+ skip_msg = (
+ "%s skipped as Neutron is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
api_version_utils.check_skip_with_microversion(
cls.volume_min_microversion, cls.volume_max_microversion,
@@ -51,6 +56,8 @@
def setup_credentials(cls):
cls.set_network_resources(
network=cls.create_default_network,
+ router=cls.create_default_network,
+ dhcp=cls.create_default_network,
subnet=cls.create_default_network)
super(BaseVolumeTest, cls).setup_credentials()
@@ -62,6 +69,8 @@
if CONF.service_available.glance:
cls.images_client = cls.os_primary.image_client_v2
+ cls.container_client = cls.os_primary.container_client
+ cls.object_client = cls.os_primary.object_client
cls.backups_client = cls.os_primary.backups_client_latest
cls.volumes_client = cls.os_primary.volumes_client_latest
cls.messages_client = cls.os_primary.volume_messages_client_latest
@@ -168,16 +177,32 @@
snapshot['id'], 'available')
return snapshot
- def create_backup(self, volume_id, backup_client=None, **kwargs):
+ def create_backup(self, volume_id, backup_client=None, object_client=None,
+ container_client=None, **kwargs):
"""Wrapper utility that returns a test backup."""
if backup_client is None:
backup_client = self.backups_client
+ if container_client is None:
+ container_client = self.container_client
+ if object_client is None:
+ object_client = self.object_client
if 'name' not in kwargs:
name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
kwargs['name'] = name
+ if CONF.volume.backup_driver == "swift":
+ if 'container' not in kwargs:
+ cont_name = self.__class__.__name__ + '-backup-container'
+ cont = data_utils.rand_name(cont_name)
+ kwargs['container'] = cont
+
+ self.addCleanup(object_storage.delete_containers,
+ kwargs['container'], container_client,
+ object_client)
+
backup = backup_client.create_backup(
volume_id=volume_id, **kwargs)['backup']
+
# addCleanup uses list pop to cleanup. Wait should be added before
# the backup is deleted
self.addCleanup(backup_client.wait_for_resource_deletion,
@@ -223,6 +248,14 @@
'name',
data_utils.rand_name(self.__class__.__name__ + '-instance'))
+ if wait_until == 'SSHABLE' and not kwargs.get('validation_resources'):
+ # If we were asked for SSHABLE but were not provided with the
+ # required validation_resources and validatable flag, ensure we
+ # pass them to create_test_server() so that it will actually wait.
+ kwargs['validation_resources'] = (
+ self.get_test_validation_resources(self.os_primary))
+ kwargs['validatable'] = True
+
tenant_network = self.get_tenant_network()
body, _ = compute.create_test_server(
self.os_primary,
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 138d120..c775292 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
from testtools import matchers
from tempest.api.volume import base
@@ -29,6 +28,8 @@
class VolumesBackupsTest(base.BaseVolumeTest):
"""Test volumes backup"""
+ create_default_network = True
+
@classmethod
def skip_checks(cls):
super(VolumesBackupsTest, cls).skip_checks()
@@ -51,8 +52,6 @@
'available')
return restored_volume
- @testtools.skipIf(CONF.volume.storage_protocol == 'ceph',
- 'ceph does not support arbitrary container names')
@decorators.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
def test_volume_backup_create_get_detailed_list_restore_delete(self):
"""Test create/get/list/restore/delete volume backup
@@ -73,22 +72,24 @@
self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
# Create a backup
- backup_name = data_utils.rand_name(
+ kwargs = {}
+ kwargs["name"] = data_utils.rand_name(
self.__class__.__name__ + '-Backup')
- description = data_utils.rand_name("volume-backup-description")
- backup = self.create_backup(volume_id=volume['id'],
- name=backup_name,
- description=description,
- container='container')
- self.assertEqual(backup_name, backup['name'])
+ kwargs["description"] = data_utils.rand_name("backup-description")
+ if CONF.volume.backup_driver == "swift":
+ kwargs["container"] = data_utils.rand_name(
+ self.__class__.__name__ + '-Backup-container')
+ backup = self.create_backup(volume_id=volume['id'], **kwargs)
+ self.assertEqual(kwargs["name"], backup['name'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
# Get a given backup
backup = self.backups_client.show_backup(backup['id'])['backup']
- self.assertEqual(backup_name, backup['name'])
- self.assertEqual(description, backup['description'])
- self.assertEqual('container', backup['container'])
+ self.assertEqual(kwargs["name"], backup['name'])
+ self.assertEqual(kwargs["description"], backup['description'])
+ if CONF.volume.backup_driver == "swift":
+ self.assertEqual(kwargs["container"], backup['container'])
# Get all backups with detail
backups = self.backups_client.list_backups(detail=True)['backups']
@@ -114,10 +115,16 @@
is "available" or "in-use".
"""
# Create a server
- volume = self.create_volume()
+ volume = self.create_volume(wait_until=False)
self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
- server = self.create_server()
+ validation_resources = self.get_test_validation_resources(
+ self.os_primary)
+ server = self.create_server(wait_until='SSHABLE',
+ validation_resources=validation_resources,
+ validatable=True)
# Attach volume to instance
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
self.attach_volume(server['id'], volume['id'])
# Create backup using force flag
backup_name = data_utils.rand_name(
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index fcbc982..c766db8 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -18,7 +18,6 @@
import testtools
from tempest.api.volume import base
-from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@@ -46,6 +45,9 @@
@decorators.idempotent_id('86be1cba-2640-11e5-9c82-635fb964c912')
@testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
"Cinder volume snapshots are disabled")
+ @testtools.skipUnless(
+ CONF.volume_feature_enabled.extend_volume_with_snapshot,
+ "Extending volume with snapshot is disabled.")
def test_volume_extend_when_volume_has_snapshot(self):
"""Test extending a volume which has a snapshot"""
volume = self.create_volume()
@@ -114,7 +116,7 @@
if the action on the server fails.
"""
# Create a test server. Will be automatically cleaned up on teardown.
- server = self.create_server()
+ server = self.create_server(wait_until='SSHABLE')
# Attach the volume to the server and wait for the volume status to be
# "in-use".
self.attach_volume(server['id'], volume['id'])
@@ -178,10 +180,16 @@
class VolumesExtendAttachedTest(BaseVolumesExtendAttachedTest):
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesExtendAttachedTest, cls).skip_checks()
+ if not CONF.service_available.nova:
+ skip_msg = ("%s skipped as Nova is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ if not CONF.volume_feature_enabled.extend_attached_volume:
+ raise cls.skipException("Attached volume extend is disabled.")
+
@decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354')
- @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume,
- "Attached volume extend is disabled.")
- @utils.services('compute')
def test_extend_attached_volume(self):
volume = self.create_volume()
self._test_extend_attached_volume(volume)
diff --git a/tempest/clients.py b/tempest/clients.py
index 1aa34d0..5b31cf8 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -83,8 +83,6 @@
def _set_image_clients(self):
if CONF.service_available.glance:
- self.image_client = self.image_v1.ImagesClient()
- self.image_member_client = self.image_v1.ImageMembersClient()
self.image_client_v2 = self.image_v2.ImagesClient()
self.image_member_client_v2 = self.image_v2.ImageMembersClient()
self.image_cache_client = self.image_v2.ImageCacheClient()
@@ -97,6 +95,7 @@
self.image_v2.NamespacePropertiesClient()
self.namespace_tags_client = self.image_v2.NamespaceTagsClient()
self.image_versions_client = self.image_v2.VersionsClient()
+ self.tasks_client = self.image_v2.TaskClient()
# NOTE(danms): If no alternate endpoint is configured,
# this client will work the same as the base self.images_client.
# If your test needs to know if these are different, check the
@@ -124,15 +123,12 @@
self.quota_classes_client = self.compute.QuotaClassesClient()
self.flavors_client = self.compute.FlavorsClient()
self.extensions_client = self.compute.ExtensionsClient()
- self.floating_ip_pools_client = self.compute.FloatingIPPoolsClient()
- self.floating_ips_bulk_client = self.compute.FloatingIPsBulkClient()
self.compute_floating_ips_client = self.compute.FloatingIPsClient()
self.compute_security_group_rules_client = (
self.compute.SecurityGroupRulesClient())
self.compute_security_groups_client = (
self.compute.SecurityGroupsClient())
self.interfaces_client = self.compute.InterfacesClient()
- self.fixed_ips_client = self.compute.FixedIPsClient()
self.availability_zone_client = self.compute.AvailabilityZoneClient()
self.aggregates_client = self.compute.AggregatesClient()
self.services_client = self.compute.ServicesClient()
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index ad0b547..f4f4b17 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -155,7 +155,7 @@
# Create the list of resources to be provisioned for each process
# NOTE(andreaf) get_credentials expects a string for types or a list for
# roles. Adding all required inputs to the spec list.
- spec = ['primary', 'alt']
+ spec = ['primary', 'alt', 'project_reader']
if CONF.service_available.swift:
spec.append([CONF.object_storage.operator_role])
spec.append([CONF.object_storage.reseller_admin_role])
@@ -163,8 +163,13 @@
spec.append('admin')
resources = []
for cred_type in spec:
+ scope = None
+ if "_" in cred_type:
+ scope = cred_type.split("_")[0]
+ cred_type = cred_type.split("_")[1:2]
+
resources.append((cred_type, cred_provider.get_credentials(
- credential_type=cred_type)))
+ credential_type=cred_type, scope=scope)))
return resources
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 3d476b9..b105c70 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -118,25 +118,16 @@
# Since we want to verify that the configuration is correct, we cannot
# rely on a specific version of the API being available.
try:
- _, versions = os.image_v1.ImagesClient().get_versions()
+ versions = os.image_v2.VersionsClient().list_versions()['versions']
+ versions = [x['id'] for x in versions]
except lib_exc.NotFound:
- # If not found, we use v2. The assumption is that either v1 or v2
- # are available, since glance is marked as available in the catalog.
- # If not, glance should be disabled in Tempest conf.
- try:
- versions = os.image_v2.VersionsClient().list_versions()['versions']
- versions = [x['id'] for x in versions]
- except lib_exc.NotFound:
- msg = ('Glance is available in the catalog, but no known version, '
- '(v1.x or v2.x) of Glance could be found, so Glance should '
- 'be configured as not available')
- LOG.warning(msg)
- print_and_or_update('glance', 'service-available', False, update)
- return
+ msg = ('Glance is available in the catalog, but no known version, '
+ 'of Glance could be found, so Glance should '
+ 'be configured as not available')
+ LOG.warning(msg)
+ print_and_or_update('glance', 'service-available', False, update)
+ return
- if CONF.image_feature_enabled.api_v1 != contains_version('v1.', versions):
- print_and_or_update('api_v1', 'image-feature-enabled',
- not CONF.image_feature_enabled.api_v1, update)
if CONF.image_feature_enabled.api_v2 != contains_version('v2.', versions):
print_and_or_update('api_v2', 'image-feature-enabled',
not CONF.image_feature_enabled.api_v2, update)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 1896084..1c0110f 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -304,6 +304,10 @@
# this additional wait state for later use.
wait_until_extra = None
if wait_until in ['PINGABLE', 'SSHABLE']:
+ if not validatable and validation_resources is None:
+ raise RuntimeError(
+ 'SSHABLE/PINGABLE requires validatable=True '
+ 'and validation_resources to be passed')
wait_until_extra = wait_until
wait_until = 'ACTIVE'
diff --git a/tempest/common/object_storage.py b/tempest/common/object_storage.py
new file mode 100644
index 0000000..7ffdc42
--- /dev/null
+++ b/tempest/common/object_storage.py
@@ -0,0 +1,64 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log
+
+from tempest import config
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
+LOG = log.getLogger(__name__)
+
+
+def delete_containers(containers, container_client, object_client):
+ """Remove containers and all objects in them.
+
+ The containers should be visible from the container_client given.
+ Will not throw any error if the containers don't exist.
+
+ :param containers: List of containers(or string of a container)
+ to be deleted
+ :param container_client: Client to be used to delete containers
+ :param object_client: Client to be used to delete objects
+ """
+ if isinstance(containers, str):
+ containers = [containers]
+
+ for cont in containers:
+ try:
+ delete_objects(cont, container_client, object_client)
+ container_client.delete_container(cont)
+ container_client.wait_for_resource_deletion(cont)
+ except lib_exc.NotFound:
+ LOG.warning(f"Container {cont} wasn't deleted as it wasn't found.")
+
+
+def delete_objects(container, container_client, object_client):
+ """Remove all objects from container.
+
+ Will not throw any error if the objects do not exist
+
+ :param container: Name of the container that contains the objects to be
+ deleted
+ :param container_client: Client to be used to list objects in
+ the container
+ :param object_client: Client to be used to delete objects
+ """
+ params = {'limit': 9999, 'format': 'json'}
+ _, objlist = container_client.list_container_objects(container, params)
+
+ for obj in objlist:
+ try:
+ object_client.delete_object(container, obj['name'])
+ object_client.wait_for_resource_deletion(obj['name'], container)
+ except lib_exc.NotFound:
+ LOG.warning(f"Object {obj} wasn't deleted as it wasn't found.")
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 4fdf6a4..0d93430 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -183,7 +183,7 @@
self.exec_command('sudo umount %s' % mount_path)
def make_fs(self, dev_name, fs='ext4'):
- cmd_mkfs = 'sudo mke2fs -t %s /dev/%s' % (fs, dev_name)
+ cmd_mkfs = 'sudo mkfs -t %s /dev/%s' % (fs, dev_name)
try:
self.exec_command(cmd_mkfs)
except tempest.lib.exceptions.SSHExecCommandFailed:
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index c5da412..d3be6fd 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -16,12 +16,10 @@
from oslo_log import log as logging
-from tempest.common import image as common_image
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.image.v1 import images_client as images_v1_client
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -77,7 +75,8 @@
if 'fault' in body:
details += 'Fault: %s.' % body['fault']
if request_id:
- details += ' Server boot request ID: %s.' % request_id
+ details += ' Request ID of server operation performed before'
+ details += ' checking the server status %s.' % request_id
raise exceptions.BuildErrorException(details, server_id=server_id)
timed_out = int(time.time()) - start_time >= timeout
@@ -92,7 +91,8 @@
'expected_task_state': expected_task_state,
'timeout': timeout})
if request_id:
- message += ' Server boot request ID: %s.' % request_id
+ message += ' Request ID of server operation performed before'
+ message += ' checking the server status %s.' % request_id
message += ' Current status: %s.' % server_status
message += ' Current task state: %s.' % task_state
caller = test_utils.find_test_caller()
@@ -154,17 +154,7 @@
The client should have a show_image(image_id) method to get the image.
The client should also have build_interval and build_timeout attributes.
"""
- if isinstance(client, images_v1_client.ImagesClient):
- # The 'check_image' method is used here because the show_image method
- # returns image details plus the image itself which is very expensive.
- # The 'check_image' method returns just image details.
- def _show_image_v1(image_id):
- resp = client.check_image(image_id)
- return common_image.get_image_meta_from_headers(resp)
-
- show_image = _show_image_v1
- else:
- show_image = client.show_image
+ show_image = client.show_image
current_status = 'An unknown status'
start = int(time.time())
@@ -222,6 +212,24 @@
raise lib_exc.TimeoutException(message)
+def wait_for_tasks_status(client, task_id, status):
+ start = int(time.time())
+ while int(time.time()) - start < client.build_timeout:
+ task = client.show_tasks(task_id)
+ if task['status'] == status:
+ return task
+ time.sleep(client.build_interval)
+ message = ('Task %(task_id)s tasks: '
+ 'failed to reach %(status)s state within the required '
+ 'time (%(timeout)s s).' % {'task_id': task_id,
+ 'status': status,
+ 'timeout': client.build_timeout})
+ caller = test_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise lib_exc.TimeoutException(message)
+
+
def wait_for_image_imported_to_stores(client, image_id, stores=None):
"""Waits for an image to be imported to all requested stores.
diff --git a/tempest/config.py b/tempest/config.py
index 551578e..7978755 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -153,13 +153,11 @@
help="The public endpoint type to use for OpenStack Identity "
"(Keystone) API v2"),
cfg.StrOpt('v3_endpoint_type',
- default='adminURL',
+ default='public',
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for OpenStack Identity "
- "(Keystone) API v3. The default value adminURL is "
- "deprecated and will be modified to publicURL in "
- "the next release."),
+ "(Keystone) API v3."),
cfg.StrOpt('admin_role',
default='admin',
help="Role required to administrate keystone."),
@@ -201,8 +199,15 @@
"default value is 0 meaning disabling this feature. "
"NOTE: This config option value must be same as "
"keystone.conf: security_compliance.unique_last_password_"
- "count otherwise test might fail"
- ),
+ "count otherwise test might fail"),
+ cfg.IntOpt('user_minimum_password_age',
+ default=0,
+ help="The number of days that a password must be used before "
+ "the user can change it. This only takes effect when "
+ "identity-feature-enabled.security_compliance is set to "
+ "'True'. For more details, refer to keystone config "
+ "options "
+ "keystone.conf:security_compliance.minimum_password_age.")
]
service_clients_group = cfg.OptGroup(name='service-clients',
@@ -714,14 +719,6 @@
'are current one. In future, Tempest will '
'test v2 APIs only so this config option '
'will be removed.'),
- cfg.BoolOpt('api_v1',
- default=False,
- help="Is the v1 image API enabled",
- deprecated_for_removal=True,
- deprecated_reason='Glance v1 APIs are deprecated and v2 APIs '
- 'are current one. In future, Tempest will '
- 'test v2 APIs only so this config option '
- 'will be removed.'),
# Image import feature is setup in devstack victoria onwards.
# Once all stable branches setup the same via glance standalone
# mode or with uwsgi, we can remove this config option.
@@ -1008,6 +1005,13 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the volume service."),
+ cfg.StrOpt('backup_driver',
+ default='ceph',
+ choices=['ceph', 'swift', 'nfs', 'glusterfs', 'posix', 'google',
+ 's3'],
+ help="What kind of backup_driver does cinder use?"
+ "https://docs.openstack.org/cinder/latest/configuration/"
+ "block-storage/backup-drivers.html"),
cfg.ListOpt('backend_names',
default=['BACKEND_1', 'BACKEND_2'],
help='A list of backend names separated by comma. '
@@ -1109,7 +1113,13 @@
'server instance? This depends on the 3.42 volume API '
'microversion and the 2.51 compute API microversion. '
'Also, not all volume or compute backends support this '
+ 'operation.'),
+ cfg.BoolOpt('extend_volume_with_snapshot',
+ default=True,
+ help='Does the cloud support extending the size of a volume '
+ 'which has snapshot? Some drivers do not support this '
'operation.')
+
]
diff --git a/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py b/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py
deleted file mode 100644
index a653213..0000000
--- a/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 NEC 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.lib.api_schema.response.compute.v2_1 import parameter_types
-
-get_fixed_ip = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'fixed_ip': {
- 'type': 'object',
- 'properties': {
- 'address': parameter_types.ip_address,
- 'cidr': {'type': 'string'},
- 'host': {'type': 'string'},
- 'hostname': {'type': 'string'}
- },
- 'additionalProperties': False,
- 'required': ['address', 'cidr', 'host', 'hostname']
- }
- },
- 'additionalProperties': False,
- 'required': ['fixed_ip']
- }
-}
-
-reserve_unreserve_fixed_ip = {
- 'status_code': [202]
-}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py b/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
index 0c66590..274540c 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
@@ -58,91 +58,6 @@
}
}
-list_floating_ip_pools = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'floating_ip_pools': {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'name': {'type': 'string'}
- },
- 'additionalProperties': False,
- 'required': ['name'],
- }
- }
- },
- 'additionalProperties': False,
- 'required': ['floating_ip_pools'],
- }
-}
-
add_remove_floating_ip = {
'status_code': [202]
}
-
-create_floating_ips_bulk = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'floating_ips_bulk_create': {
- 'type': 'object',
- 'properties': {
- 'interface': {'type': ['string', 'null']},
- 'ip_range': {'type': 'string'},
- 'pool': {'type': ['string', 'null']},
- },
- 'additionalProperties': False,
- 'required': ['interface', 'ip_range', 'pool'],
- }
- },
- 'additionalProperties': False,
- 'required': ['floating_ips_bulk_create'],
- }
-}
-
-delete_floating_ips_bulk = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'floating_ips_bulk_delete': {'type': 'string'}
- },
- 'additionalProperties': False,
- 'required': ['floating_ips_bulk_delete'],
- }
-}
-
-list_floating_ips_bulk = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'floating_ip_info': {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'address': parameter_types.ip_address,
- 'instance_uuid': {'type': ['string', 'null']},
- 'interface': {'type': ['string', 'null']},
- 'pool': {'type': ['string', 'null']},
- 'project_id': {'type': ['string', 'null']},
- 'fixed_ip': parameter_types.ip_address
- },
- 'additionalProperties': False,
- # NOTE: fixed_ip is introduced after JUNO release,
- # So it is not defined as 'required'.
- 'required': ['address', 'instance_uuid', 'interface',
- 'pool', 'project_id'],
- }
- }
- },
- 'additionalProperties': False,
- 'required': ['floating_ip_info'],
- }
-}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index bd42afd..14e2d3b 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -250,33 +250,6 @@
rescue_server_with_admin_pass['response_body'].update(
{'required': ['adminPass']})
-
-list_virtual_interfaces = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'virtual_interfaces': {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'mac_address': parameter_types.mac_address,
- 'OS-EXT-VIF-NET:net_id': {'type': 'string'}
- },
- 'additionalProperties': False,
- # 'OS-EXT-VIF-NET:net_id' is API extension So it is
- # not defined as 'required'
- 'required': ['id', 'mac_address']
- }
- }
- },
- 'additionalProperties': False,
- 'required': ['virtual_interfaces']
- }
-}
-
common_attach_volume_info = {
'type': 'object',
'properties': {
diff --git a/tempest/lib/api_schema/response/volume/volumes.py b/tempest/lib/api_schema/response/volume/volumes.py
index 4f44526..900e5ef 100644
--- a/tempest/lib/api_schema/response/volume/volumes.py
+++ b/tempest/lib/api_schema/response/volume/volumes.py
@@ -295,6 +295,7 @@
attach_volume = {'status_code': [202]}
set_bootable_volume = {'status_code': [200]}
detach_volume = {'status_code': [202]}
+terminate_connection = {'status_code': [202]}
reserve_volume = {'status_code': [202]}
unreserve_volume = {'status_code': [202]}
extend_volume = {'status_code': [202]}
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index 4cf8351..c79db15 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -93,6 +93,7 @@
if attempt >= 3:
raise
LOG.warning('Got ServerFault while running %s, retrying...', func)
+ time.sleep(1)
def call_until_true(func, duration, sleep_for, *args, **kwargs):
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 8b5c758..86ce6ec 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -48,7 +48,6 @@
'placement': placement,
'identity.v2': identity.v2,
'identity.v3': identity.v3,
- 'image.v1': image.v1,
'image.v2': image.v2,
'network': network,
'object-storage': object_storage,
diff --git a/tempest/lib/services/compute/__init__.py b/tempest/lib/services/compute/__init__.py
index da800af..10ec9be 100644
--- a/tempest/lib/services/compute/__init__.py
+++ b/tempest/lib/services/compute/__init__.py
@@ -24,12 +24,7 @@
CertificatesClient
from tempest.lib.services.compute.extensions_client import \
ExtensionsClient
-from tempest.lib.services.compute.fixed_ips_client import FixedIPsClient
from tempest.lib.services.compute.flavors_client import FlavorsClient
-from tempest.lib.services.compute.floating_ip_pools_client import \
- FloatingIPPoolsClient
-from tempest.lib.services.compute.floating_ips_bulk_client import \
- FloatingIPsBulkClient
from tempest.lib.services.compute.floating_ips_client import \
FloatingIPsClient
from tempest.lib.services.compute.hosts_client import HostsClient
@@ -69,10 +64,9 @@
__all__ = ['AgentsClient', 'AggregatesClient', 'AssistedVolumeSnapshotsClient',
'AvailabilityZoneClient', 'BaremetalNodesClient',
- 'CertificatesClient', 'ExtensionsClient', 'FixedIPsClient',
- 'FlavorsClient', 'FloatingIPPoolsClient',
- 'FloatingIPsBulkClient', 'FloatingIPsClient', 'HostsClient',
- 'HypervisorClient', 'ImagesClient', 'InstanceUsagesAuditLogClient',
+ 'CertificatesClient', 'ExtensionsClient', 'FlavorsClient',
+ 'FloatingIPsClient', 'HostsClient', 'HypervisorClient',
+ 'ImagesClient', 'InstanceUsagesAuditLogClient',
'InterfacesClient', 'KeyPairsClient', 'LimitsClient',
'MigrationsClient', 'NetworksClient', 'QuotaClassesClient',
'QuotasClient', 'SecurityGroupDefaultRulesClient',
diff --git a/tempest/lib/services/compute/fixed_ips_client.py b/tempest/lib/services/compute/fixed_ips_client.py
deleted file mode 100644
index 098c856..0000000
--- a/tempest/lib/services/compute/fixed_ips_client.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2013 IBM 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 oslo_serialization import jsonutils as json
-
-from tempest.lib.api_schema.response.compute.v2_1 import fixed_ips as schema
-from tempest.lib.common import rest_client
-from tempest.lib.services.compute import base_compute_client
-
-
-class FixedIPsClient(base_compute_client.BaseComputeClient):
-
- def show_fixed_ip(self, fixed_ip):
- url = "os-fixed-ips/%s" % fixed_ip
- resp, body = self.get(url)
- body = json.loads(body)
- self.validate_response(schema.get_fixed_ip, resp, body)
- return rest_client.ResponseBody(resp, body)
-
- def reserve_fixed_ip(self, fixed_ip, **kwargs):
- """Reserve/Unreserve a fixed IP.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/compute/#reserve-or-release-a-fixed-ip
- """
- url = "os-fixed-ips/%s/action" % fixed_ip
- resp, body = self.post(url, json.dumps(kwargs))
- self.validate_response(schema.reserve_unreserve_fixed_ip, resp, body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ip_pools_client.py b/tempest/lib/services/compute/floating_ip_pools_client.py
deleted file mode 100644
index aa065b8..0000000
--- a/tempest/lib/services/compute/floating_ip_pools_client.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from urllib import parse as urllib
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
-from tempest.lib.common import rest_client
-from tempest.lib.services.compute import base_compute_client
-
-
-class FloatingIPPoolsClient(base_compute_client.BaseComputeClient):
-
- def list_floating_ip_pools(self, params=None):
- """Gets all floating IP Pools list."""
- url = 'os-floating-ip-pools'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.validate_response(schema.list_floating_ip_pools, resp, body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ips_bulk_client.py b/tempest/lib/services/compute/floating_ips_bulk_client.py
deleted file mode 100644
index 5f06009..0000000
--- a/tempest/lib/services/compute/floating_ips_bulk_client.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
-from tempest.lib.common import rest_client
-from tempest.lib.services.compute import base_compute_client
-
-
-class FloatingIPsBulkClient(base_compute_client.BaseComputeClient):
-
- def create_floating_ips_bulk(self, ip_range, pool, interface):
- """Allocate floating IPs in bulk."""
- post_body = {
- 'ip_range': ip_range,
- 'pool': pool,
- 'interface': interface
- }
- post_body = json.dumps({'floating_ips_bulk_create': post_body})
- resp, body = self.post('os-floating-ips-bulk', post_body)
- body = json.loads(body)
- self.validate_response(schema.create_floating_ips_bulk, resp, body)
- return rest_client.ResponseBody(resp, body)
-
- def list_floating_ips_bulk(self):
- """Gets all floating IPs in bulk."""
- resp, body = self.get('os-floating-ips-bulk')
- body = json.loads(body)
- self.validate_response(schema.list_floating_ips_bulk, resp, body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_floating_ips_bulk(self, ip_range):
- """Deletes the provided floating IPs in bulk."""
- post_body = json.dumps({'ip_range': ip_range})
- resp, body = self.put('os-floating-ips-bulk/delete', post_body)
- body = json.loads(body)
- self.validate_response(schema.delete_floating_ips_bulk, resp, body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index d2bdb6e..7e3b99f 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -676,14 +676,6 @@
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,
- 'os-virtual-interfaces']))
- body = json.loads(body)
- self.validate_response(schema.list_virtual_interfaces, resp, body)
- return rest_client.ResponseBody(resp, body)
-
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server.
diff --git a/tempest/lib/services/image/__init__.py b/tempest/lib/services/image/__init__.py
index 4b01663..ee1c32c 100644
--- a/tempest/lib/services/image/__init__.py
+++ b/tempest/lib/services/image/__init__.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.lib.services.image import v1
from tempest.lib.services.image import v2
-__all__ = ['v1', 'v2']
+__all__ = ['v2']
diff --git a/tempest/lib/services/image/v1/__init__.py b/tempest/lib/services/image/v1/__init__.py
deleted file mode 100644
index 1f33cef..0000000
--- a/tempest/lib/services/image/v1/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# 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 warnings
-
-from tempest.lib.services.image.v1.image_members_client import \
- ImageMembersClient
-from tempest.lib.services.image.v1.images_client import ImagesClient
-
-__all__ = ['ImageMembersClient', 'ImagesClient']
-
-
-warnings.warn(
- "The tempest.lib.services.image.v1 module (Image v1 APIs service "
- "clients) is deprecated in favor of tempest.lib.services.image.v2 "
- "(Image v2 APIs service clients) and will be removed once Tempest stop "
- "supporting stable Ussuri.", DeprecationWarning)
diff --git a/tempest/lib/services/image/v1/image_members_client.py b/tempest/lib/services/image/v1/image_members_client.py
deleted file mode 100644
index 7499ec0..0000000
--- a/tempest/lib/services/image/v1/image_members_client.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# 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 ImageMembersClient(rest_client.RestClient):
- api_version = "v1"
-
- def list_image_members(self, image_id):
- """List all members of an image."""
- url = 'images/%s/members' % image_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_shared_images(self, tenant_id):
- """List image memberships for the given tenant.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/image/v1/#list-shared-images
- """
-
- url = 'shared-images/%s' % tenant_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def create_image_member(self, image_id, member_id, **kwargs):
- """Add a member to an image.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/image/v1/#add-member-to-image
- """
- url = 'images/%s/members/%s' % (image_id, member_id)
- body = json.dumps({'member': kwargs})
- resp, __ = self.put(url, body)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def delete_image_member(self, image_id, member_id):
- """Removes a membership from the image.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/image/v1/#remove-member
- """
- url = 'images/%s/members/%s' % (image_id, member_id)
- resp, __ = self.delete(url)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/image/v1/images_client.py b/tempest/lib/services/image/v1/images_client.py
deleted file mode 100644
index c9a4a94..0000000
--- a/tempest/lib/services/image/v1/images_client.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright 2013 IBM 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.
-
-import functools
-from urllib import parse as urllib
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-
-CHUNKSIZE = 1024 * 64 # 64kB
-
-
-class ImagesClient(rest_client.RestClient):
- api_version = "v1"
-
- def _create_with_data(self, headers, data):
- # We are going to do chunked transfert, so split the input data
- # info fixed-sized chunks.
- headers['Content-Type'] = 'application/octet-stream'
- data = iter(functools.partial(data.read, CHUNKSIZE), b'')
- resp, body = self.request('POST', 'images',
- headers=headers, body=data, chunked=True)
- self._error_checker(resp, body)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def _update_with_data(self, image_id, headers, data):
- # We are going to do chunked transfert, so split the input data
- # info fixed-sized chunks.
- headers['Content-Type'] = 'application/octet-stream'
- data = iter(functools.partial(data.read, CHUNKSIZE), b'')
- url = 'images/%s' % image_id
- resp, body = self.request('PUT', url, headers=headers,
- body=data, chunked=True)
- self._error_checker(resp, body)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- @property
- def http(self):
- if self._http is None:
- self._http = self._get_http()
- return self._http
-
- def create_image(self, data=None, headers=None):
- """Create an image.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/image/v1/index.html#create-image
- """
- if headers is None:
- headers = {}
-
- if data is not None:
- return self._create_with_data(headers, data)
-
- resp, body = self.post('images', None, headers)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_image(self, image_id, data=None, headers=None):
- """Update an image.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/image/v1/index.html#update-image
- """
- if headers is None:
- headers = {}
-
- if data is not None:
- return self._update_with_data(image_id, headers, data)
-
- url = 'images/%s' % image_id
- resp, body = self.put(url, None, headers)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_image(self, image_id):
- url = 'images/%s' % image_id
- resp, body = self.delete(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_images(self, detail=False, **kwargs):
- """Return a list of all images filtered by input parameters.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://docs.openstack.org/api-ref/image/v1/#list-images
-
- Most parameters except the following are passed to the API without
- any changes.
- :param changes_since: The name is changed to changes-since
- """
- url = 'images'
-
- if detail:
- url += '/detail'
-
- if 'changes_since' in kwargs:
- kwargs['changes-since'] = kwargs.pop('changes_since')
-
- if kwargs:
- url += '?%s' % urllib.urlencode(kwargs)
-
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def check_image(self, image_id):
- """Check image metadata."""
- url = 'images/%s' % image_id
- resp, body = self.head(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_image(self, image_id):
- """Get image details plus the image itself."""
- url = 'images/%s' % image_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBodyData(resp, body)
-
- def is_resource_deleted(self, id):
- try:
- resp = self.check_image(id)
- if resp.response["x-image-meta-status"] == 'deleted':
- return True
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'image_meta'
diff --git a/tempest/lib/services/image/v2/__init__.py b/tempest/lib/services/image/v2/__init__.py
index a2f5bdc..5e303e3 100644
--- a/tempest/lib/services/image/v2/__init__.py
+++ b/tempest/lib/services/image/v2/__init__.py
@@ -27,9 +27,11 @@
from tempest.lib.services.image.v2.resource_types_client import \
ResourceTypesClient
from tempest.lib.services.image.v2.schemas_client import SchemasClient
+from tempest.lib.services.image.v2.tasks_client import TaskClient
from tempest.lib.services.image.v2.versions_client import VersionsClient
+
__all__ = ['ImageMembersClient', 'ImagesClient', 'ImageCacheClient',
'NamespaceObjectsClient', 'NamespacePropertiesClient',
'NamespaceTagsClient', 'NamespacesClient', 'ResourceTypesClient',
- 'SchemasClient', 'VersionsClient']
+ 'SchemasClient', 'TaskClient', 'VersionsClient']
diff --git a/tempest/lib/services/image/v2/tasks_client.py b/tempest/lib/services/image/v2/tasks_client.py
new file mode 100644
index 0000000..2cb33eb
--- /dev/null
+++ b/tempest/lib/services/image/v2/tasks_client.py
@@ -0,0 +1,70 @@
+# Copyright 2023 Red Hat, Inc.
+# 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 urllib import parse as urllib
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+CHUNKSIZE = 1024 * 64 # 64kB
+
+
+class TaskClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def create_task(self, **kwargs):
+ """Create a task.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/image/v2/#create-task
+ """
+ data = json.dumps(kwargs)
+ resp, body = self.post('tasks', data)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_tasks(self, **kwargs):
+ """List tasks.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/image/v2/#list-tasks
+ """
+ url = 'tasks'
+
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
+
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_tasks(self, task_id):
+ """Show task details.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/image/v2/#show-task-details
+ """
+ url = 'tasks/%s' % task_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index ee87726..bdca0d0 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -43,7 +43,7 @@
url = str(container_name)
resp, body = self.put(url, body=None, headers=headers)
- self.expected_success([201, 202], resp.status)
+ self.expected_success([201, 202, 204], resp.status)
return resp, body
# NOTE: This alias is for the usability because PUT can be used for both
diff --git a/tempest/lib/services/volume/v3/attachments_client.py b/tempest/lib/services/volume/v3/attachments_client.py
index 5e448f7..ef8be37 100644
--- a/tempest/lib/services/volume/v3/attachments_client.py
+++ b/tempest/lib/services/volume/v3/attachments_client.py
@@ -26,3 +26,11 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def delete_attachment(self, attachment_id):
+ """Delete volume attachment."""
+ url = "attachments/%s" % (attachment_id)
+ resp, body = self.delete(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index ad8bd71..c6f8973 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -205,14 +205,23 @@
self.validate_response(schema.set_bootable_volume, resp, body)
return rest_client.ResponseBody(resp, body)
- def detach_volume(self, volume_id):
+ def detach_volume(self, volume_id, **kwargs):
"""Detaches a volume from an instance."""
- post_body = json.dumps({'os-detach': {}})
+ post_body = json.dumps({'os-detach': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.validate_response(schema.detach_volume, resp, body)
return rest_client.ResponseBody(resp, body)
+ def terminate_connection(self, volume_id, connector):
+ """Detaches a volume from an instance using terminate_connection."""
+ post_body = json.dumps(
+ {'os-terminate_connection': {'connector': connector}})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.validate_response(schema.terminate_connection, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
def reserve_volume(self, volume_id):
"""Reserves a volume."""
post_body = json.dumps({'os-reserve': {}})
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index db0aa5a..0450d94 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
import os
import subprocess
@@ -24,7 +25,6 @@
from oslo_utils import netutils
from tempest.common import compute
-from tempest.common import image as common_image
from tempest.common.utils.linux import remote_client
from tempest.common.utils import net_utils
from tempest.common import waiters
@@ -89,6 +89,16 @@
volume_microversion=cls.volume_request_microversion,
placement_microversion=cls.placement_request_microversion)
+ @classmethod
+ def setup_credentials(cls):
+ # Setting network=True, subnet=True creates a default network
+ cls.set_network_resources(
+ network=True,
+ subnet=True,
+ router=True,
+ dhcp=True)
+ super(ScenarioTest, cls).setup_credentials()
+
def setup_compute_client(cls):
"""Compute client"""
cls.compute_images_client = cls.os_primary.compute_images_client
@@ -113,15 +123,11 @@
"""This setup the service clients for the tests"""
super(ScenarioTest, 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_primary.image_client
- elif CONF.image_feature_enabled.api_v2:
+ if CONF.image_feature_enabled.api_v2:
cls.image_client = cls.os_primary.image_client_v2
else:
raise lib_exc.InvalidConfiguration(
- 'Either api_v1 or api_v2 must be True in '
- '[image-feature-enabled].')
+ 'api_v2 must be True in [image-feature-enabled].')
cls.setup_compute_client(cls)
cls.setup_network_client(cls)
@@ -184,7 +190,7 @@
return body['keypair']
def create_server(self, name=None, image_id=None, flavor=None,
- validatable=False, wait_until='ACTIVE',
+ validatable=None, wait_until='ACTIVE',
clients=None, **kwargs):
"""Wrapper utility that returns a test server.
@@ -309,6 +315,28 @@
kwargs.setdefault('availability_zone',
CONF.compute.compute_volume_common_az)
+ kwargs['validatable'] = bool(validatable)
+ keypair = kwargs.pop('keypair', None)
+ if wait_until == 'SSHABLE' and (
+ kwargs.get('validation_resources') is None):
+ # NOTE(danms): We should do this whether valdiation is enabled or
+ # not to consistently provide the resources to the
+ # create_test_server() function. If validation is disabled, then
+ # get_test_validation_resources() is basically a no-op for
+ # performance.
+ validation_resources = self.get_test_validation_resources(
+ self.os_primary)
+ if keypair:
+ validation_resources = copy.deepcopy(validation_resources)
+ validation_resources.update(
+ keypair=keypair)
+ kwargs.update({
+ 'validatable': (validatable if validatable is not None
+ else True),
+ 'validation_resources': validation_resources})
+ if keypair:
+ kwargs.update({'key_name': keypair['name']})
+
body, _ = compute.create_test_server(
clients,
tenant_network=tenant_network,
@@ -338,11 +366,7 @@
if size is None:
size = CONF.volume.volume_size
if imageRef:
- if CONF.image_feature_enabled.api_v1:
- resp = self.image_client.check_image(imageRef)
- image = common_image.get_image_meta_from_headers(resp)
- else:
- image = self.image_client.show_image(imageRef)
+ image = self.image_client.show_image(imageRef)
min_disk = image.get('min_disk')
size = max(size, min_disk)
if name is None:
@@ -763,27 +787,18 @@
'name': name,
'container_format': img_container_format,
'disk_format': img_disk_format or img_container_format,
+ 'visibility': 'private'
}
- if CONF.image_feature_enabled.api_v1:
- params['is_public'] = 'False'
- if img_properties:
- params['properties'] = img_properties
- params = {'headers': common_image.image_meta_to_headers(**params)}
- else:
- params['visibility'] = 'private'
- # Additional properties are flattened out in the v2 API.
- if img_properties:
- params.update(img_properties)
+ # Additional properties are flattened out in the v2 API.
+ if img_properties:
+ params.update(img_properties)
params.update(kwargs)
body = self.image_client.create_image(**params)
image = body['image'] if 'image' in body else body
self.addCleanup(self.image_client.delete_image, image['id'])
self.assertEqual("queued", image['status'])
with open(img_path, 'rb') as image_file:
- if CONF.image_feature_enabled.api_v1:
- self.image_client.update_image(image['id'], data=image_file)
- else:
- self.image_client.store_image_file(image['id'], image_file)
+ self.image_client.store_image_file(image['id'], image_file)
LOG.debug("image:%s", image['id'])
return image['id']
@@ -831,15 +846,9 @@
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
_image_client.delete_image, image_id)
- if CONF.image_feature_enabled.api_v1:
- # In glance v1 the additional properties are stored in the headers
- resp = _image_client.check_image(image_id)
- snapshot_image = common_image.get_image_meta_from_headers(resp)
- image_props = snapshot_image.get('properties', {})
- else:
- # In glance v2 the additional properties are flattened.
- snapshot_image = _image_client.show_image(image_id)
- image_props = snapshot_image
+ # In glance v2 the additional properties are flattened.
+ snapshot_image = _image_client.show_image(image_id)
+ image_props = snapshot_image
bdm = image_props.get('block_device_mapping')
if bdm:
@@ -1054,6 +1063,20 @@
floating_ip['id'])
return floating_ip
+ def get_floating_ip(self, server):
+ """Attempt to get an existing floating ip or a server
+
+ If one exists, return it, else return None
+ """
+ port_id, ip4 = self.get_server_port_id_and_ip4(server)
+ ips = self.floating_ips_client.list_floatingips(
+ floating_network_id=CONF.network.public_network_id,
+ port_id=port_id)
+ try:
+ return ips['floatingips'][0]['floating_ip_address']
+ except (KeyError, IndexError):
+ return None
+
def associate_floating_ip(self, floating_ip, server):
"""Associate floating ip to server
@@ -1082,7 +1105,7 @@
def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
private_key=None, server=None, username=None,
- fs='ext4'):
+ fs='vfat'):
"""Creates timestamp
This wrapper utility does ssh, creates timestamp and returns the
@@ -1093,14 +1116,19 @@
server=server,
username=username)
+ # Default the directory in which to write the timestamp file to /tmp
+ # and only use the mount_path as the target directory if we mounted
+ # dev_name to mount_path.
+ target_dir = '/tmp'
if dev_name is not None:
ssh_client.make_fs(dev_name, fs=fs)
ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
mount_path))
- cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
+ target_dir = mount_path
+ cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % target_dir
ssh_client.exec_command(cmd_timestamp)
timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
- % mount_path)
+ % target_dir)
if dev_name is not None:
ssh_client.exec_command('sudo umount %s' % mount_path)
return timestamp
@@ -1125,10 +1153,15 @@
server=server,
username=username)
+ # Default the directory from which to read the timestamp file to /tmp
+ # and only use the mount_path as the target directory if we mounted
+ # dev_name to mount_path.
+ target_dir = '/tmp'
if dev_name is not None:
ssh_client.mount(dev_name, mount_path)
+ target_dir = mount_path
timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
- % mount_path)
+ % target_dir)
if dev_name is not None:
ssh_client.exec_command('sudo umount %s' % mount_path)
return timestamp
@@ -1148,8 +1181,14 @@
# The tests calling this method don't have a floating IP
# and can't make use of the validation resources. So the
# method is creating the floating IP there.
- return self.create_floating_ip(
- server, **kwargs)['floating_ip_address']
+ fip = self.get_floating_ip(server)
+ if fip:
+ # Already have a floating ip, so use it instead of creating
+ # another
+ return fip
+ else:
+ return self.create_floating_ip(
+ server, **kwargs)['floating_ip_address']
elif CONF.validation.connect_method == 'fixed':
# Determine the network name to look for based on config or creds
# provider network resources.
@@ -1198,7 +1237,7 @@
create_kwargs = dict({'image_id': ''})
if keypair:
- create_kwargs['key_name'] = keypair['name']
+ create_kwargs['keypair'] = keypair
if security_group:
create_kwargs['security_groups'] = [
{'name': security_group['name']}]
@@ -1606,7 +1645,8 @@
def create_encrypted_volume(self, encryption_provider, volume_type,
key_size=256, cipher='aes-xts-plain64',
- control_location='front-end'):
+ control_location='front-end',
+ wait_until='available'):
"""Creates an encrypted volume"""
volume_type = self.create_volume_type(name=volume_type)
self.create_encryption_type(type_id=volume_type['id'],
@@ -1614,7 +1654,8 @@
key_size=key_size,
cipher=cipher,
control_location=control_location)
- return self.create_volume(volume_type=volume_type['name'])
+ return self.create_volume(volume_type=volume_type['name'],
+ wait_until=wait_until)
class ObjectStorageScenarioTest(ScenarioTest):
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index 9788e19..753e64f 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -16,6 +16,7 @@
import testtools
from tempest.common import utils
+from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
from tempest.scenario import manager
@@ -45,9 +46,7 @@
raise cls.skipException('Encrypted volume attach is not supported')
def launch_instance(self):
- keypair = self.create_keypair()
-
- return self.create_server(key_name=keypair['name'])
+ return self.create_server(wait_until='SSHABLE')
def attach_detach_volume(self, server, volume):
attached_volume = self.nova_volume_attach(server, volume)
@@ -58,9 +57,16 @@
@utils.services('compute', 'volume', 'image')
def test_encrypted_cinder_volumes_luks(self):
"""LUKs v1 decrypts volume through libvirt."""
- server = self.launch_instance()
volume = self.create_encrypted_volume('luks',
- volume_type='luks')
+ volume_type='luks',
+ wait_until=None)
+ server = self.launch_instance()
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
+
self.attach_detach_volume(server, volume)
@decorators.idempotent_id('7abec0a3-61a0-42a5-9e36-ad3138fb38b4')
@@ -70,16 +76,30 @@
@utils.services('compute', 'volume', 'image')
def test_encrypted_cinder_volumes_luksv2(self):
"""LUKs v2 decrypts volume through os-brick."""
- server = self.launch_instance()
volume = self.create_encrypted_volume('luks2',
- volume_type='luksv2')
+ volume_type='luksv2',
+ wait_until=None)
+ server = self.launch_instance()
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
+
self.attach_detach_volume(server, volume)
@decorators.idempotent_id('cbc752ed-b716-4717-910f-956cce965722')
@decorators.attr(type='slow')
@utils.services('compute', 'volume', 'image')
def test_encrypted_cinder_volumes_cryptsetup(self):
- server = self.launch_instance()
volume = self.create_encrypted_volume('plain',
- volume_type='cryptsetup')
+ volume_type='cryptsetup',
+ wait_until=None)
+ server = self.launch_instance()
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
+
self.attach_detach_volume(server, volume)
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 5513f4d..6372c6b 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -38,6 +38,12 @@
* check command outputs
"""
+ @classmethod
+ def skip_checks(cls):
+ super(TestMinimumBasicScenario, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
+
def nova_show(self, server):
got_server = (self.servers_client.show_server(server['id'])
['server'])
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e6c6eb6..882afff 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -206,7 +206,7 @@
self.assertEqual(resize_flavor, server['flavor']['id'])
else:
flavor = self.flavors_client.show_flavor(resize_flavor)['flavor']
- self.assertEqual(flavor['name'], server['original_name'])
+ self.assertEqual(flavor['name'], server['flavor']['original_name'])
for key in ['ram', 'vcpus', 'disk']:
self.assertEqual(flavor[key], server['flavor'][key])
self._wait_server_status_and_check_network_connectivity(
@@ -261,15 +261,29 @@
floating_ip['floating_ip_address'])
self.useFixture(downtime_meter)
+ migration_kwargs = {'host': None, 'block_migration': block_migration}
+
+ # check if microversion is less than 2.25 because of
+ # disk_over_commit is depracted since compute api version 2.25
+ # if min_microversion is None, it runs on version < 2.25
+ if (CONF.compute.min_microversion is None or
+ CONF.compute.min_microversion < 2.25):
+ migration_kwargs['disk_over_commit'] = False
+
self.admin_servers_client.live_migrate_server(
- server['id'], host=None, block_migration=block_migration,
- disk_over_commit=False)
+ server['id'], **migration_kwargs)
+
waiters.wait_for_server_status(self.servers_client,
server['id'], 'ACTIVE')
new_host = self.get_host_for_server(server['id'])
self.assertNotEqual(old_host, new_host, 'Server did not migrate')
+ # we first wait until the VM replies pings again, then check the
+ # network downtime
+ self._wait_server_status_and_check_network_connectivity(
+ server, keypair, floating_ip)
+
downtime = downtime_meter.get_downtime()
self.assertIsNotNone(downtime)
LOG.debug("Downtime seconds measured with downtime_meter = %r",
@@ -280,9 +294,6 @@
"Downtime of {} seconds is higher than expected '{}'".format(
downtime, allowed_downtime))
- self._wait_server_status_and_check_network_connectivity(
- server, keypair, floating_ip)
-
@decorators.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration is not available.')
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index cbe4122..7b819e0 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -898,10 +898,13 @@
nic=spoof_nic, should_succeed=True)
# Set a mac address by making nic down temporary
spoof_ip_addresses = ssh_client.get_nic_ip_addresses(spoof_nic)
- cmd = ("sudo ip link set {nic} down;"
+ dhcp_cmd = ("sudo start-stop-daemon -K -x /sbin/dhcpcd -p "
+ "/var/run/dhcpcd/pid -o || true")
+ cmd = ("{dhcp_cmd}; sudo ip link set {nic} down;"
"sudo ip link set dev {nic} address {mac};"
"sudo ip link set {nic} up;"
"sudo ip address flush dev {nic};").format(nic=spoof_nic,
+ dhcp_cmd=dhcp_cmd,
mac=spoof_mac)
for ip_address in spoof_ip_addresses:
cmd += (
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 990b325..1c2246d 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -14,7 +14,6 @@
# under the License.
from oslo_log import log as logging
-import testtools
from tempest.common import utils
from tempest.common import waiters
@@ -36,14 +35,21 @@
"""
@classmethod
+ def skip_checks(cls):
+ super(TestServerAdvancedOps, cls).skip_checks()
+ if not CONF.service_available.nova:
+ skip_msg = ("%s skipped as Nova is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ if not CONF.compute_feature_enabled.suspend:
+ raise cls.skipException("Suspend is not available.")
+
+ @classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True)
super(TestServerAdvancedOps, cls).setup_credentials()
@decorators.attr(type='slow')
@decorators.idempotent_id('949da7d5-72c8-4808-8802-e3d70df98e2c')
- @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
- 'Suspend is not available.')
@utils.services('compute')
def test_server_sequence_suspend_resume(self):
# We create an instance for use in this test
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 2a15470..3830fbc 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -49,16 +49,8 @@
def verify_ssh(self, keypair):
if self.run_ssh:
- # Obtain a floating IP if floating_ips is enabled
- if (CONF.network_feature_enabled.floating_ips and
- CONF.network.floating_network_name):
- fip = self.create_floating_ip(self.instance)
- self.ip = self.associate_floating_ip(
- fip, self.instance)['floating_ip_address']
- else:
- server = self.servers_client.show_server(
- self.instance['id'])['server']
- self.ip = self.get_server_ip(server)
+ # Obtain server IP
+ self.ip = self.get_server_ip(self.instance)
# Check ssh
self.ssh_client = self.get_remote_client(
ip_address=self.ip,
@@ -133,7 +125,8 @@
security_group = self.create_security_group()
self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'}
self.instance = self.create_server(
- key_name=keypair['name'],
+ keypair=keypair,
+ wait_until='SSHABLE',
security_groups=[{'name': security_group['name']}],
config_drive=CONF.compute_feature_enabled.config_drive,
metadata=self.md)
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index 9285da2..fe85234 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.common import utils
+from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions
@@ -46,7 +47,8 @@
if zone['zoneState']['available']:
for host in zone['hosts']:
if 'nova-compute' in zone['hosts'][host] and \
- zone['hosts'][host]['nova-compute']['available']:
+ zone['hosts'][host]['nova-compute']['available'] and \
+ not host.endswith('-ironic'):
hosts.append({'zone': zone['zoneName'],
'host_name': host})
@@ -60,6 +62,7 @@
# threshold (so that things don't get crazy if you have 1000
# compute nodes but set min to 3).
servers = []
+ host_server_ids = {}
for host in hosts[:CONF.compute.min_compute_nodes]:
# by getting to active state here, this means this has
@@ -67,12 +70,18 @@
# in order to use the availability_zone:host scheduler hint,
# admin client is need here.
inst = self.create_server(
+ wait_until=None,
clients=self.os_admin,
availability_zone='%(zone)s:%(host_name)s' % host)
+ host_server_ids[host['host_name']] = inst['id']
+
+ for host_name, server_id in host_server_ids.items():
+ waiters.wait_for_server_status(self.os_admin.servers_client,
+ server_id, 'ACTIVE')
server = self.os_admin.servers_client.show_server(
- inst['id'])['server']
+ server_id)['server']
# ensure server is located on the requested host
- self.assertEqual(host['host_name'], server['OS-EXT-SRV-ATTR:host'])
+ self.assertEqual(host_name, server['OS-EXT-SRV-ATTR:host'])
servers.append(server)
# make sure we really have the number of servers we think we should
diff --git a/tempest/scenario/test_server_volume_attachment.py b/tempest/scenario/test_server_volume_attachment.py
new file mode 100644
index 0000000..076b835
--- /dev/null
+++ b/tempest/scenario/test_server_volume_attachment.py
@@ -0,0 +1,208 @@
+# Copyright 2023 Red Hat
+# 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 unittest import mock
+
+from tempest.common import utils
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions
+from tempest.scenario import manager
+
+CONF = config.CONF
+
+
+class BaseAttachmentTest(manager.ScenarioTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(BaseAttachmentTest, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
+
+ @classmethod
+ def setup_clients(cls):
+ super().setup_clients()
+ cls.attachments_client = cls.os_primary.attachments_client_latest
+ cls.admin_volume_client = cls.os_admin.volumes_client_latest
+
+ def _call_with_fake_service_token(self, valid_token,
+ client, method_name, *args, **kwargs):
+ """Call client method with non-service service token
+
+ Add a service token header that can be a valid normal user token (which
+ won't have the service role) or an invalid token altogether.
+ """
+ original_raw_request = client.raw_request
+
+ def raw_request(url, method, headers=None, body=None, chunked=False,
+ log_req_body=None):
+ token = headers['X-Auth-Token']
+ if not valid_token:
+ token = token[:-1] + ('a' if token[-1] != 'a' else 'b')
+ headers['X-Service-Token'] = token
+ return original_raw_request(url, method, headers=headers,
+ body=body, chunked=chunked,
+ log_req_body=log_req_body)
+
+ client_method = getattr(client, method_name)
+ with mock.patch.object(client, 'raw_request', raw_request):
+ return client_method(*args, **kwargs)
+
+
+class TestServerVolumeAttachmentScenario(BaseAttachmentTest):
+
+ """Test server attachment behaviors
+
+ This tests that volume attachments to servers may not be removed directly
+ and are only allowed through the compute service (bug #2004555).
+ """
+
+ @decorators.attr(type='slow')
+ @decorators.idempotent_id('be615530-f105-437a-8afe-ce998c9535d9')
+ @utils.services('compute', 'volume', 'image', 'network')
+ def test_server_detach_rules(self):
+ """Test that various methods of detaching a volume honors the rules"""
+ volume = self.create_volume(wait_until=None)
+ volume2 = self.create_volume(wait_until=None)
+
+ server = self.create_server(wait_until='SSHABLE')
+ servers = self.servers_client.list_servers()['servers']
+ self.assertIn(server['id'], [x['id'] for x in servers])
+
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
+
+ volume = self.nova_volume_attach(server, volume)
+ self.addCleanup(self.nova_volume_detach, server, volume)
+ att_id = volume['attachments'][0]['attachment_id']
+
+ # Test user call to detach volume is rejected
+ self.assertRaises((exceptions.Forbidden, exceptions.Conflict),
+ self.volumes_client.detach_volume, volume['id'])
+
+ # Test user call to terminate connection is rejected
+ self.assertRaises((exceptions.Forbidden, exceptions.Conflict),
+ self.volumes_client.terminate_connection,
+ volume['id'], connector={})
+
+ # Test faking of service token on call to detach, force detach,
+ # terminate_connection
+ for valid_token in (True, False):
+ valid_exceptions = [exceptions.Forbidden, exceptions.Conflict]
+ if not valid_token:
+ valid_exceptions.append(exceptions.Unauthorized)
+ self.assertRaises(
+ tuple(valid_exceptions),
+ self._call_with_fake_service_token,
+ valid_token,
+ self.volumes_client,
+ 'detach_volume',
+ volume['id'])
+ self.assertRaises(
+ tuple(valid_exceptions),
+ self._call_with_fake_service_token,
+ valid_token,
+ self.volumes_client,
+ 'terminate_connection',
+ volume['id'], connector={})
+
+ # Reset volume's status to error
+ self.admin_volume_client.reset_volume_status(volume['id'],
+ status='error')
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'error')
+
+ # For the cleanup, we need to reset the volume status to in-use before
+ # the other cleanup steps try to detach it.
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.volumes_client, volume['id'], 'in-use')
+ self.addCleanup(self.admin_volume_client.reset_volume_status,
+ volume['id'], status='in-use')
+
+ # Test user call to force detach volume is rejected
+ self.assertRaises(
+ (exceptions.Forbidden, exceptions.Conflict),
+ self.admin_volume_client.force_detach_volume,
+ volume['id'], connector=None,
+ attachment_id=att_id)
+
+ # Test trying to override detach with force and service token
+ for valid_token in (True, False):
+ valid_exceptions = [exceptions.Forbidden, exceptions.Conflict]
+ if not valid_token:
+ valid_exceptions.append(exceptions.Unauthorized)
+ self.assertRaises(
+ tuple(valid_exceptions),
+ self._call_with_fake_service_token,
+ valid_token,
+ self.admin_volume_client,
+ 'force_detach_volume',
+ volume['id'], connector=None, attachment_id=att_id)
+
+ # Test user call to detach with mismatch is rejected
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume2['id'], 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume2 = self.volumes_client.show_volume(volume2['id'])['volume']
+
+ volume2 = self.nova_volume_attach(server, volume2)
+ att_id2 = volume2['attachments'][0]['attachment_id']
+ self.assertRaises(
+ (exceptions.Forbidden, exceptions.BadRequest),
+ self.volumes_client.detach_volume,
+ volume['id'], attachment_id=att_id2)
+
+
+class TestServerVolumeAttachScenarioOldVersion(BaseAttachmentTest):
+ volume_min_microversion = '3.27'
+ volume_max_microversion = 'latest'
+
+ @decorators.attr(type='slow')
+ @decorators.idempotent_id('6f4d2144-99f4-495c-8b0b-c6a537971418')
+ @utils.services('compute', 'volume', 'image', 'network')
+ def test_old_versions_reject(self):
+ server = self.create_server(wait_until='SSHABLE')
+ servers = self.servers_client.list_servers()['servers']
+ self.assertIn(server['id'], [x['id'] for x in servers])
+
+ volume = self.create_volume()
+
+ volume = self.nova_volume_attach(server, volume)
+ self.addCleanup(self.nova_volume_detach, server, volume)
+ att_id = volume['attachments'][0]['attachment_id']
+
+ for valid_token in (True, False):
+ valid_exceptions = [exceptions.Forbidden,
+ exceptions.Conflict]
+ if not valid_token:
+ valid_exceptions.append(exceptions.Unauthorized)
+ self.assertRaises(
+ tuple(valid_exceptions),
+ self._call_with_fake_service_token,
+ valid_token,
+ self.attachments_client,
+ 'delete_attachment',
+ att_id)
+
+ self.assertRaises(
+ (exceptions.Forbidden, exceptions.Conflict),
+ self.attachments_client.delete_attachment,
+ att_id)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 4b81b9e..92dbffb 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -16,6 +16,7 @@
import testtools
from tempest.common import utils
+from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
@@ -50,6 +51,8 @@
@classmethod
def skip_checks(cls):
super(TestStampPattern, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
if not CONF.volume_feature_enabled.snapshot:
raise cls.skipException("Cinder volume snapshots are disabled")
@@ -84,7 +87,7 @@
security_group = self.create_security_group()
# boot an instance and create a timestamp file in it
- volume = self.create_volume()
+ volume = self.create_volume(wait_until=None)
server = self.create_server(
key_name=keypair['name'],
security_groups=[{'name': security_group['name']}])
@@ -97,6 +100,12 @@
ip_for_server, private_key=keypair['private_key'],
server=server)
disks_list_before_attach = linux_client.list_disks()
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume = self.volumes_client.show_volume(volume['id'])['volume']
+
self.nova_volume_attach(server, volume)
volume_device_name = self._attached_volume_name(
disks_list_before_attach, ip_for_server, keypair['private_key'])
@@ -115,7 +124,7 @@
# create second volume from the snapshot(volume2)
volume_from_snapshot = self.create_volume(
- snapshot_id=volume_snapshot['id'])
+ snapshot_id=volume_snapshot['id'], wait_until=None)
# boot second instance from the snapshot(instance2)
server_from_snapshot = self.create_server(
@@ -135,6 +144,14 @@
disks_list_before_attach = linux_client.list_disks()
# attach volume2 to instance2
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume_from_snapshot['id'],
+ 'available')
+ # The volume retrieved on creation has a non-up-to-date status.
+ # Retrieval after it becomes active ensures correct details.
+ volume_from_snapshot = self.volumes_client.show_volume(
+ volume_from_snapshot['id'])['volume']
+
self.nova_volume_attach(server_from_snapshot, volume_from_snapshot)
volume_device_name = self._attached_volume_name(
disks_list_before_attach, ip_for_snapshot, keypair['private_key'])
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
index d0885cf..07ca38a 100644
--- a/tempest/scenario/test_volume_backup_restore.py
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -41,6 +41,8 @@
@classmethod
def skip_checks(cls):
super(TestVolumeBackupRestore, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
if not CONF.volume_feature_enabled.backup:
raise cls.skipException('Backup is not enable.')
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 2e87c15..6ebee48 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -31,6 +31,12 @@
# breathing room to get through deletes in the time allotted.
TIMEOUT_SCALING_FACTOR = 2
+ @classmethod
+ def skip_checks(cls):
+ super(TestVolumeBootPattern, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
+
def _delete_server(self, server):
self.servers_client.delete_server(server['id'])
waiters.wait_for_server_termination(self.servers_client, server['id'])
@@ -187,6 +193,7 @@
source_id=volume_origin['id'],
source_type='volume',
delete_on_termination=True,
+ wait_until='SSHABLE',
name=name)
# Create a snapshot image from the volume-backed server.
# The compute service will have the block service create a snapshot of
@@ -200,7 +207,8 @@
# disk for the server.
name = data_utils.rand_name(self.__class__.__name__ +
'-image-snapshot-server')
- instance2 = self.create_server(image_id=image['id'], name=name)
+ instance2 = self.create_server(image_id=image['id'], name=name,
+ wait_until='SSHABLE')
# Verify the server was created from the image-defined BDM.
volume_attachments = instance2['os-extended-volumes:volumes_attached']
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index 57d2a1a..5005346 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -48,6 +48,8 @@
@classmethod
def skip_checks(cls):
super(TestVolumeMigrateRetypeAttached, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ raise cls.skipException("Cinder is not available")
if not CONF.volume_feature_enabled.multi_backend:
raise cls.skipException("Cinder multi-backend feature disabled")
diff --git a/tempest/test.py b/tempest/test.py
index d49458e..3360221 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -809,7 +809,7 @@
@param os_clients: Clients to be used to provision the resources.
"""
if not CONF.validation.run_validation:
- return
+ return {}
if os_clients in cls._validation_resources:
return cls._validation_resources[os_clients]
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index 7d764be..9647467 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -153,13 +153,14 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=False)
resource_types = [k for k, _ in resources]
- # No admin, no swift, expect two credentials only
- self.assertEqual(2, len(resources))
- # Ensure create_user was invoked twice (two distinct users)
- self.assertEqual(2, self.user_create_fixture.mock.call_count)
+ # No admin, no swift, expect three credentials only
+ self.assertEqual(3, len(resources))
+ # Ensure create_user was invoked three times (three distinct users)
+ self.assertEqual(3, self.user_create_fixture.mock.call_count)
self.assertIn('primary', resource_types)
self.assertIn('alt', resource_types)
self.assertNotIn('admin', resource_types)
+ self.assertIn(['reader'], resource_types)
self.assertNotIn(['fake_operator'], resource_types)
self.assertNotIn(['fake_reseller'], resource_types)
self.assertNotIn(['fake_owner'], resource_types)
@@ -178,12 +179,13 @@
self.cred_provider, admin=True)
resource_types = [k for k, _ in resources]
# Admin, no swift, expect three credentials only
- self.assertEqual(3, len(resources))
- # Ensure create_user was invoked 3 times (3 distinct users)
- self.assertEqual(3, self.user_create_fixture.mock.call_count)
+ self.assertEqual(4, len(resources))
+ # Ensure create_user was invoked 4 times (4 distinct users)
+ self.assertEqual(4, self.user_create_fixture.mock.call_count)
self.assertIn('primary', resource_types)
self.assertIn('alt', resource_types)
self.assertIn('admin', resource_types)
+ self.assertIn(['reader'], resource_types)
self.assertNotIn(['fake_operator'], resource_types)
self.assertNotIn(['fake_reseller'], resource_types)
self.assertNotIn(['fake_owner'], resource_types)
@@ -201,13 +203,14 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=True)
resource_types = [k for k, _ in resources]
- # all options on, expect five credentials
- self.assertEqual(5, len(resources))
- # Ensure create_user was invoked 5 times (5 distinct users)
- self.assertEqual(5, self.user_create_fixture.mock.call_count)
+ # all options on, expect six credentials
+ self.assertEqual(6, len(resources))
+ # Ensure create_user was invoked 6 times (6 distinct users)
+ self.assertEqual(6, self.user_create_fixture.mock.call_count)
self.assertIn('primary', resource_types)
self.assertIn('alt', resource_types)
self.assertIn('admin', resource_types)
+ self.assertIn(['reader'], resource_types)
self.assertIn(['fake_operator'], resource_types)
self.assertIn(['fake_reseller'], resource_types)
for resource in resources:
@@ -224,13 +227,14 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=False)
resource_types = [k for k, _ in resources]
- # No Admin, swift, expect four credentials only
- self.assertEqual(4, len(resources))
- # Ensure create_user was invoked 4 times (4 distinct users)
- self.assertEqual(4, self.user_create_fixture.mock.call_count)
+ # No Admin, swift, expect five credentials only
+ self.assertEqual(5, len(resources))
+ # Ensure create_user was invoked 5 times (5 distinct users)
+ self.assertEqual(5, self.user_create_fixture.mock.call_count)
self.assertIn('primary', resource_types)
self.assertIn('alt', resource_types)
self.assertNotIn('admin', resource_types)
+ self.assertIn(['reader'], resource_types)
self.assertIn(['fake_operator'], resource_types)
self.assertIn(['fake_reseller'], resource_types)
self.assertNotIn(['fake_owner'], resource_types)
@@ -284,14 +288,14 @@
# Ordered args in [0], keyword args in [1]
accounts, f = yaml_dump_mock.call_args[0]
self.assertEqual(handle, f)
- self.assertEqual(5, len(accounts))
+ self.assertEqual(6, len(accounts))
if self.domain_is_in:
self.assertIn('domain_name', accounts[0].keys())
else:
self.assertNotIn('domain_name', accounts[0].keys())
self.assertEqual(1, len([x for x in accounts if
x.get('types') == ['admin']]))
- self.assertEqual(2, len([x for x in accounts if 'roles' in x]))
+ self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
for account in accounts:
self.assertIn('resources', account)
self.assertIn('network', account.get('resources'))
@@ -315,14 +319,14 @@
# Ordered args in [0], keyword args in [1]
accounts, f = yaml_dump_mock.call_args[0]
self.assertEqual(handle, f)
- self.assertEqual(5, len(accounts))
+ self.assertEqual(6, len(accounts))
if self.domain_is_in:
self.assertIn('domain_name', accounts[0].keys())
else:
self.assertNotIn('domain_name', accounts[0].keys())
self.assertEqual(1, len([x for x in accounts if
x.get('types') == ['admin']]))
- self.assertEqual(2, len([x for x in accounts if 'roles' in x]))
+ self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
for account in accounts:
self.assertIn('resources', account)
self.assertIn('network', account.get('resources'))
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 05ea84e..fa43e58 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -178,13 +178,13 @@
def test_verify_glance_version_no_v2_with_v1_1(self):
# This test verifies that wrong config api_v2 = True is detected
class FakeClient(object):
- def get_versions(self):
- return (None, ['v1.1'])
+ def list_versions(self):
+ return {'versions': [{'id': 'v1.1'}]}
fake_os = mock.MagicMock()
fake_module = mock.MagicMock()
- fake_module.ImagesClient = FakeClient
- fake_os.image_v1 = fake_module
+ fake_module.VersionsClient = FakeClient
+ fake_os.image_v2 = fake_module
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
@@ -194,53 +194,28 @@
def test_verify_glance_version_no_v2_with_v1_0(self):
# This test verifies that wrong config api_v2 = True is detected
class FakeClient(object):
- def get_versions(self):
- return (None, ['v1.0'])
+ def list_versions(self):
+ return {'versions': [{'id': 'v1.0'}]}
fake_os = mock.MagicMock()
fake_module = mock.MagicMock()
- fake_module.ImagesClient = FakeClient
- fake_os.image_v1 = fake_module
+ fake_module.VersionsClient = FakeClient
+ fake_os.image_v2 = fake_module
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
print_mock.assert_called_with('api_v2', 'image-feature-enabled',
False, True)
- def test_verify_glance_version_no_v1(self):
- # This test verifies that wrong config api_v1 = True is detected
- class FakeClient(object):
- def get_versions(self):
- raise lib_exc.NotFound()
-
- def list_versions(self):
- return {'versions': [{'id': 'v2.0'}]}
-
- fake_os = mock.MagicMock()
- fake_module = mock.MagicMock()
- fake_module.ImagesClient = FakeClient
- fake_module.VersionsClient = FakeClient
- fake_os.image_v1 = fake_module
- fake_os.image_v2 = fake_module
- with mock.patch.object(verify_tempest_config,
- 'print_and_or_update') as print_mock:
- verify_tempest_config.verify_glance_api_versions(fake_os, True)
- print_mock.assert_not_called()
-
def test_verify_glance_version_no_version(self):
- # This test verifies that wrong config api_v1 = True is detected
+ # This test verifies that wrong config api_v2 = True is detected
class FakeClient(object):
- def get_versions(self):
- raise lib_exc.NotFound()
-
def list_versions(self):
raise lib_exc.NotFound()
fake_os = mock.MagicMock()
fake_module = mock.MagicMock()
- fake_module.ImagesClient = FakeClient
fake_module.VersionsClient = FakeClient
- fake_os.image_v1 = fake_module
fake_os.image_v2 = fake_module
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
index 142bb08..4d933cd 100644
--- a/tempest/tests/common/test_compute.py
+++ b/tempest/tests/common/test_compute.py
@@ -17,8 +17,9 @@
from urllib import parse as urlparse
-
from tempest.common import compute
+from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
@@ -28,6 +29,58 @@
self.client_sock = mock.Mock()
self.url = urlparse.urlparse("http://www.fake.com:80")
+ @mock.patch('tempest.common.compute.' 'config.CONF.validation')
+ def test_get_server_ip_connect_method_floating(self, mock_conf):
+ fake_server = {'id': 'fake-uuid'}
+ fake_vr = {'floating_ip': {'ip': '10.10.10.1'}}
+ mock_conf.connect_method = 'floating'
+
+ fake_server_ip = compute.get_server_ip(fake_server, fake_vr)
+ self.assertEqual(fake_server_ip, '10.10.10.1')
+
+ # assert that InvalidParam is raised when validadation
+ # resources are not set
+ self.assertRaises(lib_exc.InvalidParam,
+ compute.get_server_ip,
+ fake_server)
+
+ @mock.patch('tempest.common.compute.' 'config.CONF.validation')
+ def test_get_server_ip_connect_method_fixed(self, mock_conf):
+ fake_server = {'id': 'fake-uuid',
+ 'addresses': {
+ 'private': [
+ {'addr': '192.168.0.3',
+ 'version': 4}]}}
+ mock_conf.connect_method = 'fixed'
+ mock_conf.network_for_ssh = 'private'
+ mock_conf.ip_version_for_ssh = 4
+
+ fake_server_ip = compute.get_server_ip(fake_server)
+ self.assertEqual(fake_server_ip, '192.168.0.3')
+
+ fake_server_v6 = {'id': 'fake-uuid',
+ 'addresses': {
+ 'private': [
+ {'addr': '2345:0425:2CA1::0567:5673:23b5',
+ 'version': 6}]}}
+ # assert when server is unreachable
+ self.assertRaises(exceptions.ServerUnreachable,
+ compute.get_server_ip,
+ fake_server_v6)
+
+ @mock.patch('tempest.common.compute.' 'config.CONF.validation')
+ def test_get_server_ip_invalid_config(self, mock_conf):
+ fake_server = {'id': 'fake-uuid',
+ 'addresses': {
+ 'private': [
+ {'addr': '192.168.0.3',
+ 'version': 4}]}}
+ mock_conf.connect_method = 'fake-method'
+ # assert when the connection method is not correctly set
+ self.assertRaises(lib_exc.InvalidConfiguration,
+ compute.get_server_ip,
+ fake_server)
+
def test_rfp_frame_not_cached(self):
# rfp negotiation frame arrived separately after upgrade
# response, so it's not cached.
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 93c949e..f194173 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -27,6 +27,78 @@
import tempest.tests.utils as utils
+class TestServerWaiters(base.TestCase):
+ def setUp(self):
+ super(TestServerWaiters, self).setUp()
+ self.client = mock.MagicMock()
+ self.client.build_timeout = 1
+ self.client.build_interval = 1
+
+ def test_wait_for_server_status(self):
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'ACTIVE'}
+ self.client.show_server.return_value = ({'server': fake_server})
+ start_time = int(time.time())
+ waiters.wait_for_server_status(
+ self.client, fake_server['id'], 'ACTIVE')
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertLess((end_time - start_time), 10)
+
+ def test_wait_for_server_status_build(self):
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'BUILD'}
+ self.client.show_server.return_value = ({'server': fake_server})
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, fake_server['id'], 'BUILD')
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertLess((end_time - start_time), 10)
+
+ def test_wait_for_server_status_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'SAVING'}
+ self.client.show_server.return_value = ({'server': fake_server})
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_server_status,
+ self.client, fake_server['id'], 'ACTIVE')
+
+ def test_wait_for_server_status_error_on_server_build(self):
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'ERROR'}
+ self.client.show_server.return_value = ({'server': fake_server})
+ self.assertRaises(exceptions.BuildErrorException,
+ waiters.wait_for_server_status,
+ self.client, fake_server['id'], 'ACTIVE')
+
+ def test_wait_for_server_termination(self):
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'ACTIVE'}
+ self.client.show_server.side_effect = lib_exc.NotFound
+ waiters.wait_for_server_termination(self.client, fake_server['id'])
+
+ def test_wait_for_server_termination_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'ACTIVE'}
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_server_termination,
+ self.client, fake_server['id'])
+
+ def test_wait_for_server_termination_error_status(self):
+ fake_server = {'id': 'fake-uuid',
+ 'status': 'ERROR'}
+ self.client.show_server.return_value = ({'server': fake_server})
+ self.assertRaises(lib_exc.DeleteErrorException,
+ waiters.wait_for_server_termination,
+ self.client, fake_server['id'])
+
+
class TestImageWaiters(base.TestCase):
def setUp(self):
super(TestImageWaiters, self).setUp()
@@ -145,6 +217,15 @@
waiters.wait_for_image_copied_to_stores,
self.client, 'fake_image_id')
+ def test_wait_for_image_copied_to_stores_status_killed(self):
+ self.client.show_image.return_value = ({
+ 'status': 'killed',
+ 'os_glance_importing_to_stores': None,
+ 'os_glance_failed_import': 'fake_os_glance_failed_import'})
+ self.assertRaises(exceptions.ImageKilledException,
+ waiters.wait_for_image_copied_to_stores,
+ self.client, 'fake_image_id')
+
def test_wait_for_image_tasks_status(self):
self.client.show_image_tasks.return_value = ({
'tasks': [{'status': 'success'}]})
@@ -168,6 +249,28 @@
waiters.wait_for_image_tasks_status,
self.client, 'fake_image_id', 'success')
+ def test_wait_for_tasks_status(self):
+ self.client.show_tasks.return_value = ({
+ 'status': 'success'})
+ start_time = int(time.time())
+ waiters.wait_for_tasks_status(
+ self.client, 'fake_task_id', 'success')
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertLess((end_time - start_time), 10)
+
+ def test_wait_for_tasks_status_timeout(self):
+ time_mock = self.patch('time.time')
+ self.patch('time.time', side_effect=[0., 1.])
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ self.client.show_tasks.return_value = (
+ {'status': 'success'},
+ {'status': 'processing'})
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_tasks_status,
+ self.client, 'fake_task_id', 'success')
+
class TestInterfaceWaiters(base.TestCase):
@@ -366,6 +469,31 @@
mock.call(mock.sentinel.volume_id),
mock.call(mock.sentinel.volume_id)])
+ def test_wait_for_volume_retype(self):
+ fake_volume = {'volume_type': {'id': 'fake-uuid'}}
+ show_volume = mock.Mock(return_value={'volume': fake_volume})
+ client = mock.Mock(resource_type="volume",
+ build_interval=1,
+ build_timeout=1,
+ show_volume=show_volume)
+ waiters.wait_for_volume_retype(
+ client, mock.sentinel.volume_id, fake_volume['volume_type'])
+
+ def test_wait_for_volume_retype_timeout(self):
+ fake_volume = {'volume_type': {'id': 'fake-uuid'}}
+ show_volume = mock.Mock(return_value={'volume': fake_volume})
+ client = mock.Mock(resource_type="volume",
+ build_interval=1,
+ build_timeout=1,
+ show_volume=show_volume)
+
+ self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+ self.patch('time.sleep')
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_volume_retype,
+ client, mock.sentinel.volume_id,
+ 'fake_volume_type')
+
@mock.patch.object(time, 'sleep')
def test_wait_for_volume_status_error_restoring(self, mock_sleep):
# Tests that the wait method raises VolumeRestoreErrorException if
@@ -450,7 +578,25 @@
mock.call(uuids.volume_id),
mock.call(uuids.volume_id)])
- def test_wait_for_volume_attachment(self):
+ def test_wait_for_volume_attachment_create_timeout(self):
+ show_volume = mock.MagicMock(return_value={
+ 'volume': {'attachments': [
+ {'attachment_id': uuids.attachment_id,
+ 'server_id': uuids.server_id,
+ 'volume_id': uuids.volume_id}]}})
+ client = mock.Mock(spec=volumes_client.VolumesClient,
+ build_interval=1,
+ build_timeout=1,
+ show_volume=show_volume)
+ self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+ self.patch('time.sleep')
+ # Assert that a timeout is raised if the attachment is not
+ # created within required time
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_volume_attachment_create,
+ client, 'fake_volume_id', 'fake_server_id')
+
+ def test_wait_for_volume_attachment_remove(self):
vol_detached = {'volume': {'attachments': []}}
vol_attached = {'volume': {'attachments': [
{'attachment_id': uuids.attachment_id}]}}
@@ -469,7 +615,7 @@
mock.call(uuids.volume_id),
mock.call(uuids.volume_id)])
- def test_wait_for_volume_attachment_timeout(self):
+ def test_wait_for_volume_attachment_remove_timeout(self):
show_volume = mock.MagicMock(return_value={
'volume': {'attachments': [
{'attachment_id': uuids.attachment_id}]}})
@@ -635,6 +781,67 @@
.1
)
+ def test_wait_for_caching(self):
+ mock_client = mock.Mock(
+ build_interval=1,
+ build_timeout=1
+ )
+ mock_cache_client = mock.Mock()
+ mock_cache_client.list_cache.return_value = {
+ "cached_images": [{
+ "image_id": 'fake_image_id'}]}
+ waiters.wait_for_caching(
+ mock_client, mock_cache_client, 'fake_image_id')
+
+ def test_wait_for_caching_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ mock_client = mock.Mock(
+ build_interval=1,
+ build_timeout=1
+ )
+ mock_cache_client = mock.Mock()
+ mock_cache_client.list_cache.return_value = {
+ "cached_images": [{
+ "image_id": 'fake_image_id'}]}
+ # Assert that TimeoutException is raised when the image
+ # failed to cache in time
+ self.assertRaises(
+ lib_exc.TimeoutException,
+ waiters.wait_for_caching,
+ mock_client,
+ mock_cache_client,
+ 'fake_image_id'
+ )
+
+ def test_wait_for_object_create(self):
+ mock_object_client = mock.Mock(
+ build_interval=1,
+ build_timeout=1
+ )
+ waiters.wait_for_object_create(
+ mock_object_client, 'fake_container', 'fake_object')
+
+ def test_wait_for_object_create_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ mock_object_client = mock.Mock(
+ build_interval=1,
+ build_timeout=1
+ )
+ # Assert that TimeoutException is raised when the object is not
+ # created in time
+ self.assertRaises(
+ lib_exc.TimeoutException,
+ waiters.wait_for_object_create,
+ mock_object_client,
+ 'fake_container',
+ 'fake_object',
+ .1
+ )
+
class TestPortCreationWaiter(base.TestCase):
def test_wait_for_port_status(self):
diff --git a/tempest/tests/lib/common/test_cred_client.py b/tempest/tests/lib/common/test_cred_client.py
index 7ea660b..e44c5ed 100644
--- a/tempest/tests/lib/common/test_cred_client.py
+++ b/tempest/tests/lib/common/test_cred_client.py
@@ -38,6 +38,13 @@
self.projects_client.create_tenant.assert_called_once_with(
name='fake_name', description='desc')
+ def test_show_project(self):
+ self.projects_client.show_tenant.return_value = {
+ 'tenant': 'a_tenant'
+ }
+ res = self.creds_client.show_project('fake_id')
+ self.assertEqual('a_tenant', res)
+
def test_delete_project(self):
self.creds_client.delete_project('fake_id')
self.projects_client.delete_tenant.assert_called_once_with(
@@ -79,11 +86,32 @@
self.projects_client.create_project.assert_called_once_with(
name='fake_name', description='desc', domain_id='fake_domain_id')
+ def test_show_project(self):
+ self.projects_client.show_project.return_value = {
+ 'project': 'a_tenant'
+ }
+ res = self.creds_client.show_project('fake_id')
+ self.assertEqual('a_tenant', res)
+
def test_delete_project(self):
self.creds_client.delete_project('fake_id')
self.projects_client.delete_project.assert_called_once_with(
'fake_id')
+ def test_create_domain(self):
+ self.domains_client.create_domain.return_value = {
+ 'domain': 'a_tenant'
+ }
+ res = self.creds_client.create_domain('fake_name', 'desc')
+ self.assertEqual('a_tenant', res)
+ self.domains_client.create_domain.assert_called_once_with(
+ name='fake_name', description='desc')
+
+ def test_delete_domain(self):
+ self.creds_client.delete_domain('fake_id')
+ self.domains_client.delete_domain.assert_called_once_with(
+ 'fake_id')
+
def test_get_credentials(self):
ret = self.creds_client.get_credentials(
{'name': 'some_user', 'id': 'fake_id'},
diff --git a/tempest/tests/lib/services/compute/test_fixedIPs_client.py b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
deleted file mode 100644
index 65bda45..0000000
--- a/tempest/tests/lib/services/compute/test_fixedIPs_client.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2015 NEC 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.lib.services.compute import fixed_ips_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestFixedIPsClient(base.BaseServiceTest):
- FIXED_IP_INFO = {"fixed_ip": {"address": "10.0.0.1",
- "cidr": "10.11.12.0/24",
- "host": "localhost",
- "hostname": "OpenStack"}}
-
- def setUp(self):
- super(TestFixedIPsClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.fixedIPsClient = (fixed_ips_client.
- FixedIPsClient
- (fake_auth, 'compute',
- 'regionOne'))
-
- def _test_show_fixed_ip(self, bytes_body=False):
- self.check_service_client_function(
- self.fixedIPsClient.show_fixed_ip,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FIXED_IP_INFO, bytes_body,
- status=200, fixed_ip='Identifier')
-
- def test_show_fixed_ip_with_str_body(self):
- self._test_show_fixed_ip()
-
- def test_show_fixed_ip_with_bytes_body(self):
- self._test_show_fixed_ip(True)
-
- def _test_reserve_fixed_ip(self, bytes_body=False):
- self.check_service_client_function(
- self.fixedIPsClient.reserve_fixed_ip,
- 'tempest.lib.common.rest_client.RestClient.post',
- {}, bytes_body,
- status=202, fixed_ip='Identifier')
-
- def test_reserve_fixed_ip_with_str_body(self):
- self._test_reserve_fixed_ip()
-
- def test_reserve_fixed_ip_with_bytes_body(self):
- self._test_reserve_fixed_ip(True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
deleted file mode 100644
index 6278df4..0000000
--- a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2015 NEC 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.lib.services.compute import floating_ip_pools_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestFloatingIPPoolsClient(base.BaseServiceTest):
-
- FAKE_FLOATING_IP_POOLS = {
- "floating_ip_pools":
- [
- {"name": '\u3042'},
- {"name": '\u3044'}
- ]
- }
-
- def setUp(self):
- super(TestFloatingIPPoolsClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = floating_ip_pools_client.FloatingIPPoolsClient(
- fake_auth, 'compute', 'regionOne')
-
- def test_list_floating_ip_pools_with_str_body(self):
- self.check_service_client_function(
- self.client.list_floating_ip_pools,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_FLOATING_IP_POOLS)
-
- def test_list_floating_ip_pools_with_bytes_body(self):
- self.check_service_client_function(
- self.client.list_floating_ip_pools,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_FLOATING_IP_POOLS, to_utf=True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py b/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
deleted file mode 100644
index ace76f8..0000000
--- a/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2015 NEC 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.tests.lib import fake_auth_provider
-
-from tempest.lib.services.compute import floating_ips_bulk_client
-from tempest.tests.lib.services import base
-
-
-class TestFloatingIPsBulkClient(base.BaseServiceTest):
-
- FAKE_FIP_BULK_LIST = {"floating_ip_info": [{
- "address": "10.10.10.1",
- "instance_uuid": None,
- "fixed_ip": None,
- "interface": "eth0",
- "pool": "nova",
- "project_id": None
- },
- {
- "address": "10.10.10.2",
- "instance_uuid": None,
- "fixed_ip": None,
- "interface": "eth0",
- "pool": "nova",
- "project_id": None
- }]}
-
- def setUp(self):
- super(TestFloatingIPsBulkClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = floating_ips_bulk_client.FloatingIPsBulkClient(
- fake_auth, 'compute', 'regionOne')
-
- def _test_list_floating_ips_bulk(self, bytes_body=False):
- self.check_service_client_function(
- self.client.list_floating_ips_bulk,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_FIP_BULK_LIST,
- to_utf=bytes_body)
-
- def _test_create_floating_ips_bulk(self, bytes_body=False):
- fake_fip_create_data = {"floating_ips_bulk_create": {
- "ip_range": "192.168.1.0/24", "pool": "nova", "interface": "eth0"}}
- self.check_service_client_function(
- self.client.create_floating_ips_bulk,
- 'tempest.lib.common.rest_client.RestClient.post',
- fake_fip_create_data,
- to_utf=bytes_body,
- ip_range="192.168.1.0/24", pool="nova", interface="eth0")
-
- def _test_delete_floating_ips_bulk(self, bytes_body=False):
- fake_fip_delete_data = {"floating_ips_bulk_delete": "192.168.1.0/24"}
- self.check_service_client_function(
- self.client.delete_floating_ips_bulk,
- 'tempest.lib.common.rest_client.RestClient.put',
- fake_fip_delete_data,
- to_utf=bytes_body,
- ip_range="192.168.1.0/24")
-
- def test_list_floating_ips_bulk_with_str_body(self):
- self._test_list_floating_ips_bulk()
-
- def test_list_floating_ips_bulk_with_bytes_body(self):
- self._test_list_floating_ips_bulk(True)
-
- def test_create_floating_ips_bulk_with_str_body(self):
- self._test_create_floating_ips_bulk()
-
- def test_create_floating_ips_bulk_with_bytes_body(self):
- self._test_create_floating_ips_bulk(True)
-
- def test_delete_floating_ips_bulk_with_str_body(self):
- self._test_delete_floating_ips_bulk()
-
- def test_delete_floating_ips_bulk_with_bytes_body(self):
- self._test_delete_floating_ips_bulk(True)
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index a82b255..8df82f7 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -789,21 +789,6 @@
length='fake-length'
)
- def test_list_virtual_interfaces_with_str_body(self):
- self._test_list_virtual_interfaces()
-
- def test_list_virtual_interfaces_with_bytes_body(self):
- self._test_list_virtual_interfaces(True)
-
- def _test_list_virtual_interfaces(self, bytes_body=False):
- self.check_service_client_function(
- self.client.list_virtual_interfaces,
- 'tempest.lib.common.rest_client.RestClient.get',
- {'virtual_interfaces': [self.FAKE_VIRTUAL_INTERFACES]},
- bytes_body,
- server_id=self.server_id
- )
-
def test_rescue_server_with_str_body(self):
self._test_rescue_server()
diff --git a/tempest/tests/lib/services/image/v1/__init__.py b/tempest/tests/lib/services/image/v1/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/lib/services/image/v1/__init__.py
+++ /dev/null
diff --git a/tempest/tests/lib/services/image/v1/test_image_members_client.py b/tempest/tests/lib/services/image/v1/test_image_members_client.py
deleted file mode 100644
index a5a6128..0000000
--- a/tempest/tests/lib/services/image/v1/test_image_members_client.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright 2016 NEC 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.lib.services.image.v1 import image_members_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestImageMembersClient(base.BaseServiceTest):
- FAKE_LIST_IMAGE_MEMBERS = {
- "members": [
- {
- "created_at": "2013-10-07T17:58:03Z",
- "image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe",
- "member_id": "123456789",
- "status": "pending",
- "updated_at": "2013-10-07T17:58:03Z"
- },
- {
- "created_at": "2013-10-07T17:58:55Z",
- "image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe",
- "member_id": "987654321",
- "status": "accepted",
- "updated_at": "2013-10-08T12:08:55Z"
- }
- ]
- }
-
- def setUp(self):
- super(TestImageMembersClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = image_members_client.ImageMembersClient(fake_auth,
- 'image',
- 'regionOne')
-
- def _test_list_image_members(self, bytes_body=False):
- self.check_service_client_function(
- self.client.list_image_members,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_LIST_IMAGE_MEMBERS,
- bytes_body,
- image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e")
-
- def _test_create_image_member(self, bytes_body=False):
- self.check_service_client_function(
- self.client.create_image_member,
- 'tempest.lib.common.rest_client.RestClient.put',
- {},
- bytes_body,
- image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
- member_id="8989447062e04a818baf9e073fd04fa7",
- status=204)
-
- def test_list_image_members_with_str_body(self):
- self._test_list_image_members()
-
- def test_list_image_members_with_bytes_body(self):
- self._test_list_image_members(bytes_body=True)
-
- def test_create_image_member_with_str_body(self):
- self._test_create_image_member()
-
- def test_create_image_member_with_bytes_body(self):
- self._test_create_image_member(bytes_body=True)
-
- def test_delete_image_member(self):
- self.check_service_client_function(
- self.client.delete_image_member,
- 'tempest.lib.common.rest_client.RestClient.delete',
- {},
- image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
- member_id="8989447062e04a818baf9e073fd04fa7",
- status=204)
diff --git a/tempest/tests/lib/services/image/v2/test_image_tasks_client.py b/tempest/tests/lib/services/image/v2/test_image_tasks_client.py
new file mode 100644
index 0000000..6e3b3b5
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_image_tasks_client.py
@@ -0,0 +1,86 @@
+# Copyright 2023 Red Hat, Inc. 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.image.v2 import tasks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestImageTaskClient(base.BaseServiceTest):
+ def setUp(self):
+ super(TestImageTaskClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tasks_client.TaskClient(
+ fake_auth, 'image', 'regionOne')
+
+ def test_list_task(self):
+ fake_result = {
+
+ "first": "/v2/tasks",
+ "schema": "/v2/schemas/tasks",
+ "tasks": [
+ {
+ "id": "08b7e1c8-3821-4f54-b3b8-d6655d178cdf",
+ "owner": "fa6c8c1600f4444281658a23ee6da8e8",
+ "schema": "/v2/schemas/task",
+ "self": "/v2/tasks/08b7e1c8-3821-4f54-b3b8-d6655d178cdf",
+ "status": "processing",
+ "type": "import"
+ },
+ {
+ "id": "231c311d-3557-4e23-afc4-6d98af1419e7",
+ "owner": "fa6c8c1600f4444281658a23ee6da8e8",
+ "schema": "/v2/schemas/task",
+ "self": "/v2/tasks/231c311d-3557-4e23-afc4-6d98af1419e7",
+ "status": "processing",
+ "type": "import"
+ }
+ ]
+ }
+ self.check_service_client_function(
+ self.client.list_tasks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_result,
+ mock_args=['tasks'])
+
+ def test_create_task(self):
+ fake_result = {
+ "type": "import",
+ "input": {
+ "import_from":
+ "http://download.cirros-cloud.net/0.6.1/ \
+ cirros-0.6.1-x86_64-disk.img",
+ "import_from_format": "qcow2",
+ "image_properties": {
+ "disk_format": "qcow2",
+ "container_format": "bare"
+ }
+ }
+ }
+ self.check_service_client_function(
+ self.client.create_task,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ fake_result,
+ status=201)
+
+ def test_show_task(self):
+ fake_result = {
+ "task_id": "08b7e1c8-3821-4f54-b3b8-d6655d178cdf"
+ }
+ self.check_service_client_function(
+ self.client.show_tasks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_result,
+ status=200,
+ task_id="e485aab9-0907-4973-921c-bb6da8a8fcf8")
diff --git a/tempest/tests/lib/services/registry_fixture.py b/tempest/tests/lib/services/registry_fixture.py
index a368705..d722b06 100644
--- a/tempest/tests/lib/services/registry_fixture.py
+++ b/tempest/tests/lib/services/registry_fixture.py
@@ -37,7 +37,7 @@
def __init__(self):
"""Initialise the registry fixture"""
self.services = set(['compute', 'identity.v2', 'identity.v3',
- 'image.v1', 'image.v2', 'network', 'placement',
+ 'image.v2', 'network', 'placement',
'volume.v2', 'volume.v3', 'object-storage'])
def _setUp(self):
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 26e8079..80825a4 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -69,7 +69,7 @@
creds = fake_credentials.FakeKeystoneV3Credentials()
osclients = clients.Manager(creds)
vr = self.test_test_class.get_class_validation_resources(osclients)
- self.assertIsNone(vr)
+ self.assertEqual({}, vr)
def test_validation_resources_exists(self):
cfg.CONF.set_default('run_validation', True, 'validation')
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index b96bbe4..0b6b342 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -77,6 +77,9 @@
'x/ranger-tempest-plugin'
'x/tap-as-a-service-tempest-plugin'
'x/trio2o'
+ # No changes are merging in this
+ # https://review.opendev.org/q/project:x%252Fnetworking-fortinet
+ 'x/networking-fortinet'
]
url = 'https://review.opendev.org/projects/'
diff --git a/tox.ini b/tox.ini
index 47ef5eb..de81707 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,8 @@
[tox]
envlist = pep8,py39,bashate,pip-check-reqs
minversion = 3.18.0
-ignore_basepython_conflict = True
[tempestenv]
-basepython = python3
sitepackages = False
setenv =
VIRTUAL_ENV={envdir}
@@ -15,7 +13,6 @@
-r{toxinidir}/requirements.txt
[testenv]
-basepython = python3
setenv =
VIRTUAL_ENV={envdir}
OS_LOG_CAPTURE=1
@@ -64,7 +61,7 @@
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
- coverage report
+ coverage report -m --fail-under=76
[testenv:debug]
commands = oslo_debug_helper -t tempest/tests {posargs}
@@ -72,7 +69,6 @@
[testenv:all]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
# 'all' includes slow tests
setenv =
{[tempestenv]setenv}
@@ -93,7 +89,6 @@
# 'all' includes slow tests
setenv =
{[tempestenv]setenv}
-basepython = {[tempestenv]basepython}
deps = {[tempestenv]deps}
commands =
echo "WARNING: The all-plugin env is deprecated and will be removed"
@@ -106,7 +101,6 @@
# 'all' includes slow tests
setenv =
{[tempestenv]setenv}
-basepython = {[tempestenv]basepython}
deps = {[tempestenv]deps}
commands =
find . -type f -name "*.pyc" -delete
@@ -115,7 +109,6 @@
[testenv:full]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
@@ -129,7 +122,6 @@
[testenv:integrated-full]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run. It exclude the extra
@@ -146,7 +138,6 @@
[testenv:extra-tests]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select extra tests mentioned in
@@ -161,7 +152,6 @@
[testenv:full-parallel]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# But exlcude the extra tests mentioned in tools/tempest-extra-tests-list.txt
@@ -173,7 +163,6 @@
[testenv:api-microversion-tests]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '(^tempest\.api\.compute)|(^tempest\.api\.volume)'
@@ -186,7 +175,6 @@
[testenv:integrated-network]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex1 = '(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
@@ -201,7 +189,6 @@
[testenv:integrated-compute]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex1 = '(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
@@ -216,7 +203,6 @@
[testenv:integrated-placement]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex1 = '(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
@@ -231,7 +217,6 @@
[testenv:integrated-storage]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex1 = '(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
@@ -246,7 +231,6 @@
[testenv:integrated-object-storage]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex1 = '(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
@@ -261,7 +245,6 @@
[testenv:full-serial]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|serial_tests))'
@@ -275,7 +258,6 @@
[testenv:scenario]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '(^tempest\.scenario)'
@@ -287,7 +269,6 @@
[testenv:smoke]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '\[.*\bsmoke\b.*\]'
@@ -298,7 +279,6 @@
[testenv:smoke-serial]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '\[.*\bsmoke\b.*\]'
@@ -312,7 +292,6 @@
[testenv:slow-serial]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '\[.*\bslow\b.*\]'
@@ -324,7 +303,6 @@
[testenv:slow]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select the slow tagged tests:
@@ -336,7 +314,6 @@
[testenv:multinode]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select the multinode and smoke tagged tests
@@ -348,7 +325,6 @@
[testenv:ipv6-only]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '\[.*\bsmoke|ipv6|test_network_v6\b.*\]'
@@ -369,7 +345,6 @@
[testenv:venv-tempest]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
commands = {posargs}
@@ -409,7 +384,7 @@
[testenv:pep8]
deps =
- -r{toxinidir}/test-requirements.txt
+ {[testenv]deps}
autopep8
commands =
autopep8 --exit-code --max-line-length=79 --experimental --diff -r tempest setup.py
@@ -513,7 +488,6 @@
[testenv:stestr-master]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
-basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
regex = '\[.*\bsmoke\b.*\]'
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index 2d978c0..0ac893a 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -13,6 +13,8 @@
roles: &base_roles
- zuul: opendev.org/openstack/devstack
vars: &base_vars
+ devstack_localrc:
+ IMAGE_URLS: http://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img, http://download.cirros-cloud.net/0.6.1/cirros-0.6.1-x86_64-disk.img
devstack_services:
tempest: true
devstack_local_conf:
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 6c495b7..4b4306c 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -20,8 +20,6 @@
- job:
name: tempest-ipv6-only
parent: devstack-tempest-ipv6
- # This currently works from stable/pike on.
- branches: ^(?!stable/ocata).*$
description: |
Integration test of IPv6-only deployments. This job runs
smoke and IPv6 relates tests only. Basic idea is to test
@@ -33,10 +31,6 @@
- job:
name: tempest-full
parent: devstack-tempest
- # This currently works from stable/pike on.
- # Before stable/pike, legacy version of tempest-full
- # 'legacy-tempest-dsvm-neutron-full' run.
- branches: ^(?!stable/ocata).*$
description: |
Base integration test with Neutron networking and py27.
This job is supposed to run until stable/train setup only.
@@ -78,7 +72,9 @@
# available in old tempest used till stable/wallaby,
# this job definition is only for stable/xena onwards
# and separate job definition until stable/wallaby
- branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(stein|train|ussuri|victoria|wallaby)$
+ negate: true
description: |
Base integration test with Neutron networking, horizon, swift enable,
and py3.
@@ -88,6 +84,11 @@
required-projects:
- openstack/horizon
vars:
+ # NOTE(gmann): Default concurrency is higher (number of cpu -2) which
+ # end up 6 in upstream CI. Higher concurrency means high parallel
+ # requests to services and can cause more oom issues. To avoid the
+ # oom issue, setting the concurrency to 4 in this job.
+ tempest_concurrency: 4
tox_envlist: integrated-full
devstack_localrc:
USE_PYTHON3: true
@@ -100,21 +101,14 @@
# Enbale horizon so that we can run horizon test.
horizon: true
-# TODO(gmann): As per the 2023.1 testing runtime, we need to run at least
-# one job on Focal. This job can be removed as per the future testing
-# runtime (whenever we drop the Ubuntu Focal testing).
-- job:
- name: tempest-full-ubuntu-focal
- description: This is tempest-full python3 job on Ubuntu Focal(20.04)
- parent: tempest-full-py3
- nodeset: openstack-single-node-focal
-
- job:
name: tempest-full-centos-9-stream
parent: tempest-full-py3
nodeset: devstack-single-node-centos-9-stream
# centos-9-stream is supported from yoga release onwards
- branches: ^(?!stable/(pike|queens|rocky|stein|train|ussuri|victoria|wallaby|xena)).*$
+ branches:
+ regex: ^stable/(stein|train|ussuri|victoria|wallaby|xena)$
+ negate: true
description: |
Base integration test on CentOS 9 stream
vars:
@@ -126,7 +120,6 @@
- job:
name: tempest-integrated-networking
parent: devstack-tempest
- branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for networking. This is subset of
'tempest-full-py3' job and run only Neutron and Nova related tests.
@@ -146,12 +139,16 @@
- job:
name: tempest-integrated-compute
parent: devstack-tempest
- branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for compute. This is
subset of 'tempest-full-py3' job and run Nova, Neutron, Cinder (except backup tests)
and Glance related tests. This is meant to be run on Nova gate only.
vars:
+ # NOTE(gmann): Default concurrency is higher (number of cpu -2) which
+ # end up 6 in upstream CI. Higher concurrency means high parallel
+ # requests to services and can cause more oom issues. To avoid the
+ # oom issue, setting the concurrency to 4 in this job.
+ tempest_concurrency: 4
tox_envlist: integrated-compute
tempest_exclude_regex: ""
devstack_localrc:
@@ -170,7 +167,9 @@
parent: tempest-integrated-compute
nodeset: devstack-single-node-centos-9-stream
# centos-9-stream is supported from yoga release onwards
- branches: ^(?!stable/(pike|queens|rocky|stein|train|ussuri|victoria|wallaby|xena)).*$
+ branches:
+ regex: ^stable/(stein|train|ussuri|victoria|wallaby|xena)$
+ negate: true
description: |
This job runs integration tests for compute. This is
subset of 'tempest-full-py3' job and run Nova, Neutron, Cinder (except backup tests)
@@ -184,12 +183,16 @@
- job:
name: tempest-integrated-placement
parent: devstack-tempest
- branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for placement. This is
subset of 'tempest-full-py3' job and run Nova and Neutron
related tests. This is meant to be run on Placement gate only.
vars:
+ # NOTE(gmann): Default concurrency is higher (number of cpu -2) which
+ # end up 6 in upstream CI. Higher concurrency means high parallel
+ # requests to services and can cause more oom issues. To avoid the
+ # oom issue, setting the concurrency to 4 in this job.
+ tempest_concurrency: 4
tox_envlist: integrated-placement
devstack_localrc:
USE_PYTHON3: true
@@ -205,7 +208,6 @@
- job:
name: tempest-integrated-storage
parent: devstack-tempest
- branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for image & block storage. This is
subset of 'tempest-full-py3' job and run Cinder, Glance, Swift and Nova
@@ -221,7 +223,6 @@
- job:
name: tempest-integrated-object-storage
parent: devstack-tempest
- branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for object storage. This is
subset of 'tempest-full-py3' job and run Swift, Cinder and Glance
@@ -253,8 +254,15 @@
parent: tempest-multinode-full-base
nodeset: openstack-two-node-jammy
# This job runs on ubuntu Jammy and after stable/zed.
- branches: ^(?!stable/(pike|queens|rocky|stein|train|ussuri|victoria|wallaby|xena|yoga|zed)).*$
+ branches:
+ regex: ^stable/(stein|train|ussuri|victoria|wallaby|xena|yoga|zed)$
+ negate: true
vars:
+ # NOTE(gmann): Default concurrency is higher (number of cpu -2) which
+ # end up 6 in upstream CI. Higher concurrency means high parallel
+ # requests to services and can cause more oom issues. To avoid the
+ # oom issue, setting the concurrency to 4 in this job.
+ tempest_concurrency: 4
devstack_localrc:
USE_PYTHON3: true
devstack_plugins:
@@ -283,9 +291,7 @@
* legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend
* tempest-scenario-multinode-lvm-multibackend
timeout: 10800
- # This job runs on stable/stein onwards.
- branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
- vars: &tempest_slow_vars
+ vars:
tox_envlist: slow-serial
devstack_localrc:
CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
@@ -295,7 +301,6 @@
devstack_services:
neutron-placement: true
neutron-qos: true
- tempest_concurrency: 2
group-vars:
# NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
# the controller and subnode prior to Rocky so we have to make sure the
@@ -310,17 +315,29 @@
# This job version is with swift enabled on py3
# as swift is ready on py3 from stable/ussuri onwards.
timeout: 10800
- branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train)).*$
- vars: *tempest_slow_vars
-
-- job:
- name: tempest-slow-parallel
- parent: tempest-slow-py3
- # This job run slow tests in parallel.
+ # As the 'slow' tox env which is not available in old tempest used
+ # till stable/wallaby, this job definition is only for stable/xena
+ # onwards and separate job definition until stable/wallaby
+ branches:
+ regex: ^stable/(stein|train|ussuri|victoria|wallaby)$
+ negate: true
vars:
tox_envlist: slow
devstack_localrc:
- MYSQL_REDUCE_MEMORY: true
+ CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
+ ENABLE_VOLUME_MULTIATTACH: true
+ devstack_plugins:
+ neutron: https://opendev.org/openstack/neutron
+ devstack_services:
+ neutron-placement: true
+ neutron-qos: true
+ group-vars:
+ # NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
+ # the controller and subnode prior to Rocky so we have to make sure the
+ # variable is set in both locations.
+ subnode:
+ devstack_localrc:
+ ENABLE_VOLUME_MULTIATTACH: true
- job:
name: tempest-cinder-v2-api
@@ -342,11 +359,11 @@
description: |
Integration testing for a FIPS enabled Centos 8 system
nodeset: devstack-single-node-centos-8-stream
- pre-run: playbooks/enable-fips.yaml
vars:
tox_envlist: full
configure_swap_size: 4096
nslookup_target: 'opendev.org'
+ enable_fips: True
- job:
name: tempest-centos9-stream-fips
@@ -355,11 +372,11 @@
Integration testing for a FIPS enabled Centos 9 system
timeout: 10800
nodeset: devstack-single-node-centos-9-stream
- pre-run: playbooks/enable-fips.yaml
vars:
tox_envlist: full
configure_swap_size: 4096
nslookup_target: 'opendev.org'
+ enable_fips: True
- job:
name: tempest-pg-full
@@ -379,11 +396,6 @@
parent: tempest-full-py3
description: |
This job runs the Tempest tests with scope and new defaults enabled.
- # TODO: remove this once https://review.opendev.org/c/openstack/neutron-lib/+/864213
- # fix is released in neutron-lib
- required-projects:
- - openstack/neutron-lib
- - openstack/neutron
vars:
devstack_localrc:
# Enabeling the scope and new defaults for services.
@@ -393,12 +405,25 @@
# Keystone policies are changed to work for both system as well as
# for project scoped, we need to keep scope check disable for
# keystone.
- NOVA_ENFORCE_SCOPE: true
+ # Nova, Glance, and Neutron have enabled the new defaults and scope
+ # by default in devstack.
CINDER_ENFORCE_SCOPE: true
- GLANCE_ENFORCE_SCOPE: true
- NEUTRON_ENFORCE_SCOPE: true
PLACEMENT_ENFORCE_SCOPE: true
+- job:
+ name: tempest-all-rbac-old-defaults
+ parent: tempest-all
+ description: |
+ Integration test that runs all tests on RBAC old defaults.
+ vars:
+ devstack_localrc:
+ # NOTE(gmann): Nova, Glance, and Neutron have enabled the new defaults
+ # and scope by default in devstack so we need some jobs keep testing
+ # the old defaults until they are removed from service side.
+ NOVA_ENFORCE_SCOPE: false
+ GLANCE_ENFORCE_SCOPE: false
+ NEUTRON_ENFORCE_SCOPE: false
+
- project-template:
name: integrated-gate-networking
description: |
@@ -408,27 +433,44 @@
check:
jobs:
- grenade
+ # NOTE(gmann): These template are generic and used on stable branch
+ # as well as master testing. So grenade-skip-level on stable/2023.1
+ # which test stable/yoga to stable/2023.1 upgrade is non-voting.
- grenade-skip-level:
voting: false
branches:
- stable/2023.1
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
- tempest-integrated-networking
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
gate:
jobs:
- grenade
- tempest-integrated-networking
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
- project-template:
name: integrated-gate-compute
@@ -444,6 +486,9 @@
# only from master(or the branch it was meant to run).
check:
jobs:
+ # NOTE(gmann): These template are generic and used on stable branch
+ # as well as master testing. So grenade-skip-level on stable/2023.1
+ # which test stable/yoga to stable/2023.1 upgrade is non-voting.
- grenade-skip-level:
voting: false
branches:
@@ -466,7 +511,9 @@
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
gate:
jobs:
- grenade-skip-level-always:
@@ -474,12 +521,16 @@
- master
- tempest-integrated-compute
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
periodic-weekly:
jobs:
# centos-9-stream is tested from zed release onwards
- tempest-integrated-compute-centos-9-stream:
- branches: ^(?!stable/(pike|queens|rocky|stein|train|ussuri|victoria|wallaby|xena|yoga)).*$
+ branches:
+ regex: ^stable/(stein|train|ussuri|victoria|wallaby|xena|yoga)$
+ negate: true
- project-template:
name: integrated-gate-placement
@@ -491,27 +542,44 @@
check:
jobs:
- grenade
+ # NOTE(gmann): These template are generic and used on stable branch
+ # as well as master testing. So grenade-skip-level on stable/2023.1
+ # which test stable/yoga to stable/2023.1 upgrade is non-voting.
- grenade-skip-level:
voting: false
branches:
- stable/2023.1
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
- tempest-integrated-placement
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
gate:
jobs:
- grenade
- tempest-integrated-placement
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
- project-template:
name: integrated-gate-storage
@@ -523,27 +591,44 @@
check:
jobs:
- grenade
+ # NOTE(gmann): These template are generic and used on stable branch
+ # as well as master testing. So grenade-skip-level on stable/2023.1
+ # which test stable/yoga to stable/2023.1 upgrade is non-voting.
- grenade-skip-level:
voting: false
branches:
- stable/2023.1
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
- tempest-integrated-storage
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
gate:
jobs:
- grenade
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
- tempest-integrated-storage
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
- project-template:
name: integrated-gate-object-storage
@@ -555,20 +640,34 @@
check:
jobs:
- grenade
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
- tempest-integrated-object-storage
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
gate:
jobs:
- grenade
+ # on master (SLURP 2024.1) grenade-skip-level which test stable/2023.1
+ # to stable/2024.1 upgrade is voting.
+ - grenade-skip-level:
+ branches:
+ - master
- tempest-integrated-object-storage
# Do not run it on ussuri until below issue is fixed
# https://storyboard.openstack.org/#!/story/2010057
# and job is broken up to wallaby branch due to the issue
# described in https://review.opendev.org/872341
- openstacksdk-functional-devstack:
- branches: ^(?!stable/(ussuri|victoria|wallaby)).*$
+ branches:
+ regex: ^stable/(ussuri|victoria|wallaby)$
+ negate: true
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index be8442a..3f32f9f 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -11,6 +11,7 @@
- openstack-tox-py38
- openstack-tox-py39
- openstack-tox-py310
+ - openstack-tox-py311
- tempest-full-py3:
# Define list of irrelevant files to use everywhere else
irrelevant-files: &tempest-irrelevant-files
@@ -28,8 +29,6 @@
- ^.mailmap$
- tempest-extra-tests:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-ubuntu-focal:
- irrelevant-files: *tempest-irrelevant-files
- glance-multistore-cinder-import:
voting: false
irrelevant-files: *tempest-irrelevant-files
@@ -38,9 +37,9 @@
# if things are working in latest and oldest it will work in between
# stable branches also. If anything is breaking we will be catching
# those in respective stable branch gate.
- - tempest-full-2023-1:
+ - tempest-full-2023-2:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-xena:
+ - tempest-full-yoga:
irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full-py3:
irrelevant-files: *tempest-irrelevant-files
@@ -112,6 +111,8 @@
irrelevant-files: *tempest-irrelevant-files
- grenade:
irrelevant-files: *tempest-irrelevant-files
+ - grenade-skip-level:
+ irrelevant-files: *tempest-irrelevant-files
- neutron-ovs-tempest-dvr:
voting: false
irrelevant-files: *tempest-irrelevant-files
@@ -120,6 +121,8 @@
- tempest-full-test-account-py3:
voting: false
irrelevant-files: *tempest-irrelevant-files
+ - ironic-tempest-bios-ipmi-direct-tinyipa:
+ irrelevant-files: *tempest-irrelevant-files
- openstack-tox-bashate:
irrelevant-files: *tempest-irrelevant-files-2
gate:
@@ -128,10 +131,9 @@
- openstack-tox-py38
- openstack-tox-py39
- openstack-tox-py310
+ - openstack-tox-py311
- tempest-slow-py3:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-ubuntu-focal:
- irrelevant-files: *tempest-irrelevant-files
- neutron-ovs-grenade-multinode:
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3:
@@ -140,6 +142,8 @@
irrelevant-files: *tempest-irrelevant-files
- grenade:
irrelevant-files: *tempest-irrelevant-files
+ - grenade-skip-level:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-ipv6-only:
irrelevant-files: *tempest-irrelevant-files-3
- tempest-multinode-full-py3:
@@ -150,6 +154,8 @@
# irrelevant-files: *tempest-irrelevant-files
- nova-live-migration:
irrelevant-files: *tempest-irrelevant-files
+ - ironic-tempest-bios-ipmi-direct-tinyipa:
+ irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
- nova-multi-cell
@@ -161,11 +167,10 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-all:
irrelevant-files: *tempest-irrelevant-files
- - tempest-slow-parallel
+ - tempest-all-rbac-old-defaults
- tempest-full-parallel
- tempest-full-zed-extra-tests
- tempest-full-yoga-extra-tests
- - tempest-full-xena-extra-tests
- tempest-full-enforce-scope-new-defaults-zed
- neutron-ovs-tempest-dvr-ha-multinode-full:
irrelevant-files: *tempest-irrelevant-files
@@ -185,22 +190,22 @@
irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
+ - tempest-full-2023-2
- tempest-full-2023-1
- tempest-full-zed
- tempest-full-yoga
- - tempest-full-xena
+ - tempest-slow-2023-2
- tempest-slow-2023-1
- tempest-slow-zed
- tempest-slow-yoga
- - tempest-slow-xena
+ - tempest-full-2023-2-extra-tests
- tempest-full-2023-1-extra-tests
- tempest-full-zed-extra-tests
- tempest-full-yoga-extra-tests
- - tempest-full-xena-extra-tests
periodic:
jobs:
- tempest-all
- - tempest-slow-parallel
+ - tempest-all-rbac-old-defaults
- tempest-full-parallel
- tempest-full-oslo-master
- tempest-stestr-master
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index c5fc063..2fdc2af 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -1,5 +1,11 @@
# NOTE(gmann): This file includes all stable release jobs definition.
- job:
+ name: tempest-full-2023-2
+ parent: tempest-full-py3
+ nodeset: openstack-single-node-jammy
+ override-checkout: stable/2023.2
+
+- job:
name: tempest-full-2023-1
parent: tempest-full-py3
nodeset: openstack-single-node-jammy
@@ -18,10 +24,10 @@
override-checkout: stable/yoga
- job:
- name: tempest-full-xena
- parent: tempest-full-py3
- nodeset: openstack-single-node-focal
- override-checkout: stable/xena
+ name: tempest-full-2023-2-extra-tests
+ parent: tempest-extra-tests
+ nodeset: openstack-single-node-jammy
+ override-checkout: stable/2023.2
- job:
name: tempest-full-2023-1-extra-tests
@@ -42,16 +48,16 @@
override-checkout: stable/yoga
- job:
- name: tempest-full-xena-extra-tests
- parent: tempest-extra-tests
- nodeset: openstack-single-node-focal
- override-checkout: stable/xena
+ name: tempest-slow-2023-2
+ parent: tempest-slow-py3
+ nodeset: openstack-two-node-jammy
+ override-checkout: stable/2023.2
- job:
name: tempest-slow-2023-1
parent: tempest-slow-py3
nodeset: openstack-two-node-jammy
- override-checkout: stable/2023-1
+ override-checkout: stable/2023.1
- job:
name: tempest-full-enforce-scope-new-defaults-zed
@@ -72,12 +78,6 @@
override-checkout: stable/yoga
- job:
- name: tempest-slow-xena
- parent: tempest-slow-py3
- nodeset: openstack-two-node-focal
- override-checkout: stable/xena
-
-- job:
name: tempest-full-py3
parent: devstack-tempest
# This job version is to use the 'full' tox env which
@@ -113,9 +113,6 @@
# This job version is with swift disabled on py3
# as swift was not ready on py3 until stable/train.
branches:
- - stable/pike
- - stable/queens
- - stable/rocky
- stable/stein
- stable/train
description: |
@@ -245,73 +242,11 @@
USE_PYTHON3: False
- job:
- name: tempest-multinode-full
- parent: tempest-multinode-full-base
- nodeset: openstack-two-node-xenial
- # This job runs on Xenial and this is for stable/pike, stable/queens
- # and stable/rocky. This job is prepared to make sure all stable branches
- # before stable/stein will keep running on xenial. This job can be
- # removed once stable/rocky is EOL.
- branches:
- - stable/pike
- - stable/queens
- - stable/rocky
- vars:
- devstack_localrc:
- USE_PYTHON3: False
- group-vars:
- subnode:
- devstack_localrc:
- USE_PYTHON3: False
-
-- job:
- name: tempest-slow
- parent: tempest-multinode-full
- description: |
- This multinode integration job will run all the tests tagged as slow.
- It enables the lvm multibackend setup to cover few scenario tests.
- This job will run only slow tests (API or Scenario) serially.
- Former names for this job were:
- * legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend
- * tempest-scenario-multinode-lvm-multibackend
- timeout: 10800
- branches:
- - stable/pike
- - stable/queens
- - stable/rocky
- vars:
- tox_envlist: slow-serial
- devstack_localrc:
- CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
- ENABLE_VOLUME_MULTIATTACH: true
- # to avoid https://bugs.launchpad.net/neutron/+bug/1914037
- # as we couldn't backport the fix to rocky and older releases
- IPV6_PUBLIC_RANGE: 2001:db8:0:10::/64
- IPV6_PUBLIC_NETWORK_GATEWAY: 2001:db8:0:10::2
- IPV6_ROUTER_GW_IP: 2001:db8:0:10::1
- devstack_plugins:
- neutron: https://opendev.org/openstack/neutron
- devstack_services:
- neutron-placement: true
- neutron-qos: true
- tempest_concurrency: 2
- group-vars:
- # NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
- # the controller and subnode prior to Rocky so we have to make sure the
- # variable is set in both locations.
- subnode:
- devstack_localrc:
- ENABLE_VOLUME_MULTIATTACH: true
-
-- job:
name: tempest-slow-py3
parent: tempest-slow
# This job version is with swift disabled on py3
# as swift was not ready on py3 until stable/train.
branches:
- - stable/pike
- - stable/queens
- - stable/rocky
- stable/stein
- stable/train
vars:
@@ -330,6 +265,18 @@
USE_PYTHON3: true
- job:
+ name: tempest-slow-py3
+ parent: tempest-slow
+ # This job version is to use the 'slow-serial' tox env for
+ # the stable/ussuri to stable/wallaby testing.
+ branches:
+ - stable/ussuri
+ - stable/victoria
+ - stable/wallaby
+ vars:
+ tox_envlist: slow-serial
+
+- job:
name: tempest-full-py3-opensuse15
parent: tempest-full-py3
nodeset: devstack-single-node-opensuse-15
@@ -340,9 +287,6 @@
# This job is not used after stable/xena and can be
# removed once stable/xena is EOL.
branches:
- - stable/pike
- - stable/queens
- - stable/rocky
- stable/stein
- stable/train
- stable/ussuri
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index a8c29af..10490b4 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -54,7 +54,6 @@
- job:
name: tempest-full-py3-ipv6
parent: devstack-tempest-ipv6
- branches: ^(?!stable/ocata).*$
description: |
Base integration test with Neutron networking, IPv6 and py3.
vars:
@@ -93,7 +92,12 @@
vars:
devstack_localrc:
TEMPEST_USE_TEST_ACCOUNTS: True
-
+ # FIXME(gmann): Nova and Glance have enabled the new defaults and scope
+ # by default in devstack and pre provisioned account code and testing
+ # needs to be move to new RBAC design testing. Until we do that, let's
+ # run these jobs with old defaults.
+ NOVA_ENFORCE_SCOPE: false
+ GLANCE_ENFORCE_SCOPE: false
- job:
name: tempest-full-test-account-no-admin-py3
parent: tempest-full-test-account-py3