Merge "Update service client access test_dns_integration"
diff --git a/devstack/customize_image.sh b/devstack/customize_image.sh
index 77ff699..0b9d8ab 100644
--- a/devstack/customize_image.sh
+++ b/devstack/customize_image.sh
@@ -149,6 +149,7 @@
 function customize_image {
     local image_file=$1
     local top_dir=$(dirname "${NEUTRON_TEMPEST_PLUGIN_DIR}")
+    install_customize_image_tools
     (
         export TEMP_DIR DISK_FORMAT RC_DIR
         if [[ "$(basename ${image_file})" == ubuntu-* ]]; then
diff --git a/devstack/functions.sh b/devstack/functions.sh
index 8d8a4bf..f758ff6 100644
--- a/devstack/functions.sh
+++ b/devstack/functions.sh
@@ -85,3 +85,15 @@
     fi
     iniset $TEMPEST_CONFIG neutron_plugin_options advanced_image_flavor_ref $flavor_ref
 }
+
+
+function create_flavor_for_advance_image {
+    local name=$1
+    local ram=$2
+    local disk=$3
+    local vcpus=$4
+
+    if [[ -z $(openstack flavor list | grep $name) ]]; then
+        openstack flavor create --ram $ram --disk $disk --vcpus $vcpus $name
+    fi
+}
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index 7a46014..42c31cd 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -6,7 +6,6 @@
 # install_neutron_tempest_plugin
 function install_neutron_tempest_plugin {
     setup_dev_lib "neutron-tempest-plugin"
-    install_customize_image_tools
 }
 
 if [[ "$1" == "stack" ]]; then
@@ -20,6 +19,7 @@
         test-config)
             echo_summary "Configuring neutron-tempest-plugin tempest options"
             configure_advanced_image
+            create_flavor_for_advance_image $ADVANCED_INSTANCE_TYPE 256 4 1
             configure_flavor_for_advanced_image
     esac
 fi
diff --git a/neutron_tempest_plugin/api/admin/test_ports.py b/neutron_tempest_plugin/api/admin/test_ports.py
index 6f51b1c..b277fac 100644
--- a/neutron_tempest_plugin/api/admin/test_ports.py
+++ b/neutron_tempest_plugin/api/admin/test_ports.py
@@ -94,16 +94,27 @@
         cls.prov_network = cls.create_provider_network(
             physnet_name=cls.physnet_name, start_segmentation_id=base_segm)
 
+    @classmethod
+    def setup_clients(cls):
+        super(PortTestCasesResourceRequest, cls).setup_clients()
+        cls.qos_minimum_bandwidth_rules_client = \
+            cls.os_admin.qos_minimum_bandwidth_rules_client
+        cls.qos_bw_limit_rule_client = \
+            cls.os_admin.qos_limit_bandwidth_rules_client
+
     def _create_qos_policy_and_port(self, network, vnic_type,
                                     network_policy=False):
         qos_policy = self.create_qos_policy(
             name=data_utils.rand_name('test_policy'), shared=True)
-        self.create_qos_minimum_bandwidth_rule(qos_policy['id'],
-                                               self.EGRESS_KBPS,
-                                               const.EGRESS_DIRECTION)
-        self.create_qos_minimum_bandwidth_rule(qos_policy['id'],
-                                               self.INGRESS_KBPS,
-                                               const.INGRESS_DIRECTION)
+        self.qos_minimum_bandwidth_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=qos_policy['id'],
+            **{'direction': const.EGRESS_DIRECTION,
+               'min_kbps': self.EGRESS_KBPS})
+
+        self.qos_minimum_bandwidth_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=qos_policy['id'],
+            **{'direction': const.INGRESS_DIRECTION,
+               'min_kbps': self.INGRESS_KBPS})
 
         port_policy_id = qos_policy['id'] if not network_policy else None
         port_kwargs = {
@@ -163,9 +174,11 @@
 
         # Note(lajoskatona): Add a non-minimum-bandwidth-rule to the policy
         # to make sure that the resource request is not filled with it.
-        self.create_qos_bandwidth_limit_rule(qos_policy['id'],
-                                             self.EGRESS_KBPS, 800,
-                                             const.EGRESS_DIRECTION)
+        self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
+            qos_policy['id'],
+            **{'max_kbps': self.EGRESS_KBPS,
+               'max_burst_kbps': 800,
+               'direction': const.EGRESS_DIRECTION})
 
         port_kwargs = {
             'qos_policy_id': qos_policy['id'],
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 024fe43..ecdd00a 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -763,27 +763,6 @@
         return qos_policy
 
     @classmethod
-    def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
-                                        max_burst_kbps,
-                                        direction=const.EGRESS_DIRECTION):
-        """Wrapper utility that returns a test QoS bandwidth limit rule."""
-        body = cls.admin_client.create_bandwidth_limit_rule(
-            policy_id, max_kbps, max_burst_kbps, direction)
-        qos_rule = body['bandwidth_limit_rule']
-        cls.qos_rules.append(qos_rule)
-        return qos_rule
-
-    @classmethod
-    def create_qos_minimum_bandwidth_rule(cls, policy_id, min_kbps,
-                                          direction=const.EGRESS_DIRECTION):
-        """Wrapper utility that creates and returns a QoS min bw rule."""
-        body = cls.admin_client.create_minimum_bandwidth_rule(
-            policy_id, direction, min_kbps)
-        qos_rule = body['minimum_bandwidth_rule']
-        cls.qos_rules.append(qos_rule)
-        return qos_rule
-
-    @classmethod
     def create_qos_dscp_marking_rule(cls, policy_id, dscp_mark):
         """Wrapper utility that creates and returns a QoS dscp rule."""
         body = cls.admin_client.create_dscp_marking_rule(
diff --git a/neutron_tempest_plugin/api/clients.py b/neutron_tempest_plugin/api/clients.py
index 8f5256e..6565dcb 100644
--- a/neutron_tempest_plugin/api/clients.py
+++ b/neutron_tempest_plugin/api/clients.py
@@ -22,6 +22,8 @@
 from tempest.lib.services.compute import servers_client
 from tempest.lib.services.identity.v2 import tenants_client
 from tempest.lib.services.identity.v3 import projects_client
+from tempest.lib.services.network import qos_limit_bandwidth_rules_client
+from tempest.lib.services.network import qos_minimum_bandwidth_rules_client
 
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.services.network.json import network_client
@@ -92,6 +94,26 @@
         self.az_client = availability_zone_client.AvailabilityZoneClient(
             self.auth_provider, **params)
 
+        self.qos_limit_bandwidth_rules_client = \
+            qos_limit_bandwidth_rules_client.QosLimitBandwidthRulesClient(
+                self.auth_provider,
+                CONF.network.catalog_type,
+                CONF.network.region or CONF.identity.region,
+                endpoint_type=CONF.network.endpoint_type,
+                build_interval=CONF.network.build_interval,
+                build_timeout=CONF.network.build_timeout,
+                **self.default_params)
+
+        self.qos_minimum_bandwidth_rules_client = \
+            qos_minimum_bandwidth_rules_client.QosMinimumBandwidthRulesClient(
+                self.auth_provider,
+                CONF.network.catalog_type,
+                CONF.network.region or CONF.identity.region,
+                endpoint_type=CONF.network.endpoint_type,
+                build_interval=CONF.network.build_interval,
+                build_timeout=CONF.network.build_timeout,
+                **self.default_params)
+
     def _set_identity_clients(self):
         params = {
             'service': CONF.identity.catalog_type,
diff --git a/neutron_tempest_plugin/api/test_address_groups.py b/neutron_tempest_plugin/api/test_address_groups.py
index 478d3b2..69f22d0 100644
--- a/neutron_tempest_plugin/api/test_address_groups.py
+++ b/neutron_tempest_plugin/api/test_address_groups.py
@@ -28,6 +28,119 @@
 ADDRESS_GROUP_NAME = 'test-address-group'
 
 
+class AddressGroupTest(base.BaseAdminNetworkTest):
+
+    credentials = ['primary', 'admin']
+    required_extensions = ['address-group']
+
+    @decorators.idempotent_id('496fef1b-22ce-483b-ab93-d28bf46954b0')
+    def test_address_group_lifecycle(self):
+        ag_description = "Test AG description"
+        ag_name = data_utils.rand_name(ADDRESS_GROUP_NAME)
+        addresses = ['10.10.10.3/32', '192.168.0.10/24', '2001:db8::f00/64']
+        expected_addresses = [
+            '10.10.10.3/32', '192.168.0.0/24', '2001:db8::/64']
+        ag = self.create_address_group(
+            description=ag_description,
+            name=ag_name,
+            addresses=addresses)
+        self.assertEqual(ag_description, ag['description'])
+        self.assertEqual(ag_name, ag['name'])
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(ag['addresses']))
+
+        new_description = 'New AG description'
+        updated_ag = self.client.update_address_group(
+            ag['id'], description=new_description)['address_group']
+        self.assertEqual(new_description, updated_ag['description'])
+        self.assertEqual(ag_name, updated_ag['name'])
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(updated_ag['addresses']))
+
+        self.client.delete_address_group(ag['id'])
+        with testtools.ExpectedException(exceptions.NotFound):
+            self.client.show_address_group(ag['id'])
+
+    @decorators.idempotent_id('8a42029a-40eb-4b44-a7cf-38500046f9b8')
+    def test_address_group_create_with_wrong_address(self):
+        with testtools.ExpectedException(exceptions.BadRequest):
+            self.create_address_group(
+                name=data_utils.rand_name(ADDRESS_GROUP_NAME),
+                addresses=['10.20.30.40'])
+
+        with testtools.ExpectedException(exceptions.BadRequest):
+            self.create_address_group(
+                name=data_utils.rand_name(ADDRESS_GROUP_NAME),
+                addresses=['this is bad IP address'])
+
+    @decorators.idempotent_id('27c03921-bb12-4b9a-b32e-7083bc90ff1f')
+    def test_edit_addresses_in_address_group(self):
+        addresses = ['10.10.10.3/32', '192.168.0.10/24', '2001:db8::f00/64']
+        expected_addresses = [
+            '10.10.10.3/32', '192.168.0.0/24', '2001:db8::/64']
+        ag = self.create_address_group(
+            name=data_utils.rand_name(ADDRESS_GROUP_NAME),
+            addresses=addresses)
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(ag['addresses']))
+
+        added_addresses = ['10.20.30.40/32']
+        self.client.add_addresses_to_address_group(
+            ag['id'], addresses=added_addresses)
+        updated_ag = self.client.show_address_group(ag['id'])['address_group']
+        expected_addresses += added_addresses
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(updated_ag['addresses']))
+
+        removed_addresses = [expected_addresses.pop(0)]
+        self.client.remove_addresses_from_address_group(
+            ag['id'], addresses=removed_addresses)
+        updated_ag = self.client.show_address_group(ag['id'])['address_group']
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(updated_ag['addresses']))
+
+    @decorators.idempotent_id('feec6747-b4b8-49e3-8cff-817d3f097f2c')
+    def test_add_wrong_address_to_address_group(self):
+        addresses = ['10.10.10.3/32', '192.168.0.10/24', '2001:db8::f00/64']
+        expected_addresses = [
+            '10.10.10.3/32', '192.168.0.0/24', '2001:db8::/64']
+        ag = self.create_address_group(
+            name=data_utils.rand_name(ADDRESS_GROUP_NAME),
+            addresses=addresses)
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(ag['addresses']))
+        with testtools.ExpectedException(exceptions.BadRequest):
+            self.client.add_addresses_to_address_group(
+                ag['id'], addresses=['this is bad IP address'])
+        updated_ag = self.client.show_address_group(ag['id'])['address_group']
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(updated_ag['addresses']))
+
+    @decorators.idempotent_id('74f6fd4c-257b-4725-887b-470e96960e24')
+    def test_remove_wrong_address_from_address_group(self):
+        addresses = ['10.10.10.3/32', '192.168.0.10/24', '2001:db8::f00/64']
+        expected_addresses = [
+            '10.10.10.3/32', '192.168.0.0/24', '2001:db8::/64']
+        ag = self.create_address_group(
+            name=data_utils.rand_name(ADDRESS_GROUP_NAME),
+            addresses=addresses)
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(ag['addresses']))
+        with testtools.ExpectedException(exceptions.NotFound):
+            self.client.remove_addresses_from_address_group(
+                ag['id'], addresses=['10.200.200.200'])
+        updated_ag = self.client.show_address_group(ag['id'])['address_group']
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(updated_ag['addresses']))
+
+        with testtools.ExpectedException(exceptions.BadRequest):
+            self.client.remove_addresses_from_address_group(
+                ag['id'], addresses=['this is bad IP address'])
+        updated_ag = self.client.show_address_group(ag['id'])['address_group']
+        self.assertListEqual(
+            sorted(expected_addresses), sorted(updated_ag['addresses']))
+
+
 class RbacSharedAddressGroupTest(base.BaseAdminNetworkTest):
 
     force_tenant_isolation = True
