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: ""