Merge "Mark some bgpvpn scenario tests unstable"
diff --git a/neutron_tempest_plugin/api/admin/test_tag.py b/neutron_tempest_plugin/api/admin/test_tag.py
index 7879b4c..eae7977 100644
--- a/neutron_tempest_plugin/api/admin/test_tag.py
+++ b/neutron_tempest_plugin/api/admin/test_tag.py
@@ -28,13 +28,13 @@
 
     def _get_and_compare_tags(self, tags):
         res_body = self.client.get_tags(self.resource, self.res_id)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
 
     def _test_tag_operations(self):
         # create and get tags
         tags = ['red', 'blue']
         res_body = self.client.update_tags(self.resource, self.res_id, tags)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
         self._get_and_compare_tags(tags)
 
         # add a tag
@@ -52,7 +52,7 @@
         # replace tags
         tags = ['red', 'yellow', 'purple']
         res_body = self.client.update_tags(self.resource, self.res_id, tags)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
         self._get_and_compare_tags(tags)
 
         # get tag
@@ -477,7 +477,7 @@
         # nothing specific about networks here, just a resource that is
         # available in all setups
         res_body = self.client.get_tags('networks', res_id)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('74c56fb1-a3b1-4a62-a8d2-d04dca6bd4cd')
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 77bf5e8..d63dec8 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -558,6 +558,8 @@
         """Wrapper utility that returns a test port."""
         if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
             kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
+        if CONF.network.port_profile and 'binding:profile' not in kwargs:
+            kwargs['binding:profile'] = CONF.network.port_profile
         body = cls.client.create_port(network_id=network['id'],
                                       **kwargs)
         port = body['port']
@@ -700,6 +702,12 @@
         return pf
 
     @classmethod
+    def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
+        """Wrapper utility for update_port_forwarding."""
+        client = client or cls.client
+        return client.update_port_forwarding(fip_id, pf_id, **kwargs)
+
+    @classmethod
     def delete_port_forwarding(cls, pf, client=None):
         """Delete port forwarding
 
diff --git a/neutron_tempest_plugin/api/test_allowed_address_pair.py b/neutron_tempest_plugin/api/test_allowed_address_pair.py
index dd48382..7b11638 100644
--- a/neutron_tempest_plugin/api/test_allowed_address_pair.py
+++ b/neutron_tempest_plugin/api/test_allowed_address_pair.py
@@ -81,7 +81,7 @@
         body = self.client.update_port(
             port_id, allowed_address_pairs=allowed_address_pairs)
         allowed_address_pair = body['port']['allowed_address_pairs']
-        self.assertItemsEqual(allowed_address_pair, allowed_address_pairs)
+        self.assertCountEqual(allowed_address_pair, allowed_address_pairs)
 
     @decorators.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
     def test_update_port_with_address_pair(self):
diff --git a/neutron_tempest_plugin/api/test_port_forwarding_negative.py b/neutron_tempest_plugin/api/test_port_forwarding_negative.py
index 76dd6ee..386cbed 100644
--- a/neutron_tempest_plugin/api/test_port_forwarding_negative.py
+++ b/neutron_tempest_plugin/api/test_port_forwarding_negative.py
@@ -12,6 +12,7 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
@@ -81,3 +82,43 @@
             internal_ip_address=port['fixed_ips'][0]['ip_address'],
             internal_port=1111, external_port=5555,
             protocol="tcp")
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('e9d3ffb6-e5bf-421d-acaa-ee6010dfbf14')
+    def test_out_of_range_ports(self):
+        port = self.create_port(self.network)
+        fip_for_pf = self.create_floatingip()
+
+        pf_params = {
+            'internal_port_id': port['id'],
+            'internal_ip_address': port['fixed_ips'][0]['ip_address'],
+            'internal_port': 1111,
+            'external_port': 3333,
+            'protocol': "tcp"}
+        pf = self.create_port_forwarding(fip_for_pf['id'], **pf_params)
+
+        # Check: Invalid input for external_port update
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.update_port_forwarding,
+            fip_for_pf['id'], pf['id'], external_port=108343)
+
+        # Check: Invalid input for internal_port update
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.update_port_forwarding,
+            fip_for_pf['id'], pf['id'], internal_port=108343)
+
+        # Check: Invalid input for external_port create
+        pf_params['internal_port'] = 4444
+        pf_params['external_port'] = 333333
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.create_port_forwarding, fip_for_pf['id'], **pf_params)
+
+        # Check: Invalid input for internal_port create
+        pf_params['internal_port'] = 444444
+        pf_params['external_port'] = 3333
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.create_port_forwarding, fip_for_pf['id'], **pf_params)
diff --git a/neutron_tempest_plugin/api/test_port_forwardings.py b/neutron_tempest_plugin/api/test_port_forwardings.py
index 82c3a34..79cce39 100644
--- a/neutron_tempest_plugin/api/test_port_forwardings.py
+++ b/neutron_tempest_plugin/api/test_port_forwardings.py
@@ -124,31 +124,10 @@
         fip = self.client.show_floatingip(fip['id'])['floatingip']
         self.assertEqual(0, len(fip['port_forwardings']))
 
-    @decorators.idempotent_id('8202cded-7e82-4420-9585-c091105404f6')
-    def test_associate_2_port_forwardings_to_floating_ip(self):
-        fip = self.create_floatingip()
-        forwardings_data = [(1111, 2222), (3333, 4444)]
-        created_pfs = []
-        for data in forwardings_data:
-            internal_port = data[0]
-            external_port = data[1]
-            port = self.create_port(self.network)
-            created_pf = self.create_port_forwarding(
-                fip['id'],
-                internal_port_id=port['id'],
-                internal_ip_address=port['fixed_ips'][0]['ip_address'],
-                internal_port=internal_port, external_port=external_port,
-                protocol="tcp")
-            self.assertEqual(internal_port, created_pf['internal_port'])
-            self.assertEqual(external_port, created_pf['external_port'])
-            self.assertEqual('tcp', created_pf['protocol'])
-            self.assertEqual(port['fixed_ips'][0]['ip_address'],
-                             created_pf['internal_ip_address'])
-            created_pfs.append(created_pf)
-
+    def _verify_created_pfs(self, fip_id, created_pfs):
         # Check that all PFs are visible in Floating IP details
-        fip = self.client.show_floatingip(fip['id'])['floatingip']
-        self.assertEqual(len(forwardings_data), len(fip['port_forwardings']))
+        fip = self.client.show_floatingip(fip_id)['floatingip']
+        self.assertEqual(len(created_pfs), len(fip['port_forwardings']))
         for pf in created_pfs:
             expected_pf = {
                 'external_port': pf['external_port'],
@@ -160,13 +139,73 @@
         # Test list of port forwardings
         port_forwardings = self.client.list_port_forwardings(
             fip['id'])['port_forwardings']
-        self.assertEqual(len(forwardings_data), len(port_forwardings))
+        self.assertEqual(len(created_pfs), len(port_forwardings))
         for pf in created_pfs:
             expected_pf = pf.copy()
             expected_pf.pop('client')
             expected_pf.pop('floatingip_id')
             self.assertIn(expected_pf, port_forwardings)
 
+    def _create_and_validate_pf(self, fip_id, internal_port_id,
+                                internal_ip_address, internal_port,
+                                external_port, protocol):
+        created_pf = self.create_port_forwarding(
+            fip_id,
+            internal_port_id=internal_port_id,
+            internal_ip_address=internal_ip_address,
+            internal_port=internal_port,
+            external_port=external_port,
+            protocol=protocol)
+        self.assertEqual(internal_port, created_pf['internal_port'])
+        self.assertEqual(external_port, created_pf['external_port'])
+        self.assertEqual(protocol, created_pf['protocol'])
+        self.assertEqual(internal_ip_address,
+                         created_pf['internal_ip_address'])
+        return created_pf
+
+    @decorators.idempotent_id('8202cded-7e82-4420-9585-c091105404f6')
+    def test_associate_2_port_forwardings_to_floating_ip(self):
+        fip = self.create_floatingip()
+        forwardings_data = [(1111, 2222), (3333, 4444)]
+        created_pfs = []
+        for data in forwardings_data:
+            internal_port = data[0]
+            external_port = data[1]
+            port = self.create_port(self.network)
+            created_pf = self._create_and_validate_pf(
+                fip_id=fip['id'],
+                internal_port_id=port['id'],
+                internal_ip_address=port['fixed_ips'][0]['ip_address'],
+                internal_port=internal_port, external_port=external_port,
+                protocol="tcp")
+            created_pfs.append(created_pf)
+        self._verify_created_pfs(fip['id'], created_pfs)
+
+    @decorators.idempotent_id('a7e6cc48-8a9b-49be-82fb-cef6f5c29381')
+    def test_associate_port_forwarding_to_2_fixed_ips(self):
+        fip = self.create_floatingip()
+        port = self.create_port(self.network)
+        internal_subnet_id = port['fixed_ips'][0]['subnet_id']
+        # Add a second fixed_ip address to port (same subnet)
+        port['fixed_ips'].append({'subnet_id': internal_subnet_id})
+        port = self.update_port(port, fixed_ips=port['fixed_ips'])
+        internal_ip_address1 = port['fixed_ips'][0]['ip_address']
+        internal_ip_address2 = port['fixed_ips'][1]['ip_address']
+        forwardings_data = [(4001, internal_ip_address1),
+                            (4002, internal_ip_address2)]
+        created_pfs = []
+        for data in forwardings_data:
+            external_port = data[0]
+            internal_ip_address = data[1]
+            created_pf = self._create_and_validate_pf(
+                fip_id=fip['id'],
+                internal_port_id=port['id'],
+                internal_ip_address=internal_ip_address,
+                internal_port=123, external_port=external_port,
+                protocol="tcp")
+            created_pfs.append(created_pf)
+        self._verify_created_pfs(fip['id'], created_pfs)
+
     @decorators.idempotent_id('6a34e811-66d1-4f63-aa4d-9013f15deb62')
     def test_associate_port_forwarding_to_used_floating_ip(self):
         port_for_fip = self.create_port(self.network)
diff --git a/neutron_tempest_plugin/common/ip.py b/neutron_tempest_plugin/common/ip.py
index 83cd3d9..d981770 100644
--- a/neutron_tempest_plugin/common/ip.py
+++ b/neutron_tempest_plugin/common/ip.py
@@ -36,13 +36,19 @@
     sudo = 'sudo'
     ip_path = '/sbin/ip'
 
-    def __init__(self, ssh_client=None, timeout=None):
+    def __init__(self, ssh_client=None, timeout=None, namespace=None):
         self.ssh_client = ssh_client
         self.timeout = timeout
+        self.namespace = namespace
 
     def get_command(self, obj, *command):
-        command_line = '{sudo!s} {ip_path!r} {object!s} {command!s}'.format(
-            sudo=self.sudo, ip_path=self.ip_path, object=obj,
+        command_line = '{sudo!s} {ip_path!r} '.format(sudo=self.sudo,
+                                                     ip_path=self.ip_path)
+        if self.namespace:
+            command_line += 'netns exec {ns_name!s} {ip_path!r} '.format(
+                ns_name=self.namespace, ip_path=self.ip_path)
+        command_line += '{object!s} {command!s}'.format(
+            object=obj,
             command=subprocess.list2cmdline([str(c) for c in command]))
         return command_line
 
@@ -84,6 +90,13 @@
             self.add_address(address=subport_ip, device=subport_device)
         return subport_device
 
+    def list_namespaces(self):
+        namespaces_output = self.execute("netns")
+        ns_list = []
+        for ns_line in namespaces_output.split("\n"):
+            ns_list.append(ns_line.split(" ", 1)[0])
+        return ns_list
+
     def list_addresses(self, device=None, ip_addresses=None, port=None,
                        subnets=None):
         command = ['list']
@@ -308,20 +321,24 @@
                    netaddr.IPNetwork(subnet['cidr']).prefixlen)
 
 
-def arp_table():
+def arp_table(namespace=None):
     # 192.168.0.16  0x1  0x2  dc:a6:32:06:56:51  *  enp0s31f6
     regex_str = (r"([^ ]+)\s+(0x\d+)\s+(0x\d+)\s+(\w{2}\:\w{2}\:\w{2}\:\w{2}\:"
                  r"\w{2}\:\w{2})\s+([\w+\*]+)\s+([\-\w]+)")
     regex = re.compile(regex_str)
     arp_table = []
-    with open('/proc/net/arp', 'r') as proc_file:
-        for line in proc_file.readlines():
-            m = regex.match(line)
-            if m:
-                arp_table.append(ARPregister(
-                    ip_address=m.group(1), hw_type=m.group(2),
-                    flags=m.group(3), mac_address=m.group(4),
-                    mask=m.group(5), device=m.group(6)))
+    cmd = ""
+    if namespace:
+        cmd = "sudo ip netns exec %s " % namespace
+    cmd += "cat /proc/net/arp"
+    arp_entries = shell.execute(cmd).stdout.split("\n")
+    for line in arp_entries:
+        m = regex.match(line)
+        if m:
+            arp_table.append(ARPregister(
+                ip_address=m.group(1), hw_type=m.group(2),
+                flags=m.group(3), mac_address=m.group(4),
+                mask=m.group(5), device=m.group(6)))
     return arp_table
 
 
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index 2290d0f..c0e21c1 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -120,6 +120,18 @@
                     'This is required if advanced image has to be used in '
                     'tests.'),
 
+    # Enable/disable metadata over IPv6 tests. This feature naturally
+    # does not have an API extension, but at the time of first implementation
+    # it works only on victoria+ deployments with dhcp- and/or l3-agents
+    # (which in the gate is the same as non-ovn jobs).
+    cfg.BoolOpt('ipv6_metadata',
+                default=True,
+                help='Enable metadata over IPv6 tests where the feature is '
+                     'implemented, disable where it is not. Use this instead '
+                     'of network-feature-enabled.api_extensions, since API '
+                     'extensions do not make sense for a feature not '
+                     'exposed on the API.'),
+
     # Option for creating QoS policies configures as "shared".
     # The default is false in order to prevent undesired usage
     # while testing in parallel.
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 78b766b..2eb45f8 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -21,6 +21,7 @@
 from neutron_lib.api import validators
 from neutron_lib import constants as neutron_lib_constants
 from oslo_log import log
+from paramiko import ssh_exception as ssh_exc
 from tempest.common.utils import net_utils
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
@@ -121,9 +122,11 @@
         if not kwargs.get('security_groups'):
             kwargs['security_groups'] = [{'name': 'default'}]
 
-        client = self.os_primary.servers_client
-        if kwargs.get('availability_zone'):
-            client = self.os_admin.servers_client
+        client = kwargs.pop('client', None)
+        if client is None:
+            client = self.os_primary.servers_client
+            if kwargs.get('availability_zone'):
+                client = self.os_admin.servers_client
 
         server = client.create_server(
             flavorRef=flavor_ref,
@@ -139,6 +142,10 @@
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         client.delete_server,
                         server['server']['id'])
+
+        self.wait_for_server_active(server['server'], client=client)
+        self.wait_for_guest_os_ready(server['server'], client=client)
+
         return server
 
     @classmethod
@@ -252,7 +259,6 @@
             server_kwargs['name'] = server_name
 
         self.server = self.create_server(**server_kwargs)
-        self.wait_for_server_active(self.server['server'])
         self.port = self.client.list_ports(network_id=self.network['id'],
                                            device_id=self.server[
                                                'server']['id'])['ports'][0]
@@ -264,7 +270,7 @@
                                 pkey=ssh_key, timeout=ssh_timeout)
         try:
             ssh_client.test_connection_auth()
-        except lib_exc.SSHTimeout as ssh_e:
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
             self._log_local_network_status()
@@ -294,10 +300,20 @@
                           "for the console log", server['id'])
 
     def _log_local_network_status(self):
-        local_routes = ip_utils.IPCommand().list_routes()
-        LOG.debug('Local routes:\n%s', '\n'.join(str(r) for r in local_routes))
+        self._log_ns_network_status()
+        for ns_name in ip_utils.IPCommand().list_namespaces():
+            self._log_ns_network_status(ns_name=ns_name)
+
+    def _log_ns_network_status(self, ns_name=None):
+        local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
+        LOG.debug('Namespace %s; IP Addresses:\n%s',
+                  ns_name, '\n'.join(str(r) for r in local_ips))
+        local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
+        LOG.debug('Namespace %s; Local routes:\n%s',
+                  ns_name, '\n'.join(str(r) for r in local_routes))
         arp_table = ip_utils.arp_table()
-        LOG.debug('Local ARP table:\n%s', '\n'.join(str(r) for r in arp_table))
+        LOG.debug('Namespace %s; Local ARP table:\n%s',
+                  ns_name, '\n'.join(str(r) for r in arp_table))
 
     def _check_remote_connectivity(self, source, dest, count,
                                    should_succeed=True,
@@ -379,12 +395,14 @@
                 fragmentation,
                 timeout=timeout, pattern=pattern,
                 forbid_packet_loss=forbid_packet_loss))
-        except lib_exc.SSHTimeout as ssh_e:
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
         except AssertionError:
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
 
     def ping_ip_address(self, ip_address, should_succeed=True,
@@ -452,7 +470,30 @@
         self.wait_for_server_status(
             server, constants.SERVER_STATUS_ACTIVE, client)
 
-    def check_servers_hostnames(self, servers, timeout=None, log_errors=True):
+    def wait_for_guest_os_ready(self, server, client=None):
+        if not CONF.compute_feature_enabled.console_output:
+            LOG.debug('Console output not supported, cannot check if server '
+                      '%s is ready.', server['server']['id'])
+            return
+
+        client = client or self.os_primary.servers_client
+
+        def system_booted():
+            console_output = client.get_console_output(server['id'])['output']
+            for line in console_output.split('\n'):
+                if 'login:' in line.lower():
+                    return True
+            return False
+
+        try:
+            utils.wait_until_true(system_booted, sleep=5)
+        except utils.WaitTimeout:
+            LOG.debug("No correct output in console of server %s found. "
+                      "Guest operating system status can't be checked.",
+                      server['id'])
+
+    def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
+                                external_port=None):
         """Compare hostnames of given servers with their names."""
         try:
             for server in servers:
@@ -460,7 +501,7 @@
                 if timeout:
                     kwargs['timeout'] = timeout
                 try:
-                    kwargs['port'] = (
+                    kwargs['port'] = external_port or (
                         server['port_forwarding_tcp']['external_port'])
                 except KeyError:
                     pass
@@ -471,15 +512,17 @@
                     **kwargs)
                 self.assertIn(server['name'],
                               ssh_client.exec_command('hostname'))
-        except lib_exc.SSHTimeout as ssh_e:
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             if log_errors:
                 self._log_console_output(servers)
+                self._log_local_network_status()
             raise
         except AssertionError as assert_e:
             LOG.debug(assert_e)
             if log_errors:
                 self._log_console_output(servers)
+                self._log_local_network_status()
             raise
 
     def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
@@ -504,9 +547,10 @@
             return ssh_client.execute_script(
                 get_ncat_server_cmd(port, protocol, echo_msg),
                 become_root=True, combine_stderr=True)
-        except lib_exc.SSHTimeout as ssh_e:
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
 
     def nc_client(self, ip_address, port, protocol):
diff --git a/neutron_tempest_plugin/scenario/test_connectivity.py b/neutron_tempest_plugin/scenario/test_connectivity.py
index 1a7468a..66c4789 100644
--- a/neutron_tempest_plugin/scenario/test_connectivity.py
+++ b/neutron_tempest_plugin/scenario/test_connectivity.py
@@ -65,6 +65,7 @@
 
         for vm in vms:
             self.wait_for_server_active(vm['server'])
+            self.wait_for_guest_os_ready(vm['server'])
 
         return vms
 
@@ -231,6 +232,7 @@
             networks=[{'uuid': network['id']}],
             security_groups=[{'name': self.secgroup['name']}])
         self.wait_for_server_active(vm['server'])
+        self.wait_for_guest_os_ready(vm['server'])
 
         vm_port = self.client.list_ports(
             network_id=network['id'], device_id=vm['server']['id'])['ports'][0]
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index d541642..8c8d163 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -392,7 +392,6 @@
 
     same_network = None
 
-    @test.unstable_test("bug 1897326")
     @decorators.idempotent_id('1bdd849b-03dd-4b8f-994f-457cf8a36f93')
     def test_floating_ip_update(self):
         """Test updating FIP with another port.