diff --git a/neutron_tempest_plugin/api/test_extra_dhcp_options.py b/neutron_tempest_plugin/api/test_extra_dhcp_options.py
index 844666a..91c270d 100644
--- a/neutron_tempest_plugin/api/test_extra_dhcp_options.py
+++ b/neutron_tempest_plugin/api/test_extra_dhcp_options.py
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from neutron_lib import constants
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
@@ -48,9 +49,14 @@
         cls.ip_server = ('123.123.123.45' if cls._ip_version == 4
                          else '2015::badd')
         cls.extra_dhcp_opts = [
-            {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'},
-            {'opt_value': cls.ip_tftp, 'opt_name': 'tftp-server'},
-            {'opt_value': cls.ip_server, 'opt_name': 'server-ip-address'}
+            {'opt_value': 'pxelinux.0',
+             'opt_name': 'bootfile-name'},  # default ip_version is 4
+            {'opt_value': cls.ip_tftp,
+             'opt_name': 'tftp-server',
+             'ip_version': cls._ip_version},
+            {'opt_value': cls.ip_server,
+             'opt_name': 'server-ip-address',
+             'ip_version': cls._ip_version}
         ]
 
     @decorators.idempotent_id('d2c17063-3767-4a24-be4f-a23dbfa133c9')
@@ -85,8 +91,11 @@
         self.assertEqual(len(retrieved), len(extra_dhcp_opts))
         for retrieved_option in retrieved:
             for option in extra_dhcp_opts:
+                # default ip_version is 4
+                ip_version = option.get('ip_version', constants.IP_VERSION_4)
                 if (retrieved_option['opt_value'] == option['opt_value'] and
-                    retrieved_option['opt_name'] == option['opt_name']):
+                    retrieved_option['opt_name'] == option['opt_name'] and
+                    retrieved_option['ip_version'] == ip_version):
                     break
             else:
                 self.fail('Extra DHCP option not found in port %s' %
diff --git a/neutron_tempest_plugin/api/test_qos.py b/neutron_tempest_plugin/api/test_qos.py
index 5fb0511..5284688 100644
--- a/neutron_tempest_plugin/api/test_qos.py
+++ b/neutron_tempest_plugin/api/test_qos.py
@@ -17,6 +17,7 @@
 from neutron_lib.services.qos import constants as qos_consts
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
 
@@ -34,12 +35,28 @@
 
     required_extensions = [qos_apidef.ALIAS]
 
+    @classmethod
+    def setup_clients(cls):
+        super(QosTestJSON, cls).setup_clients()
+        cls.qos_bw_limit_rule_client = \
+            cls.os_admin.qos_limit_bandwidth_rules_client
+
     @staticmethod
     def _get_driver_details(rule_type_details, driver_name):
         for driver in rule_type_details['drivers']:
             if driver['name'] == driver_name:
                 return driver
 
+    def _create_qos_bw_limit_rule(self, policy_id, rule_data):
+        rule = self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
+            qos_policy_id=policy_id,
+            **rule_data)['bandwidth_limit_rule']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.qos_bw_limit_rule_client.delete_limit_bandwidth_rule,
+            policy_id, rule['id'])
+        return rule
+
     @decorators.idempotent_id('108fbdf7-3463-4e47-9871-d07f3dcf5bbb')
     def test_create_policy(self):
         policy = self.create_qos_policy(name='test-policy',
@@ -361,11 +378,9 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        self.admin_client.create_bandwidth_limit_rule(
-            policy['id'], 200, 1337)['bandwidth_limit_rule']
-
+        self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 200, 'max_burst_kbps': 1337})
         self.admin_client.delete_qos_policy(policy['id'])
-
         with testtools.ExpectedException(exceptions.NotFound):
             self.admin_client.show_qos_policy(policy['id'])
 
@@ -429,14 +444,33 @@
 
 class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
 
+    credentials = ['primary', 'admin']
     direction = None
     required_extensions = [qos_apidef.ALIAS]
 
     @classmethod
+    def setup_clients(cls):
+        super(QosBandwidthLimitRuleTestJSON, cls).setup_clients()
+        cls.qos_bw_limit_rule_client = \
+            cls.os_admin.qos_limit_bandwidth_rules_client
+        cls.qos_bw_limit_rule_client_primary = \
+            cls.os_primary.qos_limit_bandwidth_rules_client
+
+    @classmethod
     @base.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
     def resource_setup(cls):
         super(QosBandwidthLimitRuleTestJSON, cls).resource_setup()
 
+    def _create_qos_bw_limit_rule(self, policy_id, rule_data):
+        rule = self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
+            qos_policy_id=policy_id,
+            **rule_data)['bandwidth_limit_rule']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.qos_bw_limit_rule_client.delete_limit_bandwidth_rule,
+            policy_id, rule['id'])
+        return rule
+
     @property
     def opposite_direction(self):
         if self.direction == "ingress":
@@ -451,24 +485,24 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.create_qos_bandwidth_limit_rule(
-            policy_id=policy['id'],
-            max_kbps=200,
-            max_burst_kbps=1337,
-            direction=self.direction)
+        rule = self._create_qos_bw_limit_rule(
+            policy['id'],
+            {'max_kbps': 200, 'max_burst_kbps': 1337, 'direction': 'ingress'})
 
         # Test 'show rule'
-        retrieved_rule = self.admin_client.show_bandwidth_limit_rule(
-            policy['id'], rule['id'])
+        retrieved_rule = \
+            self.qos_bw_limit_rule_client.show_limit_bandwidth_rule(
+                policy['id'], rule['id'])
+
         retrieved_rule = retrieved_rule['bandwidth_limit_rule']
         self.assertEqual(rule['id'], retrieved_rule['id'])
         self.assertEqual(200, retrieved_rule['max_kbps'])
         self.assertEqual(1337, retrieved_rule['max_burst_kbps'])
-        if self.direction:
-            self.assertEqual(self.direction, retrieved_rule['direction'])
+        self.assertEqual('ingress', retrieved_rule['direction'])
 
         # Test 'list rules'
-        rules = self.admin_client.list_bandwidth_limit_rules(policy['id'])
+        rules = self.qos_bw_limit_rule_client.list_limit_bandwidth_rules(
+            policy['id'])
         rules = rules['bandwidth_limit_rules']
         rules_ids = [r['id'] for r in rules]
         self.assertIn(rule['id'], rules_ids)
@@ -486,36 +520,34 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
-                                             max_kbps=200,
-                                             max_burst_kbps=1337,
-                                             direction=self.direction)
+        self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 200, 'max_burst_kbps': 1337})
 
-        self.assertRaises(exceptions.Conflict,
-                          self.create_qos_bandwidth_limit_rule,
-                          policy_id=policy['id'],
-                          max_kbps=201, max_burst_kbps=1338,
-                          direction=self.direction)
+        self.assertRaises(
+            exceptions.Conflict,
+            self._create_qos_bw_limit_rule,
+            policy['id'],
+            {'max_kbps': 201, 'max_burst_kbps': 1338})
 
     @decorators.idempotent_id('149a6988-2568-47d2-931e-2dbc858943b3')
     def test_rule_update(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
-                                                    max_kbps=1,
-                                                    max_burst_kbps=1,
-                                                    direction=self.direction)
+        rule = self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 1, 'max_burst_kbps': 1})
 
-        self.admin_client.update_bandwidth_limit_rule(
-            policy['id'],
-            rule['id'],
-            max_kbps=200,
-            max_burst_kbps=1337,
-            direction=self.opposite_direction)
-
-        retrieved_policy = self.admin_client.show_bandwidth_limit_rule(
-            policy['id'], rule['id'])
+        if self.opposite_direction:
+            self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
+                policy['id'], rule['id'],
+                **{'max_kbps': 200, 'max_burst_kbps': 1337,
+                   'direction': self.opposite_direction})
+        else:
+            self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
+                policy['id'], rule['id'],
+                **{'max_kbps': 200, 'max_burst_kbps': 1337})
+        retrieved_policy = self.qos_bw_limit_rule_client.\
+            show_limit_bandwidth_rule(policy['id'], rule['id'])
         retrieved_policy = retrieved_policy['bandwidth_limit_rule']
         self.assertEqual(200, retrieved_policy['max_kbps'])
         self.assertEqual(1337, retrieved_policy['max_burst_kbps'])
@@ -528,32 +560,33 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.admin_client.create_bandwidth_limit_rule(
-            policy['id'], 200, 1337, self.direction)['bandwidth_limit_rule']
-
-        retrieved_policy = self.admin_client.show_bandwidth_limit_rule(
-            policy['id'], rule['id'])
+        rule = self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 200, 'max_burst_kbps': 1337})
+        retrieved_policy = \
+            self.qos_bw_limit_rule_client.show_limit_bandwidth_rule(
+                policy['id'], rule['id'])
         retrieved_policy = retrieved_policy['bandwidth_limit_rule']
         self.assertEqual(rule['id'], retrieved_policy['id'])
-
-        self.admin_client.delete_bandwidth_limit_rule(policy['id'], rule['id'])
-        self.assertRaises(exceptions.NotFound,
-                          self.admin_client.show_bandwidth_limit_rule,
-                          policy['id'], rule['id'])
+        self.qos_bw_limit_rule_client.delete_limit_bandwidth_rule(
+            policy['id'], rule['id'])
+        self.assertRaises(
+            exceptions.NotFound,
+            self.qos_bw_limit_rule_client.show_limit_bandwidth_rule,
+            policy['id'], rule['id'])
 
     @decorators.idempotent_id('f211222c-5808-46cb-a961-983bbab6b852')
     def test_rule_create_rule_nonexistent_policy(self):
         self.assertRaises(
             exceptions.NotFound,
-            self.create_qos_bandwidth_limit_rule,
-            'policy', 200, 1337, self.direction)
+            self._create_qos_bw_limit_rule,
+            'policy', {'max_kbps': 200, 'max_burst_kbps': 1337})
 
     @decorators.idempotent_id('a4a2e7ad-786f-4927-a85a-e545a93bd274')
     def test_rule_create_forbidden_for_regular_tenants(self):
         self.assertRaises(
             exceptions.Forbidden,
-            self.client.create_bandwidth_limit_rule,
-            'policy', 1, 2, self.direction)
+            self.qos_bw_limit_rule_client_primary.create_limit_bandwidth_rule,
+            'policy', **{'max_kbps': 1, 'max_burst_kbps': 2})
 
     @decorators.idempotent_id('1bfc55d9-6fd8-4293-ab3a-b1d69bf7cd2e')
     def test_rule_update_forbidden_for_regular_tenants_own_policy(self):
@@ -561,50 +594,47 @@
                                         description='test policy',
                                         shared=False,
                                         project_id=self.client.tenant_id)
-        rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
-                                                    max_kbps=1,
-                                                    max_burst_kbps=1,
-                                                    direction=self.direction)
+        rule = self._create_qos_bw_limit_rule(
+            policy['id'],
+            {'max_kbps': 1, 'max_burst_kbps': 1})
         self.assertRaises(
             exceptions.Forbidden,
-            self.client.update_bandwidth_limit_rule,
-            policy['id'], rule['id'], max_kbps=2, max_burst_kbps=4)
+            self.qos_bw_limit_rule_client_primary.update_limit_bandwidth_rule,
+            policy['id'], rule['id'], **{'max_kbps': 2, 'max_burst_kbps': 4})
 
     @decorators.idempotent_id('9a607936-4b6f-4c2f-ad21-bd5b3d4fc91f')
     def test_rule_update_forbidden_for_regular_tenants_foreign_policy(self):
-        policy = self.create_qos_policy(name='test-policy',
-                                        description='test policy',
-                                        shared=False,
-                                        project_id=self.admin_client.tenant_id)
-        rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
-                                                    max_kbps=1,
-                                                    max_burst_kbps=1,
-                                                    direction=self.direction)
+        policy = self.create_qos_policy(
+            name='test-policy',
+            description='test policy',
+            shared=False,
+            project_id=self.admin_client.tenant_id)
+        rule = self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 1, 'max_burst_kbps': 1})
         self.assertRaises(
             exceptions.NotFound,
-            self.client.update_bandwidth_limit_rule,
-            policy['id'], rule['id'], max_kbps=2, max_burst_kbps=4)
+            self.qos_bw_limit_rule_client_primary.update_limit_bandwidth_rule,
+            policy['id'], rule['id'], **{'max_kbps': 2, 'max_burst_kbps': 4})
 
     @decorators.idempotent_id('ce0bd0c2-54d9-4e29-85f1-cfb36ac3ebe2')
     def test_get_rules_by_policy(self):
-        policy1 = self.create_qos_policy(name='test-policy1',
-                                         description='test policy1',
-                                         shared=False)
-        rule1 = self.create_qos_bandwidth_limit_rule(policy_id=policy1['id'],
-                                                     max_kbps=200,
-                                                     max_burst_kbps=1337,
-                                                     direction=self.direction)
+        policy1 = self.create_qos_policy(
+            name='test-policy1',
+            description='test policy1',
+            shared=False)
+        rule1 = self._create_qos_bw_limit_rule(
+            policy1['id'], {'max_kbps': 200, 'max_burst_kbps': 1337})
 
-        policy2 = self.create_qos_policy(name='test-policy2',
-                                         description='test policy2',
-                                         shared=False)
-        rule2 = self.create_qos_bandwidth_limit_rule(policy_id=policy2['id'],
-                                                     max_kbps=5000,
-                                                     max_burst_kbps=2523,
-                                                     direction=self.direction)
+        policy2 = self.create_qos_policy(
+            name='test-policy2',
+            description='test policy2',
+            shared=False)
+        rule2 = self._create_qos_bw_limit_rule(
+            policy2['id'], {'max_kbps': 5000, 'max_burst_kbps': 2523})
 
         # Test 'list rules'
-        rules = self.admin_client.list_bandwidth_limit_rules(policy1['id'])
+        rules = self.qos_bw_limit_rule_client.list_limit_bandwidth_rules(
+            policy1['id'])
         rules = rules['bandwidth_limit_rules']
         rules_ids = [r['id'] for r in rules]
         self.assertIn(rule1['id'], rules_ids)
@@ -622,9 +652,8 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy for attach',
                                         shared=False)
-
-        self.admin_client.create_bandwidth_limit_rule(
-            policy['id'], 1024, 1024)
+        self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 1024, 'max_burst_kbps': 1024})
 
         self.admin_client.update_network(
             self.network['id'], qos_policy_id=policy['id'])
@@ -678,37 +707,34 @@
                                         description='test policy1',
                                         shared=False)
 
-        rule1 = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
-                                                     max_kbps=1024,
-                                                     max_burst_kbps=1024,
-                                                     direction=n_constants.
-                                                     EGRESS_DIRECTION)
-        rule2 = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
-                                                     max_kbps=1024,
-                                                     max_burst_kbps=1024,
-                                                     direction=n_constants.
-                                                     INGRESS_DIRECTION)
+        rule1 = self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 1024, 'max_burst_kbps': 1024,
+                           'direction': n_constants.EGRESS_DIRECTION})
+        rule2 = self._create_qos_bw_limit_rule(
+            policy['id'], {'max_kbps': 1024, 'max_burst_kbps': 1024,
+                           'direction': n_constants.INGRESS_DIRECTION})
         # Check that the rules were added to the policy
-        rules = self.admin_client.list_bandwidth_limit_rules(
+        rules = self.qos_bw_limit_rule_client.list_limit_bandwidth_rules(
             policy['id'])['bandwidth_limit_rules']
+
         rules_ids = [rule['id'] for rule in rules]
         self.assertIn(rule1['id'], rules_ids)
         self.assertIn(rule2['id'], rules_ids)
 
         # Check that the rules creation fails for the same rule types
-        self.assertRaises(exceptions.Conflict,
-                          self.create_qos_bandwidth_limit_rule,
-                          policy_id=policy['id'],
-                          max_kbps=1025,
-                          max_burst_kbps=1025,
-                          direction=n_constants.EGRESS_DIRECTION)
+        self.assertRaises(
+            exceptions.Conflict,
+            self._create_qos_bw_limit_rule,
+            policy['id'],
+            {'max_kbps': 1025, 'max_burst_kbps': 1025,
+             'direction': n_constants.EGRESS_DIRECTION})
 
-        self.assertRaises(exceptions.Conflict,
-                          self.create_qos_bandwidth_limit_rule,
-                          policy_id=policy['id'],
-                          max_kbps=1025,
-                          max_burst_kbps=1025,
-                          direction=n_constants.INGRESS_DIRECTION)
+        self.assertRaises(
+            exceptions.Conflict,
+            self._create_qos_bw_limit_rule,
+            policy['id'],
+            {'max_kbps': 1025, 'max_burst_kbps': 1025,
+             'direction': n_constants.INGRESS_DIRECTION})
 
 
 class RbacSharedQosPoliciesTest(base.BaseAdminNetworkTest):
@@ -1175,26 +1201,36 @@
     def resource_setup(cls):
         super(QosMinimumBandwidthRuleTestJSON, cls).resource_setup()
 
+    @classmethod
+    def setup_clients(cls):
+        super(QosMinimumBandwidthRuleTestJSON, cls).setup_clients()
+        cls.qos_min_bw_rules_client = \
+            cls.os_admin.qos_minimum_bandwidth_rules_client
+        cls.qos_min_bw_rules_client_primary = \
+            cls.os_primary.qos_minimum_bandwidth_rules_client
+
     @decorators.idempotent_id('aa59b00b-3e9c-4787-92f8-93a5cdf5e378')
     def test_rule_create(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.admin_client.create_minimum_bandwidth_rule(
-            policy_id=policy['id'],
-            direction=self.DIRECTION_EGRESS,
-            min_kbps=1138)[self.RULE_NAME]
+        rule = self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=policy['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 1138})[self.RULE_NAME]
 
         # Test 'show rule'
-        retrieved_rule = self.admin_client.show_minimum_bandwidth_rule(
-            policy['id'], rule['id'])
+        retrieved_rule = \
+            self.qos_min_bw_rules_client.show_minimum_bandwidth_rule(
+                policy['id'], rule['id'])
         retrieved_rule = retrieved_rule[self.RULE_NAME]
         self.assertEqual(rule['id'], retrieved_rule['id'])
         self.assertEqual(1138, retrieved_rule['min_kbps'])
         self.assertEqual(self.DIRECTION_EGRESS, retrieved_rule['direction'])
 
         # Test 'list rules'
-        rules = self.admin_client.list_minimum_bandwidth_rules(policy['id'])
+        rules = self.qos_min_bw_rules_client.list_minimum_bandwidth_rules(
+            policy['id'])
         rules = rules[self.RULES_NAME]
         rules_ids = [r['id'] for r in rules]
         self.assertIn(rule['id'], rules_ids)
@@ -1212,24 +1248,28 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        self.assertRaises(exceptions.BadRequest,
-                          self.admin_client.create_minimum_bandwidth_rule,
-                          policy_id=policy['id'],
-                          direction=self.DIRECTION_EGRESS)
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.qos_min_bw_rules_client.create_minimum_bandwidth_rule,
+            qos_policy_id=policy['id'],
+            **{'direction': self.DIRECTION_EGRESS})
 
     @decorators.idempotent_id('aa59b00b-ab01-4787-92f8-93a5cdf5e378')
     def test_rule_create_fail_for_the_same_type(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        self.admin_client.create_minimum_bandwidth_rule(
-            policy_id=policy['id'],
-            direction=self.DIRECTION_EGRESS, min_kbps=200)
+        self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=policy['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 200})
 
-        self.assertRaises(exceptions.Conflict,
-                          self.admin_client.create_minimum_bandwidth_rule,
-                          policy_id=policy['id'],
-                          direction=self.DIRECTION_EGRESS, min_kbps=201)
+        self.assertRaises(
+            exceptions.Conflict,
+            self.qos_min_bw_rules_client.create_minimum_bandwidth_rule,
+            qos_policy_id=policy['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 201})
 
     @decorators.idempotent_id('35baf998-ae65-495c-9902-35a0d11e8936')
     @utils.requires_ext(extension="qos-bw-minimum-ingress",
@@ -1238,10 +1278,10 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        self.admin_client.create_minimum_bandwidth_rule(
-            policy_id=policy['id'],
-            direction=self.DIRECTION_INGRESS,
-            min_kbps=201)
+        self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=policy['id'],
+            **{'direction': self.DIRECTION_INGRESS,
+               'min_kbps': 201})
 
         retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
         policy_rules = retrieved_policy['policy']['rules']
@@ -1255,16 +1295,18 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.admin_client.create_minimum_bandwidth_rule(
-            policy_id=policy['id'],
-            direction=self.DIRECTION_EGRESS,
-            min_kbps=300)[self.RULE_NAME]
+        rule = self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=policy['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 300})[self.RULE_NAME]
 
-        self.admin_client.update_minimum_bandwidth_rule(policy['id'],
-            rule['id'], min_kbps=350, direction=self.DIRECTION_EGRESS)
+        self.qos_min_bw_rules_client.update_minimum_bandwidth_rule(
+            policy['id'], rule['id'],
+            **{'min_kbps': 350, 'direction': self.DIRECTION_EGRESS})
 
-        retrieved_policy = self.admin_client.show_minimum_bandwidth_rule(
-            policy['id'], rule['id'])
+        retrieved_policy = \
+            self.qos_min_bw_rules_client.show_minimum_bandwidth_rule(
+                policy['id'], rule['id'])
         retrieved_policy = retrieved_policy[self.RULE_NAME]
         self.assertEqual(350, retrieved_policy['min_kbps'])
         self.assertEqual(self.DIRECTION_EGRESS, retrieved_policy['direction'])
@@ -1274,54 +1316,60 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.admin_client.create_minimum_bandwidth_rule(
-            policy['id'], self.DIRECTION_EGRESS, min_kbps=200)[self.RULE_NAME]
+        rule = self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            policy['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 200})[self.RULE_NAME]
 
-        retrieved_policy = self.admin_client.show_minimum_bandwidth_rule(
-            policy['id'], rule['id'])
+        retrieved_policy = \
+            self.qos_min_bw_rules_client.show_minimum_bandwidth_rule(
+                policy['id'], rule['id'])
         retrieved_policy = retrieved_policy[self.RULE_NAME]
         self.assertEqual(rule['id'], retrieved_policy['id'])
 
-        self.admin_client.delete_minimum_bandwidth_rule(policy['id'],
-                                                        rule['id'])
-        self.assertRaises(exceptions.NotFound,
-                          self.admin_client.show_minimum_bandwidth_rule,
-                          policy['id'], rule['id'])
+        self.qos_min_bw_rules_client.delete_minimum_bandwidth_rule(
+            policy['id'], rule['id'])
+        self.assertRaises(
+            exceptions.NotFound,
+            self.qos_min_bw_rules_client.show_minimum_bandwidth_rule,
+            policy['id'], rule['id'])
 
     @decorators.idempotent_id('a211222c-5808-46cb-a961-983bbab6b852')
     def test_rule_create_rule_nonexistent_policy(self):
         self.assertRaises(
             exceptions.NotFound,
-            self.admin_client.create_minimum_bandwidth_rule,
-            'policy', self.DIRECTION_EGRESS, min_kbps=200)
+            self.qos_min_bw_rules_client.create_minimum_bandwidth_rule,
+            'policy',
+            **{'direction': self.DIRECTION_EGRESS, 'min_kbps': 200})
 
     @decorators.idempotent_id('b4a2e7ad-786f-4927-a85a-e545a93bd274')
     def test_rule_create_forbidden_for_regular_tenants(self):
         self.assertRaises(
             exceptions.Forbidden,
-            self.client.create_minimum_bandwidth_rule,
-            'policy', self.DIRECTION_EGRESS, min_kbps=300)
+            self.qos_min_bw_rules_client_primary.create_minimum_bandwidth_rule,
+            'policy', **{'direction': self.DIRECTION_EGRESS, 'min_kbps': 300})
 
     @decorators.idempotent_id('de0bd0c2-54d9-4e29-85f1-cfb36ac3ebe2')
     def test_get_rules_by_policy(self):
         policy1 = self.create_qos_policy(name='test-policy1',
                                          description='test policy1',
                                          shared=False)
-        rule1 = self.admin_client.create_minimum_bandwidth_rule(
-            policy_id=policy1['id'],
-            direction=self.DIRECTION_EGRESS,
-            min_kbps=200)[self.RULE_NAME]
+        rule1 = self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=policy1['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 200})[self.RULE_NAME]
 
         policy2 = self.create_qos_policy(name='test-policy2',
                                          description='test policy2',
                                          shared=False)
-        rule2 = self.admin_client.create_minimum_bandwidth_rule(
-            policy_id=policy2['id'],
-            direction=self.DIRECTION_EGRESS,
-            min_kbps=5000)[self.RULE_NAME]
+        rule2 = self.qos_min_bw_rules_client.create_minimum_bandwidth_rule(
+            qos_policy_id=policy2['id'],
+            **{'direction': self.DIRECTION_EGRESS,
+               'min_kbps': 5000})[self.RULE_NAME]
 
         # Test 'list rules'
-        rules = self.admin_client.list_minimum_bandwidth_rules(policy1['id'])
+        rules = self.qos_min_bw_rules_client.list_minimum_bandwidth_rules(
+            policy1['id'])
         rules = rules[self.RULES_NAME]
         rules_ids = [r['id'] for r in rules]
         self.assertIn(rule1['id'], rules_ids)
diff --git a/neutron_tempest_plugin/api/test_qos_negative.py b/neutron_tempest_plugin/api/test_qos_negative.py
index 2d06d11..3e80129 100644
--- a/neutron_tempest_plugin/api/test_qos_negative.py
+++ b/neutron_tempest_plugin/api/test_qos_negative.py
@@ -100,11 +100,17 @@
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
                                         shared=False)
-        rule = self.rule_create_m(policy_id=policy['id'], **create_params)
+        rule = self.rule_create_m(policy['id'], **create_params)
+        if "minimum_bandwidth_rule" in rule.keys():
+            rule_id = rule['minimum_bandwidth_rule']['id']
+        if "bandwidth_limit_rule" in rule.keys():
+            rule_id = rule['bandwidth_limit_rule']['id']
+        if "dscp_mark" in rule.keys():
+            rule_id = rule['id']
         self.assertRaises(
             lib_exc.NotFound,
             self.rule_update_m,
-            non_exist_id, rule['id'], **update_params)
+            non_exist_id, rule_id, **update_params)
 
     def _test_rule_create_rule_non_existent_policy(self, create_params):
         non_exist_id = data_utils.rand_name('qos_policy')
@@ -127,9 +133,17 @@
 class QosBandwidthLimitRuleNegativeTestJSON(QosRuleNegativeBaseTestJSON):
 
     @classmethod
+    def setup_clients(cls):
+        super(QosBandwidthLimitRuleNegativeTestJSON, cls).setup_clients()
+        cls.qos_bw_limit_rule_client = \
+            cls.os_admin.qos_limit_bandwidth_rules_client
+
+    @classmethod
     def resource_setup(cls):
-        cls.rule_create_m = cls.create_qos_bandwidth_limit_rule
-        cls.rule_update_m = cls.admin_client.update_bandwidth_limit_rule
+        cls.rule_create_m = \
+            cls.qos_bw_limit_rule_client.create_limit_bandwidth_rule
+        cls.rule_update_m = \
+            cls.qos_bw_limit_rule_client.update_limit_bandwidth_rule
         super(QosBandwidthLimitRuleNegativeTestJSON, cls).resource_setup()
 
     @decorators.attr(type='negative')
@@ -157,8 +171,10 @@
 
     @classmethod
     def resource_setup(cls):
-        cls.rule_create_m = cls.create_qos_minimum_bandwidth_rule
-        cls.rule_update_m = cls.admin_client.update_minimum_bandwidth_rule
+        cls.rule_create_m = cls.os_admin.qos_minimum_bandwidth_rules_client.\
+            create_minimum_bandwidth_rule
+        cls.rule_update_m = cls.os_admin.qos_minimum_bandwidth_rules_client.\
+            update_minimum_bandwidth_rule
         super(QosMinimumBandwidthRuleNegativeTestJSON, cls).resource_setup()
 
     @decorators.attr(type='negative')
diff --git a/neutron_tempest_plugin/api/test_trunk.py b/neutron_tempest_plugin/api/test_trunk.py
index 1f83bd8..26f8de8 100644
--- a/neutron_tempest_plugin/api/test_trunk.py
+++ b/neutron_tempest_plugin/api/test_trunk.py
@@ -247,21 +247,23 @@
     @classmethod
     def skip_checks(cls):
         super(TrunkTestMtusJSONBase, cls).skip_checks()
-        if not all(cls.is_type_driver_enabled(t) for t in ['gre', 'vxlan']):
-            msg = "Either vxlan or gre type driver not enabled."
+        if not all(cls.is_type_driver_enabled(t) for t in ['vlan', 'vxlan']):
+            msg = "Either vxlan or vlan type driver not enabled."
             raise cls.skipException(msg)
 
     def setUp(self):
         super(TrunkTestMtusJSONBase, self).setUp()
+        physnet_name = CONF.neutron_plugin_options.provider_vlans[0]
 
-        # VXLAN autocomputed MTU (1450) is smaller than that of GRE (1458)
+        # VXLAN autocomputed MTU (1450) is smaller than that of VLAN (1480)
         self.smaller_mtu_net = self.create_network(
             name=data_utils.rand_name('vxlan-net'),
             provider_network_type='vxlan')
 
         self.larger_mtu_net = self.create_network(
-            name=data_utils.rand_name('gre-net'),
-            provider_network_type='gre')
+            name=data_utils.rand_name('vlan-net'),
+            provider_network_type='vlan',
+            provider_physical_network=physnet_name)
 
         self.smaller_mtu_port = self.create_port(self.smaller_mtu_net)
         self.smaller_mtu_port_2 = self.create_port(self.smaller_mtu_net)
diff --git a/neutron_tempest_plugin/common/utils.py b/neutron_tempest_plugin/common/utils.py
index 1526ecf..898e4b9 100644
--- a/neutron_tempest_plugin/common/utils.py
+++ b/neutron_tempest_plugin/common/utils.py
@@ -204,6 +204,8 @@
 
     def __exit__(self, type, value, traceback):
         self.server_ssh.exec_command('sudo killall nc || killall nc')
-        self.server_ssh.exec_command('sudo killall tail || killall tail')
+        self.server_ssh.exec_command(
+                'sudo killall tail || killall tail || echo "True"')
         self.client_ssh.exec_command('sudo killall nc || killall nc')
-        self.client_ssh.exec_command('sudo killall tail || killall tail')
+        self.client_ssh.exec_command(
+                'sudo killall tail || killall tail || echo "True"')
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index 9902b68..a5afc73 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -344,6 +344,8 @@
     def setup_clients(cls):
         super(FloatingIPQosTest, cls).setup_clients()
         cls.admin_client = cls.os_admin.network_client
+        cls.qos_bw_limit_rule_client = \
+            cls.os_admin.qos_limit_bandwidth_rules_client
 
     @decorators.idempotent_id('5eb48aea-eaba-4c20-8a6f-7740070a0aa3')
     def test_qos(self):
@@ -364,16 +366,19 @@
         ssh_client = self._create_ssh_client()
 
         # As admin user create a new QoS rules
-        self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BYTES,
-            direction=lib_constants.INGRESS_DIRECTION)
-        self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BYTES,
-            direction=lib_constants.EGRESS_DIRECTION)
+        rule_data = {'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
+                     'max_burst_kbps': constants.LIMIT_KILO_BYTES,
+                     'direction': lib_constants.INGRESS_DIRECTION}
+        self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
+             qos_policy_id=policy_id, **rule_data)
 
-        rules = self.os_admin.network_client.list_bandwidth_limit_rules(
+        rule_data = {'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
+                     'max_burst_kbps': constants.LIMIT_KILO_BYTES,
+                     'direction': lib_constants.EGRESS_DIRECTION}
+        self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
+             qos_policy_id=policy_id, **rule_data)
+
+        rules = self.qos_bw_limit_rule_client.list_limit_bandwidth_rules(
             policy_id)
         self.assertEqual(2, len(rules['bandwidth_limit_rules']))
 
@@ -404,11 +409,10 @@
 
         # As admin user update QoS rules
         for rule in rules['bandwidth_limit_rules']:
-            self.os_admin.network_client.update_bandwidth_limit_rule(
-                policy_id,
-                rule['id'],
-                max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
-                max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
+            self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
+                policy_id, rule['id'],
+                **{'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2,
+                   'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2})
 
         # Check that actual BW while downloading file
         # is as expected (Update BW)
diff --git a/neutron_tempest_plugin/scenario/test_ipv6.py b/neutron_tempest_plugin/scenario/test_ipv6.py
index 4237d4f..d9d1a22 100644
--- a/neutron_tempest_plugin/scenario/test_ipv6.py
+++ b/neutron_tempest_plugin/scenario/test_ipv6.py
@@ -20,6 +20,7 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
+import testtools
 
 from neutron_tempest_plugin.common import ip
 from neutron_tempest_plugin.common import ssh
@@ -43,35 +44,41 @@
     """
     ip_command = ip.IPCommand(ssh)
     nic = ip_command.get_nic_name_by_mac(ipv6_port['mac_address'])
+    ip_command.set_link(nic, "up")
 
-    # NOTE(slaweq): on RHEL based OS ifcfg file for new interface is
-    # needed to make IPv6 working on it, so if
-    # /etc/sysconfig/network-scripts directory exists ifcfg-%(nic)s file
-    # should be added in it
-    if sysconfig_network_scripts_dir_exists(ssh):
+
+def configure_eth_connection_profile_NM(ssh):
+    """Prepare a Network manager profile for ipv6 port
+
+    By default the NetworkManager uses IPv6 privacy
+    format it isn't supported by neutron then we create
+    a ether profile with eui64 supported format
+
+    @param ssh: RemoteClient ssh instance to server
+    """
+    # NOTE(ccamposr): on RHEL based OS we need a ether profile with
+    # eui64 format
+    if nmcli_command_exists(ssh):
         try:
-            ssh.execute_script(
-                'echo -e "DEVICE=%(nic)s\\nNAME=%(nic)s\\nIPV6INIT=yes" | '
-                'tee /etc/sysconfig/network-scripts/ifcfg-%(nic)s; '
-                'nmcli connection reload' % {'nic': nic},
-                become_root=True)
-            ssh.execute_script('nmcli connection up %s' % nic,
+            ssh.execute_script('nmcli connection add type ethernet con-name '
+                               'ether ifname "*"', become_root=True)
+            ssh.execute_script('nmcli con mod ether ipv6.addr-gen-mode eui64',
                                become_root=True)
+
         except lib_exc.SSHExecCommandFailed as e:
             # NOTE(slaweq): Sometimes it can happen that this SSH command
             # will fail because of some error from network manager in
             # guest os.
             # But even then doing ip link set up below is fine and
             # IP address should be configured properly.
-            LOG.debug("Error during restarting %(nic)s interface on "
-                      "instance. Error message: %(error)s",
-                      {'nic': nic, 'error': e})
-    ip_command.set_link(nic, "up")
+            LOG.debug("Error creating NetworkManager profile. "
+                      "Error message: %(error)s",
+                      {'error': e})
 
 
-def sysconfig_network_scripts_dir_exists(ssh):
+def nmcli_command_exists(ssh):
     return "False" not in ssh.execute_script(
-        'test -d /etc/sysconfig/network-scripts/ || echo "False"')
+        'if ! type nmcli > /dev/null ; then echo "False"; fi')
 
 
 class IPv6Test(base.BaseTempestTestCase):
@@ -81,6 +88,12 @@
     ipv6_address_mode = 'slaac'
 
     @classmethod
+    def skip_checks(cls):
+        super(IPv6Test, cls).skip_checks()
+        if not CONF.network_feature_enabled.ipv6:
+            raise cls.skipException("IPv6 is not enabled")
+
+    @classmethod
     @tempest_utils.requires_ext(extension="router", service="network")
     def resource_setup(cls):
         super(IPv6Test, cls).resource_setup()
@@ -165,6 +178,8 @@
 
         # And plug VM to the second IPv6 network
         ipv6_port = self.create_port(ipv6_networks[1])
+        # Add NetworkManager profile with ipv6 eui64 format to guest OS
+        configure_eth_connection_profile_NM(ssh_client)
         self.create_interface(vm['id'], ipv6_port['id'])
         ip.wait_for_interface_status(
             self.os_primary.interfaces_client, vm['id'],
@@ -172,10 +187,14 @@
             ssh_client=ssh_client, mac_address=ipv6_port['mac_address'])
         self._test_ipv6_address_configured(ssh_client, vm, ipv6_port)
 
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "DHCPv6 attributes are not enabled.")
     @decorators.idempotent_id('b13e5408-5250-4a42-8e46-6996ce613e91')
     def test_ipv6_hotplug_slaac(self):
         self._test_ipv6_hotplug("slaac", "slaac")
 
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "DHCPv6 attributes are not enabled.")
     @decorators.idempotent_id('9aaedbc4-986d-42d5-9177-3e721728e7e0')
     def test_ipv6_hotplug_dhcpv6stateless(self):
         self._test_ipv6_hotplug("dhcpv6-stateless", "dhcpv6-stateless")
diff --git a/neutron_tempest_plugin/scenario/test_mac_learning.py b/neutron_tempest_plugin/scenario/test_mac_learning.py
new file mode 100644
index 0000000..736d46c
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_mac_learning.py
@@ -0,0 +1,210 @@
+# Copyright 2021 Red Hat, Inc
+# All Rights Reserved.
+#
+#    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.
+
+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 import exceptions
+from neutron_tempest_plugin.scenario import base
+
+
+CONF = config.CONF
+LOG = log.getLogger(__name__)
+
+
+# -s0 -l -c5 &> /tmp/tcpdump_out &
+def get_receiver_script(result_file, packets_expected):
+    """Script that listen icmp echos and write the output on result_file."""
+    return """#!/bin/bash
+export LC_ALL=en_US.UTF-8
+tcpdump -i any -n -v 'icmp[icmptype] = icmp-echoreply or icmp[icmptype] = \
+icmp-echo' -s0 -l -c%(packets_expected)d &> %(result_file)s &
+    """ % {'result_file': result_file,
+           'packets_expected': packets_expected}
+
+
+def get_sender_script(result_file, receiver_address, completed_message):
+    """Script that sends packets to the receiver server."""
+    return """#!/bin/bash
+export LC_ALL=en_US.UTF-8
+ping -c 5 %(address)s
+echo '%(completed_message)s' > %(result_file)s &
+    """ % {'result_file': result_file,
+           'address': receiver_address,
+           'completed_message': completed_message}
+
+
+class MacLearningTest(base.BaseTempestTestCase):
+
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    # Import configuration options
+    available_type_drivers = (
+        CONF.neutron_plugin_options.available_type_drivers)
+
+    completed_message = "Done!"
+    output_file = "/tmp/tcpdump_out"
+    sender_output_file = "/tmp/sender_out"
+    sender_script_file = "/tmp/ping.sh"
+    receiver_script_file = "/tmp/traffic.sh"
+
+    @classmethod
+    def skip_checks(cls):
+        super(MacLearningTest, cls).skip_checks()
+        advanced_image_available = (
+            CONF.neutron_plugin_options.advanced_image_ref or
+            CONF.neutron_plugin_options.default_image_is_advanced)
+        if not advanced_image_available:
+            skip_reason = "This test requires advanced tools to be executed"
+            raise cls.skipException(skip_reason)
+
+    @classmethod
+    def resource_setup(cls):
+        super(MacLearningTest, cls).resource_setup()
+
+        if CONF.neutron_plugin_options.default_image_is_advanced:
+            cls.flavor_ref = CONF.compute.flavor_ref
+            cls.image_ref = CONF.compute.image_ref
+            cls.username = CONF.validation.image_ssh_user
+        else:
+            cls.flavor_ref = (
+                CONF.neutron_plugin_options.advanced_image_flavor_ref)
+            cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
+            cls.username = CONF.neutron_plugin_options.advanced_image_ssh_user
+
+        # Setup basic topology for servers so that we can log into them
+        # It's important to keep port security and DHCP disabled for this test
+        cls.network = cls.create_network(port_security_enabled=False)
+        cls.subnet = cls.create_subnet(cls.network, enable_dhcp=False)
+        cls.router = cls.create_router_by_client()
+        cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+
+        cls.keypair = cls.create_keypair()
+
+    def _create_server(self):
+        name = data_utils.rand_name("maclearning-server")
+        server = self.create_server(
+            flavor_ref=self.flavor_ref,
+            image_ref=self.image_ref,
+            key_name=self.keypair['name'], name=name,
+            networks=[{'uuid': self.network['id']}],
+            config_drive='True')['server']
+        self.wait_for_server_active(server)
+        self.wait_for_guest_os_ready(server)
+        server['port'] = self.client.list_ports(
+            network_id=self.network['id'], device_id=server['id'])['ports'][0]
+        server['fip'] = self.create_floatingip(port=server['port'])
+        server['ssh_client'] = ssh.Client(server['fip']['floating_ip_address'],
+                                          self.username,
+                                          pkey=self.keypair['private_key'])
+        return server
+
+    def _check_cmd_installed_on_server(self, ssh_client, server_id, cmd):
+        try:
+            ssh_client.execute_script('which %s' % cmd)
+        except exceptions.SSHScriptFailed:
+            raise self.skipException(
+                "%s is not available on server %s" % (cmd, server_id))
+
+    def _prepare_sender(self, server, address):
+        check_script = get_sender_script(self.sender_output_file, address,
+                                         self.completed_message)
+        self._check_cmd_installed_on_server(server['ssh_client'], server['id'],
+                                            'tcpdump')
+        server['ssh_client'].execute_script(
+            'echo "%s" > %s' % (check_script, self.sender_script_file))
+
+    def _prepare_listener(self, server, n_packets):
+        check_script = get_receiver_script(
+            result_file=self.output_file,
+            packets_expected=n_packets)
+        self._check_cmd_installed_on_server(server['ssh_client'], server['id'],
+                                            'tcpdump')
+        server['ssh_client'].execute_script(
+            'echo "%s" > %s' % (check_script, self.receiver_script_file))
+
+    @decorators.idempotent_id('013686ac-23b1-23e4-8361-10b1c98a2861')
+    def test_mac_learning_vms_on_same_network(self):
+        """Test mac learning works in a network.
+
+        The receiver server will receive all the sent packets.
+        The non receiver should not receive any.
+
+        """
+        sender = self._create_server()
+        receiver = self._create_server()
+        non_receiver = self._create_server()
+
+        def check_server_result(server, expected_result, output_file):
+            result = server['ssh_client'].execute_script(
+                "cat {path} || echo '{path} not exists yet'".format(
+                    path=output_file))
+            LOG.debug("VM result: %s", result)
+            return expected_result in result
+
+        # Prepare the server that is intended to receive the packets
+        self._prepare_listener(receiver, 5)
+
+        # Prepare the server that is not intended receive of the packets.
+        self._prepare_listener(non_receiver, 2)
+
+        # Run the scripts
+        for server in [receiver, non_receiver]:
+            server['ssh_client'].execute_script(
+                "bash %s" % self.receiver_script_file, become_root=True)
+
+        # Prepare the server that will make the ping.
+        target_ip = receiver['port']['fixed_ips'][0]['ip_address']
+        self._prepare_sender(sender, address=target_ip)
+
+        LOG.debug("The receiver IP is: %s", target_ip)
+        # Run the sender node script
+        sender['ssh_client'].execute_script(
+                "bash %s" % self.sender_script_file, become_root=True)
+
+        # Check if the message was sent.
+        utils.wait_until_true(
+            lambda: check_server_result(
+                sender, self.completed_message,
+                self.sender_output_file),
+            exception=RuntimeError(
+                "Sender script wasn't executed properly"))
+
+        # Check receiver server
+        receiver_expected_result = '5 packets captured'
+        utils.wait_until_true(
+            lambda: check_server_result(receiver,
+                receiver_expected_result, self.output_file),
+            exception=RuntimeError(
+                'Receiver server did not receive expected packet'))
+
+        # Check the non_receiver server
+        non_receiver_expected_result = '0 packets captured'
+        try:
+            LOG.debug("Try killing non-receiver tcpdump")
+            non_receiver['ssh_client'].execute_script(
+                "killall tcpdump && sleep 2", become_root=True)
+        except exceptions.SSHScriptFailed:
+            LOG.debug("Killing tcpdump failed")
+            self.assertTrue(check_server_result(non_receiver,
+                            non_receiver_expected_result,
+                            self.output_file),
+                            'Non targeted server received unexpected packets')
+            return
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index d00210c..74be216 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -20,6 +20,7 @@
 from oslo_log import log as logging
 from tempest.common import utils as tutils
 from tempest.common import waiters
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 from neutron_tempest_plugin.api import base as base_api
@@ -138,8 +139,20 @@
                                         description='test-qos-policy',
                                         shared=True)
         self.qos_policies.append(policy['policy'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+            self.os_admin.network_client.delete_qos_policy, policy)
         return policy['policy']['id']
 
+    def _create_qos_bw_limit_rule(self, policy_id, rule_data):
+        rule = self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
+            qos_policy_id=policy_id,
+            **rule_data)['bandwidth_limit_rule']
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.qos_bw_limit_rule_client.delete_limit_bandwidth_rule,
+            policy_id, rule['id'])
+        return rule
+
     def _create_server_by_port(self, port=None):
         """Launch an instance using a port interface;
 
@@ -194,6 +207,8 @@
     def setup_clients(cls):
         super(QoSTest, cls).setup_clients()
         cls.admin_client = cls.os_admin.network_client
+        cls.qos_bw_limit_rule_client = \
+            cls.os_admin.qos_limit_bandwidth_rules_client
 
     @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
     def test_qos_basic_and_update(self):
@@ -227,11 +242,11 @@
         bw_limit_policy_id = self._create_qos_policy()
 
         # As admin user create QoS rule
-        rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id=bw_limit_policy_id,
-            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
-                'bandwidth_limit_rule']['id']
+        rule_data = {
+            'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
+            'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
+        rule_id = self._create_qos_bw_limit_rule(
+            bw_limit_policy_id, rule_data)['id']
 
         # Associate QoS to the network
         self.os_admin.network_client.update_network(
@@ -250,11 +265,12 @@
                 ' the network" Actual BW is not as expected!'))
 
         # As admin user update QoS rule
-        self.os_admin.network_client.update_bandwidth_limit_rule(
-            bw_limit_policy_id,
-            rule_id,
-            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
+        rule_update_data = {
+            'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2,
+            'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2}
+        self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
+            qos_policy_id=bw_limit_policy_id, rule_id=rule_id,
+            **rule_update_data)
 
         # Check that actual BW while downloading file
         # is as expected (Update BW)
@@ -273,11 +289,11 @@
         bw_limit_policy_id_new = self._create_qos_policy()
 
         # As admin user create a new QoS rule
-        rule_id_new = self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id=bw_limit_policy_id_new,
-            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
-                'bandwidth_limit_rule']['id']
+        rule_data_new = {
+            'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
+            'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
+        rule_id_new = self._create_qos_bw_limit_rule(
+            bw_limit_policy_id_new, rule_data_new)['id']
 
         # Associate a new QoS policy to Neutron port
         self.os_admin.network_client.update_port(
@@ -296,11 +312,12 @@
                 ' the VM port" Actual BW is not as expected!'))
 
         # As admin user update QoS rule
-        self.os_admin.network_client.update_bandwidth_limit_rule(
-            bw_limit_policy_id_new,
-            rule_id_new,
-            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
+        rule_update_data = {
+            'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 3,
+            'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 3}
+        self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
+            qos_policy_id=bw_limit_policy_id_new, rule_id=rule_id_new,
+            **rule_update_data)
 
         # Check that actual BW while downloading file
         # is as expected (Update BW)
@@ -334,11 +351,10 @@
             description='policy for attach',
             shared=False)['policy']
 
-        rule = self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id=port_policy['id'],
-            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
-                    'bandwidth_limit_rule']
+        rule_data = {
+            'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
+            'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
+        rule = self._create_qos_bw_limit_rule(port_policy['id'], rule_data)
 
         self.os_admin.network_client.update_port(
             vm_port['id'], qos_policy_id=port_policy['id'])
@@ -378,10 +394,10 @@
             name='network-policy',
             shared=False)['policy']
 
-        rule = self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id=qos_policy['id'],
-            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)
+        rule_data = {
+            'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
+            'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
+        rule = self._create_qos_bw_limit_rule(qos_policy['id'], rule_data)
 
         network = self.os_admin.network_client.update_network(
                   network['id'],
@@ -399,9 +415,9 @@
                            retrieved_net['network']['qos_policy_id'])
         retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
 
-        self.assertEqual(rule['bandwidth_limit_rule']['id'],
+        self.assertEqual(rule['id'],
                          retrieved_rule_id,
                          """The expected rule ID is {0},
                          the actual value is {1}""".
-                         format(rule['bandwidth_limit_rule']['id'],
+                         format(rule['id'],
                                 retrieved_rule_id))
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index 2678d73..a4c809e 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -627,57 +627,6 @@
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def create_bandwidth_limit_rule(self, policy_id, max_kbps,
-                                    max_burst_kbps, direction=None):
-        uri = '%s/qos/policies/%s/bandwidth_limit_rules' % (
-            self.uri_prefix, policy_id)
-        post_data = {
-            'bandwidth_limit_rule': {
-                'max_kbps': max_kbps,
-                'max_burst_kbps': max_burst_kbps
-            }
-        }
-        if direction:
-            post_data['bandwidth_limit_rule']['direction'] = direction
-        resp, body = self.post(uri, self.serialize(post_data))
-        self.expected_success(201, resp.status)
-        body = jsonutils.loads(body)
-        return service_client.ResponseBody(resp, body)
-
-    def list_bandwidth_limit_rules(self, policy_id):
-        uri = '%s/qos/policies/%s/bandwidth_limit_rules' % (
-            self.uri_prefix, policy_id)
-        resp, body = self.get(uri)
-        body = self.deserialize_single(body)
-        self.expected_success(200, resp.status)
-        return service_client.ResponseBody(resp, body)
-
-    def show_bandwidth_limit_rule(self, policy_id, rule_id):
-        uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
-            self.uri_prefix, policy_id, rule_id)
-        resp, body = self.get(uri)
-        body = self.deserialize_single(body)
-        self.expected_success(200, resp.status)
-        return service_client.ResponseBody(resp, body)
-
-    def update_bandwidth_limit_rule(self, policy_id, rule_id, **kwargs):
-        uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
-            self.uri_prefix, policy_id, rule_id)
-        if "direction" in kwargs and kwargs['direction'] is None:
-            kwargs.pop('direction')
-        post_data = {'bandwidth_limit_rule': kwargs}
-        resp, body = self.put(uri, jsonutils.dumps(post_data))
-        body = self.deserialize_single(body)
-        self.expected_success(200, resp.status)
-        return service_client.ResponseBody(resp, body)
-
-    def delete_bandwidth_limit_rule(self, policy_id, rule_id):
-        uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
-            self.uri_prefix, policy_id, rule_id)
-        resp, body = self.delete(uri)
-        self.expected_success(204, resp.status)
-        return service_client.ResponseBody(resp, body)
-
     def create_dscp_marking_rule(self, policy_id, dscp_mark):
         uri = '%s/qos/policies/%s/dscp_marking_rules' % (
             self.uri_prefix, policy_id)
@@ -723,53 +672,6 @@
         self.expected_success(204, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def create_minimum_bandwidth_rule(self, policy_id, direction,
-                                      min_kbps=None):
-        uri = '%s/qos/policies/%s/minimum_bandwidth_rules' % (
-            self.uri_prefix, policy_id)
-        data = {
-            'direction': direction,
-        }
-        if min_kbps is not None:
-            data['min_kbps'] = min_kbps
-        post_data = self.serialize({'minimum_bandwidth_rule': data})
-        resp, body = self.post(uri, post_data)
-        self.expected_success(201, resp.status)
-        body = jsonutils.loads(body)
-        return service_client.ResponseBody(resp, body)
-
-    def list_minimum_bandwidth_rules(self, policy_id):
-        uri = '%s/qos/policies/%s/minimum_bandwidth_rules' % (
-            self.uri_prefix, policy_id)
-        resp, body = self.get(uri)
-        body = self.deserialize_single(body)
-        self.expected_success(200, resp.status)
-        return service_client.ResponseBody(resp, body)
-
-    def show_minimum_bandwidth_rule(self, policy_id, rule_id):
-        uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
-            self.uri_prefix, policy_id, rule_id)
-        resp, body = self.get(uri)
-        body = self.deserialize_single(body)
-        self.expected_success(200, resp.status)
-        return service_client.ResponseBody(resp, body)
-
-    def update_minimum_bandwidth_rule(self, policy_id, rule_id, **kwargs):
-        uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
-            self.uri_prefix, policy_id, rule_id)
-        post_data = {'minimum_bandwidth_rule': kwargs}
-        resp, body = self.put(uri, jsonutils.dumps(post_data))
-        body = self.deserialize_single(body)
-        self.expected_success(200, resp.status)
-        return service_client.ResponseBody(resp, body)
-
-    def delete_minimum_bandwidth_rule(self, policy_id, rule_id):
-        uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
-            self.uri_prefix, policy_id, rule_id)
-        resp, body = self.delete(uri)
-        self.expected_success(204, resp.status)
-        return service_client.ResponseBody(resp, body)
-
     def list_qos_rule_types(self):
         uri = '%s/qos/rule-types' % self.uri_prefix
         resp, body = self.get(uri)
@@ -1126,3 +1028,21 @@
             self.uri_prefix, resource_type, resource_id, tag)
         resp, body = self.delete(uri)
         self.expected_success(204, resp.status)
+
+    def add_addresses_to_address_group(self, address_group_id, addresses):
+        uri = '%s/address-groups/%s/add_addresses' % (
+            self.uri_prefix, address_group_id)
+        request_body = {'addresses': addresses}
+        resp, response_body = self.put(uri, jsonutils.dumps(request_body))
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(
+            resp, jsonutils.loads(response_body))
+
+    def remove_addresses_from_address_group(self, address_group_id, addresses):
+        uri = '%s/address-groups/%s/remove_addresses' % (
+            self.uri_prefix, address_group_id)
+        request_body = {'addresses': addresses}
+        resp, response_body = self.put(uri, jsonutils.dumps(request_body))
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(
+            resp, jsonutils.loads(response_body))
diff --git a/neutron_tempest_plugin/tap_as_a_service/__init__.py b/neutron_tempest_plugin/tap_as_a_service/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/__init__.py
diff --git a/neutron_tempest_plugin/tap_as_a_service/api/__init__.py b/neutron_tempest_plugin/tap_as_a_service/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/api/__init__.py
diff --git a/neutron_tempest_plugin/tap_as_a_service/api/test_taas.py b/neutron_tempest_plugin/tap_as_a_service/api/test_taas.py
new file mode 100644
index 0000000..06dce53
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/api/test_taas.py
@@ -0,0 +1,129 @@
+#    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.
+
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.tap_as_a_service import base
+
+CONF = config.CONF
+
+
+class TaaSExtensionTestJSON(base.BaseTaasTest):
+
+    @classmethod
+    @utils.requires_ext(extension='taas', service='network')
+    def skip_checks(cls):
+        super(TaaSExtensionTestJSON, cls).skip_checks()
+
+    @classmethod
+    def resource_setup(cls):
+        super(TaaSExtensionTestJSON, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.ts_port = cls.create_port(cls.network)
+        cls.tf_port = cls.create_port(cls.network)
+        cls.tf2_port = cls.create_port(cls.network)
+
+    @decorators.idempotent_id('b993c14e-797a-4c91-b4da-8cb1a450aa2f')
+    def test_create_tap_service_and_flow(self):
+        """create tap service adn tap flow
+
+        Test create tap service and flow.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        self.create_tap_flow(tap_service_id=tap_service['id'],
+                             direction='BOTH', source_port=self.tf_port['id'])
+
+    @decorators.idempotent_id('897a0aaf-1b55-4ea8-9d9f-1bc0fd09cb60')
+    @utils.requires_ext(extension='taas-vlan-filter', service='network')
+    def test_create_tap_service_and_flow_vlan_filter(self):
+        """create tap service with vlan_filter
+
+        Test create tap service with additional vlan_filter argument.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(tap_service_id=tap_service['id'],
+                                        direction='BOTH',
+                                        source_port=self.tf_port['id'],
+                                        vlan_filter='189,279,999-1008')
+        self.assertEqual(tap_flow['vlan_filter'], '189,279,999-1008')
+
+    @decorators.idempotent_id('d7a2115d-16b4-41cf-95a6-dcebc3682b24')
+    def test_delete_tap_resources_after_ts_port_delete(self):
+        """delete tap resources after ts port delete
+
+        Test delete tap resources after deletion of ts port.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(tap_service_id=tap_service['id'],
+                                        direction='BOTH',
+                                        source_port=self.tf2_port['id'])
+        # delete ts_port; it shall also delete the associated tap-service and
+        # subsequently the tap-flow as well
+        self.ports_client.delete_port(self.ts_port['id'])
+        # Attempt tap-service deletion; it should throw not found exception.
+        self.assertRaises(lib_exc.NotFound,
+                          self.tap_services_client.delete_tap_service,
+                          tap_service['id'])
+        # Attempt tap-flow deletion; it should throw not found exception.
+        self.assertRaises(lib_exc.NotFound,
+                          self.tap_flows_client.delete_tap_flow,
+                          tap_flow['id'])
+
+    @decorators.idempotent_id('9ba4edfd-4002-4c44-b02b-6c4f71b40a92')
+    def test_delete_tap_resources_after_tf_port_delete(self):
+        """delete tap resources after tf port delete
+
+        Test delete tap service after deletion of tf port.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(tap_service_id=tap_service['id'],
+                                        direction='BOTH',
+                                        source_port=self.tf_port['id'])
+        # delete tf port; it shall also delete the associated tap-flow
+        self.ports_client.delete_port(self.tf_port['id'])
+        # Attempt tap-flow deletion; it should throw not found exception.
+        self.assertRaises(lib_exc.NotFound,
+                          self.tap_flows_client.delete_tap_flow,
+                          tap_flow['id'])
+        # delete tap service; it shall go fine
+        self.tap_services_client.delete_tap_service(tap_service['id'])
+
+    @decorators.idempotent_id('687089b8-b045-496d-86bf-030b380039d1')
+    def test_create_and_update_tap_service(self):
+        """create and update tap service
+
+        Test update tap service - update description.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+
+        # Update description of the tap service
+        self.update_tap_service(
+            tap_service['id'],
+            description='Tap Service Description Updated')
+
+    @decorators.idempotent_id('bb4d5482-37fc-46b5-85a5-5867e9adbfae')
+    def test_create_and_update_tap_flow(self):
+        """create and update tap flow
+
+        Test update tap flow - update description.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(
+            tap_service_id=tap_service['id'],
+            direction='BOTH', source_port=self.tf_port['id'])
+        # Update description of the tap flow
+        self.update_tap_flow(
+            tap_flow['id'],
+            description='Tap Flow Description Updated')
diff --git a/neutron_tempest_plugin/tap_as_a_service/base.py b/neutron_tempest_plugin/tap_as_a_service/base.py
new file mode 100644
index 0000000..3ddc797
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/base.py
@@ -0,0 +1,82 @@
+#    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 tempest.api.network.base as test
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+
+from neutron_tempest_plugin.tap_as_a_service.services import taas_client
+
+CONF = config.CONF
+
+
+class BaseTaasTest(test.BaseAdminNetworkTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(BaseTaasTest, cls).resource_setup()
+        os_primary = cls.os_primary
+        cls.tap_services_client = taas_client.TapServicesClient(
+            os_primary.auth_provider,
+            CONF.network.catalog_type,
+            CONF.network.region or CONF.identity.region,
+            endpoint_type=CONF.network.endpoint_type,
+            build_interval=CONF.network.build_interval,
+            build_timeout=CONF.network.build_timeout,
+            **os_primary.default_params)
+        cls.tap_flows_client = taas_client.TapFlowsClient(
+            os_primary.auth_provider,
+            CONF.network.catalog_type,
+            CONF.network.region or CONF.identity.region,
+            endpoint_type=CONF.network.endpoint_type,
+            build_interval=CONF.network.build_interval,
+            build_timeout=CONF.network.build_timeout,
+            **os_primary.default_params)
+
+    def create_tap_service(self, **kwargs):
+        body = self.tap_services_client.create_tap_service(
+            name=data_utils.rand_name("tap_service"),
+            **kwargs)
+        tap_service = body['tap_service']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_services_client.delete_tap_service,
+                        tap_service['id'])
+        return tap_service
+
+    def create_tap_flow(self, **kwargs):
+        body = self.tap_flows_client.create_tap_flow(
+            name=data_utils.rand_name("tap_service"),
+            **kwargs)
+        tap_flow = body['tap_flow']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_flows_client.delete_tap_flow,
+                        tap_flow['id'])
+        return tap_flow
+
+    def update_tap_service(self, tap_service_id, **kwargs):
+        body = self.tap_services_client.update_tap_service(
+            tap_service_id,
+            **kwargs)
+        tap_service = body['tap_service']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_services_client.delete_tap_service,
+                        tap_service['id'])
+
+    def update_tap_flow(self, tap_flow_id, **kwargs):
+        body = self.tap_flows_client.update_tap_flow(
+            tap_flow_id,
+            **kwargs)
+        tap_flow = body['tap_flow']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_flows_client.delete_tap_flow,
+                        tap_flow['id'])
diff --git a/neutron_tempest_plugin/tap_as_a_service/services/__init__.py b/neutron_tempest_plugin/tap_as_a_service/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/services/__init__.py
diff --git a/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py b/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py
new file mode 100644
index 0000000..7230cbb
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py
@@ -0,0 +1,63 @@
+#    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.
+
+from tempest.lib.services.network import base
+
+
+class TapServicesClient(base.BaseNetworkClient):
+
+    def create_tap_service(self, **kwargs):
+        uri = '/taas/tap_services'
+        post_data = {'tap_service': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_tap_service(self, tap_service_id, **kwargs):
+        uri = '/taas/tap_services/%s' % tap_service_id
+        post_data = {'tap_service': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_tap_service(self, tap_service_id, **fields):
+        uri = '/taas/tap_services/%s' % tap_service_id
+        return self.show_resource(uri, **fields)
+
+    def delete_tap_service(self, tap_service_id):
+        uri = '/taas/tap_services/%s' % tap_service_id
+        return self.delete_resource(uri)
+
+    def list_tap_services(self, **filters):
+        uri = '/taas/tap_services'
+        return self.list_resources(uri, **filters)
+
+
+class TapFlowsClient(base.BaseNetworkClient):
+
+    def create_tap_flow(self, **kwargs):
+        uri = '/taas/tap_flows'
+        post_data = {'tap_flow': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_tap_flow(self, tap_flow_id, **kwargs):
+        uri = '/taas/tap_flows/%s' % tap_flow_id
+        post_data = {'tap_flow': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_tap_flow(self, tap_flow_id, **fields):
+        uri = '/taas/tap_flows/%s' % tap_flow_id
+        return self.show_resource(uri, **fields)
+
+    def delete_tap_flow(self, tap_flow_id):
+        uri = '/taas/tap_flows/%s' % tap_flow_id
+        return self.delete_resource(uri)
+
+    def list_tap_flows(self, **filters):
+        uri = '/taas/tap_flows'
+        return self.list_resources(uri, **filters)
diff --git a/setup.cfg b/setup.cfg
index 144569f..136aa0f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,12 @@
 [metadata]
 name = neutron-tempest-plugin
 summary = Tempest plugin for Neutron Project
-description-file =
+description_file =
     README.rst
 author = OpenStack
-author-email = openstack-discuss@lists.openstack.org
-home-page = https://opendev.org/openstack/neutron-tempest-plugin
-python-requires = >=3.6
+author_email = openstack-discuss@lists.openstack.org
+home_page = https://opendev.org/openstack/neutron-tempest-plugin
+python_requires = >=3.6
 classifier =
     Environment :: OpenStack
     Intended Audience :: Information Technology
diff --git a/tools/customize_ubuntu_image b/tools/customize_ubuntu_image
index 3697265..fdd2d12 100755
--- a/tools/customize_ubuntu_image
+++ b/tools/customize_ubuntu_image
@@ -16,6 +16,13 @@
 INSTALL_GUEST_PACKAGES=(
    socat  # used to replace nc for testing advanced network features like
           # multicast
+   iperf3
+   iputils-ping
+   ncat
+   psmisc  # provides killall command
+   python3
+   tcpdump
+   vlan
 )
 
 # Function to be executed once after chroot on guest image
@@ -33,8 +40,8 @@
     # Install desired packages to Ubuntu guest image
     (
         DEBIAN_FRONTEND=noninteractive
-        apt-get update -y
-        apt-get install -y "${INSTALL_GUEST_PACKAGES[@]}"
+        sudo apt-get update -y
+        sudo apt-get install -y "${INSTALL_GUEST_PACKAGES[@]}"
     )
 }
 
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index 04fe323..12408ea 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -110,9 +110,10 @@
       devstack_localrc:
         PHYSICAL_NETWORK: default
         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
+        IMAGE_URLS: https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img
+        ADVANCED_IMAGE_NAME: ubuntu-20.04-minimal-cloudimg-amd64
+        ADVANCED_INSTANCE_TYPE: ntp_image_256M
         ADVANCED_INSTANCE_USER: ubuntu
+        CUSTOMIZE_IMAGE: true
         BUILD_TIMEOUT: 784
       tempest_concurrency: 3  # out of 4
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 063eb02..d072f9d 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -88,25 +88,16 @@
         - ipv6_metadata
       tempest_test_regex: ^neutron_tempest_plugin\.api
       devstack_services:
-        # Disable OVN services
-        br-ex-tcpdump: false
-        br-int-flows: false
-        ovn-controller: false
-        ovn-northd: false
-        ovs-vswitchd: false
-        ovsdb-server: false
-        q-ovn-metadata-agent: false
-        # Neutron services
-        q-agt: true
-        q-dhcp: true
-        q-l3: true
-        q-meta: true
-        q-metering: true
         neutron-log: true
       devstack_localrc:
-        Q_AGENT: openvswitch
-        Q_ML2_TENANT_NETWORK_TYPE: vxlan
-        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
+        # TODO(lucasagomes): Re-enable MOD_WSGI after
+        # https://bugs.launchpad.net/neutron/+bug/1912359 is implemented
+        NEUTRON_DEPLOY_MOD_WSGI: false
+        # TODO(ralonsoh): remove OVN_BUILD_FROM_SOURCE once the OS packages
+        # include at least OVN v20.12.0.
+        OVN_BUILD_FROM_SOURCE: True
+        OVN_BRANCH: "v21.03.0"
+        OVS_BRANCH: "8dc1733eaea866dce033b3c44853e1b09bf59fc7"
       devstack_local_conf:
         post-config:
           # NOTE(slaweq): We can get rid of this hardcoded absolute path when
@@ -129,6 +120,7 @@
       - ^tox.ini$
       - ^neutron/agent/.*$
       - ^neutron/privileged/.*$
+      - ^neutron_lib/tests/unit/.*$
       - ^neutron_tempest_plugin/scenario/.*$
 
 
@@ -275,6 +267,34 @@
       - ^neutron/plugins/ml2/drivers/ovn/.*$
 
 - job:
+    name: neutron-tempest-plugin-scenario-openvswitch-distributed-dhcp
+    parent: neutron-tempest-plugin-scenario-openvswitch
+    timeout: 10000
+    vars:
+      # NOTE: DHCP extra options and dns services aren't supported with
+      # distributed DHCP L2 agent extension
+      tempest_exclude_regex: "\
+          (^neutron_tempest_plugin.scenario.test_dhcp.DHCPTest.test_extra_dhcp_opts)|\
+          (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
+      devstack_services:
+        q-dhcp: false
+        q-distributed-dhcp: true
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-distributed-dhcp
+    parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    timeout: 10000
+    vars:
+      # NOTE: DHCP extra options and dns services aren't supported with
+      # distributed DHCP L2 agent extension
+      tempest_exclude_regex: "\
+          (^neutron_tempest_plugin.scenario.test_dhcp.DHCPTest.test_extra_dhcp_opts)|\
+          (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
+      devstack_services:
+        q-dhcp: false
+        q-distributed-dhcp: true
+
+- job:
     name: neutron-tempest-plugin-scenario-linuxbridge
     parent: neutron-tempest-plugin-scenario
     timeout: 10000
@@ -303,7 +323,13 @@
       network_available_features: *available_features
       # TODO(eolivare): remove VLAN Transparency tests from blacklist
       # when bug https://bugs.launchpad.net/neutron/+bug/1907548 will be fixed
-      tempest_exclude_regex: "(^neutron_tempest_plugin.scenario.test_vlan_transparency.VlanTransparencyTest)"
+      # TODO(slaweq): remove
+      # test_established_tcp_session_after_re_attachinging_sg from the
+      # exclude regex when bug https://bugs.launchpad.net/neutron/+bug/1936911
+      # will be fixed
+      tempest_exclude_regex: "\
+          (^neutron_tempest_plugin.scenario.test_vlan_transparency.VlanTransparencyTest)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.NetworkSecGroupTest.test_established_tcp_session_after_re_attachinging_sg)"
       devstack_localrc:
         Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_linuxbridge) | join(',') }}"
@@ -378,6 +404,14 @@
         Q_ML2_TENANT_NETWORK_TYPE: geneve
         Q_USE_PROVIDERNET_FOR_PUBLIC: true
         PHYSICAL_NETWORK: public
+        # NOTE(slaweq): In the job with OVN backend we can't use Ubuntu minimal
+        # image because kernel in that image don't supports MULTICAST traffic
+        # thus multicast scenario test with IGMP snooping enabled would fail
+        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: ntp_image_384M
+        ADVANCED_INSTANCE_USER: ubuntu
+        CUSTOMIZE_IMAGE: false
         ENABLE_CHASSIS_AS_GW: true
         OVN_L3_CREATE_PUBLIC_NETWORK: true
         OVN_DBS_LOG_LEVEL: dbg
@@ -386,7 +420,7 @@
         # TODO(eolivare): Remove OVN_BUILD_FROM_SOURCE once vlan-transparency
         # is included in an ovn released version
         OVN_BUILD_FROM_SOURCE: True
-        OVN_BRANCH: "v20.12.0"
+        OVN_BRANCH: "v21.06.0"
         OVS_BRANCH: "branch-2.15"
         OVS_SYSCONFDIR: "/usr/local/etc/openvswitch"
       devstack_services:
@@ -403,7 +437,6 @@
         q-meta: false
         q-metering: false
         q-qos: true
-        tls-proxy: true
         # Cinder services
         c-api: false
         c-bak: false
@@ -493,19 +526,37 @@
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_dvr) | join(',') }}"
         PHYSICAL_NETWORK: default
         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
+        IMAGE_URLS: https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img
+        ADVANCED_IMAGE_NAME: ubuntu-20.04-minimal-cloudimg-amd64
+        ADVANCED_INSTANCE_TYPE: ntp_image_256M
         ADVANCED_INSTANCE_USER: ubuntu
+        CUSTOMIZE_IMAGE: true
         BUILD_TIMEOUT: 784
+        Q_AGENT: openvswitch
+        Q_ML2_TENANT_NETWORK_TYPE: vxlan
+        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
       devstack_plugins:
         neutron: https://opendev.org/openstack/neutron.git
         neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
       tempest_plugins:
         - neutron-tempest-plugin
       devstack_services:
-        tls-proxy: false
+        tls-proxy: true
         tempest: true
+        # Disable OVN services
+        br-ex-tcpdump: false
+        br-int-flows: false
+        ovn-controller: false
+        ovn-northd: false
+        ovs-vswitchd: false
+        ovsdb-server: false
+        q-ovn-metadata-agent: false
+        # Neutron services
+        q-agt: true
+        q-dhcp: true
+        q-l3: true
+        q-meta: true
+        q-metering: true
         neutron-dns: true
         neutron-qos: true
         neutron-segments: true
@@ -580,7 +631,16 @@
     group-vars:
       subnode:
         devstack_services:
-          tls-proxy: false
+          tls-proxy: true
+          br-ex-tcpdump: false
+          br-int-flows: false
+          # Disable OVN services
+          ovn-controller: false
+          ovn-northd: false
+          ovs-vswitchd: false
+          ovsdb-server: false
+          q-ovn-metadata-agent: false
+          # Neutron services
           q-agt: true
           q-l3: true
           q-meta: true
@@ -598,6 +658,9 @@
           s-proxy: false
         devstack_localrc:
           USE_PYTHON3: true
+          Q_AGENT: openvswitch
+          Q_ML2_TENANT_NETWORK_TYPE: vxlan
+          Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
         devstack_local_conf:
           post-config:
             $NEUTRON_CONF:
@@ -842,3 +905,111 @@
       devstack_localrc:
         IPSEC_PACKAGE: strongswan
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_vpnaas) | join(',') }}"
+        Q_AGENT: openvswitch
+        Q_ML2_TENANT_NETWORK_TYPE: vxlan
+        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
+      devstack_services:
+        # Disable OVN services
+        br-ex-tcpdump: false
+        br-int-flows: false
+        ovn-controller: false
+        ovn-northd: false
+        ovs-vswitchd: false
+        ovsdb-server: false
+        q-ovn-metadata-agent: false
+        # Neutron services
+        q-agt: true
+        q-dhcp: true
+        q-meta: true
+        q-metering: true
+        q-l3: true
+
+- job:
+    name: neutron-tempest-plugin-tap-as-a-service
+    parent: neutron-tempest-plugin-base
+    description: |
+      Perform setup common to all tap-as-a-service tempest tests
+    roles:
+      - zuul: openstack/devstack
+    required-projects:
+      - openstack/devstack-gate
+      - openstack/neutron
+      - openstack/neutron-tempest-plugin
+      - openstack/tap-as-a-service
+      - openstack/tempest
+    vars:
+      tempest_test_regex: ^neutron_tempest_plugin\.tap_as_a_service
+      tox_envlist: all
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_tempest:
+        - taas
+        - taas-vlan-filter
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
+        DOWNLOAD_DEFAULT_IMAGES: false
+        IMAGE_URLS: "http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-i386-disk.img,https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img"
+        DEFAULT_IMAGE_NAME: cirros-0.3.4-i386-disk
+        ADVANCED_IMAGE_NAME: ubuntu-20.04-minimal-cloudimg-amd64
+        BUILD_TIMEOUT: 784
+        Q_AGENT: openvswitch
+        Q_ML2_TENANT_NETWORK_TYPE: vxlan
+        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
+      devstack_local_conf:
+        post-config:
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            AGENT:
+              tunnel_types: vxlan,gre
+        test-config:
+          $TEMPEST_CONFIG:
+            taas_plugin_options:
+              advanced_image_ref: ubuntu-20.04-minimal-cloudimg-amd64
+              advanced_image_ssh_user: ubuntu
+              provider_physical_network: public
+              provider_segmentation_id: 100
+            image_feature_enabled:
+              api_v2: true
+      devstack_plugins:
+        neutron: git://opendev.org/openstack/neutron.git
+        neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
+        tap-as-a-service: git://opendev.org/openstack/tap-as-a-service.git
+      devstack_services:
+        # Disable OVN services
+        ovn-controller: false
+        ovn-northd: false
+        ovs-vswitchd: false
+        ovsdb-server: false
+        q-ovn-metadata-agent: false
+        # Enable Neutron services that are not used by OVN
+        q-agt: true
+        q-dhcp: true
+        q-l3: true
+        q-meta: true
+        q-metering: true
+        br-ex-tcpdump: true
+        br-int-flows: true
+        base: false
+        key: true
+        mysql: true
+        rabbit: true
+        g-api: true
+        g-reg: true
+        n-api: true
+        n-cond: true
+        n-cpu: true
+        n-crt: true
+        n-sch: true
+        placement-api: true
+        n-api-meta: true
+        q-svc: true
+        quantum: true
+        taas: true
+        taas_openvswitch_agent: true
+        tempest: true
+        dstat: true
+    irrelevant-files: &tempest-irrelevant-files
+      - ^(test-|)requirements.txt$
+      - ^releasenotes/.*$
+      - ^doc/.*$
+      - ^.*\.rst$
+      - ^tools/.*$
+      - ^tox.ini$
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 969f80a..031860f 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -20,6 +20,8 @@
     experimental:
       jobs:
         - neutron-tempest-plugin-dvr-multinode-scenario
+        - neutron-tempest-plugin-scenario-openvswitch-distributed-dhcp
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-distributed-dhcp
 
 
 - project-template:
@@ -184,6 +186,7 @@
         - neutron-tempest-plugin-vpnaas-ussuri
         - neutron-tempest-plugin-vpnaas-victoria
         - neutron-tempest-plugin-vpnaas-wallaby
+        - neutron-tempest-plugin-tap-as-a-service
 
     gate:
       jobs:
diff --git a/zuul.d/queens_jobs.yaml b/zuul.d/queens_jobs.yaml
index 9701548..0b56b32 100644
--- a/zuul.d/queens_jobs.yaml
+++ b/zuul.d/queens_jobs.yaml
@@ -141,6 +141,7 @@
         CIRROS_VERSION: 0.3.5
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+        ADVANCED_INSTANCE_TYPE: ds512M
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-queens
@@ -169,6 +170,7 @@
         Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -243,3 +245,4 @@
         USE_PYTHON3: false
         CIRROS_VERSION: 0.3.5
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
+        ADVANCED_INSTANCE_TYPE: ds512M
diff --git a/zuul.d/rocky_jobs.yaml b/zuul.d/rocky_jobs.yaml
index 11e4c9a..83b2f26 100644
--- a/zuul.d/rocky_jobs.yaml
+++ b/zuul.d/rocky_jobs.yaml
@@ -167,6 +167,7 @@
         Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -226,6 +227,7 @@
       <<: *scenario_vars_rocky
       devstack_localrc:
         USE_PYTHON3: True
+        ADVANCED_INSTANCE_TYPE: ds512M
     branches: ^(?!stable/rocky).*$
     irrelevant-files: *openvswitch-scenario-irrelevant-files
 
@@ -263,6 +265,7 @@
         Q_ML2_TENANT_NETWORK_TYPE: vxlan
         Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -330,6 +333,7 @@
       <<: *openvswitch_vars_rocky
       devstack_localrc:
         USE_PYTHON3: True
+        ADVANCED_INSTANCE_TYPE: ds512M
     branches: ^(?!stable/rocky).*$
     irrelevant-files: *iptables_hybrid_irrelevant_files
 
@@ -352,6 +356,7 @@
         Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -403,6 +408,7 @@
         Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+        ADVANCED_INSTANCE_TYPE: ds512M
     branches: ^(?!stable/rocky).*$
 
 - job:
@@ -609,6 +615,7 @@
       devstack_localrc:
         USE_PYTHON3: false
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
+        ADVANCED_INSTANCE_TYPE: ds512M
     branches:
       - stable/rocky
 
diff --git a/zuul.d/stein_jobs.yaml b/zuul.d/stein_jobs.yaml
index 40bca7c..f84df2a 100644
--- a/zuul.d/stein_jobs.yaml
+++ b/zuul.d/stein_jobs.yaml
@@ -130,6 +130,7 @@
       network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_L3_CONF:
@@ -177,6 +178,7 @@
         Q_ML2_TENANT_NETWORK_TYPE: vxlan
         Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -263,6 +265,7 @@
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_linuxbridge) | join(',') }}"
         Q_ML2_TENANT_NETWORK_TYPE: vxlan
         Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch,linuxbridge
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -335,3 +338,5 @@
     vars:
       branch_override: stable/stein
       network_api_extensions_common: *api_extensions
+      devstack_localrc:
+        ADVANCED_INSTANCE_TYPE: ds512M
diff --git a/zuul.d/train_jobs.yaml b/zuul.d/train_jobs.yaml
index a623251..5caf127 100644
--- a/zuul.d/train_jobs.yaml
+++ b/zuul.d/train_jobs.yaml
@@ -135,6 +135,7 @@
       network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_L3_CONF:
@@ -160,6 +161,7 @@
       network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_L3_CONF:
@@ -185,6 +187,7 @@
       network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        ADVANCED_INSTANCE_TYPE: ds512M
       devstack_local_conf:
         post-config:
           $NEUTRON_L3_CONF:
@@ -217,6 +220,8 @@
     vars:
       branch_override: stable/train
       network_api_extensions_common: *api_extensions
+      devstack_localrc:
+        ADVANCED_INSTANCE_TYPE: ds512M
 
 - job:
     name: neutron-tempest-plugin-sfc-train