Merge "Fix inconsistent type of default value"
diff --git a/HACKING.rst b/HACKING.rst
index 95bcbb5..dc28e4e 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -26,6 +26,7 @@
- [T116] Unsupported 'message' Exception attribute in PY3
- [T117] Check negative tests have ``@decorators.attr(type=['negative'])``
applied.
+- [T118] LOG.warn is deprecated. Enforce use of LOG.warning.
It is recommended to use ``tox -eautopep8`` before submitting a patch.
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 7da8ddb..20ace9e 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -350,6 +350,10 @@
.. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
+ * `2.33`_
+
+ .. _2.33: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id30
+
* `2.36`_
.. _2.36: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion
@@ -370,6 +374,10 @@
.. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
+ * `2.45`_
+
+ .. _2.45: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id41
+
* `2.47`_
.. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
@@ -382,6 +390,10 @@
.. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id45
+ * `2.50`_
+
+ .. _2.50: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id46
+
* `2.53`_
.. _2.53: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-pike
@@ -414,6 +426,10 @@
.. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id58
+ * `2.64`_
+
+ .. _2.64: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id59
+
* `2.70`_
.. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64
@@ -426,6 +442,10 @@
.. _2.73: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id66
+ * `2.75`_
+
+ .. _2.75: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id68
+
* `2.79`_
.. _2.79: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-train
diff --git a/playbooks/enable-fips.yaml b/playbooks/enable-fips.yaml
new file mode 100644
index 0000000..c8f042d
--- /dev/null
+++ b/playbooks/enable-fips.yaml
@@ -0,0 +1,4 @@
+- hosts: all
+ tasks:
+ - include_role:
+ name: enable-fips
diff --git a/releasenotes/notes/add-ssh-key-type-38d7a2f900d79842.yaml b/releasenotes/notes/add-ssh-key-type-38d7a2f900d79842.yaml
new file mode 100644
index 0000000..fef3004
--- /dev/null
+++ b/releasenotes/notes/add-ssh-key-type-38d7a2f900d79842.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add parameter to specify the SSH key type. Current options are 'rsa'
+ (which is the default) and 'ecdsa'. Tempest now supports the importing
+ and generation of both 'rsa' and 'ecdsa' SSH key types.
diff --git a/releasenotes/notes/set-default-value-of-concurrency-to-2-d916d5c31e3725d5.yaml b/releasenotes/notes/set-default-value-of-concurrency-to-2-d916d5c31e3725d5.yaml
new file mode 100644
index 0000000..0d964a9
--- /dev/null
+++ b/releasenotes/notes/set-default-value-of-concurrency-to-2-d916d5c31e3725d5.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ [`bug 1948935 <https://bugs.launchpad.net/tempest/+bug/1948935>`_]
+ The default value of account-generator --concurrency parameter is now
+ set to 2 instead of 1.
diff --git a/releasenotes/notes/tempest-yoga-release-66e8484b9a402e9f.yaml b/releasenotes/notes/tempest-yoga-release-66e8484b9a402e9f.yaml
new file mode 100644
index 0000000..e41e31d
--- /dev/null
+++ b/releasenotes/notes/tempest-yoga-release-66e8484b9a402e9f.yaml
@@ -0,0 +1,18 @@
+---
+prelude: |
+ This release is to tag Tempest for OpenStack Yoga release.
+ This release marks the start of Yoga release support in Tempest.
+ After this release, Tempest will support below OpenStack Releases:
+
+ * Yoga
+ * Xena
+ * Wallaby
+ * Victoria
+ * Ussuri
+
+ Current development of Tempest is for OpenStack Zed development
+ cycle. Every Tempest commit is also tested against master during
+ the Zed cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Zed (or future release)
+ cloud.
+ To be on safe side, use this tag to test the OpenStack Yoga release.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 6a1f8b4..122f7c7 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,9 @@
:maxdepth: 1
unreleased
+ v30.0.0
+ v29.2.0
+ v29.1.0
v29.0.0
v28.1.0
v28.0.0
diff --git a/releasenotes/source/v29.1.0.rst b/releasenotes/source/v29.1.0.rst
new file mode 100644
index 0000000..f8780fd
--- /dev/null
+++ b/releasenotes/source/v29.1.0.rst
@@ -0,0 +1,5 @@
+=====================
+v29.1.0 Release Notes
+=====================
+.. release-notes:: 29.1.0 Release Notes
+ :version: 29.1.0
diff --git a/releasenotes/source/v29.2.0.rst b/releasenotes/source/v29.2.0.rst
new file mode 100644
index 0000000..4f2f2b2
--- /dev/null
+++ b/releasenotes/source/v29.2.0.rst
@@ -0,0 +1,5 @@
+=====================
+v29.2.0 Release Notes
+=====================
+.. release-notes:: 29.2.0 Release Notes
+ :version: 29.2.0
diff --git a/releasenotes/source/v30.0.0.rst b/releasenotes/source/v30.0.0.rst
new file mode 100644
index 0000000..048b8ab
--- /dev/null
+++ b/releasenotes/source/v30.0.0.rst
@@ -0,0 +1,5 @@
+=====================
+v30.0.0 Release Notes
+=====================
+.. release-notes:: 30.0.0 Release Notes
+ :version: 30.0.0
diff --git a/requirements.txt b/requirements.txt
index c71cabe..c4c7fcc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,7 @@
jsonschema>=3.2.0 # MIT
testtools>=2.2.0 # MIT
paramiko>=2.7.0 # LGPLv2.1+
+cryptography>=2.1 # BSD/Apache-2.0
netaddr>=0.7.18 # BSD
oslo.concurrency>=3.26.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
@@ -20,4 +21,3 @@
PrettyTable>=0.7.1 # BSD
urllib3>=1.21.1 # MIT
debtcollector>=1.2.0 # Apache-2.0
-unittest2>=1.1.0 # BSD
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 0c72b69..1919393 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -81,7 +81,7 @@
.. zuul:rolevar:: stable_constraints_file
:default: ''
- Upper constraints file to be used for stable branch till stable/stein.
+ Upper constraints file to be used for stable branch till stable/train.
.. zuul:rolevar:: tempest_tox_environment
:default: ''
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index a8b3ede..397de1e 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -25,11 +25,11 @@
target_branch: "{{ zuul.override_checkout }}"
when: zuul.override_checkout is defined
-- name: Use stable branch upper-constraints till stable/stein
+- name: Use stable branch upper-constraints till stable/train
set_fact:
# TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
- when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
+ when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein", "stable/train"]
- name: Use Configured upper-constraints for non-master Tempest
set_fact:
@@ -78,6 +78,17 @@
exclude_list_option: "--exclude-list={{ tempest_test_exclude_list|quote }}"
when: exclude_list_stat.stat.exists
+- name: stable/train workaround to fallback exclude-list to blacklist
+ # NOTE(gmann): stable/train use Tempest 26.1.0 and with stestr 2.5.1
+ # (beacause of upper constraints of stestr 2.5.1 in stable/train) which
+ # does not have new args exclude-list so let's fallback to old arg
+ # if new arg is passed.
+ set_fact:
+ exclude_list_option: "--blacklist-file={{ tempest_test_exclude_list|quote }}"
+ when:
+ - tempest_test_exclude_list is defined
+ - target_branch == "stable/train"
+
# TODO(kopecmartin) remove this after all consumers of the role have switched
# to tempest_exclude_regex option, until then it's kept here for the backward
# compatibility
@@ -94,6 +105,19 @@
when:
- tempest_black_regex is not defined
- tempest_exclude_regex is defined
+ - target_branch != "stable/train"
+
+- name: stable/train workaround to fallback exclude-regex to black-regex
+ # NOTE(gmann): stable/train use Tempest 26.1.0 and with stestr 2.5.1
+ # (beacause of upper constraints of stestr 2.5.1 in stable/train) which
+ # does not have new args exclude-regex so let's fallback to old arg
+ # if new arg is passed.
+ set_fact:
+ tempest_test_exclude_regex: "--black-regex={{tempest_exclude_regex|quote}}"
+ when:
+ - tempest_black_regex is not defined
+ - tempest_exclude_regex is defined
+ - target_branch == "stable/train"
- name: Run Tempest
command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} \
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 4cc5fdd..f54fb22 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -119,3 +119,5 @@
self.assertIn(agent_id_xen, map(lambda x: x['agent_id'], agents))
self.assertNotIn(body['agent_id'], map(lambda x: x['agent_id'],
agents))
+ for agent in agents:
+ self.assertEqual(agent_xen['hypervisor'], agent['hypervisor'])
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 2716259..a6c6535 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -17,6 +17,7 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
+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
@@ -237,6 +238,10 @@
wait_until='ACTIVE')
server_host = self.get_host_for_server(server['id'])
self.assertEqual(host, server_host)
+ self.servers_client.delete_server(server['id'])
+ # NOTE(gmann): We need to wait for the server to delete before
+ # addCleanup remove the host from aggregate.
+ waiters.wait_for_server_termination(self.servers_client, server['id'])
class AggregatesAdminTestV241(AggregatesAdminTestBase):
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 347193d..c7a1201 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -145,3 +145,26 @@
hypers = self.client.search_hypervisor(
hypers[0]['hypervisor_hostname'])['hypervisors']
self.assertNotEmpty(hypers, "No hypervisors found.")
+
+
+class HypervisorAdminV253TestBase(base.BaseV2ComputeAdminTest):
+ """Tests Hypervisors API above 2.53 that require admin privileges"""
+
+ min_microversion = '2.53'
+
+ @classmethod
+ def setup_clients(cls):
+ super(HypervisorAdminV253TestBase, cls).setup_clients()
+ cls.client = cls.os_admin.hypervisor_client
+
+ @decorators.idempotent_id('4ab54a14-77a2-4e39-b9d2-1306d157c705')
+ def test_list_show_detail_hypervisors(self):
+ """Verify the list, list details, and show hypevisors
+
+ This verify the Hypervisor API response schema with v2.53 microversion
+ """
+ self.client.list_hypervisors(
+ detail=True, with_servers=True)['hypervisors']
+ hypers = self.client.list_hypervisors(with_servers=True)['hypervisors']
+ self.client.show_hypervisor(
+ hypers[0]['id'], with_servers=True)['hypervisor']
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 9d5e0c9..caf4fc1 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -14,14 +14,17 @@
# under the License.
from oslo_log import log as logging
+import testtools
from testtools import matchers
from tempest.api.compute import base
from tempest.common import identity
from tempest.common import tempest_fixtures as fixtures
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -110,6 +113,8 @@
self.assertIn(quota, quota_set.keys())
@decorators.idempotent_id('55fbe2bf-21a9-435b-bbd2-4162b0ed799a')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_update_all_quota_resources_for_tenant(self):
"""Test admin can update all the compute quota limits for a project"""
default_quota_set = self.adm_client.show_default_quota_set(
@@ -141,11 +146,15 @@
# TODO(afazekas): merge these test cases
@decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_get_updated_quotas(self):
"""Test that GET shows the updated quota set of project"""
self._get_updated_quotas()
@decorators.idempotent_id('389d04f0-3a41-405f-9317-e5f86e3c44f0')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_delete_quota(self):
"""Test admin can delete the compute quota set for a project"""
project_name = data_utils.rand_name('ram_quota_project')
@@ -178,6 +187,8 @@
min_microversion = '2.36'
@decorators.idempotent_id('4268b5c9-92e5-4adc-acf1-3a2798f3d803')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_get_updated_quotas(self):
"""Test compute quotas API with microversion greater than 2.35
@@ -197,6 +208,8 @@
min_microversion = '2.57'
@decorators.idempotent_id('e641e6c6-e86c-41a4-9e5c-9493c0ae47ad')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_get_updated_quotas(self):
"""Test compute quotas API with microversion greater than 2.56
@@ -228,6 +241,8 @@
# tests that get run all by themselves at the end under a
# 'danger' flag.
@decorators.idempotent_id('7932ab0f-5136-4075-b201-c0e2338df51a')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_update_default_quotas(self):
"""Test updating default compute quota class set"""
# get the current 'default' quota class values
@@ -253,3 +268,14 @@
'default')['quota_class_set']
self.assertThat(show_body.items(),
matchers.ContainsAll(body.items()))
+
+
+class QuotaClassesAdmin257Test(QuotaClassesAdminTestJSON):
+ """Test compute quotas with microversion greater than 2.56
+
+ # NOTE(gmann): This test tests the Quota class APIs response schema
+ # for 2.57 microversion. No specific assert or behaviour verification
+ # is needed.
+ """
+
+ min_microversion = '2.57'
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index 04dbc2d..a4120bb 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute import base
from tempest.common import utils
from tempest import config
@@ -68,6 +70,8 @@
# It can be moved into the setUpClass as well.
@decorators.attr(type=['negative'])
@decorators.idempotent_id('91058876-9947-4807-9f22-f6eb17140d9b')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_create_server_when_cpu_quota_is_full(self):
"""Disallow server creation when tenant's vcpu quota is full"""
self._update_quota('cores', 0)
@@ -76,6 +80,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6fdd7012-584d-4327-a61c-49122e0d5864')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_create_server_when_memory_quota_is_full(self):
"""Disallow server creation when tenant's memory quota is full"""
self._update_quota('ram', 0)
@@ -84,6 +90,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7c6be468-0274-449a-81c3-ac1c32ee0161')
+ @testtools.skipIf(CONF.compute_feature_enabled.unified_limits,
+ 'Legacy quota update not available with unified limits')
def test_create_server_when_instances_quota_is_full(self):
"""Once instances quota limit is reached, disallow server creation"""
self._update_quota('instances', 0)
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index ab1b49a..bc00f8c 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -223,3 +223,32 @@
}
self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')
+
+
+class ServersAdmin275Test(base.BaseV2ComputeAdminTest):
+ """Test compute server with microversion greater than 2.75
+
+ # NOTE(gmann): This test tests the Server APIs response schema
+ # for 2.75 microversion. No specific assert or behaviour verification
+ # is needed.
+ """
+
+ min_microversion = '2.75'
+
+ @decorators.idempotent_id('bf2b4a00-73a3-4d53-81fa-acbcd97d6339')
+ def test_rebuild_update_server_275(self):
+ server = self.create_test_server()
+ # Checking update response schema.
+ self.servers_client.update_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
+ 'ACTIVE')
+ # Checking rebuild API response schema
+ self.servers_client.rebuild_server(server['id'], self.image_ref_alt)
+ waiters.wait_for_server_status(self.servers_client,
+ server['id'], 'ACTIVE')
+ # Checking rebuild server with admin response schema.
+ self.os_admin.servers_client.rebuild_server(
+ server['id'], self.image_ref)
+ self.addCleanup(waiters.wait_for_server_status,
+ self.os_admin.servers_client,
+ server['id'], 'ACTIVE')
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
index 549d4fb..99d8e2a 100644
--- a/tempest/api/compute/admin/test_volume.py
+++ b/tempest/api/compute/admin/test_volume.py
@@ -85,10 +85,14 @@
hw_scsi_model='virtio-scsi',
hw_disk_bus='scsi',
hw_cdrom_bus='scsi')
- server = self.create_test_server(image_id=custom_img,
- config_drive=True,
- wait_until='ACTIVE')
-
+ validation_resources = self.get_test_validation_resources(
+ self.os_primary)
+ server = self.create_test_server(
+ image_id=custom_img,
+ config_drive=True,
+ validatable=True,
+ validation_resources=validation_resources,
+ wait_until="SSHABLE")
# NOTE(lyarwood): self.create_test_server delete the server
# at class level cleanup so add server cleanup to ensure that
# the instance is deleted first before created image. This
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index ed50282..e16afaf 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -306,10 +306,18 @@
def create_test_server_group(cls, name="", policy=None):
if not name:
name = data_utils.rand_name(cls.__name__ + "-Server-Group")
- if policy is None:
- policy = ['affinity']
+ if cls.is_requested_microversion_compatible('2.63'):
+ policy = policy or ['affinity']
+ if not isinstance(policy, list):
+ policy = [policy]
+ kwargs = {'policies': policy}
+ else:
+ policy = policy or 'affinity'
+ if isinstance(policy, list):
+ policy = policy[0]
+ kwargs = {'policy': policy}
body = cls.server_groups_client.create_server_group(
- name=name, policies=policy)['server_group']
+ name=name, **kwargs)['server_group']
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.server_groups_client.delete_server_group,
@@ -487,21 +495,8 @@
:param validation_resources: The dict of validation resources
provisioned for the server.
"""
- if CONF.validation.connect_method == 'floating':
- if validation_resources:
- return validation_resources['floating_ip']['ip']
- else:
- msg = ('When validation.connect_method equals floating, '
- 'validation_resources cannot be None')
- raise lib_exc.InvalidParam(invalid_param=msg)
- elif CONF.validation.connect_method == 'fixed':
- addresses = server['addresses'][CONF.validation.network_for_ssh]
- for address in addresses:
- if address['version'] == CONF.validation.ip_version_for_ssh:
- return address['addr']
- raise exceptions.ServerUnreachable(server_id=server['id'])
- else:
- raise lib_exc.InvalidConfiguration()
+ return compute.get_server_ip(
+ server, validation_resources=validation_resources)
@classmethod
def create_volume(cls, image_ref=None, **kwargs):
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index ac18442..efecd6c 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -68,7 +68,8 @@
self.image_ssh_password,
validation_resources['keypair']['private_key'],
server=server,
- servers_client=self.servers_client)
+ servers_client=self.servers_client,
+ ssh_key_type=CONF.validation.ssh_key_type)
linux_client.validate_authentication()
def _create_server_get_interfaces(self):
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 56456f4..d099fce 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -212,7 +212,7 @@
server = self.create_test_server(
validatable=True,
- wait_until='ACTIVE',
+ wait_until='SSHABLE',
validation_resources=validation_resources,
config_drive=config_drive_enabled,
name=data_utils.rand_name('device-tagging-server'),
@@ -335,7 +335,9 @@
def verify_device_metadata(self, md_json):
try:
md_dict = json.loads(md_json)
- except (json_decoder.JSONDecodeError, TypeError):
+ except (json_decoder.JSONDecodeError, TypeError) as e:
+ LOG.warning(
+ 'Failed to decode json metadata: %s, %s', str(e), str(md_json))
return False
found_devices = [d['tags'][0] for d in md_dict['devices']
@@ -345,7 +347,9 @@
sorted(found_devices),
sorted(['nic-tag', 'volume-tag']))
return True
- except Exception:
+ except Exception as e:
+ LOG.warning(
+ 'Failed to parse metadata: %s, %s', str(e), str(md_json))
return False
def verify_empty_devices(self, md_json):
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 152e7e8..870c6f5 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -43,6 +43,17 @@
super(ServerActionsTestJSON, self).setUp()
# Check if the server is in a clean state after test
try:
+ validation_resources = self.get_class_validation_resources(
+ self.os_primary)
+ # _test_rebuild_server test compares ip address attached to the
+ # server before and after the rebuild, in order to avoid
+ # a situation when a newly created server doesn't have a floating
+ # ip attached at the beginning of the test_rebuild_server let's
+ # make sure right here the floating ip is attached
+ waiters.wait_for_server_floating_ip(
+ self.client,
+ self.client.show_server(self.server_id)['server'],
+ validation_resources['floating_ip'])
waiters.wait_for_server_status(self.client,
self.server_id, 'ACTIVE')
except lib_exc.NotFound:
@@ -781,3 +792,28 @@
self.assertEqual('novnc', body['type'])
self.assertNotEqual('', body['url'])
self._validate_url(body['url'])
+
+
+class ServersAaction247Test(base.BaseV2ComputeTest):
+ """Test compute server with microversion greater than 2.47
+
+ # NOTE(gmann): This test tests the Server create backup APIs
+ # response schema for 2.47 microversion. No specific assert
+ # or behaviour verification is needed.
+ """
+
+ min_microversion = '2.47'
+
+ @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')
+
+ backup1 = data_utils.rand_name('backup-1')
+ # Just check create_back to verify the schema with 2.47
+ self.servers_client.create_backup(server['id'],
+ backup_type='daily',
+ rotation=2,
+ name=backup1)
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 4c0d021..4811a7b 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -44,9 +44,21 @@
cls.client = cls.server_groups_client
@classmethod
+ def _set_policy(cls, policy):
+ if not cls.is_requested_microversion_compatible('2.63'):
+ return policy[0]
+ else:
+ return policy
+
+ @classmethod
def resource_setup(cls):
super(ServerGroupTestJSON, cls).resource_setup()
- cls.policy = ['affinity']
+ if cls.is_requested_microversion_compatible('2.63'):
+ cls.policy_field = 'policies'
+ cls.policy = ['affinity']
+ else:
+ cls.policy_field = 'policy'
+ cls.policy = 'affinity'
def setUp(self):
super(ServerGroupTestJSON, self).setUp()
@@ -61,9 +73,9 @@
def _create_server_group(self, name, policy):
# create the test server-group with given policy
- server_group = {'name': name, 'policies': policy}
+ server_group = {'name': name, self.policy_field: policy}
body = self.create_test_server_group(name, policy)
- for key in ['name', 'policies']:
+ for key in ['name', self.policy_field]:
self.assertEqual(server_group[key], body[key])
return body
@@ -88,7 +100,7 @@
@decorators.idempotent_id('3645a102-372f-4140-afad-13698d850d23')
def test_create_delete_server_group_with_anti_affinity_policy(self):
"""Test Create/Delete the server-group with anti-affinity policy"""
- policy = ['anti-affinity']
+ policy = self._set_policy(['anti-affinity'])
self._create_delete_server_group(policy)
@decorators.idempotent_id('154dc5a4-a2fe-44b5-b99e-f15806a4a113')
@@ -99,7 +111,7 @@
for _ in range(0, 2):
server_groups.append(self._create_server_group(server_group_name,
self.policy))
- for key in ['name', 'policies']:
+ for key in ['name', self.policy_field]:
self.assertEqual(server_groups[0][key], server_groups[1][key])
self.assertNotEqual(server_groups[0]['id'], server_groups[1]['id'])
@@ -134,3 +146,24 @@
server_group = (self.server_groups_client.show_server_group(
self.created_server_group['id'])['server_group'])
self.assertIn(server['id'], server_group['members'])
+
+
+class ServerGroup264TestJSON(base.BaseV2ComputeTest):
+ """These tests check for the server-group APIs 2.64 microversion.
+
+ This tests is only to verify the POST, GET server-groups APIs response
+ schema with 2.64 microversion
+ """
+ create_default_network = True
+ min_microversion = '2.64'
+
+ @decorators.idempotent_id('b52f09dd-2133-4037-9a5d-bdb260096a88')
+ def test_create_get_server_group(self):
+ # create, get the test server-group with given policy
+ server_group = self.create_test_server_group(
+ name='server-group', policy='affinity')
+ self.addCleanup(
+ self.server_groups_client.delete_server_group,
+ server_group['id'])
+ self.server_groups_client.list_server_groups()
+ self.server_groups_client.show_server_group(server_group['id'])
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 354e3b9..716ecda 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common import utils
from tempest.common import waiters
from tempest import config
@@ -112,7 +113,6 @@
class BaseServerStableDeviceRescueTest(base.BaseV2ComputeTest):
- create_default_network = True
@classmethod
def skip_checks(cls):
@@ -124,19 +124,31 @@
msg = "Stable rescue not available."
raise cls.skipException(msg)
+ @classmethod
+ def setup_credentials(cls):
+ cls.set_network_resources(network=True, subnet=True, router=True,
+ dhcp=True)
+ super(BaseServerStableDeviceRescueTest, cls).setup_credentials()
+
def _create_server_and_rescue_image(self, hw_rescue_device=None,
hw_rescue_bus=None,
- block_device_mapping_v2=None):
-
- server_id = self.create_test_server(
- wait_until='ACTIVE')['id']
+ block_device_mapping_v2=None,
+ validatable=False,
+ validation_resources=None,
+ wait_until='ACTIVE'):
+ server = self.create_test_server(
+ wait_until=wait_until,
+ validatable=validatable,
+ validation_resources=validation_resources)
image_id = self.create_image_from_server(
- server_id, wait_until='ACTIVE')['id']
+ server['id'], wait_until='ACTIVE')['id']
if block_device_mapping_v2:
- server_id = self.create_test_server(
- wait_until='ACTIVE',
- block_device_mapping_v2=block_device_mapping_v2)['id']
+ server = self.create_test_server(
+ wait_until=wait_until,
+ validatable=validatable,
+ validation_resources=validation_resources,
+ block_device_mapping_v2=block_device_mapping_v2)
if hw_rescue_bus:
self.images_client.update_image(
@@ -146,16 +158,28 @@
self.images_client.update_image(
image_id, [dict(add='/hw_rescue_device',
value=hw_rescue_device)])
- return server_id, image_id
+ return server, image_id
- def _test_stable_device_rescue(self, server_id, rescue_image_id):
+ def _test_stable_device_rescue(
+ self, server, rescue_image_id,
+ validation_resources=None):
self.servers_client.rescue_server(
- server_id, rescue_image_ref=rescue_image_id)
+ server['id'], rescue_image_ref=rescue_image_id)
waiters.wait_for_server_status(
- self.servers_client, server_id, 'RESCUE')
- self.servers_client.unrescue_server(server_id)
- waiters.wait_for_server_status(
- self.servers_client, server_id, 'ACTIVE')
+ self.servers_client, server['id'], 'RESCUE')
+ self.servers_client.unrescue_server(server['id'])
+ # NOTE(gmann) In next addCleanup, server unrescue is called before the
+ # detach volume is called in cleanup (added by self.attach_volume()
+ # method) so to make sure server is ready before detach operation, we
+ # need to perform ssh on it, more details are in bug#1960346.
+ if validation_resources and CONF.validation.run_validation:
+ tenant_network = self.get_tenant_network()
+ compute.wait_for_ssh_or_ping(
+ server, self.os_primary, tenant_network,
+ True, validation_resources, "SSHABLE", True)
+ else:
+ waiters.wait_for_server_status(
+ self.servers_client, server['id'], 'ACTIVE')
class ServerStableDeviceRescueTestIDE(BaseServerStableDeviceRescueTest):
@@ -172,9 +196,9 @@
"Aarch64 does not support ide bus for cdrom")
def test_stable_device_rescue_cdrom_ide(self):
"""Test rescuing server with cdrom and ide as the rescue disk"""
- server_id, rescue_image_id = self._create_server_and_rescue_image(
+ server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='cdrom', hw_rescue_bus='ide')
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(server, rescue_image_id)
class ServerStableDeviceRescueTest(BaseServerStableDeviceRescueTest):
@@ -183,23 +207,23 @@
@decorators.idempotent_id('16865750-1417-4854-bcf7-496e6753c01e')
def test_stable_device_rescue_disk_virtio(self):
"""Test rescuing server with disk and virtio as the rescue disk"""
- server_id, rescue_image_id = self._create_server_and_rescue_image(
+ server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio')
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(server, rescue_image_id)
@decorators.idempotent_id('12340157-6306-4745-bdda-cfa019908b48')
def test_stable_device_rescue_disk_scsi(self):
"""Test rescuing server with disk and scsi as the rescue disk"""
- server_id, rescue_image_id = self._create_server_and_rescue_image(
+ server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='scsi')
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(server, rescue_image_id)
@decorators.idempotent_id('647d04cf-ad35-4956-89ab-b05c5c16f30c')
def test_stable_device_rescue_disk_usb(self):
"""Test rescuing server with disk and usb as the rescue disk"""
- server_id, rescue_image_id = self._create_server_and_rescue_image(
+ server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='usb')
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(server, rescue_image_id)
@decorators.idempotent_id('a3772b42-00bf-4310-a90b-1cc6fd3e7eab')
@utils.services('volume')
@@ -209,14 +233,25 @@
Attach a volume to the server and then rescue the server with disk
and virtio as the rescue disk.
"""
- server_id, rescue_image_id = self._create_server_and_rescue_image(
- hw_rescue_device='disk', hw_rescue_bus='virtio')
- server = self.servers_client.show_server(server_id)['server']
+ # This test just check detach fail and does not
+ # perfom the detach operation but in cleanup from
+ # self.attach_volume() it will try to detach the server
+ # after unrescue the server. Due to that we need to make
+ # server SSHable before it try to detach, more details are
+ # in bug#1960346
+ 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()
self.attach_volume(server, volume)
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'in-use')
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(
+ server, rescue_image_id,
+ validation_resources=validation_resources)
class ServerBootFromVolumeStableRescueTest(BaseServerStableDeviceRescueTest):
@@ -248,10 +283,10 @@
"source_type": "blank",
"volume_size": CONF.volume.volume_size,
"destination_type": "volume"}]
- server_id, rescue_image_id = self._create_server_and_rescue_image(
+ server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio',
block_device_mapping_v2=block_device_mapping_v2)
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(server, rescue_image_id)
@decorators.attr(type='slow')
@decorators.idempotent_id('e4636333-c928-40fc-98b7-70a23eef4224')
@@ -267,7 +302,7 @@
"volume_size": CONF.volume.volume_size,
"uuid": CONF.compute.image_ref,
"destination_type": "volume"}]
- server_id, rescue_image_id = self._create_server_and_rescue_image(
+ server, rescue_image_id = self._create_server_and_rescue_image(
hw_rescue_device='disk', hw_rescue_bus='virtio',
block_device_mapping_v2=block_device_mapping_v2)
- self._test_stable_device_rescue(server_id, rescue_image_id)
+ self._test_stable_device_rescue(server, rescue_image_id)
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 9bcf062..955ba1c 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common import utils
from tempest.common import waiters
from tempest import config
@@ -38,7 +39,8 @@
@classmethod
def setup_credentials(cls):
- cls.set_network_resources(network=True, subnet=True, router=True)
+ cls.set_network_resources(network=True, subnet=True, router=True,
+ dhcp=True)
super(ServerRescueNegativeTestJSON, cls).setup_credentials()
@classmethod
@@ -136,21 +138,41 @@
def test_rescued_vm_detach_volume(self):
"""Test detaching volume from a rescued server should fail"""
volume = self.create_volume()
-
+ # This test just check detach fail and does not
+ # perfom the detach operation but in cleanup from
+ # self.attach_volume() it will try to detach the server
+ # after unrescue the server. Due to that we need to make
+ # server SSHable before it try to detach, more details are
+ # in bug#1960346
+ validation_resources = self.get_class_validation_resources(
+ self.os_primary)
+ server = self.create_test_server(
+ adminPass=self.password,
+ wait_until="SSHABLE",
+ validatable=True,
+ validation_resources=validation_resources)
# Attach the volume to the server
- server = self.servers_client.show_server(self.server_id)['server']
self.attach_volume(server, volume)
# Rescue the server
- self.servers_client.rescue_server(self.server_id,
+ self.servers_client.rescue_server(server['id'],
adminPass=self.password)
waiters.wait_for_server_status(self.servers_client,
- self.server_id, 'RESCUE')
+ server['id'], 'RESCUE')
+ # NOTE(gmann) In next addCleanup, server unrescue is called before the
+ # detach volume is called in cleanup (added by self.attach_volume()
+ # method) so to make sure server is ready before detach operation, we
+ # need to perform ssh on it, more details are in bug#1960346.
+ if CONF.validation.run_validation:
+ tenant_network = self.get_tenant_network()
+ self.addCleanup(compute.wait_for_ssh_or_ping,
+ server, self.os_primary, tenant_network,
+ True, validation_resources, "SSHABLE", True)
# addCleanup is a LIFO queue
- self.addCleanup(self._unrescue, self.server_id)
+ self.addCleanup(self._unrescue, server['id'])
# Detach the volume from the server expecting failure
self.assertRaises(lib_exc.Conflict,
self.servers_client.detach_volume,
- self.server_id,
+ server['id'],
volume['id'])
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 4c7c234..e4ec209 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -49,7 +49,7 @@
server = self.create_test_server(
validatable=True,
validation_resources=validation_resources,
- wait_until='ACTIVE',
+ wait_until='SSHABLE',
adminPass=self.image_ssh_password)
self.addCleanup(self.delete_server, server['id'])
# Record addresses so that we can ssh later
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index 516f599..43b4bf5 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.compute import base
+from tempest.api.compute.volumes import test_attach_volume
from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -20,24 +20,15 @@
CONF = config.CONF
-class AttachVolumeNegativeTest(base.BaseV2ComputeTest):
+class AttachVolumeNegativeTest(test_attach_volume.BaseAttachVolumeTest):
"""Negative tests of volume attaching"""
- create_default_network = True
-
- @classmethod
- def skip_checks(cls):
- super(AttachVolumeNegativeTest, 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)
-
@decorators.attr(type=['negative'])
@decorators.related_bug('1630783', status_code=500)
@decorators.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
def test_delete_attached_volume(self):
"""Test deleting attachemd volume should fail"""
- server = self.create_test_server(wait_until='ACTIVE')
+ server, validation_resources = self._create_server()
volume = self.create_volume()
self.attach_volume(server, volume)
@@ -54,7 +45,7 @@
depending on whether or not cinder v3.27 is being used to attach
the volume to the instance.
"""
- server = self.create_test_server(wait_until='ACTIVE')
+ server, validation_resources = self._create_server()
volume = self.create_volume()
self.attach_volume(server, volume)
@@ -66,12 +57,12 @@
@decorators.idempotent_id('ee37a796-2afb-11e7-bc0f-fa163e65f5ce')
def test_attach_attached_volume_to_different_server(self):
"""Test attaching attached volume to different server should fail"""
- server1 = self.create_test_server(wait_until='ACTIVE')
+ server1, validation_resources = self._create_server()
volume = self.create_volume()
self.attach_volume(server1, volume)
# Create server2 and attach in-use volume
- server2 = self.create_test_server(wait_until='ACTIVE')
+ server2, validation_resources = self._create_server()
self.assertRaises(lib_exc.BadRequest,
self.attach_volume, server2, volume)
diff --git a/tempest/api/volume/admin/test_group_type_specs.py b/tempest/api/volume/admin/test_group_type_specs.py
index 63c3546..181926e 100644
--- a/tempest/api/volume/admin/test_group_type_specs.py
+++ b/tempest/api/volume/admin/test_group_type_specs.py
@@ -73,10 +73,11 @@
self.assertEqual(list_specs, body)
# Delete specified item of group type specs
- delete_key = 'key1'
- self.admin_group_types_client.delete_group_type_specs_item(
- group_type['id'], delete_key)
- self.assertRaises(
- lib_exc.NotFound,
- self.admin_group_types_client.show_group_type_specs_item,
- group_type['id'], delete_key)
+ delete_keys = ['key1', 'key2', 'key3']
+ for it in delete_keys:
+ self.admin_group_types_client.delete_group_type_specs_item(
+ group_type['id'], it)
+ self.assertRaises(
+ lib_exc.NotFound,
+ self.admin_group_types_client.show_group_type_specs_item,
+ group_type['id'], it)
diff --git a/tempest/api/volume/admin/test_group_types.py b/tempest/api/volume/admin/test_group_types.py
index 8154682..406af27 100644
--- a/tempest/api/volume/admin/test_group_types.py
+++ b/tempest/api/volume/admin/test_group_types.py
@@ -71,3 +71,39 @@
self.admin_group_types_client.list_group_types()['group_types'])
group_ids = [it['id'] for it in group_list]
self.assertNotIn(body['id'], group_ids)
+
+ @decorators.idempotent_id('3d5e5cec-72b4-4511-b135-7cc2b7a053ae')
+ def test_group_type_list_by_optional_params(self):
+ """Test list group type sort/public"""
+ type_a_name = "a_{}".format(data_utils.rand_name('group-type'))
+ type_b_name = "b_{}".format(data_utils.rand_name('group-type'))
+ self.create_group_type(name=type_a_name, **{'is_public': True})
+ self.create_group_type(name=type_b_name, **{'is_public': False})
+
+ group_list = (
+ self.admin_group_types_client.list_group_types(
+ sort="name:asc", is_public=None)['group_types'])
+ name_list = [it['name'] for it in group_list]
+ self.assertLess(
+ name_list.index(type_a_name), name_list.index(type_b_name))
+
+ group_list = (
+ self.admin_group_types_client.list_group_types(
+ sort="name:desc", is_public=None)['group_types'])
+ name_list = [it['name'] for it in group_list]
+ self.assertLess(name_list.index(type_b_name),
+ name_list.index(type_a_name))
+
+ group_list = (
+ self.admin_group_types_client.list_group_types(
+ is_public=False)['group_types'])
+ name_list = [it['name'] for it in group_list]
+ self.assertNotIn(type_a_name, name_list)
+ self.assertIn(type_b_name, name_list)
+
+ group_list = (
+ self.admin_group_types_client.list_group_types(
+ is_public=True)['group_types'])
+ name_list = [it['name'] for it in group_list]
+ self.assertNotIn(type_b_name, name_list)
+ self.assertIn(type_a_name, name_list)
diff --git a/tempest/clients.py b/tempest/clients.py
index 327f0da..4c3d875 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -118,7 +118,8 @@
self.server_groups_client = self.compute.ServerGroupsClient()
self.limits_client = self.compute.LimitsClient()
self.compute_images_client = self.compute.ImagesClient()
- self.keypairs_client = self.compute.KeyPairsClient()
+ self.keypairs_client = self.compute.KeyPairsClient(
+ ssh_key_type=CONF.validation.ssh_key_type)
self.quotas_client = self.compute.QuotasClient()
self.quota_classes_client = self.compute.QuotaClassesClient()
self.flavors_client = self.compute.FlavorsClient()
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 917262e..ad0b547 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -81,11 +81,11 @@
will have the prefix with the given TAG in its name. Using tag is recommended
for the further using, cleaning resources.
-* ``-r, --concurrency CONCURRENCY`` (Optional) Concurrency count
- (default: 1). The number of accounts required can be estimated as
- CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
- a different tenant. This is required to provide isolation between test for
- running in parallel.
+* ``-r, --concurrency CONCURRENCY`` (Optional) Concurrency count (default: 2).
+ The number of accounts generated will be same as CONCURRENCY. The higher the
+ number, the more tests will run in parallel. If you want to run tests
+ sequentially then use 1 as value for concurrency (beware that tests that need
+ more credentials will fail).
* ``--with-admin`` (Optional) Creates admin for each concurrent group
(default: False).
@@ -236,7 +236,7 @@
dest='tag',
help='Resources tag')
parser.add_argument('-r', '--concurrency',
- default=1,
+ default=2,
type=positive_int,
required=False,
dest='concurrency',
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 0db1ab1..421afd3 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -130,7 +130,7 @@
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.warn(msg)
+ LOG.warning(msg)
print_and_or_update('glance', 'service-available', False, update)
return
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index a062f6f..03da49a 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -23,11 +23,14 @@
from oslo_log import log as logging
from oslo_utils import excutils
+from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest import exceptions
from tempest.lib.common import fixed_network
from tempest.lib.common import rest_client
from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
@@ -54,10 +57,104 @@
return False
+def get_server_ip(server, validation_resources=None):
+ """Get the server fixed or floating IP.
+
+ Based on the configuration we're in, return a correct ip
+ address for validating that a guest is up.
+
+ :param server: The server dict as returned by the API
+ :param validation_resources: The dict of validation resources
+ provisioned for the server.
+ """
+ if CONF.validation.connect_method == 'floating':
+ if validation_resources:
+ return validation_resources['floating_ip']['ip']
+ else:
+ msg = ('When validation.connect_method equals floating, '
+ 'validation_resources cannot be None')
+ raise lib_exc.InvalidParam(invalid_param=msg)
+ elif CONF.validation.connect_method == 'fixed':
+ addresses = server['addresses'][CONF.validation.network_for_ssh]
+ for address in addresses:
+ if address['version'] == CONF.validation.ip_version_for_ssh:
+ return address['addr']
+ raise exceptions.ServerUnreachable(server_id=server['id'])
+ else:
+ raise lib_exc.InvalidConfiguration()
+
+
+def _setup_validation_fip(
+ server, clients, tenant_network, validation_resources):
+ if CONF.service_available.neutron:
+ ifaces = clients.interfaces_client.list_interfaces(server['id'])
+ validation_port = None
+ for iface in ifaces['interfaceAttachments']:
+ if iface['net_id'] == tenant_network['id']:
+ validation_port = iface['port_id']
+ break
+ if not validation_port:
+ # NOTE(artom) This will get caught by the catch-all clause in
+ # the wait_until loop below
+ raise ValueError('Unable to setup floating IP for validation: '
+ 'port not found on tenant network')
+ clients.floating_ips_client.update_floatingip(
+ validation_resources['floating_ip']['id'],
+ port_id=validation_port)
+ else:
+ fip_client = clients.compute_floating_ips_client
+ fip_client.associate_floating_ip_to_server(
+ floating_ip=validation_resources['floating_ip']['ip'],
+ server_id=server['id'])
+
+
+def wait_for_ssh_or_ping(server, clients, tenant_network,
+ validatable, validation_resources, wait_until,
+ set_floatingip):
+ """Wait for the server for SSH or Ping as requested.
+
+ :param server: The server dict as returned by the API
+ :param clients: Client manager which provides OpenStack Tempest clients.
+ :param tenant_network: Tenant network to be used for creating a server.
+ :param validatable: Whether the server will be pingable or sshable.
+ :param validation_resources: Resources created for the connection to the
+ server. Include a keypair, a security group and an IP.
+ :param wait_until: Server status to wait for the server to reach.
+ It can be PINGABLE and SSHABLE states when the server is both
+ validatable and has the required validation_resources provided.
+ :param set_floatingip: If FIP needs to be associated to server
+ """
+ if set_floatingip and CONF.validation.connect_method == 'floating':
+ _setup_validation_fip(
+ server, clients, tenant_network, validation_resources)
+
+ server_ip = get_server_ip(
+ server, validation_resources=validation_resources)
+ if wait_until == 'PINGABLE':
+ waiters.wait_for_ping(
+ server_ip,
+ clients.servers_client.build_timeout,
+ clients.servers_client.build_interval
+ )
+ if wait_until == 'SSHABLE':
+ pkey = validation_resources['keypair']['private_key']
+ ssh_client = remote_client.RemoteClient(
+ server_ip,
+ CONF.validation.image_ssh_user,
+ pkey=pkey,
+ server=server,
+ servers_client=clients.servers_client
+ )
+ waiters.wait_for_ssh(
+ ssh_client,
+ clients.servers_client.build_timeout
+ )
+
+
def create_test_server(clients, validatable=False, validation_resources=None,
tenant_network=None, wait_until=None,
volume_backed=False, name=None, flavor=None,
- image_id=None, wait_for_sshable=True, **kwargs):
+ image_id=None, **kwargs):
"""Common wrapper utility returning a test server.
This method is a common wrapper returning a test server that can be
@@ -69,7 +166,9 @@
server. Include a keypair, a security group and an IP.
:param tenant_network: Tenant network to be used for creating a server.
:param wait_until: Server status to wait for the server to reach after
- its creation.
+ its creation. Additionally PINGABLE and SSHABLE states are also
+ accepted when the server is both validatable and has the required
+ validation_resources provided.
:param volume_backed: Whether the server is volume backed or not.
If this is true, a volume will be created and create server will be
requested with 'block_device_mapping_v2' populated with below values:
@@ -93,13 +192,9 @@
CONF.compute.flavor_ref will be used instead.
:param image_id: ID of the image to be used to provision the server. If not
defined, CONF.compute.image_ref will be used instead.
- :param wait_for_sshable: Check server's console log and wait until it will
- be ready to login.
:returns: a tuple
"""
- # TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
-
if name is None:
name = data_utils.rand_name(__name__ + "-instance")
if flavor is None:
@@ -197,6 +292,7 @@
body = clients.servers_client.create_server(name=name, imageRef=image_id,
flavorRef=flavor,
**kwargs)
+ request_id = body.response['x-openstack-request-id']
# handle the case of multiple servers
if multiple_create_request:
@@ -208,39 +304,31 @@
body = rest_client.ResponseBody(body.response, body['server'])
servers = [body]
- def _setup_validation_fip():
- if CONF.service_available.neutron:
- ifaces = clients.interfaces_client.list_interfaces(server['id'])
- validation_port = None
- for iface in ifaces['interfaceAttachments']:
- if iface['net_id'] == tenant_network['id']:
- validation_port = iface['port_id']
- break
- if not validation_port:
- # NOTE(artom) This will get caught by the catch-all clause in
- # the wait_until loop below
- raise ValueError('Unable to setup floating IP for validation: '
- 'port not found on tenant network')
- clients.floating_ips_client.update_floatingip(
- validation_resources['floating_ip']['id'],
- port_id=validation_port)
- else:
- fip_client = clients.compute_floating_ips_client
- fip_client.associate_floating_ip_to_server(
- floating_ip=validation_resources['floating_ip']['ip'],
- server_id=servers[0]['id'])
-
if wait_until:
+
+ # NOTE(lyarwood): PINGABLE and SSHABLE both require the instance to
+ # go ACTIVE initially before we can setup the fip(s) etc so stash
+ # this additional wait state for later use.
+ wait_until_extra = None
+ if wait_until in ['PINGABLE', 'SSHABLE']:
+ wait_until_extra = wait_until
+ wait_until = 'ACTIVE'
+
for server in servers:
try:
waiters.wait_for_server_status(
- clients.servers_client, server['id'], wait_until)
-
- # Multiple validatable servers are not supported for now. Their
- # creation will fail with the condition above.
+ clients.servers_client, server['id'], wait_until,
+ request_id=request_id)
if CONF.validation.run_validation and validatable:
if CONF.validation.connect_method == 'floating':
- _setup_validation_fip()
+ _setup_validation_fip(
+ server, clients, tenant_network,
+ validation_resources)
+ if wait_until_extra:
+ wait_for_ssh_or_ping(
+ server, clients, tenant_network,
+ validatable, validation_resources,
+ wait_until_extra, False)
except Exception:
with excutils.save_and_reraise_exception():
@@ -265,10 +353,6 @@
LOG.exception('Server %s failed to delete in time',
server['id'])
- if (validatable and CONF.compute_feature_enabled.console_output and
- wait_for_sshable):
- waiters.wait_for_guest_os_boot(clients.servers_client, server['id'])
-
return body, servers
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 5d6e129..9d9fab7 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -48,7 +48,8 @@
console_output_enabled=CONF.compute_feature_enabled.console_output,
ssh_shell_prologue=CONF.validation.ssh_shell_prologue,
ping_count=CONF.validation.ping_count,
- ping_size=CONF.validation.ping_size)
+ ping_size=CONF.validation.ping_size,
+ ssh_key_type=CONF.validation.ssh_key_type)
# Note that this method will not work on SLES11 guests, as they do
# not support the TYPE column on lsblk
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 1b69349..ab401fb 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
import re
import time
@@ -32,7 +33,8 @@
# NOTE(afazekas): This function needs to know a token and a subject.
def wait_for_server_status(client, server_id, status, ready_wait=True,
- extra_timeout=0, raise_on_error=True):
+ extra_timeout=0, raise_on_error=True,
+ request_id=None):
"""Waits for a server to reach a given status."""
# NOTE(afazekas): UNKNOWN status possible on ERROR
@@ -71,11 +73,12 @@
'/'.join((server_status, str(task_state))),
time.time() - start_time)
if (server_status == 'ERROR') and raise_on_error:
+ details = ''
if 'fault' in body:
- raise exceptions.BuildErrorException(body['fault'],
- server_id=server_id)
- else:
- raise exceptions.BuildErrorException(server_id=server_id)
+ details += 'Fault: %s.' % body['fault']
+ if request_id:
+ details += ' Server boot request ID: %s.' % request_id
+ raise exceptions.BuildErrorException(details, server_id=server_id)
timed_out = int(time.time()) - start_time >= timeout
@@ -88,6 +91,8 @@
'status': status,
'expected_task_state': expected_task_state,
'timeout': timeout})
+ if request_id:
+ message += ' Server boot request ID: %s.' % request_id
message += ' Current status: %s.' % server_status
message += ' Current task state: %s.' % task_state
caller = test_utils.find_test_caller()
@@ -526,23 +531,6 @@
raise lib_exc.TimeoutException(message)
-def wait_for_guest_os_boot(client, server_id):
- start_time = int(time.time())
- while True:
- console_output = client.get_console_output(server_id)['output']
- for line in console_output.split('\n'):
- if 'login:' in line.lower():
- return
- if int(time.time()) - start_time >= client.build_timeout:
- LOG.info("Guest OS on server %s probably isn't ready or its "
- "console log can't be parsed properly. If guest OS "
- "isn't ready, that may cause problems with SSH to "
- "the server.",
- server_id)
- return
- time.sleep(client.build_interval)
-
-
def wait_for_server_floating_ip(servers_client, server, floating_ip,
wait_for_disassociate=False):
"""Wait for floating IP association or disassociation.
@@ -583,3 +571,26 @@
'in time.' % (floating_ip, server['id']))
raise lib_exc.TimeoutException(msg)
time.sleep(servers_client.build_interval)
+
+
+def wait_for_ping(server_ip, timeout=30, interval=1):
+ """Waits for an address to become pingable"""
+ start_time = int(time.time())
+ while int(time.time()) - start_time < timeout:
+ response = os.system("ping -c 1 " + server_ip)
+ if response == 0:
+ return
+ time.sleep(interval)
+ raise lib_exc.TimeoutException()
+
+
+def wait_for_ssh(ssh_client, timeout=30):
+ """Waits for SSH connection to become usable"""
+ start_time = int(time.time())
+ while int(time.time()) - start_time < timeout:
+ try:
+ ssh_client.validate_authentication()
+ return
+ except lib_exc.SSHTimeout:
+ pass
+ raise lib_exc.TimeoutException()
diff --git a/tempest/config.py b/tempest/config.py
index 7c81dd2..b4d4891 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -653,6 +653,9 @@
default=True,
help='Does the test environment support attaching devices '
'using an IDE bus to the instance?'),
+ cfg.BoolOpt('unified_limits',
+ default=False,
+ help='Does the test environment support unified limits?'),
]
@@ -970,6 +973,10 @@
default='public',
help="Network used for SSH connections. Ignored if "
"connect_method=floating."),
+ cfg.StrOpt('ssh_key_type',
+ default='rsa',
+ help='Type of key to use for ssh connections. '
+ 'Valid types are rsa, ecdsa'),
]
volume_group = cfg.OptGroup(name='volume',
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index c1e6b2d..1c9c55b 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -318,3 +318,16 @@
" to all negative API tests"
)
_HAVE_NEGATIVE_DECORATOR = False
+
+
+@core.flake8ext
+def no_log_warn(logical_line):
+ """Disallow 'LOG.warn('
+
+ Use LOG.warning() instead of Deprecated LOG.warn().
+ https://docs.python.org/3/library/logging.html#logging.warning
+ """
+
+ msg = ("T118: LOG.warn is deprecated, please use LOG.warning!")
+ if "LOG.warn(" in logical_line:
+ yield (0, msg)
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 3300298..bd42afd 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -506,6 +506,10 @@
}
}
+create_backup = {
+ 'status_code': [202]
+}
+
server_actions_common_schema = {
'status_code': [202]
}
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
index dcd64cf..2b3ce38 100644
--- a/tempest/lib/api_schema/response/compute/v2_16/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -172,3 +172,4 @@
show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
+create_backup = copy.deepcopy(servers.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index 0e4bd5c..ba3d787 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -62,3 +62,4 @@
show_volume_attachment = copy.deepcopy(serversv216.show_volume_attachment)
list_volume_attachments = copy.deepcopy(serversv216.list_volume_attachments)
show_instance_action = copy.deepcopy(serversv216.show_instance_action)
+create_backup = copy.deepcopy(serversv216.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index 74c08f1..123eb72 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -105,3 +105,4 @@
show_volume_attachment = copy.deepcopy(servers219.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers219.list_volume_attachments)
show_instance_action = copy.deepcopy(servers219.show_instance_action)
+create_backup = copy.deepcopy(servers219.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_3/servers.py b/tempest/lib/api_schema/response/compute/v2_3/servers.py
index 435e3ac..d19f1ad 100644
--- a/tempest/lib/api_schema/response/compute/v2_3/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -177,3 +177,4 @@
show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
+create_backup = copy.deepcopy(servers.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_33/__init__.py b/tempest/lib/api_schema/response/compute/v2_33/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_33/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_33/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_33/hypervisors.py
new file mode 100644
index 0000000..9773605
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_33/hypervisors.py
@@ -0,0 +1,53 @@
+# Copyright 2018 ZTE 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_28 \
+ import hypervisors as hypervisorsv228
+
+###########################################################################
+#
+# 2.33:
+#
+# hypervisor_links parameter is added to the response body for the following
+# APIs:
+#
+# - GET /os-hypervisors
+# - GET /os-hypervisors/detail
+###########################################################################
+list_search_hypervisors = copy.deepcopy(
+ hypervisorsv228.list_search_hypervisors)
+list_search_hypervisors['response_body']['properties'].update(
+ {'hypervisors_links': parameter_types.links}
+)
+
+list_hypervisors_detail = copy.deepcopy(
+ hypervisorsv228.list_hypervisors_detail)
+list_hypervisors_detail['response_body']['properties'].update(
+ {'hypervisors_links': parameter_types.links}
+)
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.28 ***
+get_hypervisor = copy.deepcopy(hypervisorsv228.get_hypervisor)
+hypervisor_detail = copy.deepcopy(hypervisorsv228.hypervisor_detail)
+get_hypervisor_statistics = \
+ copy.deepcopy(hypervisorsv228.get_hypervisor_statistics)
+get_hypervisor_uptime = copy.deepcopy(hypervisorsv228.get_hypervisor_uptime)
+get_hypervisors_servers = copy.deepcopy(
+ hypervisorsv228.get_hypervisors_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_45/servers.py b/tempest/lib/api_schema/response/compute/v2_45/servers.py
new file mode 100644
index 0000000..cb0fc13
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_45/servers.py
@@ -0,0 +1,49 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_26 import servers as servers226
+
+create_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'image_id': {'type': 'string', 'format': 'uuid'}
+ },
+ 'additionalProperties': False,
+ 'required': ['image_id']
+ }
+}
+# NOTE(gmann): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.26 ***
+get_server = copy.deepcopy(servers226.get_server)
+list_servers_detail = copy.deepcopy(servers226.list_servers_detail)
+update_server = copy.deepcopy(servers226.update_server)
+rebuild_server = copy.deepcopy(servers226.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers226.rebuild_server_with_admin_pass)
+show_server_diagnostics = copy.deepcopy(servers226.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers226.get_remote_consoles)
+list_tags = copy.deepcopy(servers226.list_tags)
+update_all_tags = copy.deepcopy(servers226.update_all_tags)
+delete_all_tags = copy.deepcopy(servers226.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers226.check_tag_existence)
+update_tag = copy.deepcopy(servers226.update_tag)
+delete_tag = copy.deepcopy(servers226.delete_tag)
+list_servers = copy.deepcopy(servers226.list_servers)
+attach_volume = copy.deepcopy(servers226.attach_volume)
+show_volume_attachment = copy.deepcopy(servers226.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers226.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 7050602..1399c2d 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -13,6 +13,7 @@
import copy
from tempest.lib.api_schema.response.compute.v2_26 import servers as servers226
+from tempest.lib.api_schema.response.compute.v2_45 import servers as servers245
flavor = {
'type': 'object',
@@ -34,39 +35,40 @@
'required': ['original_name', 'disk', 'ephemeral', 'ram', 'swap', 'vcpus']
}
-get_server = copy.deepcopy(servers226.get_server)
+get_server = copy.deepcopy(servers245.get_server)
get_server['response_body']['properties']['server'][
'properties'].update({'flavor': flavor})
-list_servers_detail = copy.deepcopy(servers226.list_servers_detail)
+list_servers_detail = copy.deepcopy(servers245.list_servers_detail)
list_servers_detail['response_body']['properties']['servers']['items'][
'properties'].update({'flavor': flavor})
-update_server = copy.deepcopy(servers226.update_server)
+update_server = copy.deepcopy(servers245.update_server)
update_server['response_body']['properties']['server'][
'properties'].update({'flavor': flavor})
-rebuild_server = copy.deepcopy(servers226.rebuild_server)
+rebuild_server = copy.deepcopy(servers245.rebuild_server)
rebuild_server['response_body']['properties']['server'][
'properties'].update({'flavor': flavor})
rebuild_server_with_admin_pass = copy.deepcopy(
- servers226.rebuild_server_with_admin_pass)
+ servers245.rebuild_server_with_admin_pass)
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'properties'].update({'flavor': flavor})
# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
# to keep this schema in this file to have the generic way to select the
# right schema based on self.schema_versions_info mapping in service client.
-show_server_diagnostics = copy.deepcopy(servers226.show_server_diagnostics)
-get_remote_consoles = copy.deepcopy(servers226.get_remote_consoles)
-list_tags = copy.deepcopy(servers226.list_tags)
-update_all_tags = copy.deepcopy(servers226.update_all_tags)
-delete_all_tags = copy.deepcopy(servers226.delete_all_tags)
-check_tag_existence = copy.deepcopy(servers226.check_tag_existence)
-update_tag = copy.deepcopy(servers226.update_tag)
-delete_tag = copy.deepcopy(servers226.delete_tag)
-list_servers = copy.deepcopy(servers226.list_servers)
-attach_volume = copy.deepcopy(servers226.attach_volume)
-show_volume_attachment = copy.deepcopy(servers226.show_volume_attachment)
-list_volume_attachments = copy.deepcopy(servers226.list_volume_attachments)
+show_server_diagnostics = copy.deepcopy(servers245.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers245.get_remote_consoles)
+list_tags = copy.deepcopy(servers245.list_tags)
+update_all_tags = copy.deepcopy(servers245.update_all_tags)
+delete_all_tags = copy.deepcopy(servers245.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers245.check_tag_existence)
+update_tag = copy.deepcopy(servers245.update_tag)
+delete_tag = copy.deepcopy(servers245.delete_tag)
+list_servers = copy.deepcopy(servers245.list_servers)
+attach_volume = copy.deepcopy(servers245.attach_volume)
+show_volume_attachment = copy.deepcopy(servers245.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers245.list_volume_attachments)
show_instance_action = copy.deepcopy(servers226.show_instance_action)
+create_backup = copy.deepcopy(servers245.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
index af6344b..5b53906 100644
--- a/tempest/lib/api_schema/response/compute/v2_48/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -133,3 +133,4 @@
show_volume_attachment = copy.deepcopy(servers247.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers247.list_volume_attachments)
show_instance_action = copy.deepcopy(servers247.show_instance_action)
+create_backup = copy.deepcopy(servers247.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_50/__init__.py b/tempest/lib/api_schema/response/compute/v2_50/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_50/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_50/quota_classes.py b/tempest/lib/api_schema/response/compute/v2_50/quota_classes.py
new file mode 100644
index 0000000..4ee845f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_50/quota_classes.py
@@ -0,0 +1,48 @@
+# 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 copy
+from tempest.lib.api_schema.response.compute.v2_1 import quota_classes \
+ as quota_classesv21
+
+# Compute microversion 2.50:
+# 1. fixed_ips, floating_ips, security_group_rules and security_groups
+# are removed from:
+# * GET /os-quota-class-sets/{id}
+# * PUT /os-quota-class-sets/{id}
+# 2. server_groups and server_group_members are added to:
+# * GET /os-quota-class-sets/{id}
+# * PUT /os-quota-class-sets/{id}
+
+get_quota_class_set = copy.deepcopy(quota_classesv21.get_quota_class_set)
+update_quota_class_set = copy.deepcopy(quota_classesv21.update_quota_class_set)
+for field in ['fixed_ips', 'floating_ips', 'security_group_rules',
+ 'security_groups']:
+ get_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'properties'].pop(field, None)
+ get_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'required'].remove(field)
+ update_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'properties'].pop(field, None)
+ update_quota_class_set['response_body']['properties'][
+ 'quota_class_set']['required'].remove(field)
+for field in ['server_groups', 'server_group_members']:
+ get_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'properties'].update({field: {'type': 'integer'}})
+ get_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'required'].append(field)
+ update_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'properties'].update({field: {'type': 'integer'}})
+ update_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'required'].append(field)
diff --git a/tempest/lib/api_schema/response/compute/v2_51/servers.py b/tempest/lib/api_schema/response/compute/v2_51/servers.py
index e603287..50d6aaa 100644
--- a/tempest/lib/api_schema/response/compute/v2_51/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_51/servers.py
@@ -40,3 +40,4 @@
attach_volume = copy.deepcopy(servers248.attach_volume)
show_volume_attachment = copy.deepcopy(servers248.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers248.list_volume_attachments)
+create_backup = copy.deepcopy(servers248.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_53/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_53/hypervisors.py
new file mode 100644
index 0000000..e172f1f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/hypervisors.py
@@ -0,0 +1,68 @@
+# Copyright 2018 ZTE 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_33 \
+ import hypervisors as hypervisorsv233
+
+###########################################################################
+#
+# 2.53:
+#
+# servers parameter is added to the response body for the following
+# APIs:
+#
+# - GET /os-hypervisor
+# - GET /os-hypervisors
+# - GET /os-hypervisors/detail
+#
+###########################################################################
+
+servers = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'uuid': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ },
+}
+
+hypervisor_detail = copy.deepcopy(hypervisorsv233.hypervisor_detail)
+hypervisor_detail['properties'].update({'servers': servers})
+get_hypervisor = copy.deepcopy(hypervisorsv233.get_hypervisor)
+get_hypervisor['response_body']['properties'].update(
+ {'hypervisor': hypervisor_detail})
+list_hypervisors_detail = copy.deepcopy(
+ hypervisorsv233.list_hypervisors_detail)
+list_hypervisors_detail['response_body']['properties']['hypervisors'].update(
+ {'items': hypervisor_detail})
+
+list_search_hypervisors = copy.deepcopy(
+ hypervisorsv233.list_search_hypervisors)
+list_search_hypervisors['response_body']['properties']['hypervisors'][
+ 'items']['properties'].update({'servers': servers})
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.33 ***
+get_hypervisor_statistics = \
+ copy.deepcopy(hypervisorsv233.get_hypervisor_statistics)
+get_hypervisor_uptime = copy.deepcopy(hypervisorsv233.get_hypervisor_uptime)
+get_hypervisors_servers = copy.deepcopy(
+ hypervisorsv233.get_hypervisors_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
index 135b381..9de3016 100644
--- a/tempest/lib/api_schema/response/compute/v2_54/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -59,3 +59,4 @@
show_volume_attachment = copy.deepcopy(servers251.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers251.list_volume_attachments)
show_instance_action = copy.deepcopy(servers251.show_instance_action)
+create_backup = copy.deepcopy(servers251.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/quota_classes.py b/tempest/lib/api_schema/response/compute/v2_57/quota_classes.py
new file mode 100644
index 0000000..396ed66
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/quota_classes.py
@@ -0,0 +1,37 @@
+# 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 copy
+from tempest.lib.api_schema.response.compute.v2_50 import quota_classes \
+ as quota_classesv250
+
+# Compute microversion 2.57:
+# 1. injected_file_content_bytes, injected_file_path_bytes, injected_files
+# are removed from:
+# * GET /os-quota-class-sets/{id}
+# * PUT /os-quota-class-sets/{id}
+
+get_quota_class_set = copy.deepcopy(quota_classesv250.get_quota_class_set)
+update_quota_class_set = copy.deepcopy(
+ quota_classesv250.update_quota_class_set)
+for field in ['injected_file_content_bytes', 'injected_file_path_bytes',
+ 'injected_files']:
+ get_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'properties'].pop(field, None)
+ get_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'required'].remove(field)
+ update_quota_class_set['response_body']['properties']['quota_class_set'][
+ 'properties'].pop(field, None)
+ update_quota_class_set['response_body']['properties'][
+ 'quota_class_set']['required'].remove(field)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/servers.py b/tempest/lib/api_schema/response/compute/v2_57/servers.py
index bdff74b..ee91391 100644
--- a/tempest/lib/api_schema/response/compute/v2_57/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_57/servers.py
@@ -63,3 +63,4 @@
show_volume_attachment = copy.deepcopy(servers254.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers254.list_volume_attachments)
show_instance_action = copy.deepcopy(servers254.show_instance_action)
+create_backup = copy.deepcopy(servers254.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_58/servers.py b/tempest/lib/api_schema/response/compute/v2_58/servers.py
index 62239cf..637b765 100644
--- a/tempest/lib/api_schema/response/compute/v2_58/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_58/servers.py
@@ -42,3 +42,4 @@
attach_volume = copy.deepcopy(servers257.attach_volume)
show_volume_attachment = copy.deepcopy(servers257.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers257.list_volume_attachments)
+create_backup = copy.deepcopy(servers257.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
index 6103b7c..e6b2c32 100644
--- a/tempest/lib/api_schema/response/compute/v2_6/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -32,6 +32,7 @@
show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
+create_backup = copy.deepcopy(servers.create_backup)
# NOTE: The consolidated remote console API got introduced with v2.6
# with bp/consolidate-console-api. See Nova commit 578bafeda
diff --git a/tempest/lib/api_schema/response/compute/v2_62/servers.py b/tempest/lib/api_schema/response/compute/v2_62/servers.py
index 23eebbb..d761fe9 100644
--- a/tempest/lib/api_schema/response/compute/v2_62/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_62/servers.py
@@ -45,3 +45,4 @@
attach_volume = copy.deepcopy(servers258.attach_volume)
show_volume_attachment = copy.deepcopy(servers258.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers258.list_volume_attachments)
+create_backup = copy.deepcopy(servers258.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
index db713b1..865b4fd 100644
--- a/tempest/lib/api_schema/response/compute/v2_63/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -77,3 +77,4 @@
show_volume_attachment = copy.deepcopy(servers262.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers262.list_volume_attachments)
show_instance_action = copy.deepcopy(servers262.show_instance_action)
+create_backup = copy.deepcopy(servers262.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_64/__init__.py b/tempest/lib/api_schema/response/compute/v2_64/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_64/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_64/server_groups.py b/tempest/lib/api_schema/response/compute/v2_64/server_groups.py
new file mode 100644
index 0000000..1402de5
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_64/server_groups.py
@@ -0,0 +1,56 @@
+# Copyright 2020 ZTE 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_13 import server_groups as \
+ server_groupsv213
+
+# Compute microversion 2.64:
+# 1. change policies to policy in:
+# * GET /os-server-groups
+# * POST /os-server-groups
+# * GET /os-server-groups/{server_group_id}
+# 2. add rules in:
+# * GET /os-server-groups
+# * POST /os-server-groups
+# * GET /os-server-groups/{server_group_id}
+# 3. remove metadata from:
+# * GET /os-server-groups
+# * POST /os-server-groups
+# * GET /os-server-groups/{server_group_id}
+
+common_server_group = copy.deepcopy(server_groupsv213.common_server_group)
+common_server_group['properties']['policy'] = {'type': 'string'}
+common_server_group['properties']['rules'] = {'type': 'object'}
+common_server_group['properties'].pop('policies')
+common_server_group['properties'].pop('metadata')
+common_server_group['required'].append('policy')
+common_server_group['required'].append('rules')
+common_server_group['required'].remove('policies')
+common_server_group['required'].remove('metadata')
+
+create_show_server_group = copy.deepcopy(
+ server_groupsv213.create_show_server_group)
+create_show_server_group['response_body']['properties'][
+ 'server_group'] = common_server_group
+
+list_server_groups = copy.deepcopy(server_groupsv213.list_server_groups)
+list_server_groups['response_body']['properties']['server_groups'][
+ 'items'] = common_server_group
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+delete_server_group = copy.deepcopy(server_groupsv213.delete_server_group)
diff --git a/tempest/lib/api_schema/response/compute/v2_70/servers.py b/tempest/lib/api_schema/response/compute/v2_70/servers.py
index 6103923..6bb688a 100644
--- a/tempest/lib/api_schema/response/compute/v2_70/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_70/servers.py
@@ -79,3 +79,4 @@
update_tag = copy.deepcopy(servers263.update_tag)
delete_tag = copy.deepcopy(servers263.delete_tag)
show_instance_action = copy.deepcopy(servers263.show_instance_action)
+create_backup = copy.deepcopy(servers263.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_71/servers.py b/tempest/lib/api_schema/response/compute/v2_71/servers.py
index 3e55c1c..b1c202b 100644
--- a/tempest/lib/api_schema/response/compute/v2_71/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_71/servers.py
@@ -83,3 +83,4 @@
show_volume_attachment = copy.deepcopy(servers270.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers270.list_volume_attachments)
show_instance_action = copy.deepcopy(servers270.show_instance_action)
+create_backup = copy.deepcopy(servers270.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_73/servers.py b/tempest/lib/api_schema/response/compute/v2_73/servers.py
index e7a1d87..89f100d 100644
--- a/tempest/lib/api_schema/response/compute/v2_73/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_73/servers.py
@@ -80,3 +80,4 @@
show_volume_attachment = copy.deepcopy(servers271.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers271.list_volume_attachments)
show_instance_action = copy.deepcopy(servers271.show_instance_action)
+create_backup = copy.deepcopy(servers271.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_75/__init__.py b/tempest/lib/api_schema/response/compute/v2_75/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_75/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_75/servers.py b/tempest/lib/api_schema/response/compute/v2_75/servers.py
new file mode 100644
index 0000000..6b3e93d
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_75/servers.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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_57 import servers as servers257
+from tempest.lib.api_schema.response.compute.v2_73 import servers as servers273
+
+
+###########################################################################
+#
+# 2.75:
+#
+# Server representation is made consistent among GET, PUT
+# and Rebuild serevr APIs response.
+#
+###########################################################################
+
+rebuild_server = copy.deepcopy(servers273.get_server)
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].pop('OS-EXT-SRV-ATTR:user_data')
+rebuild_server['status_code'] = [202]
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].update({'user_data': servers257.user_data})
+rebuild_server['response_body']['properties']['server'][
+ 'required'].append('user_data')
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+update_server = copy.deepcopy(servers273.get_server)
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.73 ***
+get_server = copy.deepcopy(servers273.get_server)
+list_servers = copy.deepcopy(servers273.list_servers)
+list_servers_detail = copy.deepcopy(servers273.list_servers_detail)
+show_server_diagnostics = copy.deepcopy(servers273.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers273.get_remote_consoles)
+list_tags = copy.deepcopy(servers273.list_tags)
+update_all_tags = copy.deepcopy(servers273.update_all_tags)
+delete_all_tags = copy.deepcopy(servers273.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers273.check_tag_existence)
+update_tag = copy.deepcopy(servers273.update_tag)
+delete_tag = copy.deepcopy(servers273.delete_tag)
+attach_volume = copy.deepcopy(servers273.attach_volume)
+show_volume_attachment = copy.deepcopy(servers273.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers273.list_volume_attachments)
+show_instance_action = copy.deepcopy(servers273.show_instance_action)
+create_backup = copy.deepcopy(servers273.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_79/servers.py b/tempest/lib/api_schema/response/compute/v2_79/servers.py
index b5507f9..77d9beb 100644
--- a/tempest/lib/api_schema/response/compute/v2_79/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_79/servers.py
@@ -12,7 +12,7 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_73 import servers as servers273
+from tempest.lib.api_schema.response.compute.v2_75 import servers as servers275
###########################################################################
@@ -27,19 +27,19 @@
# - POST /servers/{server_id}/os-volume_attachments
###########################################################################
-attach_volume = copy.deepcopy(servers273.attach_volume)
+attach_volume = copy.deepcopy(servers275.attach_volume)
attach_volume['response_body']['properties']['volumeAttachment'][
'properties'].update({'delete_on_termination': {'type': 'boolean'}})
attach_volume['response_body']['properties']['volumeAttachment'][
'required'].append('delete_on_termination')
-show_volume_attachment = copy.deepcopy(servers273.show_volume_attachment)
+show_volume_attachment = copy.deepcopy(servers275.show_volume_attachment)
show_volume_attachment['response_body']['properties']['volumeAttachment'][
'properties'].update({'delete_on_termination': {'type': 'boolean'}})
show_volume_attachment['response_body']['properties'][
'volumeAttachment']['required'].append('delete_on_termination')
-list_volume_attachments = copy.deepcopy(servers273.list_volume_attachments)
+list_volume_attachments = copy.deepcopy(servers275.list_volume_attachments)
list_volume_attachments['response_body']['properties']['volumeAttachments'][
'items']['properties'].update(
{'delete_on_termination': {'type': 'boolean'}})
@@ -49,20 +49,21 @@
# NOTE(zhufl): Below are the unchanged schema in this microversion. We
# need to keep this schema in this file to have the generic way to select the
# right schema based on self.schema_versions_info mapping in service client.
-# ****** Schemas unchanged since microversion 2.73 ***
-rebuild_server = copy.deepcopy(servers273.rebuild_server)
+# ****** Schemas unchanged since microversion 2.75 ***
+rebuild_server = copy.deepcopy(servers275.rebuild_server)
rebuild_server_with_admin_pass = copy.deepcopy(
- servers273.rebuild_server_with_admin_pass)
-update_server = copy.deepcopy(servers273.update_server)
-get_server = copy.deepcopy(servers273.get_server)
-list_servers_detail = copy.deepcopy(servers273.list_servers_detail)
-list_servers = copy.deepcopy(servers273.list_servers)
-show_server_diagnostics = copy.deepcopy(servers273.show_server_diagnostics)
-get_remote_consoles = copy.deepcopy(servers273.get_remote_consoles)
-list_tags = copy.deepcopy(servers273.list_tags)
-update_all_tags = copy.deepcopy(servers273.update_all_tags)
-delete_all_tags = copy.deepcopy(servers273.delete_all_tags)
-check_tag_existence = copy.deepcopy(servers273.check_tag_existence)
-update_tag = copy.deepcopy(servers273.update_tag)
-delete_tag = copy.deepcopy(servers273.delete_tag)
-show_instance_action = copy.deepcopy(servers273.show_instance_action)
+ servers275.rebuild_server_with_admin_pass)
+update_server = copy.deepcopy(servers275.update_server)
+get_server = copy.deepcopy(servers275.get_server)
+list_servers_detail = copy.deepcopy(servers275.list_servers_detail)
+list_servers = copy.deepcopy(servers275.list_servers)
+show_server_diagnostics = copy.deepcopy(servers275.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers275.get_remote_consoles)
+list_tags = copy.deepcopy(servers275.list_tags)
+update_all_tags = copy.deepcopy(servers275.update_all_tags)
+delete_all_tags = copy.deepcopy(servers275.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers275.check_tag_existence)
+update_tag = copy.deepcopy(servers275.update_tag)
+delete_tag = copy.deepcopy(servers275.delete_tag)
+show_instance_action = copy.deepcopy(servers275.show_instance_action)
+create_backup = copy.deepcopy(servers275.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_8/servers.py b/tempest/lib/api_schema/response/compute/v2_8/servers.py
index 119d8e2..366fb1b 100644
--- a/tempest/lib/api_schema/response/compute/v2_8/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_8/servers.py
@@ -39,3 +39,4 @@
show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
+create_backup = copy.deepcopy(servers.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index 9258eec..b4c7865 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -58,3 +58,4 @@
show_volume_attachment = copy.deepcopy(servers.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
+create_backup = copy.deepcopy(servers.create_backup)
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
index 74ae77c..3be55c0 100644
--- a/tempest/lib/base.py
+++ b/tempest/lib/base.py
@@ -14,29 +14,11 @@
# under the License.
import os
-import sys
import fixtures
-import pkg_resources
import testtools
-def _handle_skip_exception():
- try:
- stestr_version = pkg_resources.parse_version(
- pkg_resources.get_distribution("stestr").version)
- stestr_min = pkg_resources.parse_version('2.5.0')
- new_stestr = (stestr_version >= stestr_min)
- import unittest
- import unittest2
- if sys.version_info >= (3, 5) and new_stestr:
- testtools.TestCase.skipException = unittest.case.SkipTest
- else:
- testtools.TestCase.skipException = unittest2.case.SkipTest
- except Exception:
- pass
-
-
class BaseTestCase(testtools.testcase.WithAttributes, testtools.TestCase):
setUpClassCalled = False
@@ -51,18 +33,6 @@
if hasattr(super(BaseTestCase, cls), 'setUpClass'):
super(BaseTestCase, cls).setUpClass()
cls.setUpClassCalled = True
- # TODO(gmann): cls.handle_skip_exception is really workaround for
- # testtools bug- https://github.com/testing-cabal/testtools/issues/272
- # stestr which is used by Tempest internally to run the test switch
- # the customize test runner(which use stdlib unittest) for >=py3.5
- # else testtools.run.- https://github.com/mtreinish/stestr/pull/265
- # These two test runner are not compatible due to skip exception
- # handling(due to unittest2). testtools.run treat unittestt.SkipTest
- # as error and stdlib unittest treat unittest2.case.SkipTest raised
- # by testtools.TestCase.skipException.
- # The below workaround can be removed once testtools fix issue# 272.
- cls.orig_skip_exception = testtools.TestCase.skipException
- _handle_skip_exception()
@classmethod
def tearDownClass(cls):
@@ -70,7 +40,6 @@
super(BaseTestCase, cls).tearDownClass()
def setUp(self):
- testtools.TestCase.skipException = self.orig_skip_exception
super(BaseTestCase, self).setUp()
if not self.setUpClassCalled:
raise RuntimeError("setUpClass does not calls the super's "
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 3f735f5..ef14dfc 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -907,8 +907,8 @@
if int(time.time()) - start_time >= self.build_timeout:
message = ('Failed to delete %(resource_type)s %(id)s within '
'the required time (%(timeout)s s). Timer started '
- 'at %(start_time)s. Timer ended at %(end_time)s'
- 'waited for %(wait_time)s' %
+ 'at %(start_time)s. Timer ended at %(end_time)s. '
+ 'Waited for %(wait_time)s s.' %
{'resource_type': self.resource_type, 'id': id,
'timeout': self.build_timeout,
'start_time': start_time,
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index ee15375..cb59a82 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -21,6 +21,7 @@
import warnings
from oslo_log import log as logging
+from oslo_utils.secretutils import md5
from tempest.lib import exceptions
@@ -33,11 +34,26 @@
LOG = logging.getLogger(__name__)
+def get_fingerprint(self):
+ """Patch paramiko
+
+ This method needs to be patched to allow paramiko to work under FIPS.
+ Until the patch to do this merges, patch paramiko here.
+
+ TODO(alee) Remove this when paramiko is patched.
+ See https://github.com/paramiko/paramiko/pull/1928
+ """
+ return md5(self.asbytes(), usedforsecurity=False).digest()
+
+
+paramiko.pkey.PKey.get_fingerprint = get_fingerprint
+
+
class Client(object):
def __init__(self, host, username, password=None, timeout=300, pkey=None,
channel_timeout=10, look_for_keys=False, key_filename=None,
- port=22, proxy_client=None):
+ port=22, proxy_client=None, ssh_key_type='rsa'):
"""SSH client.
Many of parameters are just passed to the underlying implementation
@@ -59,6 +75,7 @@
:param proxy_client: Another SSH client to provide a transport
for ssh-over-ssh. The default is None, which means
not to use ssh-over-ssh.
+ :param ssh_key_type: ssh key type (rsa, ecdsa)
:type proxy_client: ``tempest.lib.common.ssh.Client`` object
"""
self.host = host
@@ -66,8 +83,15 @@
self.port = port
self.password = password
if isinstance(pkey, str):
- pkey = paramiko.RSAKey.from_private_key(
- io.StringIO(str(pkey)))
+ if ssh_key_type == 'rsa':
+ pkey = paramiko.RSAKey.from_private_key(
+ io.StringIO(str(pkey)))
+ elif ssh_key_type == 'ecdsa':
+ pkey = paramiko.ECDSAKey.from_private_key(
+ io.StringIO(str(pkey)))
+ else:
+ raise exceptions.SSHClientUnsupportedKeyType(
+ key_type=ssh_key_type)
self.pkey = pkey
self.look_for_keys = look_for_keys
self.key_filename = key_filename
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index d84dd28..224f3bf 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -69,7 +69,7 @@
server=None, servers_client=None, ssh_timeout=300,
connect_timeout=60, console_output_enabled=True,
ssh_shell_prologue="set -eu -o pipefail; PATH=$PATH:/sbin;",
- ping_count=1, ping_size=56):
+ ping_count=1, ping_size=56, ssh_key_type='rsa'):
"""Executes commands in a VM over ssh
:param ip_address: IP address to ssh to
@@ -84,6 +84,7 @@
:param ssh_shell_prologue: Shell fragments to use before command
:param ping_count: Number of ping packets
:param ping_size: Packet size for ping packets
+ :param ssh_key_type: ssh key type (rsa, ecdsa)
"""
self.server = server
self.servers_client = servers_client
@@ -92,10 +93,12 @@
self.ssh_shell_prologue = ssh_shell_prologue
self.ping_count = ping_count
self.ping_size = ping_size
+ self.ssh_key_type = ssh_key_type
self.ssh_client = ssh.Client(ip_address, username, password,
ssh_timeout, pkey=pkey,
- channel_timeout=connect_timeout)
+ channel_timeout=connect_timeout,
+ ssh_key_type=ssh_key_type)
@debug_ssh
def exec_command(self, cmd):
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index abe68d2..dd7885e 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -256,6 +256,10 @@
"%(port)s and username: %(username)s as parent")
+class SSHClientUnsupportedKeyType(TempestException):
+ message = ("SSH client: unsupported key type %(key_type)s")
+
+
class UnknownServiceClient(TempestException):
message = "Service clients named %(services)s are not known"
diff --git a/tempest/lib/services/compute/hypervisor_client.py b/tempest/lib/services/compute/hypervisor_client.py
index 1cbfcc3..e237845 100644
--- a/tempest/lib/services/compute/hypervisor_client.py
+++ b/tempest/lib/services/compute/hypervisor_client.py
@@ -13,12 +13,18 @@
# 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 hypervisors as schemav21
from tempest.lib.api_schema.response.compute.v2_28 \
import hypervisors as schemav228
+from tempest.lib.api_schema.response.compute.v2_33 \
+ import hypervisors as schemav233
+from tempest.lib.api_schema.response.compute.v2_53 \
+ import hypervisors as schemav253
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -27,9 +33,11 @@
schema_versions_info = [
{'min': None, 'max': '2.27', 'schema': schemav21},
- {'min': '2.28', 'max': None, 'schema': schemav228}]
+ {'min': '2.28', 'max': '2.32', 'schema': schemav228},
+ {'min': '2.33', 'max': '2.52', 'schema': schemav233},
+ {'min': '2.53', 'max': None, 'schema': schemav253}]
- def list_hypervisors(self, detail=False):
+ def list_hypervisors(self, detail=False, **kwargs):
"""List hypervisors information."""
url = 'os-hypervisors'
schema = self.get_schema(self.schema_versions_info)
@@ -37,14 +45,19 @@
if detail:
url += '/detail'
_schema = schema.list_hypervisors_detail
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
resp, body = self.get(url)
body = json.loads(body)
self.validate_response(_schema, resp, body)
return rest_client.ResponseBody(resp, body)
- def show_hypervisor(self, hypervisor_id):
+ def show_hypervisor(self, hypervisor_id, **kwargs):
"""Display the details of the specified hypervisor."""
+ url = 'os-hypervisors/%s' % hypervisor_id
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
resp, body = self.get('os-hypervisors/%s' % hypervisor_id)
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
index 9d7b7fc..51a4583 100644
--- a/tempest/lib/services/compute/keypairs_client.py
+++ b/tempest/lib/services/compute/keypairs_client.py
@@ -15,6 +15,10 @@
from urllib import parse as urllib
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives import serialization
+
from oslo_serialization import jsonutils as json
from tempest.lib.api_schema.response.compute.v2_1 import keypairs as schemav21
@@ -28,6 +32,12 @@
schema_versions_info = [{'min': None, 'max': '2.1', 'schema': schemav21},
{'min': '2.2', 'max': None, 'schema': schemav22}]
+ def __init__(self, auth_provider, service, region,
+ ssh_key_type='rsa', **kwargs):
+ super(KeyPairsClient, self).__init__(
+ auth_provider, service, region, **kwargs)
+ self.ssh_key_type = ssh_key_type
+
def list_keypairs(self, **params):
"""Lists keypairs that are associated with the account.
@@ -67,12 +77,30 @@
API reference:
https://docs.openstack.org/api-ref/compute/#create-or-import-keypair
"""
+ pkey = None
+ if (self.ssh_key_type == 'ecdsa' and 'public_key' not in kwargs and
+ ('type' not in kwargs or kwargs['type'] == 'ssh')):
+ # create a ecdsa key and pass the public key into the request
+ pkey = ec.generate_private_key(ec.SECP384R1(), default_backend())
+ pubkey = pkey.public_key().public_bytes(
+ encoding=serialization.Encoding.OpenSSH,
+ format=serialization.PublicFormat.OpenSSH)
+ kwargs['public_key'] = pubkey
+
post_body = json.dumps({'keypair': kwargs})
resp, body = self.post("os-keypairs", body=post_body)
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.create_keypair, resp, body)
- return rest_client.ResponseBody(resp, body)
+ resp_body = rest_client.ResponseBody(resp, body)
+ if pkey:
+ # add the privkey to the response as it was generated here
+ privkey = pkey.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=serialization.NoEncryption())
+ resp_body['keypair']['private_key'] = privkey.decode('utf-8')
+ return resp_body
def delete_keypair(self, keypair_name, **params):
"""Deletes a keypair.
diff --git a/tempest/lib/services/compute/quota_classes_client.py b/tempest/lib/services/compute/quota_classes_client.py
index 9b64099..5f220a7 100644
--- a/tempest/lib/services/compute/quota_classes_client.py
+++ b/tempest/lib/services/compute/quota_classes_client.py
@@ -16,20 +16,30 @@
from oslo_serialization import jsonutils as json
from tempest.lib.api_schema.response.compute.v2_1\
- import quota_classes as classes_schema
+ import quota_classes as schema
+from tempest.lib.api_schema.response.compute.v2_50 import quota_classes \
+ as schemav250
+from tempest.lib.api_schema.response.compute.v2_57 import quota_classes \
+ as schemav257
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
class QuotaClassesClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.49', 'schema': schema},
+ {'min': '2.50', 'max': '2.56', 'schema': schemav250},
+ {'min': '2.57', 'max': None, 'schema': schemav257}]
+
def show_quota_class_set(self, quota_class_id):
"""List the quota class set for a quota class."""
url = 'os-quota-class-sets/%s' % quota_class_id
resp, body = self.get(url)
body = json.loads(body)
- self.validate_response(classes_schema.get_quota_class_set, resp, body)
+ _schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(_schema.get_quota_class_set, resp, body)
return rest_client.ResponseBody(resp, body)
def update_quota_class_set(self, quota_class_id, **kwargs):
@@ -45,6 +55,7 @@
post_body)
body = json.loads(body)
- self.validate_response(classes_schema.update_quota_class_set,
+ _schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(_schema.update_quota_class_set,
resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
index 89ad2d9..9895653 100644
--- a/tempest/lib/services/compute/server_groups_client.py
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -20,6 +20,8 @@
as schema
from tempest.lib.api_schema.response.compute.v2_13 import server_groups \
as schemav213
+from tempest.lib.api_schema.response.compute.v2_64 import server_groups \
+ as schemav264
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -28,7 +30,8 @@
schema_versions_info = [
{'min': None, 'max': '2.12', 'schema': schema},
- {'min': '2.13', 'max': None, 'schema': schemav213}]
+ {'min': '2.13', 'max': '2.63', 'schema': schemav213},
+ {'min': '2.64', 'max': None, 'schema': schemav264}]
def create_server_group(self, **kwargs):
"""Create the server group.
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index ed3d4c0..d2bdb6e 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -27,6 +27,7 @@
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
+from tempest.lib.api_schema.response.compute.v2_45 import servers as schemav245
from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247
from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248
from tempest.lib.api_schema.response.compute.v2_51 import servers as schemav251
@@ -39,6 +40,7 @@
from tempest.lib.api_schema.response.compute.v2_70 import servers as schemav270
from tempest.lib.api_schema.response.compute.v2_71 import servers as schemav271
from tempest.lib.api_schema.response.compute.v2_73 import servers as schemav273
+from tempest.lib.api_schema.response.compute.v2_75 import servers as schemav275
from tempest.lib.api_schema.response.compute.v2_79 import servers as schemav279
from tempest.lib.api_schema.response.compute.v2_8 import servers as schemav28
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
@@ -57,7 +59,8 @@
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
- {'min': '2.26', 'max': '2.46', 'schema': schemav226},
+ {'min': '2.26', 'max': '2.44', 'schema': schemav226},
+ {'min': '2.45', 'max': '2.46', 'schema': schemav245},
{'min': '2.47', 'max': '2.47', 'schema': schemav247},
{'min': '2.48', 'max': '2.50', 'schema': schemav248},
{'min': '2.51', 'max': '2.53', 'schema': schemav251},
@@ -68,7 +71,8 @@
{'min': '2.63', 'max': '2.69', 'schema': schemav263},
{'min': '2.70', 'max': '2.70', 'schema': schemav270},
{'min': '2.71', 'max': '2.72', 'schema': schemav271},
- {'min': '2.73', 'max': '2.78', 'schema': schemav273},
+ {'min': '2.73', 'max': '2.74', 'schema': schemav273},
+ {'min': '2.75', 'max': '2.78', 'schema': schemav275},
{'min': '2.79', 'max': None, 'schema': schemav279}]
def __init__(self, auth_provider, service, region,
@@ -235,7 +239,9 @@
API reference:
https://docs.openstack.org/api-ref/compute/#create-server-back-up-createbackup-action
"""
- return self.action(server_id, "createBackup", **kwargs)
+ schema = self.get_schema(self.schema_versions_info)
+ return self.action(server_id, "createBackup",
+ schema.create_backup, **kwargs)
def change_password(self, server_id, **kwargs):
"""Change the root password for the server.
diff --git a/tempest/scenario/test_compute_unified_limits.py b/tempest/scenario/test_compute_unified_limits.py
new file mode 100644
index 0000000..eda6d6f
--- /dev/null
+++ b/tempest/scenario/test_compute_unified_limits.py
@@ -0,0 +1,166 @@
+# Copyright 2021 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.common import utils
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest.scenario import manager
+
+CONF = config.CONF
+
+
+class ComputeProjectQuotaTest(manager.ScenarioTest):
+ """The test base class for compute unified limits tests.
+
+ Dynamic credentials (unique tenants) are created on a per-class basis, so
+ we test different quota limits in separate test classes to prevent a quota
+ limit update in one test class from affecting a test running in another
+ test class in parallel.
+
+ https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials
+ """
+ credentials = ['primary', 'system_admin']
+ force_tenant_isolation = True
+
+ @classmethod
+ def skip_checks(cls):
+ super(ComputeProjectQuotaTest, cls).skip_checks()
+ if not CONF.compute_feature_enabled.unified_limits:
+ raise cls.skipException('Compute unified limits are not enabled.')
+
+ @classmethod
+ def resource_setup(cls):
+ super(ComputeProjectQuotaTest, cls).resource_setup()
+
+ # Figure out and record the nova service id
+ services = cls.os_system_admin.identity_services_v3_client.\
+ list_services()
+ nova_services = [x for x in services['services']
+ if x['name'] == 'nova']
+ cls.nova_service_id = nova_services[0]['id']
+
+ # Pre-create quota limits in subclasses and record their IDs so we can
+ # update them in-place without needing to know which ones have been
+ # created and in which order.
+ cls.limit_ids = {}
+
+ @classmethod
+ def _create_limit(cls, name, value):
+ return cls.os_system_admin.identity_limits_client.create_limit(
+ CONF.identity.region, cls.nova_service_id,
+ cls.servers_client.tenant_id, name, value)['limits'][0]['id']
+
+ def _update_limit(self, name, value):
+ self.os_system_admin.identity_limits_client.update_limit(
+ self.limit_ids[name], value)
+
+
+class ServersQuotaTest(ComputeProjectQuotaTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(ServersQuotaTest, cls).resource_setup()
+
+ try:
+ cls.limit_ids['servers'] = cls._create_limit(
+ 'servers', 5)
+ cls.limit_ids['class:VCPU'] = cls._create_limit(
+ 'class:VCPU', 10)
+ cls.limit_ids['class:MEMORY_MB'] = cls._create_limit(
+ 'class:MEMORY_MB', 25 * 1024)
+ cls.limit_ids['class:DISK_GB'] = cls._create_limit(
+ 'class:DISK_GB', 10)
+ except lib_exc.Forbidden:
+ raise cls.skipException('Target system is not configured with '
+ 'compute unified limits')
+
+ @decorators.idempotent_id('555d8bbf-d2ed-4e39-858c-4235899402d9')
+ @utils.services('compute')
+ def test_server_count_vcpu_memory_disk_quota(self):
+ # Set a quota on the number of servers for our tenant to one.
+ self._update_limit('servers', 1)
+
+ # Create one server.
+ first = self.create_server(name='first')
+
+ # Second server would put us over quota, so expect failure.
+ # NOTE: In nova, quota exceeded raises 403 Forbidden.
+ self.assertRaises(lib_exc.Forbidden,
+ self.create_server,
+ name='second')
+
+ # Update our limit to two.
+ self._update_limit('servers', 2)
+
+ # Now the same create should succeed.
+ second = self.create_server(name='second')
+
+ # Third server would put us over quota, so expect failure.
+ self.assertRaises(lib_exc.Forbidden,
+ self.create_server,
+ name='third')
+
+ # Delete the first server to put us under quota.
+ self.servers_client.delete_server(first['id'])
+ waiters.wait_for_server_termination(self.servers_client, first['id'])
+
+ # Now the same create should succeed.
+ third = self.create_server(name='third')
+
+ # Set the servers limit back to 10 to test other resources.
+ self._update_limit('servers', 10)
+
+ # Default flavor has: VCPU=1, MEMORY_MB=512, DISK_GB=1
+ # We are currently using 2 VCPU, set the limit to 2.
+ self._update_limit('class:VCPU', 2)
+
+ # Server create should fail as it would go over quota.
+ self.assertRaises(lib_exc.Forbidden,
+ self.create_server,
+ name='fourth')
+
+ # Delete the second server to put us under quota.
+ self.servers_client.delete_server(second['id'])
+ waiters.wait_for_server_termination(self.servers_client, second['id'])
+
+ # Same create should now succeed.
+ fourth = self.create_server(name='fourth')
+
+ # We are currently using 2 DISK_GB. Set limit to 1.
+ self._update_limit('class:DISK_GB', 1)
+
+ # Server create should fail because we're already over (new) quota.
+ self.assertRaises(lib_exc.Forbidden,
+ self.create_server,
+ name='fifth')
+
+ # Delete the third server.
+ self.servers_client.delete_server(third['id'])
+ waiters.wait_for_server_termination(self.servers_client, third['id'])
+
+ # Server create should fail again because it would still put us over
+ # quota.
+ self.assertRaises(lib_exc.Forbidden,
+ self.create_server,
+ name='fifth')
+
+ # Delete the fourth server.
+ self.servers_client.delete_server(fourth['id'])
+ waiters.wait_for_server_termination(self.servers_client, fourth['id'])
+
+ # Server create should succeed now.
+ self.create_server(name='fifth')
diff --git a/tempest/scenario/test_network_qos_placement.py b/tempest/scenario/test_network_qos_placement.py
index a8e9174..365eb1b 100644
--- a/tempest/scenario/test_network_qos_placement.py
+++ b/tempest/scenario/test_network_qos_placement.py
@@ -49,8 +49,10 @@
compute_max_microversion = 'latest'
INGRESS_DIRECTION = 'ingress'
+ EGRESS_DIRECTION = 'egress'
ANY_DIRECTION = 'any'
- BW_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC"
+ INGRESS_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC"
+ EGRESS_RESOURCE_CLASS = "NET_BW_EGR_KILOBIT_PER_SEC"
# For any realistic inventory value (that is inventory != MAX_INT) an
# allocation candidate request of MAX_INT is expected to be rejected, see:
@@ -77,7 +79,7 @@
new_flavor = self.flavors_client.create_flavor(**{
'ram': old_flavor['ram'],
'vcpus': old_flavor['vcpus'],
- 'name': old_flavor['name'] + 'extra',
+ 'name': old_flavor['name'] + 'extra-%s' % data_utils.rand_int_id(),
'disk': old_flavor['disk'] + 1
})['flavor']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
@@ -107,7 +109,9 @@
super(MinBwAllocationPlacementTest, self).setUp()
self._check_if_allocation_is_possible()
- def _create_policy_and_min_bw_rule(self, name_prefix, min_kbps):
+ def _create_policy_and_min_bw_rule(
+ self, name_prefix, min_kbps, direction="ingress"
+ ):
policy = self.qos_client.create_qos_policy(
name=data_utils.rand_name(name_prefix),
shared=True)['policy']
@@ -117,7 +121,7 @@
policy['id'],
**{
'min_kbps': min_kbps,
- 'direction': self.INGRESS_DIRECTION
+ 'direction': direction,
})['minimum_bandwidth_rule']
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
@@ -167,20 +171,20 @@
def _check_if_allocation_is_possible(self):
alloc_candidates = self.placement_client.list_allocation_candidates(
- resources1='%s:%s' % (self.BW_RESOURCE_CLASS,
+ resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
self.SMALLEST_POSSIBLE_BW))
if len(alloc_candidates['provider_summaries']) == 0:
self.fail('No allocation candidates are available for %s:%s' %
- (self.BW_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
+ (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW))
# Just to be sure check with impossible high (placement max_int),
# allocation
alloc_candidates = self.placement_client.list_allocation_candidates(
- resources1='%s:%s' % (self.BW_RESOURCE_CLASS,
+ resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS,
self.PLACEMENT_MAX_INT))
if len(alloc_candidates['provider_summaries']) != 0:
self.fail('For %s:%s there should be no available candidate!' %
- (self.BW_RESOURCE_CLASS, self.PLACEMENT_MAX_INT))
+ (self.INGRESS_RESOURCE_CLASS, self.PLACEMENT_MAX_INT))
def _boot_vm_with_min_bw(self, qos_policy_id, status='ACTIVE'):
wait_until = (None if status == 'ERROR' else status)
@@ -194,22 +198,28 @@
status=status, ready_wait=False, raise_on_error=False)
return server, port
- def _assert_allocation_is_as_expected(self, consumer, port_ids,
- min_kbps=SMALLEST_POSSIBLE_BW):
+ def _assert_allocation_is_as_expected(
+ self, consumer, port_ids, min_kbps=SMALLEST_POSSIBLE_BW,
+ expected_rc=NetworkQoSPlacementTestBase.INGRESS_RESOURCE_CLASS,
+ ):
allocations = self.placement_client.list_allocations(
consumer)['allocations']
self.assertGreater(len(allocations), 0)
bw_resource_in_alloc = False
allocation_rp = None
for rp, resources in allocations.items():
- if self.BW_RESOURCE_CLASS in resources['resources']:
+ if expected_rc in resources['resources']:
self.assertEqual(
min_kbps,
- resources['resources'][self.BW_RESOURCE_CLASS])
+ resources['resources'][expected_rc])
bw_resource_in_alloc = True
allocation_rp = rp
if min_kbps:
- self.assertTrue(bw_resource_in_alloc)
+ self.assertTrue(
+ bw_resource_in_alloc,
+ f"expected {min_kbps} bandwidth allocation from {expected_rc} "
+ f"but instance has allocation {allocations} instead."
+ )
# Check binding_profile of the port is not empty and equals with
# the rp uuid
@@ -510,6 +520,60 @@
self._assert_allocation_is_as_expected(server1['id'], [port['id']],
self.BANDWIDTH_1)
+ @decorators.idempotent_id('372b2728-cfed-469a-b5f6-b75779e1ccbe')
+ @utils.services('compute', 'network')
+ def test_qos_min_bw_allocation_update_policy_direction_change(self):
+ """Test QoS min bw direction change on a bound port
+
+ Related RFE in neutron: #1882804
+ The scenario is the following:
+ * Have a port with QoS policy and minimum bandwidth rule with ingress
+ direction
+ * Boot a VM with the port.
+ * Update the port with a new policy to egress direction in
+ minimum bandwidth rule.
+ * The allocation on placement side should be according to the new
+ rules.
+ """
+ if not utils.is_network_feature_enabled('update_port_qos'):
+ raise self.skipException("update_port_qos feature is not enabled")
+
+ def create_policies():
+ self.qos_policy_ingress = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_ingress',
+ min_kbps=self.BANDWIDTH_1,
+ direction=self.INGRESS_DIRECTION,
+ )
+ self.qos_policy_egress = self._create_policy_and_min_bw_rule(
+ name_prefix='test_policy_egress',
+ min_kbps=self.BANDWIDTH_1,
+ direction=self.EGRESS_DIRECTION,
+ )
+
+ self._create_network_and_qos_policies(create_policies)
+
+ port = self.create_port(
+ self.prov_network['id'],
+ qos_policy_id=self.qos_policy_ingress['id'])
+
+ server1 = self.create_server(
+ networks=[{'port': port['id']}])
+
+ self._assert_allocation_is_as_expected(
+ server1['id'], [port['id']], self.BANDWIDTH_1,
+ expected_rc=self.INGRESS_RESOURCE_CLASS)
+
+ self.ports_client.update_port(
+ port['id'],
+ qos_policy_id=self.qos_policy_egress['id'])
+
+ self._assert_allocation_is_as_expected(
+ server1['id'], [port['id']], self.BANDWIDTH_1,
+ expected_rc=self.EGRESS_RESOURCE_CLASS)
+ self._assert_allocation_is_as_expected(
+ server1['id'], [port['id']], 0,
+ expected_rc=self.INGRESS_RESOURCE_CLASS)
+
class QoSBandwidthAndPacketRateTests(NetworkQoSPlacementTestBase):
@@ -625,9 +689,9 @@
if expected_min_kbps > 0:
bw_rp_allocs = {
- rp: alloc['resources'][self.BW_RESOURCE_CLASS]
+ rp: alloc['resources'][self.INGRESS_RESOURCE_CLASS]
for rp, alloc in allocations.items()
- if self.BW_RESOURCE_CLASS in alloc['resources']
+ if self.INGRESS_RESOURCE_CLASS in alloc['resources']
}
self.assertEqual(1, len(bw_rp_allocs))
bw_rp, bw_alloc = list(bw_rp_allocs.items())[0]
diff --git a/tempest/test.py b/tempest/test.py
index bf0aba4..dba2695 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -26,7 +26,6 @@
from tempest.common import credentials_factory as credentials
from tempest.common import utils
from tempest import config
-from tempest.lib import base as lib_base
from tempest.lib.common import api_microversion_fixture
from tempest.lib.common import fixed_network
from tempest.lib.common import profiler
@@ -142,19 +141,6 @@
# It should never be overridden by descendants
if hasattr(super(BaseTestCase, cls), 'setUpClass'):
super(BaseTestCase, cls).setUpClass()
- # All the configuration checks that may generate a skip
- # TODO(gmann): cls.handle_skip_exception is really workaround for
- # testtools bug- https://github.com/testing-cabal/testtools/issues/272
- # stestr which is used by Tempest internally to run the test switch
- # the customize test runner(which use stdlib unittest) for >=py3.5
- # else testtools.run.- https://github.com/mtreinish/stestr/pull/265
- # These two test runner are not compatible due to skip exception
- # handling(due to unittest2). testtools.run treat unittestt.SkipTest
- # as error and stdlib unittest treat unittest2.case.SkipTest raised
- # by testtools.TestCase.skipException.
- # The below workaround can be removed once testtools fix issue# 272.
- orig_skip_exception = testtools.TestCase.skipException
- lib_base._handle_skip_exception()
try:
cls.skip_checks()
@@ -182,8 +168,6 @@
raise value.with_traceback(trace)
finally:
del trace # to avoid circular refs
- finally:
- testtools.TestCase.skipException = orig_skip_exception
@classmethod
def tearDownClass(cls):
diff --git a/tempest/test_discover/test_discover.py b/tempest/test_discover/test_discover.py
index 5816ab1..a19f20b 100644
--- a/tempest/test_discover/test_discover.py
+++ b/tempest/test_discover/test_discover.py
@@ -13,15 +13,10 @@
# under the License.
import os
-import sys
+import unittest
from tempest.test_discover import plugins
-if sys.version_info >= (2, 7):
- import unittest
-else:
- import unittest2 as unittest
-
def load_tests(loader, tests, pattern):
ext_plugins = plugins.TempestTestPluginManager()
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index b76a263..1d0ee77 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -276,36 +276,6 @@
)
sleep.assert_called_once_with(client.build_interval)
- def test_wait_for_guest_os_boot(self):
- get_console_output = mock.Mock(
- side_effect=[
- {'output': 'os not ready yet\n'},
- {'output': 'login:\n'}
- ])
- client = self.mock_client(get_console_output=get_console_output)
- self.patch('time.time', return_value=0.)
- sleep = self.patch('time.sleep')
-
- with mock.patch.object(waiters.LOG, "info") as log_info:
- waiters.wait_for_guest_os_boot(client, 'server_id')
-
- get_console_output.assert_has_calls([
- mock.call('server_id'), mock.call('server_id')])
- sleep.assert_called_once_with(client.build_interval)
- log_info.assert_not_called()
-
- def test_wait_for_guest_os_boot_timeout(self):
- get_console_output = mock.Mock(
- return_value={'output': 'os not ready yet\n'})
- client = self.mock_client(get_console_output=get_console_output)
- self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
- self.patch('time.sleep')
-
- with mock.patch.object(waiters.LOG, "info") as log_info:
- waiters.wait_for_guest_os_boot(client, 'server_id')
-
- log_info.assert_called_once()
-
class TestVolumeWaiters(base.TestCase):
vol_migrating_src_host = {
@@ -553,6 +523,70 @@
mock_list_volume_attachments.assert_called_once_with(
mock.sentinel.server_id)
+ @mock.patch('os.system')
+ def test_wait_for_ping_host_alive(self, mock_ping):
+ mock_ping.return_value = 0
+ # Assert that nothing is raised as the host is alive
+ waiters.wait_for_ping('127.0.0.1', 10, 1)
+
+ @mock.patch('os.system')
+ def test_wait_for_ping_host_eventually_alive(self, mock_ping):
+ mock_ping.side_effect = [1, 1, 0]
+ # Assert that nothing is raised when the host is eventually alive
+ waiters.wait_for_ping('127.0.0.1', 10, 1)
+
+ @mock.patch('os.system')
+ def test_wait_for_ping_timeout(self, mock_ping):
+ mock_ping.return_value = 1
+ # Assert that TimeoutException is raised when the host is dead
+ self.assertRaises(
+ lib_exc.TimeoutException,
+ waiters.wait_for_ping,
+ '127.0.0.1',
+ .1,
+ .1
+ )
+
+ def test_wait_for_ssh(self):
+ mock_ssh_client = mock.Mock()
+ mock_ssh_client.validate_authentication.return_value = True
+ # Assert that nothing is raised when validate_authentication returns
+ waiters.wait_for_ssh(mock_ssh_client, .1)
+ mock_ssh_client.validate_authentication.assert_called_once()
+
+ def test_wait_for_ssh_eventually_up(self):
+ mock_ssh_client = mock.Mock()
+ timeout = lib_exc.SSHTimeout(
+ host='foo',
+ username='bar',
+ password='fizz'
+ )
+ mock_ssh_client.validate_authentication.side_effect = [
+ timeout,
+ timeout,
+ True
+ ]
+ # Assert that nothing is raised if validate_authentication passes
+ # before the timeout
+ waiters.wait_for_ssh(mock_ssh_client, 10)
+
+ def test_wait_for_ssh_timeout(self):
+ mock_ssh_client = mock.Mock()
+ timeout = lib_exc.SSHTimeout(
+ host='foo',
+ username='bar',
+ password='fizz'
+ )
+ mock_ssh_client.validate_authentication.side_effect = timeout
+ # Assert that TimeoutException is raised when validate_authentication
+ # doesn't pass in time.
+ self.assertRaises(
+ lib_exc.TimeoutException,
+ waiters.wait_for_ssh,
+ mock_ssh_client,
+ .1
+ )
+
class TestServerFloatingIPWaiters(base.TestCase):
diff --git a/tempest/tests/lib/services/image/v2/test_schemas_client.py b/tempest/tests/lib/services/image/v2/test_schemas_client.py
index 4c4b86a..9fb249b 100644
--- a/tempest/tests/lib/services/image/v2/test_schemas_client.py
+++ b/tempest/tests/lib/services/image/v2/test_schemas_client.py
@@ -75,12 +75,323 @@
}
}
+ FAKE_SHOW_SCHEMA_IMAGE = {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "links": [
+ {
+ "href": "{self}",
+ "rel": "self"
+ },
+ {
+ "href": "{file}",
+ "rel": "enclosure"
+ },
+ {
+ "href": "{schema}",
+ "rel": "describedby"
+ }
+ ],
+ "name": "image",
+ "properties": {
+ "architecture": {
+ "description": "Operating system architecture as "
+ "specified in https://docs.openstack.org/"
+ "python-glanceclient/latest/cli"
+ "/property-keys.html",
+ "is_base": False,
+ "type": "string"
+ },
+ "checksum": {
+ "description": "md5 hash of image contents.",
+ "maxLength": 32,
+ "readOnly": True,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "container_format": {
+ "description": "Format of the container",
+ "enum": [
+ None,
+ "ami",
+ "ari",
+ "aki",
+ "bare",
+ "ovf",
+ "ova",
+ "docker"
+ ],
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "created_at": {
+ "description": "Date and time of image registration",
+ "readOnly": True,
+ "type": "string"
+ },
+ "direct_url": {
+ "description": "URL to access the image file "
+ "kept in external store",
+ "readOnly": True,
+ "type": "string"
+ },
+ "disk_format": {
+ "description": "Format of the disk",
+ "enum": [
+ None,
+ "ami",
+ "ari",
+ "aki",
+ "vhd",
+ "vhdx",
+ "vmdk",
+ "raw",
+ "qcow2",
+ "vdi",
+ "iso",
+ "ploop"
+ ],
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "file": {
+ "description": "An image file url",
+ "readOnly": True,
+ "type": "string"
+ },
+ "id": {
+ "description": "An identifier for the image",
+ "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F])"
+ "{4}-([0-9a-fA-F]){4}-([0-9a-fA-F])"
+ "{4}-([0-9a-fA-F]){12}$",
+ "type": "string"
+ },
+ "instance_uuid": {
+ "description": "Metadata which can be used to record which"
+ " instance this image is associated with. "
+ "(Informational only, does not create "
+ "an instance snapshot.)",
+ "is_base": False,
+ "type": "string"
+ },
+ "kernel_id": {
+ "description": "ID of image stored in Glance that should "
+ "be used as the kernel when booting an "
+ "AMI-style image.",
+ "is_base": False,
+ "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-"
+ "([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-("
+ "[0-9a-fA-F]){12}$",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "locations": {
+ "description": "A set of URLs to access the image file "
+ "kept in external store",
+ "items": {
+ "properties": {
+ "metadata": {
+ "type": "object"
+ },
+ "url": {
+ "maxLength": 255,
+ "type": "string"
+ }
+ },
+ "required": [
+ "url",
+ "metadata"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "min_disk": {
+ "description": "Amount of disk space (in GB) "
+ "required to boot image.",
+ "type": "integer"
+ },
+ "min_ram": {
+ "description": "Amount of ram (in MB) required "
+ "to boot image.",
+ "type": "integer"
+ },
+ "name": {
+ "description": "Descriptive name for the image",
+ "maxLength": 255,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "os_distro": {
+ "description": "Common name of operating system distribution "
+ "as specified in https://docs.openstack.org/"
+ "python-glanceclient/latest/cli/"
+ "property-keys.html",
+ "is_base": False,
+ "type": "string"
+ },
+ "os_hash_algo": {
+ "description": "Algorithm to calculate the os_hash_value",
+ "maxLength": 64,
+ "readOnly": True,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "os_hash_value": {
+ "description": "Hexdigest of the image contents "
+ "using the algorithm specified by "
+ "the os_hash_algo",
+ "maxLength": 128,
+ "readOnly": True,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "os_hidden": {
+ "description": "If true, image will not appear in default"
+ " image list response.",
+ "type": "boolean"
+ },
+ "os_version": {
+ "description": "Operating system version as specified by "
+ "the distributor",
+ "is_base": False,
+ "type": "string"
+ },
+ "owner": {
+ "description": "Owner of the image",
+ "maxLength": 255,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "protected": {
+ "description": "If true, image will not be deletable.",
+ "type": "boolean"
+ },
+ "ramdisk_id": {
+ "description": "ID of image stored in Glance that should"
+ " be used as the ramdisk when booting an "
+ "AMI-style image.",
+ "is_base": False,
+ "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])"
+ "{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "schema": {
+ "description": "An image schema url",
+ "readOnly": True,
+ "type": "string"
+ },
+ "self": {
+ "description": "An image self url",
+ "readOnly": True,
+ "type": "string"
+ },
+ "size": {
+ "description": "Size of image file in bytes",
+ "readOnly": True,
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "status": {
+ "description": "Status of the image",
+ "enum": [
+ "queued",
+ "saving",
+ "active",
+ "killed",
+ "deleted",
+ "pending_delete",
+ "deactivated",
+ "uploading",
+ "importing"
+ ],
+ "readOnly": True,
+ "type": "string"
+ },
+ "tags": {
+ "description": "List of strings related to the image",
+ "items": {
+ "maxLength": 255,
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "updated_at": {
+ "description": "Date and time of the last image modification",
+ "readOnly": True,
+ "type": "string"
+ },
+ "virtual_size": {
+ "description": "Virtual size of image in bytes",
+ "readOnly": True,
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "visibility": {
+ "description": "Scope of image accessibility",
+ "enum": [
+ "public",
+ "private"
+ ],
+ "type": "string"
+ }
+ }
+ }
+
def setUp(self):
super(TestSchemasClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = schemas_client.SchemasClient(fake_auth,
'image', 'regionOne')
+ def _test_show_schema_members(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_schema,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_SCHEMA,
+ bytes_body,
+ schema="members")
+
+ def _test_show_schema_image(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_schema,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_SCHEMA_IMAGE,
+ bytes_body,
+ schema="image")
+
+ def _test_show_schema_images(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_schema,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_SCHEMA_IMAGE,
+ bytes_body,
+ schema="images")
+
def _test_show_schema(self, bytes_body=False):
self.check_service_client_function(
self.client.show_schema,
@@ -89,6 +400,24 @@
bytes_body,
schema="member")
+ def test_show_schema_members_with_str_body(self):
+ self._test_show_schema_members()
+
+ def test_show_schema_members_with_bytes_body(self):
+ self._test_show_schema_members(bytes_body=True)
+
+ def test_show_schema_image_with_str_body(self):
+ self._test_show_schema_image()
+
+ def test_show_schema_image_with_bytes_body(self):
+ self._test_show_schema_image(bytes_body=True)
+
+ def test_show_schema_images_with_str_body(self):
+ self._test_show_schema_images()
+
+ def test_show_schema_images_with_bytes_body(self):
+ self._test_show_schema_images(bytes_body=True)
+
def test_show_schema_with_str_body(self):
self._test_show_schema()
diff --git a/tempest/tests/lib/test_base.py b/tempest/tests/lib/test_base.py
index 2c16e1c..de6021c 100644
--- a/tempest/tests/lib/test_base.py
+++ b/tempest/tests/lib/test_base.py
@@ -48,7 +48,7 @@
@classmethod
def setUpClass(cls): # noqa
"""Simulate absence of super() call."""
- cls.orig_skip_exception = cls.skipException
+ pass
def setUp(self):
try:
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index 7c31185..464e66a 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -240,3 +240,9 @@
with_other_decorators=True,
with_negative_decorator=False,
expected_success=False)
+
+ def test_no_log_warn(self):
+ self.assertFalse(list(checks.no_log_warn(
+ 'LOG.warning("LOG.warn is deprecated")')))
+ self.assertTrue(list(checks.no_log_warn(
+ 'LOG.warn("LOG.warn is deprecated")')))
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index a95914a..cbb81e2 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -14,7 +14,7 @@
# under the License.
import os
-import sys
+import unittest
from unittest import mock
from oslo_config import cfg
@@ -34,12 +34,6 @@
from tempest.tests.lib.services import registry_fixture
-if sys.version_info >= (2, 7):
- import unittest
-else:
- import unittest2 as unittest
-
-
class LoggingTestResult(testtools.TestResult):
def __init__(self, log, *args, **kwargs):
diff --git a/tox.ini b/tox.ini
index 18f2aa6..b07fdaf 100644
--- a/tox.ini
+++ b/tox.ini
@@ -369,6 +369,7 @@
T115 = checks:dont_put_admin_tests_on_nonadmin_path
T116 = checks:unsupported_exception_attribute_PY3
T117 = checks:negative_test_attribute_always_applied_to_negative_tests
+ T118 = checks:no_log_warn
paths =
./tempest/hacking
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index b86268a..d35e25d 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -131,6 +131,8 @@
- job:
name: tempest-integrated-compute-centos-8-stream
parent: tempest-integrated-compute
+ # TODO(gmann): Make this job non voting until bug#1957941 if fixed.
+ voting: false
nodeset: devstack-single-node-centos-8-stream
branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri|victoria)).*$
description: |
@@ -296,6 +298,22 @@
TEMPEST_VOLUME_TYPE: volumev2
- job:
+ name: tempest-centos8-stream-fips
+ parent: devstack-tempest
+ 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
+ devstack_local_conf:
+ test-config:
+ "$TEMPEST_CONFIG":
+ validation:
+ ssh_key_type: 'ecdsa'
+
+- job:
name: tempest-pg-full
parent: tempest-full-py3
description: |
@@ -317,6 +335,8 @@
check:
jobs:
- grenade
+ - grenade-skip-level:
+ voting: false
- tempest-integrated-networking
- openstacksdk-functional-devstack
gate:
@@ -334,6 +354,8 @@
run on Nova gate only.
check:
jobs:
+ - grenade-skip-level:
+ voting: false
- tempest-integrated-compute
- tempest-integrated-compute-centos-8-stream
- openstacksdk-functional-devstack
@@ -353,6 +375,8 @@
check:
jobs:
- grenade
+ - grenade-skip-level:
+ voting: false
- tempest-integrated-placement
- openstacksdk-functional-devstack
gate:
@@ -371,6 +395,8 @@
check:
jobs:
- grenade
+ - grenade-skip-level:
+ voting: false
- tempest-integrated-storage
- openstacksdk-functional-devstack
gate:
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 9ab10d7..f687cb1 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -35,6 +35,8 @@
- glance-multistore-cinder-import:
voting: false
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-yoga:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-full-xena:
irrelevant-files: *tempest-irrelevant-files
- tempest-full-wallaby-py3:
@@ -122,6 +124,8 @@
irrelevant-files: *tempest-irrelevant-files-2
- tempest-full-py3-centos-8-stream:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-centos-9-stream:
+ irrelevant-files: *tempest-irrelevant-files
gate:
jobs:
- openstack-tox-pep8
@@ -143,6 +147,8 @@
irrelevant-files: *tempest-irrelevant-files-3
- devstack-plugin-ceph-tempest-py3:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-centos-9-stream:
+ irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
- tempest-with-latest-microversion
@@ -159,10 +165,11 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-pg-full:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-py3-opensuse15:
+ - tempest-centos8-stream-fips:
irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
+ - tempest-full-yoga
- tempest-full-xena
- tempest-full-wallaby-py3
- tempest-full-victoria-py3
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index da6cc46..f3b5716 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -1,5 +1,10 @@
# NOTE(gmann): This file includes all stable release jobs definition.
- job:
+ name: tempest-full-yoga
+ parent: tempest-full-py3
+ override-checkout: stable/yoga
+
+- job:
name: tempest-full-xena
parent: tempest-full-py3
override-checkout: stable/xena
@@ -178,3 +183,24 @@
subnode:
devstack_localrc:
USE_PYTHON3: true
+
+- job:
+ name: tempest-full-py3-opensuse15
+ parent: tempest-full-py3
+ nodeset: devstack-single-node-opensuse-15
+ description: |
+ Base integration test with Neutron networking and py36 running
+ on openSUSE Leap 15.x
+ voting: false
+ # 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
+ - stable/victoria
+ - stable/wallaby
+ - stable/xena
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index 051d8b0..a4a4b67 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -69,17 +69,10 @@
c-bak: false
- job:
- name: tempest-full-py3-opensuse15
- parent: tempest-full-py3
- nodeset: devstack-single-node-opensuse-15
- description: |
- Base integration test with Neutron networking and py36 running
- on openSUSE Leap 15.x
- voting: false
-
-- job:
name: tempest-full-py3-centos-8-stream
parent: tempest-full-py3
+ # TODO(gmann): Make this job non voting until bug#1957941 if fixed.
+ voting: false
nodeset: devstack-single-node-centos-8-stream
description: |
Base integration test with Neutron networking and py36 running
@@ -90,6 +83,12 @@
configure_swap_size: 4096
- job:
+ name: tempest-full-centos-9-stream
+ parent: tempest-full-py3-centos-8-stream
+ voting: true
+ nodeset: devstack-single-node-centos-9-stream
+
+- job:
name: tempest-tox-plugin-sanity-check
parent: tox
description: |