Merge "Adds tests to cover QOS policy"
diff --git a/.zuul.yaml b/.zuul.yaml
index 5701eb4..fe4bd4f 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -2,8 +2,8 @@
     name: patrole-base
     parent: devstack-tempest
     description: |
-       Patrole base job for admin and member roles. This job executes RBAC tests
-       for all the "core" services that Tempest covers, excluding Swift.
+      Patrole base job for admin and member roles. This job executes RBAC tests
+      for all the "core" services that Tempest covers, excluding Swift.
     required-projects:
       - name: openstack/tempest
       - name: openstack/patrole
@@ -25,6 +25,7 @@
       devstack_services:
         tempest: true
         neutron: true
+        neutron-trunk: true
       tempest_concurrency: 2
       tempest_test_regex: (?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)
       tox_envlist: all-plugin
@@ -86,6 +87,11 @@
         RBAC_TEST_ROLE: member
 
 - job:
+    name: patrole-member-rocky
+    parent: patrole-member
+    override-checkout: stable/rocky
+
+- job:
     name: patrole-member-queens
     parent: patrole-member
     override-checkout: stable/queens
@@ -133,16 +139,15 @@
     name: patrole-plugin-base
     parent: patrole-base
     description: |
-         Patrole plugin job for admin and member roles which
-         runs RBAC tests for neutron-tempest-plugin APIs (if the plugin is installed).
+      Patrole plugin job for admin and member roles which
+      runs RBAC tests for neutron-tempest-plugin APIs (if the plugin is installed).
     required-projects:
       - name: openstack/tempest
       - name: openstack/patrole
       - name: openstack/neutron-tempest-plugin
     vars:
       devstack_localrc:
-        TEMPEST_PLUGINS: "'/opt/stack/patrole
-                           /opt/stack/neutron-tempest-plugin'"
+        TEMPEST_PLUGINS: "'/opt/stack/patrole /opt/stack/neutron-tempest-plugin'"
       devstack_plugins:
         neutron: git://git.openstack.org/openstack/neutron.git
         patrole: git://git.openstack.org/openstack/patrole.git
@@ -171,19 +176,33 @@
       tempest_test_regex: (?=.*PluginRbacTest)(^patrole_tempest_plugin\.tests\.api)
 
 - project:
+    templates:
+      - openstack-cover-jobs
+      - openstack-lower-constraints-jobs
+      - openstack-python36-jobs
+      - openstack-python-jobs
+      - openstack-python35-jobs
+      - check-requirements
+      - publish-openstack-docs-pti
+      - release-notes-jobs-python3
     check:
       jobs:
         - patrole-admin
         - patrole-member
+        - patrole-member-rocky
         - patrole-member-queens
         - patrole-member-pike
         - patrole-py35-member
         - patrole-multinode-admin
         - patrole-multinode-member
-        - openstack-tox-lower-constraints
         - patrole-plugin-admin
         - patrole-plugin-member
     gate:
       jobs:
         - patrole-admin
         - patrole-member
+    periodic-stable:
+      jobs:
+        - patrole-member-rocky
+        - patrole-member-queens
+        - patrole-member-pike
diff --git a/README.rst b/README.rst
index 2028536..fdcbc6b 100644
--- a/README.rst
+++ b/README.rst
@@ -36,7 +36,7 @@
 Design Principles
 -----------------
 
-As a `Tempest plugin`_, Patrole borrows some `design principles`_ from Tempest,
+As a `Tempest plugin`_, Patrole borrows some design principles from `Tempest design principles`_,
 but not all, as its testing scope is confined to policies.
 
 * *Stability*. Patrole uses OpenStack public interfaces. Tests in Patrole
@@ -76,7 +76,7 @@
 * *Self-testing*. Patrole should be self-testing.
 
 .. _Tempest plugin: https://docs.openstack.org/tempest/latest/plugin.html
-.. _design principles: https://docs.openstack.org/tempest/latest/overview.html#design-principles
+.. _Tempest design principles: https://docs.openstack.org/tempest/latest/overview.html#design-principles
 .. _policy in code: https://specs.openstack.org/openstack/oslo-specs/specs/newton/policy-in-code.html
 .. _Nova repository: https://github.com/openstack/nova/tree/master/nova/policies
 .. _Keystone repository: https://github.com/openstack/keystone/tree/master/keystone/common/policies
@@ -120,7 +120,7 @@
 Quickstart
 ----------
 To run Patrole, you must first have `Tempest`_ installed and configured
-properly. Please reference Tempest's `Quickstart`_ guide to do so. Follow all
+properly. Please reference `Tempest_quickstart`_ guide to do so. Follow all
 the steps outlined therein. Afterward, proceed with the steps below.
 
 #. You first need to install Patrole. This is done with pip after you check out
@@ -139,7 +139,7 @@
 
 #. Next you must properly configure Patrole, which is relatively
    straightforward. For details on configuring Patrole refer to the
-   :ref:`patrole-configuration`.
+   `Patrole Configuration <https://docs.openstack.org/patrole/latest/configuration.html#patrole-configuration>`_.
 
 #. Once the configuration is done you're now ready to run Patrole. This can
    be done using the `tempest_run`_ command. This can be done by running::
@@ -170,14 +170,14 @@
 
 #. Log information from tests is captured in ``tempest.log`` under the Tempest
    repository. Some Patrole debugging information is captured in that log
-   related to expected test results and :ref:`role-overriding`.
+   related to expected test results and `Role Overriding <https://docs.openstack.org/patrole/latest/framework/rbac_utils.html#role-overriding>`_.
 
    More detailed RBAC testing log output is emitted to ``patrole.log`` under
    the Patrole repository. To configure Patrole's logging, see the
-   :ref:`patrole-configuration` guide.
+   `Patrole Configuration Guide <https://docs.openstack.org/patrole/latest/configuration.html#patrole-configuration>`_.
 
 .. _Tempest: https://github.com/openstack/tempest
-.. _Quickstart: https://docs.openstack.org/tempest/latest/overview.html#quickstart
+.. _Tempest_quickstart: https://docs.openstack.org/tempest/latest/overview.html#quickstart
 .. _tempest_run: https://docs.openstack.org/tempest/latest/run.html
 .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
 .. _ostestr: https://docs.openstack.org/os-testr/latest/
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index bd0068b..01be7d6 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -26,15 +26,20 @@
         iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_attach_policy False
         iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_reserve_policy False
         iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_unreserve_policy False
+
+        # These policies were removed in Stein but are available in Pike.
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
     fi
 
     if [[ ${DEVSTACK_SERIES} == 'queens' ]]; then
         if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
             RBAC_TEST_ROLE="Member"
         fi
+
+        # These policies were removed in Stein but are available in Queens.
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
     fi
 
-    iniset $TEMPEST_CONFIG patrole enable_rbac True
     iniset $TEMPEST_CONFIG patrole rbac_test_role $RBAC_TEST_ROLE
 }
 
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index ebc8a1d..df05eaa 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -24,16 +24,6 @@
                default='admin',
                help="""The current RBAC role against which to run
 Patrole tests."""),
-    cfg.BoolOpt('enable_rbac',
-                default=True,
-                deprecated_for_removal=True,
-                deprecated_reason="""This is a legacy option that was
-meaningful when Patrole existed downstream as a suite of tests inside Tempest.
-Installing the Patrole plugin necessarily means that RBAC tests should be run.
-This option is paradoxical with the Tempest plugin architecture.
-""",
-                deprecated_since='R',
-                help="Enables Patrole RBAC tests."),
     cfg.ListOpt('custom_policy_files',
                 default=['/etc/%s/policy.json'],
                 help="""List of the paths to search for policy files. Each
@@ -160,7 +150,17 @@
                 default=True,
                 help="""Is the Cinder policy
 "volume_extension:volume_actions:unreserve" available in the cloud? This policy
-was changed in a backwards-incompatible way.""")
+was changed in a backwards-incompatible way."""),
+    # *** Include feature flags for groups of policies below. ***
+    # Best practice is to capture new policies, removed policies, renamed
+    # policies in a group, per release.
+    #
+    # TODO(felipemonteiro): Remove these feature flags once Stein is EOL.
+    cfg.BoolOpt('removed_nova_policies_stein',
+                default=True,
+                help="""Are the Nova API extension policies available in the
+cloud (e.g. os_compute_api:os-extended-availability-zone)? These policies were
+removed in Stein because Nova API extension concept was removed in Pike."""),
 ]
 
 
diff --git a/patrole_tempest_plugin/rbac_exceptions.py b/patrole_tempest_plugin/rbac_exceptions.py
index 809a7ed..3958e17 100644
--- a/patrole_tempest_plugin/rbac_exceptions.py
+++ b/patrole_tempest_plugin/rbac_exceptions.py
@@ -16,12 +16,16 @@
 from tempest.lib import exceptions
 
 
-class RbacConflictingPolicies(exceptions.TempestException):
+class BasePatroleException(exceptions.TempestException):
+    message = "An unknown RBAC exception occurred"
+
+
+class RbacConflictingPolicies(BasePatroleException):
     message = ("Conflicting policies preventing this action from being "
                "performed.")
 
 
-class RbacMalformedResponse(exceptions.TempestException):
+class RbacMalformedResponse(BasePatroleException):
     message = ("The response body is missing the expected %(attribute)s due "
                "to policy enforcement failure.")
 
@@ -37,25 +41,25 @@
         super(RbacMalformedResponse, self).__init__(**kwargs)
 
 
-class RbacResourceSetupFailed(exceptions.TempestException):
+class RbacResourceSetupFailed(BasePatroleException):
     message = "RBAC resource setup failed"
 
 
-class RbacOverPermissionException(exceptions.TempestException):
+class RbacOverPermissionException(BasePatroleException):
     """Raised when the expected result is failure but the actual result is
     pass.
     """
     message = "Unauthorized action was allowed to be performed"
 
 
-class RbacUnderPermissionException(exceptions.TempestException):
+class RbacUnderPermissionException(BasePatroleException):
     """Raised when the expected result is pass but the actual result is
     failure.
     """
     message = "Authorized action was not allowed to be performed"
 
 
-class RbacExpectedWrongException(exceptions.TempestException):
+class RbacExpectedWrongException(BasePatroleException):
     """Raised when the expected exception does not match the actual exception
     raised, when both are instances of Forbidden or NotFound, indicating
     the test provides a wrong argument to `expected_error_codes`.
@@ -64,16 +68,30 @@
                "instead. Actual exception: %(exception)s")
 
 
-class RbacInvalidServiceException(exceptions.TempestException):
+class RbacInvalidServiceException(BasePatroleException):
     """Raised when an invalid service is passed to ``rbac_rule_validation``
     decorator.
     """
     message = "Attempted to test an invalid service"
 
 
-class RbacParsingException(exceptions.TempestException):
+class RbacParsingException(BasePatroleException):
     message = "Attempted to test an invalid policy file or action"
 
 
-class RbacInvalidErrorCode(exceptions.TempestException):
+class RbacInvalidErrorCode(BasePatroleException):
     message = "Unsupported error code passed in test"
+
+
+class RbacOverrideRoleException(BasePatroleException):
+    """Raised when override_role is used incorrectly or fails somehow.
+
+    Used for safeguarding against false positives that might occur when the
+    expected exception isn't raised inside the ``override_role`` context.
+    Specifically, when:
+
+    * ``override_role`` isn't called
+    * an exception is raised before ``override_role`` context
+    * an exception is raised after ``override_role`` context
+    """
+    message = "Override role failure or incorrect usage"
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index e6d1e80..a7927fc 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -110,7 +110,7 @@
         c) if both api_action1 and api_action2 fail, then the expected error
            code is the first error seen (404).
 
-        If an error code is missing from the list, it is defaulted to 403.
+        If it is not passed, then it is defaulted to 403.
 
     :param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
         generic check names, whose values are string literals that reference
@@ -180,6 +180,7 @@
             expected_exception, irregular_msg = _get_exception_type(
                 exp_error_code)
 
+            caught_exception = None
             test_status = 'Allowed'
 
             try:
@@ -193,13 +194,16 @@
                     LOG.error(msg)
             except (expected_exception,
                     rbac_exceptions.RbacConflictingPolicies,
-                    rbac_exceptions.RbacMalformedResponse) as e:
+                    rbac_exceptions.RbacMalformedResponse) as actual_exception:
+                caught_exception = actual_exception
                 test_status = 'Denied'
+
                 if irregular_msg:
                     LOG.warning(irregular_msg,
                                 test_func.__name__,
                                 ', '.join(rules),
                                 service)
+
                 if allowed:
                     msg = ("Role %s was not allowed to perform the following "
                            "actions: %s. Expected allowed actions: %s. "
@@ -209,8 +213,10 @@
                                sorted(disallowed_rules)))
                     LOG.error(msg)
                     raise rbac_exceptions.RbacUnderPermissionException(
-                        "%s Exception was: %s" % (msg, e))
+                        "%s Exception was: %s" % (msg, actual_exception))
             except Exception as actual_exception:
+                caught_exception = actual_exception
+
                 if _check_for_expected_mismatch_exception(expected_exception,
                                                           actual_exception):
                     LOG.error('Expected and actual exceptions do not match. '
@@ -249,6 +255,14 @@
                         "Allowed" if allowed else "Denied",
                         test_status)
 
+                # Sanity-check that ``override_role`` was called to eliminate
+                # false-positives and bad test flows resulting from exceptions
+                # getting raised too early, too late or not at all, within
+                # the scope of an RBAC test.
+                _validate_override_role_called(
+                    test_obj,
+                    actual_exception=caught_exception)
+
         return wrapper
     return decorator
 
@@ -389,7 +403,7 @@
         irregular_msg = ("NotFound exception was caught for test %s. Expected "
                          "policies which may have caused the error: %s. The "
                          "service %s throws a 404 instead of a 403, which is "
-                         "irregular.")
+                         "irregular")
     return expected_exception, irregular_msg
 
 
@@ -431,8 +445,63 @@
 
 def _check_for_expected_mismatch_exception(expected_exception,
                                            actual_exception):
+    """Checks that ``expected_exception`` matches ``actual_exception``.
+
+    Since Patrole must handle 403/404 it is important that the expected and
+    actual error codes match.
+
+    :param excepted_exception: Expected exception for test.
+    :param actual_exception: Actual exception raised by test.
+    :returns: True if match, else False.
+    :rtype: boolean
+    """
     permission_exceptions = (lib_exc.Forbidden, lib_exc.NotFound)
     if isinstance(actual_exception, permission_exceptions):
         if not isinstance(actual_exception, expected_exception.__class__):
             return True
     return False
+
+
+def _validate_override_role_called(test_obj, actual_exception):
+    """Validates that :func:`rbac_utils.RbacUtils.override_role` is called
+    during each Patrole test.
+
+    Useful for validating that the expected exception isn't raised too early
+    (before ``override_role`` call) or too late (after ``override_call``) or
+    at all (which is a bad test).
+
+    :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
+    :param actual_exception: Actual exception raised by test.
+    :raises RbacOverrideRoleException: If ``override_role`` isn't called, is
+        called too early, or is called too late.
+    """
+    called = test_obj._validate_override_role_called()
+    base_msg = ('This error is unrelated to RBAC and is due to either '
+                'an API or override role failure. Exception: %s' %
+                actual_exception)
+
+    if not called:
+        if actual_exception is not None:
+            msg = ('Caught exception (%s) but it was raised before the '
+                   '`override_role` context. ' % actual_exception.__class__)
+        else:
+            msg = 'Test missing required `override_role` call. '
+        msg += base_msg
+        LOG.error(msg)
+        raise rbac_exceptions.RbacOverrideRoleException(msg)
+    else:
+        exc_caught_in_ctx = test_obj._validate_override_role_caught_exc()
+        # This block is only executed if ``override_role`` is called. If
+        # an exception is raised and the exception wasn't raised in the
+        # ``override_role`` context and if the exception isn't a valid
+        # exception type (instance of ``BasePatroleException``), then this is
+        # a legitimate error.
+        if (not exc_caught_in_ctx and
+            actual_exception is not None and
+            not isinstance(actual_exception,
+                           rbac_exceptions.BasePatroleException)):
+            msg = ('Caught exception (%s) but it was raised after the '
+                   '`override_role` context. ' % actual_exception.__class__)
+            msg += base_msg
+            LOG.error(msg)
+            raise rbac_exceptions.RbacOverrideRoleException(msg)
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 9a9f864..366e033 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -14,10 +14,10 @@
 #    under the License.
 
 from contextlib import contextmanager
+import sys
 import time
 
 from oslo_log import log as logging
-from oslo_log import versionutils
 from oslo_utils import excutils
 
 from tempest import clients
@@ -95,11 +95,17 @@
                 # if the API call above threw an exception, any code below this
                 # point in the test is not executed.
         """
+        test_obj._set_override_role_called()
         self._override_role(test_obj, True)
         try:
             # Execute the test.
             yield
         finally:
+            # Check whether an exception was raised. If so, remember that
+            # for future validation.
+            exc = sys.exc_info()[0]
+            if exc is not None:
+                test_obj._set_override_role_caught_exc()
             # This code block is always executed, no matter the result of the
             # test. Automatically switch back to the admin role for test clean
             # up.
@@ -222,6 +228,11 @@
                 cls.setup_rbac_utils()
     """
 
+    # Shows if override_role was called.
+    __override_role_called = False
+    # Shows if exception raised during override_role.
+    __override_role_caught_exc = False
+
     @classmethod
     def get_auth_providers(cls):
         """Returns list of auth_providers used within test.
@@ -232,21 +243,36 @@
         return [cls.os_primary.auth_provider]
 
     @classmethod
-    def skip_rbac_checks(cls):
-        if not CONF.patrole.enable_rbac:
-            deprecation_msg = ("The `[patrole].enable_rbac` option is "
-                               "deprecated and will be removed in the S "
-                               "release. Patrole tests will always be enabled "
-                               "following installation of the Patrole Tempest "
-                               "plugin. Use a regex to skip tests.")
-            versionutils.report_deprecated_feature(LOG, deprecation_msg)
-            raise cls.skipException(
-                'Patrole testing not enabled so skipping %s.' % cls.__name__)
-
-    @classmethod
     def setup_rbac_utils(cls):
         cls.rbac_utils = RbacUtils(cls)
 
+    def _set_override_role_called(self):
+        """Helper for tracking whether ``override_role`` was called."""
+        self.__override_role_called = True
+
+    def _set_override_role_caught_exc(self):
+        """Helper for tracking whether exception was thrown inside
+        ``override_role``.
+        """
+        self.__override_role_caught_exc = True
+
+    def _validate_override_role_called(self):
+        """Idempotently validate that ``override_role`` is called and reset
+        its value to False for sequential tests.
+        """
+        was_called = self.__override_role_called
+        self.__override_role_called = False
+        return was_called
+
+    def _validate_override_role_caught_exc(self):
+        """Idempotently validate that exception was caught inside
+        ``override_role``, so that, by process of elimination, it can be
+        determined whether one was thrown outside (which is invalid).
+        """
+        caught_exception = self.__override_role_caught_exc
+        self.__override_role_caught_exc = False
+        return caught_exception
+
 
 def is_admin():
     """Verifies whether the current test role equals the admin role.
diff --git a/patrole_tempest_plugin/tests/api/compute/rbac_base.py b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
index 18d2f48..ab4551e 100644
--- a/patrole_tempest_plugin/tests/api/compute/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
@@ -12,24 +12,16 @@
 #    under the License.
 
 from tempest.api.compute import base as compute_base
-from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 
 from patrole_tempest_plugin import rbac_utils
 
-CONF = config.CONF
-
 
 class BaseV2ComputeRbacTest(rbac_utils.RbacUtilsMixin,
                             compute_base.BaseV2ComputeTest):
 
     @classmethod
-    def skip_checks(cls):
-        super(BaseV2ComputeRbacTest, cls).skip_checks()
-        cls.skip_rbac_checks()
-
-    @classmethod
     def setup_clients(cls):
         super(BaseV2ComputeRbacTest, cls).setup_clients()
         cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
index d6364c9..a99ddbd 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest import config
+import testtools
 
+from tempest import config
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
@@ -34,6 +35,8 @@
         cls.public_flavor_id = CONF.compute.flavor_ref
         cls.tenant_id = cls.os_primary.credentials.tenant_id
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('a2bd3740-765d-4c95-ac98-9e027378c75e')
     @rbac_rule_validation.action(
         service="nova",
@@ -50,6 +53,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('dd388146-9750-4124-82ba-62deff1052bb')
     @rbac_rule_validation.action(
         service="nova",
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py
index fbc03cf..b4531af 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.common import utils
 from tempest import config
 from tempest.lib import decorators
@@ -33,6 +35,8 @@
             msg = "os-flavor-rxtx extension not enabled."
             raise cls.skipException(msg)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('5e1fd9f0-9a08-485a-ad9c-0fc66e4d64b7')
     @rbac_rule_validation.action(
         service="nova",
@@ -44,6 +48,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute='rxtx_factor')
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('70c55a07-c843-4627-a29d-ba78673c1e63')
     @rbac_rule_validation.action(
         service="nova",
diff --git a/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
index f36b8ec..c988128 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.common import image as common_image
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -20,6 +22,7 @@
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
+from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.compute import rbac_base
 
@@ -245,18 +248,67 @@
     # https://developer.openstack.org/api-ref/compute/#images-deprecated
     max_microversion = '2.35'
 
+    @classmethod
+    def skip_checks(cls):
+        super(ImageSizeRbacTest, cls).skip_checks()
+        if not CONF.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(ImageSizeRbacTest, cls).setup_clients()
+        if CONF.image_feature_enabled.api_v2:
+            cls.glance_image_client = cls.os_primary.image_client_v2
+        elif CONF.image_feature_enabled.api_v1:
+            cls.glance_image_client = cls.os_primary.image_client
+        else:
+            raise lib_exc.InvalidConfiguration(
+                'Either api_v1 or api_v2 must be True in '
+                '[image-feature-enabled].')
+
+    @classmethod
+    def resource_setup(cls):
+        super(ImageSizeRbacTest, cls).resource_setup()
+        params = {'name': data_utils.rand_name(cls.__name__ + '-image')}
+        if CONF.image_feature_enabled.api_v1:
+            params = {'headers': common_image.image_meta_to_headers(**params)}
+
+        cls.image = cls.glance_image_client.create_image(**params)
+        cls.addClassResourceCleanup(
+            cls.glance_image_client.wait_for_resource_deletion,
+            cls.image['id'])
+        cls.addClassResourceCleanup(
+            cls.glance_image_client.delete_image, cls.image['id'])
+
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('fe34d2a6-5743-45bf-8f92-a1d703d7c7ab')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:image-size")
-    def test_list_images(self):
+    def test_show_image_includes_image_size(self):
         with self.rbac_utils.override_role(self):
-            self.compute_images_client.list_images()
+            body = self.compute_images_client.show_image(self.image['id'])[
+                'image']
 
+        expected_attr = 'OS-EXT-IMG-SIZE:size'
+        if expected_attr not in body:
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute=expected_attr)
+
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('08342c7d-297d-42ee-b398-90fce2443792')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:image-size")
-    def test_list_images_with_details(self):
+    def test_list_images_with_details_includes_image_size(self):
         with self.rbac_utils.override_role(self):
-            self.compute_images_client.list_images(detail=True)
+            body = self.compute_images_client.list_images(detail=True)[
+                'images']
+
+        expected_attr = 'OS-EXT-IMG-SIZE:size'
+        if expected_attr not in body[0]:
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute=expected_attr)
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
index d97f382..5681799 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
@@ -129,6 +129,8 @@
         waiters.wait_for_server_status(
             self.servers_client, self.server['id'], 'ACTIVE')
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @utils.requires_ext(extension='os-config-drive', service='compute')
     @decorators.idempotent_id('2c82e819-382d-4d6f-87f0-a45954cbbc64')
     @rbac_rule_validation.action(
@@ -144,6 +146,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @utils.requires_ext(extension='os-config-drive', service='compute')
     @decorators.idempotent_id('55c62ef7-b72b-4970-acc6-05b0a4316e5d')
     @rbac_rule_validation.action(
@@ -169,6 +173,8 @@
             # Force-deleting a server enforces os-deferred-delete.
             self.servers_client.force_delete_server(self.server['id'])
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('d873740a-7b10-40a9-943d-7cc18115370e')
     @utils.requires_ext(extension='OS-EXT-AZ', service='compute')
     @rbac_rule_validation.action(
@@ -185,6 +191,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('727e5360-770a-4b9c-8015-513a40216635')
     @utils.requires_ext(extension='OS-EXT-AZ', service='compute')
     @rbac_rule_validation.action(
@@ -200,6 +208,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('4aa5d93e-4887-468a-8eb4-b6eca0ca6437')
     @utils.requires_ext(extension='OS-EXT-SRV-ATTR', service='compute')
     @rbac_rule_validation.action(
@@ -222,6 +232,8 @@
                 raise rbac_exceptions.RbacMalformedResponse(
                     attribute=whole_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('2ed7aee2-94b2-4a9f-ae63-a51b7f94fe30')
     @utils.requires_ext(extension='OS-EXT-SRV-ATTR', service='compute')
     @rbac_rule_validation.action(
@@ -244,6 +256,8 @@
                 raise rbac_exceptions.RbacMalformedResponse(
                     attribute=whole_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('82053c27-3134-4003-9b55-bc9fafdb0e3b')
     @utils.requires_ext(extension='OS-EXT-STS', service='compute')
     @rbac_rule_validation.action(
@@ -261,6 +275,8 @@
                 raise rbac_exceptions.RbacMalformedResponse(
                     attribute=attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('7d2620a5-eea1-4a8b-96ea-86ad77a73fc8')
     @utils.requires_ext(extension='OS-EXT-STS', service='compute')
     @rbac_rule_validation.action(
@@ -278,6 +294,8 @@
                 raise rbac_exceptions.RbacMalformedResponse(
                     attribute=attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('21e39cbe-6c32-48fc-80dd-3e1fece6053f')
     @utils.requires_ext(extension='os-extended-volumes', service='compute')
     @rbac_rule_validation.action(
@@ -295,6 +313,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @decorators.idempotent_id('7f163708-0d25-4138-8512-dfdd72a92989')
     @utils.requires_ext(extension='os-extended-volumes', service='compute')
     @rbac_rule_validation.action(
@@ -348,6 +368,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute='events.traceback')
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:os-keypairs")
@@ -360,6 +382,8 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute='key_name')
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:os-keypairs")
@@ -469,6 +493,8 @@
         with self.rbac_utils.override_role(self):
             self.servers_client.show_password(self.server['id'])
 
+    @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+                      "This API extension policy was removed in Stein")
     @utils.requires_ext(extension='OS-SRV-USG', service='compute')
     @rbac_rule_validation.action(
         service="nova",
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index 91b3d1e..44f5962 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -16,13 +16,11 @@
 from oslo_log import log as logging
 
 from tempest.api.identity import base
-from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 
 from patrole_tempest_plugin import rbac_utils
 
-CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
 
@@ -30,11 +28,6 @@
                            base.BaseIdentityTest):
 
     @classmethod
-    def skip_checks(cls):
-        super(BaseIdentityRbacTest, cls).skip_checks()
-        cls.skip_rbac_checks()
-
-    @classmethod
     def setup_clients(cls):
         super(BaseIdentityRbacTest, cls).setup_clients()
         cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
index 00d522c..da5d4cd 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest import config
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
@@ -21,8 +20,6 @@
 from patrole_tempest_plugin import rbac_utils
 from patrole_tempest_plugin.tests.api.identity import rbac_base
 
-CONF = config.CONF
-
 
 class IdentityTokenV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
diff --git a/patrole_tempest_plugin/tests/api/image/rbac_base.py b/patrole_tempest_plugin/tests/api/image/rbac_base.py
index 954790d..becd564 100644
--- a/patrole_tempest_plugin/tests/api/image/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/image/rbac_base.py
@@ -12,22 +12,14 @@
 #    under the License.
 
 from tempest.api.image import base as image_base
-from tempest import config
 
 from patrole_tempest_plugin import rbac_utils
 
-CONF = config.CONF
-
 
 class BaseV2ImageRbacTest(rbac_utils.RbacUtilsMixin,
                           image_base.BaseV2ImageTest):
 
     @classmethod
-    def skip_checks(cls):
-        super(BaseV2ImageRbacTest, cls).skip_checks()
-        cls.skip_rbac_checks()
-
-    @classmethod
     def setup_clients(cls):
         super(BaseV2ImageRbacTest, cls).setup_clients()
         cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py b/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py
index 952c41f..4b5fd08 100644
--- a/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py
@@ -68,8 +68,8 @@
                                                          self.alt_tenant_id)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_member",
-                                 expected_error_code=404)
+                                 rules=["get_member"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('c01fd308-6484-11e6-881e-080027d0d606')
     def test_show_image_member(self):
 
diff --git a/patrole_tempest_plugin/tests/api/network/rbac_base.py b/patrole_tempest_plugin/tests/api/network/rbac_base.py
index 9d3e28b..6102347 100644
--- a/patrole_tempest_plugin/tests/api/network/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/network/rbac_base.py
@@ -14,22 +14,14 @@
 #    under the License.
 
 from tempest.api.network import base as network_base
-from tempest import config
 
 from patrole_tempest_plugin import rbac_utils
 
-CONF = config.CONF
-
 
 class BaseNetworkRbacTest(rbac_utils.RbacUtilsMixin,
                           network_base.BaseNetworkTest):
 
     @classmethod
-    def skip_checks(cls):
-        super(BaseNetworkRbacTest, cls).skip_checks()
-        cls.skip_rbac_checks()
-
-    @classmethod
     def setup_clients(cls):
         super(BaseNetworkRbacTest, cls).setup_clients()
         cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py b/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
index 2756a10..7567275 100644
--- a/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
@@ -38,8 +38,8 @@
 
     @decorators.idempotent_id('f88e38e0-ab52-4b97-8ffa-48a27f9d199b')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_agent",
-                                 expected_error_code=404)
+                                 rules=["get_agent"],
+                                 expected_error_codes=[404])
     def test_show_agent(self):
         """Show agent test.
 
diff --git a/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py b/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
index cdc9852..f8ef0bb 100644
--- a/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from oslo_serialization import jsonutils as json
+
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
@@ -116,3 +118,70 @@
 
         with self.rbac_utils.override_role(self):
             self.ntp_client.list_flavors()
+
+
+class FlavorsServiceProfilePluginRbacTest(base.BaseNetworkPluginRbacTest):
+    @classmethod
+    def resource_setup(cls):
+        super(FlavorsServiceProfilePluginRbacTest, cls).resource_setup()
+        providers = cls.ntp_client.list_service_providers()
+        if not providers["service_providers"]:
+            raise cls.skipException("No service_providers available.")
+        cls.service_type = providers["service_providers"][0]["service_type"]
+
+        cls.flavor_id = cls.create_flavor()
+        cls.service_profile_id = cls.create_service_profile()
+
+    @classmethod
+    def create_flavor(cls):
+        flavor = cls.ntp_client.create_flavor(service_type=cls.service_type)
+        flavor_id = flavor["flavor"]["id"]
+        cls.addClassResourceCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.ntp_client.delete_flavor, flavor_id)
+        return flavor_id
+
+    @classmethod
+    def create_service_profile(cls):
+        service_profile = cls.ntp_client.create_service_profile(
+            metainfo=json.dumps({'foo': 'bar'}))
+        service_profile_id = service_profile["service_profile"]["id"]
+        cls.addClassResourceCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.ntp_client.delete_service_profile, service_profile_id)
+        return service_profile_id
+
+    def create_flavor_service_profile(self, flavor_id, service_profile_id):
+        self.ntp_client.create_flavor_service_profile(
+            flavor_id, service_profile_id)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_flavor_service_profile,
+            flavor_id, service_profile_id)
+
+    @decorators.idempotent_id('aa84b4c5-0dd6-4c34-aa81-3a76507f9b81')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_flavor_service_profile"])
+    def test_create_flavor_service_profile(self):
+        """Create flavor_service_profile.
+
+        RBAC test for the neutron "create_flavor_service_profile" policy
+        """
+        with self.rbac_utils.override_role(self):
+            self.create_flavor_service_profile(self.flavor_id,
+                                               self.service_profile_id)
+
+    @decorators.idempotent_id('3b680d9e-946a-4670-ab7f-0e4576675833')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["delete_flavor_service_profile"])
+    def test_delete_flavor_service_profile(self):
+        """Delete flavor_service_profile.
+
+        RBAC test for the neutron "delete_flavor_service_profile" policy
+        """
+        self.create_flavor_service_profile(self.flavor_id,
+                                           self.service_profile_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_flavor_service_profile(
+                self.flavor_id, self.service_profile_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py b/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py
index ed52c34..8a02149 100644
--- a/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py
@@ -76,8 +76,7 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_floatingip",
-               "create_floatingip:floating_ip_address"],
-        expected_error_codes=[403, 403])
+               "create_floatingip:floating_ip_address"])
     @decorators.idempotent_id('a8bb826a-403d-4130-a55d-120a0a660806')
     def test_create_floating_ip_floatingip_address(self):
         """Create floating IP with address.
@@ -105,8 +104,8 @@
                 floating_ip['id'], port_id=None)
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_floatingip",
-                                 expected_error_code=404)
+                                 rules=["get_floatingip"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('f8846fd0-c976-48fe-a148-105303931b32')
     def test_show_floating_ip(self):
         """Show floating IP.
diff --git a/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py b/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py
index adab1e6..db099a1 100644
--- a/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py
@@ -74,8 +74,8 @@
             self._create_metering_label_rule(self.label)
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_metering_label_rule",
-                                 expected_error_code=404)
+                                 rules=["get_metering_label_rule"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('e21b40c3-d44d-412f-84ea-836ca8603bcb')
     def test_show_metering_label_rule(self):
         """Show metering label rule.
diff --git a/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py b/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py
index 0231868..0e10f5b 100644
--- a/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py
@@ -58,8 +58,8 @@
             self._create_metering_label()
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_metering_label",
-                                 expected_error_code=404)
+                                 rules=["get_metering_label"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('c57f6636-c702-4755-8eac-5e73bc1f7d14')
     def test_show_metering_label(self):
         """Show metering label.
diff --git a/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py b/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
index 0097c7b..c985111 100644
--- a/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
@@ -67,8 +67,7 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_network",
-                                        "create_network:segments"],
-                                 expected_error_codes=[403, 403])
+                                        "create_network:segments"])
     @decorators.idempotent_id('9e1d0c3d-92e3-40e3-855e-bfbb72ea6e0b')
     def test_create_network_segments(self):
         """Create network with segments.
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
index 72674f6..2e69f89 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -110,8 +110,7 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_network",
-                                        "create_network:is_default"],
-                                 expected_error_codes=[403, 403])
+                                        "create_network:is_default"])
     @decorators.idempotent_id('28602661-5ac7-407e-b739-e393f619f5e3')
     def test_create_network_is_default(self):
 
@@ -129,8 +128,7 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_network",
-                                        "create_network:shared"],
-                                 expected_error_codes=[403, 403])
+                                        "create_network:shared"])
     @decorators.idempotent_id('ccabf2a9-28c8-44b2-80e6-ffd65d43eef2')
     def test_create_network_shared(self):
 
@@ -144,8 +142,7 @@
     @utils.requires_ext(extension='external-net', service='network')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_network",
-                                        "create_network:router:external"],
-                                 expected_error_codes=[403, 403])
+                                        "create_network:router:external"])
     @decorators.idempotent_id('51adf2a7-739c-41e0-8857-3b4c460cbd24')
     def test_create_network_router_external(self):
 
@@ -160,8 +157,7 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_network",
-               "create_network:provider:physical_network"],
-        expected_error_codes=[403, 403])
+               "create_network:provider:physical_network"])
     @decorators.idempotent_id('76783fed-9ff3-4499-a0d1-82d99eec364e')
     def test_create_network_provider_physical_network(self):
 
@@ -184,8 +180,7 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_network",
-               "create_network:provider:network_type"],
-        expected_error_codes=[403, 403])
+               "create_network:provider:network_type"])
     @decorators.idempotent_id('3c42f7b8-b80c-44ef-8fa4-69ec4b1836bc')
     def test_create_network_provider_network_type(self):
 
@@ -200,8 +195,7 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_network",
-               "create_network:provider:segmentation_id"],
-        expected_error_codes=[403, 403])
+               "create_network:provider:segmentation_id"])
     @decorators.idempotent_id('b9decb7b-68ef-4504-b99b-41edbf7d2af5')
     def test_create_network_provider_segmentation_id(self):
 
@@ -338,8 +332,8 @@
                 str(exc))
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_network",
-                                 expected_error_code=404)
+                                 rules=["get_network"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('0eb62d04-338a-4ff4-a8fa-534e52110534')
     def test_show_network(self):
 
diff --git a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
index 2cf3cd6..175d051 100644
--- a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
@@ -69,8 +69,7 @@
     @decorators.idempotent_id('045ee797-4962-4913-b96a-5d7ea04099e7')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:device_owner"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:device_owner"])
     def test_create_port_device_owner(self):
         with self.rbac_utils.override_role(self):
             self.create_port(self.network,
@@ -79,8 +78,7 @@
     @decorators.idempotent_id('c4fa8844-f5ef-4daa-bfa2-b89897dfaedf')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:port_security_enabled"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:port_security_enabled"])
     def test_create_port_security_enabled(self):
         with self.rbac_utils.override_role(self):
             self.create_port(self.network, port_security_enabled=True)
@@ -88,8 +86,7 @@
     @utils.requires_ext(extension='binding', service='network')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:binding:host_id"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:binding:host_id"])
     @decorators.idempotent_id('a54bd6b8-a7eb-4101-bfe8-093930b0d660')
     def test_create_port_binding_host_id(self):
 
@@ -102,8 +99,7 @@
     @utils.requires_ext(extension='binding', service='network')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:binding:profile"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:binding:profile"])
     @decorators.idempotent_id('98fa38ab-c2ed-46a0-99f0-59f18cbd257a')
     def test_create_port_binding_profile(self):
 
@@ -120,8 +116,7 @@
         '"create_port:fixed_ips:ip_address" must be available in the cloud.')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:fixed_ips:ip_address"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:fixed_ips:ip_address"])
     @decorators.idempotent_id('2551e10d-006a-413c-925a-8c6f834c09ac')
     def test_create_port_fixed_ips_ip_address(self):
 
@@ -137,8 +132,7 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:mac_address"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:mac_address"])
     @decorators.idempotent_id('aee6d0be-a7f3-452f-aefc-796b4eb9c9a8')
     def test_create_port_mac_address(self):
 
@@ -150,8 +144,7 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_port",
-                                        "create_port:allowed_address_pairs"],
-                                 expected_error_codes=[403, 403])
+                                        "create_port:allowed_address_pairs"])
     @decorators.idempotent_id('b638d1f4-d903-4ca8-aa2a-6fd603c5ec3a')
     def test_create_port_allowed_address_pairs(self):
 
@@ -166,8 +159,8 @@
             self.create_port(**post_body)
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_port",
-                                 expected_error_code=404)
+                                 rules=["get_port"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('a9d41cb8-78a2-4b97-985c-44e4064416f4')
     def test_show_port(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/network/test_rbac_policies_rbac.py b/patrole_tempest_plugin/tests/api/network/test_rbac_policies_rbac.py
new file mode 100644
index 0000000..a8813e7
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_rbac_policies_rbac.py
@@ -0,0 +1,111 @@
+# Copyright 2018 AT&T Corporation.
+# 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 tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class RbacPoliciesPluginRbacTest(base.BaseNetworkPluginRbacTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(RbacPoliciesPluginRbacTest, cls).resource_setup()
+        cls.tenant_id = cls.os_primary.credentials.tenant_id
+        cls.network_id = cls.create_network()['id']
+
+    def create_rbac_policy(self, tenant_id, network_id):
+        policy = self.ntp_client.create_rbac_policy(
+            target_tenant=self.tenant_id,
+            object_type="network",
+            object_id=self.network_id,
+            action="access_as_shared"
+        )
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_rbac_policy, policy["rbac_policy"]["id"])
+
+        return policy["rbac_policy"]["id"]
+
+    @decorators.idempotent_id('effd9545-99ad-4c3c-92dd-ea422602c868')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_rbac_policy",
+                                        "create_rbac_policy:target_tenant"])
+    def test_create_rbac_policy(self):
+        """Create RBAC policy.
+
+        RBAC test for the neutron "create_rbac_policy" policy
+
+        We can't validate "create_rbac_policy:target_tenant" for all cases
+        since if "restrict_wildcard" rule is modified then Patrole won't be
+        able to determine the correct result since that requires relying on
+        Neutron's custom FieldCheck oslo.policy rule.
+        """
+
+        with self.rbac_utils.override_role(self):
+            self.create_rbac_policy(self.tenant_id, self.network_id)
+
+    @decorators.idempotent_id('f5d836d8-3b64-412d-a283-ee29761017f3')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_rbac_policy",
+                                        "update_rbac_policy",
+                                        "update_rbac_policy:target_tenant"],
+                                 expected_error_codes=[404, 403, 403])
+    def test_update_rbac_policy(self):
+        """Update RBAC policy.
+
+        RBAC test for the neutron "update_rbac_policy" policy
+
+        We can't validate "create_rbac_policy:target_tenant" for all cases
+        since if "restrict_wildcard" rule is modified then Patrole won't be
+        able to determine the correct result since that requires relying on
+        Neutron's custom FieldCheck oslo.policy rule.
+        """
+        policy_id = self.create_rbac_policy(self.tenant_id, self.network_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_rbac_policy(
+                policy_id, target_tenant=self.tenant_id)
+
+    @decorators.idempotent_id('9308ab18-426c-41b7-bce5-11081f7dd259')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_rbac_policy"],
+                                 expected_error_codes=[404])
+    def test_show_rbac_policy(self):
+        """Show RBAC policy.
+
+        RBAC test for the neutron "get_rbac_policy" policy
+        """
+        policy_id = self.create_rbac_policy(self.tenant_id, self.network_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_rbac_policy(policy_id)
+
+    @decorators.idempotent_id('54aa9bce-efea-47fb-b0e4-12012f82f285')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_rbac_policy",
+                                        "delete_rbac_policy"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_rbac_policy(self):
+        """Delete RBAC policy.
+
+        RBAC test for the neutron "delete_rbac_policy" policy
+        """
+        policy_id = self.create_rbac_policy(self.tenant_id, self.network_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_rbac_policy(policy_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
index a3d973d..3d7631a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -72,8 +72,7 @@
     @utils.requires_ext(extension='l3-ha', service='network')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_router",
-                                        "create_router:ha"],
-                                 expected_error_codes=[403, 403])
+                                        "create_router:ha"])
     def test_create_high_availability_router(self):
         """Create high-availability router
 
@@ -88,8 +87,7 @@
     @utils.requires_ext(extension='dvr', service='network')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_router",
-                                        "create_router:distributed"],
-                                 expected_error_codes=[403, 403])
+                                        "create_router:distributed"])
     def test_create_distributed_router(self):
         """Create distributed router
 
@@ -104,8 +102,7 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_router",
-               "create_router:external_gateway_info:enable_snat"],
-        expected_error_codes=[403, 403])
+               "create_router:external_gateway_info:enable_snat"])
     @decorators.idempotent_id('3c5acd49-0ec7-4109-ab51-640557b48ebc')
     def test_create_router_enable_snat(self):
         """Create Router Snat
@@ -126,8 +123,7 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_router",
-               "create_router:external_gateway_info:external_fixed_ips"],
-        expected_error_codes=[403, 403])
+               "create_router:external_gateway_info:external_fixed_ips"])
     @decorators.idempotent_id('d0354369-a040-4349-b869-645c8aed13cd')
     def test_create_router_external_fixed_ips(self):
         """Create Router Fixed IPs
@@ -151,8 +147,8 @@
                         router['router']['id'])
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_router",
-                                 expected_error_code=404)
+                                 rules=["get_router"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('bfbdbcff-f115-4d3e-8cd5-6ada33fd0e21')
     def test_show_router(self):
         """Get Router
diff --git a/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py b/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py
index 1cf841d..4536fdb 100644
--- a/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py
@@ -78,8 +78,8 @@
             self._create_security_group()
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_security_group",
-                                 expected_error_code=404)
+                                 rules=["get_security_group"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('56335e77-aef2-4b54-86c7-7f772034b585')
     def test_show_security_group(self):
 
@@ -149,8 +149,8 @@
                 sec_group_rule['id'])
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_security_group_rule",
-                                 expected_error_code=404)
+                                 rules=["get_security_group_rule"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('84b4038c-261e-4a94-90d5-c885739ab0d5')
     def test_show_security_group_rule(self):
 
diff --git a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
index 124b59a..7d02271 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
@@ -65,8 +65,7 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_subnetpool",
-                                        "create_subnetpool:shared"],
-                                 expected_error_codes=[403, 403])
+                                        "create_subnetpool:shared"])
     @decorators.idempotent_id('cf730989-0d47-40bc-b39a-99e7de484723')
     def test_create_subnetpool_shared(self):
         """Create subnetpool shared.
@@ -77,8 +76,8 @@
             self._create_subnetpool(shared=True)
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_subnetpool",
-                                 expected_error_code=404)
+                                 rules=["get_subnetpool"],
+                                 expected_error_codes=[404])
     @decorators.idempotent_id('4f5aee26-0507-4b6d-b44c-3128a25094d2')
     def test_show_subnetpool(self):
         """Show subnetpool.
@@ -107,8 +106,7 @@
     @decorators.idempotent_id('a16f4e5c-0675-415f-b636-00af00638693')
     @rbac_rule_validation.action(service="neutron",
                                  rules=["update_subnetpool",
-                                        "update_subnetpool:is_default"],
-                                 expected_error_codes=[403, 403])
+                                        "update_subnetpool:is_default"])
     def test_update_subnetpool_is_default(self):
         """Update default subnetpool.
 
diff --git a/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py b/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
index 77d4b42..93d79a9 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
@@ -50,8 +50,8 @@
 
     @decorators.idempotent_id('c02618e7-bb20-4abd-83c8-6eec2af08752')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_subnet",
-                                 expected_error_code=404)
+                                 rules=["get_subnet"],
+                                 expected_error_codes=[404])
     def test_show_subnet(self):
         """Show subnet.
 
diff --git a/patrole_tempest_plugin/tests/api/network/test_trunks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_trunks_rbac.py
new file mode 100644
index 0000000..063fd55
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_trunks_rbac.py
@@ -0,0 +1,85 @@
+# Copyright 2018 AT&T Corporation.
+# 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 tempest.common import utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class TrunksPluginRbacTest(base.BaseNetworkPluginRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(TrunksPluginRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('trunk', 'network'):
+            msg = "trunk extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(TrunksPluginRbacTest, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.port_id = cls.create_port(cls.network)["id"]
+
+    def create_trunk(self, port_id):
+        trunk = self.ntp_client.create_trunk(port_id, [])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_trunk, trunk["trunk"]['id'])
+
+        return trunk
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08130')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_trunk"])
+    def test_create_trunk(self):
+        """Create trunk.
+
+        RBAC test for the neutron "create_trunk" policy
+        """
+        with self.rbac_utils.override_role(self):
+            self.create_trunk(self.port_id)
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08131')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_trunk"],
+                                 expected_error_codes=[404])
+    def test_show_trunk(self):
+        """Show trunk.
+
+        RBAC test for the neutron "get_trunk" policy
+        """
+        trunk = self.create_trunk(self.port_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_trunk(trunk['trunk']['id'])
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08132')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_trunk",
+                                        "delete_trunk"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_trunk(self):
+        """Delete trunk.
+
+        RBAC test for the neutron "delete_trunk" policy
+        """
+        trunk = self.create_trunk(self.port_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_trunk(trunk['trunk']['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/rbac_base.py b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
index bac173e..14b3151 100644
--- a/patrole_tempest_plugin/tests/api/volume/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
@@ -13,14 +13,11 @@
 
 from tempest.api.volume import base as vol_base
 from tempest.common import waiters
-from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 
 from patrole_tempest_plugin import rbac_utils
 
-CONF = config.CONF
-
 
 class BaseVolumeRbacTest(rbac_utils.RbacUtilsMixin,
                          vol_base.BaseVolumeTest):
@@ -32,11 +29,6 @@
     _api_version = 3
 
     @classmethod
-    def skip_checks(cls):
-        super(BaseVolumeRbacTest, cls).skip_checks()
-        cls.skip_rbac_checks()
-
-    @classmethod
     def setup_clients(cls):
         super(BaseVolumeRbacTest, cls).setup_clients()
         cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/unit/fixtures.py b/patrole_tempest_plugin/tests/unit/fixtures.py
index 1c47985..4552224 100644
--- a/patrole_tempest_plugin/tests/unit/fixtures.py
+++ b/patrole_tempest_plugin/tests/unit/fixtures.py
@@ -16,6 +16,7 @@
 """Fixtures for Patrole tests."""
 from __future__ import absolute_import
 
+from contextlib import contextmanager
 import fixtures
 import mock
 import time
@@ -117,6 +118,17 @@
             new_role = 'member' if role_toggle else 'admin'
             self.set_roles(['admin', 'member'], [new_role])
 
+    @contextmanager
+    def real_override_role(self, test_obj):
+        """Actual call to ``override_role``.
+
+        Useful for ensuring all the necessary mocks are performed before
+        the method in question is called.
+        """
+        _rbac_utils = rbac_utils.RbacUtils(test_obj)
+        with _rbac_utils.override_role(test_obj):
+            yield
+
     def set_roles(self, roles, roles_on_project=None):
         """Set the list of available roles in the system.
 
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index 1bf5510..fe36f2c 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -12,9 +12,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from __future__ import absolute_import
+
 import mock
 from oslo_config import cfg
 
+import fixtures
 from tempest.lib import exceptions
 from tempest import manager
 from tempest import test
@@ -23,7 +26,7 @@
 from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation as rbac_rv
 from patrole_tempest_plugin import rbac_utils
-from patrole_tempest_plugin.tests.unit import fixtures
+from patrole_tempest_plugin.tests.unit import fixtures as patrole_fixtures
 
 CONF = cfg.CONF
 
@@ -43,10 +46,12 @@
         setattr(self.mock_test_args.os_primary, 'credentials', mock_creds)
 
         self.useFixture(
-            fixtures.ConfPatcher(rbac_test_role='Member', group='patrole'))
+            patrole_fixtures.ConfPatcher(rbac_test_role='Member',
+                                         group='patrole'))
         # Disable patrole log for unit tests.
         self.useFixture(
-            fixtures.ConfPatcher(enable_reporting=False, group='patrole_log'))
+            patrole_fixtures.ConfPatcher(enable_reporting=False,
+                                         group='patrole_log'))
 
 
 class RBACRuleValidationTest(BaseRBACRuleValidationTest):
@@ -54,6 +59,12 @@
     ``rbac_rule_validation`` decorator.
     """
 
+    def setUp(self):
+        super(RBACRuleValidationTest, self).setUp()
+        # This behavior is tested in separate test class below.
+        self.useFixture(fixtures.MockPatchObject(
+            rbac_rv, '_validate_override_role_called'))
+
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
     def test_rule_validation_have_permission_no_exc(self, mock_authority,
@@ -269,7 +280,7 @@
             mock_log.warning.assert_called_with(
                 "NotFound exception was caught for test %s. Expected policies "
                 "which may have caused the error: %s. The service %s throws a "
-                "404 instead of a 403, which is irregular.",
+                "404 instead of a 403, which is irregular",
                 test_policy.__name__,
                 ', '.join(policy_names),
                 mock.sentinel.service)
@@ -334,7 +345,7 @@
         expected_irregular_msg = (
             "NotFound exception was caught for test %s. Expected policies "
             "which may have caused the error: %s. The service %s throws a "
-            "404 instead of a 403, which is irregular.")
+            "404 instead of a 403, which is irregular")
 
         actual_exception, actual_irregular_msg = \
             rbac_rv._get_exception_type(404)
@@ -385,6 +396,12 @@
     Patrole RBAC validation work flows.
     """
 
+    def setUp(self):
+        super(RBACRuleValidationLoggingTest, self).setUp()
+        # This behavior is tested in separate test class below.
+        self.useFixture(fixtures.MockPatchObject(
+            rbac_rv, '_validate_override_role_called'))
+
     @mock.patch.object(rbac_rv, 'RBACLOG', autospec=True)
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
     def test_rbac_report_logging_disabled(self, mock_authority, mock_rbaclog):
@@ -392,7 +409,8 @@
         is False
         """
         self.useFixture(
-            fixtures.ConfPatcher(enable_reporting=False, group='patrole_log'))
+            patrole_fixtures.ConfPatcher(enable_reporting=False,
+                                         group='patrole_log'))
 
         mock_authority.PolicyAuthority.return_value.allowed.return_value = True
 
@@ -410,7 +428,8 @@
         True
         """
         self.useFixture(
-            fixtures.ConfPatcher(enable_reporting=True, group='patrole_log'))
+            patrole_fixtures.ConfPatcher(enable_reporting=True,
+                                         group='patrole_log'))
 
         mock_authority.PolicyAuthority.return_value.allowed.return_value = True
         policy_names = ['foo:bar', 'baz:qux']
@@ -432,6 +451,12 @@
 
 class RBACRuleValidationNegativeTest(BaseRBACRuleValidationTest):
 
+    def setUp(self):
+        super(RBACRuleValidationNegativeTest, self).setUp()
+        # This behavior is tested in separate test class below.
+        self.useFixture(fixtures.MockPatchObject(
+            rbac_rv, '_validate_override_role_called'))
+
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
     def test_rule_validation_invalid_service_raises_exc(self, mock_authority):
         """Test that invalid service raises the appropriate exception."""
@@ -451,6 +476,12 @@
     ``rbac_rule_validation`` decorator.
     """
 
+    def setUp(self):
+        super(RBACRuleValidationTestMultiPolicy, self).setUp()
+        # This behavior is tested in separate test class below.
+        self.useFixture(fixtures.MockPatchObject(
+            rbac_rv, '_validate_override_role_called'))
+
     def _assert_policy_authority_called_with(self, rules, mock_authority):
         m_authority = mock_authority.PolicyAuthority.return_value
         m_authority.allowed.assert_has_calls([
@@ -708,3 +739,189 @@
         # When expected_error_codes is provided rules must be as well.
         self.assertRaisesRegex(ValueError, error_re, _do_test,
                                None, None, None, [404])
+
+
+class RBACOverrideRoleValidationTest(BaseRBACRuleValidationTest):
+    """Class for validating that untimely exceptions (outside
+    ``override_role`` is called) result in test failures.
+
+    This regression tests false positives caused by test exceptions matching
+    the expected exception before or after the ``override_role`` context is
+    called. Also tests case where ``override_role`` is never called which is
+    an invalid Patrole test.
+
+    """
+
+    def setUp(self):
+        super(RBACOverrideRoleValidationTest, self).setUp()
+
+        # Mixin automatically initializes __override_role_called to False.
+        class FakeRbacTest(rbac_utils.RbacUtilsMixin, test.BaseTestCase):
+            def runTest(self):
+                pass
+
+        # Stub out problematic function calls.
+        FakeRbacTest.os_primary = mock.Mock(spec=manager.Manager)
+        FakeRbacTest.rbac_utils = self.useFixture(
+            patrole_fixtures.RbacUtilsFixture())
+        mock_creds = mock.Mock(user_id=mock.sentinel.user_id,
+                               project_id=mock.sentinel.project_id)
+        setattr(FakeRbacTest.os_primary, 'credentials', mock_creds)
+        setattr(FakeRbacTest.os_primary, 'auth_provider', mock.Mock())
+
+        self.parent_class = FakeRbacTest
+
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_override_role_called_inside_ctx(self,
+                                                             mock_authority):
+        """Test success case when the expected exception is raised within the
+        override_role context.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+            False
+
+        class ChildRbacTest(self.parent_class):
+
+            @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+                            expected_error_codes=[404])
+            def test_called(self_):
+                with self_.rbac_utils.real_override_role(self_):
+                    raise exceptions.NotFound()
+
+        child_test = ChildRbacTest()
+        child_test.test_called()
+
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_override_role_patrole_exception_ignored(
+            self, mock_authority):
+        """Test success case where Patrole exception is raised (which is
+        valid in case of e.g. RbacMalformedException) after override_role
+        passes.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+            True
+
+        class ChildRbacTest(self.parent_class):
+
+            @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+                            expected_error_codes=[404])
+            def test_called(self_):
+                with self_.rbac_utils.real_override_role(self_):
+                    pass
+                # Instances of BasePatroleException don't count as they are
+                # part of the validation work flow.
+                raise rbac_exceptions.BasePatroleException()
+
+        child_test = ChildRbacTest()
+        self.assertRaises(rbac_exceptions.BasePatroleException,
+                          child_test.test_called)
+
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_override_role_called_before_ctx(self,
+                                                             mock_authority):
+        """Test failure case when an exception that happens before
+        ``override_role`` context, even if it is the expected exception,
+        raises ``RbacOverrideRoleException``.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+            False
+
+        # This behavior should work for supported (NotFound/Forbidden) and
+        # miscellaneous exceptions alike.
+        for exception_type in (exceptions.NotFound,
+                               Exception):
+            class ChildRbacTest(self.parent_class):
+
+                @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+                                expected_error_codes=[404])
+                def test_called_before(self_):
+                    raise exception_type()
+
+            child_test = ChildRbacTest()
+            test_re = ".*before.*"
+            self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+                                   test_re, child_test.test_called_before)
+
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_override_role_called_after_ctx(self,
+                                                            mock_authority):
+        """Test failure case when an exception that happens before
+        ``override_role`` context, even if it is the expected exception,
+        raises ``RbacOverrideRoleException``.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+            False
+
+        # This behavior should work for supported (NotFound/Forbidden) and
+        # miscellaneous exceptions alike.
+        for exception_type in (exceptions.NotFound,
+                               Exception):
+            class ChildRbacTest(self.parent_class):
+
+                @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+                                expected_error_codes=[404])
+                def test_called_after(self_):
+                    with self_.rbac_utils.real_override_role(self_):
+                        pass
+                    # Simulates a test tearDown failure or some such.
+                    raise exception_type()
+
+            child_test = ChildRbacTest()
+            test_re = ".*after.*"
+            self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+                                   test_re, child_test.test_called_after)
+
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_override_role_never_called(self, mock_authority):
+        """Test failure case where override_role is **never** called."""
+        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+            False
+
+        class ChildRbacTest(self.parent_class):
+
+            @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+                            expected_error_codes=[404])
+            def test_never_called(self_):
+                pass
+
+        child_test = ChildRbacTest()
+        test_re = ".*missing required `override_role` call.*"
+        self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+                               test_re, child_test.test_never_called)
+
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_override_role_sequential_test_calls(
+            self, mock_authority):
+        """Test success/failure scenarios above across sequential test calls.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+            False
+
+        class ChildRbacTest(self.parent_class):
+
+            @rbac_rv.action(mock.sentinel.service, rules=["fake:rule1"],
+                            expected_error_codes=[404])
+            def test_called(self_):
+                with self_.rbac_utils.real_override_role(self_):
+                    raise exceptions.NotFound()
+
+            @rbac_rv.action(mock.sentinel.service, rules=["fake:rule2"],
+                            expected_error_codes=[404])
+            def test_called_before(self_):
+                raise exceptions.NotFound()
+
+        test_re = ".*before.*"
+
+        # Test case where override role is called in first test but *not* in
+        # second test.
+        child_test1 = ChildRbacTest()
+        child_test1.test_called()
+        self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+                               test_re, child_test1.test_called_before)
+
+        # Test case where override role is *not* called in first test but is
+        # in second test.
+        child_test2 = ChildRbacTest()
+        self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+                               test_re, child_test2.test_called_before)
+        child_test2.test_called()
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index c5264aa..5132079 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -208,11 +208,6 @@
         class FakeRbacTest(rbac_utils.RbacUtilsMixin, test.BaseTestCase):
 
             @classmethod
-            def skip_checks(cls):
-                super(FakeRbacTest, cls).skip_checks()
-                cls.skip_rbac_checks()
-
-            @classmethod
             def setup_clients(cls):
                 super(FakeRbacTest, cls).setup_clients()
                 cls.setup_rbac_utils()
@@ -237,21 +232,3 @@
 
         self.assertTrue(hasattr(child_test, 'rbac_utils'))
         self.assertIsInstance(child_test.rbac_utils, rbac_utils.RbacUtils)
-
-    def test_skip_rbac_checks(self):
-        """Validate that the child class is skipped if `[patrole] enable_rbac`
-        is False and that the child class's name is in the skip message.
-        """
-        self.useFixture(patrole_fixtures.ConfPatcher(enable_rbac=False,
-                                                     group='patrole'))
-
-        class ChildRbacTest(self.parent_class):
-            pass
-
-        child_test = ChildRbacTest()
-
-        with testtools.ExpectedException(
-                testtools.TestCase.skipException,
-                value_re=('Patrole testing not enabled so skipping %s.'
-                          % ChildRbacTest.__name__)):
-            child_test.setUpClass()
diff --git a/releasenotes/notes/check-expected-errors-only-in-override-role-f7109a73f5ff70e2.yaml b/releasenotes/notes/check-expected-errors-only-in-override-role-f7109a73f5ff70e2.yaml
new file mode 100644
index 0000000..e0ac744
--- /dev/null
+++ b/releasenotes/notes/check-expected-errors-only-in-override-role-f7109a73f5ff70e2.yaml
@@ -0,0 +1,19 @@
+---
+features:
+  - |
+    Add new exception called ``RbacOverrideRoleException``. Used for
+    safeguarding against false positives that might occur when the expected
+    exception isn't raised inside the ``override_role`` context. Specifically,
+    when:
+
+    * ``override_role`` isn't called
+    * an exception is raised before ``override_role`` context
+    * an exception is raised after ``override_role`` context
+fixes:
+  - |
+    Previously, the ``rbac_rule_validation.action`` decorator could catch
+    expected exceptions with no regard to where the error happened. Such
+    behavior could cause false-positive results. To prevent this from
+    happening from now on, if an exception happens outside of the
+    ``override_role`` context, it will cause
+    ``rbac_exceptions.RbacOverrideRoleException`` to be raised.
diff --git a/releasenotes/notes/patrole-rocky-release-e6f36691306bec7e.yaml b/releasenotes/notes/patrole-rocky-release-e6f36691306bec7e.yaml
new file mode 100644
index 0000000..22c4958
--- /dev/null
+++ b/releasenotes/notes/patrole-rocky-release-e6f36691306bec7e.yaml
@@ -0,0 +1,14 @@
+---
+prelude: >
+    This release is to tag the Patrole for OpenStack Rocky release.
+    After this release, Patrole will support below OpenStack Releases:
+
+      * Rocky
+      * Queens
+      * Pike
+
+    Current development of Patrole is for OpenStack Stein development
+    cycle. Every Patrole commit is also tested against master during
+    the Stein cycle. However, this does not necessarily mean that using
+    Patrole as of this tag will work against a Stein (or future release)
+    cloud.
diff --git a/releasenotes/notes/remove-deprecated-api-extensions-policies-fca3d31c7f5f1f6c.yaml b/releasenotes/notes/remove-deprecated-api-extensions-policies-fca3d31c7f5f1f6c.yaml
new file mode 100644
index 0000000..925791f
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-api-extensions-policies-fca3d31c7f5f1f6c.yaml
@@ -0,0 +1,23 @@
+---
+features:
+  - |
+    A new policy feature flag called
+    ``[policy_feature_flag].removed_nova_policies_stein`` has been added to
+    Patrole's config to handle Nova API extension policies removed in Stein.
+
+    The policy feature flag is applied to tests that validate response bodies
+    for expected attributes previously returned for the following policies
+    that passed authorization:
+
+      - os_compute_api:os-config-drive
+      - os_compute_api:os-extended-availability-zone
+      - os_compute_api:os-extended-status
+      - os_compute_api:os-extended-volumes
+      - os_compute_api:os-keypairs
+      - os_compute_api:os-server-usage
+      - os_compute_api:os-flavor-rxtx
+      - os_compute_api:os-flavor-access (only from /flavors APIs)
+      - os_compute_api:image-size
+
+    Note that not all removed policies are included above because test coverage
+    is missing for them (like os_compute_api:os-security-groups).
diff --git a/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml b/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml
new file mode 100644
index 0000000..53b1710
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    Remove deprecated ``[patrole].enable_rbac`` configuration option. To skip
+    Patrole tests going forward, use an appropriate regex.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index ce29994..eb061a4 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
    :maxdepth: 1
 
    unreleased
+   v0.4.0
    v0.3.0
    v0.2.0
    v0.1.0
diff --git a/releasenotes/source/v0.4.0.rst b/releasenotes/source/v0.4.0.rst
new file mode 100644
index 0000000..2ed32ff
--- /dev/null
+++ b/releasenotes/source/v0.4.0.rst
@@ -0,0 +1,6 @@
+====================
+v0.4.0 Release Notes
+====================
+
+.. release-notes:: 0.4.0 Release Notes
+   :version: 0.4.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 9085c07..a08c27a 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -8,3 +8,4 @@
 nose>=1.3.7 # LGPL
 nosexcover>=1.0.10 # BSD
 oslotest>=3.2.0 # Apache-2.0
+bandit>=1.5 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index a09822f..ea9abf1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -22,8 +22,12 @@
 
 [testenv:pep8]
 basepython = python3
-commands = flake8 {posargs}
-           check-uuid --package patrole_tempest_plugin.tests.api
+deps =
+    -r{toxinidir}/test-requirements.txt
+commands =
+    flake8 {posargs}
+    bandit -r patrole_tempest_plugin -x patrole_tempest_plugin/tests -n 5
+    check-uuid --package patrole_tempest_plugin.tests.api
 
 [testenv:uuidgen]
 basepython = python3