Add hybrid topology test

Add test to verify hybrid topology with Router
and multiple networks where we check cross
nodes connectivity

Related-Prod: PRODX-51599
Change-Id: I42798f591ffa921833e3c2e4c7c1526e14fb448b
diff --git a/ironic_tempest_plugin/common/waiters.py b/ironic_tempest_plugin/common/waiters.py
index e538cd8..d4a5e6a 100644
--- a/ironic_tempest_plugin/common/waiters.py
+++ b/ironic_tempest_plugin/common/waiters.py
@@ -73,6 +73,8 @@
         node = utils.get_node(client, node_id=node_id)
         if node[attr] in status:
             return True
+        elif not node['provision_state']:
+            return False
         elif (abort_on_error_state
               and (node['provision_state'].endswith(' failed')
                    or node['provision_state'] == 'error')):
diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py
index 182ac88..9e995b1 100644
--- a/ironic_tempest_plugin/config.py
+++ b/ironic_tempest_plugin/config.py
@@ -274,7 +274,17 @@
                     "testing purposes with the dhcp-less test scenario."),
     cfg.StrOpt("public_subnet_ip",
                help="The public subnet IP to bind the public router to for "
-                    "dhcp-less testing.")
+                    "dhcp-less testing."),
+    cfg.StrOpt("vm_flavor_id",
+               help="Flavor to create VM server."),
+    cfg.ListOpt('hybrid_topology_netA_network_types',
+                default=['geneve'],
+                help="List of possible network types for hybrid topology "
+                     "netA."),
+    cfg.ListOpt('hybrid_topology_netB_network_types',
+                default=['geneve'],
+                help="List of possible network types for hybrid topology "
+                     "netB.")
 ]
 
 BaremetalFeaturesGroup = [
@@ -307,6 +317,9 @@
                 default=False,
                 help="Define if trunks are supported by networking driver "
                      "with baremetal nodes."),
+    cfg.BoolOpt("hybrid_topology",
+                default=False,
+                help="Support deploying hybrid topology.")
 ]
 
 BaremetalIntrospectionGroup = [
diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
index 376f8e7..9b611a6 100644
--- a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
+++ b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
@@ -121,9 +121,10 @@
             cls.baremetal_client, node_id=node_id, attr='power_state',
             status=state, timeout=CONF.baremetal.power_timeout)
 
-    def wait_node(self, instance_id):
+    @classmethod
+    def wait_node(cls, instance_id):
         """Waits for a node to be associated with instance_id."""
-        ironic_waiters.wait_node_instance_association(self.baremetal_client,
+        ironic_waiters.wait_node_instance_association(cls.baremetal_client,
                                                       instance_id)
 
     @classmethod
@@ -194,6 +195,30 @@
             dest = self.get_remote_client(self.instance)
         dest.validate_authentication()
 
+    def verify_l3_connectivity(self, source_ip, private_key,
+                               destination_ip, conn_expected=True, timeout=15):
+        remote = self.get_remote_client(source_ip, private_key=private_key)
+        remote.validate_authentication()
+
+        output = remote.exec_command('ip route')
+        LOG.debug("Routing table on %s is %s", source_ip, output)
+
+        cmd = 'ping %s -c4 -w4 || exit 0' % destination_ip
+        success_substring = " bytes from %s" % destination_ip
+
+        def ping_remote():
+            output = remote.exec_command(cmd)
+            LOG.debug("Got output %s while pinging %s", output, destination_ip)
+            if conn_expected:
+                return success_substring in output
+            else:
+                return success_substring not in output
+
+        # NOTE(vsaienko): we may lost couple of pings due to missing ARPs
+        # so do several retries to get stable output.
+        res = test_utils.call_until_true(ping_remote, timeout, 1)
+        self.assertTrue(res)
+
     def boot_instance(self, clients=None, keypair=None,
                       net_id=None, fixed_ip=None, **create_kwargs):
         if clients is None:
@@ -302,7 +327,8 @@
         # Verify server connection
         self.get_remote_client(server_ip, server=instance)
 
-    def wait_for_ssh(self, ip_address,
+    @classmethod
+    def wait_for_ssh(cls, ip_address,
                      username=None,
                      private_key=None,
                      server=None,
@@ -310,8 +336,8 @@
                      delay=10):
         def _wait_ssh():
             try:
-                self.get_remote_client(ip_address, username, private_key,
-                                       server=server)
+                cls.get_remote_client(ip_address, username, private_key,
+                                      server=server)
             except Exception:
                 LOG.debug("Failed to get ssh client for %s", ip_address,
                           exc_info=True)
@@ -319,7 +345,7 @@
             return True
 
         res = test_utils.call_until_true(_wait_ssh, timeout, delay)
-        self.assertTrue(res, f"Failed to wait for ssh on {ip_address}")
+        cls.assertTrue(res, f"Failed to wait for ssh on {ip_address}")
 
     def check_vm_connectivity(self,
                               ip_address,
diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy_hybrid.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy_hybrid.py
new file mode 100644
index 0000000..b5577a5
--- /dev/null
+++ b/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy_hybrid.py
@@ -0,0 +1,876 @@
+#
+# Copyright (c) 2015 Mirantis, Inc.
+#
+# 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 netaddr
+from oslo_log import log as logging
+from tempest.api.network import base
+from tempest.common import compute
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from ironic_tempest_plugin.tests.scenario import baremetal_manager
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class BaremetalMultitenancyHybridBase(
+        baremetal_manager.BaremetalScenarioTest,
+        base.BaseAdminNetworkTest):
+    """Check hybrid topology same network type
+
+    Use default tenant network type everywhere
+
+    ---- fip network -----
+                       |
+                     ( r1 ) ------- netB --------
+                       |              |       |
+    --------- netA ------------    (vmB1)   (vmB2)
+            |        |      |       fipB1
+          (bmA)    (vmA1) (vmA2)
+         fipbmA    fipA1
+
+    * Test can ping fips fipbmA, fipA1, fipB1
+    * Test each VM can reach other VMs via FIPs
+    * Test each VM can reach othe VMs via private IPs
+    """
+
+    credentials = ['primary', 'admin', 'system_admin']
+    netA_type = "geneve"
+    netB_type = "geneve"
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaremetalMultitenancyHybridBase, cls).skip_checks()
+        neta_types = CONF.baremetal.hybrid_topology_netA_network_types
+        netb_types = CONF.baremetal.hybrid_topology_netB_network_types
+        if not CONF.baremetal.use_provision_network:
+            msg = 'Ironic/Neutron tenant isolation is not configured.'
+            raise cls.skipException(msg)
+        if (CONF.baremetal.available_nodes is not None
+                and CONF.baremetal.available_nodes < 2):
+            msg = ('Not enough baremetal nodes, %d configured, test requires '
+                   'a minimum of 2') % CONF.baremetal.available_nodes
+            raise cls.skipException(msg)
+        if not CONF.baremetal_feature_enabled.hybrid_topology:
+            msg = 'Hybrid topology not supported'
+            raise cls.skipException(msg)
+
+        if cls.netA_type not in neta_types:
+            msg = 'Hybrid topology netA type %s not supported.' % cls.netA_type
+            raise cls.skipException(msg)
+
+        if cls.netB_type not in netb_types:
+            msg = 'Hybrid topology netB type %s not supported.' % cls.netB_type
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(BaremetalMultitenancyHybridBase, cls).setup_clients()
+        cls.networks_client = cls.os_admin.networks_client
+        cls.routers_client = cls.os_admin.routers_client
+        cls.subnets_client = cls.os_admin.subnets_client
+        cls.routers_client = cls.os_admin.routers_client
+        cls.keypairs_client = cls.os_admin.keypairs_client
+        cls.servers_client = cls.os_admin.servers_client
+        cls.floating_ips_client = cls.os_admin.compute_floating_ips_client
+
+    @classmethod
+    def _delete_keypair(cls, keypair_name, **params):
+        cls.keypairs_client.delete_keypair(keypair_name, **params)
+
+    @classmethod
+    def create_keypair(cls, keypair_name=None,
+                       pub_key=None, keypair_type=None,
+                       user_id=None):
+        if keypair_name is None:
+            keypair_name = data_utils.rand_name(
+                prefix=CONF.resource_name_prefix,
+                name=cls.__class__.__name__ + '-keypair')
+        kwargs = {'name': keypair_name}
+        delete_params = {}
+        if pub_key:
+            kwargs.update({'public_key': pub_key})
+        if keypair_type:
+            kwargs.update({'type': keypair_type})
+        if user_id:
+            kwargs.update({'user_id': user_id})
+            delete_params['user_id'] = user_id
+        body = cls.keypairs_client.create_keypair(**kwargs)['keypair']
+        cls.addClassCleanup(cls._delete_keypair, keypair_name,
+                            **delete_params)
+        return body
+
+    @classmethod
+    def create_server(cls, key_name, flavor, net_id, wait_until='ACTIVE',
+                      fip=True):
+        name = data_utils.rand_name(
+            prefix=CONF.resource_name_prefix,
+            name=cls.__class__.__name__ + '-instance')
+        tenant_network = {'id': net_id}
+        cls.validation_resources = cls.get_class_validation_resources(
+            cls.os_admin)
+        body, _ = compute.create_test_server(
+            cls.os_admin,
+            validatable=False,
+            validation_resources={},
+            tenant_network=tenant_network,
+            key_name=key_name,
+            flavor=flavor,
+            wait_until=wait_until,
+            name=name)
+
+        cls.addClassCleanup(test_utils.call_and_ignore_notfound_exc,
+                            waiters.wait_for_server_termination,
+                            cls.servers_client, body['id'])
+        cls.addClassCleanup(test_utils.call_and_ignore_notfound_exc,
+                            cls.servers_client.delete_server, body['id'])
+        ip = cls.os_admin.interfaces_client.list_interfaces(
+            body['id'])['interfaceAttachments'][0]['fixed_ips'][0][
+                'ip_address']
+        vm_fip = None
+        if fip:
+            vm_fip = cls.attach_fip(body)["floating_ip_address"]
+
+        return body, ip, vm_fip
+
+    @classmethod
+    def attach_fip(cls, server):
+        port = cls.os_admin.interfaces_client.list_interfaces(
+            server['id'])['interfaceAttachments'][0]
+        floating_ip = cls.os_admin.floating_ips_client.create_floatingip(
+            floating_network_id=cls.ext_net_id, port_id=port['port_id'])[
+                'floatingip']
+        cls.addClassCleanup(test_utils.call_and_ignore_notfound_exc,
+                            cls.os_admin.floating_ips_client.delete_floatingip,
+                            floating_ip['id'])
+        return floating_ip
+
+    @classmethod
+    def resource_setup(cls):
+        netA_cidr = netaddr.IPNetwork("192.168.10.0/24")
+        netB_cidr = netaddr.IPNetwork("192.168.11.0/24")
+        cls.ext_net_id = CONF.network.public_network_id
+        vm_flavor = CONF.baremetal.vm_flavor_id
+        cls.ping_timeout = 60
+        netA_kwargs = {"port_security_enabled": False}
+        if cls.netA_type:
+            netA_kwargs["provider:network_type"] = cls.netA_type
+
+        cls.netA = cls.create_network(**netA_kwargs)
+        cls.subnetA = cls.create_subnet(cls.netA, cidr=netA_cidr)
+        cls.r1 = cls.create_router(external_network_id=cls.ext_net_id,
+                                   admin_state_up=True)
+
+        cls.create_router_interface(cls.r1['id'], cls.subnetA['id'])
+        cls.addClassCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.routers_client.remove_router_interface,
+            cls.r1['id'], subnet_id=cls.subnetA['id'])
+
+        netB_kwargs = {"port_security_enabled": False}
+        if cls.netB_type:
+            netB_kwargs["provider:network_type"] = cls.netB_type
+        cls.netB = cls.create_network(**netB_kwargs)
+        cls.subnetB = cls.create_subnet(cls.netB, cidr=netB_cidr)
+
+        cls.create_router_interface(cls.r1['id'], cls.subnetB['id'])
+        cls.addClassCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.routers_client.remove_router_interface,
+            cls.r1['id'], subnet_id=cls.subnetB['id'])
+
+        cls.keypair = cls.create_keypair()
+
+        cls.bmA1, cls.bmA1_ip, cls.bmA1_fip = cls.create_server(
+            key_name=cls.keypair['name'],
+            flavor=None,
+            net_id=cls.netA['id'],
+            fip=True
+        )
+
+        cls.vmA1, cls.vmA1_ip, cls.vmA1_fip = cls.create_server(
+            key_name=cls.keypair['name'],
+            flavor=vm_flavor,
+            net_id=cls.netA['id'],
+            fip=True
+        )
+
+        cls.vmA2, cls.vmA2_ip, cls.vmA2_fip = cls.create_server(
+            key_name=cls.keypair['name'],
+            flavor=vm_flavor,
+            net_id=cls.netA['id'],
+            fip=False
+        )
+        cls.vmB2, cls.vmB2_ip, cls.vmB2_fip = cls.create_server(
+            key_name=cls.keypair['name'],
+            flavor=vm_flavor,
+            net_id=cls.netB['id'],
+            fip=False,
+        )
+
+        cls.vmB1, cls.vmB1_ip, cls.vmB1_fip = cls.create_server(
+            key_name=cls.keypair['name'],
+            flavor=vm_flavor,
+            net_id=cls.netB['id'],
+            fip=True,
+        )
+        # Make sure latest VM with FIP booted so we can assume
+        # Previous VMs are booted as well.
+        cls.wait_for_ssh(
+            cls.vmB1_fip,
+            private_key=cls.keypair['private_key'])
+
+    def _test_connectivity_fip_vma1(self):
+        self.assertTrue(
+            self.ping_ip_address(
+                self.vmA1_fip, should_succeed=True),
+            "Can't reach vmA1 FIP")
+
+    def _test_connectivity_fip_vmb1(self):
+        self.assertTrue(
+            self.ping_ip_address(
+                self.vmB1_fip, should_succeed=True),
+            "Can't reach vmB1 FIP")
+
+    def _test_connectivity_fip_bma1(self):
+        self.assertTrue(
+            self.ping_ip_address(
+                self.bmA1_fip, should_succeed=True),
+            "Can't reach bmA1 FIP")
+
+    def _test_connectivity_bma1_vma1(self):
+        self.verify_l3_connectivity(
+            self.bmA1_fip, self.keypair['private_key'],
+            self.vmA1_ip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_bma1_vma1_fip(self):
+        self.verify_l3_connectivity(
+            self.bmA1_fip, self.keypair['private_key'],
+            self.vmA1_fip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_bma1_vma2(self):
+        self.verify_l3_connectivity(
+            self.bmA1_fip, self.keypair['private_key'],
+            self.vmA2_ip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self.verify_l3_connectivity(
+            self.bmA1_fip, self.keypair['private_key'],
+            self.vmB1_ip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_bma1_vmb1_fip(self):
+        self.verify_l3_connectivity(
+            self.bmA1_fip, self.keypair['private_key'],
+            self.vmB1_fip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self.verify_l3_connectivity(
+            self.bmA1_fip, self.keypair['private_key'],
+            self.vmB2_ip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_vma1_vmb1(self):
+        self.verify_l3_connectivity(
+            self.vmA1_fip, self.keypair['private_key'],
+            self.vmB1_ip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_vma1_vmb1_fip(self):
+        self.verify_l3_connectivity(
+            self.vmA1_fip, self.keypair['private_key'],
+            self.vmB1_fip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_vma1_vmb2(self):
+        self.verify_l3_connectivity(
+            self.vmA1_fip, self.keypair['private_key'],
+            self.vmB2_ip, conn_expected=True, timeout=self.ping_timeout)
+
+    def _test_connectivity_vmb1_vmb2(self):
+        self.verify_l3_connectivity(
+            self.vmB1_fip, self.keypair['private_key'],
+            self.vmB2_ip, conn_expected=True, timeout=self.ping_timeout)
+
+
+class BaremetalMultitenancyHybridGeneveGeneve(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "geneve"
+    netB_type = "geneve"
+
+    @decorators.idempotent_id('a431f9ee-8a1f-422b-afb7-59e54c557881')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('c01afc98-c8a3-44e5-a4c3-c4e33ac0496e')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('d616ea97-646c-4fa6-9f7e-1259f44caa57')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('038b5b6c-73ab-4ae3-9911-9dbf38dfc3d6')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('b5d36955-3af1-416f-ba11-3047a60c0340')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('f6febda2-ce2b-4c9e-a61a-20393bb4abd1')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('892946ce-8f18-461a-8082-4fab217768ac')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('5a2d04a0-4a19-4068-8221-cd5addd4eb35')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('a9f04d6a-ffef-4993-906b-3fd5152c145c')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('f2874a76-faaa-400f-84b1-52625d4cdd5c')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('2b934e22-1aa2-4296-8ebd-ad0788ec23a0')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('a60e714f-0a20-47bb-85e6-4e62b535c522')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('472ab63b-78bd-45cf-a93e-7e134c5c3ca3')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridVxlanVxlan(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "vxlan"
+    netB_type = "vxlan"
+
+    @decorators.idempotent_id('b477d023-0cff-4abc-919d-1416682d8427')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('a6474851-30a3-488f-9d6b-003b2d276910')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('daa194a5-1e39-4494-899d-414565eff818')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('9a80fcd9-1bd9-4e0b-920f-c355a6fd7ce5')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('a518057e-9490-40bd-80aa-27e15798a719')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('d5ffce46-96af-4790-974c-72f794c98808')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('77c095d0-d61c-49e6-abff-f5f74f1465ac')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('67e9b7a4-d7f2-43f5-a0ab-64f3688064ed')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('3a526609-d337-4059-9516-d58670a64df2')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('50e7a999-ffad-497e-baec-d2fb5f149c80')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('0a97471c-8d1f-4d87-916f-a64f55a3be93')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('5ad1d903-eb07-4d43-a433-00ba45a3d597')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('c9050db1-63ec-477f-997c-bb32d04b73c5')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridVlanVlan(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "vlan"
+    netB_type = "vlan"
+
+    @decorators.idempotent_id('98d035a8-027f-4dfe-8f2f-75a27c31324e')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('e36b17d5-acab-485a-8a41-d028062c7e72')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('f369fdda-bd91-4948-84c4-071cb0b2d820')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('8a157a46-7db2-4733-ae68-5ac1eeda3e6c')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('e91473bf-fc1a-4e04-acaf-b556591fdc45')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('25a738b8-fe92-46d7-9c26-085678685f7e')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('0c30b479-cdd3-4a6c-ab04-e17c6d272cb1')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('b3777955-a25a-43e4-82dd-36ea82245c32')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('4dae2535-9918-4dee-86fe-3ba1353241af')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('0018551c-99ad-4fce-8085-5367e2e7397a')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('a5441070-9796-4816-83a2-7b8ea25bbd12')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('8c5aaa38-0e3a-455f-9452-55c71dc6c65b')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('584a6e53-8dc2-471a-84b5-a00b91f767ed')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridVlanGeneve(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "vlan"
+    netB_type = "geneve"
+
+    @decorators.idempotent_id('b2a00da0-24b0-45c3-a67b-ecbf3c7ba0f2')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('115ddbdc-664c-49ed-bdf0-5af6970fdfc6')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('1b065798-50ff-4c17-bf99-e4532659a8fb')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('d74b7931-80a7-490c-82f1-c70de1cfc310')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('0ba078f4-3bee-442d-b2c1-4d4c425b45c6')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('0d9757ba-11cc-414e-8549-e8df4d68800e')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('227b0f66-e5dc-4125-9141-d8da1a502947')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('d3ac33c8-9fae-4a62-881d-359e793db590')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('3bda4b58-1741-4ac8-93c7-5cbd1fe37866')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('585f7b40-9bfe-4f2d-a8da-eb82a1376975')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('17b19d91-ccef-4192-801d-d6ccc3a7f7bb')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('cd58c82c-ac84-42c0-865a-68f9a48936d8')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('e7c55408-2dbd-4f19-be6b-e08eae163dea')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridGeneveVlan(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "geneve"
+    netB_type = "vlan"
+
+    @decorators.idempotent_id('a04724a8-501c-4892-be73-0b32403d6c4c')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('9013e093-0665-421b-ada2-50a2b1a35aeb')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('a277b123-3309-4642-abbd-aa3ff74de30c')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('2496c052-cb3b-4680-9dd7-4cb3e1272d26')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('28b1441c-0335-4228-871b-ebd0ac97b920')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('5532ad33-c81c-4b02-961c-31b5e0711603')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('ffb12276-8c6f-4554-8fbb-3496d4423365')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('0b92d2b6-4edc-4dfd-b358-eee98164930e')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('85ac259a-6103-4382-8e82-057e12b7dadc')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('630b0b5a-b687-4aa2-9a5c-1c04f33fe5e9')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('d159857e-206c-4851-9574-f0bcbf5261e9')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('7f4b2563-0eb6-445c-a511-27e999340525')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('7eecabdf-d924-4750-98cb-b4fc125dd27a')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridGeneveVxlan(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "geneve"
+    netB_type = "vxlan"
+
+    @decorators.idempotent_id('f38cc224-3efd-4b45-9924-98153c3dc5f4')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('f5486e78-77c0-4f3d-9db0-464615990610')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('19528d05-5786-4db0-b179-86e5516f464e')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('10d66679-7858-4f3e-9a31-12b65640fb76')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('8da0656d-6e66-472a-9e4b-26afd70b8eb5')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('ecc8f2c2-39c6-4927-aa2e-2135f299b884')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('297a7e1d-3bf4-4cf7-a017-9a839270a807')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('164147fa-fd5c-47ab-adb7-71669e03da54')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('a4373d5a-db3d-4c6e-b400-3f3728f49714')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('bffeedd5-6bc2-4065-9656-3871434d5658')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('24e180ab-8bcc-4ec6-8e3a-7a24269c0853')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('58fab1e3-77af-42b8-8c75-6cc073e31290')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('331b9837-d1fa-4b64-a735-58b46001f46c')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridVxlanGeneve(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "vxlan"
+    netB_type = "geneve"
+
+    @decorators.idempotent_id('9f4e3529-9d78-4abb-9c69-a87e7fdc3a55')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('c5edeec0-5d2c-4d8c-9364-e4f867e06d88')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('4132b53b-59f3-4eef-a8f9-e2e94e9dce20')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('e3069595-6b72-4a5f-935c-8b8a43b4924b')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('d3ca217f-cf17-4cbc-98eb-3c50162897d5')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('c0a541b7-21c7-4072-aa46-cea3177ec535')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('2cd56d2f-a735-4d56-8d5a-9229e79e3e9e')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('74354b6e-b53f-475a-b216-1c74fac02a33')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('8b7432cb-f42e-4e77-9e7d-0bdb1eb2c3ad')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('ee0ee344-d976-4722-8877-4b0b9c3be190')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('e8246132-eb56-4d1a-b9fe-1049093597b7')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('6bcc9c2e-93e7-4400-94b2-0550ba24e073')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('8cda0cdb-c3d9-4fa6-9f8a-884ad10c46b9')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridVxlanVlan(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "vxlan"
+    netB_type = "vlan"
+
+    @decorators.idempotent_id('add39843-6fec-4e24-ae6d-e9c00ae76cfa')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('8884568e-8b6a-4f44-9365-2ed543b7b4bc')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('c8f74c85-45a3-449e-88ad-0d5ddee1ad2c')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('27f53ed5-b361-478f-806b-adbe793464be')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('fc23d970-d95e-458d-94eb-1cc36d297150')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('66e55163-20a4-44e4-887f-aee19baa8881')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('398b3071-a8f4-44e4-84d1-8c998d216bb4')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('79f8e743-2ed3-452b-bdd1-d93475243a70')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('0a2ecb7c-ba7b-4895-b290-60ad0e886e91')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('98e1742b-7bd5-465f-ab88-ac2d734b3a1b')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('3ce41d0c-bfae-49f9-9490-999e543cd7da')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('7663be3f-9881-4354-8491-ec631dbf8e37')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('a7a21206-fd1c-4480-a38b-a89ff0b5d837')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()
+
+
+class BaremetalMultitenancyHybridVlanVxlan(
+        BaremetalMultitenancyHybridBase):
+
+    netA_type = "vlan"
+    netB_type = "vxlan"
+
+    @decorators.idempotent_id('7360a73e-a07e-4809-8372-414a15a89b78')
+    def test_connectivity_fip_vma1(self):
+        self._test_connectivity_fip_vma1()
+
+    @decorators.idempotent_id('9372d534-ad5b-4f7d-aa3f-2ac57b83f119')
+    def test_connectivity_fip_vmb1(self):
+        self._test_connectivity_fip_vmb1()
+
+    @decorators.idempotent_id('0d731398-9dc9-4d48-8baa-f07898ae30a5')
+    def test_connectivity_fip_bma1(self):
+        self._test_connectivity_fip_bma1()
+
+    @decorators.idempotent_id('1d017810-8c91-418c-b112-46804c066d96')
+    def test_connectivity_bma1_vma1(self):
+        self._test_connectivity_bma1_vma1()
+
+    @decorators.idempotent_id('c0ae2d79-bef5-4c94-879c-6ce0eeb7eb56')
+    def test_connectivity_bma1_vma1_fip(self):
+        self._test_connectivity_bma1_vma1_fip()
+
+    @decorators.idempotent_id('c600b6cd-49c1-4515-b5f1-b1c57b0bbfc5')
+    def test_connectivity_bma1_vma2(self):
+        self._test_connectivity_bma1_vma2()
+
+    @decorators.idempotent_id('960fafde-d21a-488a-81bf-174e0ed76253')
+    def test_connectivity_bma1_vmb1(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb1()
+
+    @decorators.idempotent_id('4a173ac9-04d4-4612-864c-ed73bd7595e0')
+    def test_connectivity_bma1_vmb1_fip(self):
+        self._test_connectivity_bma1_vmb1_fip()
+
+    @decorators.idempotent_id('7898cff1-71c2-4443-936a-6189d61215df')
+    def test_connectivity_bma1_vmb2(self):
+        # NOTE(vsaienko): this may fail when vmb is located
+        # on compute without vms in netA.
+        self._test_connectivity_bma1_vmb2()
+
+    @decorators.idempotent_id('760a3acf-6151-49d3-955e-5a873d779c63')
+    def test_connectivity_vma1_vmb1(self):
+        self._test_connectivity_vma1_vmb1()
+
+    @decorators.idempotent_id('df281f44-b93d-4f8f-8916-53086453bab8')
+    def test_connectivity_vma1_vmb1_fip(self):
+        self._test_connectivity_vma1_vmb1_fip()
+
+    @decorators.idempotent_id('af215661-7748-494f-822c-69352d929cd6')
+    def test_connectivity_vma1_vmb2(self):
+        self._test_connectivity_vma1_vmb2()
+
+    @decorators.idempotent_id('5aabaa14-cbed-45f6-a0ea-13952ab3f2a6')
+    def test_connectivity_vmb1_vmb2(self):
+        self._test_connectivity_vmb1_vmb2()