Merge "Bump tox minversion to 3.18.0"
diff --git a/octavia_tempest_plugin/common/cert_utils.py b/octavia_tempest_plugin/common/cert_utils.py
index f99ce88..753da6b 100644
--- a/octavia_tempest_plugin/common/cert_utils.py
+++ b/octavia_tempest_plugin/common/cert_utils.py
@@ -17,6 +17,8 @@
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.asymmetric import rsa
 from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.serialization import NoEncryption
+from cryptography.hazmat.primitives.serialization import pkcs12
 from cryptography import x509
 from cryptography.x509.oid import NameOID
 import OpenSSL
@@ -184,12 +186,29 @@
     :param server_key: A cryptography key (x509) object.
     :returns: A pkcs12 bundle.
     """
-    # TODO(johnsom) Replace with cryptography once it supports creating pkcs12
-    pkcs12 = OpenSSL.crypto.PKCS12()
-    pkcs12.set_privatekey(
-        OpenSSL.crypto.PKey.from_cryptography_key(server_key))
-    pkcs12.set_certificate(OpenSSL.crypto.X509.from_cryptography(server_cert))
-    return pkcs12.export()
+    # Use the PKCS12 serialization function from cryptography if it exists
+    # (>=3.0), otherwise use the pyOpenSSL module.
+    #
+    # The PKCS12 class of the pyOpenSSL module is not compliant with FIPS.
+    # It uses the SHA1 function [0] which is not allowed when generating
+    # digital signatures [1]
+    #
+    # [0] https://github.com/pyca/pyopenssl/blob/
+    #       65ca53a7a06a7c78c1749200a6b3a007e47d3214/src/OpenSSL/
+    #       crypto.py#L2748-L2749
+    # [1] https://nvlpubs.nist.gov/nistpubs/SpecialPublications/
+    #       NIST.SP.800-131Ar1.pdf
+    if hasattr(pkcs12, 'serialize_key_and_certificates'):
+        p12 = pkcs12.serialize_key_and_certificates(
+            b'', server_key, server_cert,
+            cas=None, encryption_algorithm=NoEncryption())
+    else:
+        p12 = OpenSSL.crypto.PKCS12()
+        p12.set_privatekey(
+            OpenSSL.crypto.PKey.from_cryptography_key(server_key))
+        p12.set_certificate(OpenSSL.crypto.X509.from_cryptography(server_cert))
+        p12 = p12.export()
+    return p12
 
 
 def generate_certificate_revocation_list(ca_cert, ca_key, cert_to_revoke):
diff --git a/octavia_tempest_plugin/tests/api/v2/test_listener.py b/octavia_tempest_plugin/tests/api/v2/test_listener.py
index 152f6ff..1c2fa75 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_listener.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_listener.py
@@ -540,6 +540,11 @@
                 const.OPERATING_STATUS, const.ONLINE,
                 CONF.load_balancer.build_interval,
                 CONF.load_balancer.build_timeout)
+            listener3 = waiters.wait_for_status(
+                self.mem_listener_client.show_listener, listener3[const.ID],
+                const.OPERATING_STATUS, const.OFFLINE,
+                CONF.load_balancer.build_interval,
+                CONF.load_balancer.build_timeout)
 
         # Test that a different users cannot see the lb_member listeners.
         expected_allowed = []
@@ -999,35 +1004,24 @@
                 self.api_version, '2.12'):
             self.assertEqual(self.allowed_cidrs, listener[const.ALLOWED_CIDRS])
 
-        # Test that a user, without the load balancer member role, cannot
-        # use this command
+        # Test that a user without the loadbalancer role cannot
+        # update a listener.
+        expected_allowed = []
+        if CONF.load_balancer.RBAC_test_type == const.OWNERADMIN:
+            expected_allowed = ['os_admin', 'os_roles_lb_admin',
+                                'os_roles_lb_member']
+        if CONF.load_balancer.RBAC_test_type == const.KEYSTONE_DEFAULT_ROLES:
+            expected_allowed = ['os_system_admin', 'os_roles_lb_member']
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
-            self.assertRaises(
-                exceptions.Forbidden,
-                self.listener_client.update_listener,
-                listener[const.ID], admin_state_up=True)
-
-        # Assert we didn't go into PENDING_*
-        listener_check = self.mem_listener_client.show_listener(
-            listener[const.ID])
-        self.assertEqual(const.ACTIVE,
-                         listener_check[const.PROVISIONING_STATUS])
-        self.assertFalse(listener_check[const.ADMIN_STATE_UP])
-
-        # Test that a user, without the load balancer member role, cannot
-        # update this listener
-        if not CONF.load_balancer.RBAC_test_type == const.NONE:
-            member2_client = self.member2_listener_client
-            self.assertRaises(exceptions.Forbidden,
-                              member2_client.update_listener,
-                              listener[const.ID], admin_state_up=True)
-
-        # Assert we didn't go into PENDING_*
-        listener_check = self.mem_listener_client.show_listener(
-            listener[const.ID])
-        self.assertEqual(const.ACTIVE,
-                         listener_check[const.PROVISIONING_STATUS])
-        self.assertFalse(listener_check[const.ADMIN_STATE_UP])
+            expected_allowed = ['os_system_admin', 'os_roles_lb_admin',
+                                'os_roles_lb_member']
+        if expected_allowed:
+            self.check_update_RBAC_enforcement(
+                'ListenerClient', 'update_listener',
+                expected_allowed,
+                status_method=self.mem_listener_client.show_listener,
+                obj_id=listener[const.ID], listener_id=listener[const.ID],
+                admin_state_up=True)
 
         new_name = data_utils.rand_name("lb_member_listener1-UPDATED")
         new_description = data_utils.arbitrary_string(size=255,
@@ -1183,21 +1177,23 @@
             CONF.load_balancer.build_interval,
             CONF.load_balancer.build_timeout)
 
-        # Test that a user without the load balancer role cannot
-        # delete this listener
+        # Test that a user without the loadbalancer role cannot
+        # delete a listener.
+        expected_allowed = []
+        if CONF.load_balancer.RBAC_test_type == const.OWNERADMIN:
+            expected_allowed = ['os_admin', 'os_roles_lb_admin',
+                                'os_roles_lb_member']
+        if CONF.load_balancer.RBAC_test_type == const.KEYSTONE_DEFAULT_ROLES:
+            expected_allowed = ['os_system_admin', 'os_roles_lb_member']
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
-            self.assertRaises(
-                exceptions.Forbidden,
-                self.listener_client.delete_listener,
-                listener[const.ID])
-
-        # Test that a different user, with the load balancer member role
-        # cannot delete this listener
-        if not CONF.load_balancer.RBAC_test_type == const.NONE:
-            member2_client = self.member2_listener_client
-            self.assertRaises(exceptions.Forbidden,
-                              member2_client.delete_listener,
-                              listener[const.ID])
+            expected_allowed = ['os_system_admin', 'os_roles_lb_admin',
+                                'os_roles_lb_member']
+        if expected_allowed:
+            self.check_update_RBAC_enforcement(
+                'ListenerClient', 'delete_listener',
+                expected_allowed,
+                status_method=self.mem_listener_client.show_listener,
+                obj_id=listener[const.ID], listener_id=listener[const.ID])
 
         self.mem_listener_client.delete_listener(listener[const.ID])
 
diff --git a/octavia_tempest_plugin/tests/api/v2/test_member.py b/octavia_tempest_plugin/tests/api/v2/test_member.py
index aa7cf25..fee2893 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_member.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_member.py
@@ -141,6 +141,8 @@
         cls.listener_pool_cache[listener_pool_key] = pool[const.ID]
         return pool[const.ID]
 
+
+class MemberAPITest1(MemberAPITest):
     @decorators.idempotent_id('0684575a-0970-4fa8-8006-10c2b39c5f2b')
     def test_ipv4_HTTP_LC_alt_monitor_member_create(self):
         pool_id = self._listener_pool_create(
@@ -1375,6 +1377,8 @@
             self.assertTrue(not any(["" in member[const.TAGS]
                                      for member in list_of_members]))
 
+
+class MemberAPITest2(MemberAPITest):
     @decorators.idempotent_id('2674b363-7922-494a-b121-cf415dbbb716')
     def test_HTTP_LC_alt_monitor_member_show(self):
         pool_id = self._listener_pool_create(
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 3f41892..9664c57 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
@@ -337,7 +337,10 @@
                 return False
             return True
 
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_cb)
         ca_store = context.get_cert_store()
@@ -473,7 +476,10 @@
             return True
 
         # Test that the default certificate is used with no SNI host request
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_server_cb)
         ca_store = context.get_cert_store()
@@ -485,7 +491,10 @@
         sock.do_handshake()
 
         # Test that the default certificate is used with bogus SNI host request
-        context = SSL.Context(SSL.TLSv1_2_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.TLSv1_2_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_server_cb)
         ca_store = context.get_cert_store()
@@ -498,7 +507,10 @@
         sock.do_handshake()
 
         # Test that the SNI1 certificate is used when SNI1 host is specified
-        context = SSL.Context(SSL.TLSv1_2_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.TLSv1_2_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_SNI1_cb)
         ca_store = context.get_cert_store()
@@ -512,7 +524,10 @@
         sock.do_handshake()
 
         # Test that the SNI2 certificate is used when SNI2 host is specified
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_SNI2_cb)
         ca_store = context.get_cert_store()
@@ -634,7 +649,10 @@
             return True
 
         # Test that the default certificate is used with no SNI host request
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_server_cb)
         ca_store = context.get_cert_store()
@@ -646,7 +664,10 @@
         sock.do_handshake()
 
         # Test that the SNI1 certificate is used when SNI1 host is specified
-        context = SSL.Context(SSL.TLSv1_2_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.TLSv1_2_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_SNI1_cb)
         ca_store = context.get_cert_store()
@@ -660,7 +681,10 @@
         sock.do_handshake()
 
         # Test that the default certificate is used when SNI2 host is specified
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_server_cb)
         ca_store = context.get_cert_store()
@@ -675,7 +699,10 @@
 
         # Test that the SNI2 certificate is used with no SNI host request
         # on listener 2, SNI2 is the default cert for listener 2
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_SNI2_cb)
         ca_store = context.get_cert_store()
@@ -688,7 +715,10 @@
 
         # Test that the SNI2 certificate is used with listener 1 host request
         # on listener 2, SNI2 is the default cert for listener 2
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_SNI2_cb)
         ca_store = context.get_cert_store()
@@ -703,7 +733,10 @@
 
         # Test that the SNI2 certificate is used with SNI1 host request
         # on listener 2, SNI2 is the default cert for listener 2
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        try:
+            context = SSL.Context(SSL.TLS_METHOD)
+        except AttributeError:
+            context = SSL.Context(SSL.SSLv23_METHOD)
         context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                            _verify_SNI2_cb)
         ca_store = context.get_cert_store()
diff --git a/setup.cfg b/setup.cfg
index d7d3196..954fe98 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,12 @@
 [metadata]
 name = octavia-tempest-plugin
 summary = Tempest plugin for Octavia
-description-file =
+description_file =
     README.rst
 author = OpenStack
-author-email = openstack-discuss@lists.openstack.org
-home-page = https://docs.openstack.org/octavia-tempest-plugin/latest/
-python-requires = >=3.6
+author_email = openstack-discuss@lists.openstack.org
+home_page = https://docs.openstack.org/octavia-tempest-plugin/latest/
+python_requires = >=3.6
 classifier =
     Environment :: OpenStack
     Intended Audience :: Information Technology
@@ -22,7 +22,7 @@
     Programming Language :: Python :: 3.8
 
 [global]
-setup-hooks =
+setup_hooks =
     pbr.hooks.setup_hook
 
 [files]
diff --git a/tox.ini b/tox.ini
index a0a2885..ed7a3cd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -45,7 +45,6 @@
 [testenv:docs]
 deps =
     -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-    -r{toxinidir}/requirements.txt
     -r{toxinidir}/test-requirements.txt
     -r{toxinidir}/doc/requirements.txt
 allowlist_externals = rm
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index ef613aa..f4dd9db 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -545,11 +545,6 @@
     override-checkout: stable/wallaby
 
 - job:
-    name: octavia-v2-dsvm-noop-api-stable-victoria
-    parent: octavia-v2-dsvm-noop-api
-    override-checkout: stable/victoria
-
-- job:
     name: octavia-v2-dsvm-scenario
     parent: octavia-dsvm-live-base
     vars:
@@ -617,10 +612,6 @@
     name: octavia-v2-dsvm-scenario-stable-wallaby
     parent: octavia-v2-dsvm-scenario
     override-checkout: stable/wallaby
-- job:
-    name: octavia-v2-dsvm-scenario-stable-victoria
-    parent: octavia-v2-dsvm-scenario
-    override-checkout: stable/victoria
 
 # Legacy jobs for the transition to the act-stdby two node jobs
 - job:
@@ -723,6 +714,13 @@
         OCTAVIA_AMP_BASE_OS: centos
         OCTAVIA_AMP_DISTRIBUTION_RELEASE_ID: 9-stream
         OCTAVIA_AMP_IMAGE_SIZE: 3
+        OCTAVIA_SSH_KEY_TYPE: ecdsa
+        OCTAVIA_SSH_KEY_BITS: 256
+      devstack_local_conf:
+        test-config:
+          "$TEMPEST_CONFIG":
+            validation:
+              ssh_key_type: ecdsa
 
 - job:
     name: octavia-v2-dsvm-scenario-ubuntu-focal
@@ -790,18 +788,6 @@
     override-checkout: stable/train
 
 - job:
-    name: octavia-v2-dsvm-tls-barbican-stable-rocky
-    parent: octavia-v2-dsvm-tls-barbican
-    nodeset: openstack-single-node-xenial
-    override-checkout: stable/rocky
-
-- job:
-    name: octavia-v2-dsvm-tls-barbican-stable-queens
-    parent: octavia-v2-dsvm-tls-barbican
-    nodeset: openstack-single-node-xenial
-    override-checkout: stable/queens
-
-- job:
     name: octavia-v2-dsvm-spare-pool
     parent: octavia-v2-dsvm-scenario
     vars:
@@ -827,26 +813,11 @@
         override-checkout: 2.30.0
 
 - job:
-    name: octavia-v2-dsvm-spare-pool-stable-yoga
-    parent: octavia-v2-dsvm-spare-pool
-    override-checkout: stable/yoga
-
-- job:
-    name: octavia-v2-dsvm-spare-pool-stable-xena
-    parent: octavia-v2-dsvm-spare-pool
-    override-checkout: stable/xena
-
-- job:
     name: octavia-v2-dsvm-spare-pool-stable-wallaby
     parent: octavia-v2-dsvm-spare-pool
     override-checkout: stable/wallaby
 
 - job:
-    name: octavia-v2-dsvm-spare-pool-stable-victoria
-    parent: octavia-v2-dsvm-spare-pool
-    override-checkout: stable/victoria
-
-- job:
     name: octavia-v2-dsvm-cinder-amphora
     parent: octavia-v2-dsvm-scenario
     required-projects:
@@ -990,11 +961,6 @@
     parent: octavia-v2-act-stdby-dsvm-scenario
     override-checkout: stable/wallaby
 
-- job:
-    name: octavia-v2-act-stdby-dsvm-scenario-stable-victoria
-    parent: octavia-v2-act-stdby-dsvm-scenario
-    override-checkout: stable/victoria
-
 ######### Third party jobs ##########
 
 - job:
diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml
index 9e4b890..116da49 100644
--- a/zuul.d/projects.yaml
+++ b/zuul.d/projects.yaml
@@ -12,18 +12,15 @@
         - octavia-v2-dsvm-noop-api-stable-yoga
         - octavia-v2-dsvm-noop-api-stable-xena
         - octavia-v2-dsvm-noop-api-stable-wallaby
-        - octavia-v2-dsvm-noop-api-stable-victoria
         - octavia-v2-dsvm-noop-api-scoped-tokens
         - octavia-v2-dsvm-scenario
         - octavia-v2-dsvm-scenario-stable-yoga
         - octavia-v2-dsvm-scenario-stable-xena
         - octavia-v2-dsvm-scenario-stable-wallaby
-        - octavia-v2-dsvm-scenario-stable-victoria
         - octavia-v2-dsvm-tls-barbican
         - octavia-v2-dsvm-tls-barbican-stable-yoga
         - octavia-v2-dsvm-tls-barbican-stable-xena
         - octavia-v2-dsvm-tls-barbican-stable-wallaby
-        - octavia-v2-dsvm-tls-barbican-stable-victoria
         - octavia-v2-dsvm-scenario-ipv6-only:
             voting: false
         - octavia-v2-dsvm-scenario-centos-8-stream:
@@ -40,18 +37,8 @@
             voting: false
         - octavia-v2-act-stdby-dsvm-scenario-stable-wallaby:
             voting: false
-        - octavia-v2-act-stdby-dsvm-scenario-stable-victoria:
-            voting: false
-        - octavia-v2-dsvm-spare-pool:
-            voting: false
-        - octavia-v2-dsvm-spare-pool-stable-yoga:
-            voting: false
-        - octavia-v2-dsvm-spare-pool-stable-xena:
-            voting: false
         - octavia-v2-dsvm-spare-pool-stable-wallaby:
             voting: false
-        - octavia-v2-dsvm-spare-pool-stable-victoria:
-            voting: false
         - octavia-v2-dsvm-cinder-amphora:
             voting: false
         # Third party provider jobs
@@ -59,23 +46,20 @@
             voting: false
         - neutron-ovn-provider-v2-scenario:
             voting: false
+    queue: octavia
     gate:
       fail-fast: true
-      queue: octavia
       jobs:
         - octavia-v2-dsvm-noop-api
         - octavia-v2-dsvm-noop-api-stable-yoga
         - octavia-v2-dsvm-noop-api-stable-xena
         - octavia-v2-dsvm-noop-api-stable-wallaby
-        - octavia-v2-dsvm-noop-api-stable-victoria
         - octavia-v2-dsvm-noop-api-scoped-tokens
         - octavia-v2-dsvm-scenario
         - octavia-v2-dsvm-scenario-stable-yoga
         - octavia-v2-dsvm-scenario-stable-xena
         - octavia-v2-dsvm-scenario-stable-wallaby
-        - octavia-v2-dsvm-scenario-stable-victoria
         - octavia-v2-dsvm-tls-barbican
         - octavia-v2-dsvm-tls-barbican-stable-yoga
         - octavia-v2-dsvm-tls-barbican-stable-xena
         - octavia-v2-dsvm-tls-barbican-stable-wallaby
-        - octavia-v2-dsvm-tls-barbican-stable-victoria