Check connection between VMs with BGPVPN after live migration

Related-prod: PRODX-26006

Change-Id: I066c34d57ba4a0cd8f6d62c9e75671fc930eb75b
(cherry picked from commit 2a34038b4963c3b74012ed9124f87acbf54eedf5)
diff --git a/neutron_tempest_plugin/bgpvpn/scenario/manager.py b/neutron_tempest_plugin/bgpvpn/scenario/manager.py
index 4ff1c0d..b342bc6 100644
--- a/neutron_tempest_plugin/bgpvpn/scenario/manager.py
+++ b/neutron_tempest_plugin/bgpvpn/scenario/manager.py
@@ -14,9 +14,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
 import subprocess
 
 import netaddr
+
 from oslo_log import log
 from oslo_utils import netutils
 
@@ -31,10 +33,56 @@
 from tempest.lib import exceptions as lib_exc
 import tempest.test
 
+
 CONF = config.CONF
 
 LOG = log.getLogger(__name__)
 
+NET_A = 'A'
+NET_A_BIS = 'A-Bis'
+NET_B = 'B'
+NET_C = 'C'
+
+if "SUBNETPOOL_PREFIX_V4" in os.environ:
+    subnet_base = netaddr.IPNetwork(os.environ['SUBNETPOOL_PREFIX_V4'])
+    if subnet_base.prefixlen > 21:
+        raise Exception("if SUBNETPOOL_PREFIX_V4 is set, it needs to offer "
+                        "space for at least 8 /24 subnets")
+else:
+    subnet_base = netaddr.IPNetwork("10.100.0.0/16")
+
+
+def assign_24(idx):
+    # how many addresses in a /24:
+    range_size = 2 ** (32 - 24)
+    return netaddr.cidr_merge(
+        subnet_base[range_size * idx:range_size * (idx + 1)])[0]
+
+
+S1A = assign_24(1)
+S2A = assign_24(2)
+S1B = assign_24(4)
+S2B = assign_24(6)
+S1C = assign_24(6)
+NET_A_S1 = str(S1A)
+NET_A_S2 = str(S2A)
+NET_B_S1 = str(S1B)
+NET_B_S2 = str(S2B)
+NET_C_S1 = str(S1C)
+IP_A_S1_1 = str(S1A[10])
+IP_B_S1_1 = str(S1B[20])
+IP_C_S1_1 = str(S1C[30])
+IP_A_S1_2 = str(S1A[30])
+IP_B_S1_2 = str(S1B[40])
+IP_A_S1_3 = str(S1A[50])
+IP_B_S1_3 = str(S1B[60])
+IP_A_S2_1 = str(S2A[50])
+IP_B_S2_1 = str(S2B[60])
+IP_A_BIS_S1_1 = IP_A_S1_1
+IP_A_BIS_S1_2 = IP_A_S1_2
+IP_A_BIS_S1_3 = IP_A_S1_3
+IP_A_BIS_S2_1 = IP_A_S2_1
+
 
 class ScenarioTest(tempest.test.BaseTestCase):
     """Base class for scenario tests. Uses tempest own clients. """
@@ -877,3 +925,298 @@
                             routers_client.remove_router_interface, router_id,
                             subnet_id=subnet['id'])
         return network, subnet, router
+
+    def _create_security_group_for_test(self):
+        self.security_group = self._create_security_group(
+            tenant_id=self.bgpvpn_client.tenant_id)
+
+    def _create_subnet_with_cidr(self, network, subnets_client=None,
+                                 namestart='subnet-smoke', **kwargs):
+        if not subnets_client:
+            subnets_client = self.subnets_client
+        tenant_cidr = kwargs.get('cidr')
+        subnet = dict(
+            name=data_utils.rand_name(namestart),
+            network_id=network['id'],
+            tenant_id=network['tenant_id'],
+            **kwargs)
+        result = subnets_client.create_subnet(**subnet)
+        self.assertIsNotNone(result, 'Unable to allocate tenant network')
+        subnet = result['subnet']
+        self.assertEqual(subnet['cidr'], tenant_cidr)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        subnets_client.delete_subnet, subnet['id'])
+        return subnet
+
+    def _create_fip_router(self, client=None, public_network_id=None,
+                           subnet_id=None):
+        router = self._create_router(client, namestart='router-')
+        router_id = router['id']
+        if public_network_id is None:
+            public_network_id = CONF.network.public_network_id
+        if client is None:
+            client = self.routers_client
+        kwargs = {'external_gateway_info': {'network_id': public_network_id}}
+        router = client.update_router(router_id, **kwargs)['router']
+        if subnet_id is not None:
+            client.add_router_interface(router_id, subnet_id=subnet_id)
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            client.remove_router_interface, router_id,
+                            subnet_id=subnet_id)
+        return router
+
+    def _associate_fip(self, server_index):
+        server = self.servers[server_index]
+        fip = self.create_floating_ip(
+            server, external_network_id=CONF.network.public_network_id,
+            port_id=self.ports[server['id']]['id'])
+        self.server_fips[server['id']] = fip
+        return fip
+
+    def _create_router_and_associate_fip(self, server_index, subnet):
+        router = self._create_fip_router(subnet_id=subnet['id'])
+        self._associate_fip(server_index)
+        return router
+
+    def _create_server(self, name, keypair, network, ip_address,
+                       security_group_ids, clients, port_security):
+        security_groups = []
+        if port_security:
+            security_groups = security_group_ids
+        create_port_body = {'fixed_ips': [{'ip_address': ip_address}],
+                            'namestart': 'port-smoke',
+                            'security_groups': security_groups}
+        port = self._create_port(network_id=network['id'],
+                                 client=clients.ports_client,
+                                 **create_port_body)
+        create_server_kwargs = {
+            'key_name': keypair['name'],
+            'networks': [{'uuid': network['id'], 'port': port['id']}]
+        }
+        body, servers = compute.create_test_server(
+            clients, wait_until='ACTIVE', name=name, **create_server_kwargs)
+        self.addCleanup(waiters.wait_for_server_termination,
+                        clients.servers_client, body['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        clients.servers_client.delete_server, body['id'])
+        server = clients.servers_client.show_server(body['id'])['server']
+        LOG.debug('Created server: %s with status: %s', server['id'],
+                  server['status'])
+        self.ports[server['id']] = port
+        return server
+
+    def _create_servers(self, ports_config=None, port_security=True):
+        keypair = self.create_keypair()
+        security_group_ids = [self.security_group['id']]
+        if ports_config is None:
+            ports_config = [[self.networks[NET_A], IP_A_S1_1],
+                            [self.networks[NET_B], IP_B_S1_1]]
+        for (i, port_config) in enumerate(ports_config):
+            network = port_config[0]
+            server = self._create_server(
+                'server-' + str(i + 1), keypair, network, port_config[1],
+                security_group_ids, self.os_primary, port_security)
+            self.servers.append(server)
+            self.servers_keypairs[server['id']] = keypair
+            self.server_fixed_ips[server['id']] = (
+                server['addresses'][network['name']][0]['addr'])
+            self.assertTrue(self.servers_keypairs)
+
+    def _associate_all_nets_to_bgpvpn(self, bgpvpn=None):
+        bgpvpn = bgpvpn or self.bgpvpn
+        for network in self.networks.values():
+            self.bgpvpn_client.create_network_association(
+                bgpvpn['id'], network['id'])
+        LOG.debug('BGPVPN network associations completed')
+
+    def _create_networks_and_subnets(self, names=None, subnet_cidrs=None,
+                                     port_security=True):
+        if not names:
+            names = [NET_A, NET_B, NET_C]
+        if not subnet_cidrs:
+            subnet_cidrs = [[NET_A_S1], [NET_B_S1], [NET_C_S1]]
+        for (name, subnet_cidrs) in zip(names, subnet_cidrs):
+            network = self._create_network(
+                namestart=name, port_security_enabled=port_security)
+            self.networks[name] = network
+            self.subnets[name] = []
+            for (j, cidr) in enumerate(subnet_cidrs):
+                sub_name = "subnet-%s-%d" % (name, j + 1)
+                subnet = self._create_subnet_with_cidr(network,
+                                                       namestart=sub_name,
+                                                       cidr=cidr,
+                                                       ip_version=4)
+                self.subnets[name].append(subnet)
+
+    def _setup_ssh_client(self, server):
+        server_fip = self.server_fips[server['id']][
+            'floating_ip_address']
+        private_key = self.servers_keypairs[server['id']][
+            'private_key']
+        ssh_client = self.get_remote_client(server_fip,
+                                            private_key=private_key)
+        return ssh_client
+
+    def _check_l3_bgpvpn(self, from_server=None, to_server=None,
+                         should_succeed=True, validate_server=False):
+        to_server = to_server or self.servers[1]
+        destination_srv = None
+        if validate_server:
+            destination_srv = '%s:%s' % (to_server['name'], to_server['id'])
+        destination_ip = self.server_fixed_ips[to_server['id']]
+        self._check_l3_bgpvpn_by_specific_ip(from_server=from_server,
+                                             to_server_ip=destination_ip,
+                                             should_succeed=should_succeed,
+                                             validate_server=destination_srv)
+
+    def _check_l3_bgpvpn_by_specific_ip(self, from_server=None,
+                                        to_server_ip=None,
+                                        should_succeed=True,
+                                        validate_server=None,
+                                        repeat_validate_server=10):
+        from_server = from_server or self.servers[0]
+        from_server_ip = self.server_fips[from_server['id']][
+            'floating_ip_address']
+        if to_server_ip is None:
+            to_server_ip = self.server_fixed_ips[self.servers[1]['id']]
+        ssh_client = self._setup_ssh_client(from_server)
+        check_reachable = should_succeed or validate_server
+        msg = ""
+        if check_reachable:
+            msg = "Timed out waiting for {ip} to become reachable".format(
+                ip=to_server_ip)
+        else:
+            msg = ("Unexpected ping response from VM with IP address "
+                   "{dest} originated from VM with IP address "
+                   "{src}").format(dest=to_server_ip, src=from_server_ip)
+        try:
+            result = self._check_remote_connectivity(ssh_client,
+                                                     to_server_ip,
+                                                     check_reachable)
+            # if a negative connectivity check was unsuccessful (unexpected
+            # ping reply) then try to know more:
+            if not check_reachable and not result:
+                try:
+                    content = ssh_client.exec_command(
+                        "nc %s 80" % to_server_ip).strip()
+                    LOG.warning("Can connect to %s: %s", to_server_ip, content)
+                except Exception:
+                    LOG.warning("Could ping %s, but no http", to_server_ip)
+
+            self.assertTrue(result, msg)
+
+            if validate_server and result:
+                # repeating multiple times gives increased odds of avoiding
+                # false positives in the case where the dataplane does
+                # equal-cost multipath
+                for i in range(repeat_validate_server):
+                    real_dest = ssh_client.exec_command(
+                        "nc %s 80" % to_server_ip).strip()
+                    result = real_dest == validate_server
+                    self.assertTrue(
+                        should_succeed == result,
+                        ("Destination server name is '%s', expected is '%s'" %
+                         (real_dest, validate_server)))
+                    LOG.info("nc server name check %d successful", i)
+        except Exception:
+            LOG.exception("Error validating connectivity to %s "
+                          "from VM with IP address %s: %s",
+                          to_server_ip, from_server_ip, msg)
+            raise
+
+    def _associate_fip_and_check_l3_bgpvpn(self, should_succeed=True):
+        subnet = self.subnets[NET_A][0]
+        self.router = self._create_router_and_associate_fip(0, subnet)
+        self._check_l3_bgpvpn(should_succeed=should_succeed)
+
+    def _live_migrate(self, server_id, target_host, state,
+                      volume_backed=False):
+        # If target_host is None,
+        # check whether source host is different with
+        # the new host after migration.
+        if target_host is None:
+            source_host = self.get_host_for_server(server_id)
+        self._migrate_server_to(server_id, target_host, volume_backed)
+        waiters.wait_for_server_status(self.servers_client, server_id, state)
+        migration_list = (self.admin_migration_client.list_migrations()
+                          ['migrations'])
+
+        msg = ("Live Migration failed. Migrations list for Instance "
+               "%s: [" % server_id)
+        for live_migration in migration_list:
+            if (live_migration['instance_uuid'] == server_id):
+                msg += "\n%s" % live_migration
+        msg += "]"
+        if target_host is None:
+            self.assertNotEqual(source_host,
+                                self.get_host_for_server(server_id), msg)
+        else:
+            self.assertEqual(target_host, self.get_host_for_server(server_id),
+                             msg)
+
+    def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
+        kwargs = dict()
+        block_migration = getattr(self, 'block_migration', None)
+        if self.block_migration is None:
+            if self.is_requested_microversion_compatible('2.24'):
+                kwargs['disk_over_commit'] = False
+            block_migration = (CONF.compute_feature_enabled.
+                               block_migration_for_live_migration and
+                               not volume_backed)
+        self.admin_servers_client.live_migrate_server(
+            server_id, host=dest_host, block_migration=block_migration,
+            **kwargs)
+
+    def _create_l3_bgpvpn(self, name='test-l3-bgpvpn', rts=None,
+                          import_rts=None, export_rts=None):
+        if rts is None and import_rts is None and export_rts is None:
+            rts = [self.RT1]
+        import_rts = import_rts or []
+        export_rts = export_rts or []
+        self.bgpvpn = self.create_bgpvpn(
+            self.bgpvpn_admin_client, tenant_id=self.bgpvpn_client.tenant_id,
+            name=name, route_targets=rts, export_targets=export_rts,
+            import_targets=import_rts)
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.bgpvpn_admin_client.delete_bgpvpn,
+                        self.bgpvpn['id'])
+        return self.bgpvpn
+
+    def _update_l3_bgpvpn(self, rts=None, import_rts=None, export_rts=None,
+                          bgpvpn=None):
+        bgpvpn = bgpvpn or self.bgpvpn
+        if rts is None:
+            rts = [self.RT1]
+        import_rts = import_rts or []
+        export_rts = export_rts or []
+        LOG.debug('Updating targets in BGPVPN %s', bgpvpn['id'])
+        self.bgpvpn_admin_client.update_bgpvpn(bgpvpn['id'],
+                                               route_targets=rts,
+                                               export_targets=export_rts,
+                                               import_targets=import_rts)
+
+    def _setup_ip_forwarding(self, server_index):
+        server = self.servers[server_index]
+        ssh_client = self._setup_ssh_client(server)
+        ssh_client.exec_command("sudo sysctl -w net.ipv4.ip_forward=1")
+
+    def _setup_ip_address(self, server_index, cidr, device=None):
+        self._setup_range_ip_address(server_index, [cidr], device=None)
+
+    def _setup_range_ip_address(self, server_index, cidrs, device=None):
+        MAX_CIDRS = 50
+        if device is None:
+            device = 'lo'
+        server = self.servers[server_index]
+        ssh_client = self._setup_ssh_client(server)
+        for i in range(0, len(cidrs), MAX_CIDRS):
+            ips = ' '.join(cidrs[i:i + MAX_CIDRS])
+            ssh_client.exec_command(
+                ("for ip in {ips}; do sudo ip addr add $ip "
+                 "dev {dev}; done").format(ips=ips, dev=device))
+
+    def _setup_http_server(self, server_index):
+        server = self.servers[server_index]
+        ssh_client = self._setup_ssh_client(server)
+        ssh_client.exec_command("sudo nc -kl -p 80 -e echo '%s:%s' &"
+                                % (server['name'], server['id']))