@@ -418,6 +417,7 @@
             servers.append(server)
         for server in servers:
             self.wait_for_server_active(server)
+            self.wait_for_guest_os_ready(server)
 
         self.fip = self.create_floatingip(port=ports[0])
         self.check_connectivity(self.fip['floating_ip_address'],
@@ -435,8 +435,7 @@
 
         # The FIP is now associated with the port of the second server.
         try:
-            common_utils.wait_until_true(_wait_for_fip_associated,
-                                         timeout=15, sleep=3)
+            common_utils.wait_until_true(_wait_for_fip_associated, sleep=3)
         except common_utils.WaitTimeout:
             self._log_console_output(servers[-1:])
             self.fail(
diff --git a/neutron_tempest_plugin/scenario/test_internal_dns.py b/neutron_tempest_plugin/scenario/test_internal_dns.py
index d19286c..c620233 100644
--- a/neutron_tempest_plugin/scenario/test_internal_dns.py
+++ b/neutron_tempest_plugin/scenario/test_internal_dns.py
@@ -52,6 +52,7 @@
                 {'name': self.security_groups[-1]['name']}],
             name='leia')
         self.wait_for_server_active(leia['server'])
+        self.wait_for_guest_os_ready(leia['server'])
 
         ssh_client = ssh.Client(
             self.fip['floating_ip_address'],
diff --git a/neutron_tempest_plugin/scenario/test_ipv6.py b/neutron_tempest_plugin/scenario/test_ipv6.py
index 02e2846..732c96d 100644
--- a/neutron_tempest_plugin/scenario/test_ipv6.py
+++ b/neutron_tempest_plugin/scenario/test_ipv6.py
@@ -15,6 +15,7 @@
 
 from neutron_lib import constants as lib_constants
 from oslo_log import log
+from paramiko import ssh_exception as ssh_exc
 from tempest.common import utils as tempest_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -108,16 +109,22 @@
                     return True
             return False
 
-        # Set NIC with IPv6 to be UP and wait until IPv6 address will be
-        # configured on this NIC
-        turn_nic6_on(ssh_client, ipv6_port)
-        # And check if IPv6 address will be properly configured on this NIC
-        utils.wait_until_true(
-            lambda: guest_has_address(ipv6_address),
-            timeout=120,
-            exception=RuntimeError(
-                "Timed out waiting for IP address {!r} to be configured in "
-                "the VM {!r}.".format(ipv6_address, vm['id'])))
+        try:
+            # Set NIC with IPv6 to be UP and wait until IPv6 address will be
+            # configured on this NIC
+            turn_nic6_on(ssh_client, ipv6_port)
+            # And check if IPv6 address will be properly configured on this NIC
+            utils.wait_until_true(
+                lambda: guest_has_address(ipv6_address),
+                timeout=120,
+                exception=RuntimeError(
+                    "Timed out waiting for IP address {!r} to be configured "
+                    "in the VM {!r}.".format(ipv6_address, vm['id'])))
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
+            LOG.debug(ssh_e)
+            self._log_console_output([vm])
+            self._log_local_network_status()
+            raise
 
     def _test_ipv6_hotplug(self, ra_mode, address_mode):
         ipv6_networks = [self.create_network() for _ in range(2)]
@@ -138,6 +145,7 @@
         }
         vm = self.create_server(**server_kwargs)['server']
         self.wait_for_server_active(vm)
+        self.wait_for_guest_os_ready(vm)
         ipv4_port = self.client.list_ports(
             network_id=self.network['id'],
             device_id=vm['id'])['ports'][0]
diff --git a/neutron_tempest_plugin/scenario/test_metadata.py b/neutron_tempest_plugin/scenario/test_metadata.py
new file mode 100644
index 0000000..3897183
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_metadata.py
@@ -0,0 +1,143 @@
+# Copyright 2020 Ericsson Software Technology
+#
+# 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 collections
+
+from neutron_lib import constants as nlib_const
+from oslo_log import log as logging
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+import testtools
+
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+Server = collections.namedtuple(
+    'Server', ['floating_ip', 'server', 'ssh_client'])
+
+
+class MetadataTest(base.BaseTempestTestCase):
+
+    """Test metadata access over IPv6 tenant subnet.
+
+    Please note that there is metadata over IPv4 test coverage in tempest:
+
+    tempest.scenario.test_server_basic_ops\
+        .TestServerBasicOps.test_server_basic_ops
+    """
+
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    @classmethod
+    def resource_setup(cls):
+        super(MetadataTest, cls).resource_setup()
+        cls.rand_name = data_utils.rand_name(
+            cls.__name__.rsplit('.', 1)[-1])
+        cls.network = cls.create_network(name=cls.rand_name)
+        cls.subnet_v4 = cls.create_subnet(
+            network=cls.network, name=cls.rand_name)
+        cls.subnet_v6 = cls.create_subnet(
+            network=cls.network, name=cls.rand_name, ip_version=6)
+        cls.router = cls.create_router_by_client()
+        cls.create_router_interface(cls.router['id'], cls.subnet_v4['id'])
+        cls.create_router_interface(cls.router['id'], cls.subnet_v6['id'])
+        cls.keypair = cls.create_keypair(name=cls.rand_name)
+        cls.security_group = cls.create_security_group(name=cls.rand_name)
+        cls.create_loginable_secgroup_rule(cls.security_group['id'])
+
+    def _create_server_with_network(self, network, use_advanced_image=False):
+        port = self._create_server_port(network=network)
+        floating_ip = self.create_floatingip(port=port)
+        ssh_client = self._create_ssh_client(
+            floating_ip=floating_ip, use_advanced_image=use_advanced_image)
+        server = self._create_server(port=port,
+                                     use_advanced_image=use_advanced_image)
+        return Server(
+            floating_ip=floating_ip, server=server, ssh_client=ssh_client)
+
+    def _create_server_port(self, network=None, **params):
+        network = network or self.network
+        return self.create_port(network=network, name=self.rand_name,
+                                security_groups=[self.security_group['id']],
+                                **params)
+
+    def _create_server(self, port, use_advanced_image=False, **params):
+        if use_advanced_image:
+            flavor_ref = CONF.neutron_plugin_options.advanced_image_flavor_ref
+            image_ref = CONF.neutron_plugin_options.advanced_image_ref
+        else:
+            flavor_ref = CONF.compute.flavor_ref
+            image_ref = CONF.compute.image_ref
+        return self.create_server(flavor_ref=flavor_ref,
+                                  image_ref=image_ref,
+                                  key_name=self.keypair['name'],
+                                  networks=[{'port': port['id']}],
+                                  **params)['server']
+
+    def _create_ssh_client(self, floating_ip, use_advanced_image=False):
+        if use_advanced_image:
+            username = CONF.neutron_plugin_options.advanced_image_ssh_user
+        else:
+            username = CONF.validation.image_ssh_user
+        return ssh.Client(host=floating_ip['floating_ip_address'],
+                          username=username,
+                          pkey=self.keypair['private_key'])
+
+    def _assert_has_ssh_connectivity(self, ssh_client):
+        ssh_client.exec_command('true')
+
+    def _get_primary_interface(self, ssh_client):
+        out = ssh_client.exec_command(
+            "ip -6 -br address show scope link up | head -1 | cut -d ' ' -f1")
+        interface = out.strip()
+        if not interface:
+            self.fail(
+                'Could not find a single interface '
+                'with an IPv6 link-local address.')
+        return interface
+
+    @testtools.skipUnless(
+        (CONF.neutron_plugin_options.ipv6_metadata and
+         (CONF.neutron_plugin_options.advanced_image_ref or
+          CONF.neutron_plugin_options.default_image_is_advanced)),
+        'Advanced image and neutron_plugin_options.ipv6_metadata=True '
+        'is required to run this test.')
+    @decorators.idempotent_id('e680949a-f1cc-11ea-b49a-cba39bbbe5ad')
+    def test_metadata_routed(self):
+        use_advanced_image = (
+            not CONF.neutron_plugin_options.default_image_is_advanced)
+
+        vm = self._create_server_with_network(
+            self.network, use_advanced_image=use_advanced_image)
+        self.wait_for_server_active(server=vm.server)
+        self.wait_for_guest_os_ready(vm.server)
+        self._assert_has_ssh_connectivity(vm.ssh_client)
+        interface = self._get_primary_interface(vm.ssh_client)
+
+        out = vm.ssh_client.exec_command(
+            'curl http://[%(address)s%%25%(interface)s]/' % {
+                'address': nlib_const.METADATA_V6_IP,
+                'interface': interface})
+        self.assertIn('latest', out)
+
+        out = vm.ssh_client.exec_command(
+            'curl http://[%(address)s%%25%(interface)s]/openstack/' % {
+                'address': nlib_const.METADATA_V6_IP,
+                'interface': interface})
+        self.assertIn('latest', out)
diff --git a/neutron_tempest_plugin/scenario/test_multicast.py b/neutron_tempest_plugin/scenario/test_multicast.py
index b2ea8ae..79f835e 100644
--- a/neutron_tempest_plugin/scenario/test_multicast.py
+++ b/neutron_tempest_plugin/scenario/test_multicast.py
@@ -201,6 +201,7 @@
             security_groups=[{'name': self.secgroup['security_group']['name']}]
         )['server']
         self.wait_for_server_active(server)
+        self.wait_for_guest_os_ready(server)
         port = self.client.list_ports(
             network_id=self.network['id'], device_id=server['id'])['ports'][0]
         server['fip'] = self.create_floatingip(port=port)
diff --git a/neutron_tempest_plugin/scenario/test_port_forwardings.py b/neutron_tempest_plugin/scenario/test_port_forwardings.py
index 3158ea0..fb6410c 100644
--- a/neutron_tempest_plugin/scenario/test_port_forwardings.py
+++ b/neutron_tempest_plugin/scenario/test_port_forwardings.py
@@ -45,9 +45,14 @@
         cls.secgroup = cls.create_security_group(
             name=data_utils.rand_name("test_port_secgroup"))
         cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
+        udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
+                       'direction': constants.INGRESS_DIRECTION,
+                       'remote_ip_prefix': '0.0.0.0/0'}
+        cls.create_secgroup_rules(
+            [udp_sg_rule], secgroup_id=cls.secgroup['id'])
         cls.keypair = cls.create_keypair()
 
-    def _prepare_resources(self, num_servers, internal_tcp_port, protocol,
+    def _prepare_resources(self, num_servers, internal_tcp_port=22,
                            external_port_base=1025):
         servers = []
         for i in range(1, num_servers + 1):
@@ -65,6 +70,7 @@
                 networks=[{'port': port['id']}])['server']
             server['name'] = name
             self.wait_for_server_active(server)
+            self.wait_for_guest_os_ready(server)
             server['port_forwarding_tcp'] = self.create_port_forwarding(
                 self.fip['id'],
                 internal_port_id=port['id'],
@@ -82,7 +88,7 @@
             servers.append(server)
         return servers
 
-    def _test_udp_port_forwarding(self, servers):
+    def _test_udp_port_forwarding(self, servers, timeout=None):
 
         def _message_received(server, ssh_client, expected_msg):
             self.nc_listen(ssh_client,
@@ -103,23 +109,22 @@
                 CONF.validation.image_ssh_user,
                 pkey=self.keypair['private_key'],
                 port=server['port_forwarding_tcp']['external_port'])
+            wait_params = {
+                'exception': RuntimeError(
+                    "Timed out waiting for message from server {!r} ".format(
+                        server['id']))
+            }
+            if timeout:
+                wait_params['timeout'] = timeout
             utils.wait_until_true(
                 lambda: _message_received(server, ssh_client, expected_msg),
-                exception=RuntimeError(
-                    "Timed out waiting for message from server {!r} ".format(
-                        server['id'])))
+                **wait_params)
 
     @test.unstable_test("bug 1896735")
     @decorators.idempotent_id('ab40fc48-ca8d-41a0-b2a3-f6679c847bfe')
     def test_port_forwarding_to_2_servers(self):
-        udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
-                       'direction': constants.INGRESS_DIRECTION,
-                       'remote_ip_prefix': '0.0.0.0/0'}
-        self.create_secgroup_rules(
-            [udp_sg_rule], secgroup_id=self.secgroup['id'])
-        servers = self._prepare_resources(
-            num_servers=2, internal_tcp_port=22,
-            protocol=constants.PROTO_NAME_TCP)
+        servers = self._prepare_resources(num_servers=2,
+                                          external_port_base=1035)
         # Test TCP port forwarding by SSH to each server
         self.check_servers_hostnames(servers)
         # And now test UDP port forwarding using nc
@@ -128,25 +133,16 @@
     @test.unstable_test("bug 1896735")
     @decorators.idempotent_id('aa19d46c-a4a6-11ea-bb37-0242ac130002')
     def test_port_forwarding_editing_and_deleting_tcp_rule(self):
-        server = self._prepare_resources(
-            num_servers=1, internal_tcp_port=22,
-            protocol=constants.PROTO_NAME_TCP,
-            external_port_base=1035)
+        test_ext_port = 3333
+        server = self._prepare_resources(num_servers=1,
+                                         external_port_base=1045)
         fip_id = server[0]['port_forwarding_tcp']['floatingip_id']
         pf_id = server[0]['port_forwarding_tcp']['id']
 
         # Check connectivity with the original parameters
         self.check_servers_hostnames(server)
 
-        # Use a reasonable timeout to verify that connections will not
-        # happen. Default would be 196 seconds, which is an overkill.
-        test_ssh_connect_timeout = 6
-
-        # Update external port and check connectivity with original parameters
-        # Port under server[0]['port_forwarding_tcp']['external_port'] should
-        # not answer at this point.
-
-        def fip_pf_connectivity():
+        def fip_pf_connectivity(test_ssh_connect_timeout=60):
             try:
                 self.check_servers_hostnames(
                     server, timeout=test_ssh_connect_timeout)
@@ -155,9 +151,13 @@
                 return False
 
         def no_fip_pf_connectivity():
-            return not fip_pf_connectivity()
+            return not fip_pf_connectivity(6)
 
-        self.client.update_port_forwarding(fip_id, pf_id, external_port=3333)
+        # Update external port and check connectivity with original parameters
+        # Port under server[0]['port_forwarding_tcp']['external_port'] should
+        # not answer at this point.
+        self.client.update_port_forwarding(fip_id, pf_id,
+                                           external_port=test_ext_port)
         utils.wait_until_true(
             no_fip_pf_connectivity,
             exception=RuntimeError(
@@ -167,7 +167,7 @@
                     server[0]['port_forwarding_tcp']['external_port'])))
 
         # Check connectivity with the new parameters
-        server[0]['port_forwarding_tcp']['external_port'] = 3333
+        server[0]['port_forwarding_tcp']['external_port'] = test_ext_port
         utils.wait_until_true(
             fip_pf_connectivity,
             exception=RuntimeError(
@@ -187,3 +187,106 @@
                 "port {!r} is still possible.".format(
                     server[0]['id'],
                     server[0]['port_forwarding_tcp']['external_port'])))
+
+    @test.unstable_test("bug 1896735")
+    @decorators.idempotent_id('6d05b1b2-6109-4c30-b402-1503f4634acb')
+    def test_port_forwarding_editing_and_deleting_udp_rule(self):
+        test_ext_port = 3344
+        server = self._prepare_resources(num_servers=1,
+                                         external_port_base=1055)
+        fip_id = server[0]['port_forwarding_udp']['floatingip_id']
+        pf_id = server[0]['port_forwarding_udp']['id']
+
+        # Check connectivity with the original parameters
+        self.check_servers_hostnames(server)
+
+        def fip_pf_udp_connectivity(test_udp_timeout=60):
+            try:
+                self._test_udp_port_forwarding(server, test_udp_timeout)
+                return True
+            except (AssertionError, RuntimeError):
+                return False
+
+        def no_fip_pf_udp_connectivity():
+            return not fip_pf_udp_connectivity(6)
+
+        # Update external port and check connectivity with original parameters
+        # Port under server[0]['port_forwarding_udp']['external_port'] should
+        # not answer at this point.
+        self.client.update_port_forwarding(fip_id, pf_id,
+                                           external_port=test_ext_port)
+        utils.wait_until_true(
+            no_fip_pf_udp_connectivity,
+            exception=RuntimeError(
+                "Connection to the server {!r} through "
+                "port {!r} is still possible.".format(
+                    server[0]['id'],
+                    server[0]['port_forwarding_udp']['external_port'])))
+
+        # Check connectivity with the new parameters
+        server[0]['port_forwarding_udp']['external_port'] = test_ext_port
+        utils.wait_until_true(
+            fip_pf_udp_connectivity,
+            exception=RuntimeError(
+                "Connection to the server {!r} through "
+                "port {!r} is not possible.".format(
+                    server[0]['id'],
+                    server[0]['port_forwarding_udp']['external_port'])))
+
+        # Remove port forwarding and ensure connection stops working.
+        self.client.delete_port_forwarding(fip_id, pf_id)
+        self.assertRaises(lib_exc.NotFound, self.client.get_port_forwarding,
+                          fip_id, pf_id)
+        utils.wait_until_true(
+            no_fip_pf_udp_connectivity,
+            exception=RuntimeError(
+                "Connection to the server {!r} through "
+                "port {!r} is still possible.".format(
+                    server[0]['id'],
+                    server[0]['port_forwarding_udp']['external_port'])))
+
+    @test.unstable_test("bug 1896735")
+    @decorators.idempotent_id('5971881d-06a0-459e-b636-ce5d1929e2d4')
+    def test_port_forwarding_to_2_fixed_ips(self):
+        port = self.create_port(self.network,
+            security_groups=[self.secgroup['id']])
+        name = data_utils.rand_name("server-0")
+        server = self.create_server(flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref, key_name=self.keypair['name'],
+            name=name, networks=[{'port': port['id']}])['server']
+        server['name'] = name
+        self.wait_for_server_active(server)
+        self.wait_for_guest_os_ready(server)
+
+        # Add a second fixed_ip address to port (same subnet)
+        internal_subnet_id = port['fixed_ips'][0]['subnet_id']
+        port['fixed_ips'].append({'subnet_id': internal_subnet_id})
+        port = self.update_port(port, fixed_ips=port['fixed_ips'])
+        internal_ip_address1 = port['fixed_ips'][0]['ip_address']
+        internal_ip_address2 = port['fixed_ips'][1]['ip_address']
+        pfs = []
+        for ip_address, external_port in [(internal_ip_address1, 1066),
+                                          (internal_ip_address2, 1067)]:
+            pf = self.create_port_forwarding(
+                self.fip['id'], internal_port_id=port['id'],
+                internal_ip_address=ip_address,
+                internal_port=22, external_port=external_port,
+                protocol=constants.PROTO_NAME_TCP)
+            pfs.append(pf)
+
+        test_ssh_connect_timeout = 32
+        number_of_connects = 0
+        for pf in pfs:
+            try:
+                self.check_servers_hostnames(
+                    [server], timeout=test_ssh_connect_timeout,
+                    external_port=pf['external_port'])
+                number_of_connects += 1
+            except (AssertionError, lib_exc.SSHTimeout):
+                pass
+
+        # TODO(flaviof): Quite possibly, the server is using only one of the
+        # fixed ips associated with the neutron port. Being so, we should not
+        # fail the test, as long as at least one connection was successful.
+        self.assertGreaterEqual(
+            number_of_connects, 1, "Did not connect via FIP port forwarding")
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index 938d2b0..6cd6b25 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -175,6 +175,7 @@
 
         server = self.create_server(**server_kwargs)
         self.wait_for_server_active(server['server'])
+        self.wait_for_guest_os_ready(server['server'])
         self.check_connectivity(self.fip['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
                                 keypair['private_key'])
diff --git a/neutron_tempest_plugin/scenario/test_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 23a5224..9059a2f 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -33,13 +33,15 @@
     required_extensions = ['router', 'security-group']
 
     def _verify_http_connection(self, ssh_client, ssh_server,
-                                test_ip, test_port, should_pass=True):
+                                test_ip, test_port, servers, should_pass=True):
         """Verify if HTTP connection works using remote hosts.
 
         :param ssh.Client ssh_client: The client host active SSH client.
         :param ssh.Client ssh_server: The HTTP server host active SSH client.
         :param string test_ip: IP address of HTTP server
         :param string test_port: Port of HTTP server
+        :param list servers: List of servers for which console output will be
+                             logged in case when test case
         :param bool should_pass: Wheter test should pass or not.
 
         :return: if passed or not
@@ -57,6 +59,8 @@
         except Exception as e:
             if not should_pass:
                 return
+            self._log_console_output(servers)
+            self._log_local_network_status()
             raise e
 
     @classmethod
@@ -378,6 +382,7 @@
                 ssh_clients[0],
                 ssh_clients[2],
                 test_ip, port,
+                servers,
                 should_pass=False)
 
         # add two remote-group rules with port-ranges
@@ -399,7 +404,8 @@
             self._verify_http_connection(
                 ssh_clients[0],
                 ssh_clients[2],
-                test_ip, port)
+                test_ip, port,
+                servers)
 
     @decorators.idempotent_id('f07d0159-8f9e-4faa-87f5-a869ab0ad490')
     def test_intra_sg_isolation(self):
diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py
index 585af06..03bdd11 100644
--- a/neutron_tempest_plugin/scenario/test_trunk.py
+++ b/neutron_tempest_plugin/scenario/test_trunk.py
@@ -161,6 +161,7 @@
 
     def _configure_vlan_subport(self, vm, vlan_tag, vlan_subnet):
         self.wait_for_server_active(server=vm.server)
+        self.wait_for_guest_os_ready(vm.server)
         self._wait_for_trunk(trunk=vm.trunk)
         self._wait_for_port(port=vm.port)
         self._wait_for_port(port=vm.subport)
@@ -199,6 +200,7 @@
         vm2 = self._create_server_with_trunk_port()
         for vm in (vm1, vm2):
             self.wait_for_server_active(server=vm.server)
+            self.wait_for_guest_os_ready(vm.server)
             self._wait_for_trunk(vm.trunk)
             self._assert_has_ssh_connectivity(vm.ssh_client)
 
@@ -325,6 +327,7 @@
                                 use_advanced_image=use_advanced_image)
         for role in ['migrate', 'connection_test']:
             self.wait_for_server_active(servers[role].server)
+            self.wait_for_guest_os_ready(servers[role].server)
             self._configure_vlan_subport(vm=servers[role],
                                          vlan_tag=vlan_tag,
                                          vlan_subnet=vlan_subnet)
@@ -377,6 +380,7 @@
                                      vlan_subnet=vlan_subnet)
         for vm in vms:
             self.wait_for_server_active(vm.server)
+            self.wait_for_guest_os_ready(vm.server)
 
         # allow ICMP traffic
         sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index f056c7f..0b2544b 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -952,8 +952,6 @@
         return service_client.ResponseBody(resp, body)
 
     def list_floatingips(self, **kwargs):
-        post_body = {'floatingips': kwargs}
-        body = jsonutils.dumps(post_body)
         uri = '%s/floatingips' % self.uri_prefix
         if kwargs:
             uri += '?' + urlparse.urlencode(kwargs, doseq=1)
diff --git a/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py b/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py
index 995868a..2f091e0 100644
--- a/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py
+++ b/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py
@@ -14,6 +14,7 @@
 
 import time
 
+from neutron_lib.utils import test
 from oslo_log import log
 from tempest.common import utils
 from tempest import config
@@ -953,6 +954,7 @@
             username=self.ssh_user,
             private_key=self.keypair['private_key'])
 
+    @test.unstable_test("bug 1897753")
     @decorators.idempotent_id('f970f6b3-6541-47ac-a9ea-f769be1e21ba')
     @utils.services('compute', 'network')
     def test_update_port_pair_group_remove_port_pairs(self):
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index db59ac0..77947fa 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -17,6 +17,8 @@
       devstack_localrc:
         USE_PYTHON3: true
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
+        CIRROS_VERSION: 0.5.1
+        BUILD_TIMEOUT: 784
       devstack_plugins:
         neutron: https://opendev.org/openstack/neutron.git
         neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
@@ -100,8 +102,9 @@
       tempest_test_regex: ^neutron_tempest_plugin\.scenario
       devstack_localrc:
         PHYSICAL_NETWORK: default
-        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img
-        ADVANCED_IMAGE_NAME: ubuntu-16.04-server-cloudimg-amd64-disk1
+        CIRROS_VERSION: 0.5.1
+        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-amd64.img
+        ADVANCED_IMAGE_NAME: ubuntu-18.04-server-cloudimg-amd64
         ADVANCED_INSTANCE_TYPE: ds512M
         ADVANCED_INSTANCE_USER: ubuntu
         BUILD_TIMEOUT: 784
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 2c4fc02..71dfcb2 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -239,6 +239,7 @@
           $TEMPEST_CONFIG:
             neutron_plugin_options:
               available_type_drivers: local,flat,vlan,geneve
+              ipv6_metadata: False
               is_igmp_snooping_enabled: True
 
 - job:
@@ -270,8 +271,9 @@
         USE_PYTHON3: true
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_dvr) | join(',') }}"
         PHYSICAL_NETWORK: default
-        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img
-        ADVANCED_IMAGE_NAME: ubuntu-16.04-server-cloudimg-amd64-disk1
+        CIRROS_VERSION: 0.5.1
+        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-amd64.img
+        ADVANCED_IMAGE_NAME: ubuntu-18.04-server-cloudimg-amd64
         ADVANCED_INSTANCE_TYPE: ds512M
         ADVANCED_INSTANCE_USER: ubuntu
         BUILD_TIMEOUT: 784
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index afab1d3..0355f69 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -136,6 +136,30 @@
         - neutron-tempest-plugin-dvr-multinode-scenario-ussuri
 
 
+- project-template:
+    name: neutron-tempest-plugin-jobs-victoria
+    check:
+      jobs:
+        - neutron-tempest-plugin-api-victoria
+        - neutron-tempest-plugin-designate-scenario-victoria:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
+        - neutron-tempest-plugin-scenario-linuxbridge-victoria
+        - neutron-tempest-plugin-scenario-openvswitch-victoria
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-victoria
+        - neutron-tempest-plugin-scenario-ovn-victoria
+    gate:
+      jobs:
+        - neutron-tempest-plugin-api-victoria
+    #TODO(slaweq): Move neutron-tempest-plugin-dvr-multinode-scenario out of
+    #              the experimental queue when it will be more stable
+    experimental:
+      jobs:
+        - neutron-tempest-plugin-dvr-multinode-scenario-victoria
+
+
 - project:
     templates:
       - build-openstack-docs-pti
@@ -143,6 +167,7 @@
       - neutron-tempest-plugin-jobs-stein
       - neutron-tempest-plugin-jobs-train
       - neutron-tempest-plugin-jobs-ussuri
+      - neutron-tempest-plugin-jobs-victoria
       - check-requirements
       - tempest-plugin-jobs
       - release-notes-jobs-python3
@@ -151,6 +176,7 @@
         - neutron-tempest-plugin-sfc
         - neutron-tempest-plugin-sfc-train
         - neutron-tempest-plugin-sfc-ussuri
+        - neutron-tempest-plugin-sfc-victoria
         - neutron-tempest-plugin-bgpvpn-bagpipe:
             # TODO(slaweq): switch it to be voting when bug
             # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
@@ -162,6 +188,11 @@
             # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
             # will be fixed
             voting: false
+        - neutron-tempest-plugin-bgpvpn-bagpipe-victoria:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
+            # will be fixed
+            voting: false
         - neutron-tempest-plugin-fwaas-train:
             # TODO(slaweq): switch it to be voting when bug
             # https://bugs.launchpad.net/neutron/+bug/1858645 will be fixed
@@ -172,8 +203,10 @@
             voting: false
         - neutron-tempest-plugin-dynamic-routing
         - neutron-tempest-plugin-dynamic-routing-ussuri
+        - neutron-tempest-plugin-dynamic-routing-victoria
         - neutron-tempest-plugin-vpnaas
         - neutron-tempest-plugin-vpnaas-ussuri
+        - neutron-tempest-plugin-vpnaas-victoria
 
     gate:
       jobs:
diff --git a/zuul.d/queens_jobs.yaml b/zuul.d/queens_jobs.yaml
index 2b52978..b0ee336 100644
--- a/zuul.d/queens_jobs.yaml
+++ b/zuul.d/queens_jobs.yaml
@@ -67,6 +67,7 @@
         - dvr
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
 
@@ -95,13 +96,17 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-queens
-    parent: neutron-tempest-plugin-scenario-linuxbridge
+    parent: neutron-tempest-plugin-scenario
     nodeset: openstack-single-node-xenial
+    timeout: 10000
+    roles:
+      - zuul: openstack/neutron
     override-checkout: stable/queens
     required-projects:
       - openstack/devstack-gate
@@ -118,15 +123,30 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
+        Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
       devstack_local_conf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              enable_dvr: false
+            AGENT:
+              debug_iptables_rules: true
+          # NOTE(slaweq): We can get rid of this hardcoded absolute path when
+          # devstack-tempest job will be switched to use lib/neutron instead of
+          # lib/neutron-legacy
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            ml2:
+              type_drivers: flat,vlan,local,vxlan
         test-config:
           # NOTE: ignores linux bridge's trunk delete on bound port test
-          # for queens branch (as https://review.opendev.org/#/c/605589/
-          # fix will not apply for queens branch)
+          # for rocky branch (as https://review.opendev.org/#/c/605589/
+          # fix will not apply for rocky branch)
           $TEMPEST_CONFIG:
             neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
               q_agent: None
 
 - job:
@@ -152,6 +172,7 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
 
 - job:
@@ -176,4 +197,5 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
diff --git a/zuul.d/rocky_jobs.yaml b/zuul.d/rocky_jobs.yaml
index a45cae8..6eb8c15 100644
--- a/zuul.d/rocky_jobs.yaml
+++ b/zuul.d/rocky_jobs.yaml
@@ -176,10 +176,13 @@
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-rocky
-    parent: neutron-tempest-plugin-scenario-linuxbridge
+    parent: neutron-tempest-plugin-scenario
+    timeout: 10000
     description: |
       This job run on py2 for stable/rocky gate.
     nodeset: openstack-single-node-xenial
+    roles:
+      - zuul: openstack/neutron
     override-checkout: stable/rocky
     required-projects: *required-projects-rocky
     vars: &linuxbridge_vars_rocky
@@ -187,15 +190,29 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         USE_PYTHON3: false
+        Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
       devstack_local_conf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              enable_dvr: false
+            AGENT:
+              debug_iptables_rules: true
+          # NOTE(slaweq): We can get rid of this hardcoded absolute path when
+          # devstack-tempest job will be switched to use lib/neutron instead of
+          # lib/neutron-legacy
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            ml2:
+              type_drivers: flat,vlan,local,vxlan
         test-config:
           # NOTE: ignores linux bridge's trunk delete on bound port test
           # for rocky branch (as https://review.opendev.org/#/c/605589/
           # fix will not apply for rocky branch)
           $TEMPEST_CONFIG:
             neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
               q_agent: None
       # NOTE(bcafarel): newer tests, unstable on rocky branch
       tempest_black_regex: "\
@@ -206,17 +223,23 @@
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-rocky
-    parent: neutron-tempest-plugin-scenario-linuxbridge
+    parent: neutron-tempest-plugin-scenario
     nodeset: openstack-single-node-xenial
+    timeout: 10000
     description: |
       This job run on py3 for other than stable/rocky gate
       which is nothing but neutron-tempest-pluign master gate.
+    roles:
+      - zuul: openstack/neutron
     override-checkout: stable/rocky
     required-projects: *required-projects-rocky
     vars:
       <<: *linuxbridge_vars_rocky
       devstack_localrc:
         USE_PYTHON3: True
+        Q_AGENT: linuxbridge
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
     branches: ^(?!stable/rocky).*$
 
 - job:
diff --git a/zuul.d/stein_jobs.yaml b/zuul.d/stein_jobs.yaml
index b132cfc..ff6ed38 100644
--- a/zuul.d/stein_jobs.yaml
+++ b/zuul.d/stein_jobs.yaml
@@ -86,6 +86,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-stein
@@ -97,6 +102,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-stein
@@ -108,6 +118,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-stein
diff --git a/zuul.d/train_jobs.yaml b/zuul.d/train_jobs.yaml
index ab560e0..a9cc5be 100644
--- a/zuul.d/train_jobs.yaml
+++ b/zuul.d/train_jobs.yaml
@@ -91,6 +91,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-train
@@ -102,6 +107,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-train
@@ -113,6 +123,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-train
diff --git a/zuul.d/ussuri_jobs.yaml b/zuul.d/ussuri_jobs.yaml
index e15cf55..135d9f5 100644
--- a/zuul.d/ussuri_jobs.yaml
+++ b/zuul.d/ussuri_jobs.yaml
@@ -95,6 +95,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-ussuri
@@ -106,6 +111,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-ussuri
@@ -117,6 +127,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
 
 - job:
     name: neutron-tempest-plugin-scenario-ovn-ussuri
diff --git a/zuul.d/victoria_jobs.yaml b/zuul.d/victoria_jobs.yaml
new file mode 100644
index 0000000..0bc1e13
--- /dev/null
+++ b/zuul.d/victoria_jobs.yaml
@@ -0,0 +1,188 @@
+- job:
+    name: neutron-tempest-plugin-api-victoria
+    parent: neutron-tempest-plugin-api
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      # TODO(slaweq): find a way to put this list of extensions in
+      # neutron repository and keep it different per branch,
+      # then it could be removed from here
+      network_api_extensions_common: &api_extensions
+        - address-scope
+        - agent
+        - allowed-address-pairs
+        - auto-allocated-topology
+        - availability_zone
+        - binding
+        - default-subnetpools
+        - dhcp_agent_scheduler
+        - dns-domain-ports
+        - dns-integration
+        - empty-string-filtering
+        - expose-port-forwarding-in-fip
+        - expose-l3-conntrack-helper
+        - ext-gw-mode
+        - external-net
+        - extra_dhcp_opt
+        - extraroute
+        - extraroute-atomic
+        - filter-validation
+        - fip-port-details
+        - flavors
+        - floating-ip-port-forwarding
+        - floatingip-pools
+        - ip-substring-filtering
+        - l3-conntrack-helper
+        - l3-flavors
+        - l3-ha
+        - l3_agent_scheduler
+        - logging
+        - metering
+        - multi-provider
+        - net-mtu
+        - net-mtu-writable
+        - network-ip-availability
+        - network_availability_zone
+        - network-segment-range
+        - pagination
+        - port-resource-request
+        - port-mac-address-regenerate
+        - port-security
+        - port-security-groups-filtering
+        - project-id
+        - provider
+        - qos
+        - qos-bw-minimum-ingress
+        - qos-fip
+        - quotas
+        - quota_details
+        - rbac-address-scope
+        - rbac-policies
+        - rbac-security-groups
+        - rbac-subnetpool
+        - router
+        - router-admin-state-down-before-update
+        - router_availability_zone
+        - security-group
+        - segment
+        - service-type
+        - sorting
+        - standard-attr-description
+        - standard-attr-revisions
+        - standard-attr-segment
+        - standard-attr-tag
+        - standard-attr-timestamp
+        - subnet_allocation
+        - subnet-dns-publish-fixed-ip
+        - subnetpool-prefix-ops
+        - tag-ports-during-bulk-creation
+        - trunk
+        - trunk-details
+        - uplink-status-propagation
+      network_api_extensions_tempest:
+        - dvr
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-victoria
+    parent: neutron-tempest-plugin-scenario-openvswitch
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions: *api_extensions
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-victoria
+    parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    override-checkout: stable-victoria
+    vars:
+      branch_override: stable-victoria
+      network_api_extensions: *api_extensions
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
+
+- job:
+    name: neutron-tempest-plugin-scenario-linuxbridge-victoria
+    parent: neutron-tempest-plugin-scenario-linuxbridge
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions: *api_extensions
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              ipv6_metadata: False
+
+- job:
+    name: neutron-tempest-plugin-scenario-ovn-victoria
+    parent: neutron-tempest-plugin-scenario-ovn
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions: *api_extensions
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+
+- job:
+    name: neutron-tempest-plugin-dvr-multinode-scenario-victoria
+    parent: neutron-tempest-plugin-dvr-multinode-scenario
+    override-checkout: stable/victoria
+    vars:
+      network_api_extensions_common: *api_extensions
+      branch_override: stable/victoria
+
+- job:
+    name: neutron-tempest-plugin-designate-scenario-victoria
+    parent: neutron-tempest-plugin-designate-scenario
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-sfc-victoria
+    parent: neutron-tempest-plugin-sfc
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-bgpvpn-bagpipe-victoria
+    parent: neutron-tempest-plugin-bgpvpn-bagpipe
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-dynamic-routing-victoria
+    parent: neutron-tempest-plugin-dynamic-routing
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-vpnaas-victoria
+    parent: neutron-tempest-plugin-vpnaas
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions