Merge "Security group assignment negative tests"
diff --git a/.zuul.yaml b/.zuul.yaml
index e544bdb..7a3c106 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -181,7 +181,7 @@
       - openstack/devstack-gate
       - openstack/neutron
       - name: openstack/neutron-tempest-plugin
-        override-checkout: 0.7.0
+        override-checkout: 0.3.0
       - openstack/tempest
     vars:
       branch_override: stable/queens
@@ -243,7 +243,6 @@
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
-        TEMPEST_BRANCH: queens-em
 
 - job:
     name: neutron-tempest-plugin-api-rocky
@@ -534,19 +533,22 @@
       - openstack/devstack-gate
       - openstack/neutron
       - name: openstack/neutron-tempest-plugin
-        override-checkout: 0.7.0
+        override-checkout: 0.3.0
       - openstack/tempest
     vars:
       branch_override: stable/queens
       network_api_extensions: *api_extensions_queens
       # TODO(slaweq): remove trunks subport_connectivity test from blacklist
       # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
-      tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)"
+      # NOTE(bcafarel): remove DNS test as queens pinned version does not have
+      # fix for https://bugs.launchpad.net/neutron/+bug/1826419
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)|\
+          (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
-        TEMPEST_BRANCH: queens-em
 
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-rocky
@@ -684,16 +686,19 @@
       - openstack/devstack-gate
       - openstack/neutron
       - name: openstack/neutron-tempest-plugin
-        override-checkout: 0.7.0
+        override-checkout: 0.3.0
       - openstack/tempest
     vars:
       branch_override: stable/queens
       network_api_extensions: *api_extensions_queens
+      # NOTE(bcafarel): remove DNS test as queens pinned version does not have
+      # fix for https://bugs.launchpad.net/neutron/+bug/1826419
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
-        TEMPEST_BRANCH: queens-em
       devstack_local_conf:
         test-config:
           # NOTE: ignores linux bridge's trunk delete on bound port test
@@ -887,18 +892,21 @@
       - openstack/devstack-gate
       - openstack/neutron
       - name: openstack/neutron-tempest-plugin
-        override-checkout: 0.7.0
+        override-checkout: 0.3.0
       - openstack/tempest
     vars:
       branch_override: stable/queens
       network_api_extensions_common: *api_extensions_queens
       # TODO(slaweq): remove trunks subport_connectivity test from blacklist
       # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
-      tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)"
+      # NOTE(bcafarel): remove DNS test as queens pinned version does not have
+      # fix for https://bugs.launchpad.net/neutron/+bug/1826419
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)|\
+          (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
-        TEMPEST_BRANCH: queens-em
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-rocky
@@ -980,15 +988,18 @@
       - openstack/devstack-gate
       - openstack/neutron
       - name: openstack/neutron-tempest-plugin
-        override-checkout: 0.7.0
+        override-checkout: 0.3.0
       - openstack/tempest
     vars:
       branch_override: stable/queens
       network_api_extensions_common: *api_extensions_queens
+      # NOTE(bcafarel): remove DNS test as queens pinned version does not have
+      # fix for https://bugs.launchpad.net/neutron/+bug/1826419
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
-        TEMPEST_BRANCH: queens-em
 
 - job:
     name: neutron-tempest-plugin-designate-scenario-rocky
@@ -1244,7 +1255,9 @@
     templates:
       - build-openstack-docs-pti
       - neutron-tempest-plugin-jobs
-      - neutron-tempest-plugin-jobs-rocky
+      # TODO(slaweq): bring rocky jobs back when dropping py27
+      # drama will be finally over
+      # - neutron-tempest-plugin-jobs-rocky
       - neutron-tempest-plugin-jobs-stein
       - neutron-tempest-plugin-jobs-train
       - check-requirements
diff --git a/neutron_tempest_plugin/api/admin/test_shared_network_extension.py b/neutron_tempest_plugin/api/admin/test_shared_network_extension.py
index eb902b9..1444b2d 100644
--- a/neutron_tempest_plugin/api/admin/test_shared_network_extension.py
+++ b/neutron_tempest_plugin/api/admin/test_shared_network_extension.py
@@ -104,8 +104,8 @@
         port = self.create_port(self.shared_network)
         self.addCleanup(self.admin_client.delete_port, port['id'])
         # verify the tenant id of admin network and non admin port
-        self.assertNotEqual(self.shared_network['tenant_id'],
-                            port['tenant_id'])
+        self.assertNotEqual(self.shared_network['project_id'],
+                            port['project_id'])
 
     @decorators.idempotent_id('3e39c4a6-9caf-4710-88f1-d20073c6dd76')
     def test_create_bulk_shared_network(self):
@@ -183,7 +183,7 @@
         super(RBACSharedNetworksTest, cls).resource_setup()
         cls.client2 = cls.os_alt.network_client
 
-    def _make_admin_net_and_subnet_shared_to_tenant_id(self, tenant_id):
+    def _make_admin_net_and_subnet_shared_to_project_id(self, project_id):
         net = self.admin_client.create_network(
             name=data_utils.rand_name('test-network'))['network']
         self.addCleanup(self.admin_client.delete_network, net['id'])
@@ -191,7 +191,7 @@
         # network is shared to first unprivileged client by default
         pol = self.admin_client.create_rbac_policy(
             object_type='network', object_id=net['id'],
-            action='access_as_shared', target_tenant=tenant_id
+            action='access_as_shared', target_tenant=project_id
         )['rbac_policy']
         return {'network': net, 'subnet': subnet, 'policy': pol}
 
@@ -199,21 +199,21 @@
     @decorators.idempotent_id('86c3529b-1231-40de-803c-bfffffff1eee')
     def test_create_rbac_policy_with_target_tenant_none(self):
         with testtools.ExpectedException(lib_exc.BadRequest):
-            self._make_admin_net_and_subnet_shared_to_tenant_id(
-                tenant_id=None)
+            self._make_admin_net_and_subnet_shared_to_project_id(
+                project_id=None)
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('86c3529b-1231-40de-803c-bfffffff1fff')
     def test_create_rbac_policy_with_target_tenant_too_long_id(self):
         with testtools.ExpectedException(lib_exc.BadRequest):
-            target_tenant = '1234' * 100
-            self._make_admin_net_and_subnet_shared_to_tenant_id(
-                tenant_id=target_tenant)
+            target_project = '1234' * 100
+            self._make_admin_net_and_subnet_shared_to_project_id(
+                project_id=target_project)
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff1fff')
     def test_network_only_visible_to_policy_target(self):
-        net = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        net = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)['network']
         self.client.show_network(net['id'])
         with testtools.ExpectedException(lib_exc.NotFound):
@@ -222,7 +222,7 @@
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff2fff')
     def test_subnet_on_network_only_visible_to_policy_target(self):
-        sub = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        sub = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)['subnet']
         self.client.show_subnet(sub['id'])
         with testtools.ExpectedException(lib_exc.NotFound):
@@ -231,7 +231,7 @@
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff2eee')
     def test_policy_target_update(self):
-        res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        res = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)
         # change to client2
         update_res = self.admin_client.update_rbac_policy(
@@ -245,7 +245,7 @@
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-affefefef321')
     def test_duplicate_policy_error(self):
-        res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        res = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)
         with testtools.ExpectedException(lib_exc.Conflict):
             self.admin_client.create_rbac_policy(
@@ -254,7 +254,7 @@
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff3fff')
     def test_port_presence_prevents_network_rbac_policy_deletion(self):
-        res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        res = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)
         port = self.create_port(res['network'])
         # a port on the network should prevent the deletion of a policy
@@ -272,7 +272,7 @@
         self_share = self.client.create_rbac_policy(
                          object_type='network', object_id=net['id'],
                          action='access_as_shared',
-                         target_tenant=net['tenant_id'])['rbac_policy']
+                         target_tenant=net['project_id'])['rbac_policy']
         port = self.create_port(net)
         self.client.delete_rbac_policy(self_share['id'])
         self.client.delete_port(port['id'])
@@ -318,14 +318,14 @@
             object_type='network', object_id=net['id'],
             action='access_as_shared', target_tenant=self.client2.tenant_id)
         field_args = (('id',), ('id', 'action'), ('object_type', 'object_id'),
-                      ('tenant_id', 'target_tenant'))
+                      ('project_id', 'target_tenant'))
         for fields in field_args:
             res = self.client.list_rbac_policies(fields=fields)
             self.assertEqual(set(fields), set(res['rbac_policies'][0].keys()))
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff5fff')
     def test_policy_show(self):
-        res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        res = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)
         p1 = res['policy']
         p2 = self.admin_client.create_rbac_policy(
@@ -358,7 +358,7 @@
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff6fff')
     def test_regular_client_blocked_from_sharing_anothers_network(self):
-        net = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        net = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)['network']
         with testtools.ExpectedException(lib_exc.BadRequest):
             self.client.create_rbac_policy(
@@ -402,7 +402,7 @@
         self_share = self.client.create_rbac_policy(
                          object_type='network', object_id=net['id'],
                          action='access_as_shared',
-                         target_tenant=net['tenant_id'])['rbac_policy']
+                         target_tenant=net['project_id'])['rbac_policy']
         port = self.create_port(net)
         self.client.update_rbac_policy(self_share['id'],
                                        target_tenant=self.client2.tenant_id)
@@ -411,7 +411,7 @@
     @utils.requires_ext(extension="standard-attr-revisions", service="network")
     @decorators.idempotent_id('86c3529b-1231-40de-1234-89664291a4cb')
     def test_rbac_bumps_network_revision(self):
-        resp = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        resp = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)
         net_id = resp['network']['id']
         rev = self.client.show_network(net_id)['network']['revision_number']
@@ -425,7 +425,7 @@
 
     @decorators.idempotent_id('86c3529b-1231-40de-803c-aeeeeeee7fff')
     def test_filtering_works_with_rbac_records_present(self):
-        resp = self._make_admin_net_and_subnet_shared_to_tenant_id(
+        resp = self._make_admin_net_and_subnet_shared_to_project_id(
             self.client.tenant_id)
         net = resp['network']['id']
         sub = resp['subnet']['id']
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 4441dd1..0891bb6 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -378,16 +378,6 @@
         return cls.create_network(name=network_name, shared=True, **kwargs)
 
     @classmethod
-    def create_network_keystone_v3(cls, network_name=None, project_id=None,
-                                   tenant_id=None, client=None):
-        params = {}
-        if project_id:
-            params['project_id'] = project_id
-        if tenant_id:
-            params['tenant_id'] = tenant_id
-        return cls.create_network(name=network_name, client=client, **params)
-
-    @classmethod
     def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
                       ip_version=None, client=None, reserve_cidr=True,
                       **kwargs):
diff --git a/neutron_tempest_plugin/api/test_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
index 26a8c05..cdd7017 100644
--- a/neutron_tempest_plugin/api/test_security_groups.py
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -91,6 +91,94 @@
             self.assertIsNotNone(secgrp['id'])
 
 
+class BaseSecGroupQuota(base.BaseAdminNetworkTest):
+
+    def _create_max_allowed_sg_amount(self):
+        sg_amount = self._get_sg_amount()
+        sg_quota = self._get_sg_quota()
+        sg_to_create = sg_quota - sg_amount
+        self._create_security_groups(sg_to_create)
+
+    def _create_security_groups(self, amount):
+        for _ in range(amount):
+            sg = self.create_security_group()
+            self.addCleanup(self.delete_security_group, sg)
+
+    def _increase_sg_quota(self):
+        sg_quota = self._get_sg_quota()
+        new_sg_quota = 2 * sg_quota
+        self._set_sg_quota(new_sg_quota)
+        return new_sg_quota
+
+    def _decrease_sg_quota(self):
+        sg_quota = self._get_sg_quota()
+        new_sg_quota = sg_quota // 2
+        self._set_sg_quota(new_sg_quota)
+        return new_sg_quota
+
+    def _set_sg_quota(self, val):
+        sg_quota = self._get_sg_quota()
+        project_id = self.client.tenant_id
+        self.admin_client.update_quotas(project_id, **{'security_group': val})
+        self.addCleanup(self.admin_client.update_quotas,
+                project_id,
+                **{'security_group': sg_quota})
+
+    def _get_sg_quota(self):
+        project_id = self.client.tenant_id
+        quotas = self.admin_client.show_quotas(project_id)
+        return quotas['quota']['security_group']
+
+    def _get_sg_amount(self):
+        project_id = self.client.tenant_id
+        filter_query = {'project_id': project_id}
+        security_groups = self.client.list_security_groups(**filter_query)
+        return len(security_groups['security_groups'])
+
+
+class SecGroupQuotaTest(BaseSecGroupQuota):
+
+    credentials = ['primary', 'admin']
+    required_extensions = ['security-group', 'quotas']
+
+    @decorators.idempotent_id('1826aa02-090d-4717-b43a-50ee449b02e7')
+    def test_sg_quota_values(self):
+        values = [-1, 0, 10, 2147483647]
+        for value in values:
+            self._set_sg_quota(value)
+            self.assertEqual(value, self._get_sg_quota())
+
+    @decorators.idempotent_id('df7981fb-b83a-4779-b13e-65494ef44a72')
+    def test_max_allowed_sg_amount(self):
+        self._create_max_allowed_sg_amount()
+        self.assertEqual(self._get_sg_quota(), self._get_sg_amount())
+
+    @decorators.idempotent_id('623d909c-6ef8-43d6-93ee-97086e2651e8')
+    def test_sg_quota_increased(self):
+        self._create_max_allowed_sg_amount()
+        new_quota = self._increase_sg_quota()
+        self._create_max_allowed_sg_amount()
+        quota_set = self._get_sg_quota()
+        self.assertEqual(quota_set, new_quota,
+                "Security group quota was not changed correctly")
+        self.assertEqual(quota_set, self._get_sg_amount(),
+                "Amount of security groups doesn't match quota")
+
+    @decorators.idempotent_id('ba95676c-8d9a-4482-b4ec-74d51a4602a6')
+    def test_sg_quota_decrease_less_than_created(self):
+        self._create_max_allowed_sg_amount()
+        new_quota = self._decrease_sg_quota()
+        self.assertEqual(self._get_sg_quota(), new_quota)
+
+    @decorators.idempotent_id('d43cf1a7-aa7e-4c41-9340-627a1a6ab961')
+    def test_create_sg_when_quota_disabled(self):
+        sg_amount = self._get_sg_amount()
+        self._set_sg_quota(-1)
+        self._create_security_groups(10)
+        new_sg_amount = self._get_sg_amount()
+        self.assertGreater(new_sg_amount, sg_amount)
+
+
 class SecGroupProtocolTest(base.BaseNetworkTest):
 
     protocol_names = base_security_groups.V4_PROTOCOL_NAMES
diff --git a/neutron_tempest_plugin/api/test_security_groups_negative.py b/neutron_tempest_plugin/api/test_security_groups_negative.py
index d857197..24e2289 100644
--- a/neutron_tempest_plugin/api/test_security_groups_negative.py
+++ b/neutron_tempest_plugin/api/test_security_groups_negative.py
@@ -21,6 +21,7 @@
 
 from neutron_tempest_plugin.api import base
 from neutron_tempest_plugin.api import base_security_groups
+from neutron_tempest_plugin.api import test_security_groups
 
 
 LONG_NAME_NG = 'x' * (db_const.NAME_FIELD_SIZE + 1)
@@ -150,3 +151,22 @@
     def test_create_security_group_rule_with_ipv6_protocol_integers(self):
         self._test_create_security_group_rule_with_bad_protocols(
             base_security_groups.V6_PROTOCOL_INTS)
+
+
+class NegativeSecGroupQuotaTest(test_security_groups.BaseSecGroupQuota):
+
+    credentials = ['primary', 'admin']
+    required_extensions = ['security-group', 'quotas']
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('63f00cba-fcf5-4000-a3ee-eca58a1795c1')
+    def test_create_excess_sg(self):
+        self._set_sg_quota(0)
+        self.assertRaises(lib_exc.Conflict, self.create_security_group)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('90a83445-bbc2-49d8-8c85-a111c08cd7fb')
+    def test_sg_quota_incorrect_values(self):
+        values = [-2, 2147483648, "value"]
+        for value in values:
+            self.assertRaises(lib_exc.BadRequest, self._set_sg_quota, value)
diff --git a/neutron_tempest_plugin/common/ip.py b/neutron_tempest_plugin/common/ip.py
index 1702bd3..70a3dd5 100644
--- a/neutron_tempest_plugin/common/ip.py
+++ b/neutron_tempest_plugin/common/ip.py
@@ -15,6 +15,7 @@
 #    under the License.
 
 import collections
+import re
 import subprocess
 
 import netaddr
@@ -299,6 +300,23 @@
                    netaddr.IPNetwork(subnet['cidr']).prefixlen)
 
 
+def arp_table():
+    # 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)))
+    return arp_table
+
+
 class Route(HasProperties,
             collections.namedtuple('Route',
                                    ['dest', 'properties'])):
@@ -314,3 +332,40 @@
     @property
     def src_ip(self):
         return netaddr.IPAddress(self.src)
+
+    def __str__(self):
+        properties_str = ' '.join('%s %s' % (k, v)
+                                  for k, v in self.properties.items())
+        return '%(dest)s %(properties)s' % {'dest': self.dest,
+                                            'properties': properties_str}
+
+
+class ARPregister(collections.namedtuple(
+        'ARPregister',
+        ['ip_address', 'hw_type', 'flags', 'mac_address', 'mask', 'device'])):
+
+    def __str__(self):
+        return '%s %s %s %s %s %s' % (self.ip_address, self.hw_type,
+                                      self.flags, self.mac_address, self.mask,
+                                      self.device)
+
+
+def find_valid_cidr(valid_cidr='10.0.0.0/8', used_cidr=None):
+    total_ips = netaddr.IPSet(netaddr.IPNetwork(valid_cidr))
+    if used_cidr:
+        used_network = netaddr.IPNetwork(used_cidr)
+        netmask = used_network.netmask.netmask_bits()
+        valid_ips = total_ips.difference(netaddr.IPSet(used_network))
+    else:
+        valid_ips = total_ips
+        netmask = 24
+
+    for ip in valid_ips:
+        valid_network = netaddr.IPNetwork('%s/%s' % (ip, netmask))
+        if valid_network in valid_ips:
+            return valid_network.cidr
+
+    exception_str = 'No valid CIDR found in %s' % valid_cidr
+    if used_cidr:
+        exception_str += ', used CIDR %s' % used_cidr
+    raise Exception(exception_str)
diff --git a/neutron_tempest_plugin/common/utils.py b/neutron_tempest_plugin/common/utils.py
index bd7a367..631f75b 100644
--- a/neutron_tempest_plugin/common/utils.py
+++ b/neutron_tempest_plugin/common/utils.py
@@ -18,7 +18,6 @@
 
 """Utilities and helper functions."""
 
-import functools
 import threading
 import time
 try:
@@ -83,22 +82,6 @@
         raise WaitTimeout("Timed out after %d seconds" % timeout)
 
 
-# TODO(haleyb): move to neutron-lib
-# code copied from neutron repository - neutron/tests/base.py
-def unstable_test(reason):
-    def decor(f):
-        @functools.wraps(f)
-        def inner(self, *args, **kwargs):
-            try:
-                return f(self, *args, **kwargs)
-            except Exception as e:
-                msg = ("%s was marked as unstable because of %s, "
-                       "failure was: %s") % (self.id(), reason, e)
-                raise self.skipTest(msg)
-        return inner
-    return decor
-
-
 def override_class(overriden_class, overrider_class):
     """Override class definition with a MixIn class
 
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index 51fbafc..2f2f913 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -67,7 +67,7 @@
 
     # Multicast tests settings
     cfg.StrOpt('multicast_group_range',
-               default='224.0.0.120-224.0.0.250',
+               default='225.0.0.120-225.0.0.250',
                help='Unallocated multi-cast IPv4 range, which will be used to '
                     'test the multi-cast support.'),
 
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 5a29aa1..42bd33b 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -26,6 +26,7 @@
 from tempest.lib import exceptions as lib_exc
 
 from neutron_tempest_plugin.api import base as base_api
+from neutron_tempest_plugin.common import ip as ip_utils
 from neutron_tempest_plugin.common import shell
 from neutron_tempest_plugin.common import ssh
 from neutron_tempest_plugin import config
@@ -219,6 +220,7 @@
         except lib_exc.SSHTimeout as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
 
     def _log_console_output(self, servers=None):
@@ -239,10 +241,16 @@
                 LOG.debug("Server %s disappeared(deleted) while looking "
                           "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))
+        arp_table = ip_utils.arp_table()
+        LOG.debug('Local ARP table:\n%s', '\n'.join(str(r) for r in arp_table))
+
     def _check_remote_connectivity(self, source, dest, count,
                                    should_succeed=True,
                                    nic=None, mtu=None, fragmentation=True,
-                                   timeout=None):
+                                   timeout=None, pattern=None):
         """check ping server via source ssh connection
 
         :param source: RemoteClient: an ssh connection from which to ping
@@ -253,12 +261,13 @@
         :param mtu: mtu size for the packet to be sent
         :param fragmentation: Flag for packet fragmentation
         :param timeout: Timeout for all ping packet(s) to succeed
+        :param pattern: hex digits included in ICMP messages
         :returns: boolean -- should_succeed == ping
         :returns: ping is false if ping failed
         """
         def ping_host(source, host, count,
                       size=CONF.validation.ping_size, nic=None, mtu=None,
-                      fragmentation=True):
+                      fragmentation=True, pattern=None):
             IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
             IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
 
@@ -274,13 +283,16 @@
                     cmd += ' -M do'
                 size = str(net_utils.get_ping_payload_size(
                     mtu=mtu, ip_version=ip_version))
+            if pattern:
+                cmd += ' -p {pattern}'.format(pattern=pattern)
             cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
             return source.exec_command(cmd)
 
         def ping_remote():
             try:
                 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
-                                   fragmentation=fragmentation)
+                                   fragmentation=fragmentation,
+                                   pattern=pattern)
 
             except lib_exc.SSHExecCommandFailed:
                 LOG.warning('Failed to ping IP: %s via a ssh connection '
@@ -301,12 +313,13 @@
     def check_remote_connectivity(self, source, dest, should_succeed=True,
                                   nic=None, mtu=None, fragmentation=True,
                                   servers=None, timeout=None,
-                                  ping_count=CONF.validation.ping_count):
+                                  ping_count=CONF.validation.ping_count,
+                                  pattern=None):
         try:
             self.assertTrue(self._check_remote_connectivity(
                 source, dest, ping_count, should_succeed, nic, mtu,
                 fragmentation,
-                timeout=timeout))
+                timeout=timeout, pattern=pattern))
         except lib_exc.SSHTimeout as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
@@ -416,7 +429,7 @@
         udp = ''
         if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
             udp = '-u'
-        cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo %(msg)s &" % {
+        cmd = "sudo nc %(udp)s -p %(port)s -lk -c echo %(msg)s &" % {
             'udp': udp, 'port': port, 'msg': echo_msg}
         try:
             return ssh_client.exec_command(cmd)
diff --git a/neutron_tempest_plugin/scenario/test_connectivity.py b/neutron_tempest_plugin/scenario/test_connectivity.py
index 78d8d95..5aa8f73 100644
--- a/neutron_tempest_plugin/scenario/test_connectivity.py
+++ b/neutron_tempest_plugin/scenario/test_connectivity.py
@@ -13,11 +13,15 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import netaddr
+
+from neutron_lib import constants
 from tempest.common import compute
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+from neutron_tempest_plugin.common import ip as ip_utils
 from neutron_tempest_plugin.common import ssh
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import base
@@ -161,14 +165,14 @@
         Subnet is connected to dvr and non-dvr routers in the same time, test
         ensures that connectivity from VM to both routers is working.
 
-        Test scenario:
+        Test scenario: (NOTE: 10.1.0.0/24 private CIDR is used as an example)
         +----------------+                  +------------+
         | Non-dvr router |                  | DVR router |
         |                |                  |            |
-        |    10.0.0.1    |                  |  10.0.0.x  |
+        |    10.1.0.1    |                  |  10.1.0.x  |
         +-------+--------+                  +-----+------+
                 |                                 |
-                |         10.0.0.0/24             |
+                |         10.1.0.0/24             |
                 +----------------+----------------+
                                  |
                                +-+-+
@@ -176,16 +180,28 @@
                                +---+
 
         where:
-        10.0.0.1 - is subnet's gateway IP address,
-        10.0.0.x - is any other IP address taken from subnet's range
+        10.1.0.1 - is subnet's gateway IP address,
+        10.1.0.x - is any other IP address taken from subnet's range
 
-        Test ensures that both 10.0.0.1 and 10.0.0.x IP addresses are
+        Test ensures that both 10.1.0.1 and 10.1.0.x IP addresses are
         reachable from VM.
         """
+        ext_network = self.client.show_network(self.external_network_id)
+        for ext_subnetid in ext_network['network']['subnets']:
+            ext_subnet = self.os_admin.network_client.show_subnet(ext_subnetid)
+            ext_cidr = ext_subnet['subnet']['cidr']
+            if ext_subnet['subnet']['ip_version'] == constants.IP_VERSION_4:
+                break
+        else:
+            self.fail('No IPv4 subnet was found in external network %s' %
+                      ext_network['network']['id'])
+
+        subnet_cidr = ip_utils.find_valid_cidr(used_cidr=ext_cidr)
+        gw_ip = netaddr.IPAddress(subnet_cidr.first + 1)
 
         network = self.create_network()
         subnet = self.create_subnet(
-            network, cidr="10.0.0.0/24", gateway="10.0.0.1")
+            network, cidr=str(subnet_cidr), gateway=str(gw_ip))
 
         non_dvr_router = self.create_router_by_client(
             tenant_id=self.client.tenant_id,
@@ -221,8 +237,7 @@
             fip['floating_ip_address'], CONF.validation.image_ssh_user,
             pkey=self.keypair['private_key'])
 
-        self.check_remote_connectivity(
-            sshclient, '10.0.0.1', ping_count=10)
+        self.check_remote_connectivity(sshclient, str(gw_ip), ping_count=10)
         self.check_remote_connectivity(
             sshclient, dvr_router_port['fixed_ips'][0]['ip_address'],
             ping_count=10)
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index 39aa09d..e276a02 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -17,6 +17,7 @@
 
 from neutron_lib import constants as lib_constants
 from neutron_lib.services.qos import constants as qos_consts
+from neutron_lib.utils import test
 from tempest.common import utils
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
@@ -151,7 +152,7 @@
 
     same_network = True
 
-    @common_utils.unstable_test("bug 1717302")
+    @test.unstable_test("bug 1717302")
     @decorators.idempotent_id('05c4e3b3-7319-4052-90ad-e8916436c23b')
     def test_east_west(self):
         self._test_east_west()
@@ -169,7 +170,7 @@
 
     same_network = False
 
-    @common_utils.unstable_test("bug 1717302")
+    @test.unstable_test("bug 1717302")
     @decorators.idempotent_id('f18f0090-3289-4783-b956-a0f8ac511e8b')
     def test_east_west(self):
         self._test_east_west()
@@ -212,7 +213,7 @@
     def resource_setup(cls):
         super(FloatingIPPortDetailsTest, cls).resource_setup()
 
-    @common_utils.unstable_test("bug 1815585")
+    @test.unstable_test("bug 1815585")
     @decorators.idempotent_id('a663aeee-dd81-492b-a207-354fd6284dbe')
     def test_floatingip_port_details(self):
         """Tests the following:
diff --git a/neutron_tempest_plugin/scenario/test_migration.py b/neutron_tempest_plugin/scenario/test_migration.py
index f4b918c..410c64e 100644
--- a/neutron_tempest_plugin/scenario/test_migration.py
+++ b/neutron_tempest_plugin/scenario/test_migration.py
@@ -17,6 +17,7 @@
 
 from neutron_lib.api.definitions import portbindings as pb
 from neutron_lib import constants as const
+from neutron_lib.utils import test
 from tempest.common import utils
 from tempest.lib import decorators
 import testtools
@@ -224,7 +225,7 @@
 
 class NetworkMigrationFromDVRHA(NetworkMigrationTestBase):
 
-    @common_utils.unstable_test("bug 1756301")
+    @test.unstable_test("bug 1756301")
     @decorators.idempotent_id('1be9b2e2-379c-40a4-a269-6687b81df691')
     @testtools.skipUnless(
         CONF.neutron_plugin_options.l3_agent_mode == 'dvr_snat',
@@ -233,7 +234,7 @@
         self._test_migration(before_dvr=True, before_ha=True,
                              after_dvr=False, after_ha=False)
 
-    @common_utils.unstable_test("bug 1756301")
+    @test.unstable_test("bug 1756301")
     @decorators.idempotent_id('55957267-4e84-4314-a2f7-7cd36a2df04b')
     @testtools.skipUnless(
         CONF.neutron_plugin_options.l3_agent_mode == 'dvr_snat',
@@ -242,7 +243,7 @@
         self._test_migration(before_dvr=True, before_ha=True,
                              after_dvr=False, after_ha=True)
 
-    @common_utils.unstable_test("bug 1756301")
+    @test.unstable_test("bug 1756301")
     @decorators.idempotent_id('d6bedff1-72be-4a9a-8ea2-dc037cd838e0')
     @testtools.skipUnless(
         CONF.neutron_plugin_options.l3_agent_mode == 'dvr_snat',
diff --git a/neutron_tempest_plugin/scenario/test_multicast.py b/neutron_tempest_plugin/scenario/test_multicast.py
index d511b3b..a39a0c3 100644
--- a/neutron_tempest_plugin/scenario/test_multicast.py
+++ b/neutron_tempest_plugin/scenario/test_multicast.py
@@ -15,6 +15,7 @@
 
 import netaddr
 from neutron_lib import constants
+from neutron_lib.utils import test
 from oslo_log import log
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -229,7 +230,7 @@
         server['ssh_client'].execute_script(
             'echo "%s" > ~/multicast_traffic_receiver.py' % check_script)
 
-    @utils.unstable_test("bug 1850288")
+    @test.unstable_test("bug 1850288")
     @decorators.idempotent_id('113486fc-24c9-4be4-8361-03b1c9892867')
     def test_multicast_between_vms_on_same_network(self):
         """Test multicast messaging between two servers on the same network
diff --git a/neutron_tempest_plugin/scenario/test_port_forwardings.py b/neutron_tempest_plugin/scenario/test_port_forwardings.py
index 06f175b..7283887 100644
--- a/neutron_tempest_plugin/scenario/test_port_forwardings.py
+++ b/neutron_tempest_plugin/scenario/test_port_forwardings.py
@@ -14,12 +14,12 @@
 #    under the License.
 
 from neutron_lib import constants
+from neutron_lib.utils import test
 from oslo_log import log
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
 from neutron_tempest_plugin.common import ssh
-from neutron_tempest_plugin.common import utils
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import base
 
@@ -101,7 +101,7 @@
                     server['port_forwarding_udp']['external_port'],
                     constants.PROTO_NAME_UDP))
 
-    @utils.unstable_test("bug 1850800")
+    @test.unstable_test("bug 1850800")
     @decorators.idempotent_id('ab40fc48-ca8d-41a0-b2a3-f6679c847bfe')
     def test_port_forwarding_to_2_servers(self):
         udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index 05095a7..8f79b5d 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -1063,21 +1063,6 @@
         self.expected_success(204, resp.status)
         service_client.ResponseBody(resp, body)
 
-    def create_network_keystone_v3(self, name, project_id, tenant_id=None):
-        uri = '%s/networks' % self.uri_prefix
-        post_data = {
-            'network': {
-                'name': name,
-                'project_id': project_id
-            }
-        }
-        if tenant_id is not None:
-            post_data['network']['tenant_id'] = tenant_id
-        resp, body = self.post(uri, self.serialize(post_data))
-        body = self.deserialize_single(body)
-        self.expected_success(201, resp.status)
-        return service_client.ResponseBody(resp, body)
-
     def list_extensions(self, **filters):
         uri = self.get_uri("extensions")
         if filters: