Merge "Enable nova anti-affinity in two-node act-stdby job"
diff --git a/octavia_tempest_plugin/common/constants.py b/octavia_tempest_plugin/common/constants.py
index 0424cc5..16d6cdf 100644
--- a/octavia_tempest_plugin/common/constants.py
+++ b/octavia_tempest_plugin/common/constants.py
@@ -14,6 +14,7 @@
 
 # API field names
 ACTIVE_CONNECTIONS = 'active_connections'
+ALLOWED_CIDRS = 'allowed_cidrs'
 AVAILABILITY_ZONE = 'availability_zone'
 AVAILABILITY_ZONE_DATA = 'availability_zone_data'
 AVAILABILITY_ZONE_PROFILE_ID = 'availability_zone_profile_id'
@@ -56,6 +57,7 @@
 INSERT_HEADERS = 'insert_headers'
 X_FORWARDED_FOR = 'X-Forwarded-For'
 X_FORWARDED_PORT = 'X-Forwarded-Port'
+TAGS = 'tags'
 TIMEOUT_CLIENT_DATA = 'timeout_client_data'
 TIMEOUT_MEMBER_CONNECT = 'timeout_member_connect'
 TIMEOUT_MEMBER_DATA = 'timeout_member_data'
diff --git a/octavia_tempest_plugin/common/decorators.py b/octavia_tempest_plugin/common/decorators.py
new file mode 100644
index 0000000..b484497
--- /dev/null
+++ b/octavia_tempest_plugin/common/decorators.py
@@ -0,0 +1,54 @@
+# Copyright 2020 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 functools import wraps
+
+import testtools
+
+from oslo_utils import excutils
+from tempest import config
+from tempest.lib import exceptions
+
+CONF = config.CONF
+
+
+def skip_if_not_implemented(f):
+    """A decorator to raise a skip exception for not implemented features.
+
+    This decorator raises a skipException if the method raises a
+    NotImplemented exception. If "skip_if_not_implemented=False"
+    argument was passed to the method, the NotImplemented exception will
+    be raised.
+
+    @param skip_if_not_implemented: If True (default), raise skipException.
+    """
+    @wraps(f)
+    def wrapper(*func_args, **func_kwargs):
+
+        skip = func_kwargs.pop('skip_if_not_implemented', True)
+        if CONF.loadbalancer_feature_enabled.not_implemented_is_error:
+            skip = False
+        try:
+            return f(*func_args, **func_kwargs)
+        except exceptions.NotImplemented as e:
+            with excutils.save_and_reraise_exception():
+                if not skip:
+                    raise
+                message = ("The configured provider driver '{driver}' "
+                           "does not support a feature required for this "
+                           "test.".format(
+                               driver=CONF.load_balancer.provider))
+                if hasattr(e, 'resp_body'):
+                    message = e.resp_body.get('faultstring', message)
+                raise testtools.TestCase.skipException(message)
+    return wrapper
diff --git a/octavia_tempest_plugin/config.py b/octavia_tempest_plugin/config.py
index fc04c33..601c6c8 100644
--- a/octavia_tempest_plugin/config.py
+++ b/octavia_tempest_plugin/config.py
@@ -204,11 +204,26 @@
     cfg.BoolOpt('test_reuse_connection', default=True,
                 help='Reuse TCP connections while testing LB with '
                      'HTTP members (keep-alive).'),
+    # Log offloading specific options
+    cfg.StrOpt('tenant_flow_log_file',
+               default='/var/log/octavia-tenant-traffic.log',
+               help='File path, on the tempest system, to the tenant flow '
+                    'log file.'),
+    cfg.StrOpt('amphora_admin_log_file',
+               default='/var/log/octavia-amphora.log',
+               help='File path, on the tempest system, to the amphora admin '
+                    'log file.'),
 ]
 
 lb_feature_enabled_group = cfg.OptGroup(name='loadbalancer-feature-enabled',
                                         title='Enabled/Disabled LB features')
 LBFeatureEnabledGroup = [
+    cfg.BoolOpt('not_implemented_is_error',
+                default=True,
+                help="When True, not-implemented responses from the API are "
+                     "considered an error and test failure. This should be "
+                     "used when a driver should support all of the Octavia "
+                     "API features, such as the reference driver."),
     cfg.BoolOpt('health_monitor_enabled',
                 default=True,
                 help="Whether Health Monitor is available with provider "
@@ -237,4 +252,8 @@
                 default=True,
                 help="Whether session persistence is supported with the "
                      "provider driver."),
+    cfg.BoolOpt('log_offload_enabled', default=False,
+                help="Whether the log offload tests will run. These require "
+                     "the tempest instance have access to the log files "
+                     "specified in the tempest configuration."),
 ]
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py b/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py
index 4094515..aed93b4 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py
@@ -16,6 +16,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 CONF = config.CONF
@@ -28,6 +29,7 @@
     stats_root_tag = 'amphora_stats'
     base_uri = '/v2.0/octavia/{object}'
 
+    @skip_if_not_implemented
     def show_amphora(self, amphora_id, query_params=None,
                      return_object_only=True):
         """Get amphora details.
@@ -69,6 +71,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_amphorae(self, query_params=None, return_object_only=True):
         """Get a list of amphora objects.
 
@@ -107,6 +110,7 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def get_amphora_stats(self, amphora_id, query_params=None,
                           return_object_only=True):
         """Get amphora statistics.
@@ -158,6 +162,7 @@
         else:
             return jsonutils.loads(body.decode('utf-8'))
 
+    @skip_if_not_implemented
     def update_amphora_config(self, amphora_id):
         """Update the amphora agent configuration.
 
@@ -193,6 +198,7 @@
         response, body = self.put(uri, '')
         self.expected_success(202, response.status)
 
+    @skip_if_not_implemented
     def amphora_failover(self, amphora_id):
         """Failover an amphora.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_capabilities_client.py b/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_capabilities_client.py
index 92696a7..679af74 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_capabilities_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_capabilities_client.py
@@ -14,6 +14,7 @@
 #   under the License.
 #
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 from octavia_tempest_plugin.services.load_balancer.v2 import provider_client
 
@@ -36,6 +37,7 @@
             object=self.list_root_tag
         )
 
+    @skip_if_not_implemented
     def list_availability_zone_capabilities(self, provider, query_params=None,
                                             return_object_only=True):
         """Get a list of provider availability zone capability objects.
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_client.py b/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_client.py
index c729f21..ab87a85 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_client.py
@@ -16,6 +16,7 @@
 from oslo_log import log as logging
 from tempest.lib import exceptions
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 LOG = logging.getLogger(__name__)
@@ -33,6 +34,7 @@
         super(AvailabilityZoneClient, self).__init__(*args, **kwargs)
         self.uri = self.base_uri.format(object=self.resource_path)
 
+    @skip_if_not_implemented
     def create_availability_zone(self, name, availability_zone_profile_id,
                                  description=Unset, enabled=Unset,
                                  return_object_only=True):
@@ -75,6 +77,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_availability_zone(self, availability_zone_name, query_params=None,
                                return_object_only=True):
         """Get the availability zone details.
@@ -116,6 +119,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_availability_zones(self, query_params=None,
                                 return_object_only=True):
         """Get a list of availability zone objects.
@@ -155,6 +159,7 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_availability_zone(self, availability_zone_name,
                                  description=Unset, enabled=Unset,
                                  return_object_only=True):
@@ -195,6 +200,7 @@
         kwargs['obj_id'] = kwargs.pop('availability_zone_name')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_availability_zone(self, availability_zone_name,
                                  ignore_errors=False):
         """Delete an availability zone.
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_profile_client.py b/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_profile_client.py
index 1aaab90..071b15f 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_profile_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/availability_zone_profile_client.py
@@ -16,6 +16,7 @@
 from oslo_log import log as logging
 from tempest.lib import exceptions
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 LOG = logging.getLogger(__name__)
@@ -33,6 +34,7 @@
         super(AvailabilityZoneProfileClient, self).__init__(*args, **kwargs)
         self.uri = self.base_uri.format(object=self.resource_path)
 
+    @skip_if_not_implemented
     def create_availability_zone_profile(self, name, provider_name,
                                          availability_zone_data,
                                          return_object_only=True):
@@ -73,6 +75,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_availability_zone_profile(self, availability_zone_profile_id,
                                        query_params=None,
                                        return_object_only=True):
@@ -116,6 +119,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_availability_zone_profiles(self, query_params=None,
                                         return_object_only=True):
         """Get a list of availability zone profile objects.
@@ -155,6 +159,7 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_availability_zone_profile(
             self, availability_zone_profile_id, name=Unset,
             provider_name=Unset, availability_zone_data=Unset,
@@ -199,6 +204,7 @@
         kwargs['obj_id'] = kwargs.pop('availability_zone_profile_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_availability_zone_profile(self, availability_zone_profile_id,
                                          ignore_errors=False):
         """Delete an availability zone profile.
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/flavor_capabilities_client.py b/octavia_tempest_plugin/services/load_balancer/v2/flavor_capabilities_client.py
index 4c23042..eb07faf 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/flavor_capabilities_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/flavor_capabilities_client.py
@@ -13,6 +13,7 @@
 #   under the License.
 #
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 from octavia_tempest_plugin.services.load_balancer.v2 import provider_client
 
@@ -34,6 +35,7 @@
             object=self.list_root_tag
         )
 
+    @skip_if_not_implemented
     def list_flavor_capabilities(self, provider, query_params=None,
                                  return_object_only=True):
         """Get a list of provider flavor capability objects.
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/flavor_client.py b/octavia_tempest_plugin/services/load_balancer/v2/flavor_client.py
index 085da9e..8a87a33 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/flavor_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/flavor_client.py
@@ -16,6 +16,7 @@
 from oslo_log import log as logging
 from tempest.lib import exceptions
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 LOG = logging.getLogger(__name__)
@@ -27,6 +28,7 @@
     root_tag = 'flavor'
     list_root_tag = 'flavors'
 
+    @skip_if_not_implemented
     def create_flavor(self, name, flavor_profile_id, description=Unset,
                       enabled=Unset, return_object_only=True):
         """Create a flavor.
@@ -67,6 +69,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_flavor(self, flavor_id, query_params=None,
                     return_object_only=True):
         """Get the flavor details.
@@ -108,6 +111,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_flavors(self, query_params=None, return_object_only=True):
         """Get a list of flavor objects.
 
@@ -146,6 +150,7 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_flavor(self, flavor_id, name=Unset, description=Unset,
                       enabled=Unset, return_object_only=True):
         """Update a flavor.
@@ -186,6 +191,7 @@
         kwargs['obj_id'] = kwargs.pop('flavor_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_flavor(self, flavor_id, ignore_errors=False):
         """Delete a flavor.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/flavor_profile_client.py b/octavia_tempest_plugin/services/load_balancer/v2/flavor_profile_client.py
index 811cff8..bc0f2fb 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/flavor_profile_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/flavor_profile_client.py
@@ -16,6 +16,7 @@
 from oslo_log import log as logging
 from tempest.lib import exceptions
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 LOG = logging.getLogger(__name__)
@@ -27,6 +28,7 @@
     root_tag = 'flavorprofile'
     list_root_tag = 'flavorprofiles'
 
+    @skip_if_not_implemented
     def create_flavor_profile(self, name, provider_name, flavor_data,
                               return_object_only=True):
         """Create a flavor profile.
@@ -65,6 +67,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_flavor_profile(self, flavorprofile_id, query_params=None,
                             return_object_only=True):
         """Get the flavor profile details.
@@ -106,6 +109,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_flavor_profiles(self, query_params=None, return_object_only=True):
         """Get a list of flavor profile objects.
 
@@ -144,6 +148,7 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_flavor_profile(
             self, flavorprofile_id, name=Unset, provider_name=Unset,
             flavor_data=Unset, return_object_only=True):
@@ -185,6 +190,7 @@
         kwargs['obj_id'] = kwargs.pop('flavorprofile_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_flavor_profile(self, flavorprofile_id, ignore_errors=False):
         """Delete a flavor profile.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/healthmonitor_client.py b/octavia_tempest_plugin/services/load_balancer/v2/healthmonitor_client.py
index 70dce4c..4ce362e 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/healthmonitor_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/healthmonitor_client.py
@@ -14,6 +14,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 CONF = config.CONF
@@ -26,8 +27,9 @@
     list_root_tag = 'healthmonitors'
     resource_name = 'healthmonitor'
 
+    @skip_if_not_implemented
     def create_healthmonitor(self, pool_id, type, delay, timeout, max_retries,
-                             max_retries_down=Unset, name=Unset,
+                             max_retries_down=Unset, name=Unset, tags=Unset,
                              http_method=Unset, url_path=Unset,
                              expected_codes=Unset, admin_state_up=Unset,
                              return_object_only=True):
@@ -44,6 +46,7 @@
                                  changing the operating status of the member to
                                  ERROR.
         :param name: Human-readable name of the resource.
+        :param tags: Human-readable tags of the resource.
         :param http_method: The HTTP method that the health monitor uses for
                             requests.
         :param url_path: The HTTP URL path of the request sent by the monitor
@@ -86,6 +89,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_healthmonitor(self, healthmonitor_id, query_params=None,
                            return_object_only=True):
         """Get healthmonitor details.
@@ -127,6 +131,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_healthmonitors(self, query_params=None, return_object_only=True):
         """Get a list of healthmonitor objects.
 
@@ -165,9 +170,10 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_healthmonitor(self, healthmonitor_id, delay=Unset,
                              timeout=Unset, max_retries=Unset,
-                             max_retries_down=Unset, name=Unset,
+                             max_retries_down=Unset, name=Unset, tags=Unset,
                              http_method=Unset, url_path=Unset,
                              expected_codes=Unset, admin_state_up=Unset,
                              return_object_only=True):
@@ -183,6 +189,7 @@
                                  changing the operating status of the member to
                                  ERROR.
         :param name: Human-readable name of the resource.
+        :param tags: Human-readable tags of the resource.
         :param http_method: The HTTP method that the health monitor uses for
                             requests.
         :param url_path: The HTTP URL path of the request sent by the monitor
@@ -226,6 +233,7 @@
         kwargs['obj_id'] = kwargs.pop('healthmonitor_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_healthmonitor(self, healthmonitor_id, ignore_errors=False):
         """Delete a healthmonitor.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/l7policy_client.py b/octavia_tempest_plugin/services/load_balancer/v2/l7policy_client.py
index 674ec02..36eef40 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/l7policy_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/l7policy_client.py
@@ -14,6 +14,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 CONF = config.CONF
@@ -26,8 +27,9 @@
     list_root_tag = 'l7policies'
     resource_name = 'l7policy'
 
+    @skip_if_not_implemented
     def create_l7policy(self, listener_id, action, name=Unset,
-                        description=Unset, admin_state_up=Unset,
+                        description=Unset, tags=Unset, admin_state_up=Unset,
                         position=Unset, redirect_pool_id=Unset,
                         redirect_url=Unset, return_object_only=True):
         """Create a l7policy.
@@ -36,6 +38,7 @@
         :param action: The l7policy action.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param position: The position of this policy on the listener.
@@ -77,6 +80,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_l7policy(self, l7policy_id, query_params=None,
                       return_object_only=True):
         """Get l7policy details.
@@ -118,6 +122,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_l7policies(self, query_params=None, return_object_only=True):
         """Get a list of l7policy objects.
 
@@ -156,8 +161,9 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_l7policy(self, l7policy_id, action=Unset, name=Unset,
-                        description=Unset, admin_state_up=Unset,
+                        description=Unset, tags=Unset, admin_state_up=Unset,
                         position=Unset, redirect_pool_id=Unset,
                         redirect_url=Unset, return_object_only=True):
         """Update a l7policy.
@@ -166,6 +172,7 @@
         :param action: The l7policy action.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param position: The position of this policy on the listener.
@@ -208,6 +215,7 @@
         kwargs['obj_id'] = kwargs.pop('l7policy_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_l7policy(self, l7policy_id, ignore_errors=False):
         """Delete a l7policy.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/l7rule_client.py b/octavia_tempest_plugin/services/load_balancer/v2/l7rule_client.py
index 2ca1c71..da40af2 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/l7rule_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/l7rule_client.py
@@ -14,6 +14,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 from octavia_tempest_plugin.services.load_balancer.v2 import l7policy_client
 
@@ -38,7 +39,8 @@
             object=self.list_root_tag
         )
 
-    def create_l7rule(self, l7policy_id, type, value, compare_type,
+    @skip_if_not_implemented
+    def create_l7rule(self, l7policy_id, type, value, compare_type, tags=Unset,
                       admin_state_up=Unset, key=Unset, invert=Unset,
                       return_object_only=True):
         """Create a l7rule.
@@ -47,6 +49,7 @@
         :param type: The L7 rule type.
         :param value: The value to use for the comparison.
         :param compare_type: The comparison type for the L7 rule.
+        :param tags: The tags of the L7 rule.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param key: The key to use for the comparison.
@@ -86,6 +89,7 @@
         kwargs['parent_id'] = kwargs.pop('l7policy_id')
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_l7rule(self, l7rule_id, l7policy_id, query_params=None,
                     return_object_only=True):
         """Get l7rule details.
@@ -129,6 +133,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_l7rules(self, l7policy_id, query_params=None,
                      return_object_only=True):
         """Get a list of l7rule objects.
@@ -170,9 +175,10 @@
                                   query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_l7rule(self, l7rule_id, l7policy_id, type=Unset, value=Unset,
-                      compare_type=Unset, admin_state_up=Unset, key=Unset,
-                      invert=Unset, return_object_only=True):
+                      compare_type=Unset, tags=Unset, admin_state_up=Unset,
+                      key=Unset, invert=Unset, return_object_only=True):
         """Update a l7rule.
 
         :param l7rule_id: The l7rule ID to update.
@@ -180,6 +186,7 @@
         :param type: The L7 rule type.
         :param value: The value to use for the comparison.
         :param compare_type: The comparison type for the L7 rule.
+        :param tags: The tags of the L7 rule.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param key: The key to use for the comparison.
@@ -220,6 +227,7 @@
         kwargs['parent_id'] = kwargs.pop('l7policy_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_l7rule(self, l7rule_id, l7policy_id, ignore_errors=False):
         """Delete a l7rule.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py b/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py
index 1cc17ff..a3f5958 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py
@@ -17,6 +17,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 CONF = config.CONF
@@ -28,16 +29,18 @@
     root_tag = 'listener'
     list_root_tag = 'listeners'
 
+    @skip_if_not_implemented
     def create_listener(self, protocol, protocol_port, loadbalancer_id,
-                        name=Unset, description=Unset, admin_state_up=Unset,
-                        connection_limit=Unset, timeout_client_data=Unset,
+                        name=Unset, description=Unset, tags=Unset,
+                        admin_state_up=Unset, connection_limit=Unset,
+                        timeout_client_data=Unset,
                         timeout_member_connect=Unset,
                         timeout_member_data=Unset, timeout_tcp_inspect=Unset,
                         insert_headers=Unset, default_pool_id=Unset,
                         default_tls_container_ref=Unset,
                         sni_container_refs=Unset, client_authentication=Unset,
                         client_ca_tls_container_ref=Unset,
-                        client_crl_container_ref=Unset,
+                        client_crl_container_ref=Unset, allowed_cidrs=Unset,
                         return_object_only=True):
         """Create a listener.
 
@@ -46,6 +49,7 @@
         :param loadbalancer_id: The ID of the load balancer.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param connection_limit: The maximum number of connections permitted
@@ -84,6 +88,7 @@
                                          secret containing a PEM format CA
                                          revocation list file for
                                          TERMINATED_HTTPS listeners.
+        :param allowed_cidrs: A list of IPv4 or IPv6 CIDRs.
         :param return_object_only: If True, the response returns the object
                                    inside the root tag. False returns the full
                                    response from the API.
@@ -118,6 +123,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_listener(self, listener_id, query_params=None,
                       return_object_only=True):
         """Get listener details.
@@ -159,6 +165,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_listeners(self, query_params=None, return_object_only=True):
         """Get a list of listener objects.
 
@@ -197,22 +204,24 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_listener(self, listener_id, name=Unset, description=Unset,
-                        admin_state_up=Unset, connection_limit=Unset,
-                        timeout_client_data=Unset,
+                        tags=Unset, admin_state_up=Unset,
+                        connection_limit=Unset, timeout_client_data=Unset,
                         timeout_member_connect=Unset,
                         timeout_member_data=Unset, timeout_tcp_inspect=Unset,
                         insert_headers=Unset, default_pool_id=Unset,
                         default_tls_container_ref=Unset,
                         sni_container_refs=Unset, client_authentication=Unset,
                         client_ca_tls_container_ref=Unset,
-                        client_crl_container_ref=Unset,
+                        client_crl_container_ref=Unset, allowed_cidrs=Unset,
                         return_object_only=True):
         """Update a listener.
 
         :param listener_id: The listener ID to update.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param connection_limit: The maximum number of connections permitted
@@ -251,6 +260,7 @@
                                          secret containing a PEM format CA
                                          revocation list file for
                                          TERMINATED_HTTPS listeners.
+        :param allowed_cidrs: A list of IPv4 or IPv6 CIDRs.
         :param return_object_only: If True, the response returns the object
                                    inside the root tag. False returns the full
                                    response from the API.
@@ -286,6 +296,7 @@
         kwargs['obj_id'] = kwargs.pop('listener_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_listener(self, listener_id, ignore_errors=False):
         """Delete a listener.
 
@@ -322,6 +333,7 @@
         return self._delete_obj(obj_id=listener_id,
                                 ignore_errors=ignore_errors)
 
+    @skip_if_not_implemented
     def get_listener_stats(self, listener_id, query_params=None,
                            return_object_only=True):
         """Get listener statistics.
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/loadbalancer_client.py b/octavia_tempest_plugin/services/load_balancer/v2/loadbalancer_client.py
index 9a287cf..9499d89 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/loadbalancer_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/loadbalancer_client.py
@@ -17,6 +17,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 CONF = config.CONF
@@ -34,12 +35,14 @@
         self.timeout = CONF.load_balancer.lb_build_timeout
         self.build_interval = CONF.load_balancer.lb_build_interval
 
+    @skip_if_not_implemented
     def create_loadbalancer(self, name=Unset, description=Unset,
                             admin_state_up=Unset, flavor_id=Unset,
                             listeners=Unset, project_id=Unset, provider=Unset,
                             vip_address=Unset, vip_network_id=Unset,
                             vip_port_id=Unset, vip_qos_policy_id=Unset,
-                            vip_subnet_id=Unset, return_object_only=True):
+                            vip_subnet_id=Unset, return_object_only=True,
+                            tags=Unset):
         """Create a loadbalancer.
 
         :param name: Human-readable name of the resource.
@@ -56,6 +59,7 @@
         :param vip_qos_policy_id: The ID of the QoS Policy which will apply to
                                   the Virtual IP (VIP).
         :param vip_subnet_id: The ID of the subnet for the Virtual IP (VIP).
+        :param tags: A human-readable tags of the resource.
         :param return_object_only: If True, the response returns the object
                                    inside the root tag. False returns the full
                                    response from the API.
@@ -90,6 +94,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_loadbalancer(self, lb_id, query_params=None,
                           return_object_only=True):
         """Get loadbalancer details.
@@ -131,6 +136,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_loadbalancers(self, query_params=None, return_object_only=True):
         """Get a list of loadbalancer objects.
 
@@ -169,14 +175,17 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_loadbalancer(self, lb_id, name=Unset, description=Unset,
-                            admin_state_up=Unset, vip_qos_policy_id=Unset,
+                            tags=Unset, admin_state_up=Unset,
+                            vip_qos_policy_id=Unset,
                             return_object_only=True):
         """Update a loadbalancer.
 
         :param lb_id: The loadbalancer ID to update.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param vip_qos_policy_id: The ID of the QoS Policy which will apply to
@@ -216,6 +225,7 @@
         kwargs['obj_id'] = kwargs.pop('lb_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_loadbalancer(self, lb_id, cascade=False, ignore_errors=False):
         """Delete a loadbalancer.
 
@@ -255,6 +265,7 @@
                                 ignore_errors=ignore_errors,
                                 cascade=cascade)
 
+    @skip_if_not_implemented
     def failover_loadbalancer(self, lb_id):
         """Failover a loadbalancer.
 
@@ -291,6 +302,7 @@
         self.expected_success(202, response.status)
         return
 
+    @skip_if_not_implemented
     def get_loadbalancer_stats(self, lb_id, query_params=None,
                                return_object_only=True):
         """Get loadbalancer statistics.
@@ -341,6 +353,7 @@
         else:
             return jsonutils.loads(body.decode('utf-8'))
 
+    @skip_if_not_implemented
     def get_loadbalancer_status(self, lb_id, query_params=None,
                                 return_object_only=True):
         """Get a loadbalancer status tree.
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/member_client.py b/octavia_tempest_plugin/services/load_balancer/v2/member_client.py
index 63e836a..c0d83da 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/member_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/member_client.py
@@ -16,6 +16,7 @@
 from oslo_serialization import jsonutils
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 from octavia_tempest_plugin.services.load_balancer.v2 import pool_client
 
@@ -39,8 +40,10 @@
             object=self.list_root_tag
         )
 
+    @skip_if_not_implemented
     def create_member(self, pool_id, address, protocol_port,
-                      name=Unset, admin_state_up=Unset, weight=Unset,
+                      name=Unset, tags=Unset, admin_state_up=Unset,
+                      weight=Unset,
                       backup=Unset, subnet_id=Unset, monitor_address=Unset,
                       monitor_port=Unset, return_object_only=True):
         """Create a member.
@@ -49,12 +52,15 @@
         :param address: The IP address of the resource.
         :param protocol_port: The protocol port number for the resource.
         :param name: Human-readable name of the resource.
+        :param tags: Human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param weight: The weight of a member determines the portion of
                        requests or connections it services compared to the
                        other members of the pool.
         :param backup: Is the member a backup?
+        :param subnet_id: The subnet ID which the member service
+                                 is accessible from
         :param monitor_address: An alternate IP address used for health
                                 monitoring a backend member.
         :param monitor_port: An alternate protocol port used for health
@@ -94,6 +100,7 @@
         kwargs['parent_id'] = kwargs.pop('pool_id')
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_member(self, member_id, pool_id, query_params=None,
                     return_object_only=True):
         """Get member details.
@@ -137,6 +144,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_members(self, pool_id, query_params=None,
                      return_object_only=True):
         """Get a list of member objects.
@@ -178,7 +186,8 @@
                                   query_params=query_params,
                                   return_object_only=return_object_only)
 
-    def update_member(self, member_id, pool_id, name=Unset,
+    @skip_if_not_implemented
+    def update_member(self, member_id, pool_id, name=Unset, tags=Unset,
                       admin_state_up=Unset, weight=Unset, backup=Unset,
                       monitor_address=Unset, monitor_port=Unset,
                       return_object_only=True):
@@ -187,6 +196,7 @@
         :param member_id: The member ID to update.
         :param pool_id: The ID of the pool where the member lives.
         :param name: Human-readable name of the resource.
+        :param tags: Human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param weight: The weight of a member determines the portion of
@@ -233,6 +243,7 @@
         kwargs['parent_id'] = kwargs.pop('pool_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def update_members(self, pool_id, members_list):
         """Batch update all members on a pool.
 
@@ -272,6 +283,7 @@
         self.expected_success(202, response.status)
         return
 
+    @skip_if_not_implemented
     def delete_member(self, member_id, pool_id, ignore_errors=False):
         """Delete a member.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py b/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py
index 46ec38d..98f4bfa 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py
@@ -14,6 +14,7 @@
 
 from tempest import config
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 CONF = config.CONF
@@ -26,8 +27,10 @@
     list_root_tag = 'pools'
     resource_name = 'pool'
 
+    @skip_if_not_implemented
     def create_pool(self, protocol, lb_algorithm, loadbalancer_id=Unset,
                     listener_id=Unset, name=Unset, description=Unset,
+                    tags=Unset,
                     admin_state_up=Unset, session_persistence=Unset,
                     return_object_only=True):
         """Create a pool.
@@ -38,6 +41,7 @@
         :param listener_id: The ID of the listener for the pool.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param session_persistence: A JSON object specifying the session
@@ -77,6 +81,7 @@
                   if arg != 'self' and value is not Unset}
         return self._create_object(**kwargs)
 
+    @skip_if_not_implemented
     def show_pool(self, pool_id, query_params=None, return_object_only=True):
         """Get pool details.
 
@@ -117,6 +122,7 @@
                                  query_params=query_params,
                                  return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def list_pools(self, query_params=None, return_object_only=True):
         """Get a list of pool objects.
 
@@ -155,8 +161,9 @@
         return self._list_objects(query_params=query_params,
                                   return_object_only=return_object_only)
 
+    @skip_if_not_implemented
     def update_pool(self, pool_id, lb_algorithm=Unset, name=Unset,
-                    description=Unset, admin_state_up=Unset,
+                    description=Unset, tags=Unset, admin_state_up=Unset,
                     session_persistence=Unset, return_object_only=True):
         """Update a pool.
 
@@ -164,6 +171,7 @@
         :param lb_algorithm: The load balancing algorithm for the pool.
         :param name: Human-readable name of the resource.
         :param description: A human-readable description for the resource.
+        :param tags: A human-readable tags of the resource.
         :param admin_state_up: The administrative state of the resource, which
                                is up (true) or down (false).
         :param session_persistence: A JSON object specifying the session
@@ -204,6 +212,7 @@
         kwargs['obj_id'] = kwargs.pop('pool_id')
         return self._update_object(**kwargs)
 
+    @skip_if_not_implemented
     def delete_pool(self, pool_id, ignore_errors=False):
         """Delete a pool.
 
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/provider_client.py b/octavia_tempest_plugin/services/load_balancer/v2/provider_client.py
index cbef1df..319826e 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/provider_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/provider_client.py
@@ -13,6 +13,7 @@
 #   under the License.
 #
 
+from octavia_tempest_plugin.common.decorators import skip_if_not_implemented
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
 
 Unset = base_client.Unset
@@ -22,6 +23,7 @@
 
     list_root_tag = 'providers'
 
+    @skip_if_not_implemented
     def list_providers(self, query_params=None, return_object_only=True):
         """Get a list of provider objects.
 
diff --git a/octavia_tempest_plugin/tests/api/v2/test_amphora.py b/octavia_tempest_plugin/tests/api/v2/test_amphora.py
index d1106e6..91241c6 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_amphora.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_amphora.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+import time
 from uuid import UUID
 
 from dateutil import parser
@@ -227,3 +229,63 @@
 
         for new_amp in after_amphorae:
             self.assertNotEqual(amphora_1[const.ID], new_amp[const.ID])
+
+    @testtools.skipIf(CONF.load_balancer.test_with_noop,
+                      'Log offload tests will not work in noop mode.')
+    @testtools.skipUnless(
+        CONF.loadbalancer_feature_enabled.log_offload_enabled,
+        'Skipping log offload tests because tempest configuration '
+        '[loadbalancer-feature-enabled] log_offload_enabled is False.')
+    @decorators.idempotent_id('4e3c6fcb-5f83-4da1-8296-56f209eae30d')
+    def test_admin_log(self):
+        """Tests admin log offloading
+
+        * Create a listener
+        * Validates the listener config log message is present
+        """
+        listener_name = data_utils.rand_name("lb_member_listener1_admin_log")
+        protocol_port = '8124'
+        listener_kwargs = {
+            const.NAME: listener_name,
+            const.PROTOCOL: const.HTTP,
+            const.PROTOCOL_PORT: protocol_port,
+            const.LOADBALANCER_ID: self.lb_id,
+        }
+        listener = self.mem_listener_client.create_listener(**listener_kwargs)
+        listener_id = listener[const.ID]
+        self.addCleanup(
+            self.mem_listener_client.cleanup_listener,
+            listener_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        # We need to give the log subsystem time to commit the log
+        time.sleep(CONF.load_balancer.check_interval)
+
+        # Check for an amphora agent API call and code log entry
+        # One is logged via the gunicorn logging and the other via
+        # oslo.logging.
+        agent_found = False
+        client_found = False
+        with open(CONF.load_balancer.amphora_admin_log_file) as f:
+            for line in f:
+                if 'Octavia HaProxy Rest Client' in line:
+                    client_found = True
+                if ' amphora-agent: ' in line:
+                    agent_found = True
+                if client_found and agent_found:
+                    break
+
+        self.assertTrue(
+            client_found,
+            'Octavia user agent string was not found in: {0}'.format(
+                CONF.load_balancer.amphora_admin_log_file))
+
+        self.assertTrue(
+            agent_found, 'Amphora agent string was not found in: {0}'.format(
+                CONF.load_balancer.amphora_admin_log_file))
diff --git a/octavia_tempest_plugin/tests/api/v2/test_availability_zone.py b/octavia_tempest_plugin/tests/api/v2/test_availability_zone.py
index 816e00e..29426c2 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_availability_zone.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_availability_zone.py
@@ -127,6 +127,15 @@
         self.assertEqual(self.availability_zone_profile_id,
                          availability_zone[const.AVAILABILITY_ZONE_PROFILE_ID])
 
+        # Test that availability_zones do not support tags
+        availability_zone_tags = ["Hello", "World"]
+        tags_availability_zone_kwargs = availability_zone_kwargs.copy()
+        tags_availability_zone_kwargs[const.TAGS] = availability_zone_tags
+        self.assertRaises(
+            TypeError,
+            self.lb_admin_availability_zone_client.create_availability_zone,
+            **tags_availability_zone_kwargs)
+
     @decorators.idempotent_id('bba84c0c-2832-4c4c-90ff-d28acfe4ae36')
     def test_availability_zone_list(self):
         """Tests availability zone list API and field filtering.
diff --git a/octavia_tempest_plugin/tests/api/v2/test_availability_zone_profile.py b/octavia_tempest_plugin/tests/api/v2/test_availability_zone_profile.py
index c062dbe..f5e4b7e 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_availability_zone_profile.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_availability_zone_profile.py
@@ -104,6 +104,17 @@
             availability_zone_data_json,
             availability_zone_profile[const.AVAILABILITY_ZONE_DATA])
 
+        # Testing that availability_zone_profiles do not support tags
+        availability_zone_profile_tags = ["Hello", "World"]
+        tags_availability_zone_profile_kwargs = (
+            availability_zone_profile_kwargs.copy())
+        tags_availability_zone_profile_kwargs[const.TAGS] = (
+            availability_zone_profile_tags)
+        az_profile_client = self.lb_admin_availability_zone_profile_client
+        self.assertRaises(TypeError,
+                          az_profile_client.create_availability_zone_profile,
+                          **tags_availability_zone_profile_kwargs)
+
     @decorators.idempotent_id('ef7d1c45-e312-46ce-8dcb-f2fe26295658')
     def test_availability_zone_profile_list(self):
         """Tests availability zone profile list API and field filtering.
diff --git a/octavia_tempest_plugin/tests/api/v2/test_flavor.py b/octavia_tempest_plugin/tests/api/v2/test_flavor.py
index be3ac76..4333121 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_flavor.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_flavor.py
@@ -105,6 +105,14 @@
         self.assertEqual(self.flavor_profile_id,
                          flavor[const.FLAVOR_PROFILE_ID])
 
+        # Test that flavors do not support tags
+        flavor_tags = ["Hello", "World"]
+        tags_flavor_kwargs = flavor_kwargs.copy()
+        tags_flavor_kwargs[const.TAGS] = flavor_tags
+        self.assertRaises(TypeError,
+                          self.lb_admin_flavor_client.create_flavor,
+                          **tags_flavor_kwargs)
+
     @decorators.idempotent_id('3ef040ee-fe7e-457b-a56f-8b152f7afa3b')
     def test_flavor_list(self):
         """Tests flavor list API and field filtering.
diff --git a/octavia_tempest_plugin/tests/api/v2/test_flavor_profile.py b/octavia_tempest_plugin/tests/api/v2/test_flavor_profile.py
index 3bd6e54..8f9c7d3 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_flavor_profile.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_flavor_profile.py
@@ -80,6 +80,15 @@
                          flavor_profile[const.PROVIDER_NAME])
         self.assertEqual(flavor_data_json, flavor_profile[const.FLAVOR_DATA])
 
+        # Testing that flavor_profiles do not support tags
+        flavor_profile_tags = ["Hello", "World"]
+        tags_flavor_profile_kwargs = flavor_profile_kwargs.copy()
+        tags_flavor_profile_kwargs[const.TAGS] = flavor_profile_tags
+        self.assertRaises(
+            TypeError,
+            self.lb_admin_flavor_profile_client.create_flavor_profile,
+            **tags_flavor_profile_kwargs)
+
     @decorators.idempotent_id('c4e17fdf-849a-4132-93ae-dfca21ce4444')
     def test_flavor_profile_list(self):
         """Tests flavor profile list API and field filtering.
diff --git a/octavia_tempest_plugin/tests/api/v2/test_healthmonitor.py b/octavia_tempest_plugin/tests/api/v2/test_healthmonitor.py
index 1feb9d9..5f4be23 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_healthmonitor.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_healthmonitor.py
@@ -107,6 +107,13 @@
             const.ADMIN_STATE_UP: True,
         }
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            hw_tags = ["Hello", "World"]
+            hm_kwargs.update({
+                const.TAGS: hw_tags
+            })
+
         # Test that a user without the loadbalancer role cannot
         # create a healthmonitor
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -148,6 +155,10 @@
         for item in equal_items:
             self.assertEqual(hm_kwargs[item], hm[item])
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(hm_kwargs[const.TAGS], hm[const.TAGS])
+
     # Helper functions for test healthmonitor list
     def _filter_hms_by_pool_id(self, hms, pool_ids):
         return [hm for hm in hms
@@ -250,6 +261,13 @@
             const.EXPECTED_CODES: '200-204',
             const.ADMIN_STATE_UP: True,
         }
+
+        if self.mem_healthmonitor_client.is_version_supported(
+                self.api_version, '2.5'):
+            hm1_tags = ["English", "Mathematics",
+                        "Marketing", "Creativity"]
+            hm1_kwargs.update({const.TAGS: hm1_tags})
+
         hm1 = self.mem_healthmonitor_client.create_healthmonitor(
             **hm1_kwargs)
         self.addCleanup(
@@ -286,6 +304,13 @@
             const.EXPECTED_CODES: '200-204',
             const.ADMIN_STATE_UP: True,
         }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            hm2_tags = ["English", "Spanish",
+                        "Soft_skills", "Creativity"]
+            hm2_kwargs.update({const.TAGS: hm2_tags})
+
         hm2 = self.mem_healthmonitor_client.create_healthmonitor(
             **hm2_kwargs)
         self.addCleanup(
@@ -322,6 +347,13 @@
             const.EXPECTED_CODES: '200-204',
             const.ADMIN_STATE_UP: False,
         }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            hm3_tags = ["English", "Project_management",
+                        "Communication", "Creativity"]
+            hm3_kwargs.update({const.TAGS: hm3_tags})
+
         hm3 = self.mem_healthmonitor_client.create_healthmonitor(
             **hm3_kwargs)
         self.addCleanup(
@@ -455,6 +487,28 @@
         self.assertEqual(hm1[const.NAME],
                          hms[0][const.NAME])
 
+        # Creating a list of 3 healthmonitors, each one contains different tags
+        if self.mem_healthmonitor_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_hms = [hm1, hm2, hm3]
+            test_list = []
+            for hm in list_of_hms:
+
+                # If tags "English" and "Creativity" are in the HM's tags
+                # and "Spanish" is not, add the HM to the list
+                if "English" in hm[const.TAGS] and "Creativity" in (
+                    hm[const.TAGS]) and "Spanish" not in (
+                        hm[const.TAGS]):
+                    test_list.append(hm[const.NAME])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                test_list, [hm1[const.NAME], hm3[const.NAME]])
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in hm[const.TAGS]
+                                     for hm in list_of_hms]))
+
     @decorators.idempotent_id('284e8d3b-7b2d-4697-9e41-580b3423c0b4')
     def test_healthmonitor_show(self):
         """Tests healthmonitor show API.
@@ -607,6 +661,13 @@
             const.ADMIN_STATE_UP: False,
         }
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            hw_tags = ["Hello", "World"]
+            hm_kwargs.update({
+                const.TAGS: hw_tags
+            })
+
         hm = self.mem_healthmonitor_client.create_healthmonitor(**hm_kwargs)
         self.addCleanup(
             self.mem_healthmonitor_client.cleanup_healthmonitor,
@@ -643,6 +704,10 @@
         for item in equal_items:
             self.assertEqual(hm_kwargs[item], hm[item])
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(hm_kwargs[const.TAGS], hm[const.TAGS])
+
         # Test that a user, without the loadbalancer member role, cannot
         # use this command
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -685,6 +750,14 @@
             const.EXPECTED_CODES: '201,202',
             const.ADMIN_STATE_UP: not hm_kwargs[const.ADMIN_STATE_UP],
         }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            hw_new_tags = ["Hola", "Mundo"]
+            hm_update_kwargs.update({
+                const.TAGS: hw_new_tags
+            })
+
         hm = self.mem_healthmonitor_client.update_healthmonitor(
             hm[const.ID], **hm_update_kwargs)
 
@@ -712,6 +785,10 @@
                        const.HTTP_METHOD, const.URL_PATH, const.EXPECTED_CODES,
                        const.ADMIN_STATE_UP]
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(hm_update_kwargs[const.TAGS], hm[const.TAGS])
+
         for item in equal_items:
             self.assertEqual(hm_update_kwargs[item], hm[item])
 
diff --git a/octavia_tempest_plugin/tests/api/v2/test_l7policy.py b/octavia_tempest_plugin/tests/api/v2/test_l7policy.py
index b1bccc2..419a20f 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_l7policy.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_l7policy.py
@@ -134,6 +134,14 @@
             const.ADMIN_STATE_UP: True,
             const.POSITION: 1,
         }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7_policy_tags = ["Hello", "World"]
+            l7policy_kwargs.update({
+                const.TAGS: l7_policy_tags
+            })
+
         if url:
             l7policy_kwargs[const.ACTION] = const.REDIRECT_TO_URL
             l7policy_kwargs[const.REDIRECT_URL] = url
@@ -202,6 +210,11 @@
             self.assertIsNone(l7policy.pop(const.REDIRECT_URL, None))
             self.assertIsNone(l7policy.pop(const.REDIRECT_POOL_ID, None))
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(l7policy_kwargs[const.TAGS],
+                                  l7policy[const.TAGS])
+
     @decorators.idempotent_id('42fa14ba-caf1-465e-ab36-27e7501f95ef')
     def test_l7policy_list(self):
         """Tests l7policy list API and field filtering.
@@ -248,6 +261,13 @@
             const.POSITION: 1,
             const.ACTION: const.REJECT
         }
+
+        if self.mem_l7policy_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7policy1_tags = ["English", "Mathematics",
+                              "Marketing", "Creativity"]
+            l7policy1_kwargs.update({const.TAGS: l7policy1_tags})
+
         l7policy1 = self.mem_l7policy_client.create_l7policy(
             **l7policy1_kwargs)
         self.addCleanup(
@@ -281,6 +301,13 @@
             const.ACTION: const.REDIRECT_TO_POOL,
             const.REDIRECT_POOL_ID: self.pool_id
         }
+
+        if self.mem_l7policy_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7policy2_tags = ["English", "Spanish",
+                              "Soft_skills", "Creativity"]
+            l7policy2_kwargs.update({const.TAGS: l7policy2_tags})
+
         l7policy2 = self.mem_l7policy_client.create_l7policy(
             **l7policy2_kwargs)
         self.addCleanup(
@@ -315,6 +342,13 @@
             const.ACTION: const.REDIRECT_TO_URL,
             const.REDIRECT_URL: l7_redirect_url
         }
+
+        if self.mem_l7policy_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7policy3_tags = ["English", "Project_management",
+                              "Communication", "Creativity"]
+            l7policy3_kwargs.update({const.TAGS: l7policy3_tags})
+
         l7policy3 = self.mem_l7policy_client.create_l7policy(
             **l7policy3_kwargs)
         self.addCleanup(
@@ -473,6 +507,28 @@
         self.assertEqual(l7policy1[const.DESCRIPTION],
                          l7policies[0][const.DESCRIPTION])
 
+        # Creating a list of 3 l7policies, each one contains different tags
+        if self.mem_l7policy_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_l7policies = [l7policy1, l7policy2, l7policy3]
+            test_list = []
+            for l7policy in list_of_l7policies:
+
+                # If tags "English" and "Creativity" are in the l7policy's tags
+                # and "Spanish" is not, add the l7policy to the list
+                if "English" in l7policy[const.TAGS] and "Creativity" in (
+                        l7policy[const.TAGS]) and "Spanish" not in (
+                        l7policy[const.TAGS]):
+                    test_list.append(l7policy[const.NAME])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                test_list, [l7policy1[const.NAME], l7policy3[const.NAME]])
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in l7policy[const.TAGS]
+                                     for l7policy in list_of_l7policies]))
+
     @decorators.idempotent_id('baaa8104-a037-4976-b908-82a0b3e08129')
     def test_l7policy_show(self):
         """Tests l7policy show API.
@@ -630,6 +686,13 @@
             const.REDIRECT_URL: l7_redirect_url,
         }
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7_policy_tags = ["Hello", "World"]
+            l7policy_kwargs.update({
+                const.TAGS: l7_policy_tags
+            })
+
         l7policy = self.mem_l7policy_client.create_l7policy(**l7policy_kwargs)
         self.addClassResourceCleanup(
             self.mem_l7policy_client.cleanup_l7policy,
@@ -665,6 +728,11 @@
         self.assertEqual(l7_redirect_url, l7policy[const.REDIRECT_URL])
         self.assertIsNone(l7policy.pop(const.REDIRECT_POOL_ID, None))
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(l7policy_kwargs[const.TAGS],
+                                  l7policy[const.TAGS])
+
         # Test that a user, without the load balancer member role, cannot
         # use this command
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -706,6 +774,14 @@
             const.ACTION: const.REDIRECT_TO_POOL,
             const.REDIRECT_POOL_ID: self.pool_id,
         }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7_policy_new_tags = ["Hola", "Mundo"]
+            l7policy_update_kwargs.update({
+                const.TAGS: l7_policy_new_tags
+            })
+
         l7policy = self.mem_l7policy_client.update_l7policy(
             l7policy[const.ID], **l7policy_update_kwargs)
 
@@ -745,6 +821,11 @@
         self.assertEqual(self.pool_id, l7policy[const.REDIRECT_POOL_ID])
         self.assertIsNone(l7policy.pop(const.REDIRECT_URL, None))
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(l7policy_update_kwargs[const.TAGS],
+                                  l7policy[const.TAGS])
+
     @decorators.idempotent_id('7925eb4b-94b6-4c28-98c2-fd0b4f0976cc')
     def test_l7policy_delete(self):
         """Tests l7policy create and delete APIs.
diff --git a/octavia_tempest_plugin/tests/api/v2/test_l7rule.py b/octavia_tempest_plugin/tests/api/v2/test_l7rule.py
index 9a73034..c2526bf 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_l7rule.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_l7rule.py
@@ -143,6 +143,13 @@
             const.INVERT: False,
         }
 
+        if self.mem_l7policy_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7_rule_tags = ["Hello", "World"]
+            l7rule_kwargs.update({
+                const.TAGS: l7_rule_tags
+            })
+
         # Test that a user without the load balancer role cannot
         # create a l7rule
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -193,6 +200,11 @@
         for item in equal_items:
             self.assertEqual(l7rule_kwargs[item], l7rule[item])
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(l7rule_kwargs[const.TAGS],
+                                  l7rule[const.TAGS])
+
     @decorators.idempotent_id('69095254-f106-4fb6-9f54-7a78cc14fb51')
     def test_l7rule_list(self):
         """Tests l7rule list API and field filtering.
@@ -232,6 +244,13 @@
             const.COMPARE_TYPE: const.EQUAL_TO,
             const.KEY: 'mykey2-list',
         }
+
+        if self.mem_lb_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7rule1_tags = ["English", "Mathematics",
+                            "Marketing", "Creativity"]
+            l7rule1_kwargs.update({const.TAGS: l7rule1_tags})
+
         l7rule1 = self.mem_l7rule_client.create_l7rule(
             **l7rule1_kwargs)
         self.addCleanup(
@@ -263,6 +282,13 @@
             const.COMPARE_TYPE: const.EQUAL_TO,
             const.KEY: 'mykey1-list',
         }
+
+        if self.mem_lb_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7rule2_tags = ["English", "Spanish",
+                            "Soft_skills", "Creativity"]
+            l7rule2_kwargs.update({const.TAGS: l7rule2_tags})
+
         l7rule2 = self.mem_l7rule_client.create_l7rule(
             **l7rule2_kwargs)
         self.addCleanup(
@@ -294,6 +320,13 @@
             const.COMPARE_TYPE: const.EQUAL_TO,
             const.KEY: 'mykey3-list',
         }
+
+        if self.mem_lb_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7rule3_tags = ["English", "Project_management",
+                            "Communication", "Creativity"]
+            l7rule3_kwargs.update({const.TAGS: l7rule3_tags})
+
         l7rule3 = self.mem_l7rule_client.create_l7rule(
             **l7rule3_kwargs)
         self.addCleanup(
@@ -414,6 +447,28 @@
         self.assertEqual(l7rule1[const.VALUE],
                          l7rules[0][const.VALUE])
 
+        # Creating a list of 3 l7rules, each one contains different tags
+        if self.mem_l7rule_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_l7rules = [l7rule1, l7rule2, l7rule3]
+            test_list = []
+            for l7rule in list_of_l7rules:
+
+                # If tags "English" and "Creativity" are in the l7rule's tags
+                # and "Spanish" is not, add the l7rule to the list
+                if "English" in l7rule[const.TAGS] and "Creativity" in (
+                    l7rule[const.TAGS]) and "Spanish" not in (
+                        l7rule[const.TAGS]):
+                    test_list.append(l7rule[const.VALUE])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                [l7rule1[const.VALUE], l7rule3[const.VALUE]], test_list)
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in l7rule[const.TAGS]
+                                     for l7rule in list_of_l7rules]))
+
     @decorators.idempotent_id('b80b34c3-09fc-467b-8027-7350adb17070')
     def test_l7rule_show(self):
         """Tests l7rule show API.
@@ -526,6 +581,13 @@
             const.INVERT: False,
         }
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7_rule_tags = ["Hello", "World"]
+            l7rule_kwargs.update({
+                const.TAGS: l7_rule_tags
+            })
+
         l7rule = self.mem_l7rule_client.create_l7rule(**l7rule_kwargs)
         self.addClassResourceCleanup(
             self.mem_l7rule_client.cleanup_l7rule,
@@ -557,6 +619,11 @@
         equal_items = [const.ADMIN_STATE_UP, const.TYPE, const.VALUE,
                        const.COMPARE_TYPE, const.KEY, const.INVERT]
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(l7rule_kwargs[const.TAGS],
+                                  l7rule[const.TAGS])
+
         for item in equal_items:
             self.assertEqual(l7rule_kwargs[item], l7rule[item])
 
@@ -599,6 +666,13 @@
             const.KEY: 'mykey-UPDATED',
             const.INVERT: True,
         }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            l7rule_update_kwargs.update({
+                const.TAGS: ["Hola", "Mundo"]
+            })
+
         l7rule = self.mem_l7rule_client.update_l7rule(
             l7rule[const.ID], **l7rule_update_kwargs)
 
@@ -624,6 +698,12 @@
         # Test changed items (which is all of them, for l7rules)
         equal_items = [const.ADMIN_STATE_UP, const.TYPE, const.VALUE,
                        const.COMPARE_TYPE, const.KEY, const.INVERT]
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(l7rule_update_kwargs[const.TAGS],
+                                  l7rule[const.TAGS])
+
         for item in equal_items:
             self.assertEqual(l7rule_update_kwargs[item], l7rule[item])
 
diff --git a/octavia_tempest_plugin/tests/api/v2/test_listener.py b/octavia_tempest_plugin/tests/api/v2/test_listener.py
index 3a45656..db98958 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_listener.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_listener.py
@@ -59,6 +59,10 @@
                                 CONF.load_balancer.lb_build_interval,
                                 CONF.load_balancer.lb_build_timeout)
 
+        cls.allowed_cidrs = ['192.0.1.0/24']
+        if CONF.load_balancer.test_with_ipv6:
+            cls.allowed_cidrs = ['2001:db8:a0b:12f0::/64']
+
     @decorators.idempotent_id('88d0ec83-7b08-48d9-96e2-0df1d2f8cd98')
     def test_listener_create(self):
         """Tests listener create and basic show APIs.
@@ -102,6 +106,25 @@
                 const.TIMEOUT_TCP_INSPECT: 50,
             })
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener_tags = [str(x) for x in range(100)]
+            listener_kwargs.update({
+                const.TAGS: listener_tags
+            })
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            # Test that CIDR IP version matches VIP IP version
+            bad_cidrs = ['192.0.1.0/24', '2001:db8:a0b:12f0::/64']
+            listener_kwargs.update({const.ALLOWED_CIDRS: bad_cidrs})
+            self.assertRaises(
+                exceptions.BadRequest,
+                self.mem_listener_client.create_listener,
+                **listener_kwargs)
+
+            listener_kwargs.update({const.ALLOWED_CIDRS: self.allowed_cidrs})
+
         # Test that a user without the load balancer role cannot
         # create a listener
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -135,9 +158,21 @@
                 CONF.load_balancer.build_interval,
                 CONF.load_balancer.build_timeout)
 
-        self.assertEqual(listener_name, listener[const.NAME])
-        self.assertEqual(listener_description, listener[const.DESCRIPTION])
-        self.assertTrue(listener[const.ADMIN_STATE_UP])
+        equal_items = [const.NAME, const.DESCRIPTION,
+                       const.ADMIN_STATE_UP,
+                       const.PROTOCOL, const.PROTOCOL_PORT,
+                       const.CONNECTION_LIMIT]
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.1'):
+            equal_items.append(const.TIMEOUT_CLIENT_DATA)
+            equal_items.append(const.TIMEOUT_MEMBER_CONNECT)
+            equal_items.append(const.TIMEOUT_MEMBER_DATA)
+            equal_items.append(const.TIMEOUT_TCP_INSPECT)
+
+        for item in equal_items:
+            self.assertEqual(listener_kwargs[item], listener[item])
+
         parser.parse(listener[const.CREATED_AT])
         parser.parse(listener[const.UPDATED_AT])
         UUID(listener[const.ID])
@@ -146,20 +181,21 @@
             self.assertEqual(const.OFFLINE, listener[const.OPERATING_STATUS])
         else:
             self.assertEqual(const.ONLINE, listener[const.OPERATING_STATUS])
-        self.assertEqual(self.protocol, listener[const.PROTOCOL])
-        self.assertEqual(80, listener[const.PROTOCOL_PORT])
-        self.assertEqual(200, listener[const.CONNECTION_LIMIT])
+
         insert_headers = listener[const.INSERT_HEADERS]
         self.assertTrue(
             strutils.bool_from_string(insert_headers[const.X_FORWARDED_FOR]))
         self.assertTrue(
             strutils.bool_from_string(insert_headers[const.X_FORWARDED_PORT]))
+
         if self.mem_listener_client.is_version_supported(
-                self.api_version, '2.1'):
-            self.assertEqual(1000, listener[const.TIMEOUT_CLIENT_DATA])
-            self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_CONNECT])
-            self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_DATA])
-            self.assertEqual(50, listener[const.TIMEOUT_TCP_INSPECT])
+                self.api_version, '2.5'):
+            self.assertCountEqual(listener_kwargs[const.TAGS],
+                                  listener[const.TAGS])
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            self.assertEqual(self.allowed_cidrs, listener[const.ALLOWED_CIDRS])
 
     @decorators.idempotent_id('cceac303-4db5-4d5a-9f6e-ff33780a5f29')
     def test_listener_create_on_same_port(self):
@@ -336,6 +372,12 @@
             const.PROTOCOL_PORT: 80,
             const.LOADBALANCER_ID: lb_id,
         }
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener1_tags = ["English", "Mathematics",
+                              "Marketing", "Creativity"]
+            listener1_kwargs.update({const.TAGS: listener1_tags})
+
         listener1 = self.mem_listener_client.create_listener(
             **listener1_kwargs)
         self.addCleanup(
@@ -368,6 +410,12 @@
             const.PROTOCOL_PORT: 81,
             const.LOADBALANCER_ID: lb_id,
         }
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener2_tags = ["English", "Spanish",
+                              "Soft_skills", "Creativity"]
+            listener2_kwargs.update({const.TAGS: listener2_tags})
+
         listener2 = self.mem_listener_client.create_listener(
             **listener2_kwargs)
         self.addCleanup(
@@ -400,6 +448,12 @@
             const.PROTOCOL_PORT: 82,
             const.LOADBALANCER_ID: lb_id,
         }
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener3_tags = ["English", "Project_management",
+                              "Communication", "Creativity"]
+            listener3_kwargs.update({const.TAGS: listener3_tags})
+
         listener3 = self.mem_listener_client.create_listener(
             **listener3_kwargs)
         self.addCleanup(
@@ -487,6 +541,9 @@
             show_listener_response_fields.append('timeout_member_connect')
             show_listener_response_fields.append('timeout_member_data')
             show_listener_response_fields.append('timeout_tcp_inspect')
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            show_listener_response_fields.append('allowed_cidrs')
         for field in show_listener_response_fields:
             if field in (const.DEFAULT_POOL_ID, const.L7_POLICIES):
                 continue
@@ -542,6 +599,28 @@
         self.assertEqual(listener1[const.DESCRIPTION],
                          listeners[0][const.DESCRIPTION])
 
+        # Creating a list of 3 listeners, each one contains different tags
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_listeners = [listener1, listener2, listener3]
+            test_list = []
+            for listener in list_of_listeners:
+
+                # If tags "English" and "Creativity" are in the listener's tags
+                # and "Spanish" is not, add the listener to the list
+                if "English" in listener[const.TAGS] and "Creativity" in (
+                    listener[const.TAGS]) and "Spanish" not in (
+                        listener[const.TAGS]):
+                    test_list.append(listener[const.NAME])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                test_list, [listener1[const.NAME], listener3[const.NAME]])
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in listener[const.TAGS]
+                                     for listener in list_of_listeners]))
+
     @decorators.idempotent_id('6e299eae-6907-4dfc-89c2-e57709d25d3d')
     def test_listener_show(self):
         """Tests listener show API.
@@ -581,6 +660,17 @@
                 const.TIMEOUT_TCP_INSPECT: 50,
             })
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener_tags = ["hello", "world"]
+            listener_kwargs.update({
+                const.TAGS: listener_tags
+            })
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            listener_kwargs.update({const.ALLOWED_CIDRS: self.allowed_cidrs})
+
         listener = self.mem_listener_client.create_listener(**listener_kwargs)
         self.addClassResourceCleanup(
             self.mem_listener_client.cleanup_listener,
@@ -605,10 +695,32 @@
                 const.ONLINE,
                 CONF.load_balancer.build_interval,
                 CONF.load_balancer.build_timeout)
+        equal_items = [const.NAME, const.DESCRIPTION,
+                       const.ADMIN_STATE_UP,
+                       const.PROTOCOL, const.PROTOCOL_PORT,
+                       const.CONNECTION_LIMIT]
 
-        self.assertEqual(listener_name, listener[const.NAME])
-        self.assertEqual(listener_description, listener[const.DESCRIPTION])
-        self.assertTrue(listener[const.ADMIN_STATE_UP])
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.1'):
+            equal_items.append(const.TIMEOUT_CLIENT_DATA)
+            equal_items.append(const.TIMEOUT_MEMBER_CONNECT)
+            equal_items.append(const.TIMEOUT_MEMBER_DATA)
+            equal_items.append(const.TIMEOUT_TCP_INSPECT)
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(listener_kwargs[const.TAGS],
+                                  listener[const.TAGS])
+
+        for item in equal_items:
+            self.assertEqual(listener_kwargs[item], listener[item])
+
+        insert_headers = listener[const.INSERT_HEADERS]
+        self.assertTrue(
+            strutils.bool_from_string(insert_headers[const.X_FORWARDED_FOR]))
+        self.assertTrue(
+            strutils.bool_from_string(insert_headers[const.X_FORWARDED_PORT]))
+
         parser.parse(listener[const.CREATED_AT])
         parser.parse(listener[const.UPDATED_AT])
         UUID(listener[const.ID])
@@ -617,21 +729,10 @@
             self.assertEqual(const.OFFLINE, listener[const.OPERATING_STATUS])
         else:
             self.assertEqual(const.ONLINE, listener[const.OPERATING_STATUS])
-        self.assertEqual(self.protocol, listener[const.PROTOCOL])
-        self.assertEqual(81, listener[const.PROTOCOL_PORT])
-        self.assertEqual(200, listener[const.CONNECTION_LIMIT])
-        insert_headers = listener[const.INSERT_HEADERS]
-        self.assertTrue(
-            strutils.bool_from_string(insert_headers[const.X_FORWARDED_FOR]))
-        self.assertTrue(
-            strutils.bool_from_string(insert_headers[const.X_FORWARDED_PORT]))
 
         if self.mem_listener_client.is_version_supported(
-                self.api_version, '2.1'):
-            self.assertEqual(1000, listener[const.TIMEOUT_CLIENT_DATA])
-            self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_CONNECT])
-            self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_DATA])
-            self.assertEqual(50, listener[const.TIMEOUT_TCP_INSPECT])
+                self.api_version, '2.12'):
+            self.assertEqual(self.allowed_cidrs, listener[const.ALLOWED_CIDRS])
 
         # Test that a user with lb_admin role can see the listener
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -702,6 +803,17 @@
                 const.TIMEOUT_TCP_INSPECT: 50,
             })
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener_tags = ["Hello", "World"]
+            listener_kwargs.update({
+                const.TAGS: listener_tags
+            })
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            listener_kwargs.update({const.ALLOWED_CIDRS: self.allowed_cidrs})
+
         listener = self.mem_listener_client.create_listener(**listener_kwargs)
         self.addClassResourceCleanup(
             self.mem_listener_client.cleanup_listener,
@@ -743,6 +855,15 @@
             self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_DATA])
             self.assertEqual(50, listener[const.TIMEOUT_TCP_INSPECT])
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(listener_kwargs[const.TAGS],
+                                  listener[const.TAGS])
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            self.assertEqual(self.allowed_cidrs, listener[const.ALLOWED_CIDRS])
+
         # Test that a user, without the load balancer member role, cannot
         # use this command
         if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
@@ -799,6 +920,28 @@
                 const.TIMEOUT_TCP_INSPECT: 100,
             })
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            listener_updated_tags = ["Hola", "Mundo"]
+            listener_update_kwargs.update({
+                const.TAGS: listener_updated_tags
+            })
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            # Test that CIDR IP version matches VIP IP version
+            bad_cidrs = ['192.0.2.0/24', '2001:db8::/6']
+            listener_update_kwargs.update({const.ALLOWED_CIDRS: bad_cidrs})
+            self.assertRaises(
+                exceptions.BadRequest,
+                self.mem_listener_client.update_listener,
+                listener[const.ID], **listener_update_kwargs)
+
+            new_cidrs = ['192.0.2.0/24']
+            if CONF.load_balancer.test_with_ipv6:
+                new_cidrs = ['2001:db8::/64']
+            listener_update_kwargs.update({const.ALLOWED_CIDRS: new_cidrs})
+
         listener = self.mem_listener_client.update_listener(
             listener[const.ID], **listener_update_kwargs)
 
@@ -842,6 +985,18 @@
             self.assertEqual(2000, listener[const.TIMEOUT_MEMBER_DATA])
             self.assertEqual(100, listener[const.TIMEOUT_TCP_INSPECT])
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(listener_update_kwargs[const.TAGS],
+                                  listener[const.TAGS])
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            expected_cidrs = ['192.0.2.0/24']
+            if CONF.load_balancer.test_with_ipv6:
+                expected_cidrs = ['2001:db8::/64']
+            self.assertEqual(expected_cidrs, listener[const.ALLOWED_CIDRS])
+
     @decorators.idempotent_id('16f11c82-f069-4592-8954-81b35a98e3b7')
     def test_listener_delete(self):
         """Tests listener create and delete APIs.
diff --git a/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py b/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
index b4dde1f..684eddb 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
@@ -69,6 +69,13 @@
                      # vip_qos_policy_id=lb_qos_policy_id)
                      const.NAME: lb_name}
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            lb_tags = ["Hello", "World"]
+            lb_kwargs.update({
+                const.TAGS: lb_tags
+            })
+
         self._setup_lb_network_kwargs(lb_kwargs, ip_version, use_fixed_ip=True)
 
         # Test that a user without the load balancer role cannot
@@ -139,6 +146,10 @@
         except Exception:
             pass
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(lb_kwargs[const.TAGS], lb[const.TAGS])
+
     @decorators.idempotent_id('643ef031-c800-45f2-b229-3c8f8b37c829')
     def test_load_balancer_delete(self):
         """Tests load balancer create and delete APIs.
@@ -263,17 +274,29 @@
 
         lb_name = data_utils.rand_name("lb_member_lb2-list")
         lb_description = data_utils.rand_name('B')
+        lb_admin_state_up = True
+        lb_provider = CONF.load_balancer.provider
+        lb_vip_network_id = self.lb_member_vip_net[const.ID]
 
-        lb = self.mem_lb_client.create_loadbalancer(
-            admin_state_up=True,
-            description=lb_description,
+        lb_kwargs = {
+            const.ADMIN_STATE_UP: lb_admin_state_up,
+            const.DESCRIPTION: lb_description,
             # TODO(johnsom) Fix test to use a real flavor
             # flavor=lb_flavor,
-            provider=CONF.load_balancer.provider,
-            name=lb_name,
+            const.PROVIDER: lb_provider,
+            const.NAME: lb_name,
             # TODO(johnsom) Add QoS
             # vip_qos_policy_id=lb_qos_policy_id)
-            vip_network_id=self.lb_member_vip_net[const.ID])
+            const.VIP_NETWORK_ID: lb_vip_network_id
+        }
+
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            lb_tags = ["English", "Mathematics", "Marketing", "Creativity"]
+            lb_kwargs.update({const.TAGS: lb_tags})
+
+        lb = self.mem_lb_client.create_loadbalancer(
+            **lb_kwargs)
+
         self.addCleanup(
             self.mem_lb_client.cleanup_loadbalancer,
             lb[const.ID])
@@ -298,13 +321,25 @@
 
         lb_name = data_utils.rand_name("lb_member_lb1-list")
         lb_description = data_utils.rand_name('A')
+        lb_admin_state_up = True
+        lb_provider = CONF.load_balancer.provider
+        lb_vip_network_id = self.lb_member_vip_net[const.ID]
+
+        lb_kwargs = {
+            const.ADMIN_STATE_UP: lb_admin_state_up,
+            const.DESCRIPTION: lb_description,
+            const.PROVIDER: lb_provider,
+            const.NAME: lb_name,
+            const.VIP_NETWORK_ID: lb_vip_network_id,
+        }
+
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            lb_tags = ["English", "Spanish", "Soft_skills", "Creativity"]
+            lb_kwargs.update({const.TAGS: lb_tags})
 
         lb = self.mem_lb_client.create_loadbalancer(
-            admin_state_up=True,
-            description=lb_description,
-            provider=CONF.load_balancer.provider,
-            name=lb_name,
-            vip_network_id=self.lb_member_vip_net[const.ID])
+            **lb_kwargs)
+
         self.addCleanup(
             self.mem_lb_client.cleanup_loadbalancer,
             lb[const.ID])
@@ -329,13 +364,26 @@
 
         lb_name = data_utils.rand_name("lb_member_lb3-list")
         lb_description = data_utils.rand_name('C')
+        lb_admin_state_up = False
+        lb_provider = CONF.load_balancer.provider
+        lb_vip_network_id = self.lb_member_vip_net[const.ID]
+
+        lb_kwargs = {
+            const.ADMIN_STATE_UP: lb_admin_state_up,
+            const.DESCRIPTION: lb_description,
+            const.PROVIDER: lb_provider,
+            const.NAME: lb_name,
+            const.VIP_NETWORK_ID: lb_vip_network_id,
+        }
+
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            lb_tags = ["English", "Project_management",
+                       "Communication", "Creativity"]
+            lb_kwargs.update({const.TAGS: lb_tags})
 
         lb = self.mem_lb_client.create_loadbalancer(
-            admin_state_up=False,
-            description=lb_description,
-            provider=CONF.load_balancer.provider,
-            name=lb_name,
-            vip_network_id=self.lb_member_vip_net[const.ID])
+            **lb_kwargs)
+
         self.addCleanup(
             self.mem_lb_client.cleanup_loadbalancer,
             lb[const.ID])
@@ -442,6 +490,28 @@
         self.assertEqual(lb2[const.DESCRIPTION], lbs[1][const.DESCRIPTION])
         self.assertEqual(lb1[const.DESCRIPTION], lbs[0][const.DESCRIPTION])
 
+        # Creating a list of 3 LBs, each one contains different tags
+        if self.mem_lb_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_lbs = [lb1, lb2, lb3]
+            test_list = []
+            for lb in list_of_lbs:
+
+                # If tags "English" and "Creativity" are in the LB's tags
+                # and "Spanish" is not, add the LB to the list
+                if "English" in lb[const.TAGS] and "Creativity" in (
+                    lb[const.TAGS]) and "Spanish" not in (
+                        lb[const.TAGS]):
+                    test_list.append(lb[const.NAME])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                test_list, [lb1[const.NAME], lb3[const.NAME]])
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in lb[const.TAGS]
+                                     for lb in list_of_lbs]))
+
     @decorators.idempotent_id('826ae612-8717-4c64-a8a7-cb9570a85870')
     def test_load_balancer_show(self):
         """Tests load balancer show API.
@@ -561,6 +631,13 @@
                      # vip_qos_policy_id=lb_qos_policy_id)
                      const.NAME: lb_name}
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            lb_tags = ["Hello", "World"]
+            lb_kwargs.update({
+                const.TAGS: lb_tags
+            })
+
         self._setup_lb_network_kwargs(lb_kwargs, 4, use_fixed_ip=True)
 
         lb = self.mem_lb_client.create_loadbalancer(**lb_kwargs)
@@ -594,6 +671,10 @@
             self.assertEqual(lb_kwargs[const.VIP_SUBNET_ID],
                              lb[const.VIP_SUBNET_ID])
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(lb_kwargs[const.TAGS], lb[const.TAGS])
+
         new_name = data_utils.rand_name("lb_member_lb1-update")
         new_description = data_utils.arbitrary_string(size=255,
                                                       base_text='new')
@@ -624,13 +705,26 @@
         self.assertEqual(const.ACTIVE, lb_check[const.PROVISIONING_STATUS])
         self.assertFalse(lb_check[const.ADMIN_STATE_UP])
 
-        lb = self.mem_lb_client.update_loadbalancer(
-            lb[const.ID],
-            admin_state_up=True,
-            description=new_description,
+        admin_state_up = True
+
+        lb_update_kwargs = {
+            # const.ID: lb[const.ID],
+            const.ADMIN_STATE_UP: admin_state_up,
+            const.DESCRIPTION: new_description,
             # TODO(johnsom) Add QoS
             # vip_qos_policy_id=lb_qos_policy_id)
-            name=new_name)
+            const.NAME: new_name
+        }
+
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.5'):
+            new_tags = ["Hola", "Mundo"]
+            lb_update_kwargs.update({
+                const.TAGS: new_tags
+            })
+
+        lb = self.mem_lb_client.update_loadbalancer(
+            lb[const.ID], **lb_update_kwargs)
 
         lb = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
                                      lb[const.ID], const.PROVISIONING_STATUS,
@@ -641,6 +735,7 @@
         self.assertTrue(lb[const.ADMIN_STATE_UP])
         self.assertEqual(new_description, lb[const.DESCRIPTION])
         self.assertEqual(new_name, lb[const.NAME])
+        self.assertCountEqual(lb_update_kwargs[const.TAGS], lb[const.TAGS])
         # TODO(johnsom) Add QoS
 
         # Attempt to clean up so that one full test run doesn't start 10+
diff --git a/octavia_tempest_plugin/tests/api/v2/test_member.py b/octavia_tempest_plugin/tests/api/v2/test_member.py
index a279495..583f93e 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_member.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_member.py
@@ -153,6 +153,13 @@
                 const.BACKUP: False,
             })
 
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            member_tags = ["hello", "world"]
+            member_kwargs.update({
+                const.TAGS: member_tags
+            })
+
         if self.lb_member_vip_subnet:
             member_kwargs[const.SUBNET_ID] = self.lb_member_vip_subnet[
                 const.ID]
@@ -197,6 +204,11 @@
                 self.api_version, '2.1'):
             equal_items.append(const.BACKUP)
 
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(member_kwargs[const.TAGS],
+                                  member[const.TAGS])
+
         if monitor:
             equal_items += [const.MONITOR_ADDRESS, const.MONITOR_PORT]
         if const.SUBNET_ID in member_kwargs:
@@ -247,6 +259,13 @@
             const.ADDRESS: '192.0.2.1',
             const.PROTOCOL_PORT: 101,
         }
+
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            member1_tags = ["English", "Mathematics",
+                            "Marketing", "Creativity"]
+            member1_kwargs.update({const.TAGS: member1_tags})
+
         member1 = self.mem_member_client.create_member(
             **member1_kwargs)
         self.addCleanup(
@@ -278,6 +297,13 @@
             const.ADDRESS: '192.0.2.1',
             const.PROTOCOL_PORT: 100,
         }
+
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            member2_tags = ["English", "Spanish",
+                            "Soft_skills", "Creativity"]
+            member2_kwargs.update({const.TAGS: member2_tags})
+
         member2 = self.mem_member_client.create_member(
             **member2_kwargs)
         self.addCleanup(
@@ -309,6 +335,13 @@
             const.ADDRESS: '192.0.2.1',
             const.PROTOCOL_PORT: 102,
         }
+
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            member3_tags = ["English", "Project_management",
+                            "Communication", "Creativity"]
+            member3_kwargs.update({const.TAGS: member3_tags})
+
         member3 = self.mem_member_client.create_member(
             **member3_kwargs)
         self.addCleanup(
@@ -433,6 +466,28 @@
         self.assertEqual(member1[const.PROTOCOL_PORT],
                          members[0][const.PROTOCOL_PORT])
 
+        # Creating a list of 3 members, each one contains different tags
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_members = [member1, member2, member3]
+            test_list = []
+            for member in list_of_members:
+
+                # If tags "English" and "Creativity" are in the member's tags
+                # and "Spanish" is not, add the member to the list
+                if "English" in member[const.TAGS] and "Creativity" in (
+                    member[const.TAGS]) and "Spanish" not in (
+                        member[const.TAGS]):
+                    test_list.append(member[const.NAME])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                test_list, [member1[const.NAME], member3[const.NAME]])
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in member[const.TAGS]
+                                     for member in list_of_members]))
+
     @decorators.idempotent_id('7674ae04-7e92-44ef-9adf-40718d7ec705')
     def test_member_show(self):
         """Tests member show API.
@@ -563,6 +618,13 @@
                 const.BACKUP: False,
             })
 
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            member_tags = ["Hello", "World"]
+            member_kwargs.update({
+                const.TAGS: member_tags
+            })
+
         if self.lb_member_vip_subnet:
             member_kwargs[const.SUBNET_ID] = self.lb_member_vip_subnet[
                 const.ID]
@@ -609,6 +671,11 @@
                 self.api_version, '2.1'):
             equal_items.append(const.BACKUP)
 
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(member_kwargs[const.TAGS],
+                                  member[const.TAGS])
+
         if monitor:
             equal_items += [const.MONITOR_ADDRESS, const.MONITOR_PORT]
         if const.SUBNET_ID in member_kwargs:
@@ -672,6 +739,14 @@
             member_update_kwargs.update({
                 const.BACKUP: not member[const.BACKUP]
             })
+
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            new_tags = ["Hola", "Mundo"]
+            member_update_kwargs.update({
+                const.TAGS: new_tags
+            })
+
         if monitor:
             member_update_kwargs[const.MONITOR_ADDRESS] = '192.0.2.3'
             member_update_kwargs[const.MONITOR_PORT] = member[
@@ -709,6 +784,11 @@
                 self.api_version, '2.1'):
             equal_items.append(const.BACKUP)
 
+        if self.mem_member_client.is_version_supported(
+                self.api_version, '2.5'):
+            self.assertCountEqual(member_update_kwargs[const.TAGS],
+                                  member[const.TAGS])
+
         if monitor:
             equal_items += [const.MONITOR_ADDRESS, const.MONITOR_PORT]
         for item in equal_items:
diff --git a/octavia_tempest_plugin/tests/api/v2/test_pool.py b/octavia_tempest_plugin/tests/api/v2/test_pool.py
index fd3053d..88e0119 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_pool.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_pool.py
@@ -104,6 +104,13 @@
             const.PROTOCOL: self.protocol,
             const.LB_ALGORITHM: self.lb_algorithm,
         }
+
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            pool_tags = ["Hello", "World"]
+            pool_kwargs.update({
+                const.TAGS: pool_tags
+            })
+
         if self.lb_feature_enabled.session_persistence_enabled:
             pool_kwargs[const.SESSION_PERSISTENCE] = {
                 const.TYPE: const.SESSION_PERSISTENCE_APP_COOKIE,
@@ -179,6 +186,9 @@
             self.assertEqual(pool_sp_cookie_name,
                              pool[const.SESSION_PERSISTENCE][
                                  const.COOKIE_NAME])
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            self.assertCountEqual(pool_kwargs[const.TAGS],
+                                  pool[const.TAGS])
 
     @decorators.idempotent_id('6959a32e-fb34-4f3e-be68-8880c6450016')
     def test_pool_list(self):
@@ -222,6 +232,13 @@
             const.LB_ALGORITHM: self.lb_algorithm,
             const.LOADBALANCER_ID: lb_id,
         }
+
+        if self.mem_pool_client.is_version_supported(
+                self.api_version, '2.5'):
+            pool1_tags = ["English", "Mathematics",
+                          "Marketing", "Creativity"]
+            pool1_kwargs.update({const.TAGS: pool1_tags})
+
         if self.lb_feature_enabled.session_persistence_enabled:
             pool1_kwargs[const.SESSION_PERSISTENCE] = {
                 const.TYPE: const.SESSION_PERSISTENCE_APP_COOKIE,
@@ -260,6 +277,13 @@
             const.LB_ALGORITHM: self.lb_algorithm,
             const.LOADBALANCER_ID: lb_id,
         }
+
+        if self.mem_pool_client.is_version_supported(
+                self.api_version, '2.5'):
+            pool2_tags = ["English", "Spanish",
+                          "Soft_skills", "Creativity"]
+            pool2_kwargs.update({const.TAGS: pool2_tags})
+
         if self.lb_feature_enabled.session_persistence_enabled:
             pool2_kwargs[const.SESSION_PERSISTENCE] = {
                 const.TYPE: const.SESSION_PERSISTENCE_APP_COOKIE,
@@ -298,6 +322,13 @@
             # No session persistence, just so there's one test for that
             const.LOADBALANCER_ID: lb_id,
         }
+
+        if self.mem_pool_client.is_version_supported(
+                self.api_version, '2.5'):
+            pool3_tags = ["English", "Project_management",
+                          "Communication", "Creativity"]
+            pool3_kwargs.update({const.TAGS: pool3_tags})
+
         pool3 = self.mem_pool_client.create_pool(
             **pool3_kwargs)
         self.addCleanup(
@@ -418,6 +449,28 @@
         self.assertEqual(pool1[const.DESCRIPTION],
                          pools[0][const.DESCRIPTION])
 
+        # Creating a list of 3 pools, each one contains different tags
+        if self.mem_pool_client.is_version_supported(
+                self.api_version, '2.5'):
+            list_of_pools = [pool1, pool2, pool3]
+            test_list = []
+            for pool in list_of_pools:
+
+                # If tags "English" and "Creativity" are in the pool's tags
+                # and "Spanish" is not, add the pool to the list
+                if "English" in pool[const.TAGS] and "Creativity" in (
+                    pool[const.TAGS]) and "Spanish" not in (
+                        pool[const.TAGS]):
+                    test_list.append(pool[const.NAME])
+
+            # Tests if only the first and the third ones have those tags
+            self.assertEqual(
+                test_list, [pool1[const.NAME], pool3[const.NAME]])
+
+            # Tests that filtering by an empty tag will return an empty list
+            self.assertTrue(not any(["" in pool[const.TAGS]
+                                     for pool in list_of_pools]))
+
     @decorators.idempotent_id('b7932438-1aea-4175-a50c-984fee1c0cad')
     def test_pool_show(self):
         """Tests pool show API.
@@ -535,6 +588,13 @@
             const.LB_ALGORITHM: self.lb_algorithm,
             const.LOADBALANCER_ID: self.lb_id,
         }
+
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            pool_tags = ["Hello", "World"]
+            pool_kwargs.update({
+                const.TAGS: pool_tags
+            })
+
         if self.lb_feature_enabled.session_persistence_enabled:
             pool_kwargs[const.SESSION_PERSISTENCE] = {
                 const.TYPE: const.SESSION_PERSISTENCE_APP_COOKIE,
@@ -610,6 +670,10 @@
                          pool_check[const.PROVISIONING_STATUS])
         self.assertFalse(pool_check[const.ADMIN_STATE_UP])
 
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            self.assertCountEqual(pool_kwargs[const.TAGS],
+                                  pool[const.TAGS])
+
         new_name = data_utils.rand_name("lb_member_pool1-UPDATED")
         new_description = data_utils.arbitrary_string(size=255,
                                                       base_text='new')
@@ -619,6 +683,13 @@
             const.ADMIN_STATE_UP: True,
             const.LB_ALGORITHM: self.lb_algorithm,
         }
+
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            new_tags = ["Hola", "Mundo"]
+            pool_update_kwargs.update({
+                const.TAGS: new_tags
+            })
+
         if self.lb_feature_enabled.session_persistence_enabled:
             pool_update_kwargs[const.SESSION_PERSISTENCE] = {
                 const.TYPE: const.SESSION_PERSISTENCE_HTTP_COOKIE,
@@ -650,6 +721,10 @@
             self.assertIsNone(
                 pool[const.SESSION_PERSISTENCE].get(const.COOKIE_NAME))
 
+        if self.mem_lb_client.is_version_supported(self.api_version, '2.5'):
+            self.assertCountEqual(pool_update_kwargs[const.TAGS],
+                                  pool[const.TAGS])
+
         # Also test removing a Session Persistence
         if self.lb_feature_enabled.session_persistence_enabled:
             pool_update_kwargs = {
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_ipv6_traffic_ops.py b/octavia_tempest_plugin/tests/scenario/v2/test_ipv6_traffic_ops.py
index b37ab57..fbfe930 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_ipv6_traffic_ops.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_ipv6_traffic_ops.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import requests
+
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -290,3 +292,144 @@
                                      'in Octavia API version 2.1 or newer')
 
         self._test_ipv6_vip_ipv6_members_traffic(const.UDP, 8080)
+
+    @decorators.idempotent_id('84b23f68-4bc3-49e5-8372-60c25fe69613')
+    def test_listener_with_allowed_cidrs(self):
+        """Tests traffic through a loadbalancer with allowed CIDRs set.
+
+        * Set up listener with allowed CIDRS (allow all) on a loadbalancer.
+        * Set up pool on a loadbalancer
+        * Set up members on a loadbalancer.
+        * Test traffic to ensure it is balanced properly.
+        * Update allowed CIDRs to restrict traffic to a small subnet.
+        * Assert loadbalancer does not respond to client requests.
+        """
+
+        if not self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            raise self.skipException('Allowed CIDRS in listeners is only '
+                                     'available on Octavia API version 2.12 '
+                                     'or newer.')
+
+        listener_name = data_utils.rand_name("lb_member_listener2_cidrs")
+        listener_port = 8080
+        listener_kwargs = {
+            const.NAME: listener_name,
+            const.PROTOCOL: self.protocol,
+            const.PROTOCOL_PORT: listener_port,
+            const.LOADBALANCER_ID: self.lb_id,
+            const.ALLOWED_CIDRS: ['::/0']
+        }
+        listener = self.mem_listener_client.create_listener(**listener_kwargs)
+        listener_id = listener[const.ID]
+        self.addCleanup(
+            self.mem_listener_client.cleanup_listener,
+            listener_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        pool_name = data_utils.rand_name("lb_member_pool3_cidrs")
+        pool_kwargs = {
+            const.NAME: pool_name,
+            const.PROTOCOL: self.protocol,
+            const.LB_ALGORITHM: self.lb_algorithm,
+            const.LISTENER_ID: listener_id,
+        }
+        pool = self.mem_pool_client.create_pool(**pool_kwargs)
+        pool_id = pool[const.ID]
+        self.addCleanup(
+            self.mem_pool_client.cleanup_pool,
+            pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        # Set up Member 1 for Webserver 1
+        member1_name = data_utils.rand_name("lb_member_member1-cidrs-traffic")
+        member1_kwargs = {
+            const.POOL_ID: pool_id,
+            const.NAME: member1_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver1_ip,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_1_subnet:
+            member1_kwargs[const.SUBNET_ID] = self.lb_member_1_subnet[const.ID]
+
+        member1 = self.mem_member_client.create_member(
+            **member1_kwargs)
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member1[const.ID], pool_id=pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        # Set up Member 2 for Webserver 2
+        member2_name = data_utils.rand_name("lb_member_member2-cidrs-traffic")
+        member2_kwargs = {
+            const.POOL_ID: pool_id,
+            const.NAME: member2_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver2_ip,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_2_subnet:
+            member2_kwargs[const.SUBNET_ID] = self.lb_member_2_subnet[const.ID]
+
+        member2 = self.mem_member_client.create_member(**member2_kwargs)
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member2[const.ID], pool_id=pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        # Send some traffic
+        self.check_members_balanced(
+            self.lb_vip_address, protocol_port=listener_port)
+
+        listener_kwargs = {
+            const.LISTENER_ID: listener_id,
+            const.ALLOWED_CIDRS: ['2001:db8:a0b:12f0::/128']
+        }
+        self.mem_listener_client.update_listener(**listener_kwargs)
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        url_for_vip = 'http://[{}]:{}/'.format(self.lb_vip_address,
+                                               listener_port)
+
+        # NOTE: Before we start with the consistent response check, we must
+        # wait until Neutron completes the SG update.
+        # See https://bugs.launchpad.net/neutron/+bug/1866353.
+        def expect_conn_error(url):
+            try:
+                requests.Session().get(url)
+            except requests.exceptions.ConnectionError:
+                return True
+            return False
+
+        waiters.wait_until_true(expect_conn_error, url=url_for_vip)
+
+        # Assert that the server is consistently unavailable
+        self.assertConsistentResponse(
+            (None, None), url_for_vip, repeat=3, conn_error=True)
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_listener.py b/octavia_tempest_plugin/tests/scenario/v2/test_listener.py
index bb3df64..c056bd0 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_listener.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_listener.py
@@ -96,6 +96,10 @@
                                 CONF.load_balancer.build_interval,
                                 CONF.load_balancer.build_timeout)
 
+        cls.allowed_cidrs = ['192.0.1.0/24']
+        if CONF.load_balancer.test_with_ipv6:
+            cls.allowed_cidrs = ['2001:db8:a0b:12f0::/64']
+
     @decorators.idempotent_id('4a874014-b7d1-49a4-ac9a-2400b3434700')
     def test_listener_CRUD(self):
         """Tests listener create, read, update, delete
@@ -134,6 +138,9 @@
                 const.TIMEOUT_MEMBER_DATA: 1000,
                 const.TIMEOUT_TCP_INSPECT: 50,
             })
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            listener_kwargs.update({const.ALLOWED_CIDRS: self.allowed_cidrs})
 
         listener = self.mem_listener_client.create_listener(**listener_kwargs)
         self.addCleanup(
@@ -176,6 +183,9 @@
             self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_CONNECT])
             self.assertEqual(1000, listener[const.TIMEOUT_MEMBER_DATA])
             self.assertEqual(50, listener[const.TIMEOUT_TCP_INSPECT])
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            self.assertEqual(self.allowed_cidrs, listener[const.ALLOWED_CIDRS])
 
         # Listener update
         new_name = data_utils.rand_name("lb_member_listener1-update")
@@ -204,6 +214,13 @@
                 const.TIMEOUT_TCP_INSPECT: 100,
             })
 
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            new_cidrs = ['192.0.2.0/24']
+            if CONF.load_balancer.test_with_ipv6:
+                new_cidrs = ['2001:db8::/64']
+            listener_update_kwargs.update({const.ALLOWED_CIDRS: new_cidrs})
+
         listener = self.mem_listener_client.update_listener(
             listener[const.ID], **listener_update_kwargs)
 
@@ -249,6 +266,12 @@
             self.assertEqual(2000, listener[const.TIMEOUT_MEMBER_CONNECT])
             self.assertEqual(2000, listener[const.TIMEOUT_MEMBER_DATA])
             self.assertEqual(100, listener[const.TIMEOUT_TCP_INSPECT])
+        if self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            expected_cidrs = ['192.0.2.0/24']
+            if CONF.load_balancer.test_with_ipv6:
+                expected_cidrs = ['2001:db8::/64']
+            self.assertEqual(expected_cidrs, listener[const.ALLOWED_CIDRS])
 
         # Listener delete
         waiters.wait_for_status(
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py b/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py
index 83049c7..7dd4a29 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py
@@ -12,15 +12,22 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import datetime
+import ipaddress
+import requests
+import shlex
 import testtools
+import time
 
 from oslo_log import log as logging
+from oslo_utils import uuidutils
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
 from octavia_tempest_plugin.common import constants as const
 from octavia_tempest_plugin.tests import test_base
+from octavia_tempest_plugin.tests import validators
 from octavia_tempest_plugin.tests import waiters
 
 CONF = config.CONF
@@ -800,3 +807,280 @@
                                      'in Octavia API version 2.1 or newer')
 
         self._test_mixed_ipv4_ipv6_members_traffic(const.UDP, 8080)
+
+    @testtools.skipIf(CONF.load_balancer.test_with_noop,
+                      'Log offload tests will not work in noop mode.')
+    @testtools.skipUnless(
+        CONF.loadbalancer_feature_enabled.log_offload_enabled,
+        'Skipping log offload tests because tempest configuration '
+        '[loadbalancer-feature-enabled] log_offload_enabled is False.')
+    @testtools.skipUnless(
+        CONF.loadbalancer_feature_enabled.l7_protocol_enabled,
+        'Log offload tests require l7_protocol_enabled.')
+    @decorators.idempotent_id('571dddd9-f5bd-404e-a799-9df7ac9e2fa9')
+    def test_tenant_flow_log(self):
+        """Tests tenant flow log offloading
+
+        * Set up a member on a loadbalancer.
+        * Sends a request to the load balancer.
+        * Validates the flow log record for the request.
+        """
+        listener_name = data_utils.rand_name("lb_member_listener1_tenant_flow")
+        protocol_port = '8123'
+        listener_kwargs = {
+            const.NAME: listener_name,
+            const.PROTOCOL: const.HTTP,
+            const.PROTOCOL_PORT: protocol_port,
+            const.LOADBALANCER_ID: self.lb_id,
+        }
+        listener = self.mem_listener_client.create_listener(**listener_kwargs)
+        listener_id = listener[const.ID]
+        self.addCleanup(
+            self.mem_listener_client.cleanup_listener,
+            listener_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        pool_name = data_utils.rand_name("lb_member_pool1_tenant_flow")
+        pool_kwargs = {
+            const.NAME: pool_name,
+            const.PROTOCOL: const.HTTP,
+            const.LB_ALGORITHM: const.LB_ALGORITHM_SOURCE_IP,
+            const.LISTENER_ID: listener_id,
+        }
+        pool = self.mem_pool_client.create_pool(**pool_kwargs)
+        pool_id = pool[const.ID]
+        self.addCleanup(
+            self.mem_pool_client.cleanup_pool,
+            pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        # Set up Member for Webserver 1
+        member_name = data_utils.rand_name("lb_member_member-tenant_flow")
+        member_kwargs = {
+            const.POOL_ID: pool_id,
+            const.NAME: member_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver1_ip,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_1_subnet:
+            member_kwargs[const.SUBNET_ID] = self.lb_member_1_subnet[const.ID]
+
+        member = self.mem_member_client.create_member(**member_kwargs)
+        member_id = member[const.ID]
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member[const.ID], pool_id=pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        project_id = self.os_roles_lb_member.credentials.project_id
+        unique_request_id = uuidutils.generate_uuid()
+        LOG.info('Tenant flow logging unique request ID is: %s',
+                 unique_request_id)
+
+        # Make the request
+        URL = 'http://{0}:{1}/{2}'.format(
+            self.lb_vip_address, protocol_port, unique_request_id)
+        validators.validate_URL_response(URL, expected_status_code=200)
+
+        # We need to give the log subsystem time to commit the log
+        time.sleep(CONF.load_balancer.check_interval)
+
+        # Get the tenant log entry
+        log_line = None
+        with open(CONF.load_balancer.tenant_flow_log_file) as f:
+            for line in f:
+                if unique_request_id in line:
+                    log_line = line
+                    break
+        self.assertIsNotNone(
+            log_line, 'Tenant log entry was not found in {0}.'.format(
+                CONF.load_balancer.tenant_flow_log_file))
+
+        # Remove the syslog prefix
+        log_line = log_line[log_line.index(project_id):]
+
+        # Split the line into the log format fields
+        fields = shlex.split(log_line)
+
+        # Validate the fields
+        self.assertEqual(project_id, fields[0])  # project_id
+        self.assertEqual(self.lb_id, fields[1])  # loadbalancer_id
+        self.assertEqual(listener_id, fields[2])  # listener_id
+        ipaddress.ip_address(fields[3])  # client_ip
+        self.assertGreaterEqual(int(fields[4]), 0)  # client_port
+        self.assertLessEqual(int(fields[4]), 65535)  # client_port
+        datetime.datetime.strptime(fields[5],
+                                   '%d/%b/%Y:%H:%M:%S.%f')  # date_time
+        request_string = 'GET /{0} HTTP/1.1'.format(unique_request_id)
+        self.assertEqual(request_string, fields[6])  # request_string
+        self.assertEqual('200', fields[7])  # http_status
+        self.assertTrue(fields[8].isdigit())  # bytes_read
+        self.assertTrue(fields[9].isdigit())  # bytes_uploaded
+        self.assertEqual('-', fields[10])  # client_cert_verify
+        self.assertEqual("", fields[11])  # cert_dn
+        pool_string = '{0}:{1}'.format(pool_id, listener_id)
+        self.assertEqual(pool_string, fields[12])  # pool_id
+        self.assertEqual(member_id, fields[13])  # member_id
+        self.assertTrue(fields[14].isdigit())  # processing_time
+        self.assertEqual('----', fields[15])  # term_state
+
+    @testtools.skipIf(CONF.load_balancer.test_with_noop,
+                      'Traffic tests will not work in noop mode.')
+    @decorators.idempotent_id('13b0f2de-9934-457b-8be0-f1bffc6915a0')
+    def test_listener_with_allowed_cidrs(self):
+        """Tests traffic through a loadbalancer with allowed CIDRs set.
+
+        * Set up listener with allowed CIDRS (allow all) on a loadbalancer.
+        * Set up pool on a loadbalancer
+        * Set up members on a loadbalancer.
+        * Test traffic to ensure it is balanced properly.
+        * Update allowed CIDRs to restrict traffic to a small subnet.
+        * Assert loadbalancer does not respond to client requests.
+        """
+
+        if not self.mem_listener_client.is_version_supported(
+                self.api_version, '2.12'):
+            raise self.skipException('Allowed CIDRS in listeners is only '
+                                     'available on Octavia API version 2.12 '
+                                     'or newer.')
+
+        listener_name = data_utils.rand_name("lb_member_listener2_cidrs")
+        listener_port = 8080
+        listener_kwargs = {
+            const.NAME: listener_name,
+            const.PROTOCOL: self.protocol,
+            const.PROTOCOL_PORT: listener_port,
+            const.LOADBALANCER_ID: self.lb_id,
+            const.ALLOWED_CIDRS: ['0.0.0.0/0']
+        }
+        listener = self.mem_listener_client.create_listener(**listener_kwargs)
+        listener_id = listener[const.ID]
+        self.addCleanup(
+            self.mem_listener_client.cleanup_listener,
+            listener_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        pool_name = data_utils.rand_name("lb_member_pool3_cidrs")
+        pool_kwargs = {
+            const.NAME: pool_name,
+            const.PROTOCOL: self.protocol,
+            const.LB_ALGORITHM: self.lb_algorithm,
+            const.LISTENER_ID: listener_id,
+        }
+        pool = self.mem_pool_client.create_pool(**pool_kwargs)
+        pool_id = pool[const.ID]
+        self.addCleanup(
+            self.mem_pool_client.cleanup_pool,
+            pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        # Set up Member 1 for Webserver 1
+        member1_name = data_utils.rand_name("lb_member_member1-cidrs-traffic")
+        member1_kwargs = {
+            const.POOL_ID: pool_id,
+            const.NAME: member1_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver1_ip,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_1_subnet:
+            member1_kwargs[const.SUBNET_ID] = self.lb_member_1_subnet[const.ID]
+
+        member1 = self.mem_member_client.create_member(
+            **member1_kwargs)
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member1[const.ID], pool_id=pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        # Set up Member 2 for Webserver 2
+        member2_name = data_utils.rand_name("lb_member_member2-cidrs-traffic")
+        member2_kwargs = {
+            const.POOL_ID: pool_id,
+            const.NAME: member2_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver2_ip,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_2_subnet:
+            member2_kwargs[const.SUBNET_ID] = self.lb_member_2_subnet[const.ID]
+
+        member2 = self.mem_member_client.create_member(**member2_kwargs)
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member2[const.ID], pool_id=pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        # Send some traffic
+        self.check_members_balanced(
+            self.lb_vip_address, protocol_port=listener_port)
+
+        listener_kwargs = {
+            const.LISTENER_ID: listener_id,
+            const.ALLOWED_CIDRS: ['192.0.1.0/32']
+        }
+        self.mem_listener_client.update_listener(**listener_kwargs)
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.build_interval,
+                                CONF.load_balancer.build_timeout)
+
+        url_for_vip = 'http://{}:{}/'.format(
+            self.lb_vip_address, listener_port)
+
+        # NOTE: Before we start with the consistent response check, we must
+        # wait until Neutron completes the SG update.
+        # See https://bugs.launchpad.net/neutron/+bug/1866353.
+        def expect_conn_error(url):
+            try:
+                requests.Session().get(url)
+            except requests.exceptions.ConnectionError:
+                return True
+            return False
+
+        waiters.wait_until_true(expect_conn_error, url=url_for_vip)
+
+        # Assert that the server is consistently unavailable
+        self.assertConsistentResponse(
+            (None, None), url_for_vip, repeat=3, conn_error=True)
diff --git a/octavia_tempest_plugin/tests/test_base.py b/octavia_tempest_plugin/tests/test_base.py
index 741bb1c..bd1a225 100644
--- a/octavia_tempest_plugin/tests/test_base.py
+++ b/octavia_tempest_plugin/tests/test_base.py
@@ -503,6 +503,8 @@
             if CONF.load_balancer.test_with_noop:
                 lb_kwargs[const.VIP_NETWORK_ID] = (
                     cls.lb_member_vip_net[const.ID])
+                if ip_version == 6:
+                    lb_kwargs[const.VIP_ADDRESS] = lb_vip_address
         else:
             lb_kwargs[const.VIP_NETWORK_ID] = cls.lb_member_vip_net[const.ID]
             lb_kwargs[const.VIP_SUBNET_ID] = None
@@ -1099,7 +1101,8 @@
             protocol_port=protocol_port)
 
     def assertConsistentResponse(self, response, url, method='GET', repeat=10,
-                                 redirect=False, timeout=2, **kwargs):
+                                 redirect=False, timeout=2,
+                                 conn_error=False, **kwargs):
         """Assert that a request to URL gets the expected response.
 
         :param response: Expected response in format (status_code, content).
@@ -1112,6 +1115,7 @@
         :param redirect: Is the request a redirect? If true, assume the passed
                          content should be the next URL in the chain.
         :param timeout: Optional seconds to wait for the server to send data.
+        :param conn_error: Optional Expect a connection error?
 
         :return: boolean success status
 
@@ -1121,6 +1125,13 @@
         response_code, response_content = response
 
         for i in range(0, repeat):
+            if conn_error:
+                self.assertRaises(
+                    requests.exceptions.ConnectionError, session.request,
+                    method, url, allow_redirects=not redirect, timeout=timeout,
+                    **kwargs)
+                continue
+
             req = session.request(method, url, allow_redirects=not redirect,
                                   timeout=timeout, **kwargs)
             if response_code:
diff --git a/octavia_tempest_plugin/tests/waiters.py b/octavia_tempest_plugin/tests/waiters.py
index eb7410a..e0d9d2d 100644
--- a/octavia_tempest_plugin/tests/waiters.py
+++ b/octavia_tempest_plugin/tests/waiters.py
@@ -210,3 +210,26 @@
                            timeout=check_timeout))
             raise exceptions.TimeoutException(message)
         time.sleep(check_interval)
+
+
+def wait_until_true(func, timeout=60, sleep=1, **kwargs):
+    """Wait until callable predicate is evaluated as True
+
+    :param func: Callable deciding whether waiting should continue.
+    :param timeout: Timeout in seconds how long should function wait.
+    :param sleep: Polling interval for results in seconds.
+    """
+    start = int(time.time())
+    while True:
+        try:
+            ret = func(**kwargs)
+            if ret:
+                return
+        except Exception as e:
+            LOG.error(e)
+
+        if int(time.time()) - start >= timeout:
+            message = "Timed out after {timeout} seconds waiting".format(
+                timeout=timeout)
+            raise exceptions.TimeoutException(message)
+        time.sleep(sleep)
diff --git a/releasenotes/notes/Add-amphora-admin-log-offloading-012aca8151ebbc54.yaml b/releasenotes/notes/Add-amphora-admin-log-offloading-012aca8151ebbc54.yaml
new file mode 100644
index 0000000..9099bc6
--- /dev/null
+++ b/releasenotes/notes/Add-amphora-admin-log-offloading-012aca8151ebbc54.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add an optional amphora admin log offloading scenario test.
diff --git a/releasenotes/notes/add-tags-api-tests-1130aab82bb0f7f2.yaml b/releasenotes/notes/add-tags-api-tests-1130aab82bb0f7f2.yaml
new file mode 100644
index 0000000..513a63f
--- /dev/null
+++ b/releasenotes/notes/add-tags-api-tests-1130aab82bb0f7f2.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    API tests for tags were added.
diff --git a/releasenotes/notes/add-tenant-flow-log-offload-scenario-test-39bf9bf51f70d7bb.yaml b/releasenotes/notes/add-tenant-flow-log-offload-scenario-test-39bf9bf51f70d7bb.yaml
new file mode 100644
index 0000000..6a49c97
--- /dev/null
+++ b/releasenotes/notes/add-tenant-flow-log-offload-scenario-test-39bf9bf51f70d7bb.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Adds an optional tenant flow log offload scenario test.
diff --git a/requirements.txt b/requirements.txt
index b30b450..e5b93b8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,4 +16,5 @@
 six>=1.10.0 # MIT
 tempest>=17.1.0 # Apache-2.0
 tenacity>=4.4.0 # Apache-2.0
+testtools>=2.2.0 # MIT
 keystoneauth1>=3.3.0 # Apache-2.0
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index 9344eb9..511e3c0 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -469,6 +469,10 @@
           $OCTAVIA_CONF:
             api_settings:
               api_v1_enabled: False
+        test-config:
+          "$TEMPEST_CONFIG":
+            loadbalancer-feature-enabled:
+              log_offload_enabled: True
       tempest_concurrency: 2
       tempest_test_regex: ^octavia_tempest_plugin.tests.scenario.v2
       tox_envlist: all
@@ -527,6 +531,12 @@
     required-projects:
       - name: openstack/diskimage-builder
         override-checkout: 2.30.0
+    vars:
+      devstack_local_conf:
+        test-config:
+          "$TEMPEST_CONFIG":
+            loadbalancer-feature-enabled:
+              log_offload_enabled: False
 
 # Legacy jobs for the transition to the act-stdby two node jobs
 - job: