Merge "Rewrite regular expressions to be RE2 compliant"
diff --git a/octavia_tempest_plugin/common/constants.py b/octavia_tempest_plugin/common/constants.py
index 48a83ac..f03da3e 100644
--- a/octavia_tempest_plugin/common/constants.py
+++ b/octavia_tempest_plugin/common/constants.py
@@ -245,11 +245,17 @@
 
 # Flavor capabilities
 LOADBALANCER_TOPOLOGY = 'loadbalancer_topology'
+COMPUTE_FLAVOR = 'compute_flavor'
 
 # Availability zone capabilities
 COMPUTE_ZONE = 'compute_zone'
 MANAGEMENT_NETWORK = 'management_network'
 
+# Compute flavors
+RAM = 'ram'
+VCPUS = 'vcpus'
+DISK = 'disk'
+
 # API valid fields
 SHOW_LOAD_BALANCER_RESPONSE_FIELDS = (
     ADMIN_STATE_UP, CREATED_AT, DESCRIPTION, FLAVOR_ID, ID, LISTENERS, NAME,
diff --git a/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py b/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py
index 2e1464c..3c92181 100644
--- a/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py
+++ b/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py
@@ -1256,7 +1256,18 @@
                                 const.ACTIVE,
                                 CONF.load_balancer.build_interval,
                                 CONF.load_balancer.build_timeout)
-
+        # TODO(johnsom) - Remove this once eventlet is removed from OpenStack
+        # NOTE(pas-ha): depending on what other tempest plugins are installed
+        # the eventlet might've been imported by that time, and, since
+        # dnspython 2.2.0, importing eventlet or any part of it effectively
+        # instantiates a dummy httpx.Client instance, thus pinning the ssl
+        # implementation in httpx to the eventlet's one.
+        # This leads to error in the Client() call below, as the ssl lib in
+        # this module is different from ssl lib in httpx._config,
+        # which fails isinstance check for ssl.SSLContext.
+        # Use the ssl module that is actually used by httpx to instantiate
+        # the SSL context to be used with httpx.
+        ssl = httpx._config.ssl
         context = ssl.create_default_context(cadata=self.ca_cert.public_bytes(
             serialization.Encoding.PEM).decode('utf-8'))
         context.check_hostname = False
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py b/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
index 6c52f84..e50d243 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
@@ -22,6 +22,7 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
 
 from octavia_tempest_plugin.common import constants as const
 from octavia_tempest_plugin.tests import test_base
@@ -38,14 +39,17 @@
         """Setup resources needed by the tests."""
         super(LoadBalancerScenarioTest, cls).resource_setup()
 
+        cls.flavor_id = cls._flavor_create({
+            const.LOADBALANCER_TOPOLOGY:
+            CONF.load_balancer.loadbalancer_topology})
+
+    @classmethod
+    def _flavor_create(cls, flavor_dict, skip_on_not_implemented=False):
         if cls.lb_admin_flavor_profile_client.is_version_supported(
                 cls.api_version, '2.6'):
-
             # Create a shared flavor profile
             flavor_profile_name = data_utils.rand_name("lb_scenario-setup")
-            flavor_data = {const.LOADBALANCER_TOPOLOGY:
-                           CONF.load_balancer.loadbalancer_topology}
-            flavor_data_json = jsonutils.dumps(flavor_data)
+            flavor_data_json = jsonutils.dumps(flavor_dict)
 
             flavor_profile_kwargs = {
                 const.NAME: flavor_profile_name,
@@ -54,12 +58,12 @@
             }
 
             try:
-                cls.flavor_profile = (
+                flavor_profile = (
                     cls.lb_admin_flavor_profile_client.create_flavor_profile(
                         **flavor_profile_kwargs))
                 cls.addClassResourceCleanup(
                     cls.lb_admin_flavor_profile_client.cleanup_flavor_profile,
-                    cls.flavor_profile[const.ID])
+                    flavor_profile[const.ID])
 
                 flavor_name = data_utils.rand_name("lb_scenario-setup")
                 flavor_description = data_utils.arbitrary_string(size=255)
@@ -68,20 +72,21 @@
                     const.NAME: flavor_name,
                     const.DESCRIPTION: flavor_description,
                     const.ENABLED: True,
-                    const.FLAVOR_PROFILE_ID: cls.flavor_profile[const.ID]}
+                    const.FLAVOR_PROFILE_ID: flavor_profile[const.ID]}
 
-                cls.flavor = cls.lb_admin_flavor_client.create_flavor(
+                flavor = cls.lb_admin_flavor_client.create_flavor(
                     **flavor_kwargs)
                 cls.addClassResourceCleanup(
                     cls.lb_admin_flavor_client.cleanup_a_flavor,
-                    cls.flavor[const.ID])
-                cls.flavor_id = cls.flavor[const.ID]
-            except testtools.TestCase.skipException:
-                LOG.debug("Provider driver %s doesn't support flavors.",
-                          CONF.load_balancer.provider)
-                cls.flavor_profile = None
-                cls.flavor_id = None
-                cls.flavor = None
+                    flavor[const.ID])
+                return flavor[const.ID]
+            except (testtools.TestCase.skipException,
+                    exceptions.NotImplemented):
+                msg = (f"Provider driver {CONF.load_balancer.provider} "
+                       "doesn't support flavors.")
+                LOG.debug(msg)
+                if skip_on_not_implemented:
+                    raise cls.skipException(msg)
 
     @decorators.idempotent_id('a5e2e120-4f7e-4c8b-8aac-cf09cb56711c')
     def test_load_balancer_ipv4_CRUD(self):
@@ -93,7 +98,20 @@
     def test_load_balancer_ipv6_CRUD(self):
         self._test_load_balancer_CRUD(6)
 
-    def _test_load_balancer_CRUD(self, ip_version):
+    @decorators.idempotent_id('c9d8b6dd-ef29-40d8-b329-86d31857df3f')
+    def test_load_balancer_ipv4_CRUD_with_compute_flavor(self):
+        self._test_load_balancer_CRUD(4,
+                                      use_custom_compute_flavor=True)
+
+    @decorators.idempotent_id('2f1c2bdc-0df9-4c1e-be83-910fcd5af8f2')
+    @testtools.skipUnless(CONF.load_balancer.test_with_ipv6,
+                          'IPv6 testing is disabled')
+    def test_load_balancer_ipv6_CRUD_with_compute_flavor(self):
+        self._test_load_balancer_CRUD(6,
+                                      use_custom_compute_flavor=True)
+
+    def _test_load_balancer_CRUD(self, ip_version,
+                                 use_custom_compute_flavor=False):
         """Tests load balancer create, read, update, delete
 
         * Create a fully populated load balancer.
@@ -101,6 +119,33 @@
         * Update the load balancer.
         * Delete the load balancer.
         """
+        if use_custom_compute_flavor:
+            if not self.lb_admin_flavor_profile_client.is_version_supported(
+                    self.api_version, '2.6'):
+                raise self.skipException(
+                    'Flavors and flavor profiles are supported in '
+                    'Octavia API version 2.6 or newer.')
+
+            compute_flavor_kwargs = {
+                const.NAME: data_utils.rand_name("lb_scenario_alt_amp_flavor"),
+                const.RAM: 2048,
+                const.VCPUS: 1,
+                const.DISK: 4,
+            }
+
+            compute_flavor = (
+                self.os_admin_compute_flavors_client.create_flavor(
+                    **compute_flavor_kwargs)['flavor'])
+            self.addCleanup(
+                self.os_admin_compute_flavors_client.delete_flavor,
+                compute_flavor[const.ID])
+
+            flavor_id = self._flavor_create({
+                const.COMPUTE_FLAVOR: compute_flavor[const.ID]
+            }, skip_on_not_implemented=True)
+        else:
+            flavor_id = self.flavor_id
+
         lb_name = data_utils.rand_name("lb_member_lb1-CRUD")
         lb_description = data_utils.arbitrary_string(size=255)
 
@@ -110,8 +155,8 @@
                      const.NAME: lb_name}
 
         if self.lb_admin_flavor_profile_client.is_version_supported(
-                self.api_version, '2.6') and self.flavor_id:
-            lb_kwargs[const.FLAVOR_ID] = self.flavor_id
+                self.api_version, '2.6') and flavor_id:
+            lb_kwargs[const.FLAVOR_ID] = flavor_id
 
         self._setup_lb_network_kwargs(lb_kwargs, ip_version)
 
@@ -147,6 +192,13 @@
             self.assertEqual(lb_kwargs[const.VIP_SUBNET_ID],
                              lb[const.VIP_SUBNET_ID])
 
+        if use_custom_compute_flavor:
+            amphorae = self.lb_admin_amphora_client.list_amphorae(
+                query_params=f'{const.LOADBALANCER_ID}={lb[const.ID]}')
+            amphora = amphorae[0]
+            self.assertEqual(compute_flavor[const.ID],
+                             amphora[const.COMPUTE_FLAVOR])
+
         # Load balancer update
         new_name = data_utils.rand_name("lb_member_lb1-update")
         new_description = data_utils.arbitrary_string(size=255,
diff --git a/octavia_tempest_plugin/tests/test_base.py b/octavia_tempest_plugin/tests/test_base.py
index 55c6223..a04f1cb 100644
--- a/octavia_tempest_plugin/tests/test_base.py
+++ b/octavia_tempest_plugin/tests/test_base.py
@@ -287,6 +287,7 @@
             lb_admin_prefix.AvailabilityZoneClient())
         cls.mem_availability_zone_client = (
             cls.os_roles_lb_member.load_balancer_v2.AvailabilityZoneClient())
+        cls.os_admin_compute_flavors_client = cls.os_admin.flavors_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/releasenotes/notes/add-load-balancer-with-custom-flavor-9a27a1d4142a84d3.yaml b/releasenotes/notes/add-load-balancer-with-custom-flavor-9a27a1d4142a84d3.yaml
new file mode 100644
index 0000000..5d9837c
--- /dev/null
+++ b/releasenotes/notes/add-load-balancer-with-custom-flavor-9a27a1d4142a84d3.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add a load-balancer scenario test with a custom amphora compute flavor
+    (using Octavia flavor and flavor profile features).
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index cde36a0..4513080 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -29,16 +29,6 @@
           - controller
 
 - nodeset:
-    name: octavia-single-node-centos-7
-    nodes:
-      - name: controller
-        label: nested-virt-centos-7
-    groups:
-      - name: tempest
-        nodes:
-          - controller
-
-- nodeset:
     name: octavia-single-node-centos-8
     nodes:
       - name: controller
@@ -774,16 +764,6 @@
                 loadbalancer_topology: ACTIVE_STANDBY
 
 - job:
-    name: octavia-v2-dsvm-py2-scenario-centos-7
-    parent: octavia-v2-dsvm-py2-scenario
-    nodeset: octavia-single-node-centos-7
-    vars:
-      devstack_localrc:
-        OCTAVIA_AMP_BASE_OS: centos
-        OCTAVIA_AMP_DISTRIBUTION_RELEASE_ID: 7
-        OCTAVIA_AMP_IMAGE_SIZE: 3
-
-- job:
     name: octavia-v2-dsvm-scenario-centos-8
     parent: octavia-v2-dsvm-scenario
     nodeset: octavia-single-node-centos-8
@@ -915,6 +895,12 @@
     override-checkout: stable/2024.1
 
 - job:
+    name: octavia-v2-dsvm-tls-barbican-stable-2024-1
+    parent: octavia-v2-dsvm-tls-barbican
+    nodeset: octavia-single-node-ubuntu-jammy
+    override-checkout: stable/2024.1
+
+- job:
     name: octavia-v2-dsvm-tls-barbican-stable-2023-2
     parent: octavia-v2-dsvm-tls-barbican
     nodeset: octavia-single-node-ubuntu-jammy
@@ -1006,11 +992,6 @@
     parent: octavia-v2-dsvm-scenario
 
 - job:
-    name: octavia-v2-dsvm-scenario-centos-7
-    parent: octavia-v2-dsvm-py2-scenario-centos-7
-    nodeset: octavia-single-node-centos-7
-
-- job:
     name: octavia-v2-act-stdby-iptables-dsvm-scenario
     parent: octavia-dsvm-live-base
     pre-run: playbooks/act_stby_iptables/pre.yaml
@@ -1048,22 +1029,6 @@
         override-checkout: 2.30.0
 
 - job:
-    name: octavia-v2-act-stdby-iptables-dsvm-py2-scenario-centos-7
-    parent: octavia-v2-act-stdby-iptables-dsvm-py2-scenario
-    nodeset: octavia-single-node-centos-7
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: False
-        OCTAVIA_AMP_BASE_OS: centos
-        OCTAVIA_AMP_DISTRIBUTION_RELEASE_ID: 7
-        OCTAVIA_AMP_IMAGE_SIZE: 3
-      devstack_local_conf:
-        test-config:
-          "$TEMPEST_CONFIG":
-            load_balancer:
-              amphora_ssh_user: centos
-
-- job:
     name: octavia-v2-act-stdby-dsvm-scenario-base
     parent: octavia-dsvm-live-base
     vars:
@@ -1095,6 +1060,12 @@
     override-checkout: stable/2024.1
 
 - job:
+    name: octavia-v2-act-stdby-dsvm-scenario-stable-2024-1
+    parent: octavia-v2-act-stdby-dsvm-scenario
+    nodeset: octavia-single-node-ubuntu-jammy
+    override-checkout: stable/2024.1
+
+- job:
     name: octavia-v2-act-stdby-dsvm-scenario-stable-2023-2
     parent: octavia-v2-act-stdby-dsvm-scenario
     nodeset: octavia-single-node-ubuntu-jammy