Merge "Update aggregate tests for bug#1907775"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 0b80b72..ecf2930 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
@@ -428,7 +432,11 @@
* `2.79`_
- .. _2.79: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-train
+ .. _2.79: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-train
+
+ * `2.86`_
+
+ .. _2.86: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id79
* Volume
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/deprecate-old-api-microversion-fixture-a471aac985c0f3fb.yaml b/releasenotes/notes/deprecate-old-api-microversion-fixture-a471aac985c0f3fb.yaml
new file mode 100644
index 0000000..652f7fa
--- /dev/null
+++ b/releasenotes/notes/deprecate-old-api-microversion-fixture-a471aac985c0f3fb.yaml
@@ -0,0 +1,7 @@
+---
+deprecations:
+ - |
+ Old APIMicroversionFixture classes ``tempest.api.compute.api_microversion_fixture.APIMicroversionFixture``
+ and ``tempest.api.volume.api_microversion_fixture.APIMicroversionFixture``
+ has been deprecated for removal in favor of new location
+ ``tempest.lib.common.api_microversion_fixture.APIMicroversionFixture``
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/requirements.txt b/requirements.txt
index c71cabe..bc8358b 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
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_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 4c531b3..10018fe 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -127,3 +127,34 @@
self.flavor['id'], 'hw:numa_nodes')
self.assertEqual(body['hw:numa_nodes'], '1')
self.assertNotIn('hw:cpu_policy', body)
+
+
+class FlavorMetadataValidation(base.BaseV2ComputeAdminTest):
+
+ min_microversion = '2.86'
+
+ @classmethod
+ def resource_setup(cls):
+ super(FlavorMetadataValidation, cls).resource_setup()
+ cls.flavor_name_prefix = 'test_flavor_validate_metadata_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+ cls.ephemeral = 10
+ cls.swap = 1024
+ cls.rxtx = 2
+
+ @decorators.idempotent_id('d3114f03-b0f2-4dc7-be11-70c0abc178b3')
+ def test_flavor_update_with_custom_namespace(self):
+ """Test flavor creation with a custom namespace, key and value"""
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ flavor_id = self.create_flavor(ram=self.ram,
+ vcpus=self.vcpus,
+ disk=self.disk,
+ name=flavor_name)['id']
+ specs = {'hw:cpu_policy': 'shared', 'foo:bar': 'baz'}
+ body = self.admin_flavors_client.set_flavor_extra_spec(
+ flavor_id,
+ **specs)['extra_specs']
+ self.assertEqual(body['foo:bar'], 'baz')
+ self.assertEqual(body['hw:cpu_policy'], 'shared')
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/api_microversion_fixture.py b/tempest/api/compute/api_microversion_fixture.py
index 695af52..1f55a65 100644
--- a/tempest/api/compute/api_microversion_fixture.py
+++ b/tempest/api/compute/api_microversion_fixture.py
@@ -13,14 +13,23 @@
# under the License.
import fixtures
+from oslo_log import log as logging
from tempest.lib.services.compute import base_compute_client
+LOG = logging.getLogger(__name__)
+
class APIMicroversionFixture(fixtures.Fixture):
def __init__(self, compute_microversion):
self.compute_microversion = compute_microversion
+ new_fixture = (
+ 'tempest.lib.common.api_microversion_fixture.'
+ 'APIMicroversionFixture')
+ LOG.warning("%s class is deprecated and moved to %s. It"
+ " will be removed in Z cycle.",
+ self.__class__.__name__, new_fixture)
def _setUp(self):
super(APIMicroversionFixture, self)._setUp()
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/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index ddfc78a..73903cf 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -256,7 +256,6 @@
volume_max_microversion = 'latest'
@decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
- @decorators.skip_because(bug='1770179')
def test_reset_group_snapshot_status(self):
"""Test resetting group snapshot status to creating/available/error"""
# Create volume type
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/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
index 7bbe674..219fde8 100644
--- a/tempest/api/volume/api_microversion_fixture.py
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -12,14 +12,23 @@
# under the License.
import fixtures
+from oslo_log import log as logging
from tempest.lib.services.volume import base_client
+LOG = logging.getLogger(__name__)
+
class APIMicroversionFixture(fixtures.Fixture):
def __init__(self, volume_microversion):
self.volume_microversion = volume_microversion
+ new_fixture = (
+ 'tempest.lib.common.api_microversion_fixture.'
+ 'APIMicroversionFixture')
+ LOG.warning("%s class is deprecated and moved to %s. It"
+ " will be removed in Z cycle.",
+ self.__class__.__name__, new_fixture)
def _setUp(self):
super(APIMicroversionFixture, self)._setUp()
diff --git a/tempest/clients.py b/tempest/clients.py
index 3d799c5..4c3d875 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -74,6 +74,8 @@
self.qos_client = self.network.QosClient()
self.qos_min_bw_client = self.network.QosMinimumBandwidthRulesClient()
self.qos_limit_bw_client = self.network.QosLimitBandwidthRulesClient()
+ self.qos_min_pps_client = (
+ self.network.QosMinimumPacketRateRulesClient())
self.segments_client = self.network.SegmentsClient()
self.trunks_client = self.network.TrunksClient()
self.log_resource_client = self.network.LogResourceClient()
@@ -116,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/common/compute.py b/tempest/common/compute.py
index a062f6f..2443a67 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -197,6 +197,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:
@@ -234,7 +235,8 @@
for server in servers:
try:
waiters.wait_for_server_status(
- clients.servers_client, server['id'], wait_until)
+ clients.servers_client, server['id'], wait_until,
+ request_id=request_id)
# Multiple validatable servers are not supported for now. Their
# creation will fail with the condition above.
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..21d0109 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -32,7 +32,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 +72,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 +90,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()
diff --git a/tempest/config.py b/tempest/config.py
index 662a249..03ddbf5 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -875,7 +875,10 @@
'bandwidth allocation.'),
cfg.StrOpt('provider_net_base_segmentation_id', default=3000,
help='Base segmentation ID to create provider networks. '
- 'This value will be increased in case of conflict.')
+ 'This value will be increased in case of conflict.'),
+ cfg.BoolOpt('qos_min_bw_and_pps', default=False,
+ help='Does the test environment have minimum bandwidth and '
+ 'packet rate inventories configured?'),
]
dashboard_group = cfg.OptGroup(name="dashboard",
@@ -967,6 +970,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/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_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/volume/volumes.py b/tempest/lib/api_schema/response/volume/volumes.py
index ffcf488..4f44526 100644
--- a/tempest/lib/api_schema/response/volume/volumes.py
+++ b/tempest/lib/api_schema/response/volume/volumes.py
@@ -21,7 +21,7 @@
'items': {
'type': 'object',
'properties': {
- 'server_id': {'type': 'string', 'format': 'uuid'},
+ 'server_id': {'type': ['string', 'null'], 'format': 'uuid'},
'attachment_id': {'type': 'string', 'format': 'uuid'},
'attached_at': parameter_types.date_time_or_null,
'host_name': {'type': ['string', 'null']},
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 0ae11ca..466222d 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -38,7 +38,7 @@
class SourcePatcher(object):
- """"Lazy patcher for python source files"""
+ """Lazy patcher for python source files"""
def __init__(self):
self.source_files = None
@@ -431,14 +431,21 @@
help='Package with tests')
parser.add_argument('--fix', action='store_true', dest='fix_tests',
help='Attempt to fix tests without UUIDs')
+ parser.add_argument('--libpath', action='store', dest='libpath',
+ default=".", type=str,
+ help='Path to package')
+
args = parser.parse_args()
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+ sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
+ sys.path.insert(0, args.libpath)
pkg = importlib.import_module(args.package)
+
checker = TestChecker(pkg)
errors = False
tests = checker.get_tests()
untagged = checker.find_untagged(tests)
errors = checker.report_collisions(tests) or errors
+
if args.fix_tests and untagged:
checker.fix_tests(untagged)
else:
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..eb03faa 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -37,7 +37,7 @@
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 +59,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 +67,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/network/__init__.py b/tempest/lib/services/network/__init__.py
index 98d7482..faf35d1 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -31,6 +31,8 @@
QosLimitBandwidthRulesClient
from tempest.lib.services.network.qos_minimum_bandwidth_rules_client import \
QosMinimumBandwidthRulesClient
+from tempest.lib.services.network.qos_minimum_packet_rate_rules_client import \
+ QosMinimumPacketRateRulesClient
from tempest.lib.services.network.quotas_client import QuotasClient
from tempest.lib.services.network.routers_client import RoutersClient
from tempest.lib.services.network.security_group_rules_client import \
@@ -54,4 +56,4 @@
'SecurityGroupRulesClient', 'SecurityGroupsClient',
'SegmentsClient', 'ServiceProvidersClient', 'SubnetpoolsClient',
'SubnetsClient', 'TagsClient', 'TrunksClient', 'LogResourceClient',
- 'LoggableResourceClient']
+ 'LoggableResourceClient', 'QosMinimumPacketRateRulesClient']
diff --git a/tempest/scenario/test_network_qos_placement.py b/tempest/scenario/test_network_qos_placement.py
index db4751b..adb0ee3 100644
--- a/tempest/scenario/test_network_qos_placement.py
+++ b/tempest/scenario/test_network_qos_placement.py
@@ -49,7 +49,10 @@
compute_max_microversion = 'latest'
INGRESS_DIRECTION = 'ingress'
- BW_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC"
+ EGRESS_DIRECTION = 'egress'
+ ANY_DIRECTION = 'any'
+ 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:
@@ -106,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']
@@ -116,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,
@@ -166,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)
@@ -193,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
@@ -508,3 +519,554 @@
**{'description': 'foo'})
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):
+
+ PPS_RESOURCE_CLASS = "NET_PACKET_RATE_KILOPACKET_PER_SEC"
+
+ @classmethod
+ def skip_checks(cls):
+ super().skip_checks()
+ if not CONF.network_feature_enabled.qos_min_bw_and_pps:
+ msg = (
+ "Skipped as no resource inventories are configured for QoS "
+ "minimum bandwidth and packet rate testing.")
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super().setup_clients()
+ cls.qos_min_pps_client = cls.os_admin.qos_min_pps_client
+
+ def setUp(self):
+ super().setUp()
+ self.network = self._create_network()
+
+ def _create_qos_policy_with_bw_and_pps_rules(self, min_kbps, min_kpps):
+ policy = self.qos_client.create_qos_policy(
+ name=data_utils.rand_name(),
+ shared=True
+ )['policy']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.qos_client.delete_qos_policy,
+ policy['id']
+ )
+
+ if min_kbps > 0:
+ bw_rule = self.qos_min_bw_client.create_minimum_bandwidth_rule(
+ policy['id'],
+ min_kbps=min_kbps,
+ direction=self.INGRESS_DIRECTION
+ )['minimum_bandwidth_rule']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.qos_min_bw_client.delete_minimum_bandwidth_rule,
+ policy['id'],
+ bw_rule['id']
+ )
+
+ if min_kpps > 0:
+ pps_rule = self.qos_min_pps_client.create_minimum_packet_rate_rule(
+ policy['id'],
+ min_kpps=min_kpps,
+ direction=self.ANY_DIRECTION
+ )['minimum_packet_rate_rule']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.qos_min_pps_client.delete_minimum_packet_rate_rule,
+ policy['id'],
+ pps_rule['id']
+ )
+
+ return policy
+
+ def _create_network(self):
+ physnet_name = CONF.network_feature_enabled.qos_placement_physnet
+ base_segm = (
+ CONF.network_feature_enabled.provider_net_base_segmentation_id)
+
+ # setup_network_subnet_with_router will add the necessary cleanup calls
+ network, _, _ = self.setup_network_subnet_with_router(
+ networks_client=self.networks_client,
+ routers_client=self.routers_client,
+ subnets_client=self.subnets_client,
+ shared=True,
+ **{
+ 'provider:network_type': 'vlan',
+ 'provider:physical_network': physnet_name,
+ # +1 to be different from the segmentation_id used in
+ # MinBwAllocationPlacementTest
+ 'provider:segmentation_id': int(base_segm) + 1,
+ }
+ )
+ return network
+
+ def _create_port_with_qos_policy(self, policy):
+ port = self.ports_client.create_port(
+ name=data_utils.rand_name(self.__class__.__name__),
+ network_id=self.network['id'],
+ qos_policy_id=policy['id'] if policy else None,
+ )['port']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.ports_client.delete_port, port['id']
+ )
+ return port
+
+ def assert_allocations(
+ self, server, port, expected_min_kbps, expected_min_kpps
+ ):
+ allocations = self.placement_client.list_allocations(
+ server['id'])['allocations']
+
+ # one allocation for the flavor related resources on the compute RP
+ expected_allocation = 1
+ # one allocation due to bw rule
+ if expected_min_kbps > 0:
+ expected_allocation += 1
+ # one allocation due to pps rule
+ if expected_min_kpps > 0:
+ expected_allocation += 1
+ self.assertEqual(expected_allocation, len(allocations), allocations)
+
+ expected_rp_uuids_in_binding_allocation = set()
+
+ if expected_min_kbps > 0:
+ bw_rp_allocs = {
+ rp: alloc['resources'][self.INGRESS_RESOURCE_CLASS]
+ for rp, alloc in allocations.items()
+ 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]
+ self.assertEqual(expected_min_kbps, bw_alloc)
+ expected_rp_uuids_in_binding_allocation.add(bw_rp)
+
+ if expected_min_kpps > 0:
+ pps_rp_allocs = {
+ rp: alloc['resources'][self.PPS_RESOURCE_CLASS]
+ for rp, alloc in allocations.items()
+ if self.PPS_RESOURCE_CLASS in alloc['resources']
+ }
+ self.assertEqual(1, len(pps_rp_allocs))
+ pps_rp, pps_alloc = list(pps_rp_allocs.items())[0]
+ self.assertEqual(expected_min_kpps, pps_alloc)
+ expected_rp_uuids_in_binding_allocation.add(pps_rp)
+
+ # Let's check port.binding:profile.allocation points to the two
+ # provider resource allocated from
+ port = self.os_admin.ports_client.show_port(port['id'])
+ port_binding_alloc = port[
+ 'port']['binding:profile'].get('allocation', {})
+ self.assertEqual(
+ expected_rp_uuids_in_binding_allocation,
+ set(port_binding_alloc.values())
+ )
+
+ def assert_no_allocation(self, server, port):
+ # check that there are no allocations
+ allocations = self.placement_client.list_allocations(
+ server['id'])['allocations']
+ self.assertEqual(0, len(allocations))
+
+ # check that binding_profile of the port is empty
+ port = self.os_admin.ports_client.show_port(port['id'])
+ self.assertEqual(0, len(port['port']['binding:profile']))
+
+ @decorators.idempotent_id('93d1a88d-235e-4b7b-b44d-2a17dcf4e213')
+ @utils.services('compute', 'network')
+ def test_server_create_delete(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+ port = self._create_port_with_qos_policy(policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.servers_client.delete_server(server['id'])
+ waiters.wait_for_server_termination(self.servers_client, server['id'])
+
+ self.assert_no_allocation(server, port)
+
+ def _test_create_server_negative(self, min_kbps=1000, min_kpps=100):
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+ port = self._create_port_with_qos_policy(policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until=None)
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='ERROR', ready_wait=False, raise_on_error=False)
+
+ # check that the creation failed with No valid host
+ server = self.servers_client.show_server(server['id'])['server']
+ self.assertIn('fault', server)
+ self.assertIn('No valid host', server['fault']['message'])
+
+ self.assert_no_allocation(server, port)
+
+ @decorators.idempotent_id('915dd2ce-4890-40c8-9db6-f3e04080c6c1')
+ @utils.services('compute', 'network')
+ def test_server_create_no_valid_host_due_to_bandwidth(self):
+ self._test_create_server_negative(min_kbps=self.PLACEMENT_MAX_INT)
+
+ @decorators.idempotent_id('2d4a755e-10b9-4ac0-bef2-3f89de1f150b')
+ @utils.services('compute', 'network')
+ def test_server_create_no_valid_host_due_to_packet_rate(self):
+ self._test_create_server_negative(min_kpps=self.PLACEMENT_MAX_INT)
+
+ @decorators.idempotent_id('69d93e4f-0dfc-4d17-8d84-cc5c3c842cd5')
+ @testtools.skipUnless(
+ CONF.compute_feature_enabled.resize, 'Resize not available.')
+ @utils.services('compute', 'network')
+ def test_server_resize(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+ port = self._create_port_with_qos_policy(policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ new_flavor = self._create_flavor_to_resize_to()
+
+ self.servers_client.resize_server(
+ server_id=server['id'], flavor_ref=new_flavor['id']
+ )
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.servers_client.confirm_resize_server(server_id=server['id'])
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='ACTIVE', ready_wait=False, raise_on_error=True)
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ @decorators.idempotent_id('d01d4aee-ca06-4e4e-add7-8a47fe0daf96')
+ @testtools.skipUnless(
+ CONF.compute_feature_enabled.resize, 'Resize not available.')
+ @utils.services('compute', 'network')
+ def test_server_resize_revert(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+ port = self._create_port_with_qos_policy(policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ new_flavor = self._create_flavor_to_resize_to()
+
+ self.servers_client.resize_server(
+ server_id=server['id'], flavor_ref=new_flavor['id']
+ )
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.servers_client.revert_resize_server(server_id=server['id'])
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='ACTIVE', ready_wait=False, raise_on_error=True)
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ @decorators.idempotent_id('bdd0b31c-c8b0-4b7b-b80a-545a46b32abe')
+ @testtools.skipUnless(
+ CONF.compute_feature_enabled.cold_migration,
+ 'Cold migration is not available.')
+ @testtools.skipUnless(
+ CONF.compute.min_compute_nodes > 1,
+ 'Less than 2 compute nodes, skipping multinode tests.')
+ @utils.services('compute', 'network')
+ def test_server_migrate(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+ port = self._create_port_with_qos_policy(policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.os_adm.servers_client.migrate_server(server_id=server['id'])
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='VERIFY_RESIZE', ready_wait=False, raise_on_error=False)
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.os_adm.servers_client.confirm_resize_server(
+ server_id=server['id'])
+ waiters.wait_for_server_status(
+ client=self.servers_client, server_id=server['id'],
+ status='ACTIVE', ready_wait=False, raise_on_error=True)
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ @decorators.idempotent_id('fdb260e3-caa5-482d-ac7c-8c22adf3d750')
+ @utils.services('compute', 'network')
+ def test_qos_policy_update_on_bound_port(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+
+ min_kbps2 = 2000
+ min_kpps2 = 50
+ policy2 = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps2, min_kpps2)
+
+ port = self._create_port_with_qos_policy(policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.ports_client.update_port(
+ port['id'],
+ qos_policy_id=policy2['id'])
+
+ self.assert_allocations(server, port, min_kbps2, min_kpps2)
+
+ @decorators.idempotent_id('e6a20125-a02e-49f5-bcf6-894305ee3715')
+ @utils.services('compute', 'network')
+ def test_qos_policy_update_on_bound_port_from_null_policy(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+
+ port = self._create_port_with_qos_policy(policy=None)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, 0, 0)
+
+ self.ports_client.update_port(
+ port['id'],
+ qos_policy_id=policy['id'])
+
+ # NOTE(gibi): This is unintuitive but it is the expected behavior.
+ # If there was no policy attached to the port when the server was
+ # created then neutron still allows adding a policy to the port later
+ # as this operation was support before placement enforcement was added
+ # for the qos minimum bandwidth rule. However neutron cannot create
+ # the placement resource allocation for this port.
+ self.assert_allocations(server, port, 0, 0)
+
+ @decorators.idempotent_id('f5864761-966c-4e49-b430-ac0044b7d658')
+ @utils.services('compute', 'network')
+ def test_qos_policy_update_on_bound_port_additional_rule(self):
+ min_kbps = 1000
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, 0)
+
+ min_kbps2 = 2000
+ min_kpps2 = 50
+ policy2 = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps2, min_kpps2)
+
+ port = self._create_port_with_qos_policy(policy=policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, 0)
+
+ self.ports_client.update_port(
+ port['id'],
+ qos_policy_id=policy2['id'])
+
+ # FIXME(gibi): Agree in the spec: do we ignore the pps request or we
+ # reject the update? It seems current implementation goes with
+ # ignoring the additional pps rule.
+ self.assert_allocations(server, port, min_kbps2, 0)
+
+ @decorators.idempotent_id('fbbb9c81-ed21-48c3-bdba-ce2361e93aad')
+ @utils.services('compute', 'network')
+ def test_qos_policy_update_on_bound_port_to_null_policy(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+
+ port = self._create_port_with_qos_policy(policy=policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ self.ports_client.update_port(
+ port['id'],
+ qos_policy_id=None)
+
+ self.assert_allocations(server, port, 0, 0)
+
+ @decorators.idempotent_id('0393d038-03ad-4844-a0e4-83010f69dabb')
+ @utils.services('compute', 'network')
+ def test_interface_attach_detach(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+
+ port = self._create_port_with_qos_policy(policy=None)
+
+ port2 = self._create_port_with_qos_policy(policy=policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, 0, 0)
+
+ self.interface_client.create_interface(
+ server_id=server['id'],
+ port_id=port2['id'])
+ waiters.wait_for_interface_status(
+ self.interface_client, server['id'], port2['id'], 'ACTIVE')
+
+ self.assert_allocations(server, port2, min_kbps, min_kpps)
+
+ req_id = self.interface_client.delete_interface(
+ server_id=server['id'],
+ port_id=port2['id']).response['x-openstack-request-id']
+ waiters.wait_for_interface_detach(
+ self.servers_client, server['id'], port2['id'], req_id)
+
+ self.assert_allocations(server, port2, 0, 0)
+
+ @decorators.idempotent_id('36ffdb85-6cc2-4cc9-a426-cad5bac8626b')
+ @testtools.skipUnless(
+ CONF.compute.min_compute_nodes > 1,
+ 'Less than 2 compute nodes, skipping multinode tests.')
+ @testtools.skipUnless(
+ CONF.compute_feature_enabled.live_migration,
+ 'Live migration not available')
+ @utils.services('compute', 'network')
+ def test_server_live_migrate(self):
+ min_kbps = 1000
+ min_kpps = 100
+ policy = self._create_qos_policy_with_bw_and_pps_rules(
+ min_kbps, min_kpps)
+
+ port = self._create_port_with_qos_policy(policy=policy)
+
+ server = self.create_server(
+ networks=[{'port': port['id']}],
+ wait_until='ACTIVE'
+ )
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
+
+ server_details = self.os_adm.servers_client.show_server(server['id'])
+ source_host = server_details['server']['OS-EXT-SRV-ATTR:host']
+
+ self.os_adm.servers_client.live_migrate_server(
+ server['id'], block_migration=True, host=None)
+ waiters.wait_for_server_status(
+ self.servers_client, server['id'], 'ACTIVE')
+
+ server_details = self.os_adm.servers_client.show_server(server['id'])
+ new_host = server_details['server']['OS-EXT-SRV-ATTR:host']
+
+ self.assertNotEqual(source_host, new_host, "Live migration failed")
+
+ self.assert_allocations(server, port, min_kbps, min_kpps)
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..eef5b41 100644
--- a/tempest/tests/lib/services/image/v2/test_schemas_client.py
+++ b/tempest/tests/lib/services/image/v2/test_schemas_client.py
@@ -81,6 +81,14 @@
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(self, bytes_body=False):
self.check_service_client_function(
self.client.show_schema,
@@ -89,6 +97,12 @@
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_with_str_body(self):
self._test_show_schema()
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index b86268a..fad17dd 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: |
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 36f4920..3f98f7e 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -43,8 +43,6 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-ussuri-py3:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-train-py3:
- irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full-py3:
irrelevant-files: *tempest-irrelevant-files
- tempest-tox-plugin-sanity-check:
@@ -163,13 +161,14 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3-opensuse15:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-centos8-stream-fips:
+ irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
- tempest-full-xena
- tempest-full-wallaby-py3
- tempest-full-victoria-py3
- tempest-full-ussuri-py3
- - tempest-full-train-py3
periodic:
jobs:
- tempest-all
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index e682457..da6cc46 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -21,12 +21,6 @@
override-checkout: stable/ussuri
- job:
- name: tempest-full-train-py3
- parent: tempest-full-py3
- nodeset: openstack-single-node-bionic
- override-checkout: stable/train
-
-- job:
name: tempest-full-py3
parent: devstack-tempest
# This job version is with swift disabled on py3
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index 051d8b0..a24c73d 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -80,6 +80,8 @@
- 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