Merge "Allow visibility of external subnet as shared ones"
diff --git a/releasenotes/notes/add-enable-volume-image-dep-tests-option-150b929d18da233f.yaml b/releasenotes/notes/add-enable-volume-image-dep-tests-option-150b929d18da233f.yaml
new file mode 100644
index 0000000..e78201e
--- /dev/null
+++ b/releasenotes/notes/add-enable-volume-image-dep-tests-option-150b929d18da233f.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add new config option 'enable_volume_image_dep_tests' in section
+ [volume-feature-enabled] which should be used in
+ image<->volume<->snapshot dependency tests.
diff --git a/releasenotes/notes/add-placement-resource-provider-traits-api-calls-9f4b0455afec9afb.yaml b/releasenotes/notes/add-placement-resource-provider-traits-api-calls-9f4b0455afec9afb.yaml
new file mode 100644
index 0000000..1d1811c
--- /dev/null
+++ b/releasenotes/notes/add-placement-resource-provider-traits-api-calls-9f4b0455afec9afb.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Adds API calls for traits in ResourceProvidersClient.
diff --git a/releasenotes/notes/add-placement-traits-api-calls-087061f5455f0b12.yaml b/releasenotes/notes/add-placement-traits-api-calls-087061f5455f0b12.yaml
new file mode 100644
index 0000000..77d0b38
--- /dev/null
+++ b/releasenotes/notes/add-placement-traits-api-calls-087061f5455f0b12.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Adds API calls for traits in PlacementClient.
diff --git a/releasenotes/notes/cleanup-attr-decorator-alias-78ce21eb20d87e01.yaml b/releasenotes/notes/cleanup-attr-decorator-alias-78ce21eb20d87e01.yaml
new file mode 100644
index 0000000..43091e1
--- /dev/null
+++ b/releasenotes/notes/cleanup-attr-decorator-alias-78ce21eb20d87e01.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ The ``attr`` decorator is no longer available in the ``tempest.test``
+ module. Use the ``tempest.lib.decorators`` module instead.
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index ee25a22..596d2bd 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -99,11 +99,14 @@
def test_delete_server_while_in_verify_resize_state(self):
"""Test deleting a server while it's VM state is VERIFY_RESIZE"""
server = self.create_test_server(wait_until='ACTIVE')
- self.client.resize_server(server['id'], self.flavor_ref_alt)
- waiters.wait_for_server_status(self.client, server['id'],
- 'VERIFY_RESIZE')
- self.client.delete_server(server['id'])
- waiters.wait_for_server_termination(self.client, server['id'])
+ body = self.client.resize_server(server['id'], self.flavor_ref_alt)
+ request_id = body.response['x-openstack-request-id']
+ waiters.wait_for_server_status(
+ self.client, server['id'], 'VERIFY_RESIZE', request_id=request_id)
+ body = self.client.delete_server(server['id'])
+ request_id = body.response['x-openstack-request-id']
+ waiters.wait_for_server_termination(
+ self.client, server['id'], request_id=request_id)
@decorators.idempotent_id('d0f3f0d6-d9b6-4a32-8da4-23015dcab23c')
@utils.services('volume')
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 89d5f91..0cc088a 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -100,10 +100,9 @@
"""Create Image & stage image file for glance-direct import method."""
image_name = data_utils.rand_name('test-image')
container_format = CONF.image.container_formats[0]
- disk_format = CONF.image.disk_formats[0]
image = self.create_image(name=image_name,
container_format=container_format,
- disk_format=disk_format,
+ disk_format='raw',
visibility='private')
self.assertEqual('queued', image['status'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index e468e32..5bb8eef 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -76,7 +76,7 @@
'%s import method' % method)
def _stage_and_check(self):
- image = self._create_image()
+ image = self._create_image(disk_format='raw')
# Stage image data
file_content = data_utils.random_bytes()
image_file = io.BytesIO(file_content)
@@ -131,7 +131,7 @@
self.assertEqual(image['id'], body['id'])
self.assertEqual('queued', body['status'])
# import image from web to backend
- image_uri = CONF.image.http_image
+ image_uri = CONF.image.http_qcow2_image
self.client.image_import(image['id'], method='web-download',
import_params={'uri': image_uri})
waiters.wait_for_image_imported_to_stores(self.client, image['id'])
diff --git a/tempest/api/image/v2/test_images_dependency.py b/tempest/api/image/v2/test_images_dependency.py
new file mode 100644
index 0000000..326045b
--- /dev/null
+++ b/tempest/api/image/v2/test_images_dependency.py
@@ -0,0 +1,103 @@
+# Copyright 2024 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import io
+
+from oslo_log import log as logging
+
+from tempest.api.compute import base as compute_base
+from tempest.api.image import base as image_base
+from tempest.common import utils
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.scenario import manager
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class ImageDependencyTests(image_base.BaseV2ImageTest,
+ compute_base.BaseV2ComputeTest,
+ manager.ScenarioTest):
+ """Test image, instance, and snapshot dependency.
+
+ The tests create image and remove the base image that other snapshots
+ were depend on.In OpenStack, images and snapshots should be separate,
+ but in some configurations like Glance with Ceph storage,
+ there were cases where images couldn't be removed.
+ This was fixed in glance store for RBD backend.
+
+ * Dependency scenarios:
+ - image > instance -> snapshot dependency
+
+ NOTE: volume -> image dependencies tests are in cinder-tempest-plugin
+ """
+
+ @classmethod
+ def skip_checks(cls):
+ super(ImageDependencyTests, cls).skip_checks()
+ if not CONF.volume_feature_enabled.enable_volume_image_dep_tests:
+ skip_msg = (
+ "%s Volume/image dependency tests "
+ "not enabled" % (cls.__name__))
+ raise cls.skipException(skip_msg)
+
+ def _create_instance_snapshot(self):
+ """Create instance from image and then snapshot the instance."""
+ # Create image and store data to image
+ image_name = data_utils.rand_name(
+ prefix=CONF.resource_name_prefix,
+ name='image-dependency-test')
+ image = self.create_image(name=image_name,
+ container_format='bare',
+ disk_format='raw',
+ visibility='private')
+ file_content = data_utils.random_bytes()
+ image_file = io.BytesIO(file_content)
+ self.client.store_image_file(image['id'], image_file)
+ waiters.wait_for_image_status(
+ self.client, image['id'], 'active')
+ # Create instance
+ instance = self.create_test_server(
+ name='instance-depend-image',
+ image_id=image['id'],
+ wait_until='ACTIVE')
+ LOG.info("Instance from image is created %s", instance)
+ instance_observed = \
+ self.servers_client.show_server(instance['id'])['server']
+ # Create instance snapshot
+ snapshot_instance = self.create_server_snapshot(
+ server=instance_observed)
+ LOG.info("Instance snapshot is created %s", snapshot_instance)
+ return image['id'], snapshot_instance['id']
+
+ @decorators.idempotent_id('d19b0731-e98e-4103-8b0e-02f651b8f586')
+ @utils.services('compute')
+ def test_nova_image_snapshot_dependency(self):
+ """Test with image > instance > snapshot dependency.
+
+ Create instance snapshot and check if we able to delete base
+ image
+
+ """
+ base_image_id, snapshot_image_id = self._create_instance_snapshot()
+ self.client.delete_image(base_image_id)
+ self.client.wait_for_resource_deletion(base_image_id)
+ images_list = self.client.list_images()['images']
+ fetched_images_id = [img['id'] for img in images_list]
+ self.assertNotIn(base_image_id, fetched_images_id)
+ self.assertIn(snapshot_image_id, fetched_images_id)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d65b491..e249f35 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -103,7 +103,8 @@
old_task_state = task_state
-def wait_for_server_termination(client, server_id, ignore_error=False):
+def wait_for_server_termination(client, server_id, ignore_error=False,
+ request_id=None):
"""Waits for server to reach termination."""
try:
body = client.show_server(server_id)['server']
@@ -126,9 +127,13 @@
'/'.join((server_status, str(task_state))),
time.time() - start_time)
if server_status == 'ERROR' and not ignore_error:
- raise lib_exc.DeleteErrorException(
- "Server %s failed to delete and is in ERROR status" %
- server_id)
+ details = ("Server %s failed to delete and is in ERROR status." %
+ server_id)
+ if 'fault' in body:
+ details += ' Fault: %s.' % body['fault']
+ if request_id:
+ details += ' Server delete request ID: %s.' % request_id
+ raise lib_exc.DeleteErrorException(details, server_id=server_id)
if server_status == 'SOFT_DELETED':
# Soft-deleted instances need to be forcibly deleted to
diff --git a/tempest/config.py b/tempest/config.py
index fc50db5..a98082a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -635,12 +635,17 @@
cfg.BoolOpt('image_caching_enabled',
default=False,
help=("Flag to enable if caching is enabled by image "
- "service, operator should set this parameter to True"
+ "service, operator should set this parameter to True "
"if 'image_cache_dir' is set in glance-api.conf")),
cfg.StrOpt('http_image',
default='http://download.cirros-cloud.net/0.3.1/'
'cirros-0.3.1-x86_64-uec.tar.gz',
help='http accessible image'),
+ cfg.StrOpt('http_qcow2_image',
+ default='http://download.cirros-cloud.net/0.6.2/'
+ 'cirros-0.6.2-x86_64-disk.img',
+ help='http qcow2 accessible image which will be used '
+ 'for image conversion if enabled.'),
cfg.IntOpt('build_timeout',
default=300,
help="Timeout in seconds to wait for an image to "
@@ -1071,7 +1076,10 @@
default=None,
help='Volume types used for data volumes. Multiple volume '
'types can be assigned.'),
-
+ cfg.BoolOpt('enable_volume_image_dep_tests',
+ default=True,
+ help='Run tests for dependencies between images, volumes'
+ 'and instance snapshots')
]
@@ -1176,7 +1184,7 @@
default='icmp',
choices=('icmp', 'tcp', 'udp'),
help='The protocol used in security groups tests to check '
- 'connectivity.'),
+ 'connectivity.')
]
diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py
index 216ac08..f272cbf 100644
--- a/tempest/lib/services/placement/placement_client.py
+++ b/tempest/lib/services/placement/placement_client.py
@@ -49,3 +49,39 @@
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+
+ def list_traits(self, **params):
+ """API ref https://docs.openstack.org/api-ref/placement/#traits
+ """
+ url = "/traits"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_trait(self, name, **params):
+ url = "/traits"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+ url = f"{url}/{name}"
+ resp, _ = self.get(url)
+ self.expected_success(204, resp.status)
+ return resp.status
+
+ def create_trait(self, name, **params):
+ url = f"/traits/{name}"
+ json_body = json.dumps(params)
+ resp, _ = self.put(url, body=json_body)
+ return resp.status
+
+ def delete_trait(self, name):
+ url = f"/traits/{name}"
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
+ return resp.status
diff --git a/tempest/lib/services/placement/resource_providers_client.py b/tempest/lib/services/placement/resource_providers_client.py
index 3214053..a336500 100644
--- a/tempest/lib/services/placement/resource_providers_client.py
+++ b/tempest/lib/services/placement/resource_providers_client.py
@@ -121,3 +121,29 @@
resp, body = self.delete(url)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def list_resource_provider_traits(self, rp_uuid, **kwargs):
+ """https://docs.openstack.org/api-ref/placement/#resource-provider-traits
+ """
+ url = f"/resource_providers/{rp_uuid}/traits"
+ if kwargs:
+ url += '?%s' % urllib.urlencode(kwargs)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_resource_provider_traits(self, rp_uuid, **kwargs):
+ url = f"/resource_providers/{rp_uuid}/traits"
+ data = json.dumps(kwargs)
+ resp, body = self.put(url, data)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_resource_provider_traits(self, rp_uuid):
+ url = f"/resource_providers/{rp_uuid}/traits"
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/test_instances_with_cinder_volumes.py b/tempest/scenario/test_instances_with_cinder_volumes.py
index a907acd..b9ac2c8 100644
--- a/tempest/scenario/test_instances_with_cinder_volumes.py
+++ b/tempest/scenario/test_instances_with_cinder_volumes.py
@@ -181,21 +181,24 @@
server=server
)
+ server_name = server['name'].split('-')[-1]
+
# run write test on all volumes
for volume in attached_volumes:
- # get the mount path
- dev_name = volume['attachments'][0]['device'][5:]
-
# dev name volume['attachments'][0]['device'][5:] is like
# /dev/vdb, we need to remove /dev/ -> first 5 chars
+ dev_name = volume['attachments'][0]['device'][5:]
+
+ mount_path = f"/mnt/{server_name}"
+
timestamp_before = self.create_timestamp(
ssh_ip, private_key=keypair['private_key'], server=server,
- dev_name=dev_name,
+ dev_name=dev_name, mount_path=mount_path,
)
timestamp_after = self.get_timestamp(
ssh_ip, private_key=keypair['private_key'], server=server,
- dev_name=dev_name,
+ dev_name=dev_name, mount_path=mount_path,
)
self.assertEqual(timestamp_before, timestamp_after)
diff --git a/tempest/test.py b/tempest/test.py
index 173bfab..85a6c36 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -31,7 +31,6 @@
from tempest.lib.common import fixed_network
from tempest.lib.common import profiler
from tempest.lib.common import validation_resources as vr
-from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
@@ -39,11 +38,6 @@
CONF = config.CONF
-attr = debtcollector.moves.moved_function(
- decorators.attr, 'attr', __name__,
- version='Pike', removal_version='?')
-
-
at_exit_set = set()
diff --git a/tempest/tests/lib/services/placement/test_placement_client.py b/tempest/tests/lib/services/placement/test_placement_client.py
index 1396a85..bb57bb0 100644
--- a/tempest/tests/lib/services/placement/test_placement_client.py
+++ b/tempest/tests/lib/services/placement/test_placement_client.py
@@ -87,3 +87,77 @@
def test_list_allocations_with_bytes_body(self):
self._test_list_allocations(bytes_body=True)
+
+ FAKE_ALL_TRAITS = {
+ "traits": [
+ "CUSTOM_HW_FPGA_CLASS1",
+ "CUSTOM_HW_FPGA_CLASS2",
+ "CUSTOM_HW_FPGA_CLASS3"
+ ]
+ }
+
+ FAKE_ASSOCIATED_TRAITS = {
+ "traits": [
+ "CUSTOM_HW_FPGA_CLASS1",
+ "CUSTOM_HW_FPGA_CLASS2"
+ ]
+ }
+
+ def test_list_traits(self):
+ self.check_service_client_function(
+ self.client.list_traits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ALL_TRAITS)
+
+ self.check_service_client_function(
+ self.client.list_traits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ASSOCIATED_TRAITS,
+ **{
+ "associated": "true"
+ })
+
+ self.check_service_client_function(
+ self.client.list_traits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ALL_TRAITS,
+ **{
+ "associated": "true",
+ "name": "startswith:CUSTOM_HW_FPGPA"
+ })
+
+ def test_show_traits(self):
+ self.check_service_client_function(
+ self.client.show_trait,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ 204, status=204,
+ name="CUSTOM_HW_FPGA_CLASS1")
+
+ self.check_service_client_function(
+ self.client.show_trait,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ 404, status=404,
+ # trait with this name does not exists
+ name="CUSTOM_HW_FPGA_CLASS4")
+
+ def test_create_traits(self):
+ self.check_service_client_function(
+ self.client.create_trait,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ 204, status=204,
+ # try to create trait with existing name
+ name="CUSTOM_HW_FPGA_CLASS1")
+
+ self.check_service_client_function(
+ self.client.create_trait,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ 201, status=201,
+ # create new trait
+ name="CUSTOM_HW_FPGA_CLASS4")
+
+ def test_delete_traits(self):
+ self.check_service_client_function(
+ self.client.delete_trait,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ 204, status=204,
+ name="CUSTOM_HW_FPGA_CLASS1")
diff --git a/tempest/tests/lib/services/placement/test_resource_providers_client.py b/tempest/tests/lib/services/placement/test_resource_providers_client.py
index 2871395..399f323 100644
--- a/tempest/tests/lib/services/placement/test_resource_providers_client.py
+++ b/tempest/tests/lib/services/placement/test_resource_providers_client.py
@@ -204,3 +204,40 @@
def test_show_resource_provider_usages_with_with_bytes_body(self):
self._test_list_resource_provider_inventories(bytes_body=True)
+
+ FAKE_ALL_RESOURCE_PROVIDER_TRAITS = {
+ "resource_provider_generation": 0,
+ "traits": [
+ "CUSTOM_HW_FPGA_CLASS1",
+ "CUSTOM_HW_FPGA_CLASS2"
+ ]
+ }
+ FAKE_NEW_RESOURCE_PROVIDER_TRAITS = {
+ "resource_provider_generation": 1,
+ "traits": [
+ "CUSTOM_HW_FPGA_CLASS1",
+ "CUSTOM_HW_FPGA_CLASS2"
+ ]
+ }
+
+ def test_list_resource_provider_traits(self):
+ self.check_service_client_function(
+ self.client.list_resource_provider_traits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ALL_RESOURCE_PROVIDER_TRAITS,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID)
+
+ def test_update_resource_provider_traits(self):
+ self.check_service_client_function(
+ self.client.update_resource_provider_traits,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_NEW_RESOURCE_PROVIDER_TRAITS,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID,
+ **self.FAKE_NEW_RESOURCE_PROVIDER_TRAITS)
+
+ def test_delete_resource_provider_traits(self):
+ self.check_service_client_function(
+ self.client.delete_resource_provider_traits,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ self.FAKE_ALL_RESOURCE_PROVIDER_TRAITS, status=204,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID)
diff --git a/tox.ini b/tox.ini
index fcdf6ff..e3c8fcf 100644
--- a/tox.ini
+++ b/tox.ini
@@ -197,7 +197,7 @@
# tests listed in exclude-list file:
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex {[testenv:integrated-compute]regex1} --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
+ tempest run --slowest --regex {[testenv:integrated-compute]regex1} --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
tempest run --combine --serial --slowest --regex {[testenv:integrated-compute]regex2} --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
[testenv:integrated-placement]
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 2fd6e36..1343a7c 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -65,6 +65,10 @@
branches:
regex: ^.*/(victoria|wallaby)$
negate: true
+ # NOTE(sean-k-mooney): this job and its descendants frequently times out
+ # run on rax-* providers with a timeout of 2 hours. temporary increase
+ # the timeout to 2.5 hours.
+ timeout: 9000
description: |
Base integration test with Neutron networking, horizon, swift enable,
and py3.
@@ -78,6 +82,8 @@
# end up 6 in upstream CI. Higher concurrency means high parallel
# requests to services and can cause more oom issues. To avoid the
# oom issue, setting the concurrency to 4 in this job.
+ # NOTE(sean-k-mooney): now that we use zswap we should be able to
+ # increase the concurrency to 6.
tempest_concurrency: 4
tox_envlist: integrated-full
devstack_localrc:
@@ -133,11 +139,18 @@
This job runs integration tests for compute. This is
subset of 'tempest-full-py3' job and run Nova, Neutron, Cinder (except backup tests)
and Glance related tests. This is meant to be run on Nova gate only.
+ # NOTE(sean-k-mooney): this job and its descendants frequently times out
+ # when run on rax-* providers, recent optimizations have reduced the
+ # runtime of the job but it still times out. temporary increase the
+ # timeout to 2.5 hours.
+ timeout: 9000
vars:
# NOTE(gmann): Default concurrency is higher (number of cpu -2) which
# end up 6 in upstream CI. Higher concurrency means high parallel
# requests to services and can cause more oom issues. To avoid the
# oom issue, setting the concurrency to 4 in this job.
+ # NOTE(sean-k-mooney): now that we use zswap we should be able to
+ # increase the concurrency to 6.
tempest_concurrency: 4
tox_envlist: integrated-compute
tempest_exclude_regex: ""