Merge "Add documentation about white box/black box testing to HACKING"
diff --git a/.zuul.yaml b/.zuul.yaml
index 21b5679..085e775 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,31 +1,38 @@
 - job:
     name: patrole-base
     parent: devstack-tempest
-    description: Patrole base job for admin and member roles.
+    description: |
+      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
     timeout: 7800
     roles:
       - zuul: openstack-dev/devstack
-    irrelevant-files:
+    # Define common irrelevant files to use everywhere else
+    irrelevant-files: &patrole-irrelevant-files
       - ^(test-|)requirements.txt$
       - ^.*\.rst$
       - ^doc/.*
+      - ^etc/.*$
       - ^patrole/patrole_tempest_plugin/tests/unit/.*$
+      - ^patrole/patrole_tempest_plugin/hacking/.*$
       - ^releasenotes/.*
       - ^setup.cfg$
     vars:
       devstack_localrc:
-        TEMPEST_PLUGINS: "'{{ ansible_user_dir }}/src/git.openstack.org/openstack/patrole'"
+        TEMPEST_PLUGINS: "'/opt/stack/patrole'"
       devstack_plugins:
         patrole: git://git.openstack.org/openstack/patrole.git
       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
+      tox_envlist: all
+      tox_extra_args: --sitepackages
 
 - job:
     name: patrole-base-multinode
@@ -43,13 +50,7 @@
       - openstack-infra/devstack-gate
       - openstack/tempest
       - openstack/patrole
-    irrelevant-files:
-      - ^(test-|)requirements.txt$
-      - ^.*\.rst$
-      - ^doc/.*
-      - ^patrole/patrole_tempest_plugin/tests/unit/.*$
-      - ^releasenotes/.*
-      - ^setup.cfg$
+    irrelevant-files: *patrole-irrelevant-files
     vars:
       devstack_localrc:
         TEMPEST_PLUGINS: "'{{ ansible_user_dir }}/src/git.openstack.org/openstack/patrole'"
@@ -60,7 +61,8 @@
         neutron: true
       tempest_concurrency: 1
       tempest_test_regex: (?=.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)
-      tox_envlist: all-plugin
+      tox_envlist: all
+      tox_extra_args: --sitepackages
 
 - job:
     name: patrole-admin
@@ -68,7 +70,7 @@
     description: Patrole job for admin role.
     vars:
       devstack_localrc:
-        RBAC_TEST_ROLE: admin
+        RBAC_TEST_ROLES: admin
 
 - job:
     name: patrole-member
@@ -81,7 +83,12 @@
       - stable/pike
     vars:
       devstack_localrc:
-        RBAC_TEST_ROLE: member
+        RBAC_TEST_ROLES: member
+
+- job:
+    name: patrole-member-rocky
+    parent: patrole-member
+    override-checkout: stable/rocky
 
 - job:
     name: patrole-member-queens
@@ -99,7 +106,7 @@
     voting: false
     vars:
       devstack_localrc:
-        RBAC_TEST_ROLE: admin
+        RBAC_TEST_ROLES: admin
 
 - job:
     name: patrole-multinode-member
@@ -107,7 +114,7 @@
     voting: false
     vars:
       devstack_localrc:
-        RBAC_TEST_ROLE: member
+        RBAC_TEST_ROLES: member
 
 - job:
     name: patrole-py35-member
@@ -117,7 +124,7 @@
       devstack_localrc:
         # Use member for py35 because arguably negative testing is more
         # important than admin, which is already covered by patrole-admin job.
-        RBAC_TEST_ROLE: member
+        RBAC_TEST_ROLES: member
         USE_PYTHON3: true
       devstack_services:
         s-account: false
@@ -127,18 +134,78 @@
         # Without Swift, c-bak cannot run (in the gate at least).
         c-bak: false
 
+- job:
+    name: patrole-extension-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).
+
+      Covers Neutron extension functionality only. Should not be used for
+      supporting Neutron plugins like fwaas.
+    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'"
+      devstack_plugins:
+        neutron: git://git.openstack.org/openstack/neutron.git
+        patrole: git://git.openstack.org/openstack/patrole.git
+        neutron-tempest-plugin: git://git.openstack.org/openstack/neutron-tempest-plugin.git
+      devstack_services:
+        tempest: true
+        neutron: true
+        neutron-segments: true
+        neutron-qos: true
+
+- job:
+    name: patrole-extension-member
+    parent: patrole-extension-base
+    voting: false
+    vars:
+      devstack_localrc:
+        RBAC_TEST_ROLES: member
+      tempest_test_regex: (?=.*ExtRbacTest)(^patrole_tempest_plugin\.tests\.api)
+
+- job:
+    name: patrole-extension-admin
+    parent: patrole-extension-base
+    voting: false
+    vars:
+      devstack_localrc:
+        RBAC_TEST_ROLES: admin
+      tempest_test_regex: (?=.*ExtRbacTest)(^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-extension-admin
+        - patrole-extension-member
     gate:
       jobs:
         - patrole-admin
         - patrole-member
+    periodic-stable:
+      jobs:
+        - patrole-member-rocky
+        - patrole-member-queens
+        - patrole-member-pike
diff --git a/HACKING.rst b/HACKING.rst
index 68d0d91..9868e39 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -33,12 +33,15 @@
 The following are Patrole's specific Commandments:
 
 - [P100] The ``rbac_rule_validation.action`` decorator must be applied to
-  an RBAC test
+  all RBAC tests
 - [P101] RBAC test filenames must end with "_rbac.py"; for example,
   test_servers_rbac.py, not test_servers.py
 - [P102] RBAC test class names must end in 'RbacTest'
 - [P103] ``self.client`` must not be used as a client alias; this allows for
   code that is more maintainable and easier to read
+- [P104] RBAC `extension test class`_ names must end in 'ExtRbacTest'
+
+.. _extension test class: https://github.com/openstack/patrole/tree/master/patrole_tempest_plugin/tests/api/network#neutron-extension-rbac-tests
 
 Role Overriding
 ---------------
diff --git a/README.rst b/README.rst
index b07b109..5331445 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::
@@ -153,10 +153,11 @@
 
    will run the same set of tests as the default gate jobs.
 
-   You can also run Patrole tests using `tox`_. To do so, ``cd`` into the
+   You can also run Patrole tests using `tox`_, but as Patrole needs access to
+   global packages use ``--sitepackages`` argument. To do so, ``cd`` into the
    **Tempest** directory and run::
 
-     $ tox -eall-plugin -- patrole_tempest_plugin.tests.api
+     $ tox -eall --sitepackages -- patrole_tempest_plugin.tests.api
 
    .. note::
 
@@ -170,14 +171,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/
@@ -186,20 +187,20 @@
 RBAC Tests
 ----------
 
-To change the role that the patrole tests are being run as, edit
-``rbac_test_role`` in the ``patrole`` section of tempest.conf: ::
+To change the roles that the patrole tests are being run as, edit
+``rbac_test_roles`` in the ``patrole`` section of tempest.conf: ::
 
     [patrole]
-    rbac_test_role = member
+    rbac_test_role = member,reader
     ...
 
 .. note::
 
-  The ``rbac_test_role`` is service-specific. member, for example,
+  The ``rbac_test_roles`` is service-specific. member, for example,
   is an arbitrary role, but by convention is used to designate the default
   non-admin role in the system. Most Patrole tests should be run with
   **admin** and **member** roles. However, other services may use entirely
-  different roles.
+  different roles or role combinations.
 
 For more information about RBAC, reference the `rbac-overview`_
 documentation page.
diff --git a/REVIEWING.rst b/REVIEWING.rst
index 7e3c71f..4ee847f 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -109,6 +109,58 @@
 whether to skip or not.
 
 
+Multi-Policy Guidelines
+-----------------------
+
+Care should be taken when using multiple policies in an RBAC test. The
+following guidelines should be followed before deciding to add multiple
+policies to a Patrole test.
+
+.. _general-multi-policy-guidelines:
+
+General Multi-policy API Code Guidelines
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The list below enumerates guidelines beginning with those with the highest
+priority and ending with those with the lowest priority. A higher priority
+item takes precedence over lower priority items.
+
+#. Order the policies in the ``rules`` parameter chronologically with respect
+   to the order they're called by the API endpoint under test.
+#. Only use policies that map to the API by referencing the appropriate policy
+   in code documentation.
+#. Only include the minimum number of policies needed to test the API
+   correctly: don't add extraneous policies.
+#. If possible, only use policies that directly relate to the API. If the
+   policies are used across multiple APIs, try to omit it. If a "generic"
+   policy needs to be added to get the test to pass, then this is fair game.
+#. Limit the number of policies to a reasonable number, such as 3.
+
+Neutron Multi-policy API Code Guidelines
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Because Neutron can raise a 403 or 404 following failed authorization, Patrole
+uses the ``expected_error_codes`` parameter to accommodate this behavior.
+Each policy action enumerated in ``rules`` must have a corresponding entry
+in ``expected_error_codes``. Each expected error code must be either a 403 or a
+404, which indicates that, when policy enforcement fails for the corresponding
+policy action, that error code is expected by Patrole. For more information
+about these parameters, see :ref:`rbac-validation`.
+
+The list below enumerates additional multi-policy guidelines that apply in
+particular to Neutron. A higher priority item takes precedence over lower
+priority items.
+
+#. Order the expected error codes in the ``expected_error_codes`` parameter
+   chronologically with respect to the order each corresponding policy in
+   ``rules`` is authorized by the API under test.
+#. Ensure the :ref:`neutron-multi-policy-validation` is followed when
+   determining the expected error code for each corresponding policy.
+
+The same guidelines under :ref:`general-multi-policy-guidelines` should be
+applied afterward.
+
+
 Release Notes
 -------------
 Release notes are how we indicate to users and other consumers of Patrole what
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index bd0068b..6b95182 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -14,9 +14,14 @@
     setup_package $PATROLE_DIR -e
 
     if [[ ${DEVSTACK_SERIES} == 'pike' ]]; then
-        if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
-            RBAC_TEST_ROLE="Member"
-        fi
+        IFS=',' read -ra roles_array <<< "$RBAC_TEST_ROLES"
+        RBAC_TEST_ROLES=""
+        for i in "${roles_array[@]}"; do
+            if [[ $i == "member" ]]; then
+                i="Member"
+            fi
+            RBAC_TEST_ROLES="$i,$RBAC_TEST_ROLES"
+        done
 
         # Policies used by Patrole testing that were changed in a backwards-incompatible way.
         # TODO(felipemonteiro): Remove these once stable/pike becomes EOL.
@@ -26,16 +31,38 @@
         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
+
+        # TODO(cl566n): Remove these once stable/pike becomes EOL.
+        # These policies were removed in Stein but are available in Pike.
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_keystone_policies_stein False
+        iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
     fi
 
     if [[ ${DEVSTACK_SERIES} == 'queens' ]]; then
-        if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
-            RBAC_TEST_ROLE="Member"
-        fi
+        IFS=',' read -ra roles_array <<< "$RBAC_TEST_ROLES"
+        RBAC_TEST_ROLES=""
+        for i in "${roles_array[@]}"; do
+            if [[ $i == "member" ]]; then
+                i="Member"
+            fi
+            RBAC_TEST_ROLES="$i,$RBAC_TEST_ROLES"
+        done
+
+        # TODO(cl566n): Remove these once stable/queens becomes EOL.
+        # These policies were removed in Stein but are available in Queens.
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_keystone_policies_stein False
+        iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
     fi
 
-    iniset $TEMPEST_CONFIG patrole enable_rbac True
-    iniset $TEMPEST_CONFIG patrole rbac_test_role $RBAC_TEST_ROLE
+    if [[ ${DEVSTACK_SERIES} == 'rocky' ]]; then
+        # TODO(cl566n): Policies used by Patrole testing. Remove these once stable/rocky becomes EOL.
+        iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
+        iniset $TEMPEST_CONFIG policy-feature-enabled removed_keystone_policies_stein False
+    fi
+
+    iniset $TEMPEST_CONFIG patrole rbac_test_roles $RBAC_TEST_ROLES
 }
 
 if is_service_enabled tempest; then
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index f6aaf04..05716fe 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -7,34 +7,28 @@
 file. All Patrole-specific configuration options should be included under
 the ``patrole`` group.
 
-RBAC Test Role
---------------
+RBAC Test Roles
+---------------
 
-The RBAC test role governs which role is used when running Patrole tests. For
-example, setting ``rbac_test_role`` to "admin" will execute all RBAC tests
-using admin credentials. Changing the ``rbac_test_role`` value will `override`
-Tempest's primary credentials to use that role.
+The RBAC test roles govern the list of roles to be used when running Patrole
+tests. For example, setting ``rbac_test_roles`` to "admin" will execute all
+RBAC tests using admin credentials. Changing the ``rbac_test_roles`` value
+will `override` Tempest's primary credentials to use that role.
 
-This implies that, if ``rbac_test_role`` is "admin", regardless of the Tempest
+This implies that, if ``rbac_test_roles`` is "admin", regardless of the Tempest
 credentials used by a client, the client will be calling APIs using the admin
 role. That is, ``self.os_primary.servers_client`` will run as though it were
 ``self.os_admin.servers_client``.
 
-Similarly, setting ``rbac_test_role`` to a non-admin role results in Tempest's
-primary credentials being overridden by the role specified by
-``rbac_test_role``.
+Similarly, setting ``rbac_test_roles`` with various roles, results in
+Tempest's primary credentials being overridden by the roles specified by
+``rbac_test_roles``.
 
 .. note::
 
-    Only the role of the primary Tempest credentials ("os_primary") is
+    Only the roles of the primary Tempest credentials ("os_primary") are
     modified. The ``user_id`` and ``project_id`` remain unchanged.
 
-Enable RBAC
------------
-
-Given the value of ``enable_rbac``, enables or disables Patrole tests. If
-``enable_rbac`` is ``False``, then Patrole tests are skipped.
-
 Custom Policy Files
 -------------------
 
diff --git a/doc/source/framework/overview.rst b/doc/source/framework/overview.rst
index 4902f7b..6f72eec 100644
--- a/doc/source/framework/overview.rst
+++ b/doc/source/framework/overview.rst
@@ -1,17 +1,19 @@
 RBAC Testing Validation
 =======================
 
---------
-Overview
---------
+.. _validation-workflow-overview:
+
+----------------------------
+Validation Workflow Overview
+----------------------------
 
 RBAC testing validation is broken up into 3 stages:
 
 #. "Expected" stage. Determine whether the test should be able to succeed
-   or fail based on the test role defined by ``[patrole] rbac_test_role``)
+   or fail based on the test roles defined by ``[patrole] rbac_test_roles``)
    and the policy action that the test enforces.
 #. "Actual" stage. Run the test by calling the API endpoint that enforces
-   the expected policy action using the test role.
+   the expected policy action using the test roles.
 #. Comparing the outputs from both stages for consistency. A "consistent"
    result is treated as a pass and an "inconsistent" result is treated
    as a failure. "Consistent" (or successful) cases include:
@@ -36,6 +38,16 @@
    ``oslo.policy`` or a 403 from the API call and a ``True`` result from
    ``oslo.policy`` are failing results.
 
+.. warning::
+
+  Note that Patrole cannot currently derive the expected policy result for
+  service-specific ``oslo.policy`` `checks`_, like Neutron's `FieldCheck`_,
+  because such checks are contained within the service's code base itself,
+  which Patrole cannot import.
+
+.. _checks: https://docs.openstack.org/oslo.policy/latest/reference/api/oslo_policy.policy.html#generic-checks
+.. _FieldCheck: https://docs.openstack.org/neutron/pike/contributor/internals/policy.html#fieldcheck-verify-resource-attributes
+
 -------------------------------
 The RBAC Rule Validation Module
 -------------------------------
@@ -51,7 +63,7 @@
 ---------------------------
 
 Module called by :ref:`rbac-validation` to verify whether the test
-role is allowed to execute a policy action by querying ``oslo.policy`` with
+roles are allowed to execute a policy action by querying ``oslo.policy`` with
 required test data. The result is used by :ref:`rbac-validation` as the
 "Expected" result.
 
diff --git a/doc/source/framework/policy_authority.rst b/doc/source/framework/policy_authority.rst
index 822c7b6..37b698c 100644
--- a/doc/source/framework/policy_authority.rst
+++ b/doc/source/framework/policy_authority.rst
@@ -60,3 +60,4 @@
 .. automodule:: patrole_tempest_plugin.policy_authority
    :members:
    :undoc-members:
+   :special-members:
diff --git a/doc/source/framework/rbac_authority.rst b/doc/source/framework/rbac_authority.rst
index 84c372b..40f2a8d 100644
--- a/doc/source/framework/rbac_authority.rst
+++ b/doc/source/framework/rbac_authority.rst
@@ -35,3 +35,4 @@
 .. automodule:: patrole_tempest_plugin.rbac_authority
    :members:
    :undoc-members:
+   :special-members:
diff --git a/doc/source/framework/rbac_utils.rst b/doc/source/framework/rbac_utils.rst
index 7143928..b13a4a3 100644
--- a/doc/source/framework/rbac_utils.rst
+++ b/doc/source/framework/rbac_utils.rst
@@ -23,156 +23,14 @@
 and test execution, respectively. This is especially true when considering
 custom policy rule definitions, which can be arbitrarily complex.
 
-.. _role-overriding:
-
-Role Overriding
-^^^^^^^^^^^^^^^
-
-Role overriding is the way Patrole is able to create resources and delete
-resources -- including those that require admin credentials -- while still
-being able to exercise the same set of Tempest credentials to perform the API
-action that authorizes the policy under test, by manipulating the role of
-the Tempest credentials.
-
-Patrole implicitly splits up each test into 3 stages: set up, test execution,
-and teardown.
-
-The role workflow is as follows:
-
-#. Setup: Admin role is used automatically. The primary credentials are
-   overridden with the admin role.
-#. Test execution: ``[patrole] rbac_test_role`` is used manually via the
-   call to ``with rbac_utils.override_role(self)``. Everything that
-   is executed within this contextmanager uses the primary
-   credentials overridden with the ``[patrole] rbac_test_role``.
-#. Teardown: Admin role is used automatically. The primary credentials have
-   been overridden with the admin role.
-
-.. _Tempest credentials: https://docs.openstack.org/tempest/latest/library/credential_providers.html
-.. _dynamic credentials: https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials
-
-Test Setup
-----------
-
-Automatic role override in background.
-
-Resources can be set up inside the ``resource_setup`` class method that Tempest
-provides. These resources are typically reserved for "expensive" resources
-in terms of memory or storage requirements, like volumes and VMs. These
-resources are **always** created via the admin role; Patrole automatically
-handles this.
-
-Like Tempest, however, Patrole must also create resources inside tests
-themselves. At the beginning of each test, the primary credentials have already
-been overridden with the admin role. One can create whatever test-level
-resources one needs, without having to worry about permissions.
-
-Test Execution
---------------
-
-Manual role override required.
-
-"Test execution" here means calling the API endpoint that enforces the policy
-action expected by the ``rbac_rule_validation`` decorator. Test execution
-should be performed *only after* calling
-``with rbac_utils.override_role(self)``.
-
-Immediately after that call, the API endpoint that enforces the policy should
-be called.
-
-Examples
-^^^^^^^^
-
-Always use the contextmanager before calling the API that enforces the
-expected policy action.
-
-Example::
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-aggregates:show")
-    def test_show_aggregate_rbac(self):
-        # Do test setup before the ``override_role`` call.
-        aggregate_id = self._create_aggregate()
-        # Call the ``override_role`` method so that the primary credentials
-        # have the test role needed for test execution.
-        with self.rbac_utils.override_role(self):
-            self.aggregates_client.show_aggregate(aggregate_id)
-
-When using a waiter, do the wait outside the contextmanager. "Waiting" always
-entails executing a ``GET`` request to the server, until the state of the
-returned resource matches a desired state. These ``GET`` requests enforce
-a different policy than the one expected. This is undesirable because
-Patrole should only test policies in isolation from one another.
-
-Otherwise, the test result will be tainted, because instead of only the
-expected policy getting enforced with the ``os_primary`` role, at least
-two policies get enforced.
-
-Example using waiter::
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-admin-password")
-    def test_change_server_password(self):
-        original_password = self.servers_client.show_password(
-            self.server['id'])
-        self.addCleanup(self.servers_client.change_password, self.server['id'],
-                        adminPass=original_password)
-
-        with self.rbac_utils.override_role(self):
-            self.servers_client.change_password(
-                self.server['id'], adminPass=data_utils.rand_password())
-        # Call the waiter outside the ``override_role`` contextmanager, so that
-        # it is executed with admin role.
-        waiters.wait_for_server_status(
-            self.servers_client, self.server['id'], 'ACTIVE')
-
-Below is an example of a method that enforces multiple policies getting
-called inside the contextmanager. The ``_complex_setup_method`` below
-performs the correct API that enforces the expected policy -- in this
-case ``self.resources_client.create_resource`` -- but then proceeds to
-use a waiter.
-
-Incorrect::
-
-    def _complex_setup_method(self):
-        resource = self.resources_client.create_resource(
-            **kwargs)['resource']
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self._delete_resource, resource)
-        waiters.wait_for_resource_status(
-            self.resources_client, resource['id'], 'available')
-        return resource
-
-    @rbac_rule_validation.action(
-        service="example-service",
-        rule="example-rule")
-    def test_change_server_password(self):
-        # Never call a helper function inside the contextmanager that calls a
-        # bunch of APIs. Only call the API that enforces the policy action
-        # contained in the decorator above.
-        with self.rbac_utils.override_role(self):
-            self._complex_setup_method()
-
-To fix this test, see the "Example using waiter" section above. It is
-recommended to re-implement the logic in a helper method inside a test such
-that only the relevant API is called inside the contextmanager, with
-everything extraneous outside.
-
-Test Cleanup
-------------
-
-Automatic role override in background.
-
-After the test -- no matter whether it ended successfully or in failure --
-the credentials are overridden with the admin role by the Patrole framework,
-*before* ``tearDown`` or ``tearDownClass`` are called. This means that
-resources are always cleaned up using the admin role.
-
 Implementation
 --------------
 
 .. automodule:: patrole_tempest_plugin.rbac_utils
    :members:
    :private-members:
+   :special-members:
+
+.. _Tempest credentials: https://docs.openstack.org/tempest/latest/library/credential_providers.html
+.. _dynamic credentials: https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials
+
diff --git a/doc/source/framework/rbac_validation.rst b/doc/source/framework/rbac_validation.rst
index 186dfe2..6cd1534 100644
--- a/doc/source/framework/rbac_validation.rst
+++ b/doc/source/framework/rbac_validation.rst
@@ -17,3 +17,4 @@
 .. automodule:: patrole_tempest_plugin.rbac_rule_validation
    :members:
    :private-members:
+   :special-members:
diff --git a/doc/source/framework/requirements_authority.rst b/doc/source/framework/requirements_authority.rst
index 6c4fcc0..cf7c51c 100644
--- a/doc/source/framework/requirements_authority.rst
+++ b/doc/source/framework/requirements_authority.rst
@@ -7,8 +7,9 @@
 --------
 
 Requirements-driven approach to declaring the expected RBAC test results
-referenced by Patrole. Uses a high-level YAML syntax to crystallize policy
-requirements concisely and unambiguously.
+referenced by Patrole. These requirements express the *intention* behind the
+policy. A high-level YAML syntax is used to concisely and clearly map each
+policy action to the list of associated roles.
 
 .. note::
 
@@ -29,10 +30,6 @@
 :ref:`custom-requirements-file` which precisely defines the cloud's RBAC
 requirements.
 
-Using a high-level declarative language, the requirements are captured
-unambiguously in the :ref:`custom-requirements-file`, allowing operators to
-validate their requirements against their OpenStack cloud.
-
 This validation approach should be used when:
 
 * The cloud has heavily customized policy files that require careful validation
@@ -71,31 +68,87 @@
 file must be located on the same host that Patrole runs on. The YAML
 file should be written as follows:
 
+  .. code-block:: yaml
+
+      <service_foo>:
+        <logical_or_example>:
+          - <allowed_role_1>
+          - <allowed_role_2>
+        <logical_and_example>:
+          - <allowed_role_3>, <allowed_role_4>
+      <service_bar>:
+        <logical_not_example>:
+          - <!disallowed_role_5>
+
+Where:
+
+* ``service`` - the service that is being tested (Cinder, Nova, etc.).
+* ``api_action`` - the policy action that is being tested. Examples:
+
+  * volume:create
+  * os_compute_api:servers:start
+  * add_image
+
+* ``allowed_role`` - the ``oslo.policy`` role that is allowed to perform the
+  API.
+
+Each item under ``logical_or_example`` is "logical OR"-ed together. Each role
+in the comma-separated string under ``logical_and_example`` is "logical AND"-ed
+together. And each item prefixed with "!" under ``logical_not_example`` is
+"logical negated".
+
+.. note::
+
+  The custom requirements file only allows policy actions to be mapped to
+  the associated roles that define it. Complex ``oslo.policy`` constructs
+  like ``literals`` or ``GenericChecks`` are not supported. For more
+  information, reference the `oslo.policy documentation`_.
+
+.. _oslo.policy documentation: https://docs.openstack.org/oslo.policy/latest/reference/api/oslo_policy.policy.html#policy-rule-expressions
+
+Examples
+~~~~~~~~
+
+Items within ``api_action`` are considered as logical or, so you may read:
+
 .. code-block:: yaml
 
     <service_foo>:
+      # "api_action_a: allowed_role_1 or allowed_role_2 or allowed_role_3"
       <api_action_a>:
         - <allowed_role_1>
         - <allowed_role_2>
         - <allowed_role_3>
-      <api_action_b>:
-        - <allowed_role_2>
-        - <allowed_role_4>
-    <service_bar>:
-      <api_action_c>:
+
+as ``<allowed_role_1> or <allowed_role_2> or <allowed_role_3>``.
+
+Roles within comma-separated items are considered as logic and, so you may
+read:
+
+.. code-block:: yaml
+
+    <service_foo>:
+      # "api_action_a: (allowed_role_1 and allowed_role_2) or allowed_role_3"
+      <api_action_a>:
+        - <allowed_role_1>, <allowed_role_2>
         - <allowed_role_3>
 
-Where:
+as ``<allowed_role_1> and <allowed_role_2> or <allowed_role_3>``.
 
-service = the service that is being tested (Cinder, Nova, etc.).
+Also negative roles may be defined with an exclamation mark ahead of role:
 
-api_action = the policy action that is being tested. Examples:
+.. code-block:: yaml
 
-* volume:create
-* os_compute_api:servers:start
-* add_image
+    <service_foo>:
+      # "api_action_a: (allowed_role_1 and allowed_role_2 and not
+      # disallowed_role_4) or allowed_role_3"
+      <api_action_a>:
+        - <allowed_role_1>, <allowed_role_2>, !<disallowed_role_4>
+        - <allowed_role_3>
 
-allowed_role = the ``oslo.policy`` role that is allowed to perform the API.
+This example must be read as ``<allowed_role_1> and <allowed_role_2> and not
+<disallowed_role_4> or <allowed_role_3>``.
+
 
 Implementation
 --------------
@@ -103,3 +156,4 @@
 .. automodule:: patrole_tempest_plugin.requirements_authority
    :members:
    :undoc-members:
+   :special-members:
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 2dbf63b..a9dcdc0 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -17,6 +17,7 @@
    :maxdepth: 2
 
    rbac-overview
+   multi-policy-validation
 
 User's Guide
 ============
@@ -54,6 +55,7 @@
 
    HACKING
    REVIEWING
+   test_writing_guide
 
 Framework
 ---------
diff --git a/doc/source/multi-policy-validation.rst b/doc/source/multi-policy-validation.rst
new file mode 100644
index 0000000..d38b31e
--- /dev/null
+++ b/doc/source/multi-policy-validation.rst
@@ -0,0 +1,187 @@
+.. _multi-policy-validation:
+
+=======================
+Multi-policy Validation
+=======================
+
+Introduction
+------------
+
+Multi-policy validation exists in Patrole because if one policy were assumed,
+then tests could fail because they would not consider all the policies actually
+being enforced. The reasoning can be found in `this spec`_. Basically,
+since Patrole derives the expected test result dynamically in order to test any
+role, each policy enforced by the API under test must be considered to derive
+an accurate expected test result, or else the expected and actual test
+results will not always match, resulting in overall test failure. For more
+information about Patrole's RBAC validation work flow, reference
+:ref:`rbac-validation`.
+
+Multi-policy support allows Patrole to more accurately offer RBAC tests for API
+endpoints that enforce multiple policy actions.
+
+.. _this spec: https://github.com/openstack/qa-specs/blob/master/specs/patrole/rbac-testing-multiple-policies.rst
+
+Scope
+-----
+
+Multiple policies should be applied only to tests that require them. Not all
+API endpoints enforce multiple policies. Some services consistently enforce
+1 policy per API, while on the other side of the spectrum, services like
+Neutron have much more involved policy enforcement work flows. See
+:ref:`neutron-multi-policy-validation` for more information.
+
+.. _neutron-multi-policy-validation:
+
+Neutron Multi-policy Validation
+-------------------------------
+
+Neutron can raise different :ref:`policy-error-codes` following failed policy
+authorization. Many endpoints in Neutron enforce multiple policies, which
+complicates matters when trying to determine whether the endpoint raises a
+403 or a 404 following unauthorized access.
+
+Multi-policy Examples
+---------------------
+
+General Examples
+^^^^^^^^^^^^^^^^
+
+Below is an example of multi-policy validation for a carefully chosen Nova API:
+
+.. code-block:: python
+
+  @rbac_rule_validation.action(
+  service="nova",
+  rules=["os_compute_api:os-lock-server:unlock",
+         "os_compute_api:os-lock-server:unlock:unlock_override"])
+  @decorators.idempotent_id('40dfeef9-73ee-48a9-be19-a219875de457')
+  def test_unlock_server_override(self):
+      """Test force unlock server, part of os-lock-server.
+
+      In order to trigger the unlock:unlock_override policy instead
+      of the unlock policy, the server must be locked by a different
+      user than the one who is attempting to unlock it.
+      """
+      self.os_admin.servers_client.lock_server(self.server['id'])
+      self.addCleanup(self.servers_client.unlock_server, self.server['id'])
+
+      with self.rbac_utils.override_role(self):
+          self.servers_client.unlock_server(self.server['id'])
+
+While the ``expected_error_codes`` parameter is omitted in the example above,
+Patrole automatically populates it with a 403 for each policy in ``rules``.
+Therefore, in the example above, the following expected error codes/rules
+relationship is observed:
+
+* "os_compute_api:os-lock-server:unlock" => 403
+* "os_compute_api:os-lock-server:unlock:unlock_override"  => 403
+
+Below is an example that uses ``expected_error_codes`` to account for the
+fact that Neutron is expected to raise a ``404`` on the first policy that
+is enforced server-side ("get_port"). Also, in this example, soft authorization
+is performed, meaning that it is necessary to check the response body for an
+attribute that is added only following successful policy authorization.
+
+.. code-block:: python
+
+    @utils.requires_ext(extension='binding', service='network')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_port",
+                                        "get_port:binding:vif_type"],
+                                 expected_error_codes=[404, 403])
+    @decorators.idempotent_id('125aff0b-8fed-4f8e-8410-338616594b06')
+    def test_show_port_binding_vif_type(self):
+
+        # Verify specific fields of a port
+        fields = ['binding:vif_type']
+
+        with self.rbac_utils.override_role(self):
+            retrieved_port = self.ports_client.show_port(
+                self.port['id'], fields=fields)['port']
+
+        # Rather than throwing a 403, the field is not present, so raise exc.
+        if fields[0] not in retrieved_port:
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='binding:vif_type')
+
+Note that in the example above, failure to authorize
+"get_port:binding:vif_type" results in the response body getting successfully
+returned by the server, but without additional dictionary keys. If Patrole
+fails to find those expected keys, it *acts as though* a 403 was thrown (by
+raising an exception itself, the ``rbac_rule_validation`` decorator handles
+the rest).
+
+Neutron Examples
+^^^^^^^^^^^^^^^^
+
+A basic Neutron example that only expects 403's to be raised:
+
+.. code-block:: python
+
+    @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])
+    @decorators.idempotent_id('51adf2a7-739c-41e0-8857-3b4c460cbd24')
+    def test_create_network_router_external(self):
+
+        """Create External Router Network Test
+
+        RBAC test for the neutron create_network:router:external policy
+        """
+        with self.rbac_utils.override_role(self):
+            self._create_network(router_external=True)
+
+Note that above the following expected error codes/rules relationship is
+observed:
+
+* "create_network" => 403
+* "create_network:router:external"  => 403
+
+A more involved example that expects a 404 to be raised, should the first
+policy under ``rules`` fail authorization, and a 403 to be raised for any
+subsequent policy authorization failure:
+
+.. code-block:: python
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_network",
+                                        "update_network",
+                                        "update_network:shared"],
+                                 expected_error_codes=[404, 403, 403])
+    @decorators.idempotent_id('37ea3e33-47d9-49fc-9bba-1af98fbd46d6')
+    def test_update_network_shared(self):
+
+        """Update Shared Network Test
+
+        RBAC test for the neutron update_network:shared policy
+        """
+        with self.rbac_utils.override_role(self):
+            self._update_network(shared_network=True)
+        self.addCleanup(self._update_network, shared_network=False)
+
+Note that above the following expected error codes/rules relationship is
+observed:
+
+* "get_network" => 404
+* "update_network"  => 403
+* "update_network:shared" => 403
+
+Limitations
+-----------
+
+Multi-policy validation in RBAC tests comes with limitations, due to technical
+and practical challenges.
+
+Technically, there are challenges associated with multiple policies across
+cross-service API communication in OpenStack, such as between Nova and Cinder
+or Nova and Neutron. The current framework does not account for these
+cross-service policy enforcement workflows, and it is still up for debate
+whether it should.
+
+Practically, it is not possible to enumerate every policy enforced by every API
+in Patrole, as the maintenance overhead would be huge.
+
+.. _Neutron policy documentation: https://docs.openstack.org/neutron/pike/contributor/internals/policy.html
diff --git a/doc/source/rbac-overview.rst b/doc/source/rbac-overview.rst
index 4b2023e..cc47f75 100644
--- a/doc/source/rbac-overview.rst
+++ b/doc/source/rbac-overview.rst
@@ -59,7 +59,7 @@
 services are secure, from an RBAC perspective, for each release.
 
 Patrole strives to validate RBAC by using the policy in code documentation,
-wherever possible.
+wherever possible. See :ref:`validation-workflow-overview` for more details.
 
 .. _custom-policies:
 
@@ -87,8 +87,7 @@
 
 This has implications on Patrole's :ref:`design-principles`: validating custom
 overrides requires the ability to handle arbitrary roles, which requires logic
-capable of dynamically determining expected test behavior. See
-:ref:`rbac-validation` for more details.
+capable of dynamically determining expected test behavior.
 
 Note that support for custom policies is limited. This is because custom
 policies can be arbitrarily complex, requiring that tests be very robust
@@ -127,12 +126,9 @@
   degree of log tracing is required by developers to confirm that the expected
   policies are getting enforced, prior to the tests getting merged.
 
-.. todo::
+For more information, see :ref:`multi-policy-validation`.
 
-  Link to multi-policy validation documentation section once it has been
-  written.
-
-.. _error-codes:
+.. _policy-error-codes:
 
 Error Codes
 -----------
@@ -142,6 +138,39 @@
 as well. See Neutron's `authorization policy enforcement`_ documentation
 for more details.
 
+Admin Context Policy
+--------------------
+
+The so-called "admin context" policy refers to the following policy definition
+(using the legacy policy file syntax):
+
+.. code-block:: javascript
+
+  {
+    "context_is_admin": "role:admin"
+    ...
+  }
+
+Which is unfortunately used to bypass ``oslo.policy`` authorization checks,
+for example:
+
+.. code-block:: python
+
+  # This function is responsible for calling oslo.policy to check whether
+  # requests are authorized to perform an API action.
+  def enforce(context, action, target, [...]):
+    # Here this condition, if True, skips over the enforce call below which
+    # is what calls oslo.policy.
+    if context.is_admin:
+        return True
+    _ENFORCER.enforce([...])  # This is what can be skipped over.
+    [...]
+
+This type of behavior is currently present in many services. Unless such
+logic is removed in the future for services that implement it, Patrole
+won't really be able to validate that admin role works from an ``oslo.policy``
+perspective.
+
 Glossary
 --------
 
@@ -166,7 +195,7 @@
     in an exception getting raised or a boolean value getting returned.
     Hard authorization results in an exception getting raised. Usually, this
     results in a ``403 Forbidden`` getting returned for unauthorized requests.
-    (See :ref:`error-codes` for further details.)
+    (See :ref:`policy-error-codes` for further details.)
 
     Related term: :term:`soft authorization`.
 
diff --git a/doc/source/test_writing_guide.rst b/doc/source/test_writing_guide.rst
new file mode 100644
index 0000000..4e0f0be
--- /dev/null
+++ b/doc/source/test_writing_guide.rst
@@ -0,0 +1,166 @@
+Patrole Test Writing Overview
+=============================
+
+Introduction
+------------
+
+Patrole tests are broken up into 3 stages:
+
+#. :ref:`rbac-test-setup`
+#. :ref:`rbac-test-execution`
+#. :ref:`rbac-test-cleanup`
+
+See the :ref:`framework overview documentation <validation-workflow-overview>`
+for a high-level explanation of the entire testing work flow and framework
+implementation. The guide that follows is concerned with helping developers
+know how to write Patrole tests.
+
+.. _role-overriding:
+
+Role Overriding
+---------------
+
+Role overriding is the way Patrole is able to create resources and delete
+resources -- including those that require admin credentials -- while still
+being able to exercise the same set of Tempest credentials to perform the API
+action that authorizes the policy under test, by manipulating roles of the
+Tempest credentials.
+
+Patrole implicitly splits up each test into 3 stages: set up, test execution,
+and teardown.
+
+The role workflow is as follows:
+
+#. Setup: Admin role is used automatically. The primary credentials are
+   overridden with the admin role.
+#. Test execution: ``[patrole] rbac_test_roles`` is used manually via the
+   call to ``with rbac_utils.override_role(self)``. Everything that
+   is executed within this contextmanager uses the primary
+   credentials overridden with the ``[patrole] rbac_test_roles``.
+#. Teardown: Admin role is used automatically. The primary credentials have
+   been overridden with the admin role.
+
+.. _rbac-test-setup:
+
+Test Setup
+----------
+
+Automatic role override in background.
+
+Resources can be set up inside the ``resource_setup`` class method that Tempest
+provides. These resources are typically reserved for "expensive" resources
+in terms of memory or storage requirements, like volumes and VMs. These
+resources are **always** created via the admin role; Patrole automatically
+handles this.
+
+Like Tempest, however, Patrole must also create resources inside tests
+themselves. At the beginning of each test, the primary credentials have already
+been overridden with the admin role. One can create whatever test-level
+resources one needs, without having to worry about permissions.
+
+.. _rbac-test-execution:
+
+Test Execution
+--------------
+
+Manual role override required.
+
+"Test execution" here means calling the API endpoint that enforces the policy
+action expected by the ``rbac_rule_validation`` decorator. Test execution
+should be performed *only after* calling
+``with rbac_utils.override_role(self)``.
+
+Immediately after that call, the API endpoint that enforces the policy should
+be called.
+
+Examples
+^^^^^^^^
+
+Always use the contextmanager before calling the API that enforces the
+expected policy action.
+
+Example::
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rules=["os_compute_api:os-aggregates:show"])
+    def test_show_aggregate_rbac(self):
+        # Do test setup before the ``override_role`` call.
+        aggregate_id = self._create_aggregate()
+        # Call the ``override_role`` method so that the primary credentials
+        # have the test role needed for test execution.
+        with self.rbac_utils.override_role(self):
+            self.aggregates_client.show_aggregate(aggregate_id)
+
+When using a waiter, do the wait outside the contextmanager. "Waiting" always
+entails executing a ``GET`` request to the server, until the state of the
+returned resource matches a desired state. These ``GET`` requests enforce
+a different policy than the one expected. This is undesirable because
+Patrole should only test policies in isolation from one another.
+
+Otherwise, the test result will be tainted, because instead of only the
+expected policy getting enforced with the ``os_primary`` role, at least
+two policies get enforced.
+
+Example using waiter::
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rules=["os_compute_api:os-admin-password"])
+    def test_change_server_password(self):
+        original_password = self.servers_client.show_password(
+            self.server['id'])
+        self.addCleanup(self.servers_client.change_password, self.server['id'],
+                        adminPass=original_password)
+
+        with self.rbac_utils.override_role(self):
+            self.servers_client.change_password(
+                self.server['id'], adminPass=data_utils.rand_password())
+        # Call the waiter outside the ``override_role`` contextmanager, so that
+        # it is executed with admin role.
+        waiters.wait_for_server_status(
+            self.servers_client, self.server['id'], 'ACTIVE')
+
+Below is an example of a method that enforces multiple policies getting
+called inside the contextmanager. The ``_complex_setup_method`` below
+performs the correct API that enforces the expected policy -- in this
+case ``self.resources_client.create_resource`` -- but then proceeds to
+use a waiter.
+
+Incorrect::
+
+    def _complex_setup_method(self):
+        resource = self.resources_client.create_resource(
+            **kwargs)['resource']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self._delete_resource, resource)
+        waiters.wait_for_resource_status(
+            self.resources_client, resource['id'], 'available')
+        return resource
+
+    @rbac_rule_validation.action(
+        service="example-service",
+        rules=["example-rule"])
+    def test_change_server_password(self):
+        # Never call a helper function inside the contextmanager that calls a
+        # bunch of APIs. Only call the API that enforces the policy action
+        # contained in the decorator above.
+        with self.rbac_utils.override_role(self):
+            self._complex_setup_method()
+
+To fix this test, see the "Example using waiter" section above. It is
+recommended to re-implement the logic in a helper method inside a test such
+that only the relevant API is called inside the contextmanager, with
+everything extraneous outside.
+
+.. _rbac-test-cleanup:
+
+Test Cleanup
+------------
+
+Automatic role override in background.
+
+After the test -- no matter whether it ended successfully or in failure --
+the credentials are overridden with the admin role by the Patrole framework,
+*before* ``tearDown`` or ``tearDownClass`` are called. This means that
+resources are always cleaned up using the admin role.
diff --git a/etc/patrole.conf.sample b/etc/patrole.conf.sample
index 6de073d..6433f40 100644
--- a/etc/patrole.conf.sample
+++ b/etc/patrole.conf.sample
@@ -7,12 +7,17 @@
 # From patrole.config
 #
 
-# The current RBAC role against which to run Patrole
-# tests. (string value)
-#rbac_test_role = admin
+# DEPRECATED: The current RBAC role against which to run
+# Patrole tests. (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: This option is deprecated and being
+# replaced with ``rbac_test_roles``.
+#rbac_test_role =
 
-# Enables RBAC tests. (boolean value)
-#enable_rbac = true
+# The current RBAC roles to be assigned to Keystone
+# Group against which to run Patrole tests. (list value)
+#rbac_test_roles = admin
 
 # List of the paths to search for policy files. Each
 # policy path assumes that the service name is included in the path
@@ -20,9 +25,11 @@
 # assumes Patrole is on the same host as the policy files. The paths
 # should be
 # ordered by precedence, with high-priority paths before low-priority
-# paths. The
-# first path that is found to contain the service's policy file will
-# be used.
+# paths. All
+# the paths that are found to contain the service's policy file will
+# be used and
+# all policy files will be merged. Allowed ``json`` or ``yaml``
+# formats.
 #  (list value)
 #custom_policy_files = /etc/%s/policy.json
 
@@ -150,3 +157,16 @@
 # This policy
 # was changed in a backwards-incompatible way. (boolean value)
 #volume_extension_volume_actions_unreserve_policy = true
+
+# 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. (boolean value)
+#removed_nova_policies_stein = true
+
+# Are the Cinder API extension policies available in the
+# cloud (e.g. [create|update|get|delete]_encryption_policy)? These
+# policies are
+# added in Stein. (boolean value)
+#added_cinder_policies_stein = true
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index 4dc27b9..90d5fba 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -22,20 +22,24 @@
 PatroleGroup = [
     cfg.StrOpt('rbac_test_role',
                default='admin',
-               help="""The current RBAC role against which to run Patrole
-tests."""),
-    cfg.BoolOpt('enable_rbac',
-                default=True,
-                help="Enables RBAC tests."),
-    # TODO(rb560u): There needs to be support for reading these JSON files from
-    # other hosts. It may be possible to leverage the v3 identity policy API.
+               deprecated_for_removal=True,
+               deprecated_reason="""This option is deprecated and being
+replaced with ``rbac_test_roles``.
+""",
+               help="""The current RBAC role against which to run
+Patrole tests."""),
+    cfg.ListOpt('rbac_test_roles',
+                help="""List of the RBAC roles against which to run
+Patrole tests.""",
+                default=['admin']),
     cfg.ListOpt('custom_policy_files',
                 default=['/etc/%s/policy.json'],
                 help="""List of the paths to search for policy files. Each
 policy path assumes that the service name is included in the path once. Also
 assumes Patrole is on the same host as the policy files. The paths should be
-ordered by precedence, with high-priority paths before low-priority paths. The
-first path that is found to contain the service's policy file will be used.
+ordered by precedence, with high-priority paths before low-priority paths. All
+the paths that are found to contain the service's policy file will be used and
+all policy files will be merged. Allowed ``json`` or ``yaml`` formats.
 """),
     cfg.BoolOpt('test_custom_requirements',
                 default=False,
@@ -155,7 +159,27 @@
                 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."""),
+    cfg.BoolOpt('removed_keystone_policies_stein',
+                default=True,
+                help="""Are the obsolete Keystone policies available in the
+cloud (e.g. identity:[create|update|get|delete]_credential)? These policies
+were removed in Stein."""),
+    cfg.BoolOpt('added_cinder_policies_stein',
+                default=True,
+                help="""Are the Cinder Stein policies available in the cloud
+(e.g. [create|update|get|delete]_encryption_policy)? These policies are added
+in Stein.""")
 ]
 
 
diff --git a/patrole_tempest_plugin/hacking/checks.py b/patrole_tempest_plugin/hacking/checks.py
index d106da8..d7b772d 100644
--- a/patrole_tempest_plugin/hacking/checks.py
+++ b/patrole_tempest_plugin/hacking/checks.py
@@ -36,6 +36,8 @@
 RULE_VALIDATION_DECORATOR = re.compile(
     r'\s*@rbac_rule_validation.action\(.*')
 IDEMPOTENT_ID_DECORATOR = re.compile(r'\s*@decorators\.idempotent_id\((.*)\)')
+EXT_RBAC_TEST = re.compile(
+    r"class .+\(.+ExtRbacTest\)|class .+ExtRbacTest\(.+\)")
 
 have_rbac_decorator = False
 
@@ -211,6 +213,44 @@
             return 0, "Do not use 'self.client' as a service client alias"
 
 
+def no_extension_rbac_test_suffix_in_plugin_test_class_name(physical_line,
+                                                            filename):
+    """Check that Extension RBAC class names end with "ExtRbacTest"
+
+    P104
+    """
+    suffix = "ExtRbacTest"
+    if "patrole_tempest_plugin/tests/api" in filename:
+        if EXT_RBAC_TEST.match(physical_line):
+            subclass, superclass = physical_line.split('(')
+            subclass = subclass.split('class')[1].strip()
+            superclass = superclass.split(')')[0].strip()
+            if "." in superclass:
+                superclass = superclass.split(".")[1]
+
+            both_have = all(
+                clazz.endswith(suffix) for clazz in [subclass, superclass])
+            none_have = not any(
+                clazz.endswith(suffix) for clazz in [subclass, superclass])
+
+            if not (both_have or none_have):
+                if (subclass.startswith("Base") and
+                        superclass.startswith("Base")):
+                    return
+
+                # Case 1: Subclass of "BaseExtRbacTest" must end in `suffix`
+                # Case 2: Subclass that ends in `suffix` must inherit from base
+                # class ending in `suffix`.
+                if not subclass.endswith(suffix):
+                    error = ("Plugin RBAC test subclasses must end in "
+                             "'ExtRbacTest'")
+                    return len(subclass) - 1, error
+                elif not superclass.endswith(suffix):
+                    error = ("Plugin RBAC test subclasses must inherit from a "
+                             "'ExtRbacTest' base class")
+                    return len(superclass) - 1, error
+
+
 def factory(register):
     register(import_no_clients_in_api_tests)
     register(no_setup_teardown_class_for_tests)
@@ -223,3 +263,4 @@
     register(no_rbac_rule_validation_decorator)
     register(no_rbac_suffix_in_test_filename)
     register(no_rbac_test_suffix_in_test_class_name)
+    register(no_extension_rbac_test_suffix_in_plugin_test_class_name)
diff --git a/patrole_tempest_plugin/policy_authority.py b/patrole_tempest_plugin/policy_authority.py
index 3339a5d..e0a26a3 100644
--- a/patrole_tempest_plugin/policy_authority.py
+++ b/patrole_tempest_plugin/policy_authority.py
@@ -13,8 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import collections
 import copy
-import json
+import glob
 import os
 
 from oslo_log import log as logging
@@ -103,17 +104,14 @@
         if extra_target_data is None:
             extra_target_data = {}
 
-        self.validate_service(service)
+        self.service = self.validate_service(service)
 
         # Prioritize dynamically searching for policy files over relying on
         # deprecated service-specific policy file locations.
-        self.path = None
         if CONF.patrole.custom_policy_files:
             self.discover_policy_files()
-            self.path = self.policy_files.get(service)
 
-        self.rules = policy.Rules.load(self._get_policy_data(service),
-                                       'default')
+        self.rules = self.get_rules()
         self.project_id = project_id
         self.user_id = user_id
         self.extra_target_data = extra_target_data
@@ -139,102 +137,94 @@
             raise rbac_exceptions.RbacInvalidServiceException(
                 "%s is NOT a valid service." % service)
 
+        return service
+
     @classmethod
     def discover_policy_files(cls):
         """Dynamically discover the policy file for each service in
-        ``cls.available_services``. Pick the first candidate path found
+        ``cls.available_services``. Pick all candidate paths found
         out of the potential paths in ``[patrole] custom_policy_files``.
         """
         if not hasattr(cls, 'policy_files'):
-            cls.policy_files = {}
+            cls.policy_files = collections.defaultdict(list)
             for service in cls.available_services:
                 for candidate_path in CONF.patrole.custom_policy_files:
-                    if os.path.isfile(candidate_path % service):
-                        cls.policy_files.setdefault(service,
-                                                    candidate_path % service)
+                    path = candidate_path % service
+                    for filename in glob.iglob(path):
+                        if os.path.isfile(filename):
+                            cls.policy_files[service].append(filename)
 
-    def allowed(self, rule_name, role):
+    def allowed(self, rule_name, roles):
         """Checks if a given rule in a policy is allowed with given role.
 
         :param string rule_name: Policy name to pass to``oslo.policy``.
-        :param string role: Role to validate for authorization.
+        :param List[string] roles: List of roles to validate for authorization.
         :raises RbacParsingException: If ``rule_name`` does not exist in the
             cloud (in policy file or among registered in-code policy defaults).
         """
-        is_admin_context = self._is_admin_context(role)
+        is_admin_context = self._is_admin_context(roles)
         is_allowed = self._allowed(
-            access=self._get_access_token(role),
+            access=self._get_access_token(roles),
             apply_rule=rule_name,
             is_admin=is_admin_context)
         return is_allowed
 
-    def _get_policy_data(self, service):
-        file_policy_data = {}
-        mgr_policy_data = {}
-        policy_data = {}
-
+    def get_rules(self):
+        rules = policy.Rules()
         # Check whether policy file exists and attempt to read it.
-        if self.path and os.path.isfile(self.path):
+        for path in self.policy_files[self.service]:
             try:
-                with open(self.path, 'r') as policy_file:
-                    file_policy_data = policy_file.read()
-                file_policy_data = json.loads(file_policy_data)
-            except (IOError, ValueError) as e:
-                msg = "Failed to read policy file for service. "
-                if isinstance(e, IOError):
-                    msg += "Please check that policy path exists."
-                else:
-                    msg += "JSON may be improperly formatted."
-                LOG.debug(msg)
-                file_policy_data = {}
+                with open(path, 'r') as fp:
+                    for k, v in policy.Rules.load(fp.read()).items():
+                        if k not in rules:
+                            rules[k] = v
+                        # If the policy name and rule are the same, no
+                        # ambiguity, so no reason to warn.
+                        elif str(v) != str(rules[k]):
+                            msg = ("The same policy name: %s was found in "
+                                   "multiple policies files for service %s. "
+                                   "This can lead to policy rule ambiguity. "
+                                   "Using rule: %s; Rule from file: %s")
+                            LOG.warning(msg, k, self.service, rules[k], v)
+            except (ValueError, IOError):
+                LOG.warning("Failed to read policy file '%s' for service %s.",
+                            path, self.service)
 
         # Check whether policy actions are defined in code. Nova and Keystone,
         # for example, define their default policy actions in code.
         mgr = stevedore.named.NamedExtensionManager(
             'oslo.policy.policies',
-            names=[service],
-            on_load_failure_callback=None,
+            names=[self.service],
             invoke_on_load=True,
             warn_on_missing_entrypoint=False)
 
         if mgr:
-            policy_generator = {policy.name: policy.obj for policy in mgr}
-            if policy_generator and service in policy_generator:
-                for rule in policy_generator[service]:
-                    mgr_policy_data[rule.name] = str(rule.check)
+            policy_generator = {plc.name: plc.obj for plc in mgr}
+            if self.service in policy_generator:
+                for rule in policy_generator[self.service]:
+                    if rule.name not in rules:
+                        rules[rule.name] = rule.check
+                    elif str(rule.check) != str(rules[rule.name]):
+                        msg = ("The same policy name: %s was found in the "
+                               "policies files and in the code for service "
+                               "%s. This can lead to policy rule ambiguity. "
+                               "Using rule: %s; Rule from code: %s")
+                        LOG.warning(msg, rule.name, self.service,
+                                    rules[rule.name], rule.check)
 
-        # If data from both file and code exist, combine both together.
-        if file_policy_data and mgr_policy_data:
-            # Add the policy actions from code first.
-            for action, rule in mgr_policy_data.items():
-                policy_data[action] = rule
-            # Overwrite with any custom policy actions defined in policy.json.
-            for action, rule in file_policy_data.items():
-                policy_data[action] = rule
-        elif file_policy_data:
-            policy_data = file_policy_data
-        elif mgr_policy_data:
-            policy_data = mgr_policy_data
-        else:
-            error_message = (
-                'Policy file for {0} service was not found among the '
+        if not rules:
+            msg = (
+                'Policy files for {0} service were not found among the '
                 'registered in-code policies or in any of the possible policy '
-                'files: {1}.'.format(service,
-                                     [loc % service for loc in
-                                      CONF.patrole.custom_policy_files])
-            )
-            raise rbac_exceptions.RbacParsingException(error_message)
+                'files: {1}.'.format(
+                    self.service,
+                    [loc % self.service
+                     for loc in CONF.patrole.custom_policy_files]))
+            raise rbac_exceptions.RbacParsingException(msg)
 
-        try:
-            policy_data = json.dumps(policy_data)
-        except (TypeError, ValueError):
-            error_message = 'Policy file for {0} service is invalid.'.format(
-                service)
-            raise rbac_exceptions.RbacParsingException(error_message)
+        return rules
 
-        return policy_data
-
-    def _is_admin_context(self, role):
+    def _is_admin_context(self, roles):
         """Checks whether a role has admin context.
 
         If context_is_admin is contained in the policy file, then checks
@@ -243,17 +233,17 @@
         """
         if 'context_is_admin' in self.rules.keys():
             return self._allowed(
-                access=self._get_access_token(role),
+                access=self._get_access_token(roles),
                 apply_rule='context_is_admin')
-        return role == CONF.identity.admin_role
+        return CONF.identity.admin_role in roles
 
-    def _get_access_token(self, role):
+    def _get_access_token(self, roles):
         access_token = {
             "token": {
                 "roles": [
                     {
                         "name": role
-                    }
+                    } for role in roles
                 ],
                 "project_id": self.project_id,
                 "tenant_id": self.project_id,
@@ -296,9 +286,11 @@
 
     def _try_rule(self, apply_rule, target, access_data, o):
         if apply_rule not in self.rules:
-            message = ("Policy action \"{0}\" not found in policy file: {1} or"
-                       " among registered policy in code defaults for service."
-                       ).format(apply_rule, self.path)
+            message = ('Policy action "{0}" not found in policy files: '
+                       '{1} or among registered policy in code defaults for '
+                       '{2} service.').format(apply_rule,
+                                              self.policy_files[self.service],
+                                              self.service)
             LOG.debug(message)
             raise rbac_exceptions.RbacParsingException(message)
         else:
diff --git a/patrole_tempest_plugin/rbac_exceptions.py b/patrole_tempest_plugin/rbac_exceptions.py
index 809a7ed..ad697b0 100644
--- a/patrole_tempest_plugin/rbac_exceptions.py
+++ b/patrole_tempest_plugin/rbac_exceptions.py
@@ -16,46 +16,59 @@
 from tempest.lib import exceptions
 
 
-class RbacConflictingPolicies(exceptions.TempestException):
-    message = ("Conflicting policies preventing this action from being "
-               "performed.")
+class BasePatroleException(exceptions.TempestException):
+    message = "An unknown RBAC exception occurred"
 
 
-class RbacMalformedResponse(exceptions.TempestException):
+class BasePatroleResponseBodyException(BasePatroleException):
+    message = "Response body incomplete due to RBAC authorization failure"
+
+
+class RbacMissingAttributeResponseBody(BasePatroleResponseBodyException):
+    """Raised when a list or show action is missing an attribute following
+    RBAC authorization failure.
+    """
     message = ("The response body is missing the expected %(attribute)s due "
-               "to policy enforcement failure.")
-
-    def __init__(self, empty=False, extra_attr=False, **kwargs):
-        if empty:
-            self.message = ("The response body is empty due to policy "
-                            "enforcement failure.")
-            kwargs = {}
-        if extra_attr:
-            self.message = ("The response body contained an unexpected "
-                            "attribute due to policy enforcement failure.")
-            kwargs = {}
-        super(RbacMalformedResponse, self).__init__(**kwargs)
+               "to policy enforcement failure")
 
 
-class RbacResourceSetupFailed(exceptions.TempestException):
+class RbacPartialResponseBody(BasePatroleResponseBodyException):
+    """Raised when a list action only returns a subset of the available
+    resources.
+
+    For example, admin can return more resources than member for a list action.
+    """
+    message = ("The response body only lists a subset of the available "
+               "resources due to partial policy enforcement failure. Response "
+               "body: %(body)s")
+
+
+class RbacEmptyResponseBody(BasePatroleResponseBodyException):
+    """Raised when a list or show action is empty following RBAC authorization
+    failure.
+    """
+    message = ("The response body is empty due to policy enforcement failure.")
+
+
+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 +77,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..9ca437b 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -38,8 +38,9 @@
 RBACLOG = logging.getLogger('rbac_reporting')
 
 
-def action(service, rule='', rules=None,
-           expected_error_code=_DEFAULT_ERROR_CODE, expected_error_codes=None,
+def action(service,
+           rules=None,
+           expected_error_codes=None,
            extra_target_data=None):
     """A decorator for verifying OpenStack policy enforcement.
 
@@ -47,7 +48,7 @@
 
     * an OpenStack service,
     * a policy action (``rule``) enforced by that service, and
-    * the test role defined by ``[patrole] rbac_test_role``
+    * the test roles defined by ``[patrole] rbac_test_roles``
 
     determines whether the test role has sufficient permissions to perform an
     API call that enforces the ``rule``.
@@ -72,26 +73,15 @@
     As such, negative and positive testing can be applied using this decorator.
 
     :param str service: An OpenStack service. Examples: "nova" or "neutron".
-    :param str rule: (DEPRECATED) A policy action defined in a policy.json file
-        or in code.
-    :param list rules: A list of policy actions defined in a policy.json file
-        or in code. The rules are logical-ANDed together to derive the expected
-        result.
+    :param list rules: A list of policy actions defined in a policy file or in
+        code. The rules are logical-ANDed together to derive the expected
+        result. Also accepts list of callables that return a policy action.
 
         .. note::
 
             Patrole currently only supports custom JSON policy files.
 
-    :param int expected_error_code: (DEPRECATED) Overrides default value of 403
-        (Forbidden) with endpoint-specific error code. Currently only supports
-        403 and 404. Support for 404 is needed because some services, like
-        Neutron, intentionally throw a 404 for security reasons.
-
-        .. warning::
-
-            A 404 should not be provided *unless* the endpoint masks a
-            ``Forbidden`` exception as a ``NotFound`` exception.
-
+    :type rules: list[str] or list[callable]
     :param list expected_error_codes: When the ``rules`` list parameter is
         used, then this list indicates the expected error code to use if one
         of the rules does not allow the role being tested. This list must
@@ -110,8 +100,14 @@
         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.
 
+        .. warning::
+
+            A 404 should not be provided *unless* the endpoint masks a
+            ``Forbidden`` exception as a ``NotFound`` exception.
+
+    :type expected_error_codes: list[int]
     :param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
         generic check names, whose values are string literals that reference
         nested ``tempest.test.BaseTestCase`` attributes. Used by
@@ -132,7 +128,8 @@
     Examples::
 
         @rbac_rule_validation.action(
-            service="nova", rule="os_compute_api:os-agents")
+            service="nova",
+            rules=["os_compute_api:os-agents"])
         def test_list_agents_rbac(self):
             # The call to `override_role` is mandatory.
             with self.rbac_utils.override_role(self):
@@ -142,12 +139,19 @@
     if extra_target_data is None:
         extra_target_data = {}
 
-    rules, expected_error_codes = _prepare_multi_policy(rule, rules,
-                                                        expected_error_code,
+    rules, expected_error_codes = _prepare_multi_policy(rules,
                                                         expected_error_codes)
 
     def decorator(test_func):
-        role = CONF.patrole.rbac_test_role
+        roles = CONF.patrole.rbac_test_roles
+        # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
+        if CONF.patrole.rbac_test_role:
+            msg = ('CONF.patrole.rbac_test_role is deprecated in favor of '
+                   'CONF.patrole.rbac_test_roles and will be removed in '
+                   'future.')
+            versionutils.report_deprecated_feature(LOG, msg)
+            if not roles:
+                roles.append(CONF.patrole.rbac_test_role)
 
         @functools.wraps(test_func)
         def wrapper(*args, **kwargs):
@@ -167,7 +171,6 @@
                     disallowed_rules.append(rule)
                 allowed = allowed and _allowed
 
-            exp_error_code = expected_error_code
             if disallowed_rules:
                 # Choose the first disallowed rule and expect the error
                 # code corresponding to it.
@@ -176,10 +179,13 @@
                 LOG.debug("%s: Expecting %d to be raised for policy name: %s",
                           test_func.__name__, exp_error_code,
                           disallowed_rules[0])
+            else:
+                exp_error_code = expected_error_codes[0]
 
             expected_exception, irregular_msg = _get_exception_type(
                 exp_error_code)
 
+            caught_exception = None
             test_status = 'Allowed'
 
             try:
@@ -192,25 +198,30 @@
                     test_status = ('Error, %s' % (msg))
                     LOG.error(msg)
             except (expected_exception,
-                    rbac_exceptions.RbacConflictingPolicies,
-                    rbac_exceptions.RbacMalformedResponse) as e:
+                    rbac_exceptions.BasePatroleResponseBodyException) \
+                    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. "
-                           "Expected disallowed actions: %s." % (
-                               role, sorted(rules),
+                    msg = ("User with roles %s was not allowed to perform the "
+                           "following actions: %s. Expected allowed actions: "
+                           "%s. Expected disallowed actions: %s." % (
+                               roles, sorted(rules),
                                sorted(set(rules) - set(disallowed_rules)),
                                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. '
@@ -235,7 +246,7 @@
                     msg = (
                         "OverPermission: Role %s was allowed to perform the "
                         "following disallowed actions: %s" % (
-                            role, sorted(disallowed_rules)
+                            roles, sorted(disallowed_rules)
                         )
                     )
                     LOG.error(msg)
@@ -249,11 +260,19 @@
                         "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
 
 
-def _prepare_multi_policy(rule, rules, exp_error_code, exp_error_codes):
+def _prepare_multi_policy(rules, exp_error_codes):
     if exp_error_codes:
         if not rules:
             msg = ("The `rules` list must be provided if using the "
@@ -263,34 +282,15 @@
             msg = ("The `expected_error_codes` list is not the same length "
                    "as the `rules` list.")
             raise ValueError(msg)
-        if exp_error_code:
-            deprecation_msg = (
-                "The `exp_error_code` argument has been deprecated in favor "
-                "of `exp_error_codes` and will be removed in a future "
-                "version.")
-            versionutils.report_deprecated_feature(LOG, deprecation_msg)
-            LOG.debug("The `exp_error_codes` argument will be used instead of "
-                      "`exp_error_code`.")
         if not isinstance(exp_error_codes, (tuple, list)):
             exp_error_codes = [exp_error_codes]
     else:
         exp_error_codes = []
-        if exp_error_code:
-            exp_error_codes.append(exp_error_code)
 
     if rules is None:
         rules = []
     elif not isinstance(rules, (tuple, list)):
         rules = [rules]
-    if rule:
-        deprecation_msg = (
-            "The `rule` argument has been deprecated in favor of `rules` "
-            "and will be removed in a future version.")
-        versionutils.report_deprecated_feature(LOG, deprecation_msg)
-        if rules:
-            LOG.debug("The `rules` argument will be used instead of `rule`.")
-        else:
-            rules.append(rule)
 
     # Fill in the exp_error_codes if needed. This is needed for the scenarios
     # where no exp_error_codes array is provided, so the error codes must be
@@ -302,7 +302,11 @@
         for i in range(num_rules - num_ecs):
             exp_error_codes.append(_DEFAULT_ERROR_CODE)
 
-    return rules, exp_error_codes
+    evaluated_rules = [
+        r() if callable(r) else r for r in rules
+    ]
+
+    return evaluated_rules, exp_error_codes
 
 
 def _is_authorized(test_obj, service, rule, extra_target_data):
@@ -334,7 +338,12 @@
         LOG.error(msg)
         raise rbac_exceptions.RbacResourceSetupFailed(msg)
 
-    role = CONF.patrole.rbac_test_role
+    roles = CONF.patrole.rbac_test_roles
+    # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
+    if CONF.patrole.rbac_test_role:
+        if not roles:
+            roles.append(CONF.patrole.rbac_test_role)
+
     # Test RBAC against custom requirements. Otherwise use oslo.policy.
     if CONF.patrole.test_custom_requirements:
         authority = requirements_authority.RequirementsAuthority(
@@ -345,14 +354,14 @@
         authority = policy_authority.PolicyAuthority(
             project_id, user_id, service,
             extra_target_data=formatted_target_data)
-    is_allowed = authority.allowed(rule, role)
+    is_allowed = authority.allowed(rule, roles)
 
     if is_allowed:
         LOG.debug("[Policy action]: %s, [Role]: %s is allowed!", rule,
-                  role)
+                  roles)
     else:
         LOG.debug("[Policy action]: %s, [Role]: %s is NOT allowed!",
-                  rule, role)
+                  rule, roles)
 
     return is_allowed
 
@@ -389,7 +398,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 +440,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 6c40aa1..33955c3 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 from contextlib import contextmanager
+import sys
 import time
 
 from oslo_log import log as logging
@@ -22,6 +23,7 @@
 from tempest import clients
 from tempest.common import credentials_factory as credentials
 from tempest import config
+from tempest.lib import exceptions as lib_exc
 
 from patrole_tempest_plugin import rbac_exceptions
 
@@ -38,7 +40,7 @@
     up, and primary credentials, needed to perform the API call which does
     policy enforcement. The primary credentials always cycle between roles
     defined by ``CONF.identity.admin_role`` and
-    ``CONF.patrole.rbac_test_role``.
+    ``CONF.patrole.rbac_test_roles``.
     """
 
     def __init__(self, test_obj):
@@ -49,16 +51,22 @@
         # Intialize the admin roles_client to perform role switching.
         admin_mgr = clients.Manager(
             credentials.get_configured_admin_credentials())
-        if test_obj.get_identity_version() == 'v3':
+        if CONF.identity_feature_enabled.api_v3:
             admin_roles_client = admin_mgr.roles_v3_client
         else:
-            admin_roles_client = admin_mgr.roles_client
+            raise lib_exc.InvalidConfiguration(
+                "Patrole role overriding only supports v3 identity API.")
 
         self.admin_roles_client = admin_roles_client
+
+        self.user_id = test_obj.os_primary.credentials.user_id
+        self.project_id = test_obj.os_primary.credentials.tenant_id
+
+        # Change default role to admin
         self._override_role(test_obj, False)
 
     admin_role_id = None
-    rbac_role_id = None
+    rbac_role_ids = None
 
     @contextmanager
     def override_role(self, test_obj):
@@ -66,7 +74,7 @@
 
         Temporarily change the role used by ``os_primary`` credentials to:
 
-        * ``[patrole] rbac_test_role`` before test execution
+        * ``[patrole] rbac_test_roles`` before test execution
         * ``[identity] admin_role`` after test execution
 
         Automatically switches to admin role after test execution.
@@ -82,7 +90,7 @@
         Example::
 
             @rbac_rule_validation.action(service='test',
-                                         rule='a:test:rule')
+                                         rules=['a:test:rule'])
             def test_foo(self):
                 # Allocate test-level resources here.
                 with self.rbac_utils.override_role(self):
@@ -94,11 +102,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.
@@ -113,79 +127,91 @@
             * If True: role is set to ``[patrole] rbac_test_role``
             * If False: role is set to ``[identity] admin_role``
         """
-        self.user_id = test_obj.os_primary.credentials.user_id
-        self.project_id = test_obj.os_primary.credentials.tenant_id
-        self.token = test_obj.os_primary.auth_provider.get_token()
-
         LOG.debug('Overriding role to: %s.', toggle_rbac_role)
-        role_already_present = False
+        roles_already_present = False
 
         try:
-            if not all([self.admin_role_id, self.rbac_role_id]):
+            if not all([self.admin_role_id, self.rbac_role_ids]):
                 self._get_roles_by_name()
 
-            target_role = (
-                self.rbac_role_id if toggle_rbac_role else self.admin_role_id)
-            role_already_present = self._list_and_clear_user_roles_on_project(
-                target_role)
+            target_roles = (self.rbac_role_ids
+                            if toggle_rbac_role else [self.admin_role_id])
+            roles_already_present = self._list_and_clear_user_roles_on_project(
+                target_roles)
 
             # Do not override roles if `target_role` already exists.
-            if not role_already_present:
-                self._create_user_role_on_project(target_role)
+            if not roles_already_present:
+                self._create_user_role_on_project(target_roles)
         except Exception as exp:
             with excutils.save_and_reraise_exception():
                 LOG.exception(exp)
         finally:
-            test_obj.os_primary.auth_provider.clear_auth()
+            auth_providers = test_obj.get_auth_providers()
+            for provider in auth_providers:
+                provider.clear_auth()
             # Fernet tokens are not subsecond aware so sleep to ensure we are
             # passing the second boundary before attempting to authenticate.
             # Only sleep if a token revocation occurred as a result of role
             # overriding. This will optimize test runtime in the case where
-            # ``[identity] admin_role`` == ``[patrole] rbac_test_role``.
-            if not role_already_present:
+            # ``[identity] admin_role`` == ``[patrole] rbac_test_roles``.
+            if not roles_already_present:
                 time.sleep(1)
-            test_obj.os_primary.auth_provider.set_auth()
+
+            for provider in auth_providers:
+                provider.set_auth()
 
     def _get_roles_by_name(self):
         available_roles = self.admin_roles_client.list_roles()['roles']
         role_map = {r['name']: r['id'] for r in available_roles}
         LOG.debug('Available roles: %s', list(role_map.keys()))
 
-        admin_role_id = role_map.get(CONF.identity.admin_role)
-        rbac_role_id = role_map.get(CONF.patrole.rbac_test_role)
+        rbac_role_ids = []
+        roles = CONF.patrole.rbac_test_roles
+        # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
+        if CONF.patrole.rbac_test_role:
+            if not roles:
+                roles.append(CONF.patrole.rbac_test_role)
 
-        if not all([admin_role_id, rbac_role_id]):
+        for role_name in roles:
+            rbac_role_ids.append(role_map.get(role_name))
+
+        admin_role_id = role_map.get(CONF.identity.admin_role)
+
+        if not all([admin_role_id, all(rbac_role_ids)]):
             missing_roles = []
-            msg = ("Could not find `[patrole] rbac_test_role` or "
+            msg = ("Could not find `[patrole] rbac_test_roles` or "
                    "`[identity] admin_role`, both of which are required for "
                    "RBAC testing.")
             if not admin_role_id:
                 missing_roles.append(CONF.identity.admin_role)
-            if not rbac_role_id:
-                missing_roles.append(CONF.patrole.rbac_test_role)
+            if not all(rbac_role_ids):
+                missing_roles += [role_name for role_name in roles
+                                  if not role_map.get(role_name)]
+
             msg += " Following roles were not found: %s." % (
                 ", ".join(missing_roles))
             msg += " Available roles: %s." % ", ".join(list(role_map.keys()))
             raise rbac_exceptions.RbacResourceSetupFailed(msg)
 
         self.admin_role_id = admin_role_id
-        self.rbac_role_id = rbac_role_id
+        self.rbac_role_ids = rbac_role_ids
 
-    def _create_user_role_on_project(self, role_id):
-        self.admin_roles_client.create_user_role_on_project(
-            self.project_id, self.user_id, role_id)
+    def _create_user_role_on_project(self, role_ids):
+        for role_id in role_ids:
+            self.admin_roles_client.create_user_role_on_project(
+                self.project_id, self.user_id, role_id)
 
-    def _list_and_clear_user_roles_on_project(self, role_id):
+    def _list_and_clear_user_roles_on_project(self, role_ids):
         roles = self.admin_roles_client.list_user_roles_on_project(
             self.project_id, self.user_id)['roles']
-        role_ids = [role['id'] for role in roles]
+        all_role_ids = [role['id'] for role in roles]
 
-        # NOTE(felipemonteiro): We do not use ``role_id in role_ids`` here to
-        # avoid over-permission errors: if the current list of roles on the
+        # NOTE(felipemonteiro): We do not use ``role_id in all_role_ids`` here
+        # to avoid over-permission errors: if the current list of roles on the
         # project includes "admin" and "Member", and we are switching to the
         # "Member" role, then we must delete the "admin" role. Thus, we only
         # return early if the user's roles on the project are an exact match.
-        if [role_id] == role_ids:
+        if set(role_ids) == set(all_role_ids):
             return True
 
         for role in roles:
@@ -217,22 +243,63 @@
                 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 skip_rbac_checks(cls):
-        if not CONF.patrole.enable_rbac:
-            raise cls.skipException(
-                'Patrole testing not enabled so skipping %s.' % cls.__name__)
+    def get_auth_providers(cls):
+        """Returns list of auth_providers used within test.
+
+        Tests may redefine this method to include their own or third party
+        client auth_providers.
+        """
+        return [cls.os_primary.auth_provider]
 
     @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.
 
-    :returns: True if ``rbac_test_role`` is the admin role.
+    :returns: True if ``rbac_test_roles`` contain the admin role.
     """
+    roles = CONF.patrole.rbac_test_roles
+    # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
+    if CONF.patrole.rbac_test_role:
+        roles.append(CONF.patrole.rbac_test_role)
+        roles = list(set(roles))
+
     # TODO(felipemonteiro): Make this more robust via a context is admin
     # lookup.
-    return CONF.patrole.rbac_test_role == CONF.identity.admin_role
+    return CONF.identity.admin_role in roles
diff --git a/patrole_tempest_plugin/requirements_authority.py b/patrole_tempest_plugin/requirements_authority.py
index 75df9f4..4d6f25b 100644
--- a/patrole_tempest_plugin/requirements_authority.py
+++ b/patrole_tempest_plugin/requirements_authority.py
@@ -12,14 +12,16 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+import copy
 import yaml
 
 from oslo_log import log as logging
 
 from tempest import config
-from tempest.lib import exceptions
+from tempest.lib import exceptions as lib_exc
 
 from patrole_tempest_plugin.rbac_authority import RbacAuthority
+from patrole_tempest_plugin import rbac_exceptions
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -50,7 +52,7 @@
             <service_foo>:
               <api_action_a>:
                 - <allowed_role_1>
-                - <allowed_role_2>
+                - <allowed_role_2>,<allowed_role_3>
                 - <allowed_role_3>
               <api_action_b>:
                 - <allowed_role_2>
@@ -67,11 +69,20 @@
         try:
             for section in RequirementsParser.Inner._rbac_map:
                 if component in section:
-                    return section[component]
+                    rules = copy.copy(section[component])
+
+                    for rule in rules:
+                        rules[rule] = [
+                            roles.split(',') for roles in rules[rule]]
+
+                        for i, role_pack in enumerate(rules[rule]):
+                            rules[rule][i] = [r.strip() for r in role_pack]
+
+                    return rules
         except yaml.parser.ParserError:
             LOG.error("Error while parsing the requirements YAML file. Did "
                       "you pass a valid component name from the test case?")
-        return None
+        return {}
 
 
 class RequirementsAuthority(RbacAuthority):
@@ -88,35 +99,52 @@
             Defaults to ``[patrole].custom_requirements_file``.
         :param str component: Name of the OpenStack service to be validated.
         """
-        filepath = filepath or CONF.patrole.custom_requirements_file
-
+        self.filepath = filepath or CONF.patrole.custom_requirements_file
         if component is not None:
-            self.roles_dict = RequirementsParser(filepath).parse(component)
+            self.roles_dict = RequirementsParser(self.filepath).parse(
+                component)
         else:
             self.roles_dict = None
 
-    def allowed(self, rule_name, role):
+    def allowed(self, rule_name, roles):
         """Checks if a given rule in a policy is allowed with given role.
 
         :param string rule_name: Rule to be checked using provided requirements
             file specified by ``[patrole].custom_requirements_file``. Must be
             a key present in this file, under the appropriate component.
-        :param string role: Role to validate against custom requirements file.
+        :param List[string] roles: Roles to validate against custom
+            requirements file.
         :returns: True if ``role`` is allowed to perform ``rule_name``, else
             False.
         :rtype: bool
-        :raises KeyError: If ``rule_name`` does not exist among the keyed
-            policy names in the custom requirements file.
+        :raises RbacParsingException: If ``rule_name`` does not exist among the
+            keyed policy names in the custom requirements file.
         """
-        if self.roles_dict is None:
-            raise exceptions.InvalidConfiguration(
+        if not self.roles_dict:
+            raise lib_exc.InvalidConfiguration(
                 "Roles dictionary parsed from requirements YAML file is "
                 "empty. Ensure the requirements YAML file is correctly "
                 "formatted.")
         try:
-            _api = self.roles_dict[rule_name]
-            return role in _api
+            requirement_roles = self.roles_dict[rule_name]
         except KeyError:
-            raise KeyError("'%s' API is not defined in the requirements YAML "
-                           "file" % rule_name)
+            raise rbac_exceptions.RbacParsingException(
+                "'%s' rule name is not defined in the requirements YAML file: "
+                "%s" % (rule_name, self.filepath))
+
+        for role_reqs in requirement_roles:
+            required_roles = [
+                role for role in role_reqs if not role.startswith("!")]
+            forbidden_roles = [
+                role[1:] for role in role_reqs if role.startswith("!")]
+
+            # User must have all required roles
+            required_passed = all([r in roles for r in required_roles])
+            # User must not have any forbidden roles
+            forbidden_passed = all([r not in forbidden_roles
+                                    for r in roles])
+
+            if required_passed and forbidden_passed:
+                return True
+
         return False
diff --git a/patrole_tempest_plugin/tests/api/README.rst b/patrole_tempest_plugin/tests/api/README.rst
new file mode 120000
index 0000000..e2853ec
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/README.rst
@@ -0,0 +1 @@
+../../../doc/source/field_guide/rbac.rst
\ No newline at end of file
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_agents_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
index a046f96..617aa5b 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
@@ -42,7 +42,7 @@
         return kwargs
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-agents")
+        service="nova", rules=["os_compute_api:os-agents"])
     @decorators.idempotent_id('d1bc6d97-07f5-4f45-ac29-1c619a6a7e27')
     def test_list_agents_rbac(self):
         with self.rbac_utils.override_role(self):
@@ -50,7 +50,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-agents")
+        rules=["os_compute_api:os-agents"])
     @decorators.idempotent_id('77d6cae4-1ced-47f7-af2e-3d6a45958fd6')
     def test_create_agent(self):
         params = {'hypervisor': 'kvm', 'os': 'win', 'architecture': 'x86',
@@ -63,7 +63,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-agents")
+        rules=["os_compute_api:os-agents"])
     @decorators.idempotent_id('b22f2681-9ffb-439b-b240-dae503e41020')
     def test_update_agent(self):
         params = self._param_helper(
@@ -84,7 +84,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-agents")
+        rules=["os_compute_api:os-agents"])
     @decorators.idempotent_id('c5042af8-0682-43b0-abc4-bf33349e23dd')
     def test_delete_agent(self):
         params = self._param_helper(
diff --git a/patrole_tempest_plugin/tests/api/compute/test_aggregates_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_aggregates_rbac.py
index b7cd392..dea8bb9 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_aggregates_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_aggregates_rbac.py
@@ -81,14 +81,14 @@
                         host=self.host)
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:create")
+        service="nova", rules=["os_compute_api:os-aggregates:create"])
     @decorators.idempotent_id('ba754393-896e-434a-9704-452ff4a84f3f')
     def test_create_aggregate_rbac(self):
         with self.rbac_utils.override_role(self):
             self._create_aggregate()
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:show")
+        service="nova", rules=["os_compute_api:os-aggregates:show"])
     @decorators.idempotent_id('8fb0b749-b120-4727-b3fb-bcfa3fa6f55b')
     def test_show_aggregate_rbac(self):
         aggregate_id = self._create_aggregate()
@@ -96,14 +96,14 @@
             self.aggregates_client.show_aggregate(aggregate_id)
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:index")
+        service="nova", rules=["os_compute_api:os-aggregates:index"])
     @decorators.idempotent_id('146284da-5dd6-4c97-b598-42b480f014c6')
     def test_list_aggregate_rbac(self):
         with self.rbac_utils.override_role(self):
             self.aggregates_client.list_aggregates()
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:update")
+        service="nova", rules=["os_compute_api:os-aggregates:update"])
     @decorators.idempotent_id('c94e0d69-99b6-477e-b301-2cd0e9d0ad81')
     def test_update_aggregate_rbac(self):
         aggregate_id = self._create_aggregate()
@@ -113,7 +113,7 @@
                                                     name=new_name)
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:delete")
+        service="nova", rules=["os_compute_api:os-aggregates:delete"])
     @decorators.idempotent_id('5a50c5a6-0f12-4405-a1ce-2288ae895ea6')
     def test_delete_aggregate_rbac(self):
         aggregate_id = self._create_aggregate()
@@ -121,7 +121,7 @@
             self.aggregates_client.delete_aggregate(aggregate_id)
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:add_host")
+        service="nova", rules=["os_compute_api:os-aggregates:add_host"])
     @decorators.idempotent_id('97e6e9df-5291-4faa-8147-755b2d1f1ce2')
     def test_add_host_to_aggregate_rbac(self):
         aggregate_id = self._create_aggregate()
@@ -129,7 +129,7 @@
             self._add_host_to_aggregate(aggregate_id)
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:remove_host")
+        service="nova", rules=["os_compute_api:os-aggregates:remove_host"])
     @decorators.idempotent_id('5b035a25-75d2-4d72-b4d6-0f0337335628')
     def test_remove_host_from_aggregate_rbac(self):
         aggregate_id = self._create_aggregate()
@@ -138,7 +138,7 @@
             self.aggregates_client.remove_host(aggregate_id, host=self.host)
 
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-aggregates:set_metadata")
+        service="nova", rules=["os_compute_api:os-aggregates:set_metadata"])
     @decorators.idempotent_id('ed6f3849-065c-4ae9-a81e-6ad7ed0d3d9d')
     def test_set_metadata_on_aggregate_rbac(self):
         aggregate_id = self._create_aggregate()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_availability_zone_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_availability_zone_rbac.py
index 66dce5c..d8b165c 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_availability_zone_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_availability_zone_rbac.py
@@ -28,15 +28,17 @@
                    "enabled." % cls.__name__)
             raise cls.skipException(msg)
 
-    @rbac_rule_validation.action(service="nova", rule="os_compute_api:"
-                                 "os-availability-zone:list")
+    @rbac_rule_validation.action(
+        service="nova",
+        rules=["os_compute_api:os-availability-zone:list"])
     @decorators.idempotent_id('cd34e7ea-d26e-4fa3-a8d0-f8883726ce3d')
     def test_get_availability_zone_list_rbac(self):
         with self.rbac_utils.override_role(self):
             self.availability_zone_client.list_availability_zones()
 
-    @rbac_rule_validation.action(service="nova", rule="os_compute_api:"
-                                 "os-availability-zone:detail")
+    @rbac_rule_validation.action(
+        service="nova",
+        rules=["os_compute_api:os-availability-zone:detail"])
     @decorators.idempotent_id('2f61c191-6ece-4f21-b487-39d749e3d38e')
     def test_get_availability_zone_list_detail_rbac(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_fixed_ips_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_fixed_ips_rbac.py
index f426cf3..25f7d5f 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_fixed_ips_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_fixed_ips_rbac.py
@@ -56,7 +56,7 @@
     @decorators.idempotent_id('c89391f7-4844-4a70-a116-37c1336efb99')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-fixed-ips")
+        rules=["os_compute_api:os-fixed-ips"])
     def test_show_fixed_ip_details(self):
         with self.rbac_utils.override_role(self):
             self.fixed_ips_client.show_fixed_ip(self.ip)
@@ -64,7 +64,7 @@
     @decorators.idempotent_id('f0314501-735d-4315-9856-959e01e82f0d')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-fixed-ips")
+        rules=["os_compute_api:os-fixed-ips"])
     def test_set_reserve(self):
         with self.rbac_utils.override_role(self):
             self.fixed_ips_client.reserve_fixed_ip(self.ip, reserve="None")
@@ -72,7 +72,7 @@
     @decorators.idempotent_id('866a6fdc-a237-4502-9bf2-52fe82aba356')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-fixed-ips")
+        rules=["os_compute_api:os-fixed-ips"])
     def test_set_unreserve(self):
         with self.rbac_utils.override_role(self):
             self.fixed_ips_client.reserve_fixed_ip(self.ip, unreserve="None")
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..8d4d70f 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,10 +35,12 @@
         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",
-        rule="os_compute_api:os-flavor-access")
+        rules=["os_compute_api:os-flavor-access"])
     def test_show_flavor_contains_is_public_key(self):
         public_flavor_id = CONF.compute.flavor_ref
 
@@ -47,13 +50,15 @@
 
         expected_attr = 'os-flavor-access:is_public'
         if expected_attr not in body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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",
-        rule="os_compute_api:os-flavor-access")
+        rules=["os_compute_api:os-flavor-access"])
     def test_list_flavors_details_contains_is_public_key(self):
         expected_attr = 'os-flavor-access:is_public'
 
@@ -66,13 +71,13 @@
         # If the `expected_attr` was not found in any flavor, then policy
         # enforcement failed.
         if not public_flavors:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute=expected_attr)
 
     @decorators.idempotent_id('39cb5c8f-9990-436f-9282-fc76a41d9bac')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-access:add_tenant_access")
+        rules=["os_compute_api:os-flavor-access:add_tenant_access"])
     def test_add_flavor_access(self):
         with self.rbac_utils.override_role(self):
             self.flavors_client.add_flavor_access(
@@ -83,7 +88,7 @@
     @decorators.idempotent_id('61b8621f-52e4-473a-8d07-e228af8853d1')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-access:remove_tenant_access")
+        rules=["os_compute_api:os-flavor-access:remove_tenant_access"])
     def test_remove_flavor_access(self):
         self.flavors_client.add_flavor_access(
             flavor_id=self.flavor_id, tenant_id=self.tenant_id)
@@ -98,7 +103,7 @@
     @decorators.idempotent_id('e1cf59fb-7f32-40a1-96b9-248ab23dd581')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-access")
+        rules=["os_compute_api:os-flavor-access"])
     def test_list_flavor_access(self):
         # Add flavor access for os_primary so that it can access the flavor or
         # else a NotFound is raised.
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py
index 816492c..b781540 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py
@@ -50,7 +50,7 @@
     @decorators.idempotent_id('daee891d-dfe9-4501-a39c-29f2371bec3c')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-extra-specs:show")
+        rules=["os_compute_api:os-flavor-extra-specs:show"])
     def test_show_flavor_extra_spec(self):
         key = self._set_flavor_extra_spec()
         with self.rbac_utils.override_role(self):
@@ -59,7 +59,7 @@
     @decorators.idempotent_id('fcffeca2-ed04-4e85-bf93-02fb5643f22b')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-extra-specs:create")
+        rules=["os_compute_api:os-flavor-extra-specs:create"])
     def test_set_flavor_extra_spec(self):
         with self.rbac_utils.override_role(self):
             self._set_flavor_extra_spec()
@@ -67,7 +67,7 @@
     @decorators.idempotent_id('42b85279-6bfa-4f58-b7a2-258c284f03c5')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-extra-specs:update")
+        rules=["os_compute_api:os-flavor-extra-specs:update"])
     def test_update_flavor_extra_spec(self):
         key = self._set_flavor_extra_spec()
         update_val = data_utils.rand_name(self.__class__.__name__ + '-val')
@@ -78,7 +78,7 @@
     @decorators.idempotent_id('4b0e5471-e010-4c09-8965-80898e6760a3')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-extra-specs:delete")
+        rules=["os_compute_api:os-flavor-extra-specs:delete"])
     def test_unset_flavor_extra_spec(self):
         key = self._set_flavor_extra_spec()
         with self.rbac_utils.override_role(self):
@@ -87,7 +87,7 @@
     @decorators.idempotent_id('02c3831a-3ce9-476e-a722-d805ac2da621')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-extra-specs:index")
+        rules=["os_compute_api:os-flavor-extra-specs:index"])
     def test_list_flavor_extra_specs(self):
         self._set_flavor_extra_spec()
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py
index f0f267c..f968d4e 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py
@@ -32,7 +32,7 @@
     @decorators.idempotent_id('a4e7faec-7a4b-4809-9856-90d5b747ca35')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-manage:create")
+        rules=["os_compute_api:os-flavor-manage:create"])
     def test_create_flavor_manage(self):
         with self.rbac_utils.override_role(self):
             self.create_flavor()
@@ -40,7 +40,7 @@
     @decorators.idempotent_id('782e988e-061b-4c40-896f-a77c70c2b057')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-flavor-manage:delete")
+        rules=["os_compute_api:os-flavor-manage:delete"])
     def test_delete_flavor_manage(self):
         flavor_id = self.create_flavor()['id']
 
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..cbb2e19 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,25 +35,29 @@
             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",
-        rule="os_compute_api:os-flavor-rxtx")
+        rules=["os_compute_api:os-flavor-rxtx"])
     def test_list_flavors_details_rxtx(self):
         with self.rbac_utils.override_role(self):
             result = self.flavors_client.list_flavors(detail=True)['flavors']
         if 'rxtx_factor' not in result[0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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",
-        rule="os_compute_api:os-flavor-rxtx")
+        rules=["os_compute_api:os-flavor-rxtx"])
     def test_get_flavor_rxtx(self):
         with self.rbac_utils.override_role(self):
             result = self.flavors_client.show_flavor(
                 CONF.compute.flavor_ref)['flavor']
         if 'rxtx_factor' not in result:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='rxtx_factor')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py
index 7467130..eef7943 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py
@@ -48,7 +48,7 @@
     @decorators.idempotent_id('c1a17153-b25d-4444-a721-5897d7737482')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ip-pools")
+        rules=["os_compute_api:os-floating-ip-pools"])
     def test_list_floating_ip_pools(self):
         with self.rbac_utils.override_role(self):
             self.fip_pools_client.list_floating_ip_pools()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py
index 3ccef73..4a8426c 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py
@@ -89,7 +89,7 @@
     @decorators.idempotent_id('9a49e73f-96a0-4e93-830a-22c4e443b486')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips-bulk")
+        rules=["os_compute_api:os-floating-ips-bulk"])
     def test_create_floating_ips_bulk(self):
         with self.rbac_utils.override_role(self):
             self._create_floating_ips_bulk()
@@ -97,7 +97,7 @@
     @decorators.idempotent_id('3b5c8a02-005d-4256-8a95-6fa2f389c6cf')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips-bulk")
+        rules=["os_compute_api:os-floating-ips-bulk"])
     def test_list_floating_ips_bulk(self):
         with self.rbac_utils.override_role(self):
             self.fip_bulk_client.list_floating_ips_bulk()
@@ -105,7 +105,7 @@
     @decorators.idempotent_id('37c2b759-c494-4e20-9dba-6a67b2df9573')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips-bulk")
+        rules=["os_compute_api:os-floating-ips-bulk"])
     def test_delete_floating_ips_bulk(self):
         self._create_floating_ips_bulk()
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py
index 1045512..0f37a80 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py
@@ -44,7 +44,7 @@
     @decorators.idempotent_id('ac1b3053-f755-4cda-85a0-30e88b88d7ba')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips")
+        rules=["os_compute_api:os-floating-ips"])
     def test_list_floating_ips(self):
         with self.rbac_utils.override_role(self):
             self.floating_ips_client.list_floating_ips()
@@ -52,7 +52,7 @@
     @decorators.idempotent_id('bebe52b3-5269-4e72-80c8-5a4a39c3bfa6')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips")
+        rules=["os_compute_api:os-floating-ips"])
     def test_show_floating_ip(self):
         body = self.floating_ips_client.create_floating_ip(
             pool=CONF.network.floating_network_name)['floating_ip']
@@ -64,7 +64,7 @@
     @decorators.idempotent_id('2bfb8745-c329-4ee9-95f6-c165a1989dbf')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips")
+        rules=["os_compute_api:os-floating-ips"])
     def test_create_floating_ips(self):
         with self.rbac_utils.override_role(self):
             body = self.floating_ips_client.create_floating_ip(
@@ -75,7 +75,7 @@
     @decorators.idempotent_id('d3028373-5027-4e7a-b761-01c515403ecb')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-floating-ips")
+        rules=["os_compute_api:os-floating-ips"])
     def test_delete_floating_ip(self):
         body = self.floating_ips_client.create_floating_ip(
             pool=CONF.network.floating_network_name)['floating_ip']
diff --git a/patrole_tempest_plugin/tests/api/compute/test_hosts_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_hosts_rbac.py
index 41d2656..f2d8113 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_hosts_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_hosts_rbac.py
@@ -36,7 +36,7 @@
     @decorators.idempotent_id('035b7935-2fae-4218-8d37-27fa83097494')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hosts")
+        rules=["os_compute_api:os-hosts"])
     def test_list_hosts(self):
         with self.rbac_utils.override_role(self):
             self.hosts_client.list_hosts()
@@ -44,7 +44,7 @@
     @decorators.idempotent_id('bc10d8b4-d2c3-4d4e-9d2b-31d1bd3e1b51')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hosts")
+        rules=["os_compute_api:os-hosts"])
     def test_show_host_details(self):
         hosts = self.hosts_client.list_hosts()['hosts']
         hosts = [host for host in hosts if host['service'] == 'compute']
diff --git a/patrole_tempest_plugin/tests/api/compute/test_hypervisor_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_hypervisor_rbac.py
index 33f40e7..5488556 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_hypervisor_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_hypervisor_rbac.py
@@ -39,7 +39,7 @@
     @decorators.idempotent_id('17bbeb9a-e73e-445f-a771-c794448ef562')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_list_hypervisors(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.list_hypervisors()
@@ -47,7 +47,7 @@
     @decorators.idempotent_id('36b95c7d-1085-487a-a674-b7c1ca35f520')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_list_hypervisors_with_details(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.list_hypervisors(detail=True)
@@ -55,7 +55,7 @@
     @decorators.idempotent_id('8a7f6f9e-34a6-4480-8875-bba566c3a581')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_show_hypervisor(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.show_hypervisor(self.hypervisor['id'])
@@ -63,7 +63,7 @@
     @decorators.idempotent_id('ca0e465c-6365-4a7f-ae58-6f8ddbca06c2')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_show_hypervisor_statistics(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.show_hypervisor_statistics()
@@ -71,7 +71,7 @@
     @decorators.idempotent_id('109b37c5-91ba-4da5-b2a2-d7618d84406d')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_show_hypervisor_uptime(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.show_hypervisor_uptime(
@@ -102,7 +102,7 @@
     @decorators.idempotent_id('b86f03cf-2e79-4d88-9eea-62f761591413')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_list_servers_on_hypervisor(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.list_servers_on_hypervisor(
@@ -111,7 +111,7 @@
     @decorators.idempotent_id('3dbc71c1-8f04-4674-a67c-dcb2fd99b1b4')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-hypervisors")
+        rules=["os_compute_api:os-hypervisors"])
     def test_search_hypervisor(self):
         with self.rbac_utils.override_role(self):
             self.hypervisor_client.search_hypervisor(
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..e16222c 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
 
@@ -76,7 +79,7 @@
     @decorators.idempotent_id('b861f302-b72b-4055-81db-c62ff30b136d')
     @rbac_rule_validation.action(
         service="glance",
-        rule="get_images")
+        rules=["get_images"])
     def test_list_images(self):
         with self.rbac_utils.override_role(self):
             self.compute_images_client.list_images()
@@ -84,7 +87,7 @@
     @decorators.idempotent_id('4365ae0f-15ee-4b54-a527-1679faaed140')
     @rbac_rule_validation.action(
         service="glance",
-        rule="get_images")
+        rules=["get_images"])
     def test_list_images_with_details(self):
         with self.rbac_utils.override_role(self):
             self.compute_images_client.list_images(detail=True)
@@ -92,7 +95,7 @@
     @decorators.idempotent_id('886dfcae-51bf-4610-9e52-82d7189524c2')
     @rbac_rule_validation.action(
         service="glance",
-        rule="get_image")
+        rules=["get_image"])
     def test_show_image_details(self):
         with self.rbac_utils.override_role(self):
             self.compute_images_client.show_image(self.image['id'])
@@ -100,7 +103,7 @@
     @decorators.idempotent_id('5888c7aa-0803-46d4-a3fb-5d4729465cd5')
     @rbac_rule_validation.action(
         service="glance",
-        rule="delete_image")
+        rules=["delete_image"])
     def test_delete_image(self):
         image = self.glance_image_client.create_image(
             name=data_utils.rand_name(self.__class__.__name__ + '-image'))
@@ -161,7 +164,7 @@
     @decorators.idempotent_id('dbe09d4c-e615-48cb-b908-a06a0f410a8e')
     @rbac_rule_validation.action(
         service="glance",
-        rule="get_image")
+        rules=["get_image"])
     def test_show_image_metadata_item(self):
         self.compute_images_client.set_image_metadata(self.image['id'],
                                                       meta={'foo': 'bar'})
@@ -175,7 +178,7 @@
     @decorators.idempotent_id('59f66079-d564-47e8-81b0-03c2e84d339e')
     @rbac_rule_validation.action(
         service="glance",
-        rule="get_image")
+        rules=["get_image"])
     def test_list_image_metadata(self):
         with self.rbac_utils.override_role(self):
             self.compute_images_client.list_image_metadata(self.image['id'])
@@ -183,7 +186,7 @@
     @decorators.idempotent_id('575604aa-909f-4b1b-a5a5-cfae1f63044b')
     @rbac_rule_validation.action(
         service="glance",
-        rule="modify_image")
+        rules=["modify_image"])
     def test_create_image_metadata(self):
         with self.rbac_utils.override_role(self):
             # NOTE(felipemonteiro): Although the name of the client function
@@ -197,7 +200,7 @@
     @decorators.idempotent_id('fb8c4eb6-00e5-454c-b8bc-0e801ec369f1')
     @rbac_rule_validation.action(
         service="glance",
-        rule="modify_image")
+        rules=["modify_image"])
     def test_update_image_metadata(self):
         with self.rbac_utils.override_role(self):
             self.compute_images_client.set_image_metadata(self.image['id'],
@@ -208,7 +211,7 @@
     @decorators.idempotent_id('9c7c2036-af9b-49a8-8ba1-09b027ee5def')
     @rbac_rule_validation.action(
         service="glance",
-        rule="modify_image")
+        rules=["modify_image"])
     def test_update_image_metadata_item(self):
         with self.rbac_utils.override_role(self):
             self.compute_images_client.set_image_metadata_item(
@@ -219,7 +222,7 @@
     @decorators.idempotent_id('5f0dc4e6-0761-4613-9bde-0a6acdc78f46')
     @rbac_rule_validation.action(
         service="glance",
-        rule="modify_image")
+        rules=["modify_image"])
     def test_delete_image_metadata_item(self):
         self.compute_images_client.set_image_metadata(self.image['id'],
                                                       meta={'foo': 'bar'})
@@ -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):
+        rules=["os_compute_api:image-size"])
+    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.RbacMissingAttributeResponseBody(
+                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):
+        rules=["os_compute_api:image-size"])
+    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.RbacMissingAttributeResponseBody(
+                attribute=expected_attr)
diff --git a/patrole_tempest_plugin/tests/api/compute/test_instance_usages_audit_log_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_instance_usages_audit_log_rbac.py
index 347b7df..163d29a 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_instance_usages_audit_log_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_instance_usages_audit_log_rbac.py
@@ -36,7 +36,7 @@
 
     @decorators.idempotent_id('c80246c0-5c13-4ab0-97ba-91551cd53dc1')
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-instance-usage-audit-log")
+        service="nova", rules=["os_compute_api:os-instance-usage-audit-log"])
     def test_list_instance_usage_audit_logs(self):
         with self.rbac_utils.override_role(self):
             (self.instance_usages_audit_log_client
@@ -44,7 +44,7 @@
 
     @decorators.idempotent_id('ded8bfbd-5d90-4a58-aee0-d31231bf3c9b')
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-instance-usage-audit-log")
+        service="nova", rules=["os_compute_api:os-instance-usage-audit-log"])
     def test_show_instance_usage_audit_log(self):
         now = datetime.datetime.now()
 
diff --git a/patrole_tempest_plugin/tests/api/compute/test_keypairs_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_keypairs_rbac.py
index b359ad2..c024a38 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_keypairs_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_keypairs_rbac.py
@@ -34,7 +34,7 @@
     @decorators.idempotent_id('16e0ae81-e05f-48cd-b253-cf31ab0732f0')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-keypairs:create")
+        rules=["os_compute_api:os-keypairs:create"])
     def test_create_keypair(self):
         with self.rbac_utils.override_role(self):
             self._create_keypair()
@@ -42,7 +42,7 @@
     @decorators.idempotent_id('85a5eb99-40ec-4e77-9358-bee2cdf9d7df')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-keypairs:show")
+        rules=["os_compute_api:os-keypairs:show"])
     def test_show_keypair(self):
         kp_name = self._create_keypair()['keypair']['name']
         with self.rbac_utils.override_role(self):
@@ -51,7 +51,7 @@
     @decorators.idempotent_id('6bff9f1c-b809-43c1-8d63-61fbd19d49d3')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-keypairs:delete")
+        rules=["os_compute_api:os-keypairs:delete"])
     def test_delete_keypair(self):
         kp_name = self._create_keypair()['keypair']['name']
         with self.rbac_utils.override_role(self):
@@ -60,7 +60,7 @@
     @decorators.idempotent_id('6bb31346-ff7f-4b10-978e-170ac5fcfa3e')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-keypairs:index")
+        rules=["os_compute_api:os-keypairs:index"])
     def test_index_keypair(self):
         with self.rbac_utils.override_role(self):
             self.keypairs_client.list_keypairs()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_limits_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_limits_rbac.py
index 9442a5a..f1e0393 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_limits_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_limits_rbac.py
@@ -28,7 +28,7 @@
             raise cls.skipException(msg)
 
     @rbac_rule_validation.action(service="nova",
-                                 rule="os_compute_api:limits")
+                                 rules=["os_compute_api:limits"])
     @decorators.idempotent_id('3fb60f83-9a5f-4fdd-89d9-26c3710844a1')
     def test_show_limits(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_migrations_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_migrations_rbac.py
index 1597a04..6596ac9 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_migrations_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_migrations_rbac.py
@@ -32,7 +32,7 @@
     @decorators.idempotent_id('5795231c-3729-448c-a072-9a225db1a328')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-migrations:index")
+        rules=["os_compute_api:os-migrations:index"])
     def test_list_services(self):
         with self.rbac_utils.override_role(self):
             self.migrations_client.list_migrations()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py
index 2f86763..201922c 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.common import identity
 from tempest.common import tempest_fixtures as fixtures
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
@@ -24,6 +25,8 @@
 
 class QuotaClassesRbacTest(rbac_base.BaseV2ComputeRbacTest):
 
+    credentials = ['primary', 'admin']
+
     def setUp(self):
         # All test cases in this class need to externally lock on doing
         # anything with default quota values.
@@ -48,16 +51,19 @@
     def resource_setup(cls):
         super(QuotaClassesRbacTest, cls).resource_setup()
         # Create a project with its own quota.
-        project_name = data_utils.rand_name(cls.__name__ + '-Project')
-        cls.project_id = cls.identity_projects_client.create_project(
-            project_name)['project']['id']
+        project_name = data_utils.rand_name(cls.__name__ + '-project')
+        project_desc = project_name + '-desc'
+        project = identity.identity_utils(cls.os_admin).create_project(
+            name=project_name, description=project_desc)
+        cls.project_id = project['id']
         cls.addClassResourceCleanup(
-            cls.identity_projects_client.delete_project, cls.project_id)
+            identity.identity_utils(cls.os_admin).delete_project,
+            cls.project_id)
 
     @decorators.idempotent_id('c10198ed-9df2-440e-a49b-367dadc6de94')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-class-sets:show")
+        rules=["os_compute_api:os-quota-class-sets:show"])
     def test_show_quota_class_set(self):
         with self.rbac_utils.override_role(self):
             self.quota_classes_client.show_quota_class_set('default')
@@ -65,7 +71,7 @@
     @decorators.idempotent_id('81889e69-efd2-4e96-bb4c-ee3b646b9755')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-class-sets:update")
+        rules=["os_compute_api:os-quota-class-sets:update"])
     def test_update_quota_class_set(self):
         # Update the pre-existing quotas for the project_id.
         quota_class_set = self.quota_classes_client.show_quota_class_set(
diff --git a/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py
index ec4511a..2b05408 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py
@@ -13,9 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.common import identity
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 from patrole_tempest_plugin import rbac_rule_validation
@@ -24,6 +24,8 @@
 
 class QuotaSetsRbacTest(rbac_base.BaseV2ComputeRbacTest):
 
+    credentials = ['primary', 'admin']
+
     @classmethod
     def setup_clients(cls):
         super(QuotaSetsRbacTest, cls).setup_clients()
@@ -52,7 +54,7 @@
     @decorators.idempotent_id('8229ceb0-db6a-4a2c-99c2-de226905d8b6')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-sets:update")
+        rules=["os_compute_api:os-quota-sets:update"])
     def test_update_quota_set(self):
         default_quota_set = self.quotas_client.show_default_quota_set(
             self.tenant_id)['quota_set']
@@ -69,7 +71,7 @@
     @decorators.idempotent_id('58df5613-8f3c-400a-8b4b-2bae624d05e9')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-sets:defaults")
+        rules=["os_compute_api:os-quota-sets:defaults"])
     def test_show_default_quota_set(self):
         with self.rbac_utils.override_role(self):
             self.quotas_client.show_default_quota_set(self.tenant_id)
@@ -77,7 +79,7 @@
     @decorators.idempotent_id('e8169ac4-c402-4864-894e-aba74e3a459c')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-sets:show")
+        rules=["os_compute_api:os-quota-sets:show"])
     def test_show_quota_set(self):
         with self.rbac_utils.override_role(self):
             self.quotas_client.show_quota_set(self.tenant_id)
@@ -85,14 +87,17 @@
     @decorators.idempotent_id('4e240644-bf61-4872-9c32-8289ee2fdbbd')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-sets:delete")
+        rules=["os_compute_api:os-quota-sets:delete"])
     def test_delete_quota_set(self):
         project_name = data_utils.rand_name(
             self.__class__.__name__ + '-project')
-        project = self.projects_client.create_project(name=project_name)
-        project_id = project['project']['id']
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        self.projects_client.delete_project, project_id)
+        project_desc = project_name + '-desc'
+        project = identity.identity_utils(self.os_admin).create_project(
+            name=project_name, description=project_desc)
+        project_id = project['id']
+        self.addCleanup(
+            identity.identity_utils(self.os_admin).delete_project,
+            project_id)
 
         with self.rbac_utils.override_role(self):
             self.quotas_client.delete_quota_set(project_id)
@@ -100,7 +105,7 @@
     @decorators.idempotent_id('ac9184b6-f3b3-4e17-a632-4b92c6500f86')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-quota-sets:detail")
+        rules=["os_compute_api:os-quota-sets:detail"])
     def test_show_quota_set_details(self):
         with self.rbac_utils.override_role(self):
             self.quotas_client.show_quota_set(self.tenant_id,
diff --git a/patrole_tempest_plugin/tests/api/compute/test_security_groups_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_security_groups_rbac.py
index fa89a79..33d2ed1 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_security_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_security_groups_rbac.py
@@ -55,7 +55,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('3db159c6-a467-469f-9a25-574197885520')
     def test_list_security_groups_by_server(self):
         with self.rbac_utils.override_role(self):
@@ -64,7 +64,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('ea1ca73f-2d1d-43cb-9a46-900d7927b357')
     def test_create_security_group_for_server(self):
         sg_name = self.create_security_group()['name']
@@ -78,7 +78,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('0ad2e856-e2d3-4ac5-a620-f93d0d3d2626')
     def test_remove_security_group_from_server(self):
         sg_name = self.create_security_group()['name']
@@ -116,7 +116,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('4ac58e49-48c1-4fca-a6c3-3f95fb99eb77')
     def test_list_security_groups(self):
         with self.rbac_utils.override_role(self):
@@ -124,7 +124,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('e8fe7f5a-69ee-412d-81d3-a8c7a488b54d')
     def test_create_security_groups(self):
         with self.rbac_utils.override_role(self):
@@ -132,7 +132,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('59127e8e-302d-11e7-93ae-92361f002671')
     def test_delete_security_groups(self):
         sec_group_id = self.create_security_group()['id']
@@ -141,7 +141,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('3de5c6bc-b822-469e-a627-82427d38b067')
     def test_update_security_groups(self):
         sec_group_id = self.create_security_group()['id']
@@ -154,7 +154,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-security-groups")
+        rules=["os_compute_api:os-security-groups"])
     @decorators.idempotent_id('6edc0320-302d-11e7-93ae-92361f002671')
     def test_show_security_groups(self):
         sec_group_id = self.create_security_group()['id']
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index 1fe52e9..0ff6ebe 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
@@ -119,7 +119,7 @@
                           'Pause is not available.')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-pause-server:pause")
+        rules=["os_compute_api:os-pause-server:pause"])
     def test_pause_server(self):
         with self.rbac_utils.override_role(self):
             self._pause_server()
@@ -129,7 +129,7 @@
                           'Pause is not available.')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-pause-server:unpause")
+        rules=["os_compute_api:os-pause-server:unpause"])
     def test_unpause_server(self):
         self._pause_server()
         with self.rbac_utils.override_role(self):
@@ -139,7 +139,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:stop")
+        rules=["os_compute_api:servers:stop"])
     @decorators.idempotent_id('ab4a17d2-166f-4a6d-9944-f17baa576cf2')
     def test_stop_server(self):
         with self.rbac_utils.override_role(self):
@@ -148,7 +148,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:start")
+        rules=["os_compute_api:servers:start"])
     @decorators.idempotent_id('8876bfa9-4d10-406e-a335-a57e451abb12')
     def test_start_server(self):
         self._stop_server()
@@ -161,7 +161,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:resize")
+        rules=["os_compute_api:servers:resize"])
     @decorators.idempotent_id('0546fbdd-2d8f-4ce8-ac00-f1e2129d0765')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize is not available.')
@@ -172,7 +172,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:revert_resize")
+        rules=["os_compute_api:servers:revert_resize"])
     @decorators.idempotent_id('d41b64b8-a72d-414a-a4c5-94e1eb5e5a96')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize is not available.')
@@ -187,7 +187,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:confirm_resize")
+        rules=["os_compute_api:servers:confirm_resize"])
     @decorators.idempotent_id('f51620cb-dfcb-4e5d-b421-2e0edaa1316e')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize is not available.')
@@ -202,7 +202,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:rebuild")
+        rules=["os_compute_api:servers:rebuild"])
     @decorators.idempotent_id('54b1a30b-c96c-472c-9c83-ccaf6ec7e20b')
     def test_rebuild_server(self):
         with self.rbac_utils.override_role(self):
@@ -212,7 +212,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:reboot")
+        rules=["os_compute_api:servers:reboot"])
     @decorators.idempotent_id('19f27856-56e1-44f8-8615-7257f6b85cbb')
     def test_reboot_server(self):
         with self.rbac_utils.override_role(self):
@@ -222,7 +222,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:index")
+        rules=["os_compute_api:servers:index"])
     @decorators.idempotent_id('631f0d86-7607-4198-8312-9da2f05464a4')
     def test_server_index(self):
         with self.rbac_utils.override_role(self):
@@ -230,7 +230,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:detail")
+        rules=["os_compute_api:servers:detail"])
     @decorators.idempotent_id('96093480-3ce5-4a8b-b569-aed870379c24')
     def test_server_detail(self):
         with self.rbac_utils.override_role(self):
@@ -238,7 +238,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:detail:get_all_tenants")
+        rules=["os_compute_api:servers:detail:get_all_tenants"])
     @decorators.idempotent_id('a9e5a1c0-acfe-49a2-b2b1-fd8b19d61f71')
     def test_server_detail_all_tenants(self):
         with self.rbac_utils.override_role(self):
@@ -246,7 +246,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:index:get_all_tenants")
+        rules=["os_compute_api:servers:index:get_all_tenants"])
     @decorators.idempotent_id('4b93ba56-69e6-41f5-82c4-84a5c4c42091')
     def test_server_index_all_tenants(self):
         with self.rbac_utils.override_role(self):
@@ -254,7 +254,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:show")
+        rules=["os_compute_api:servers:show"])
     @decorators.idempotent_id('eaaf4f51-31b5-497f-8f0f-f527e5f70b83')
     def test_show_server(self):
         with self.rbac_utils.override_role(self):
@@ -263,7 +263,7 @@
     @utils.services('image')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:create_image")
+        rules=["os_compute_api:servers:create_image"])
     @decorators.idempotent_id('ba0ac859-99f4-4055-b5e0-e0905a44d331')
     def test_create_image(self):
         with self.rbac_utils.override_role(self):
@@ -273,7 +273,7 @@
     @utils.services('image', 'volume')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:create_image:allow_volume_backed")
+        rules=["os_compute_api:servers:create_image:allow_volume_backed"])
     @decorators.idempotent_id('8b869f73-49b3-4cc4-a0ce-ef64f8e1d6f9')
     def test_create_image_from_volume_backed_server(self):
         # volume_backed=True creates a volume and create server will be
@@ -299,7 +299,7 @@
     @utils.services('image')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-create-backup")
+        rules=["os_compute_api:os-create-backup"])
     def test_create_backup(self):
         # Prioritize glance v2 over v1 for deleting/waiting for image status.
         if CONF.image_feature_enabled.api_v2:
@@ -334,7 +334,7 @@
     @decorators.idempotent_id('0b70c527-af75-4bed-9ccf-4f1310a8b60f')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-shelve:shelve")
+        rules=["os_compute_api:os-shelve:shelve"])
     def test_shelve_server(self):
         with self.rbac_utils.override_role(self):
             self._shelve_server()
@@ -343,7 +343,7 @@
     @decorators.idempotent_id('4b6e849a-9182-49ff-9257-e97e751b475e')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-shelve:unshelve")
+        rules=["os_compute_api:os-shelve:unshelve"])
     def test_unshelve_server(self):
         self._shelve_server()
         with self.rbac_utils.override_role(self):
@@ -364,7 +364,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-evacuate")
+        rules=["os_compute_api:os-evacuate"])
     @decorators.idempotent_id('78ecef3c-faff-412a-83be-47651963eb21')
     def test_evacuate_server(self):
         fake_host_name = data_utils.rand_name(
@@ -396,12 +396,12 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:show:host_status")
+        rules=["os_compute_api:servers:show:host_status"])
     @decorators.idempotent_id('736da575-86f8-4b2a-9902-dd37dc9a409b')
     def test_show_server_host_status(self):
         with self.rbac_utils.override_role(self):
             server = self.servers_client.show_server(self.server_id)['server']
 
         if 'host_status' not in server:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='host_status')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_consoles_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_consoles_rbac.py
index fa2f359..4570ea1 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_consoles_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_consoles_rbac.py
@@ -37,7 +37,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-console-output")
+        rules=["os_compute_api:os-console-output"])
     @decorators.idempotent_id('90fd80f6-456c-11e7-a919-92ebcb67fe33')
     def test_get_console_output(self):
         with self.rbac_utils.override_role(self):
@@ -61,7 +61,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-remote-consoles")
+        rules=["os_compute_api:os-remote-consoles"])
     @decorators.idempotent_id('b0a72c02-9b15-4dcb-b186-efe8753370ab')
     def test_get_vnc_console_output(self):
         with self.rbac_utils.override_role(self):
@@ -86,7 +86,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-remote-consoles")
+        rules=["os_compute_api:os-remote-consoles"])
     @decorators.idempotent_id('879597de-87e0-4da9-a60a-28c8088dc508')
     def test_get_remote_console_output(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_groups_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_groups_rbac.py
index 1674b1a..26ef0fe 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_groups_rbac.py
@@ -31,7 +31,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-groups:create")
+        rules=["os_compute_api:os-server-groups:create"])
     @decorators.idempotent_id('7f3eae94-6130-47e9-81ac-34009f55be2f')
     def test_create_server_group(self):
         with self.rbac_utils.override_role(self):
@@ -39,7 +39,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-groups:delete")
+        rules=["os_compute_api:os-server-groups:delete"])
     @decorators.idempotent_id('832d9be3-632e-47b2-93d2-5897db43e3e2')
     def test_delete_server_group(self):
         server_group = self.create_test_server_group()
@@ -48,7 +48,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-groups:index")
+        rules=["os_compute_api:os-server-groups:index"])
     @decorators.idempotent_id('5eccd67f-5945-483b-b1c8-de851ebfc1c1')
     def test_list_server_groups(self):
         with self.rbac_utils.override_role(self):
@@ -56,7 +56,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-groups:show")
+        rules=["os_compute_api:os-server-groups:show"])
     @decorators.idempotent_id('62534e3f-7e99-4a3d-a08e-33e056460cf2')
     def test_show_server_group(self):
         server_group = self.create_test_server_group()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_metadata_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_metadata_rbac.py
index 05b1758..6193938 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_metadata_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_metadata_rbac.py
@@ -35,7 +35,7 @@
     @decorators.idempotent_id('b07bbc27-58e2-4581-869d-ad228cec5d9a')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:server-metadata:index")
+        rules=["os_compute_api:server-metadata:index"])
     def test_list_server_metadata(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.list_server_metadata(self.server['id'])
@@ -43,7 +43,7 @@
     @decorators.idempotent_id('6e76748b-2417-4fa2-b41a-c0cc4bff356b')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:server-metadata:update_all")
+        rules=["os_compute_api:server-metadata:update_all"])
     def test_set_server_metadata(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.set_server_metadata(self.server['id'], {})
@@ -51,7 +51,7 @@
     @decorators.idempotent_id('1060bac4-fe16-4a77-be64-d8e482a06eab')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:server-metadata:create")
+        rules=["os_compute_api:server-metadata:create"])
     def test_update_server_metadata(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.update_server_metadata(self.server['id'], {})
@@ -59,7 +59,7 @@
     @decorators.idempotent_id('93dd8323-d3fa-48d1-8bd6-91c1b62fc341')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:server-metadata:show")
+        rules=["os_compute_api:server-metadata:show"])
     def test_show_server_metadata_item(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.show_server_metadata_item(
@@ -68,7 +68,7 @@
     @decorators.idempotent_id('79511293-4bd7-447d-ba7e-634d0f4da70c')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:server-metadata:update")
+        rules=["os_compute_api:server-metadata:update"])
     def test_set_server_metadata_item(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.set_server_metadata_item(
@@ -77,7 +77,7 @@
     @decorators.idempotent_id('feec5064-678d-40bc-a88f-c856e18d1e31')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:server-metadata:delete")
+        rules=["os_compute_api:server-metadata:delete"])
     def test_delete_server_metadata_item(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.delete_server_metadata_item(
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_migrations_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_migrations_rbac.py
index a867b81..8ae8bc0 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_migrations_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_migrations_rbac.py
@@ -62,7 +62,7 @@
                           'Cold migration not available.')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-migrate-server:migrate")
+        rules=["os_compute_api:os-migrate-server:migrate"])
     @decorators.idempotent_id('c6f1607c-9fed-4c00-807e-9ba675b98b1b')
     def test_cold_migration(self):
         server = self.create_test_server(wait_until="ACTIVE")
@@ -76,7 +76,7 @@
                           'Live migration feature is not enabled.')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-migrate-server:migrate_live")
+        rules=["os_compute_api:os-migrate-server:migrate_live"])
     @decorators.idempotent_id('33520834-72c8-4edb-a189-a2e0fc9eb0d3')
     def test_migration_live(self):
         server_id = self.create_test_server(wait_until="ACTIVE",
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..64e1300 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
@@ -81,7 +81,7 @@
     @utils.requires_ext(extension='os-admin-actions', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-admin-actions:reset_state")
+        rules=["os_compute_api:os-admin-actions:reset_state"])
     @decorators.idempotent_id('ae84dd0b-f364-462e-b565-3457f9c019ef')
     def test_reset_server_state(self):
         """Test reset server state, part of os-admin-actions."""
@@ -93,7 +93,7 @@
     @utils.requires_ext(extension='os-admin-actions', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-admin-actions:inject_network_info")
+        rules=["os_compute_api:os-admin-actions:inject_network_info"])
     @decorators.idempotent_id('ce48c340-51c1-4cff-9b6e-0cc5ef008630')
     def test_inject_network_info(self):
         """Test inject network info, part of os-admin-actions."""
@@ -103,7 +103,7 @@
     @utils.requires_ext(extension='os-admin-actions', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-admin-actions:reset_network")
+        rules=["os_compute_api:os-admin-actions:reset_network"])
     @decorators.idempotent_id('2911a242-15c4-4fcb-80d5-80a8930661b0')
     def test_reset_network(self):
         """Test reset network, part of os-admin-actions."""
@@ -114,7 +114,7 @@
                           'Change password not available.')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-admin-password")
+        rules=["os_compute_api:os-admin-password"])
     @decorators.idempotent_id('908a7d59-3a66-441c-94cf-38e57ed14956')
     def test_change_server_password(self):
         """Test change admin password, part of os-admin-password."""
@@ -129,11 +129,13 @@
         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(
         service="nova",
-        rule="os_compute_api:os-config-drive")
+        rules=["os_compute_api:os-config-drive"])
     def test_list_servers_with_details_config_drive(self):
         """Test list servers with config_drive property in response body."""
         with self.rbac_utils.override_role(self):
@@ -141,39 +143,43 @@
         expected_attr = 'config_drive'
         # If the first server contains "config_drive", then all the others do.
         if expected_attr not in body[0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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(
         service="nova",
-        rule="os_compute_api:os-config-drive")
+        rules=["os_compute_api:os-config-drive"])
     def test_show_server_config_drive(self):
         """Test show server with config_drive property in response body."""
         with self.rbac_utils.override_role(self):
             body = self.servers_client.show_server(self.server['id'])['server']
         expected_attr = 'config_drive'
         if expected_attr not in body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute=expected_attr)
 
     @utils.requires_ext(extension='os-deferred-delete', service='compute')
     @decorators.idempotent_id('189bfed4-1e6d-475c-bb8c-d57e60895391')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-deferred-delete")
+        rules=["os_compute_api:os-deferred-delete"])
     def test_force_delete_server(self):
         """Test force delete server, part of os-deferred-delete."""
         with self.rbac_utils.override_role(self):
             # 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(
         service="nova",
-        rule="os_compute_api:os-extended-availability-zone")
+        rules=["os_compute_api:os-extended-availability-zone"])
     def test_list_servers_with_details_extended_availability_zone(self):
         """Test list servers OS-EXT-AZ:availability_zone attr in resp body."""
         expected_attr = 'OS-EXT-AZ:availability_zone'
@@ -182,14 +188,16 @@
             body = self.servers_client.list_servers(detail=True)['servers']
         # If the first server contains `expected_attr`, then all the others do.
         if expected_attr not in body[0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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(
         service="nova",
-        rule="os_compute_api:os-extended-availability-zone")
+        rules=["os_compute_api:os-extended-availability-zone"])
     def test_show_server_extended_availability_zone(self):
         """Test show server OS-EXT-AZ:availability_zone attr in resp body."""
         expected_attr = 'OS-EXT-AZ:availability_zone'
@@ -197,14 +205,16 @@
         with self.rbac_utils.override_role(self):
             body = self.servers_client.show_server(self.server['id'])['server']
         if expected_attr not in body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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(
         service="nova",
-        rule="os_compute_api:os-extended-server-attributes")
+        rules=["os_compute_api:os-extended-server-attributes"])
     def test_list_servers_extended_server_attributes(self):
         """Test list servers with details, with extended server attributes in
         response body.
@@ -219,14 +229,16 @@
         for attr in ('host', 'instance_name'):
             whole_attr = 'OS-EXT-SRV-ATTR:%s' % attr
             if whole_attr not in body[0]:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     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(
         service="nova",
-        rule="os_compute_api:os-extended-server-attributes")
+        rules=["os_compute_api:os-extended-server-attributes"])
     def test_show_server_extended_server_attributes(self):
         """Test show server with extended server attributes in response
         body.
@@ -241,14 +253,16 @@
         for attr in ('host', 'instance_name'):
             whole_attr = 'OS-EXT-SRV-ATTR:%s' % attr
             if whole_attr not in body:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     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(
         service="nova",
-        rule="os_compute_api:os-extended-status")
+        rules=["os_compute_api:os-extended-status"])
     def test_list_servers_extended_status(self):
         """Test list servers with extended properties in response body."""
         with self.rbac_utils.override_role(self):
@@ -258,14 +272,16 @@
                           'OS-EXT-STS:power_state')
         for attr in expected_attrs:
             if attr not in body[0]:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     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(
         service="nova",
-        rule="os_compute_api:os-extended-status")
+        rules=["os_compute_api:os-extended-status"])
     def test_show_server_extended_status(self):
         """Test show server with extended properties in response body."""
         with self.rbac_utils.override_role(self):
@@ -275,14 +291,16 @@
                           'OS-EXT-STS:power_state')
         for attr in expected_attrs:
             if attr not in body:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     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(
         service="nova",
-        rule="os_compute_api:os-extended-volumes")
+        rules=["os_compute_api:os-extended-volumes"])
     def test_list_servers_with_details_extended_volumes(self):
         """Test list servers os-extended-volumes:volumes_attached attr in resp
         body.
@@ -292,14 +310,16 @@
         with self.rbac_utils.override_role(self):
             body = self.servers_client.list_servers(detail=True)['servers']
         if expected_attr not in body[0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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(
         service="nova",
-        rule="os_compute_api:os-extended-volumes")
+        rules=["os_compute_api:os-extended-volumes"])
     def test_show_server_extended_volumes(self):
         """Test show server os-extended-volumes:volumes_attached attr in resp
         body.
@@ -309,14 +329,14 @@
         with self.rbac_utils.override_role(self):
             body = self.servers_client.show_server(self.server['id'])['server']
         if expected_attr not in body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute=expected_attr)
 
     @utils.requires_ext(extension='os-instance-actions', service='compute')
     @decorators.idempotent_id('9d1b131d-407e-4fa3-8eef-eb2c4526f1da')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-instance-actions")
+        rules=["os_compute_api:os-instance-actions"])
     def test_list_instance_actions(self):
         """Test list instance actions, part of os-instance-actions."""
         with self.rbac_utils.override_role(self):
@@ -326,7 +346,7 @@
     @decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-instance-actions:events")
+        rules=["os_compute_api:os-instance-actions:events"])
     def test_show_instance_action(self):
         """Test show instance action, part of os-instance-actions.
 
@@ -340,40 +360,44 @@
                 self.server['id'], request_id)['instanceAction']
 
         if 'events' not in instance_action:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='events')
         # Microversion 2.51+ returns 'events' always, but not 'traceback'. If
         # 'traceback' is also present then policy enforcement passed.
         if 'traceback' not in instance_action['events'][0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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")
+        rules=["os_compute_api:os-keypairs"])
     @decorators.idempotent_id('81e6fa34-c06b-42ca-b195-82bf8699b940')
     def test_show_server_keypair(self):
         with self.rbac_utils.override_role(self):
             result = self.servers_client.show_server(self.server['id'])[
                 'server']
         if 'key_name' not in result:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 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")
+        rules=["os_compute_api:os-keypairs"])
     @decorators.idempotent_id('41ca4280-ec59-4b80-a9b1-6bc6366faf39')
     def test_list_servers_keypairs(self):
         with self.rbac_utils.override_role(self):
             result = self.servers_client.list_servers(detail=True)['servers']
         if 'key_name' not in result[0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='key_name')
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-lock-server:lock")
+        rules=["os_compute_api:os-lock-server:lock"])
     @decorators.idempotent_id('b81e10fb-1864-498f-8c1d-5175c6fec5fb')
     def test_lock_server(self):
         """Test lock server, part of os-lock-server."""
@@ -383,7 +407,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-lock-server:unlock")
+        rules=["os_compute_api:os-lock-server:unlock"])
     @decorators.idempotent_id('d50ef8e8-4bce-11e7-b114-b2f933d5fe66')
     def test_unlock_server(self):
         """Test unlock server, part of os-lock-server."""
@@ -414,7 +438,7 @@
     @utils.requires_ext(extension='os-rescue', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-rescue")
+        rules=["os_compute_api:os-rescue"])
     @decorators.idempotent_id('fbbb2afc-ed0e-4552-887d-ac00fb5d436e')
     def test_rescue_server(self):
         """Test rescue server, part of os-rescue."""
@@ -427,7 +451,7 @@
     @utils.requires_ext(extension='os-rescue', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-rescue")
+        rules=["os_compute_api:os-rescue"])
     def test_unrescue_server(self):
         """Test unrescue server, part of os-rescue."""
         self.servers_client.rescue_server(self.server['id'])
@@ -442,7 +466,7 @@
     @utils.requires_ext(extension='os-server-diagnostics', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-diagnostics")
+        rules=["os_compute_api:os-server-diagnostics"])
     @decorators.idempotent_id('5dabfcc4-bedb-417b-8247-b3ee7c5c0f3e')
     def test_show_server_diagnostics(self):
         """Test show server diagnostics, part of os-server-diagnostics."""
@@ -453,7 +477,7 @@
     @decorators.idempotent_id('aaf43f78-c178-4581-ac18-14afd3f1f6ba')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-password")
+        rules=["os_compute_api:os-server-password"])
     def test_delete_server_password(self):
         """Test delete server password, part of os-server-password."""
         with self.rbac_utils.override_role(self):
@@ -462,17 +486,19 @@
     @utils.requires_ext(extension='os-server-password', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-password")
+        rules=["os_compute_api:os-server-password"])
     @decorators.idempotent_id('f677971a-7d20-493c-977f-6ff0a74b5b2c')
     def test_get_server_password(self):
         """Test show server password, part of os-server-password."""
         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",
-        rule="os_compute_api:os-server-usage")
+        rules=["os_compute_api:os-server-usage"])
     @decorators.idempotent_id('f0437ead-b9fb-462a-9f3d-ce53fac9d57a')
     def test_show_server_usage(self):
         """Test show server usage, part of os-server-usage.
@@ -488,13 +514,13 @@
             body = self.servers_client.show_server(self.server['id'])['server']
         for expected_attr in expected_attrs:
             if expected_attr not in body:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     attribute=expected_attr)
 
     @utils.requires_ext(extension='os-simple-tenant-usage', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-simple-tenant-usage:list")
+        rules=["os_compute_api:os-simple-tenant-usage:list"])
     @decorators.idempotent_id('2aef094f-0452-4df6-a66a-0ec22a92b16e')
     def test_list_simple_tenant_usages(self):
         """Test list tenant usages, part of os-simple-tenant-usage."""
@@ -504,7 +530,7 @@
     @utils.requires_ext(extension='os-simple-tenant-usage', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-simple-tenant-usage:show")
+        rules=["os_compute_api:os-simple-tenant-usage:show"])
     @decorators.idempotent_id('fe7eacda-15c4-4bf7-93ef-1091c4546a9d')
     def test_show_simple_tenant_usage(self):
         """Test show tenant usage, part of os-simple-tenant-usage."""
@@ -518,7 +544,7 @@
     @decorators.idempotent_id('b775930f-237c-431c-83ae-d33ed1b9700b')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-suspend-server:suspend")
+        rules=["os_compute_api:os-suspend-server:suspend"])
     def test_suspend_server(self):
         """Test suspend server, part of os-suspend-server."""
         with self.rbac_utils.override_role(self):
@@ -532,7 +558,7 @@
     @decorators.idempotent_id('4d90bd02-11f8-45b1-a8a1-534665584675')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-suspend-server:resume")
+        rules=["os_compute_api:os-suspend-server:resume"])
     def test_resume_server(self):
         """Test resume server, part of os-suspend-server."""
         self.servers_client.suspend_server(self.server['id'])
@@ -628,7 +654,7 @@
     @decorators.idempotent_id('ddf53cb6-4a0a-4e5a-91e3-6c32aaa3b9b6')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-attach-interfaces")
+        rules=["os_compute_api:os-attach-interfaces"])
     def test_list_interfaces(self):
         """Test list interfaces, part of os-attach-interfaces."""
         with self.rbac_utils.override_role(self):
@@ -640,7 +666,7 @@
     @utils.requires_ext(extension='os-attach-interfaces', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-attach-interfaces")
+        rules=["os_compute_api:os-attach-interfaces"])
     def test_show_interface(self):
         """Test show interfaces, part of os-attach-interfaces."""
         interface = self._attach_interface_to_server()
@@ -654,7 +680,7 @@
     @decorators.idempotent_id('d2d3a24d-4738-4bce-a287-36d664746cde')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-attach-interfaces:create")
+        rules=["os_compute_api:os-attach-interfaces:create"])
     def test_create_interface(self):
         """Test create interface, part of os-attach-interfaces."""
         network_id = self.network['id']
@@ -678,7 +704,7 @@
     @decorators.idempotent_id('55b05692-ed44-4608-a84c-cd4219c82799')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-attach-interfaces:delete")
+        rules=["os_compute_api:os-attach-interfaces:delete"])
     def test_delete_interface(self):
         """Test delete interface, part of os-attach-interfaces."""
         interface = self._attach_interface_to_server()
@@ -693,7 +719,7 @@
     @utils.requires_ext(extension='os-ips', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:ips:index")
+        rules=["os_compute_api:ips:index"])
     def test_list_addresses(self):
         """Test list server addresses, part of ips policy family."""
         with self.rbac_utils.override_role(self):
@@ -703,7 +729,7 @@
     @utils.requires_ext(extension='os-ips', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:ips:show")
+        rules=["os_compute_api:ips:show"])
     def test_list_addresses_by_network(self):
         """Test list server addresses by network, part of ips policy family."""
         addresses = self.servers_client.list_addresses(self.server['id'])[
@@ -718,7 +744,7 @@
                           "Interface attachment is not available.")
     @utils.requires_ext(extension='os-multinic', service='compute')
     @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-multinic")
+        service="nova", rules=["os_compute_api:os-multinic"])
     @decorators.idempotent_id('bd3e2c74-130a-40f0-8085-124d93fe67da')
     def test_add_fixed_ip(self):
         """Test add fixed ip to server network, part of os-multinic."""
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
index cae31f6..826e469 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
@@ -21,9 +21,7 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
-from tempest.lib import exceptions
 
-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 as base
 
@@ -47,7 +45,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:create")
+        rules=["os_compute_api:servers:create"])
     @decorators.idempotent_id('4f34c73a-6ddc-4677-976f-71320fa855bd')
     def test_create_server(self):
         with self.rbac_utils.override_role(self):
@@ -61,7 +59,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:create:forced_host")
+        rules=["os_compute_api:servers:create:forced_host"])
     @decorators.idempotent_id('0ae3c401-52ab-41bc-ab96-c598a65d9ae5')
     def test_create_server_forced_host(self):
         # Retrieve 'nova' zone host information from availiability_zone_list
@@ -88,7 +86,7 @@
     @utils.services('volume')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:create:attach_volume")
+        rules=["os_compute_api:servers:create:attach_volume"])
     @decorators.idempotent_id('eeddac5e-15aa-454f-838d-db608aae4dd8')
     def test_create_server_attach_volume(self):
         # To create a bootable volume, the UUID of the image from which
@@ -126,7 +124,7 @@
     @utils.services('network')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:create:attach_network")
+        rules=["os_compute_api:servers:create:attach_network"])
     @decorators.idempotent_id('b44cd4ff-50a4-42ce-ada3-724e213cd540')
     def test_create_server_attach_network(self):
         def _create_network_resources():
@@ -165,7 +163,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:delete")
+        rules=["os_compute_api:servers:delete"])
     @decorators.idempotent_id('062e3440-e873-4b41-9317-bf6d8be50c12')
     def test_delete_server(self):
         server = self.create_test_server(wait_until='ACTIVE')
@@ -177,18 +175,12 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:servers:update")
+        rules=["os_compute_api:servers:update"])
     @decorators.idempotent_id('077b17cb-5621-43b9-8adf-5725f0d7a863')
     def test_update_server(self):
         new_name = data_utils.rand_name(self.__class__.__name__ + '-server')
         with self.rbac_utils.override_role(self):
-            try:
-                self.servers_client.update_server(self.server['id'],
-                                                  name=new_name)
-                waiters.wait_for_server_status(self.servers_client,
-                                               self.server['id'], 'ACTIVE')
-            except exceptions.ServerFault as e:
-                # Some other policy may have blocked it.
-                LOG.info("ServerFault exception caught. Some other policy "
-                         "blocked updating of server")
-                raise rbac_exceptions.RbacConflictingPolicies(e)
+            self.servers_client.update_server(self.server['id'],
+                                              name=new_name)
+        waiters.wait_for_server_status(self.servers_client,
+                                       self.server['id'], 'ACTIVE')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_tags_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_tags_rbac.py
index 70e7da9..ac571b9 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_tags_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_tags_rbac.py
@@ -47,7 +47,7 @@
     @decorators.idempotent_id('99e73dd3-adec-4044-b46c-84bdded35d09')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-tags:index")
+        rules=["os_compute_api:os-server-tags:index"])
     def test_list_tags(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.list_tags(self.server['id'])
@@ -55,7 +55,7 @@
     @decorators.idempotent_id('9297c99e-94eb-429f-93cf-9b1838e33622')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-tags:show")
+        rules=["os_compute_api:os-server-tags:show"])
     def test_check_tag_existence(self):
         tag_name = self._add_tag_to_server()
         with self.rbac_utils.override_role(self):
@@ -65,7 +65,7 @@
     @decorators.idempotent_id('0d84ee94-d3ca-4635-8edf-b7f67ab8e4a3')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-tags:update")
+        rules=["os_compute_api:os-server-tags:update"])
     def test_update_tag(self):
         with self.rbac_utils.override_role(self):
             self._add_tag_to_server()
@@ -73,7 +73,7 @@
     @decorators.idempotent_id('115c2694-00aa-41ee-99f6-9eab4040c182')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-tags:delete")
+        rules=["os_compute_api:os-server-tags:delete"])
     def test_delete_tag(self):
         tag_name = self._add_tag_to_server()
         with self.rbac_utils.override_role(self):
@@ -82,7 +82,7 @@
     @decorators.idempotent_id('a8e19b87-6580-4bc8-9933-e62561ff667d')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-tags:update_all")
+        rules=["os_compute_api:os-server-tags:update_all"])
     def test_update_all_tags(self):
         new_tag_name = data_utils.rand_name(self.__class__.__name__ + '-tag')
         with self.rbac_utils.override_role(self):
@@ -92,7 +92,7 @@
     @decorators.idempotent_id('89d51936-e333-42f9-a045-132a4865ba1a')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-server-tags:delete_all")
+        rules=["os_compute_api:os-server-tags:delete_all"])
     def test_delete_all_tags(self):
         with self.rbac_utils.override_role(self):
             self.servers_client.delete_all_tags(self.server['id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
index a510d1e..cb76605 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
@@ -51,7 +51,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes-attachments:index")
+        rules=["os_compute_api:os-volumes-attachments:index"])
     @decorators.idempotent_id('529b668b-6edb-41d5-8886-d7dbd0614678')
     def test_list_volume_attachments(self):
         with self.rbac_utils.override_role(self):
@@ -60,7 +60,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes-attachments:create")
+        rules=["os_compute_api:os-volumes-attachments:create"])
     @decorators.idempotent_id('21c2c3fd-fbe8-41b1-8ef8-115ec47d54c1')
     def test_create_volume_attachment(self):
         with self.rbac_utils.override_role(self):
@@ -69,7 +69,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes-attachments:show")
+        rules=["os_compute_api:os-volumes-attachments:show"])
     @decorators.idempotent_id('997df9c2-6e54-47b6-ab74-e4fdb500f385')
     def test_show_volume_attachment(self):
         attachment = self.attach_volume(self.server, self.volume)
@@ -83,7 +83,7 @@
                           'In-place swapping of volumes not supported.')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes-attachments:update")
+        rules=["os_compute_api:os-volumes-attachments:update"])
     @decorators.idempotent_id('bd667186-eca6-4b78-ab6a-3e2fabcb971f')
     def test_update_volume_attachment(self):
         attachment = self.attach_volume(self.server, self.volume)
@@ -108,7 +108,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes-attachments:delete")
+        rules=["os_compute_api:os-volumes-attachments:delete"])
     @decorators.idempotent_id('12b03e90-d087-46af-9c4d-507d021c4984')
     def test_delete_volume_attachment(self):
         self.attach_volume(self.server, self.volume)
diff --git a/patrole_tempest_plugin/tests/api/compute/test_services_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_services_rbac.py
index 183d990..f32e117 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_services_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_services_rbac.py
@@ -31,7 +31,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-services")
+        rules=["os_compute_api:os-services"])
     @decorators.idempotent_id('7472261b-9c6d-453a-bcb3-aecaa29ad281')
     def test_list_services(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py
index 49fb34c..a4ccc10 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py
@@ -54,7 +54,7 @@
     @decorators.idempotent_id('42b39ba1-14aa-4799-9518-34367d0da67a')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-tenant-networks")
+        rules=["os_compute_api:os-tenant-networks"])
     def test_list_show_tenant_networks(self):
         with self.rbac_utils.override_role(self):
             self.tenant_networks_client.list_tenant_networks()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
index ae77a34..8376be0 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
@@ -45,7 +45,7 @@
 
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-virtual-interfaces")
+        rules=["os_compute_api:os-virtual-interfaces"])
     @decorators.idempotent_id('fc719ae3-0f73-4689-8378-1b841f0f2818')
     def test_list_virtual_interfaces(self):
         """Test list virtual interfaces, part of os-virtual-interfaces.
diff --git a/patrole_tempest_plugin/tests/api/compute/test_volume_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_volume_rbac.py
index b07fb3f..3c693ab 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_volume_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_volume_rbac.py
@@ -60,7 +60,7 @@
     @decorators.idempotent_id('2402013e-a624-43e3-9518-44a5d1dbb32d')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_create_volume(self):
         with self.rbac_utils.override_role(self):
             volume = self.volumes_extensions_client.create_volume(
@@ -73,7 +73,7 @@
     @decorators.idempotent_id('69b3888c-dff2-47b0-9fa4-0672619c9054')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_list_volumes(self):
         with self.rbac_utils.override_role(self):
             self.volumes_extensions_client.list_volumes()
@@ -81,7 +81,7 @@
     @decorators.idempotent_id('4ba0a820-040f-488b-86bb-be2e920ea12c')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_show_volume(self):
         with self.rbac_utils.override_role(self):
             self.volumes_extensions_client.show_volume(self.volume['id'])
@@ -89,7 +89,7 @@
     @decorators.idempotent_id('6e7870f2-1bb2-4b58-96f8-6782071ef327')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_delete_volume(self):
         volume = self.create_volume()
         with self.rbac_utils.override_role(self):
@@ -98,7 +98,7 @@
     @decorators.idempotent_id('0c3eaa4f-69d6-4a13-9dda-19585f36b1c1')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_create_snapshot(self):
         s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
         with self.rbac_utils.override_role(self):
@@ -110,7 +110,7 @@
     @decorators.idempotent_id('e944e816-416c-11e7-a919-92ebcb67fe33')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_list_snapshots(self):
         with self.rbac_utils.override_role(self):
             self.snapshots_extensions_client.list_snapshots()
@@ -118,7 +118,7 @@
     @decorators.idempotent_id('19c2e6bd-585b-472f-a8d7-71ea9299c655')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_show_snapshot(self):
         s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
         snapshot = self.snapshots_extensions_client.create_snapshot(
@@ -131,7 +131,7 @@
     @decorators.idempotent_id('f4f5635c-416c-11e7-a919-92ebcb67fe33')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-volumes")
+        rules=["os_compute_api:os-volumes"])
     def test_delete_snapshot(self):
         s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
         snapshot = self.snapshots_extensions_client.create_snapshot(
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_application_credentials_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py
index c7a6033..1d466f3 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py
@@ -52,23 +52,26 @@
         return application_credential
 
     @decorators.idempotent_id('b53bee14-e9df-4929-b257-6def76c12e4d')
-    @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_application_credential")
+    @rbac_rule_validation.action(
+        service="keystone",
+        rules=["identity:create_application_credential"])
     def test_create_application_credential(self):
         with self.rbac_utils.override_role(self):
             self._create_application_credential()
 
     @decorators.idempotent_id('58b3c3a0-5ad0-44f7-8da7-0736f71f7168')
-    @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_application_credentials")
+    @rbac_rule_validation.action(
+        service="keystone",
+        rules=["identity:list_application_credentials"])
     def test_list_application_credentials(self):
         with self.rbac_utils.override_role(self):
             self.application_credentials_client.list_application_credentials(
                 user_id=self.user_id)
 
     @decorators.idempotent_id('d7b13968-a8a6-47fd-8e1d-7cc7f565c7f8')
-    @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_application_credential")
+    @rbac_rule_validation.action(
+        service="keystone",
+        rules=["identity:get_application_credential"])
     def test_show_application_credential(self):
         app_cred = self._create_application_credential()
         with self.rbac_utils.override_role(self):
@@ -76,8 +79,9 @@
                 user_id=self.user_id, application_credential_id=app_cred['id'])
 
     @decorators.idempotent_id('521b7c0f-1dd5-47a6-ae95-95c0323d7735')
-    @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_application_credential")
+    @rbac_rule_validation.action(
+        service="keystone",
+        rules=["identity:delete_application_credential"])
     def test_delete_application_credential(self):
         app_cred = self._create_application_credential()
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_auth_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_auth_rbac.py
index 8393696..a63192f 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_auth_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_auth_rbac.py
@@ -32,14 +32,14 @@
 
     @decorators.idempotent_id('2a9fbf7f-6feb-4161-ae4b-faf7d6421b1a')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_auth_projects")
+                                 rules=["identity:get_auth_projects"])
     def test_list_auth_projects(self):
         with self.rbac_utils.override_role(self):
             self.identity_client.list_auth_projects()
 
     @decorators.idempotent_id('6a40af0d-7265-4657-b6b2-87a2828e263e')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_auth_domains")
+                                 rules=["identity:get_auth_domains"])
     def test_list_auth_domain(self):
         with self.rbac_utils.override_role(self):
             self.identity_client.list_auth_domains()
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_credentials_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_credentials_rbac.py
index af6feb6..26e34da 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_credentials_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_credentials_rbac.py
@@ -13,13 +13,21 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.identity import rbac_base
 
+CONF = config.CONF
 
+
+@testtools.skipIf(
+    CONF.policy_feature_enabled.removed_keystone_policies_stein,
+    "This policy is unavailable in Stein so cannot be tested.")
 class IdentityCredentialsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     def _create_user_project_and_credential(self):
@@ -29,7 +37,7 @@
         return credential
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_credential")
+                                 rules=["identity:create_credential"])
     @decorators.idempotent_id('c1ab6d34-c59f-4ae1-bae9-bb3c1089b48e')
     def test_create_credential(self):
         project = self.setup_test_project()
@@ -38,7 +46,7 @@
             self.setup_test_credential(user=user)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_credential")
+                                 rules=["identity:update_credential"])
     @decorators.idempotent_id('cfb05ce3-bffb-496e-a3c2-9515d730da63')
     def test_update_credential(self):
         credential = self._create_user_project_and_credential()
@@ -54,7 +62,7 @@
                 project_id=credential['project_id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_credential")
+                                 rules=["identity:delete_credential"])
     @decorators.idempotent_id('87ab42af-8d41-401b-90df-21e72919fcde')
     def test_delete_credential(self):
         credential = self._create_user_project_and_credential()
@@ -63,7 +71,7 @@
             self.creds_client.delete_credential(credential['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_credential")
+                                 rules=["identity:get_credential"])
     @decorators.idempotent_id('1b6eeae6-f1e8-4cdf-8903-1c002b1fc271')
     def test_show_credential(self):
         credential = self._create_user_project_and_credential()
@@ -72,7 +80,7 @@
             self.creds_client.show_credential(credential['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_credentials")
+                                 rules=["identity:list_credentials"])
     @decorators.idempotent_id('3de303e2-12a7-4811-805a-f18906472038')
     def test_list_credentials(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_domain_configuration_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_domain_configuration_rbac.py
index 8db8906..4fa3937 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_domain_configuration_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_domain_configuration_rbac.py
@@ -53,14 +53,14 @@
         return domain_config
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_domain_config")
+                                 rules=["identity:create_domain_config"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd115')
     def test_create_domain_config(self):
         with self.rbac_utils.override_role(self):
             self._create_domain_config(self.domain_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain_config")
+                                 rules=["identity:get_domain_config"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd118')
     def test_show_domain_config(self):
         with self.rbac_utils.override_role(self):
@@ -68,7 +68,7 @@
 
     @decorators.idempotent_id('1b539f95-4991-4e09-960f-fa771e1007d7')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain_config")
+                                 rules=["identity:get_domain_config"])
     def test_show_domain_group_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.show_domain_group_config(
@@ -76,7 +76,7 @@
 
     @decorators.idempotent_id('590c774d-a294-44f8-866e-aac9f4ab3809')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain_config")
+                                 rules=["identity:get_domain_config"])
     def test_show_domain_group_option_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.show_domain_group_option_config(
@@ -85,7 +85,7 @@
     @decorators.idempotent_id('21053885-1ce3-4167-b5e3-e470253481da')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:get_security_compliance_domain_config")
+        rules=["identity:get_security_compliance_domain_config"])
     def test_show_security_compliance_domain_config(self):
         # The "security_compliance" group can only be shown for the default
         # domain.
@@ -95,28 +95,28 @@
 
     @decorators.idempotent_id('d1addd10-9ae4-4360-9961-47324fd22f23')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain_config_default")
+                                 rules=["identity:get_domain_config_default"])
     def test_show_default_config_settings(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.show_default_config_settings()
 
     @decorators.idempotent_id('63183377-251f-4622-81f0-6b58a8a285c9')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain_config_default")
+                                 rules=["identity:get_domain_config_default"])
     def test_show_default_group_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.show_default_group_config('identity')
 
     @decorators.idempotent_id('6440e9c1-e8da-474d-9118-89996fffe5f8')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain_config_default")
+                                 rules=["identity:get_domain_config_default"])
     def test_show_default_group_option(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.show_default_group_option('identity',
                                                                 'driver')
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_domain_config")
+                                 rules=["identity:update_domain_config"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd116')
     def test_update_domain_config(self):
         updated_config = {'ldap': {'url': data_utils.rand_url()}}
@@ -126,7 +126,7 @@
 
     @decorators.idempotent_id('6e32bf96-dbe9-4ac8-b814-0e79fa948285')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_domain_config")
+                                 rules=["identity:update_domain_config"])
     def test_update_domain_group_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.update_domain_group_config(
@@ -134,14 +134,14 @@
 
     @decorators.idempotent_id('d2c510da-a077-4c67-9522-27745ef2812b')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_domain_config")
+                                 rules=["identity:update_domain_config"])
     def test_update_domain_group_option_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.update_domain_group_option_config(
                 self.domain_id, 'identity', 'driver', driver='ldap')
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_domain_config")
+                                 rules=["identity:delete_domain_config"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd117')
     def test_delete_domain_config(self):
         with self.rbac_utils.override_role(self):
@@ -149,7 +149,7 @@
 
     @decorators.idempotent_id('f479694b-df02-4d5a-88b6-c8b52f9341eb')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_domain_config")
+                                 rules=["identity:delete_domain_config"])
     def test_delete_domain_group_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.delete_domain_group_config(
@@ -157,7 +157,7 @@
 
     @decorators.idempotent_id('f594bde3-31c9-414f-922d-0ddafdc0ca40')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_domain_config")
+                                 rules=["identity:delete_domain_config"])
     def test_delete_domain_group_option_config(self):
         with self.rbac_utils.override_role(self):
             self.domain_config_client.delete_domain_group_option_config(
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_domains_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_domains_rbac.py
index 3837051..ab38876 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_domains_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_domains_rbac.py
@@ -23,14 +23,14 @@
 class IdentityDomainsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_domain")
+                                 rules=["identity:create_domain"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd110')
     def test_create_domain(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_domain()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_domain")
+                                 rules=["identity:update_domain"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd111')
     def test_update_domain(self):
         domain = self.setup_test_domain()
@@ -42,7 +42,7 @@
                                               name=new_domain_name)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_domain")
+                                 rules=["identity:delete_domain"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd112')
     def test_delete_domain(self):
         domain = self.setup_test_domain()
@@ -54,7 +54,7 @@
             self.domains_client.delete_domain(domain['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_domain")
+                                 rules=["identity:get_domain"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd113')
     def test_show_domain(self):
         domain = self.setup_test_domain()
@@ -62,7 +62,7 @@
             self.domains_client.show_domain(domain['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_domains")
+                                 rules=["identity:list_domains"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd114')
     def test_list_domains(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_endpoints_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_endpoints_rbac.py
index ad1fd9b..ec5ffa3 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_endpoints_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_endpoints_rbac.py
@@ -23,7 +23,7 @@
 class IdentityEndpointsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_endpoint")
+                                 rules=["identity:create_endpoint"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd127')
     def test_create_endpoint(self):
         service = self.setup_test_service()
@@ -31,7 +31,7 @@
             self.setup_test_endpoint(service=service)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_endpoint")
+                                 rules=["identity:update_endpoint"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd128')
     def test_update_endpoint(self):
         endpoint = self.setup_test_endpoint()
@@ -43,7 +43,7 @@
                 url=new_url)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_endpoint")
+                                 rules=["identity:delete_endpoint"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd129')
     def test_delete_endpoint(self):
         endpoint = self.setup_test_endpoint()
@@ -52,7 +52,7 @@
             self.endpoints_client.delete_endpoint(endpoint['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_endpoint")
+                                 rules=["identity:get_endpoint"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd130')
     def test_show_endpoint(self):
         endpoint = self.setup_test_endpoint()
@@ -61,7 +61,7 @@
             self.endpoints_client.show_endpoint(endpoint['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_endpoints")
+                                 rules=["identity:list_endpoints"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd131')
     def test_list_endpoints(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_groups_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_groups_rbac.py
index 6e58289..be49d84 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_groups_rbac.py
@@ -58,14 +58,14 @@
         return endpoint_group['id']
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_endpoint_group")
+                                 rules=["identity:create_endpoint_group"])
     @decorators.idempotent_id('b4765906-52ec-477b-b441-a8508ced68e3')
     def test_create_endpoint_group(self):
         with self.rbac_utils.override_role(self):
             self._create_endpoint_group(ignore_not_found=True)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_endpoint_groups")
+                                 rules=["identity:list_endpoint_groups"])
     @decorators.idempotent_id('089aa3a7-ba1f-4f70-a1cf-f298a845058a')
     def test_list_endpoint_groups(self):
         with self.rbac_utils.override_role(self):
@@ -73,14 +73,14 @@
 
     @decorators.idempotent_id('5c16368d-1485-4c28-9803-db3fa3510623')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_endpoint_group")
+                                 rules=["identity:get_endpoint_group"])
     def test_check_endpoint_group(self):
         with self.rbac_utils.override_role(self):
             self.endpoint_groups_client.check_endpoint_group(
                 self.endpoint_group_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_endpoint_group")
+                                 rules=["identity:get_endpoint_group"])
     @decorators.idempotent_id('bd2b6fb8-661f-4255-84b2-50fea4a1dc61')
     def test_show_endpoint_group(self):
         with self.rbac_utils.override_role(self):
@@ -88,7 +88,7 @@
                 self.endpoint_group_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_endpoint_group")
+                                 rules=["identity:update_endpoint_group"])
     @decorators.idempotent_id('028b9198-ec35-4bd5-8f72-e23dfb7a0c8e')
     def test_update_endpoint_group(self):
         updated_name = data_utils.rand_name(
@@ -99,7 +99,7 @@
                 self.endpoint_group_id, name=updated_name)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_endpoint_group")
+                                 rules=["identity:delete_endpoint_group"])
     @decorators.idempotent_id('88cc105e-70d9-48ac-927e-200ef41e070c')
     def test_delete_endpoint_group(self):
         endpoint_group_id = self._create_endpoint_group(ignore_not_found=True)
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_projects_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_projects_rbac.py
index 1045b9b..17e918d 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_projects_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_ep_filter_projects_rbac.py
@@ -44,7 +44,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:add_endpoint_to_project")
+        rules=["identity:add_endpoint_to_project"])
     @decorators.idempotent_id('9199ec13-816d-4efe-b8b1-e1cd026b9747')
     def test_add_endpoint_to_project(self):
         # Adding endpoints to projects
@@ -53,7 +53,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:list_projects_for_endpoint")
+        rules=["identity:list_projects_for_endpoint"])
     @decorators.idempotent_id('f53dca42-ec8a-48e9-924b-0bbe6c99727f')
     def test_list_projects_for_endpoint(self):
         with self.rbac_utils.override_role(self):
@@ -62,7 +62,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:check_endpoint_in_project")
+        rules=["identity:check_endpoint_in_project"])
     @decorators.idempotent_id('0c1425eb-833c-4aa1-a21d-52ffa41fdc6a')
     def test_check_endpoint_in_project(self):
         self._add_endpoint_to_project()
@@ -72,7 +72,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:list_endpoints_for_project")
+        rules=["identity:list_endpoints_for_project"])
     @decorators.idempotent_id('5d86c659-c6ad-41e0-854e-3823e95c7cc2')
     def test_list_endpoints_in_project(self):
         with self.rbac_utils.override_role(self):
@@ -81,7 +81,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:remove_endpoint_from_project")
+        rules=["identity:remove_endpoint_from_project"])
     @decorators.idempotent_id('b4e21c10-4f47-427b-9b8a-f5b5601adfda')
     def test_remove_endpoint_from_project(self):
         self._add_endpoint_to_project(ignore_not_found=True)
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_groups_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_groups_rbac.py
index 06148d9..9814e3b 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_groups_rbac.py
@@ -30,14 +30,14 @@
         return (group['id'], user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_group")
+                                 rules=["identity:create_group"])
     @decorators.idempotent_id('88377f51-9074-4d64-a22f-f8931d048c9a')
     def test_create_group(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_group()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_group")
+                                 rules=["identity:update_group"])
     @decorators.idempotent_id('790fb7be-a657-4a64-9b83-c43425cf180b')
     def test_update_group(self):
         group = self.setup_test_group()
@@ -48,7 +48,7 @@
             self.groups_client.update_group(group['id'], name=new_group_name)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_group")
+                                 rules=["identity:delete_group"])
     @decorators.idempotent_id('646b52da-2a5f-486a-afb0-51fdc86a6c12')
     def test_delete_group(self):
         group = self.setup_test_group()
@@ -57,7 +57,7 @@
             self.groups_client.delete_group(group['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_group")
+                                 rules=["identity:get_group"])
     @decorators.idempotent_id('d530f0ad-42b9-429b-ad05-e53ac95a040e')
     def test_show_group(self):
         group = self.setup_test_group()
@@ -66,14 +66,14 @@
             self.groups_client.show_group(group['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_groups")
+                                 rules=["identity:list_groups"])
     @decorators.idempotent_id('c4d0f76b-735f-4fd0-868b-0006bc420ff4')
     def test_list_groups(self):
         with self.rbac_utils.override_role(self):
             self.groups_client.list_groups()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:add_user_to_group")
+                                 rules=["identity:add_user_to_group"])
     @decorators.idempotent_id('fdd49b74-3ed3-4736-9f0e-9027a32017ac')
     def test_add_user_group(self):
         group = self.setup_test_group()
@@ -83,7 +83,7 @@
             self.groups_client.add_group_user(group['id'], user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:remove_user_from_group")
+                                 rules=["identity:remove_user_from_group"])
     @decorators.idempotent_id('8a60d11c-7d2b-47e5-a0f3-9ea900ca66fe')
     def test_remove_user_group(self):
         group_id, user_id = self._create_user_and_add_to_new_group()
@@ -92,7 +92,7 @@
             self.groups_client.delete_group_user(group_id, user_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_users_in_group")
+                                 rules=["identity:list_users_in_group"])
     @decorators.idempotent_id('b3e394a7-079e-4a0d-a4ff-9b266293d1ee')
     def test_list_user_group(self):
         group = self.setup_test_group()
@@ -101,7 +101,7 @@
             self.groups_client.list_group_users(group['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_user_in_group")
+                                 rules=["identity:check_user_in_group"])
     @decorators.idempotent_id('d3603241-fd87-4a2d-94f9-f32469d1aaba')
     def test_check_user_group(self):
         group_id, user_id = self._create_user_and_add_to_new_group()
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
index f591e15..ecd534d 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
@@ -34,14 +34,14 @@
         return consumer
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_consumer")
+                                 rules=["identity:create_consumer"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d970')
     def test_create_consumer(self):
         with self.rbac_utils.override_role(self):
             self._create_consumer()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_consumer")
+                                 rules=["identity:delete_consumer"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d971')
     def test_delete_consumer(self):
         consumer = self._create_consumer()
@@ -50,7 +50,7 @@
             self.consumers_client.delete_consumer(consumer['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_consumer")
+                                 rules=["identity:update_consumer"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d972')
     def test_update_consumer(self):
         consumer = self._create_consumer()
@@ -62,7 +62,7 @@
                                                   updated_description)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_consumer")
+                                 rules=["identity:get_consumer"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d973')
     def test_show_consumer(self):
         consumer = self._create_consumer()
@@ -71,7 +71,7 @@
             self.consumers_client.show_consumer(consumer['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_consumers")
+                                 rules=["identity:list_consumers"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d975')
     def test_list_consumers(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py
index 13731d5..30b386f 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py
@@ -80,7 +80,7 @@
         return access_key
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:authorize_request_token")
+                                 rules=["identity:authorize_request_token"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d976')
     def test_authorize_request_token(self):
         _, request_token = self._create_consumer_and_request_token()
@@ -91,7 +91,7 @@
                 self.role_ids)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_access_token")
+                                 rules=["identity:get_access_token"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d977')
     def test_get_access_token(self):
         access_token = self._create_access_token()
@@ -101,7 +101,7 @@
                                                      access_token)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_access_token_role")
+                                 rules=["identity:get_access_token_role"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d980')
     def test_get_access_token_role(self):
         access_token = self._create_access_token()
@@ -111,14 +111,14 @@
                 self.user_id, access_token, self.role_ids[0])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_access_tokens")
+                                 rules=["identity:list_access_tokens"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d979')
     def test_list_access_tokens(self):
         with self.rbac_utils.override_role(self):
             self.oauth_token_client.list_access_tokens(self.user_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_access_token_roles")
+                                 rules=["identity:list_access_token_roles"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d978')
     def test_list_access_token_roles(self):
         access_token = self._create_access_token()
@@ -128,7 +128,7 @@
                 self.user_id, access_token)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_access_token")
+                                 rules=["identity:delete_access_token"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d981')
     def test_revoke_access_token(self):
         access_token = self._create_access_token()
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_policies_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_policies_rbac.py
index a8c10ca..b0a3087 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_policies_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_policies_rbac.py
@@ -23,14 +23,14 @@
 class IdentityPoliciesV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_policy")
+                                 rules=["identity:create_policy"])
     @decorators.idempotent_id('de2f7ecb-fbf0-41f3-abf4-b97b5e082fd5')
     def test_create_policy(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_policy()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_policy")
+                                 rules=["identity:update_policy"])
     @decorators.idempotent_id('9cfed3c6-0b27-4d15-be67-e06e0cfb01b9')
     def test_update_policy(self):
         policy = self.setup_test_policy()
@@ -42,7 +42,7 @@
                                                type=updated_policy_type)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_policy")
+                                 rules=["identity:delete_policy"])
     @decorators.idempotent_id('dcd93f75-1e1b-4fbe-bee0-9c4c7b201735')
     def test_delete_policy(self):
         policy = self.setup_test_policy()
@@ -51,7 +51,7 @@
             self.policies_client.delete_policy(policy['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_policy")
+                                 rules=["identity:get_policy"])
     @decorators.idempotent_id('d7e415c2-945a-4504-9571-0e2d0dd8594b')
     def test_show_policy(self):
         policy = self.setup_test_policy()
@@ -60,7 +60,7 @@
             self.policies_client.show_policy(policy['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_policies")
+                                 rules=["identity:list_policies"])
     @decorators.idempotent_id('35a56161-4054-4237-8a78-7ce805dce202')
     def test_list_policies(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_policy_association_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_policy_association_rbac.py
index 2a69224..f56b6e4 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_policy_association_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_policy_association_rbac.py
@@ -58,7 +58,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:create_policy_association_for_endpoint")
+        rules=["identity:create_policy_association_for_endpoint"])
     @decorators.idempotent_id('1b3f4f62-4f4a-4d27-be27-9a113058597f')
     def test_update_policy_association_for_endpoint(self):
         with self.rbac_utils.override_role(self):
@@ -67,7 +67,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:check_policy_association_for_endpoint")
+        rules=["identity:check_policy_association_for_endpoint"])
     @decorators.idempotent_id('25ce8c89-e751-465c-8d35-52bacd774beb')
     def test_show_policy_association_for_endpoint(self):
         self._update_policy_association_for_endpoint(
@@ -78,7 +78,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:delete_policy_association_for_endpoint")
+        rules=["identity:delete_policy_association_for_endpoint"])
     @decorators.idempotent_id('95cad2d8-bcd0-4c4e-a8f7-cc80601e43a1')
     def test_delete_policy_association_for_endpoint(self):
         self._update_policy_association_for_endpoint(
@@ -89,7 +89,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:create_policy_association_for_service")
+        rules=["identity:create_policy_association_for_service"])
     @decorators.idempotent_id('57fb80fe-6ce2-4995-b710-4692b3fc3cdc')
     def test_update_policy_association_for_service(self):
         with self.rbac_utils.override_role(self):
@@ -98,7 +98,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:check_policy_association_for_service")
+        rules=["identity:check_policy_association_for_service"])
     @decorators.idempotent_id('5cbe285f-4888-4f98-978f-30210ff28b74')
     def test_show_policy_association_for_service(self):
         self._update_policy_association_for_service(
@@ -109,7 +109,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:delete_policy_association_for_service")
+        rules=["identity:delete_policy_association_for_service"])
     @decorators.idempotent_id('f754455c-02a4-4fb6-8c73-64ef453f955f')
     def test_delete_policy_association_for_service(self):
         self._update_policy_association_for_service(
@@ -120,7 +120,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:create_policy_association_for_region_and_service")
+        rules=["identity:create_policy_association_for_region_and_service"])
     @decorators.idempotent_id('54d2a93e-c84d-4079-8ea9-2fb227c262a1')
     def test_update_policy_association_for_region_and_service(self):
         with self.rbac_utils.override_role(self):
@@ -129,7 +129,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:check_policy_association_for_region_and_service")
+        rules=["identity:check_policy_association_for_region_and_service"])
     @decorators.idempotent_id('0763b780-52c1-47bc-9316-1fe12a2ab0bc')
     def test_show_policy_association_for_region_and_service(self):
         self._update_policy_association_for_region_and_service(
@@ -141,7 +141,7 @@
 
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:delete_policy_association_for_region_and_service")
+        rules=["identity:delete_policy_association_for_region_and_service"])
     @decorators.idempotent_id('9c956888-81d4-4a24-8203-bff7b8a7834c')
     def test_delete_policy_association_for_region_and_service(self):
         self._update_policy_association_for_region_and_service(
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_project_tags_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_project_tags_rbac.py
index e0be9cb..debc2e9 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_project_tags_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_project_tags_rbac.py
@@ -42,7 +42,7 @@
 
     @decorators.idempotent_id('acbd7b2d-0a4d-4990-9fab-eccad69d4238')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_project_tag")
+                                 rules=["identity:create_project_tag"])
     def test_update_project_tag(self):
         tag = data_utils.rand_name(self.__class__.__name__ + '-Tag')
         with self.rbac_utils.override_role(self):
@@ -50,14 +50,14 @@
 
     @decorators.idempotent_id('e122d7d1-bb6d-43af-b489-afa8c609b9ae')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_project_tags")
+                                 rules=["identity:list_project_tags"])
     def test_list_project_tags(self):
         with self.rbac_utils.override_role(self):
             self.project_tags_client.list_project_tags(self.project_id)
 
     @decorators.idempotent_id('716f9081-4626-4594-a82c-e7dc037464ac')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_project_tags")
+                                 rules=["identity:update_project_tags"])
     def test_update_all_project_tags(self):
         tags = [
             data_utils.rand_name(self.__class__.__name__ + '-Tag')
@@ -69,7 +69,7 @@
 
     @decorators.idempotent_id('974cb1da-d7d4-4863-99da-4a3f0c801729')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_project_tag")
+                                 rules=["identity:get_project_tag"])
     def test_check_project_tag_existence(self):
         tag = data_utils.rand_name(self.__class__.__name__ + '-Tag')
         self.project_tags_client.update_project_tag(self.project_id, tag)
@@ -80,7 +80,7 @@
 
     @decorators.idempotent_id('ffe0c8e1-f9eb-43c5-8097-1e938fc08e07')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_project_tag")
+                                 rules=["identity:delete_project_tag"])
     def test_delete_project_tag(self):
         tag = data_utils.rand_name(self.__class__.__name__ + '-Tag')
         self.project_tags_client.update_project_tag(self.project_id, tag)
@@ -90,7 +90,7 @@
 
     @decorators.idempotent_id('94d0ef63-e9e3-4287-9c5e-bd5464467d77')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_project_tags")
+                                 rules=["identity:delete_project_tags"])
     def test_delete_all_project_tags(self):
         with self.rbac_utils.override_role(self):
             self.project_tags_client.delete_all_project_tags(self.project_id)
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_projects_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_projects_rbac.py
index 0b394b4..e1e6f08 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_projects_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_projects_rbac.py
@@ -23,14 +23,14 @@
 class IdentityProjectV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_project")
+                                 rules=["identity:create_project"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d904')
     def test_create_project(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_project()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_project")
+                                 rules=["identity:update_project"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d905')
     def test_update_project(self):
         project = self.setup_test_project()
@@ -42,7 +42,7 @@
                                                 description=new_desc)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_project")
+                                 rules=["identity:delete_project"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d906')
     def test_delete_project(self):
         project = self.setup_test_project()
@@ -51,7 +51,7 @@
             self.projects_client.delete_project(project['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_project")
+                                 rules=["identity:get_project"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d907')
     def test_show_project(self):
         project = self.setup_test_project()
@@ -60,7 +60,7 @@
             self.projects_client.show_project(project['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_projects")
+                                 rules=["identity:list_projects"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d908')
     def test_list_projects(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_regions_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_regions_rbac.py
index 14b9de5..1d81319 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_regions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_regions_rbac.py
@@ -23,14 +23,14 @@
 class IdentityRegionsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_region")
+                                 rules=["identity:create_region"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd119')
     def test_create_region(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_region()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_region")
+                                 rules=["identity:update_region"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd120')
     def test_update_region(self):
         region = self.setup_test_region()
@@ -42,7 +42,7 @@
                                               description=new_description)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_region")
+                                 rules=["identity:delete_region"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd121')
     def test_delete_region(self):
         region = self.setup_test_region()
@@ -51,7 +51,7 @@
             self.regions_client.delete_region(region['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_region")
+                                 rules=["identity:get_region"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd122')
     def test_show_region(self):
         region = self.setup_test_region()
@@ -60,7 +60,7 @@
             self.regions_client.show_region(region['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_regions")
+                                 rules=["identity:list_regions"])
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd123')
     def test_list_regions(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py
index 90cf255..3eabac8 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py
@@ -23,7 +23,7 @@
 
     @decorators.idempotent_id('afe57adb-1b9c-43d9-84a9-f0cf4c94e416')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_role_assignments")
+                                 rules=["identity:list_role_assignments"])
     def test_list_role_assignments(self):
         with self.rbac_utils.override_role(self):
             self.role_assignments_client.list_role_assignments()
@@ -31,7 +31,7 @@
     @decorators.idempotent_id('36c7a990-857e-415c-8717-38d7200a9894')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:list_role_assignments_for_tree")
+        rules=["identity:list_role_assignments_for_tree"])
     def test_list_role_assignments_for_tree(self):
         project = self.setup_test_project()
 
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py
index 099c702..8242194 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py
@@ -34,14 +34,14 @@
         cls.user = cls.setup_test_user()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_role")
+                                 rules=["identity:create_role"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d904')
     def test_create_role(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_role()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_role")
+                                 rules=["identity:update_role"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d905')
     def test_update_role(self):
         new_role_name = data_utils.rand_name(
@@ -52,7 +52,7 @@
                                           name=new_role_name)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_role")
+                                 rules=["identity:delete_role"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d906')
     def test_delete_role(self):
         role = self.setup_test_role()
@@ -61,21 +61,21 @@
             self.roles_client.delete_role(role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_role")
+                                 rules=["identity:get_role"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d907')
     def test_show_role(self):
         with self.rbac_utils.override_role(self):
             self.roles_client.show_role(self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_roles")
+                                 rules=["identity:list_roles"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d908')
     def test_list_roles(self):
         with self.rbac_utils.override_role(self):
             self.roles_client.list_roles()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_grant")
+                                 rules=["identity:create_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d909')
     def test_create_user_role_on_project(self):
         with self.rbac_utils.override_role(self):
@@ -90,7 +90,7 @@
                         self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_grant")
+                                 rules=["identity:create_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d90c')
     def test_create_group_role_on_project(self):
         with self.rbac_utils.override_role(self):
@@ -105,7 +105,7 @@
                         self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_grant")
+                                 rules=["identity:create_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d90f')
     def test_create_user_role_on_domain(self):
         with self.rbac_utils.override_role(self):
@@ -120,7 +120,7 @@
                         self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_grant")
+                                 rules=["identity:create_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d912')
     def test_create_group_role_on_domain(self):
         with self.rbac_utils.override_role(self):
@@ -135,7 +135,7 @@
                         self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_grant")
+                                 rules=["identity:check_grant"])
     @decorators.idempotent_id('22921b1e-1a33-4026-bff9-f236d6dd149c')
     def test_check_user_role_existence_on_project(self):
         self.roles_client.create_user_role_on_project(
@@ -156,7 +156,7 @@
 
     @decorators.idempotent_id('92f8e67d-85bf-407d-9814-edd5664abc47')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_grant")
+                                 rules=["identity:check_grant"])
     def test_check_user_role_existence_on_domain(self):
         self.roles_client.create_user_role_on_domain(
             self.domain['id'],
@@ -176,7 +176,7 @@
 
     @decorators.idempotent_id('8738d3d2-8c84-4423-b36c-7c59eaa08b73')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_grant")
+                                 rules=["identity:check_grant"])
     def test_check_role_from_group_on_project_existence(self):
         self.roles_client.create_group_role_on_project(
             self.project['id'],
@@ -196,7 +196,7 @@
 
     @decorators.idempotent_id('e7d73bd0-cf5e-4c0c-9c93-cf53e23232d6')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_grant")
+                                 rules=["identity:check_grant"])
     def test_check_role_from_group_on_domain_existence(self):
         self.roles_client.create_group_role_on_domain(
             self.domain['id'],
@@ -215,7 +215,7 @@
                 self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:revoke_grant")
+                                 rules=["identity:revoke_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d90a')
     def test_delete_role_from_user_on_project(self):
         self.roles_client.create_user_role_on_project(
@@ -235,7 +235,7 @@
                 self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:revoke_grant")
+                                 rules=["identity:revoke_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d90d')
     def test_delete_role_from_group_on_project(self):
         self.roles_client.create_group_role_on_project(
@@ -255,7 +255,7 @@
                 self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:revoke_grant")
+                                 rules=["identity:revoke_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d910')
     def test_delete_role_from_user_on_domain(self):
         self.roles_client.create_user_role_on_domain(
@@ -275,7 +275,7 @@
                 self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:revoke_grant")
+                                 rules=["identity:revoke_grant"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d913')
     def test_delete_role_from_group_on_domain(self):
         self.roles_client.create_group_role_on_domain(
@@ -295,7 +295,7 @@
                 self.role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_grants")
+                                 rules=["identity:list_grants"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d90b')
     def test_list_user_roles_on_project(self):
         with self.rbac_utils.override_role(self):
@@ -304,7 +304,7 @@
                 self.user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_grants")
+                                 rules=["identity:list_grants"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d90e')
     def test_list_group_roles_on_project(self):
         with self.rbac_utils.override_role(self):
@@ -313,7 +313,7 @@
                 self.group['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_grants")
+                                 rules=["identity:list_grants"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d911')
     def test_list_user_roles_on_domain(self):
         with self.rbac_utils.override_role(self):
@@ -322,7 +322,7 @@
                 self.user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_grants")
+                                 rules=["identity:list_grants"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1395-080044d0d914')
     def test_list_group_roles_on_domain(self):
         with self.rbac_utils.override_role(self):
@@ -332,7 +332,7 @@
 
     @decorators.idempotent_id('2aef3eaa-8156-4962-a01d-c9bb0e499e15')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_implied_role")
+                                 rules=["identity:create_implied_role"])
     def test_create_role_inference_rule(self):
         with self.rbac_utils.override_role(self):
             self.roles_client.create_role_inference_rule(
@@ -342,7 +342,7 @@
 
     @decorators.idempotent_id('83f997b2-55c4-4894-b1f2-e175b19d1fa5')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_implied_role")
+                                 rules=["identity:get_implied_role"])
     def test_show_role_inference_rule(self):
         self.roles_client.create_role_inference_rule(
             self.role['id'], self.implies_role['id'])
@@ -355,14 +355,14 @@
 
     @decorators.idempotent_id('f7bb39bf-0b06-468e-a8b0-60a4fb1f258d')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_implied_roles")
+                                 rules=["identity:list_implied_roles"])
     def test_list_role_inferences_rules(self):
         with self.rbac_utils.override_role(self):
             self.roles_client.list_role_inferences_rules(self.role['id'])
 
     @decorators.idempotent_id('eca2d502-09bb-45cd-9773-bce2e7bcddd1')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_implied_role")
+                                 rules=["identity:check_implied_role"])
     def test_check_role_inference_rule(self):
         self.roles_client.create_role_inference_rule(
             self.role['id'], self.implies_role['id'])
@@ -375,7 +375,7 @@
 
     @decorators.idempotent_id('13a5db1e-dd4a-4ca1-81ec-d5452aaaf54b')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_implied_role")
+                                 rules=["identity:delete_implied_role"])
     def test_delete_role_inference_rule(self):
         self.roles_client.create_role_inference_rule(
             self.role['id'], self.implies_role['id'])
@@ -389,7 +389,7 @@
 
     @decorators.idempotent_id('05869f2b-4dd4-425a-905e-eec9a6f06374')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_role_inference_rules")
+                                 rules=["identity:list_role_inference_rules"])
     def test_list_all_role_inference_rules(self):
         with self.rbac_utils.override_role(self):
             self.roles_client.list_all_role_inference_rules()
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_services_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_services_rbac.py
index 6ab17ff..41ba5ba 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_services_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_services_rbac.py
@@ -23,14 +23,14 @@
 class IdentitySericesV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_service")
+                                 rules=["identity:create_service"])
     @decorators.idempotent_id('9a4bb317-f0bb-4005-8df0-4b672885b7c8')
     def test_create_service(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_service()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_service")
+                                 rules=["identity:update_service"])
     @decorators.idempotent_id('b39447d1-2cf6-40e5-a899-46f287f2ecf0')
     def test_update_service(self):
         service = self.setup_test_service()
@@ -43,7 +43,7 @@
                                                 type=service['type'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_service")
+                                 rules=["identity:delete_service"])
     @decorators.idempotent_id('177b991a-438d-4bef-8e9f-9c6cc5a1c9e8')
     def test_delete_service(self):
         service = self.setup_test_service()
@@ -52,7 +52,7 @@
             self.services_client.delete_service(service['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_service")
+                                 rules=["identity:get_service"])
     @decorators.idempotent_id('d89a9ac6-cd53-428d-84c0-5bc71f4a432d')
     def test_show_service(self):
         service = self.setup_test_service()
@@ -61,7 +61,7 @@
             self.services_client.show_service(service['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_services")
+                                 rules=["identity:list_services"])
     @decorators.idempotent_id('706e6bea-3385-4718-919c-0b5121395806')
     def test_list_services(self):
         with self.rbac_utils.override_role(self):
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..c1c2934 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):
 
@@ -46,7 +43,7 @@
     @decorators.attr(type=['negative'])
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:validate_token",
+        rules=["identity:validate_token"],
         extra_target_data={
             "target.token.user_id":
             "os_alt.auth_provider.credentials.user_id"
@@ -65,7 +62,7 @@
     @decorators.attr(type=['negative'])
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:revoke_token",
+        rules=["identity:revoke_token"],
         extra_target_data={
             "target.token.user_id":
             "os_alt.auth_provider.credentials.user_id"
@@ -84,7 +81,7 @@
     @decorators.attr(type=['negative'])
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:check_token",
+        rules=["identity:check_token"],
         extra_target_data={
             "target.token.user_id":
             "os_alt.auth_provider.credentials.user_id"
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_rbac.py
index 23ee768..a648774 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_rbac.py
@@ -30,7 +30,7 @@
     @decorators.idempotent_id('201e2fe5-2023-4bce-9189-78b51520a91e')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:validate_token",
+        rules=["identity:validate_token"],
         extra_target_data={
             "target.token.user_id":
             "os_primary.auth_provider.credentials.user_id"
@@ -43,7 +43,7 @@
     @decorators.idempotent_id('42a299db-fe0a-4ea0-9824-0bfd13155886')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:revoke_token",
+        rules=["identity:revoke_token"],
         extra_target_data={
             "target.token.user_id":
             "os_primary.auth_provider.credentials.user_id"
@@ -56,7 +56,7 @@
     @decorators.idempotent_id('3554d218-8cd6-4730-a1b2-0e22f9b78f45')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:check_token",
+        rules=["identity:check_token"],
         extra_target_data={
             "target.token.user_id":
             "os_primary.auth_provider.credentials.user_id"
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py
index 91dbb53..985b992 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py
@@ -65,7 +65,7 @@
     @decorators.idempotent_id('7ab595a7-9b71-45fe-91d8-2793b0292f72')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:create_trust",
+        rules=["identity:create_trust"],
         extra_target_data={
             "trust.trustor_user_id": "os_primary.credentials.user_id"
         })
@@ -78,7 +78,7 @@
     @decorators.attr(type=['negative'])
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:create_trust",
+        rules=["identity:create_trust"],
         extra_target_data={
             "trust.trustor_user_id": "os_alt.credentials.user_id"
         })
@@ -94,7 +94,7 @@
     @decorators.idempotent_id('d9a6fd06-08f6-462c-a86c-ce009adf1230')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:delete_trust")
+        rules=["identity:delete_trust"])
     def test_delete_trust(self):
         trust = self.setup_test_trust(trustor_user_id=self.trustor_user_id,
                                       trustee_user_id=self.trustee_user_id)
@@ -105,7 +105,7 @@
     @decorators.idempotent_id('f2e32896-bf66-4f4e-89cf-e7fba0ef1f38')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:list_trusts")
+        rules=["identity:list_trusts"])
     def test_list_trusts(self):
         with self.rbac_utils.override_role(self):
             self.trusts_client.list_trusts(
@@ -114,7 +114,7 @@
     @decorators.idempotent_id('3c9ff92f-a73e-4f9b-8865-e017f38c70f5')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:list_roles_for_trust")
+        rules=["identity:list_roles_for_trust"])
     def test_list_roles_for_trust(self):
         with self.rbac_utils.override_role(self):
             self.trusts_client.list_trust_roles(self.trust['id'])
@@ -122,7 +122,7 @@
     @decorators.idempotent_id('3bb4f97b-cecd-4c7d-ad10-b88ee6c5d573')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:get_role_for_trust")
+        rules=["identity:get_role_for_trust"])
     def test_show_trust_role(self):
         with self.rbac_utils.override_role(self):
             self.trusts_client.show_trust_role(
@@ -131,7 +131,7 @@
     @decorators.idempotent_id('0184e0fb-641e-4b52-ab73-81c1ce6ca5c1')
     @rbac_rule_validation.action(
         service="keystone",
-        rule="identity:get_trust")
+        rules=["identity:get_trust"])
     def test_show_trust(self):
         with self.rbac_utils.override_role(self):
             self.trusts_client.show_trust(self.trust['id'])
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py
index bd97535..c633bb8 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_users_rbac.py
@@ -28,14 +28,14 @@
         cls.default_user_id = cls.os_primary.credentials.user_id
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_user")
+                                 rules=["identity:create_user"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d904')
     def test_create_user(self):
         with self.rbac_utils.override_role(self):
             self.setup_test_user()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_user")
+                                 rules=["identity:update_user"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d905')
     def test_update_user(self):
         user = self.setup_test_user()
@@ -48,7 +48,7 @@
                                           email=new_email)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_user")
+                                 rules=["identity:delete_user"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d906')
     def test_delete_user(self):
         user = self.setup_test_user()
@@ -57,28 +57,28 @@
             self.users_client.delete_user(user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_users")
+                                 rules=["identity:list_users"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d907')
     def test_list_users(self):
         with self.rbac_utils.override_role(self):
             self.users_client.list_users()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_user")
+                                 rules=["identity:get_user"])
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d908')
     def test_show_own_user(self):
         with self.rbac_utils.override_role(self):
             self.users_client.show_user(self.default_user_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_groups_for_user")
+                                 rules=["identity:list_groups_for_user"])
     @decorators.idempotent_id('bd5946d4-46d2-423d-a800-a3e7aabc18b3')
     def test_list_own_user_group(self):
         with self.rbac_utils.override_role(self):
             self.users_client.list_user_groups(self.default_user_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_user_projects")
+                                 rules=["identity:list_user_projects"])
     @decorators.idempotent_id('0f148510-63bf-11e6-1564-080044d0d909')
     def test_list_own_user_projects(self):
         with self.rbac_utils.override_role(self):
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_image_namespace_objects_rbac.py b/patrole_tempest_plugin/tests/api/image/test_image_namespace_objects_rbac.py
index 3ad5c74..24e5806 100644
--- a/patrole_tempest_plugin/tests/api/image/test_image_namespace_objects_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_image_namespace_objects_rbac.py
@@ -24,7 +24,7 @@
 class ImageNamespacesObjectsRbacTest(rbac_base.BaseV2ImageRbacTest):
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_metadef_object")
+                                 rules=["add_metadef_object"])
     @decorators.idempotent_id("772156f2-e33d-432e-8521-12385746c2f0")
     def test_create_metadef_object_in_namespace(self):
         """Create Metadef Object Namespace Test
@@ -45,7 +45,7 @@
                         namespace['namespace'], object_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_objects")
+                                 rules=["get_metadef_objects"])
     @decorators.idempotent_id("48b50ecb-237d-4909-be62-b6a05c47b64d")
     def test_list_metadef_objects_in_namespace(self):
         """List Metadef Object Namespace Test
@@ -59,7 +59,7 @@
                 namespace['namespace'])
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_metadef_object")
+                                 rules=["modify_metadef_object"])
     @decorators.idempotent_id("cd130b1d-89fa-479c-a90e-498d895fb455")
     def test_update_metadef_object_in_namespace(self):
         """Update Metadef Object Namespace Test
@@ -83,7 +83,7 @@
                 namespace['namespace'], object_name, name=new_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_object")
+                                 rules=["get_metadef_object"])
     @decorators.idempotent_id("93c61420-5b80-4a0e-b6f3-4ccc6e90b865")
     def test_show_metadef_object_in_namespace(self):
         """Show Metadef Object Namespace Test
diff --git a/patrole_tempest_plugin/tests/api/image/test_image_namespace_property_rbac.py b/patrole_tempest_plugin/tests/api/image/test_image_namespace_property_rbac.py
index 75cf66d..1059450 100644
--- a/patrole_tempest_plugin/tests/api/image/test_image_namespace_property_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_image_namespace_property_rbac.py
@@ -29,7 +29,7 @@
         cls.resource_name = body['resource_types'][0]['name']
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_metadef_property")
+                                 rules=["add_metadef_property"])
     @decorators.idempotent_id('383555ca-677b-43e9-b809-acc2b5a0176c')
     def test_add_md_properties(self):
         """Create Image Metadef Namespace Property Test
@@ -45,7 +45,7 @@
                 title=property_name, name=self.resource_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_properties")
+                                 rules=["get_metadef_properties"])
     @decorators.idempotent_id('d5177611-c2b5-4000-bd9c-1987af9222ea')
     def test_get_md_properties(self):
         """List Image Metadef Namespace Properties Test
@@ -58,7 +58,7 @@
                 namespace=namespace['namespace'])
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_property")
+                                 rules=["get_metadef_property"])
     @decorators.idempotent_id('cfeda2af-bcab-433e-80c7-4b40c774aed5')
     def test_get_md_property(self):
         """Get Image Metadef Namespace Property Test
@@ -77,7 +77,7 @@
                 namespace['namespace'], self.resource_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_metadef_property")
+                                 rules=["modify_metadef_property"])
     @decorators.idempotent_id('fdaf9363-4010-4f2f-8192-1b28f6b22e69')
     def test_modify_md_properties(self):
         """Modify Image Metadef Namespace Policy Test
diff --git a/patrole_tempest_plugin/tests/api/image/test_image_namespace_rbac.py b/patrole_tempest_plugin/tests/api/image/test_image_namespace_rbac.py
index 204263a..aade60f 100644
--- a/patrole_tempest_plugin/tests/api/image/test_image_namespace_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_image_namespace_rbac.py
@@ -24,7 +24,7 @@
 class ImageNamespacesRbacTest(rbac_base.BaseV2ImageRbacTest):
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_metadef_namespace")
+                                 rules=["add_metadef_namespace"])
     @decorators.idempotent_id('e0730ead-b824-4ffc-b774-9469df0e4da6')
     def test_create_metadef_namespace(self):
         """Create Image Metadef Namespace Test
@@ -43,7 +43,7 @@
             namespace_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_namespaces")
+                                 rules=["get_metadef_namespaces"])
     @decorators.idempotent_id('f0b12538-9047-489e-98a5-2d78f48ce789')
     def test_list_metadef_namespaces(self):
         """List Image Metadef Namespace Test
@@ -54,7 +54,7 @@
             self.namespaces_client.list_namespaces()
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_metadef_namespace")
+                                 rules=["modify_metadef_namespace"])
     @decorators.idempotent_id('72c14a7e-927d-4f1a-9e1f-25475552922b')
     def test_modify_metadef_namespace(self):
         """Modify Image Metadef Namespace Test
diff --git a/patrole_tempest_plugin/tests/api/image/test_image_namespace_tags_rbac.py b/patrole_tempest_plugin/tests/api/image/test_image_namespace_tags_rbac.py
index 1a85b74..bc387ee 100644
--- a/patrole_tempest_plugin/tests/api/image/test_image_namespace_tags_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_image_namespace_tags_rbac.py
@@ -67,14 +67,14 @@
 
     @decorators.idempotent_id('50bedccb-9d0b-4138-8d95-31a89250edf6')
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_metadef_tag")
+                                 rules=["add_metadef_tag"])
     def test_create_namespace_tag(self):
         with self.rbac_utils.override_role(self):
             self._create_namespace_tag()
 
     @decorators.idempotent_id('4acf70cc-05da-4b1e-87b2-d5e4475164e7')
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_tag")
+                                 rules=["get_metadef_tag"])
     def test_show_namespace_tag(self):
         tag_name = self._create_namespace_tag()
         with self.rbac_utils.override_role(self):
@@ -83,7 +83,7 @@
 
     @decorators.idempotent_id('01593828-3edb-461e-8abc-8fdeb3927e37')
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_metadef_tag")
+                                 rules=["modify_metadef_tag"])
     def test_update_namespace_tag(self):
         tag_name = self._create_namespace_tag()
         updated_tag_name = data_utils.rand_name(
@@ -95,14 +95,14 @@
 
     @decorators.idempotent_id('20ffaf76-ebdc-4267-a1ad-194346f5cc91')
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_metadef_tags")
+                                 rules=["add_metadef_tags"])
     def test_create_namespace_tags(self):
         with self.rbac_utils.override_role(self):
             self._create_namespace_tag(multiple=True)
 
     @decorators.idempotent_id('d37c1501-e787-449d-89b3-754a942a459a')
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_tags")
+                                 rules=["get_metadef_tags"])
     def test_list_namespace_tags(self):
         with self.rbac_utils.override_role(self):
             self.namespace_tags_client.list_namespace_tags(self.namespace)
diff --git a/patrole_tempest_plugin/tests/api/image/test_image_resource_types_rbac.py b/patrole_tempest_plugin/tests/api/image/test_image_resource_types_rbac.py
index 7b03158..94f9cd4 100644
--- a/patrole_tempest_plugin/tests/api/image/test_image_resource_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_image_resource_types_rbac.py
@@ -36,7 +36,7 @@
             cls.namespaces_client.delete_namespace, cls.namespace_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="list_metadef_resource_types")
+                                 rules=["list_metadef_resource_types"])
     @decorators.idempotent_id('0416fc4d-cfdc-447b-88b6-d9f1dd0382f7')
     def test_list_metadef_resource_types(self):
         """List Metadef Resource Type Image Test
@@ -47,7 +47,7 @@
             self.resource_types_client.list_resource_types()
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_metadef_resource_type")
+                                 rules=["get_metadef_resource_type"])
     @decorators.idempotent_id('3698d53c-71ae-4803-a2c3-c272c054f25c')
     def test_get_metadef_resource_type(self):
         """Get Metadef Resource Type Image Test
@@ -58,8 +58,9 @@
             self.resource_types_client.list_resource_type_association(
                 self.namespace_name)
 
-    @rbac_rule_validation.action(service="glance",
-                                 rule="add_metadef_resource_type_association")
+    @rbac_rule_validation.action(
+        service="glance",
+        rules=["add_metadef_resource_type_association"])
     @decorators.idempotent_id('ef9fbc60-3e28-4164-a25c-d30d892f7939')
     def test_add_metadef_resource_type(self):
         type_name = data_utils.rand_name()
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..656703a 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
@@ -36,7 +36,7 @@
         cls.image_member_client = cls.os_primary.image_member_client_v2
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_member")
+                                 rules=["add_member"])
     @decorators.idempotent_id('b1b85ace-6484-11e6-881e-080027d0d606')
     def test_add_image_member(self):
 
@@ -51,7 +51,7 @@
                 image_id, member=self.alt_tenant_id)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="delete_member")
+                                 rules=["delete_member"])
     @decorators.idempotent_id('ba075234-6484-11e6-881e-080027d0d606')
     def test_delete_image_member(self):
 
@@ -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):
 
@@ -88,7 +88,7 @@
                                                        self.alt_tenant_id)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_member")
+                                 rules=["modify_member"])
     @decorators.idempotent_id('ca448bb2-6484-11e6-881e-080027d0d606')
     def test_update_image_member(self):
 
@@ -110,7 +110,7 @@
                 status='pending')
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_members")
+                                 rules=["get_members"])
     @decorators.idempotent_id('d0a2dc20-6484-11e6-881e-080027d0d606')
     def test_list_image_members(self):
 
diff --git a/patrole_tempest_plugin/tests/api/image/test_images_rbac.py b/patrole_tempest_plugin/tests/api/image/test_images_rbac.py
index e97e803..aa96112 100644
--- a/patrole_tempest_plugin/tests/api/image/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_images_rbac.py
@@ -42,7 +42,7 @@
         return self.image_client.store_image_file(image_id, image_file)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="add_image")
+                                 rules=["add_image"])
     @decorators.idempotent_id('0f148510-63bf-11e6-b348-080027d0d606')
     def test_create_image(self):
 
@@ -54,7 +54,7 @@
             self._create_image()
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="upload_image")
+                                 rules=["upload_image"])
     @decorators.idempotent_id('fdc0c7e2-ad58-4c5a-ba9d-1f6046a5b656')
     def test_upload_image(self):
 
@@ -69,7 +69,7 @@
 
     @decorators.idempotent_id('f0c268f3-cb51-49aa-9bd5-d30cf647322f')
     @rbac_rule_validation.action(service="glance",
-                                 rule="download_image")
+                                 rules=["download_image"])
     def test_download_image(self):
 
         """Download Image Test
@@ -83,7 +83,7 @@
             self.image_client.show_image_file(image['id'])
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="delete_image")
+                                 rules=["delete_image"])
     @decorators.idempotent_id('3b5c341e-645b-11e6-ac4f-080027d0d606')
     def test_delete_image(self):
 
@@ -98,7 +98,7 @@
         self.image_client.wait_for_resource_deletion(image['id'])
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_image")
+                                 rules=["get_image"])
     @decorators.idempotent_id('3085c7c6-645b-11e6-ac4f-080027d0d606')
     def test_show_image(self):
 
@@ -112,7 +112,7 @@
             self.image_client.show_image(image['id'])
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="get_images")
+                                 rules=["get_images"])
     @decorators.idempotent_id('bf1a4e94-645b-11e6-ac4f-080027d0d606')
     def test_list_images(self):
 
@@ -124,7 +124,7 @@
             self.image_client.list_images()['images']
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_image")
+                                 rules=["modify_image"])
     @decorators.idempotent_id('32ecf48c-645e-11e6-ac4f-080027d0d606')
     def test_update_image(self):
 
@@ -142,7 +142,7 @@
 
     @decorators.idempotent_id('244050d9-1b9a-446a-b3c5-f26f3ba8eb75')
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_image")
+                                 rules=["modify_image"])
     def test_create_image_tag(self):
 
         """Create image tag
@@ -158,7 +158,7 @@
 
     @decorators.idempotent_id('c4a0bf9c-b78b-48c6-a31f-72c95f943c6e')
     @rbac_rule_validation.action(service="glance",
-                                 rule="modify_image")
+                                 rules=["modify_image"])
     def test_delete_image_tag(self):
 
         """Delete image tag
@@ -173,7 +173,7 @@
             self.image_client.delete_image_tag(image['id'], tag_name)
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="publicize_image")
+                                 rules=["publicize_image"])
     @decorators.idempotent_id('0ea4809c-6461-11e6-ac4f-080027d0d606')
     def test_publicize_image(self):
 
@@ -186,7 +186,7 @@
 
     @decorators.idempotent_id('0f2d8427-134a-4d3c-a102-5fcdf5443d09')
     @rbac_rule_validation.action(service="glance",
-                                 rule="communitize_image")
+                                 rules=["communitize_image"])
     def test_communitize_image(self):
 
         """Communitize Image Test
@@ -197,7 +197,7 @@
             self._create_image(visibility='community')
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="deactivate")
+                                 rules=["deactivate"])
     @decorators.idempotent_id('b488458c-65df-11e6-9947-080027824017')
     def test_deactivate_image(self):
 
@@ -212,7 +212,7 @@
             self.image_client.deactivate_image(image['id'])
 
     @rbac_rule_validation.action(service="glance",
-                                 rule="reactivate")
+                                 rules=["reactivate"])
     @decorators.idempotent_id('d3fa28b8-65df-11e6-9947-080027824017')
     def test_reactivate_image(self):
 
diff --git a/patrole_tempest_plugin/tests/api/network/README.rst b/patrole_tempest_plugin/tests/api/network/README.rst
new file mode 100644
index 0000000..352af8a
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/README.rst
@@ -0,0 +1,47 @@
+.. _network-rbac-tests:
+
+Network RBAC Tests
+==================
+
+What are these tests?
+---------------------
+
+These tests are RBAC tests for Neutron and its associated plugins. They are
+broken up into the following categories:
+
+* :ref:`neutron-rbac-tests`
+* :ref:`neutron-extension-rbac-tests`
+
+.. _neutron-rbac-tests:
+
+Neutron tests
+^^^^^^^^^^^^^
+
+Neutron RBAC tests inherit from the base class ``BaseNetworkRbacTest``. They
+test many of the Neutron policies found in the service's `policy.json file`_.
+These tests are gated in many `Zuul jobs`_ (master, n-1, n-2) against many
+roles (member, admin).
+
+.. _neutron-extension-rbac-tests:
+
+Neutron extension tests
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The Neutron RBAC plugin tests focus on testing RBAC for various Neutron
+extensions, or, stated differently: tests that rely on
+`neutron-tempest-plugin`_.
+
+These tests inherit from the base class ``BaseNetworkExtRbacTest``. If an
+extension or plugin is not enabled in the cloud, the corresponding tests are
+gracefully skipped.
+
+.. note::
+
+  Patrole should import as few dependencies from ``neutron_tempest_plugin`` as
+  possible (such as ``neutron_tempest_plugin.api.clients`` for the service
+  clients) because the module is not a `stable interface`_.
+
+.. _policy.json file: https://github.com/openstack/neutron/blob/master/etc/policy.json
+.. _Zuul jobs: https://github.com/openstack/patrole/blob/master/.zuul.yaml
+.. _neutron-tempest-plugin: https://github.com/openstack/neutron-tempest-plugin
+.. _stable interface: https://github.com/openstack/neutron-tempest-plugin/tree/master/neutron_tempest_plugin#warning
diff --git a/patrole_tempest_plugin/tests/api/network/rbac_base.py b/patrole_tempest_plugin/tests/api/network/rbac_base.py
index 3065c13..347651d 100644
--- a/patrole_tempest_plugin/tests/api/network/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/network/rbac_base.py
@@ -14,22 +14,61 @@
 #    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()
+
+
+class BaseNetworkExtRbacTest(BaseNetworkRbacTest):
+    """Base class to be used with tests that require neutron-tempest-plugin.
+    """
+
+    @classmethod
+    def get_auth_providers(cls):
+        """Register auth_provider from neutron-tempest-plugin.
+        """
+        providers = super(BaseNetworkExtRbacTest, cls).get_auth_providers()
+        if cls.is_neutron_tempest_plugin_avaliable():
+            providers.append(cls.ntp_client.auth_provider)
+        return providers
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaseNetworkExtRbacTest, cls).skip_checks()
+
+        if not cls.is_neutron_tempest_plugin_avaliable():
+            msg = ("neutron-tempest-plugin not installed.")
+            raise cls.skipException(msg)
+
+    @classmethod
+    def is_neutron_tempest_plugin_avaliable(cls):
+        try:
+            import neutron_tempest_plugin  # noqa
+            return True
+        except ImportError:
+            return False
+
+    @classmethod
+    def get_client_manager(cls, credential_type=None, roles=None,
+                           force_new=None):
+        manager = super(BaseNetworkExtRbacTest, cls).get_client_manager(
+            credential_type=credential_type,
+            roles=roles,
+            force_new=force_new
+        )
+
+        # Import neutron-tempest-plugin clients
+        if cls.is_neutron_tempest_plugin_avaliable():
+            from neutron_tempest_plugin.api import clients
+            neutron_tempest_manager = clients.Manager(manager.credentials)
+            cls.ntp_client = neutron_tempest_manager.network_client
+
+        return manager
diff --git a/patrole_tempest_plugin/tests/api/network/test_address_scope_rbac.py b/patrole_tempest_plugin/tests/api/network/test_address_scope_rbac.py
new file mode 100644
index 0000000..6cdeccd
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_address_scope_rbac.py
@@ -0,0 +1,139 @@
+# 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 data_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 AddressScopeExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(AddressScopeExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('address-scope', 'network'):
+            msg = "address-scope extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(AddressScopeExtRbacTest, cls).resource_setup()
+        cls.network = cls.create_network()
+
+    def _create_address_scope(self, name=None, **kwargs):
+        name = name or data_utils.rand_name(self.__class__.__name__)
+        address_scope = self.ntp_client.create_address_scope(name=name,
+                                                             ip_version=6,
+                                                             **kwargs)
+        address_scope = address_scope['address_scope']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ntp_client.delete_address_scope,
+                        address_scope['id'])
+        return address_scope
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_address_scope"],
+                                 expected_error_codes=[403])
+    @decorators.idempotent_id('8cb2d6b5-23c2-4648-997b-7a6ae55be3ad')
+    def test_create_address_scope(self):
+
+        """Create Address Scope
+
+        RBAC test for the neutron create_address_scope policy
+        """
+        with self.rbac_utils.override_role(self):
+            self._create_address_scope()
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_address_scope",
+                                        "create_address_scope:shared"],
+                                 expected_error_codes=[403, 403])
+    @decorators.idempotent_id('0c3f55c0-6ebe-4251-afca-62c5cb4632ca')
+    def test_create_address_scope_shared(self):
+
+        """Create Shared Address Scope
+
+        RBAC test for the neutron create_address_scope:shared policy
+        """
+        with self.rbac_utils.override_role(self):
+            self._create_address_scope(shared=True)
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_address_scope"],
+                                 expected_error_codes=[404])
+    @decorators.idempotent_id('a53f741b-46f6-412f-936f-ac920d449da8')
+    def test_get_address_scope(self):
+
+        """Get Address Scope
+
+        RBAC test for the neutron get_address_scope policy
+        """
+        address_scope = self._create_address_scope()
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_address_scope(address_scope['id'])
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_address_scope",
+                                        "update_address_scope"],
+                                 expected_error_codes=[404, 403])
+    @decorators.idempotent_id('3ce4d606-e067-4ef5-840f-96c680226e73')
+    def test_update_address_scope(self):
+
+        """Update Address Scope
+
+        RBAC test for neutron update_address_scope policy
+        """
+        address_scope = self._create_address_scope()
+        name = data_utils.rand_name(self.__class__.__name__)
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_address_scope(address_scope['id'],
+                                                 name=name)
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_address_scope",
+                                        "update_address_scope",
+                                        "update_address_scope:shared"],
+                                 expected_error_codes=[404, 403, 403])
+    @decorators.idempotent_id('77d3a9d2-721a-4d9f-9654-6b52f113df85')
+    def test_update_address_scope_shared(self):
+
+        """Update Shared Address Scope
+
+        RBAC test for neutron update_address_scope:shared policy
+        """
+        address_scope = self._create_address_scope(shared=True)
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_address_scope(address_scope['id'],
+                                                 shared=True)
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_address_scope",
+                                        "delete_address_scope"],
+                                 expected_error_codes=[404, 403])
+    @decorators.idempotent_id('277d8e47-e498-4452-b969-a91f747296ba')
+    def test_delete_address_scope(self):
+
+        """Delete Address Scope
+
+        RBAC test for neutron delete_address_scope policy
+        """
+        address_scope = self._create_address_scope()
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_address_scope(address_scope['id'])
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..c2b23f2 100644
--- a/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 from tempest.common import utils
+from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
@@ -38,8 +39,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.
 
@@ -99,7 +100,7 @@
 
     @decorators.idempotent_id('5d2bbdbc-40a5-43d2-828a-84dc93fcc453')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_l3-routers")
+                                 rules=["get_l3-routers"])
     def test_list_routers_on_l3_agent(self):
         """List routers on L3 agent test.
 
@@ -110,7 +111,7 @@
 
     @decorators.idempotent_id('466b2a10-8747-4c09-855a-bd90a1c86ce7')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_l3-router")
+                                 rules=["create_l3-router"])
     def test_create_router_on_l3_agent(self):
         """Create router on L3 agent test.
 
@@ -126,7 +127,7 @@
 
     @decorators.idempotent_id('8138cfc9-3e48-4a34-adf6-894077aa1be4')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="delete_l3-router")
+                                 rules=["delete_l3-router"])
     def test_delete_router_from_l3_agent(self):
         """Delete router from L3 agent test.
 
@@ -192,7 +193,7 @@
 
     @decorators.idempotent_id('dc84087b-4c2a-4878-8ed0-40370e19da17')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_dhcp-networks")
+                                 rules=["get_dhcp-networks"])
     def test_list_networks_hosted_by_one_dhcp_agent(self):
         """List networks hosted by one DHCP agent test.
 
@@ -204,7 +205,7 @@
 
     @decorators.idempotent_id('14e014ac-f355-46d3-b6d8-98f2c9ec1610')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_dhcp-network")
+                                 rules=["create_dhcp-network"])
     def test_add_dhcp_agent_to_network(self):
         """Add DHCP agent to network test.
 
@@ -220,7 +221,7 @@
 
     @decorators.idempotent_id('937a4302-4b49-407d-9980-5843d7badc38')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="delete_dhcp-network")
+                                 rules=["delete_dhcp-network"])
     def test_delete_network_from_dhcp_agent(self):
         """Delete DHCP agent from network test.
 
@@ -235,3 +236,32 @@
         with self.rbac_utils.override_role(self):
             self.agents_client.delete_network_from_dhcp_agent(
                 self.agent['id'], network_id=network_id)
+
+
+class L3AgentsExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(L3AgentsExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('l3_agent_scheduler', 'network'):
+            msg = "l3_agent_scheduler extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(L3AgentsExtRbacTest, cls).resource_setup()
+        name = data_utils.rand_name(cls.__name__ + '-Router')
+        cls.router = cls.ntp_client.create_router(name)['router']
+
+    @decorators.idempotent_id('5d2bbdbc-40a5-43d2-828a-84dc93bcd321')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_l3-agents"])
+    def test_list_l3_agents_on_router(self):
+        """List L3 agents on router test.
+
+        RBAC test for the neutron get_l3-agents policy
+        """
+        with self.rbac_utils.override_role(self):
+            # NOTE: It is not empty list since it's a special case where
+            # policy.enforce is called from the controller.
+            self.ntp_client.list_l3_agents_hosting_router(self.router['id'])
diff --git a/patrole_tempest_plugin/tests/api/network/test_auto_allocated_topology_rbac.py b/patrole_tempest_plugin/tests/api/network/test_auto_allocated_topology_rbac.py
new file mode 100644
index 0000000..4001255
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_auto_allocated_topology_rbac.py
@@ -0,0 +1,78 @@
+# 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 import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class AutoAllocationTopologyExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(AutoAllocationTopologyExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('auto-allocated-topology',
+                                          'network'):
+            msg = "auto-allocated-topology extension not enabled."
+            raise cls.skipException(msg)
+
+    @decorators.idempotent_id('299CB831-F6B2-49CA-882B-E9A8E36945A2')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_auto_allocated_topology"],
+                                 expected_error_codes=[404])
+    def test_show_auto_allocated_topology(self):
+        """Test show auto_allocated_topology.
+
+        RBAC test for the neutron "get_auto_allocated_topology" policy
+        """
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.get_auto_allocated_topology(
+                tenant_id=self.os_primary.credentials.tenant_id)
+
+    def _ensure_network_not_in_use(cls, network_id):
+        ports = cls.ntp_client.list_ports(network_id=network_id)["ports"]
+
+        # Every subnet within network should have a router interface
+        expected_ports_count = len(
+            cls.ntp_client.show_network(network_id)["network"]["subnets"])
+        # Every network should have a single dhcp interface
+        expected_ports_count += 1
+
+        if len(ports) != expected_ports_count:
+            msg = "Auto Allocated Topology in use."
+            cls.skipException(msg)
+
+    @decorators.idempotent_id('A0606AFE-065E-4C09-8E51-58EE7FBA30A2')
+    @decorators.attr(type='slow')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_auto_allocated_topology",
+                                        "delete_auto_allocated_topology"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_auto_allocated_topology(self):
+        """Test delete auto_allocated_topology.
+
+        RBAC test for the neutron "delete_auto_allocated_topology" policy
+        """
+        tenant_id = self.os_primary.credentials.tenant_id
+        net_id = self.ntp_client.get_auto_allocated_topology(
+            tenant_id=tenant_id)["auto_allocated_topology"]["id"]
+
+        self._ensure_network_not_in_use(net_id)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_auto_allocated_topology(
+                tenant_id=self.os_primary.credentials.tenant_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_dscp_marking_rule_rbac.py b/patrole_tempest_plugin/tests/api/network/test_dscp_marking_rule_rbac.py
new file mode 100644
index 0000000..e03de74
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_dscp_marking_rule_rbac.py
@@ -0,0 +1,106 @@
+# 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 data_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 DscpMarkingRuleExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(DscpMarkingRuleExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('qos', 'network'):
+            msg = "qos extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(DscpMarkingRuleExtRbacTest, cls).resource_setup()
+        name = data_utils.rand_name(cls.__class__.__name__ + '-qos')
+        cls.policy_id = cls.ntp_client.create_qos_policy(
+            name=name)["policy"]["id"]
+        cls.addClassResourceCleanup(
+            cls.ntp_client.delete_qos_policy, cls.policy_id)
+
+    def create_policy_dscp_marking_rule(cls):
+        rule = cls.ntp_client.create_dscp_marking_rule(cls.policy_id, 10)
+        rule_id = rule['dscp_marking_rule']['id']
+        cls.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.ntp_client.delete_dscp_marking_rule, cls.policy_id, rule_id)
+        return rule_id
+
+    @decorators.idempotent_id('2717AB75-E4CF-4CA4-AF04-5BEC0C808AA5')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_policy_dscp_marking_rule"])
+    def test_create_policy_dscp_marking_rule(self):
+        """Create policy_dscp_marking_rule.
+
+        RBAC test for the neutron "create_policy_dscp_marking_rule" policy
+        """
+
+        with self.rbac_utils.override_role(self):
+            self.create_policy_dscp_marking_rule()
+
+    @decorators.idempotent_id('3D68F50E-B948-4B25-8A72-F6F4890BBC6F')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_dscp_marking_rule"],
+                                 expected_error_codes=[404])
+    def test_show_policy_dscp_marking_rule(self):
+        """Show policy_dscp_marking_rule.
+
+        RBAC test for the neutron "get_policy_dscp_marking_rule" policy
+        """
+        rule_id = self.create_policy_dscp_marking_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_dscp_marking_rule(self.policy_id, rule_id)
+
+    @decorators.idempotent_id('33830794-8731-45C3-BC97-17718555DD7C')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_dscp_marking_rule",
+                                        "update_policy_dscp_marking_rule"],
+                                 expected_error_codes=[404, 403])
+    def test_update_policy_dscp_marking_rule(self):
+        """Update policy_dscp_marking_rule.
+
+        RBAC test for the neutron "update_policy_dscp_marking_rule" policy
+        """
+        rule_id = self.create_policy_dscp_marking_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_dscp_marking_rule(
+                self.policy_id, rule_id, dscp_mark=16)
+
+    @decorators.idempotent_id('7BF564DD-3648-4D12-8A8B-6D5E576D1843')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_dscp_marking_rule",
+                                        "delete_policy_dscp_marking_rule"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_policy_dscp_marking_rule(self):
+        """Delete policy_dscp_marking_rule.
+
+        RBAC test for the neutron "delete_policy_dscp_marking_rule" policy
+        """
+        rule_id = self.create_policy_dscp_marking_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_dscp_marking_rule(self.policy_id, rule_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py b/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
new file mode 100644
index 0000000..dea95ba
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
@@ -0,0 +1,189 @@
+# 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 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
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class FlavorsExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(FlavorsExtRbacTest, 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"]
+
+    @decorators.idempotent_id('2632a61b-831e-4da5-82c8-a5f7d448589b')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_flavor"])
+    def test_create_flavor(self):
+        """Create flavor.
+
+        RBAC test for the neutron "create_flavor" policy
+        """
+        with self.rbac_utils.override_role(self):
+            flavor = self.ntp_client.create_flavor(
+                service_type=self.service_type)
+
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_flavor, flavor["flavor"]["id"])
+
+    @decorators.idempotent_id('9c53164c-117d-4b44-a5cb-96f08386513f')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_flavor",
+                                        "update_flavor"],
+                                 expected_error_codes=[404, 403])
+    def test_update_flavor(self):
+        """Update flavor.
+
+        RBAC test for the neutron "update_flavor" policy
+        """
+        flavor = self.ntp_client.create_flavor(service_type=self.service_type)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_flavor, flavor["flavor"]["id"])
+
+        name = data_utils.rand_name(self.__class__.__name__ + '-Flavor')
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_flavor(flavor["flavor"]["id"], name=name)
+
+    @decorators.idempotent_id('1de15f9e-5080-4259-ab41-e230bb312de8')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_flavor",
+                                        "delete_flavor"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_flavor(self):
+        """Delete flavor.
+
+        RBAC test for the neutron "delete_flavor" policy
+        """
+        flavor = self.ntp_client.create_flavor(service_type=self.service_type)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_flavor, flavor["flavor"]["id"])
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_flavor(flavor["flavor"]["id"])
+
+    @decorators.idempotent_id('c2baf35f-e6c1-4833-9114-aadd9b1f6aaa')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_flavor"],
+                                 expected_error_codes=[404])
+    def test_show_flavor(self):
+        """Show flavor.
+
+        RBAC test for the neutron "get_flavor" policy
+        """
+        flavor = self.ntp_client.create_flavor(service_type=self.service_type)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_flavor, flavor["flavor"]["id"])
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_flavor(flavor["flavor"]["id"])
+
+    @decorators.idempotent_id('ab10bd5d-987e-4255-966f-947670ffd0fa')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_flavors"])
+    def test_list_flavors(self):
+        """List flavors.
+
+        RBAC test for the neutron "get_flavors" policy
+        """
+        flavor = self.ntp_client.create_flavor(service_type=self.service_type)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.ntp_client.delete_flavor, flavor["flavor"]["id"])
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.list_flavors()
+
+
+class FlavorsServiceProfileExtRbacTest(base.BaseNetworkExtRbacTest):
+    @classmethod
+    def resource_setup(cls):
+        super(FlavorsServiceProfileExtRbacTest, 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=["get_flavor_service_profile",
+                                        "delete_flavor_service_profile"],
+                                 expected_error_codes=[404, 403])
+    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..336490a 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
@@ -63,7 +63,7 @@
         return floating_ip
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_floatingip")
+                                 rules=["create_floatingip"])
     @decorators.idempotent_id('f8f7474c-b8a5-4174-af84-73097d6ced38')
     def test_create_floating_ip(self):
         """Create floating IP.
@@ -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..bf49053 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
@@ -63,7 +63,7 @@
         return label_rule
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_metering_label_rule")
+                                 rules=["create_metering_label_rule"])
     @decorators.idempotent_id('81e81776-9d41-4d5e-b5c4-59d5c54a31ad')
     def test_create_metering_label_rule(self):
         """Create metering label rule.
@@ -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..ed6e316 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
@@ -47,7 +47,7 @@
         return label
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_metering_label")
+                                 rules=["create_metering_label"])
     @decorators.idempotent_id('e8cfc8b8-c159-48f0-93b3-591625a02f8b')
     def test_create_metering_label(self):
         """Create metering label.
@@ -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..b449970 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.
@@ -119,4 +118,4 @@
             LOG.info("NotFound or Forbidden exception are not thrown when "
                      "role doesn't have access to the endpoint. Instead, "
                      "the response will have an empty network body.")
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
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 5506d90..b39489a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -97,7 +97,7 @@
         return updated_network
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_network")
+                                 rules=["create_network"])
     @decorators.idempotent_id('95b9baab-1ece-4e2b-89c8-8d671d974e54')
     def test_create_network(self):
 
@@ -110,8 +110,25 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_network",
-                                        "create_network:shared"],
-                                 expected_error_codes=[403, 403])
+                                        "create_network:is_default"])
+    @decorators.idempotent_id('28602661-5ac7-407e-b739-e393f619f5e3')
+    def test_create_network_is_default(self):
+
+        """Create Is Default Network Test
+
+        RBAC test for the neutron create_network:is_default policy
+        """
+        try:
+            with self.rbac_utils.override_role(self):
+                self._create_network(is_default=True)
+        except lib_exc.Conflict as exc:
+            # A default network might already exist
+            self.assertIn('A default external network already exists',
+                          str(exc))
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_network",
+                                        "create_network:shared"])
     @decorators.idempotent_id('ccabf2a9-28c8-44b2-80e6-ffd65d43eef2')
     def test_create_network_shared(self):
 
@@ -125,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):
 
@@ -141,8 +157,30 @@
     @rbac_rule_validation.action(
         service="neutron",
         rules=["create_network",
-               "create_network:provider:network_type"],
-        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):
+
+        """Create Network Physical Network Provider Test
+
+        RBAC test for neutron create_network:provider:physical_network policy
+        """
+        try:
+            with self.rbac_utils.override_role(self):
+                self._create_network(provider_physical_network='provider',
+                                     provider_network_type='flat')
+        except lib_exc.BadRequest as exc:
+            # There probably won't be a physical network called 'provider', but
+            # we aren't testing state of the network
+            self.assertIn("Invalid input for operation: physical_network " +
+                          "'provider' unknown for flat provider network.",
+                          str(exc))
+
+    @utils.requires_ext(extension='provider', service='network')
+    @rbac_rule_validation.action(
+        service="neutron",
+        rules=["create_network",
+               "create_network:provider:network_type"])
     @decorators.idempotent_id('3c42f7b8-b80c-44ef-8fa4-69ec4b1836bc')
     def test_create_network_provider_network_type(self):
 
@@ -157,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):
 
@@ -295,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):
 
@@ -326,7 +363,7 @@
                 self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
 
     @utils.requires_ext(extension='provider', service='network')
     @rbac_rule_validation.action(service="neutron",
@@ -347,7 +384,7 @@
                 self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
 
     @utils.requires_ext(extension='provider', service='network')
     @rbac_rule_validation.action(
@@ -369,7 +406,7 @@
                 self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
 
     @utils.requires_ext(extension='provider', service='network')
     @rbac_rule_validation.action(
@@ -391,7 +428,7 @@
                 self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["get_network", "delete_network"],
@@ -410,7 +447,7 @@
     @utils.requires_ext(extension='dhcp_agent_scheduler', service='network')
     @decorators.idempotent_id('b524f19f-fbb4-4d11-a85d-03bfae17bf0e')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_dhcp-agents")
+                                 rules=["get_dhcp-agents"])
     def test_list_dhcp_agents_on_hosting_network(self):
 
         """List DHCP Agents on Hosting Network Test
diff --git a/patrole_tempest_plugin/tests/api/network/test_policy_bandwidth_limit_rule_rbac.py b/patrole_tempest_plugin/tests/api/network/test_policy_bandwidth_limit_rule_rbac.py
new file mode 100644
index 0000000..ab881a7
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_policy_bandwidth_limit_rule_rbac.py
@@ -0,0 +1,109 @@
+# Copyright 2017 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 data_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 PolicyBandwidthLimitRuleExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(PolicyBandwidthLimitRuleExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('qos', 'network'):
+            msg = "qos extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(PolicyBandwidthLimitRuleExtRbacTest, cls).resource_setup()
+        name = data_utils.rand_name(cls.__class__.__name__ + '-qos-policy')
+        cls.policy_id = cls.ntp_client.create_qos_policy(
+            name=name)["policy"]["id"]
+        cls.addClassResourceCleanup(cls.ntp_client.delete_qos_policy,
+                                    cls.policy_id)
+
+    def _create_bandwidth_limit_rule(self):
+        rule = self.ntp_client.create_bandwidth_limit_rule(
+            self.policy_id, max_kbps=1000, max_burst_kbps=1000,
+            direction="egress")
+        rule_id = rule['bandwidth_limit_rule']['id']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ntp_client.delete_bandwidth_limit_rule,
+                        self.policy_id, rule_id)
+        return rule_id
+
+    @decorators.idempotent_id('E0FDCB39-E16D-4AF5-9165-3FEFD116E69D')
+    @rbac_rule_validation.action(
+        service="neutron", rules=["create_policy_bandwidth_limit_rule"])
+    def test_create_policy_bandwidth_limit_rule(self):
+        """Create bandwidth_limit_rule.
+
+        RBAC test for the neutron "create_policy_bandwidth_limit_rule" policy
+        """
+
+        with self.rbac_utils.override_role(self):
+            self._create_bandwidth_limit_rule()
+
+    @decorators.idempotent_id('A092BD50-364F-4F55-B81A-37DAD6E77B95')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_bandwidth_limit_rule"],
+                                 expected_error_codes=[404])
+    def test_show_policy_bandwidth_limit_rule(self):
+        """Show bandwidth_limit_rule.
+
+        RBAC test for the neutron "get_policy_bandwidth_limit_rule" policy
+        """
+        rule_id = self._create_bandwidth_limit_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_bandwidth_limit_rule(self.policy_id, rule_id)
+
+    @decorators.idempotent_id('CAA27599-082B-44B9-AF09-8C9B8E777ED7')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_bandwidth_limit_rule",
+                                        "update_policy_bandwidth_limit_rule"],
+                                 expected_error_codes=[404, 403])
+    def test_update_policy_bandwidth_limit_rule(self):
+        """Update bandwidth_limit_rule.
+
+        RBAC test for the neutron "update_policy_bandwidth_limit_rule" policy
+        """
+        rule_id = self._create_bandwidth_limit_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_bandwidth_limit_rule(
+                self.policy_id, rule_id, max_kbps=2000)
+
+    @decorators.idempotent_id('BF6D9ED7-4B04-423D-857D-455DB0705852')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_bandwidth_limit_rule",
+                                        "delete_policy_bandwidth_limit_rule"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_policy_bandwidth_limit_rule(self):
+        """Delete bandwidth_limit_rule.
+
+        RBAC test for the neutron "delete_policy_bandwidth_limit_rule" policy
+        """
+        rule_id = self._create_bandwidth_limit_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_bandwidth_limit_rule(self.policy_id,
+                                                        rule_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_policy_minimum_bandwidth_rule_rbac.py b/patrole_tempest_plugin/tests/api/network/test_policy_minimum_bandwidth_rule_rbac.py
new file mode 100644
index 0000000..6d108af
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_policy_minimum_bandwidth_rule_rbac.py
@@ -0,0 +1,112 @@
+# Copyright 2017 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 data_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 PolicyMinimumBandwidthRuleExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(PolicyMinimumBandwidthRuleExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('qos', 'network'):
+            msg = "qos extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(PolicyMinimumBandwidthRuleExtRbacTest, cls).resource_setup()
+        name = data_utils.rand_name(cls.__class__.__name__ + '-qos')
+        cls.policy_id = cls.ntp_client.create_qos_policy(
+            name=name)["policy"]["id"]
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.ntp_client.delete_qos_policy,
+                                    cls.policy_id)
+
+    def create_minimum_bandwidth_rule(self):
+        rule = self.ntp_client.create_minimum_bandwidth_rule(
+            self.policy_id, direction="egress", min_kbps=1000)
+        rule_id = rule['minimum_bandwidth_rule']['id']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ntp_client.delete_minimum_bandwidth_rule,
+                        self.policy_id, rule_id)
+        return rule_id
+
+    @decorators.idempotent_id('25B5EF3A-DF2A-4C80-A498-3BE14A321D97')
+    @rbac_rule_validation.action(
+        service="neutron", rules=["create_policy_minimum_bandwidth_rule"])
+    def test_create_policy_minimum_bandwidth_rule(self):
+        """Create policy_minimum_bandwidth_rule.
+
+        RBAC test for the neutron "create_policy_minimum_bandwidth_rule" policy
+        """
+
+        with self.rbac_utils.override_role(self):
+            self.create_minimum_bandwidth_rule()
+
+    @decorators.idempotent_id('01DD902C-47C5-45D2-9A0E-7AF05981DF21')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy_minimum_bandwidth_rule"],
+                                 expected_error_codes=[404])
+    def test_show_policy_minimum_bandwidth_rule(self):
+        """Show policy_minimum_bandwidth_rule.
+
+        RBAC test for the neutron "get_policy_minimum_bandwidth_rule" policy
+        """
+        rule_id = self.create_minimum_bandwidth_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_minimum_bandwidth_rule(
+                self.policy_id, rule_id)
+
+    @decorators.idempotent_id('50AFE69B-455C-413A-BDC6-26B42DC8D55D')
+    @rbac_rule_validation.action(
+        service="neutron",
+        rules=["get_policy_minimum_bandwidth_rule",
+               "update_policy_minimum_bandwidth_rule"],
+        expected_error_codes=[404, 403])
+    def test_update_policy_minimum_bandwidth_rule(self):
+        """Update policy_minimum_bandwidth_rule.
+
+        RBAC test for the neutron "update_policy_minimum_bandwidth_rule" policy
+        """
+        rule_id = self.create_minimum_bandwidth_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_minimum_bandwidth_rule(
+                self.policy_id, rule_id, min_kbps=2000)
+
+    @decorators.idempotent_id('2112E325-C3B2-4071-8A93-B218F275A83B')
+    @rbac_rule_validation.action(
+        service="neutron",
+        rules=["get_policy_minimum_bandwidth_rule",
+               "delete_policy_minimum_bandwidth_rule"],
+        expected_error_codes=[404, 403])
+    def test_delete_policy_minimum_bandwidth_rule(self):
+        """Delete policy_minimum_bandwidth_rule.
+
+        RBAC test for the neutron "delete_policy_minimum_bandwidth_rule" policy
+        """
+        rule_id = self.create_minimum_bandwidth_rule()
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_minimum_bandwidth_rule(
+                self.policy_id, rule_id)
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..dd3537f 100644
--- a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
@@ -59,7 +59,7 @@
         return ip_list
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_port")
+                                 rules=["create_port"])
     @decorators.idempotent_id('0ec8c551-625c-4864-8a52-85baa7c40f22')
     def test_create_port(self):
 
@@ -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):
@@ -190,7 +183,7 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='binding:vif_type')
 
     @utils.requires_ext(extension='binding', service='network')
@@ -210,7 +203,7 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='binding:vif_details')
 
     @utils.requires_ext(extension='binding', service='network')
@@ -233,7 +226,7 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='binding:host_id')
 
     @utils.requires_ext(extension='binding', service='network')
@@ -257,7 +250,7 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='binding:profile')
 
     @rbac_rule_validation.action(service="neutron",
diff --git a/patrole_tempest_plugin/tests/api/network/test_qos_rbac.py b/patrole_tempest_plugin/tests/api/network/test_qos_rbac.py
new file mode 100644
index 0000000..3fcb7e4
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_qos_rbac.py
@@ -0,0 +1,100 @@
+# 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 data_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 QosExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(QosExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('qos', 'network'):
+            msg = "qos extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(QosExtRbacTest, cls).resource_setup()
+        cls.network = cls.create_network()
+
+    def create_policy(self, name=None):
+        name = name or data_utils.rand_name(self.__class__.__name__)
+        policy = self.ntp_client.create_qos_policy(name)['policy']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.ntp_client.delete_qos_policy, policy['id'])
+        return policy
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_policy"],
+                                 expected_error_codes=[403])
+    @decorators.idempotent_id('2ade2e48-7f82-4650-a69c-933d8d594636')
+    def test_create_policy(self):
+
+        """Create Policy Test
+
+        RBAC test for the neutron create_policy policy
+        """
+        with self.rbac_utils.override_role(self):
+            self.create_policy()
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy"],
+                                 expected_error_codes=[404])
+    @decorators.idempotent_id('d004a8de-b226-4eb4-9fdc-8202a7f64c56')
+    def test_get_policy(self):
+
+        """Show Policy Test
+
+        RBAC test for the neutron get_policy policy
+        """
+        policy = self.create_policy()
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_qos_policy(policy['id'])
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy", "update_policy"],
+                                 expected_error_codes=[404, 403])
+    @decorators.idempotent_id('fb74d56f-1dfc-490b-a9e1-454af583eefb')
+    def test_update_policy(self):
+
+        """Update Policy Test
+
+        RBAC test for the neutron update_policy policy
+        """
+        policy = self.create_policy()
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_qos_policy(policy['id'],
+                                              description='updated')
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_policy", "delete_policy"],
+                                 expected_error_codes=[404, 403])
+    @decorators.idempotent_id('ef4c23a6-4095-47a6-958e-1df585f7d8db')
+    def test_delete_policy(self):
+
+        """Delete Policy Test
+
+        RBAC test for the neutron delete_policy policy
+        """
+        policy = self.create_policy()
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_qos_policy(policy['id'])
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..2123eb3
--- /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 RbacPoliciesExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(RbacPoliciesExtRbacTest, 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=tenant_id,
+            object_type="network",
+            object_id=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..399ad47 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -56,7 +56,7 @@
         return unused_ip[0]
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_router")
+                                 rules=["create_router"])
     @decorators.idempotent_id('acc5005c-bdb6-4192-bc9f-ece9035bb488')
     def test_create_router(self):
         """Create Router
@@ -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
@@ -183,7 +179,7 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if 'distributed' not in retrieved_fields:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='distributed')
 
     @decorators.idempotent_id('defc502c-4159-4824-b4d9-3cdcc39015b2')
@@ -205,7 +201,7 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if 'ha' not in retrieved_fields:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='ha')
 
     @rbac_rule_validation.action(service="neutron",
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..e9fa018 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
@@ -70,7 +70,7 @@
         return sec_group_rule
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_security_group")
+                                 rules=["create_security_group"])
     @decorators.idempotent_id('db7003ce-5717-4e5b-afc7-befa35e8c67f')
     def test_create_security_group(self):
 
@@ -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):
 
@@ -116,7 +116,7 @@
                 description="test description")
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_security_group")
+                                 rules=["get_security_group"])
     @decorators.idempotent_id('fbaf8d96-ed3e-49af-b24c-5fb44f05bbb7')
     def test_list_security_groups(self):
 
@@ -126,10 +126,10 @@
 
         # Neutron may return an empty list if access is denied.
         if not security_groups['security_groups']:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_security_group_rule")
+                                 rules=["create_security_group_rule"])
     @decorators.idempotent_id('953d78df-00cd-416f-9cbd-b7cb4ea65772')
     def test_create_security_group_rule(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):
 
@@ -160,7 +160,7 @@
                 sec_group_rule['id'])
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_security_group_rule")
+                                 rules=["get_security_group_rule"])
     @decorators.idempotent_id('05739ab6-fa35-11e6-bc64-92361f002671')
     def test_list_security_group_rules(self):
 
@@ -170,4 +170,4 @@
 
         # Neutron may return an empty list if access is denied.
         if not security_rules['security_group_rules']:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
diff --git a/patrole_tempest_plugin/tests/api/network/test_segments_rbac.py b/patrole_tempest_plugin/tests/api/network/test_segments_rbac.py
new file mode 100644
index 0000000..0b58649
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_segments_rbac.py
@@ -0,0 +1,122 @@
+# 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.
+
+import random
+
+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 SegmentsExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(SegmentsExtRbacTest, cls).skip_checks()
+        if not utils.is_extension_enabled('segment', 'network'):
+            msg = "segment extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(SegmentsExtRbacTest, cls).resource_setup()
+        cls.network = cls.create_network()
+
+    @classmethod
+    def get_free_segmentation_id(cls):
+        # Select unused segmentation_id to prevent usage conflict
+        segments = cls.ntp_client.list_segments()["segments"]
+        segmentation_ids = [s["segmentation_id"] for s in segments]
+
+        # With 2+ concurrency, tests that ran in the same moment may fail due
+        # to usage conflict. To prevent it we select segmentation to start
+        # randomly.
+        segmentation_id = random.randint(1000, 5000)
+        while segmentation_id in segmentation_ids:
+            segmentation_id += 1
+
+        return segmentation_id
+
+    @classmethod
+    def create_segment(cls, network):
+        segmentation_id = cls.get_free_segmentation_id()
+
+        seg = cls.ntp_client.create_segment(
+            network_id=network['id'], network_type="gre",
+            segmentation_id=segmentation_id)
+        cls.addClassResourceCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.ntp_client.delete_segment, seg['segment']['id'])
+
+        return seg
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08126')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_segment"])
+    def test_create_segment(self):
+        """Create segment.
+
+        RBAC test for the neutron "create_segment" policy
+        """
+        with self.rbac_utils.override_role(self):
+            self.create_segment(self.network)
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08127')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_segment"],
+                                 expected_error_codes=[404])
+    def test_show_segment(self):
+        """Show segment.
+
+        RBAC test for the neutron "get_segment" policy
+        """
+        segment = self.create_segment(self.network)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.show_segment(segment['segment']['id'])
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08128')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_segment",
+                                        "update_segment"],
+                                 expected_error_codes=[404, 403])
+    def test_update_segment(self):
+        """Update segment.
+
+        RBAC test for the neutron "update_segment" policy
+        """
+        segment = self.create_segment(self.network)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.update_segment(segment['segment']['id'],
+                                           name="NewName")
+
+    @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08129')
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["get_segment",
+                                        "delete_segment"],
+                                 expected_error_codes=[404, 403])
+    def test_delete_segment(self):
+        """Delete segment.
+
+        RBAC test for the neutron "delete_segment" policy
+        """
+        segment = self.create_segment(self.network)
+
+        with self.rbac_utils.override_role(self):
+            self.ntp_client.delete_segment(segment['segment']['id'])
diff --git a/patrole_tempest_plugin/tests/api/network/test_service_providers_rbac.py b/patrole_tempest_plugin/tests/api/network/test_service_providers_rbac.py
index fd85444..561a72c 100644
--- a/patrole_tempest_plugin/tests/api/network/test_service_providers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_service_providers_rbac.py
@@ -22,7 +22,7 @@
 class ServiceProvidersRbacTest(base.BaseNetworkRbacTest):
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_service_provider")
+                                 rules=["get_service_provider"])
     @decorators.idempotent_id('15f573b7-474a-4b37-8629-7fac86553ce5')
     def test_list_service_providers(self):
         with self.rbac_utils.override_role(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..bc6b923 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
@@ -53,7 +53,7 @@
         return subnetpool
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_subnetpool")
+                                 rules=["create_subnetpool"])
     @decorators.idempotent_id('1b5509fd-2c32-44a8-a786-1b6ca162dbd1')
     def test_create_subnetpool(self):
         """Create subnetpool.
@@ -65,6 +65,31 @@
 
     @rbac_rule_validation.action(service="neutron",
                                  rules=["create_subnetpool",
+                                        "create_subnetpool:is_default"],
+                                 expected_error_codes=[403, 403])
+    @decorators.idempotent_id('1b5509fd-2c32-44a8-a786-1b6ca162dbd2')
+    def test_create_subnetpool_default(self):
+        """Create default subnetpool.
+
+        RBAC test for the neutron create_subnetpool:is_default policy
+        """
+        # Most likely we already have default subnetpools for ipv4 and ipv6,
+        # so we temporary mark them as is_default=False, to let this test pass.
+        def_pools = self.subnetpools_client.list_subnetpools(is_default=True)
+        for default_pool in def_pools["subnetpools"]:
+            self.subnetpools_client.update_subnetpool(default_pool["id"],
+                                                      is_default=False)
+
+            self.addCleanup(self.subnetpools_client.update_subnetpool,
+                            default_pool["id"], is_default=True)
+
+        with self.rbac_utils.override_role(self):
+            # It apparently only enforces the policy for is_default=True.
+            # It does nothing for is_default=False
+            self._create_subnetpool(is_default=True)
+
+    @rbac_rule_validation.action(service="neutron",
+                                 rules=["create_subnetpool",
                                         "create_subnetpool:shared"],
                                  expected_error_codes=[403, 403])
     @decorators.idempotent_id('cf730989-0d47-40bc-b39a-99e7de484723')
@@ -77,8 +102,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 +132,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..8fe157a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
@@ -39,7 +39,7 @@
 
     @decorators.idempotent_id('0481adeb-4301-44d5-851c-35910cc18a6b')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="create_subnet")
+                                 rules=["create_subnet"])
     def test_create_subnet(self):
         """Create subnet.
 
@@ -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.
 
@@ -62,7 +62,7 @@
 
     @decorators.idempotent_id('e2ddc415-5cab-43f4-9b61-166aed65d637')
     @rbac_rule_validation.action(service="neutron",
-                                 rule="get_subnet")
+                                 rules=["get_subnet"])
     def test_list_subnets(self):
         """List subnets.
 
@@ -73,7 +73,7 @@
 
         # Neutron may return an empty list if access is denied.
         if not subnets['subnets']:
-            raise rbac_exceptions.RbacMalformedResponse(empty=True)
+            raise rbac_exceptions.RbacEmptyResponseBody()
 
     @decorators.idempotent_id('f36cd821-dd22-4bd0-b43d-110fc4b553eb')
     @rbac_rule_validation.action(service="neutron",
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..4b2eefd
--- /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 TrunksExtRbacTest(base.BaseNetworkExtRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(TrunksExtRbacTest, 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(TrunksExtRbacTest, 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..1d0a44d 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,18 +29,13 @@
     _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()
-        cls.volume_hosts_client = cls.os_primary.volume_hosts_v2_client
-        cls.volume_types_client = cls.os_primary.volume_types_v2_client
-        cls.groups_client = cls.os_primary.groups_v3_client
-        cls.group_types_client = cls.os_primary.group_types_v3_client
+        cls.volume_hosts_client = cls.os_primary.volume_hosts_client_latest
+        cls.volume_types_client = cls.os_primary.volume_types_client_latest
+        cls.groups_client = cls.os_primary.groups_client_latest
+        cls.group_types_client = cls.os_primary.group_types_client_latest
 
     @classmethod
     def create_volume_type(cls, name=None, **kwargs):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_capabilities_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_capabilities_rbac.py
index 82b89ab..3770f84 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_capabilities_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_capabilities_rbac.py
@@ -32,11 +32,12 @@
     @classmethod
     def setup_clients(cls):
         super(CapabilitiesV3RbacTest, cls).setup_clients()
-        cls.capabilities_client = cls.os_primary.volume_capabilities_v2_client
-        cls.hosts_client = cls.os_primary.volume_hosts_v2_client
+        cls.capabilities_client = \
+            cls.os_primary.volume_capabilities_client_latest
+        cls.hosts_client = cls.os_primary.volume_hosts_client_latest
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:capabilities")
+                                 rules=["volume_extension:capabilities"])
     @decorators.idempotent_id('40928b74-2141-11e7-93ae-92361f002671')
     def test_show_back_end_capabilities(self):
         host = self.hosts_client.list_hosts()['hosts'][0]['host_name']
diff --git a/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
index f10e41b..8443943 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
@@ -13,12 +13,36 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import functools
+
 from tempest.common import utils
+from tempest import config
 from tempest.lib import decorators
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.volume import rbac_base
 
+CONF = config.CONF
+
+
+def _get_volume_type_encryption_policy(action):
+    feature_flag = CONF.policy_feature_enabled.added_cinder_policies_stein
+
+    if feature_flag:
+        return "volume_extension:volume_type_encryption:%s" % action
+
+    return "volume_extension:volume_type_encryption"
+
+
+_CREATE_VOLUME_TYPE_ENCRYPTION = functools.partial(
+    _get_volume_type_encryption_policy, "create")
+_SHOW_VOLUME_TYPE_ENCRYPTION = functools.partial(
+    _get_volume_type_encryption_policy, "get")
+_UPDATE_VOLUME_TYPE_ENCRYPTION = functools.partial(
+    _get_volume_type_encryption_policy, "update")
+_DELETE_VOLUME_TYPE_ENCRYPTION = functools.partial(
+    _get_volume_type_encryption_policy, "delete")
+
 
 class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
 
@@ -32,7 +56,8 @@
     @classmethod
     def setup_clients(cls):
         super(EncryptionTypesV3RbacTest, cls).setup_clients()
-        cls.encryption_types_client = cls.os_primary.encryption_types_v2_client
+        cls.encryption_types_client = \
+            cls.os_primary.encryption_types_client_latest
 
     def _create_volume_type_encryption(self):
         vol_type_id = self.create_volume_type()['id']
@@ -45,7 +70,7 @@
     @decorators.idempotent_id('ffd94ce5-c24b-4b6c-84c9-c5aad8c3010c')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_encryption")
+        rules=[_CREATE_VOLUME_TYPE_ENCRYPTION])
     def test_create_volume_type_encryption(self):
         vol_type_id = self.create_volume_type()['id']
         with self.rbac_utils.override_role(self):
@@ -57,7 +82,7 @@
     @decorators.idempotent_id('6599e72e-acef-4c0d-a9b2-463fca30d1da')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_encryption")
+        rules=[_DELETE_VOLUME_TYPE_ENCRYPTION])
     def test_delete_volume_type_encryption(self):
         vol_type_id = self._create_volume_type_encryption()
         with self.rbac_utils.override_role(self):
@@ -66,7 +91,7 @@
     @decorators.idempotent_id('42da9fec-32fd-4dca-9242-8a53b2fed25a')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_encryption")
+        rules=[_UPDATE_VOLUME_TYPE_ENCRYPTION])
     def test_update_volume_type_encryption(self):
         vol_type_id = self._create_volume_type_encryption()
         with self.rbac_utils.override_role(self):
@@ -77,7 +102,7 @@
     @decorators.idempotent_id('1381a3dc-248f-4282-b231-c9399018c804')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_encryption")
+        rules=[_SHOW_VOLUME_TYPE_ENCRYPTION])
     def test_show_volume_type_encryption(self):
         vol_type_id = self._create_volume_type_encryption()
         with self.rbac_utils.override_role(self):
@@ -86,7 +111,7 @@
     @decorators.idempotent_id('d4ed3cf8-52b2-4fa2-910d-e405361f0881')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_encryption")
+        rules=[_SHOW_VOLUME_TYPE_ENCRYPTION])
     def test_show_encryption_specs_item(self):
         vol_type_id = self._create_volume_type_encryption()
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_group_snapshots_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_group_snapshots_rbac.py
index 73d7bf2..56a0233 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_group_snapshots_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_group_snapshots_rbac.py
@@ -75,7 +75,7 @@
     def setup_clients(cls):
         super(GroupSnaphotsV314RbacTest, cls).setup_clients()
         cls.group_snapshot_client = \
-            cls.os_primary.group_snapshots_v3_client
+            cls.os_primary.group_snapshots_client_latest
 
     def setUp(self):
         super(GroupSnaphotsV314RbacTest, self).setUp()
@@ -89,7 +89,7 @@
     @decorators.idempotent_id('653df0e8-d90a-474a-a5ce-3c2339aff7ba')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:create_group_snapshot"
+        rules=["group:create_group_snapshot"]
     )
     def test_create_group_snapshot(self):
         with self.rbac_utils.override_role(self):
@@ -112,7 +112,7 @@
     @decorators.idempotent_id('8b966844-4421-4f73-940b-9157cb878331')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:get_group_snapshot"
+        rules=["group:get_group_snapshot"]
     )
     def test_show_group_snapshot_rbac(self):
         group_snapshot_name = data_utils.rand_name('group_snapshot')
@@ -125,7 +125,7 @@
     @decorators.idempotent_id('e9de6dae-1efb-47cd-a3a8-d1f4b8f9f3ff')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:get_all_group_snapshots"
+        rules=["group:get_all_group_snapshots"]
     )
     def test_list_group_snapshot_rbac(self):
         with self.rbac_utils.override_role(self):
@@ -134,7 +134,7 @@
     @decorators.idempotent_id('cf2e25ee-ca58-4ad6-b98d-33235c77db7b')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:delete_group_snapshot"
+        rules=["group:delete_group_snapshot"]
         )
     def test_delete_group_snapshot_rbac(self):
         group_snapshot_name = data_utils.rand_name('group_snapshot')
@@ -172,7 +172,7 @@
     def setup_clients(cls):
         super(GroupSnaphotsV319RbacTest, cls).setup_clients()
         cls.group_snapshot_client = \
-            cls.os_primary.group_snapshots_v3_client
+            cls.os_primary.group_snapshots_client_latest
 
     def setUp(self):
         super(GroupSnaphotsV319RbacTest, self).setUp()
@@ -186,7 +186,7 @@
     @decorators.idempotent_id('3f0c842e-0c72-4f5e-a9c2-281070be3e2c')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:reset_group_snapshot_status"
+        rules=["group:reset_group_snapshot_status"]
         )
     def test_reset_group_snapshot_rbac(self):
         group_snapshot_name = data_utils.rand_name('group_snapshot')
diff --git a/patrole_tempest_plugin/tests/api/volume/test_group_type_specs.py b/patrole_tempest_plugin/tests/api/volume/test_group_type_specs.py
index a34a55d..9c41dfc 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_group_type_specs.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_group_type_specs.py
@@ -27,7 +27,7 @@
     @decorators.idempotent_id('b2859734-00ad-4a22-88ee-541698e90d12')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_specs"
+        rules=["group:group_types_specs"]
     )
     def test_group_type_specs_create(self):
         # Create new group type
@@ -47,7 +47,7 @@
     @decorators.idempotent_id('469d0253-aa13-423f-8264-231ac17effbf')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_specs"
+        rules=["group:group_types_specs"]
     )
     def test_group_type_specs_show(self):
         group_type = self.create_group_type()
@@ -65,7 +65,7 @@
     @decorators.idempotent_id('2e706a4e-dec9-46bf-9426-1c5b6f3ce102')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_specs"
+        rules=["group:group_types_specs"]
     )
     def test_group_type_specs_update(self):
         group_type = self.create_group_type()
@@ -81,7 +81,7 @@
     @decorators.idempotent_id('fd5e332b-fb2c-4957-ace9-11d60ddd5472')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_specs"
+        rules=["group:group_types_specs"]
     )
     def test_group_type_specs_list(self):
         group_type = self.create_group_type()
@@ -92,7 +92,7 @@
     @decorators.idempotent_id('d9639a07-e441-4576-baf6-7ec732b16572')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_specs"
+        rules=["group:group_types_specs"]
     )
     def test_group_type_specs_delete(self):
         group_type = self.create_group_type()
diff --git a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
index ecd193b..730e349 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
@@ -63,7 +63,7 @@
     @decorators.idempotent_id('43235328-66ae-424f-bc7f-f709c0ca268c')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:create")
+        rules=["group:create"])
     def test_create_group(self, name=None):
 
         group_name = name or data_utils.rand_name(
@@ -80,7 +80,7 @@
     @decorators.idempotent_id('9dc34a62-ae3e-439e-92b6-9389ea4c2863')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:get")
+        rules=["group:get"])
     def test_show_group(self):
         group = self._create_group(group_type=self.group_type_id,
                                    volume_types=[self.volume_type_id])
@@ -91,7 +91,7 @@
     @decorators.idempotent_id('db43841b-a173-4317-acfc-f83e4e48e4ee')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:get_all")
+        rules=["group:get_all"])
     def test_list_groups(self):
         with self.rbac_utils.override_role(self):
             self.groups_client.list_groups()['groups']
@@ -99,7 +99,7 @@
     @decorators.idempotent_id('5378da93-9c26-4ad4-b039-0555e2b8f668')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:get_all")
+        rules=["group:get_all"])
     def test_list_groups_with_details(self):
         with self.rbac_utils.override_role(self):
             self.groups_client.list_groups(detail=True)['groups']
@@ -107,7 +107,7 @@
     @decorators.idempotent_id('f499fc48-df83-4917-bf8d-783ebf6f080b')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:update")
+        rules=["group:update"])
     def test_update_group(self):
         group = self._create_group(group_type=self.group_type_id,
                                    volume_types=[self.volume_type_id])
@@ -119,7 +119,7 @@
     @decorators.idempotent_id('66fda391-5774-42a9-a018-80b34e57ab76')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:delete")
+        rules=["group:delete"])
     def test_delete_group(self):
 
         group = self._create_group(ignore_notfound=True,
@@ -146,7 +146,7 @@
     @decorators.idempotent_id('b849c1d4-3215-4f9d-b1e6-0aeb4b2b65ac')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:reset_status")
+        rules=["group:reset_status"])
     def test_reset_group_status(self):
         group = self._create_group(ignore_notfound=False,
                                    group_type=self.group_type_id,
@@ -166,7 +166,7 @@
     @decorators.idempotent_id('2820f12c-4681-4c7f-b28d-e6925637dff6')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_manage")
+        rules=["group:group_types_manage"])
     def test_create_group_type(self):
         with self.rbac_utils.override_role(self):
             self.create_group_type(ignore_notfound=True)
@@ -174,7 +174,7 @@
     @decorators.idempotent_id('f77f8156-4fc9-4f02-be15-8930f748e10c')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_manage")
+        rules=["group:group_types_manage"])
     def test_delete_group_type(self):
         group_type = self.create_group_type(ignore_notfound=True)
 
@@ -184,7 +184,7 @@
     @decorators.idempotent_id('67929954-4551-4d22-b15a-27fb6e56b711')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:group_types_manage")
+        rules=["group:group_types_manage"])
     def test_update_group_type(self):
         group_type = self.create_group_type()
         update_params = {
@@ -199,7 +199,7 @@
     @decorators.idempotent_id('a5f88c26-df7c-4f21-a3ae-7a4c2d6212b4')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:access_group_types_specs")
+        rules=["group:access_group_types_specs"])
     def test_create_group_type_group_specs(self):
         # TODO(felipemonteiro): Combine with ``test_create_group_type``
         # once multiple policy testing is supported. This policy is
@@ -208,18 +208,18 @@
             group_type = self.create_group_type(ignore_notfound=True)
 
         if 'group_specs' not in group_type:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='group_specs')
 
     @decorators.idempotent_id('8d9e2831-24c3-47b7-a76a-2e563287f12f')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="group:access_group_types_specs")
+        rules=["group:access_group_types_specs"])
     def test_show_group_type(self):
         group_type = self.create_group_type()
         with self.rbac_utils.override_role(self):
             resp_body = self.group_types_client.show_group_type(
                 group_type['id'])['group_type']
         if 'group_specs' not in resp_body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute='group_specs')
diff --git a/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py
index aec5cb1..2bd0992 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py
@@ -33,7 +33,7 @@
         '"limits_extension:used_limits" must be available in the cloud.')
     @decorators.idempotent_id('dab04510-5b86-4479-a633-6e496ff405af')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="limits_extension:used_limits")
+                                 rules=["limits_extension:used_limits"])
     def test_show_limits(self):
         # It is enough to check whether any of the following keys below
         # are in the response body under ['limits']['absolute'], but no harm
@@ -51,4 +51,5 @@
                 'limits']['absolute']
         for key in expected_keys:
             if key not in absolute_limits:
-                raise rbac_exceptions.RbacMalformedResponse(attribute=key)
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
+                    attribute=key)
diff --git a/patrole_tempest_plugin/tests/api/volume/test_qos_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_qos_rbac.py
index a62bbda..2f144b0 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_qos_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_qos_rbac.py
@@ -27,7 +27,7 @@
     @classmethod
     def setup_clients(cls):
         super(VolumeQOSV3RbacTest, cls).setup_clients()
-        cls.qos_client = cls.os_primary.volume_qos_v2_client
+        cls.qos_client = cls.os_primary.volume_qos_client_latest
 
     def _create_test_qos_specs(self, name=None, consumer=None, **kwargs):
         name = name or data_utils.rand_name(self.__class__.__name__ + '-QoS')
@@ -39,31 +39,32 @@
         return qos_specs
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:create")
+        service="cinder", rules=["volume_extension:qos_specs_manage:create"])
     @decorators.idempotent_id('4f9f45f0-b379-4577-a279-cec3e917cbec')
     def test_create_qos_with_consumer(self):
         with self.rbac_utils.override_role(self):
             self._create_test_qos_specs()
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:delete")
+        service="cinder", rules=["volume_extension:qos_specs_manage:delete"])
     @decorators.idempotent_id('fbc8a77e-6b6d-45ae-bebe-c496eb8f06f7')
     def test_delete_qos_with_consumer(self):
         qos = self._create_test_qos_specs()
         with self.rbac_utils.override_role(self):
             self.qos_client.delete_qos(qos['id'])
 
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:qos_specs_manage:get")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rules=["volume_extension:qos_specs_manage:get"])
     @decorators.idempotent_id('22aff0dd-0343-408d-ae80-e77551956e14')
     def test_show_qos(self):
         qos = self._create_test_qos_specs()
         with self.rbac_utils.override_role(self):
             self.qos_client.show_qos(qos['id'])['qos_specs']
 
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:"
-                                      "qos_specs_manage:get_all")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rules=["volume_extension:qos_specs_manage:get_all"])
     @decorators.idempotent_id('ff1e98f3-d456-40a9-96d4-c7e4a55dcffa')
     def test_get_association_qos(self):
         qos = self._create_test_qos_specs()
@@ -76,14 +77,14 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:qos_specs_manage:get_all")
+        rules=["volume_extension:qos_specs_manage:get_all"])
     @decorators.idempotent_id('546b8bb1-04a4-4387-9506-a538a7f3cd6a')
     def test_list_qos(self):
         with self.rbac_utils.override_role(self):
             self.qos_client.list_qos()['qos_specs']
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:update")
+        service="cinder", rules=["volume_extension:qos_specs_manage:update"])
     @decorators.idempotent_id('89b630b7-c170-47c3-ac80-50ed425c2d98')
     def test_set_qos_key(self):
         qos = self._create_test_qos_specs()
@@ -92,7 +93,7 @@
                 qos['id'], iops_bytes='500')['qos_specs']
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:update")
+        service="cinder", rules=["volume_extension:qos_specs_manage:update"])
     @decorators.idempotent_id('6c50c837-de77-4dae-a2ec-30e05c62969c')
     def test_unset_qos_key(self):
         qos = self._create_test_qos_specs()
@@ -104,7 +105,7 @@
                                         'qos-key-unset', args=['iops_bytes'])
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:update")
+        service="cinder", rules=["volume_extension:qos_specs_manage:update"])
     @decorators.idempotent_id('2047b347-8bbe-405e-bf5a-c75a0d7e3930')
     def test_associate_qos(self):
         qos = self._create_test_qos_specs()
@@ -117,7 +118,7 @@
             self.qos_client.disassociate_qos, qos['id'], vol_type)
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:update")
+        service="cinder", rules=["volume_extension:qos_specs_manage:update"])
     @decorators.idempotent_id('f12aeca1-0c02-4f33-b805-014171e5b2d4')
     def test_disassociate_qos(self):
         qos = self._create_test_qos_specs()
@@ -132,7 +133,7 @@
                                         'disassociate', args=vol_type)
 
     @rbac_rule_validation.action(
-        service="cinder", rule="volume_extension:qos_specs_manage:update")
+        service="cinder", rules=["volume_extension:qos_specs_manage:update"])
     @decorators.idempotent_id('9f6e664d-a5d9-4e71-b122-73a3086be1b9')
     def test_disassociate_all_qos(self):
         qos = self._create_test_qos_specs()
diff --git a/patrole_tempest_plugin/tests/api/volume/test_quota_classes_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_quota_classes_rbac.py
index dace257..dcc67f6 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_quota_classes_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_quota_classes_rbac.py
@@ -39,7 +39,7 @@
 
     @decorators.idempotent_id('1a060def-2b43-4534-97f5-5eadbbe8c726')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quota_classes")
+                                 rules=["volume_extension:quota_classes"])
     def test_show_quota_class_set(self):
         with self.rbac_utils.override_role(self):
             self.quota_classes_client.show_quota_class_set(
@@ -47,7 +47,7 @@
 
     @decorators.idempotent_id('72159478-23a7-4c75-989f-6bac609eca62')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quota_classes")
+                                 rules=["volume_extension:quota_classes"])
     def test_update_quota_class_set(self):
         quota_class_set = self.quota_classes_client.show_quota_class_set(
             self.quota_name)['quota_class_set']
diff --git a/patrole_tempest_plugin/tests/api/volume/test_scheduler_stats_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_scheduler_stats_rbac.py
index a243587..ff12cba 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_scheduler_stats_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_scheduler_stats_rbac.py
@@ -33,11 +33,11 @@
     def setup_clients(cls):
         super(SchedulerStatsV3RbacTest, cls).setup_clients()
         cls.scheduler_stats_client =\
-            cls.os_primary.volume_scheduler_stats_v2_client
+            cls.os_primary.volume_scheduler_stats_client_latest
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="scheduler_extension:scheduler_stats:get_pools")
+        rules=["scheduler_extension:scheduler_stats:get_pools"])
     @decorators.idempotent_id('5f800441-4d30-48ec-9e5b-0d55bc86acbb')
     def test_list_back_end_storage_pools(self):
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_snapshot_manage_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_snapshot_manage_rbac.py
index d238045..1fc4c24 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_snapshot_manage_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_snapshot_manage_rbac.py
@@ -40,7 +40,8 @@
     @classmethod
     def setup_clients(cls):
         super(SnapshotManageRbacTest, cls).setup_clients()
-        cls.snapshot_manage_client = cls.os_primary.snapshot_manage_v2_client
+        cls.snapshot_manage_client = \
+            cls.os_primary.snapshot_manage_client_latest
 
     @classmethod
     def resource_setup(cls):
@@ -51,7 +52,7 @@
     @decorators.idempotent_id('bd7d62f2-e485-4626-87ef-03b7f19ee1d0')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="snapshot_extension:snapshot_manage")
+        rules=["snapshot_extension:snapshot_manage"])
     def test_manage_snapshot_rbac(self):
         name = data_utils.rand_name(self.__class__.__name__ +
                                     '-Managed-Snapshot')
@@ -73,7 +74,7 @@
     @decorators.idempotent_id('4a2e8934-9c0b-434e-8f0b-e18b9aff126f')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="snapshot_extension:snapshot_unmanage")
+        rules=["snapshot_extension:snapshot_unmanage"])
     def test_unmanage_snapshot_rbac(self):
         with self.rbac_utils.override_role(self):
             self.snapshots_client.unmanage_snapshot(self.snapshot['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py
index 65b7526..ed42b2d 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py
@@ -49,7 +49,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:snapshot_admin_actions:reset_status")
+        rules=["volume_extension:snapshot_admin_actions:reset_status"])
     @decorators.idempotent_id('ea430145-34ef-408d-b678-95d5ae5f46eb')
     def test_reset_snapshot_status(self):
         status = 'error'
@@ -61,7 +61,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:snapshot_admin_actions:force_delete")
+        rules=["volume_extension:snapshot_admin_actions:force_delete"])
     @decorators.idempotent_id('a8b0f7d8-4c00-4645-b8d5-33ab4eecc6cb')
     def test_snapshot_force_delete(self):
         temp_snapshot = self.create_snapshot(self.volume['id'])
@@ -73,7 +73,7 @@
     @decorators.idempotent_id('a95eab2a-c441-4609-9235-f7478627da88')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="snapshot_extension:snapshot_actions:update_snapshot_status")
+        rules=["snapshot_extension:snapshot_actions:update_snapshot_status"])
     def test_update_snapshot_status(self):
         status = 'creating'
         self.snapshots_client.reset_snapshot_status(
diff --git a/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py
index 1c5fb2e..1141b7e 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py
@@ -48,7 +48,7 @@
             self.snapshot_id, metadata)['metadata']
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_snapshot_metadata")
+                                 rules=["volume:get_snapshot_metadata"])
     @decorators.idempotent_id('f6912bb1-62e6-483d-bcd0-e98c1641f4c3')
     def test_get_snapshot_metadata(self):
         # Create volume and snapshot metadata
@@ -60,7 +60,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_tenant_attribute")
+        rules=["volume_extension:volume_tenant_attribute"])
     @decorators.idempotent_id('e2c73b00-0c19-4bb7-8c61-d84b1a223ed1')
     def test_get_snapshot_metadata_for_volume_tenant(self):
         # Create volume and snapshot metadata
@@ -72,7 +72,7 @@
 
     @decorators.idempotent_id('7ea597f6-c544-4b10-aab0-ff68f595fb06')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update_snapshot_metadata")
+                                 rules=["volume:update_snapshot_metadata"])
     def test_update_snapshot_metadata(self):
         self._create_test_snapshot_metadata()
         with self.rbac_utils.override_role(self):
@@ -83,7 +83,7 @@
 
     @decorators.idempotent_id('93068d02-0131-4dd3-af16-fc40d7128d93')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_snapshot_metadata")
+                                 rules=["volume:get_snapshot_metadata"])
     def test_show_snapshot_metadata_item(self):
         self._create_test_snapshot_metadata()
         with self.rbac_utils.override_role(self):
@@ -92,7 +92,7 @@
 
     @decorators.idempotent_id('1f8f43e7-da31-4128-bb3c-73fc548650e3')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update_snapshot_metadata")
+                                 rules=["volume:update_snapshot_metadata"])
     def test_update_snapshot_metadata_item(self):
         update_item = {"key3": "value3_update"}
         self._create_test_snapshot_metadata()
@@ -102,7 +102,7 @@
 
     @decorators.idempotent_id('3ec32516-f7cd-4f88-b78a-ddee67492071')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:delete_snapshot_metadata")
+                                 rules=["volume:delete_snapshot_metadata"])
     def test_delete_snapshot_metadata_item(self):
         self._create_test_snapshot_metadata()
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py
index 56ee1e0..11c42b1 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py
@@ -31,7 +31,7 @@
     @classmethod
     def setup_clients(cls):
         super(MessagesV3RbacTest, cls).setup_clients()
-        cls.messages_client = cls.os_primary.volume_v3_messages_client
+        cls.messages_client = cls.os_primary.volume_messages_client_latest
 
     def _create_user_message(self):
         """Trigger a 'no valid host' situation to generate a message."""
@@ -65,7 +65,7 @@
     @decorators.idempotent_id('bf7f31a1-509b-4a7d-a8a8-ad6ce68229c7')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="message:get_all")
+        rules=["message:get_all"])
     def test_list_messages(self):
         with self.rbac_utils.override_role(self):
             self.messages_client.list_messages()['messages']
@@ -73,7 +73,7 @@
     @decorators.idempotent_id('9cc1ad1e-68a2-4407-8b60-ea77909bce08')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="message:get")
+        rules=["message:get"])
     def test_show_message(self):
         message_id = self._create_user_message()
 
@@ -83,7 +83,7 @@
     @decorators.idempotent_id('65ca7fb7-7f2c-443e-b144-ac86973a97be')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="message:delete")
+        rules=["message:delete"])
     def test_delete_message(self):
         message_id = self._create_user_message()
 
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
index a4fc3fd..2a5b9fe 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
@@ -76,7 +76,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_actions:attach")
+        rules=["volume_extension:volume_actions:attach"])
     @decorators.idempotent_id('f97b10e4-2eed-4f8b-8632-71c02cb9fe42')
     def test_attach_volume_to_instance(self):
         server = self._create_server()
@@ -94,7 +94,7 @@
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_actions:detach")
+        rules=["volume_extension:volume_actions:detach"])
     @decorators.idempotent_id('5a042f6a-688b-42e6-a02e-fe5c47b89b07')
     def test_detach_volume_from_instance(self):
         server = self._create_server()
@@ -107,7 +107,7 @@
             self.volumes_client, volume_id, 'available')
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update_readonly_flag")
+                                 rules=["volume:update_readonly_flag"])
     @decorators.idempotent_id('2750717a-f250-4e41-9e09-02624aad6ff8')
     def test_volume_readonly_update(self):
         with self.rbac_utils.override_role(self):
@@ -118,7 +118,7 @@
 
     @decorators.idempotent_id('59b783c0-f4ef-430c-8a90-1bad97d4ec5c')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update")
+                                 rules=["volume:update"])
     def test_volume_set_bootable(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.set_bootable_volume(self.volume['id'],
@@ -132,7 +132,7 @@
     @decorators.idempotent_id('41566922-75a1-4484-99c7-9c8782ee99ac')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_actions:reserve")
+        rules=["volume_extension:volume_actions:reserve"])
     def test_volume_reserve(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.reserve_volume(self.volume['id'])
@@ -145,14 +145,14 @@
     @decorators.idempotent_id('e5fa9564-77d9-4e57-b0c0-3e0ae4d08535')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_actions:unreserve")
+        rules=["volume_extension:volume_actions:unreserve"])
     def test_volume_unreserve(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.unreserve_volume(self.volume['id'])
 
     @decorators.idempotent_id('c015c82f-7010-48cc-bd71-4ef542046f20')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:retype")
+                                 rules=["volume:retype"])
     def test_volume_retype(self):
         vol_type = self.create_volume_type()['name']
         volume = self.create_volume()
@@ -164,7 +164,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_admin_actions:reset_status")
+        rules=["volume_extension:volume_admin_actions:reset_status"])
     @decorators.idempotent_id('4b3dad7d-0e73-4839-8781-796dd3d7af1d')
     def test_volume_reset_status(self):
         volume = self.create_volume()
@@ -175,7 +175,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_admin_actions:force_delete")
+        rules=["volume_extension:volume_admin_actions:force_delete"])
     @decorators.idempotent_id('a312a937-6abf-4b91-a950-747086cbce48')
     def test_volume_force_delete(self):
         volume = self.create_volume()
@@ -189,7 +189,7 @@
     @utils.services('compute')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_admin_actions:force_detach")
+        rules=["volume_extension:volume_admin_actions:force_detach"])
     def test_force_detach_volume_from_instance(self):
         volume = self.create_volume()
         server = self._create_server()
@@ -227,7 +227,7 @@
     @utils.services('image')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_actions:upload_image")
+        rules=["volume_extension:volume_actions:upload_image"])
     @decorators.idempotent_id('b0d0da46-903c-4445-893e-20e680d68b50')
     def test_volume_upload_image(self):
         # TODO(felipemonteiro): The ``upload_volume`` endpoint also enforces
@@ -251,7 +251,7 @@
     @utils.services('image')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_actions:upload_public")
+        rules=["volume_extension:volume_actions:upload_public"])
     @decorators.idempotent_id('578a84dd-a6bd-4f97-a418-4a0c3c272c08')
     def test_volume_upload_public(self):
         # This also enforces "volume_extension:volume_actions:upload_image".
@@ -277,7 +277,7 @@
     max_microversion = 'latest'
 
     @decorators.idempotent_id('a654833d-4811-4acd-93ef-5ac4a34c75bc')
-    @rbac_rule_validation.action(service="cinder", rule="volume:get_all")
+    @rbac_rule_validation.action(service="cinder", rules=["volume:get_all"])
     def test_show_volume_summary(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.show_volume_summary()
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_basic_crud_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_basic_crud_rbac.py
index 61532c6..ac2255a 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_basic_crud_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_basic_crud_rbac.py
@@ -13,12 +13,16 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.common import waiters
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.volume import rbac_base
 
+CONF = config.CONF
+
 
 class VolumesBasicCrudV3RbacTest(rbac_base.BaseVolumeRbacTest):
 
@@ -28,34 +32,66 @@
         cls.volume = cls.create_volume()
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:create")
+                                 rules=["volume:create"])
     @decorators.idempotent_id('426b08ef-6394-4d06-9128-965d5a6c38ef')
     def test_create_volume(self):
+        name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
+        size = CONF.volume.volume_size
+
         with self.rbac_utils.override_role(self):
-            self.create_volume()
+            volume = self.volumes_client.create_volume(name=name, size=size)[
+                'volume']
+        # Use helper in base Tempest volume class which waits for deletion.
+        self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
+
+    @decorators.idempotent_id('a009e6dc-e8bf-4412-99f5-8e45cffcffec')
+    @rbac_rule_validation.action(service="cinder",
+                                 rules=["volume:create_from_image"])
+    def test_create_volume_from_image(self):
+        name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
+        size = CONF.volume.volume_size
+        img_uuid = CONF.compute.image_ref
+
+        with self.rbac_utils.override_role(self):
+            volume = self.volumes_client.create_volume(
+                name=name, size=size, imageRef=img_uuid)['volume']
+        # Use helper in base Tempest volume class which waits for deletion.
+        self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:delete")
+                                 rules=["volume:delete"])
     @decorators.idempotent_id('6de9f9c2-509f-4558-867b-af21c7163be4')
     def test_delete_volume(self):
         volume = self.create_volume()
         with self.rbac_utils.override_role(self):
             self.volumes_client.delete_volume(volume['id'])
+        self.volumes_client.wait_for_resource_deletion(volume['id'])
 
-    @rbac_rule_validation.action(service="cinder", rule="volume:get")
+    @rbac_rule_validation.action(service="cinder", rules=["volume:get"])
     @decorators.idempotent_id('c4c3fdd5-b1b1-49c3-b977-a9f40ee9257a')
-    def test_get_volume(self):
+    def test_show_volume(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.show_volume(self.volume['id'])
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_all")
+                                 rules=["volume:get_all"])
     @decorators.idempotent_id('e3ab7906-b04b-4c45-aa11-1104d302f940')
-    def test_volume_list(self):
+    def test_list_volumes(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.list_volumes()
 
-    @rbac_rule_validation.action(service="cinder", rule="volume:update")
+    @decorators.idempotent_id('9b6d5beb-254f-4f1b-9906-0bdce4042f53')
+    @rbac_rule_validation.action(service="cinder",
+                                 rules=["volume:get_all"])
+    def test_list_volumes_with_details(self):
+        with self.rbac_utils.override_role(self):
+            self.volumes_client.list_volumes(detail=True)
+
+    @rbac_rule_validation.action(service="cinder", rules=["volume:update"])
     @decorators.idempotent_id('b751b889-9a9b-40d8-ae7d-4b0f65e71ac7')
     def test_update_volume(self):
         update_name = data_utils.rand_name(self.__class__.__name__ + 'volume')
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_hosts_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_hosts_rbac.py
index c21c40e..8e2e264 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_hosts_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_hosts_rbac.py
@@ -22,7 +22,7 @@
 class VolumeHostsV3RbacTest(rbac_base.BaseVolumeRbacTest):
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:hosts")
+                                 rules=["volume_extension:hosts"])
     @decorators.idempotent_id('64e837f5-5452-4e26-b934-c721ea7a8644')
     def test_list_hosts(self):
         with self.rbac_utils.override_role(self):
@@ -30,7 +30,7 @@
 
     @decorators.idempotent_id('9ddf321e-788f-4787-b8cc-dfa59e264143')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:hosts")
+                                 rules=["volume_extension:hosts"])
     def test_show_host(self):
         hosts = self.volume_hosts_client.list_hosts()['hosts']
         host_names = [host['host_name'] for host in hosts]
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
index 8a50141..7e0044d 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
@@ -42,14 +42,14 @@
                         self.volume['id'], "key1")
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:create_volume_metadata")
+                                 rules=["volume:create_volume_metadata"])
     @decorators.idempotent_id('232bbb8b-4c29-44dc-9077-b1398c20b738')
     def test_create_volume_metadata(self):
         with self.rbac_utils.override_role(self):
             self._add_metadata(self.volume)
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_volume_metadata")
+                                 rules=["volume:get_volume_metadata"])
     @decorators.idempotent_id('87ea37d9-23ab-47b2-a59c-16fc4d2c6dfa')
     def test_show_volume_metadata(self):
         with self.rbac_utils.override_role(self):
@@ -58,7 +58,7 @@
 
     @decorators.idempotent_id('5c0f4c19-b448-4f51-9224-dad5faddc3bb')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_volume_metadata")
+                                 rules=["volume:get_volume_metadata"])
     def test_show_volume_metadata_item(self):
         self._add_metadata(self.volume)
 
@@ -67,7 +67,7 @@
                 self.volume['id'], "key1")
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:delete_volume_metadata")
+                                 rules=["volume:delete_volume_metadata"])
     @decorators.idempotent_id('7498dfc1-9db2-4423-ad20-e6dcb25d1beb')
     def test_delete_volume_metadata_item(self):
         self._add_metadata(self.volume)
@@ -77,7 +77,7 @@
                                                             "key1")
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update_volume_metadata")
+                                 rules=["volume:update_volume_metadata"])
     @decorators.idempotent_id('8ce2ff80-99ba-49ae-9bb1-7e96729ee5af')
     def test_update_volume_metadata_item(self):
         self._add_metadata(self.volume)
@@ -88,7 +88,7 @@
 
     @decorators.idempotent_id('a231b445-97a5-4657-b05f-245895e88da9')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update_volume_metadata")
+                                 rules=["volume:update_volume_metadata"])
     def test_update_volume_metadata(self):
         self._add_metadata(self.volume)
         updated_metadata = {"key1": "value1"}
@@ -99,7 +99,7 @@
     @decorators.idempotent_id('39e8f82c-f1fc-4905-bf47-177ce2f71bb9')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_image_metadata")
+        rules=["volume_extension:volume_image_metadata"])
     def test_list_volumes_details_image_metadata(self):
         self.volumes_client.update_volume_image_metadata(
             self.volume['id'], image_id=self.image_id)
@@ -111,13 +111,13 @@
                 'volumes']
         expected_attr = 'volume_image_metadata'
         if expected_attr not in resp_body[0]:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute=expected_attr)
 
     @decorators.idempotent_id('53f94d52-0dd5-42cf-a3a4-59b35150b3d5')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_image_metadata")
+        rules=["volume_extension:volume_image_metadata"])
     def test_show_volume_details_image_metadata(self):
         self.volumes_client.update_volume_image_metadata(
             self.volume['id'], image_id=self.image_id)
@@ -129,13 +129,13 @@
                 'volume']
         expected_attr = 'volume_image_metadata'
         if expected_attr not in resp_body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute=expected_attr)
 
     @decorators.idempotent_id('a9d9e825-5ea3-42e6-96f3-7ac4e97b2ed0')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_image_metadata")
+        rules=["volume_extension:volume_image_metadata"])
     def test_update_volume_image_metadata(self):
         with self.rbac_utils.override_role(self):
             self.volumes_client.update_volume_image_metadata(
@@ -146,7 +146,7 @@
     @decorators.idempotent_id('a41c8eed-2051-4a25-b401-df036faacbdc')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_image_metadata")
+        rules=["volume_extension:volume_image_metadata"])
     def test_delete_volume_image_metadata(self):
         self.volumes_client.update_volume_image_metadata(
             self.volume['id'], image_id=self.image_id)
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_quotas_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_quotas_rbac.py
index 32cc48c..f49c2fd 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_quotas_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_quotas_rbac.py
@@ -32,7 +32,7 @@
     @classmethod
     def setup_clients(cls):
         super(VolumeQuotasV3RbacTest, cls).setup_clients()
-        cls.quotas_client = cls.os_primary.volume_quotas_v2_client
+        cls.quotas_client = cls.os_primary.volume_quotas_client_latest
 
     def _restore_default_quota_set(self):
         default_quota_set = self.quotas_client.show_default_quota_set(
@@ -45,21 +45,21 @@
 
     @decorators.idempotent_id('427c9f0c-982e-403d-ae45-c05f4d6322ff')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quotas:show")
+                                 rules=["volume_extension:quotas:show"])
     def test_list_quotas(self):
         with self.rbac_utils.override_role(self):
             self.quotas_client.show_quota_set(self.demo_tenant_id)
 
     @decorators.idempotent_id('e47cf444-2753-4983-be6d-fc0d6523720f')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quotas:show")
+                                 rules=["volume_extension:quotas:show"])
     def test_list_quotas_usage_true(self):
         with self.rbac_utils.override_role(self):
             self.quotas_client.show_quota_set(self.demo_tenant_id,
                                               params={'usage': True})
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quotas:show")
+                                 rules=["volume_extension:quotas:show"])
     @decorators.idempotent_id('b3c7177e-b6b1-4d0f-810a-fc95606964dd')
     def test_list_default_quotas(self):
         with self.rbac_utils.override_role(self):
@@ -67,7 +67,7 @@
                 self.demo_tenant_id)
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quotas:update")
+                                 rules=["volume_extension:quotas:update"])
     @decorators.idempotent_id('60f8f421-1630-4953-b449-b22af32265c7')
     def test_update_quota_set(self):
         self._restore_default_quota_set()
@@ -81,7 +81,7 @@
 
     @decorators.idempotent_id('329bdb88-5132-4810-b1fc-350d181577e3')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:quotas:delete")
+                                 rules=["volume_extension:quotas:delete"])
     def test_delete_quota_set(self):
         self._restore_default_quota_set()
 
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py
index 1711c88..0f4e458 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py
@@ -36,12 +36,12 @@
     @classmethod
     def setup_clients(cls):
         super(VolumeServicesV3RbacTest, cls).setup_clients()
-        cls.services_client = cls.os_primary.volume_services_v2_client
+        cls.services_client = cls.os_primary.volume_services_client_latest
 
     @decorators.idempotent_id('b9134f01-97c0-4abd-9455-fe2f03e3f966')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:services:index")
+        rules=["volume_extension:services:index"])
     def test_list_services(self):
         with self.rbac_utils.override_role(self):
             self.services_client.list_services()['services']
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_transfers_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_transfers_rbac.py
index ad0d031..5e0fd21 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_transfers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_transfers_rbac.py
@@ -26,7 +26,7 @@
     @classmethod
     def setup_clients(cls):
         super(VolumesTransfersV3RbacTest, cls).setup_clients()
-        cls.transfers_client = cls.os_primary.volume_transfers_v2_client
+        cls.transfers_client = cls.os_primary.volume_transfers_client_latest
 
     @classmethod
     def resource_setup(cls):
@@ -49,22 +49,27 @@
         return transfer
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:create_transfer")
+                                 rules=["volume:create_transfer"])
     @decorators.idempotent_id('25413af4-468d-48ff-94ca-4436f8526b3e')
     def test_create_volume_transfer(self):
         with self.rbac_utils.override_role(self):
             self._create_transfer()
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, self.volume['id'], 'awaiting-transfer')
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_transfer")
+                                 rules=["volume:get_transfer"])
     @decorators.idempotent_id('7a0925d3-ed97-4c25-8299-e5cdabe2eb55')
     def test_get_volume_transfer(self):
         transfer = self._create_transfer()
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, self.volume['id'], 'awaiting-transfer')
+
         with self.rbac_utils.override_role(self):
             self.transfers_client.show_volume_transfer(transfer['id'])
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_all_transfers")
+                                 rules=["volume:get_all_transfers"])
     @decorators.idempotent_id('02a06f2b-5040-49e2-b2b7-619a7db59603')
     def test_list_volume_transfers(self):
         with self.rbac_utils.override_role(self):
@@ -72,25 +77,33 @@
 
     @decorators.idempotent_id('e84e45b0-9872-40bf-bf44-971266161a86')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_all_transfers")
+                                 rules=["volume:get_all_transfers"])
     def test_list_volume_transfers_details(self):
         with self.rbac_utils.override_role(self):
             self.transfers_client.list_volume_transfers(detail=True)
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:accept_transfer")
+                                 rules=["volume:accept_transfer"])
     @decorators.idempotent_id('987f2a11-d657-4984-a6c9-28f06c1cd014')
     def test_accept_volume_transfer(self):
         transfer = self._create_transfer()
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, self.volume['id'], 'awaiting-transfer')
+
         with self.rbac_utils.override_role(self):
             self.transfers_client.accept_volume_transfer(
                 transfer['id'], auth_key=transfer['auth_key'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                self.volume['id'], 'available')
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:delete_transfer")
+                                 rules=["volume:delete_transfer"])
     @decorators.idempotent_id('4672187e-7fff-454b-832a-5c8865dda868')
     def test_delete_volume_transfer(self):
         transfer = self._create_transfer()
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, self.volume['id'], 'awaiting-transfer')
+
         with self.rbac_utils.override_role(self):
             self.transfers_client.delete_volume_transfer(transfer['id'])
         waiters.wait_for_volume_resource_status(
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_types_access_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_types_access_rbac.py
index 89dc0a2..3aed54d 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_types_access_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_types_access_rbac.py
@@ -52,7 +52,7 @@
     @decorators.idempotent_id('af70e6ad-e931-419f-9200-8bcc284e4e47')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_access")
+        rules=["volume_extension:volume_type_access"])
     def test_list_type_access(self):
         self._add_type_access()
 
@@ -63,7 +63,7 @@
     @decorators.idempotent_id('b462eeba-45d0-4d6e-945a-a1d27708d367')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_access:addProjectAccess")
+        rules=["volume_extension:volume_type_access:addProjectAccess"])
     def test_add_type_access(self):
         with self.rbac_utils.override_role(self):
             self._add_type_access(ignore_not_found=True)
@@ -71,7 +71,7 @@
     @decorators.idempotent_id('8f848aeb-636a-46f1-aeeb-e2a60e9d2bfe')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_type_access:removeProjectAccess")
+        rules=["volume_extension:volume_type_access:removeProjectAccess"])
     def test_remove_type_access(self):
         self._add_type_access(ignore_not_found=True)
 
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_types_extra_specs_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_types_extra_specs_rbac.py
index 8d4c265..b610dde 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_types_extra_specs_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_types_extra_specs_rbac.py
@@ -55,7 +55,7 @@
     @decorators.idempotent_id('76c36be2-2b6c-4acf-9aac-c9dc5c17cdbe')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_extra_specs:index")
+        rules=["volume_extension:types_extra_specs:index"])
     def test_list_volume_types_extra_specs(self):
         with self.rbac_utils.override_role(self):
             self.volume_types_client.list_volume_types_extra_specs(
@@ -63,7 +63,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_extra_specs:create")
+        rules=["volume_extension:types_extra_specs:create"])
     @decorators.idempotent_id('eea40251-990b-49b0-99ae-10e4585b479b')
     def test_create_volume_type_extra_specs(self):
         with self.rbac_utils.override_role(self):
@@ -72,7 +72,7 @@
     @decorators.idempotent_id('e2dcc9c6-2fef-431d-afaf-92b45bc76d1a')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_extra_specs:show")
+        rules=["volume_extension:types_extra_specs:show"])
     def test_show_volume_type_extra_specs(self):
         self._create_volume_type_extra_specs()
 
@@ -83,7 +83,7 @@
     @decorators.idempotent_id('93001912-f938-41c7-8787-62dc7010fd52')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_extra_specs:delete")
+        rules=["volume_extension:types_extra_specs:delete"])
     def test_delete_volume_type_extra_specs(self):
         self._create_volume_type_extra_specs(ignore_not_found=True)
 
@@ -94,7 +94,7 @@
     @decorators.idempotent_id('0a444437-7402-4fbe-a18a-93af2ee00618')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_extra_specs:update")
+        rules=["volume_extension:types_extra_specs:update"])
     def test_update_volume_type_extra_specs(self):
         self._create_volume_type_extra_specs()
         update_extra_specs = {self.spec_key: "val2"}
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_types_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_types_rbac.py
index a37661e..a5bec1f 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_types_rbac.py
@@ -24,7 +24,7 @@
     @decorators.idempotent_id('e2bbf968-d947-4a15-a4da-a98c3069731e')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_manage")
+        rules=["volume_extension:types_manage"])
     def test_create_volume_type(self):
         with self.rbac_utils.override_role(self):
             self.create_volume_type()
@@ -32,7 +32,7 @@
     @decorators.idempotent_id('2b74ac82-e03e-4801-86f3-d05c9acfd66b')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_manage")
+        rules=["volume_extension:types_manage"])
     def test_update_volume_type(self):
         volume_type = self.create_volume_type()
         with self.rbac_utils.override_role(self):
@@ -42,7 +42,7 @@
     @decorators.idempotent_id('90aec0ef-4f9b-4170-be6b-a392c12540be')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:types_manage")
+        rules=["volume_extension:types_manage"])
     def test_delete_volume_type(self):
         volume_type = self.create_volume_type()
         with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
index 5aff7a9..0efeb33 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
@@ -57,7 +57,7 @@
 
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:create")
+                                 rules=["backup:create"])
     @decorators.idempotent_id('6887ec94-0bcf-4ab7-b30f-3808a4b5a2a5')
     def test_create_backup(self):
         backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
@@ -73,14 +73,14 @@
 
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:get")
+                                 rules=["backup:get"])
     @decorators.idempotent_id('abd92bdd-b0fb-4dc4-9cfc-de9e968f8c8a')
     def test_show_backup(self):
         with self.rbac_utils.override_role(self):
             self.backups_client.show_backup(self.backup['id'])
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:get_all")
+                                 rules=["backup:get_all"])
     @decorators.idempotent_id('4d18f0f0-7e01-4007-b622-dedc859b22f6')
     def test_list_backups(self):
         with self.rbac_utils.override_role(self):
@@ -88,7 +88,7 @@
 
     @decorators.idempotent_id('dbd69865-876f-4835-b70e-7341153fb162')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:get_all")
+                                 rules=["backup:get_all"])
     def test_list_backups_with_details(self):
         with self.rbac_utils.override_role(self):
             self.backups_client.list_backups(detail=True)
@@ -97,7 +97,7 @@
     @decorators.idempotent_id('50f43bde-205e-438e-9a05-5eac07fc3d63')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:backup_admin_actions:reset_status")
+        rules=["volume_extension:backup_admin_actions:reset_status"])
     def test_reset_backup_status(self):
         # Use instance-level create_backup for easier debugging.
         backup = self.create_backup(volume_id=self.volume['id'])
@@ -112,7 +112,7 @@
 
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:restore")
+                                 rules=["backup:restore"])
     @decorators.idempotent_id('9c794bf9-2446-4f41-8fe0-80b71e757f9d')
     def test_restore_backup(self):
         with self.rbac_utils.override_role(self):
@@ -125,7 +125,7 @@
 
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:delete")
+                                 rules=["backup:delete"])
     @decorators.idempotent_id('d5d0c6a2-413d-437e-a73f-4bf2b41a20ed')
     def test_delete_backup(self):
         # Do not call the create_backup in Tempest's base volume class, because
@@ -147,7 +147,7 @@
 
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:export-import")
+                                 rules=["backup:export-import"])
     @decorators.idempotent_id('e984ec8d-e8eb-485c-98bc-f1856020303c')
     def test_export_backup(self):
         with self.rbac_utils.override_role(self):
@@ -156,7 +156,7 @@
 
     @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:backup-import")
+                                 rules=["backup:backup-import"])
     @decorators.idempotent_id('1e70f039-4556-44cc-9cc1-edf2b7ed648b')
     def test_import_backup(self):
         export_backup = self.backups_client.export_backup(
@@ -176,6 +176,11 @@
 
 
 class VolumesBackupsV318RbacTest(rbac_base.BaseVolumeRbacTest):
+    """Validates that the ``GET /backups/{backup_id}`` and
+    ``GET /backups/details`` APIs inject the expected attribute
+    'os-backup-project-attr:project_id' into the response body following
+    successful authorization.
+    """
     _api_version = 3
     # The minimum microversion for showing 'os-backup-project-attr:project_id'
     # is 3.18.
@@ -197,7 +202,7 @@
 
     @decorators.idempotent_id('69801485-d5be-4e75-bbb4-168d50b5a8c2')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="backup:backup_project_attribute")
+                                 rules=["backup:backup_project_attribute"])
     def test_show_backup_project_attribute(self):
         with self.rbac_utils.override_role(self):
             body = self.backups_client.show_backup(self.backup['id'])['backup']
@@ -205,7 +210,18 @@
         # Show backup API attempts to inject the attribute below into the
         # response body but only if policy enforcement succeeds.
         if self.expected_attr not in body:
-            raise rbac_exceptions.RbacMalformedResponse(
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
+                attribute=self.expected_attr)
+
+    @decorators.idempotent_id('aa40b7c0-5974-48be-8cbc-e23cc61c4c68')
+    @rbac_rule_validation.action(service="cinder",
+                                 rules=["backup:backup_project_attribute"])
+    def test_list_backup_details_project_attribute(self):
+        with self.rbac_utils.override_role(self):
+            body = self.backups_client.list_backups(detail=True)['backups']
+
+        if self.expected_attr not in body[0]:
+            raise rbac_exceptions.RbacMissingAttributeResponseBody(
                 attribute=self.expected_attr)
 
 
@@ -230,7 +246,7 @@
     @decorators.idempotent_id('b45b0e98-6eb8-4c62-aa53-0f8c7c09faa6')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="backup:update")
+        rules=["backup:update"])
     def test_backup_update(self):
         update_kwargs = {
             'name': data_utils.rand_name(self.__class__.__name__ + '-Backup'),
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_extend_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_extend_rbac.py
index 18883c9..ec6cf66 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_extend_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_extend_rbac.py
@@ -28,7 +28,7 @@
         # Create a test shared volume for tests
         cls.volume = cls.create_volume()
 
-    @rbac_rule_validation.action(service="cinder", rule="volume:extend")
+    @rbac_rule_validation.action(service="cinder", rules=["volume:extend"])
     @decorators.idempotent_id('1627b065-4081-4e14-8340-8e4fb02ceaf2')
     def test_volume_extend(self):
         # Extend volume test
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py
index 852d81e..9f21c4a 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py
@@ -41,7 +41,7 @@
     @classmethod
     def setup_clients(cls):
         super(VolumesManageV3RbacTest, cls).setup_clients()
-        cls.volume_manage_client = cls.os_primary.volume_manage_v2_client
+        cls.volume_manage_client = cls.os_primary.volume_manage_client_latest
 
     def _manage_volume(self, org_volume):
         # Manage volume
@@ -66,7 +66,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_manage")
+        rules=["volume_extension:volume_manage"])
     @decorators.idempotent_id('114f9708-939b-407e-aeac-d21ebfabaad3')
     def test_volume_manage(self):
         volume_id = self.create_volume()['id']
@@ -108,7 +108,7 @@
 
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:volume_unmanage")
+        rules=["volume_extension:volume_unmanage"])
     @decorators.idempotent_id('d5d72abe-60bc-45ac-a8f2-c21b24f0b5d6')
     def test_volume_unmanage(self):
         volume_id = self.create_volume()['id']
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py
index 7d721c4..55adf1a 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py
@@ -46,7 +46,7 @@
             detail=with_detail, **params)['snapshots']
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:create_snapshot")
+                                 rules=["volume:create_snapshot"])
     @decorators.idempotent_id('ac7b2ee5-fbc0-4360-afc2-de8fa4881ede')
     def test_create_snapshot(self):
         # Create a temp snapshot
@@ -54,7 +54,7 @@
             self.create_snapshot(self.volume['id'])
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_snapshot")
+                                 rules=["volume:get_snapshot"])
     @decorators.idempotent_id('93a11b40-1ba8-44d6-a196-f8d97220f796')
     def test_show_snapshot(self):
         # Get the snapshot
@@ -65,7 +65,7 @@
     @decorators.idempotent_id('5d6f5f21-9293-4f2a-8f44-cabdc24d92cb')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:extended_snapshot_attributes")
+        rules=["volume_extension:extended_snapshot_attributes"])
     def test_show_snapshot_with_extended_attributes(self):
         """List snapshots with extended attributes."""
         expected_attrs = ('os-extended-snapshot-attributes:project_id',
@@ -76,11 +76,11 @@
                 self.snapshot['id'])['snapshot']
         for expected_attr in expected_attrs:
             if expected_attr not in resp:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     attribute=expected_attr)
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:update_snapshot")
+                                 rules=["volume:update_snapshot"])
     @decorators.idempotent_id('53fe8ee3-3bea-4ae8-a979-3c98ea72f620')
     def test_update_snapshot(self):
         new_desc = 'This is the new description of snapshot.'
@@ -93,7 +93,7 @@
             self.snapshots_client, self.snapshot['id'], 'available')
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:delete_snapshot")
+                                 rules=["volume:delete_snapshot"])
     @decorators.idempotent_id('c7fe54ec-3b70-4772-ba11-f166d95888a3')
     def test_delete_snapshot(self):
         # Create a temp snapshot
@@ -105,7 +105,7 @@
             temp_snapshot['id'])
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_all_snapshots")
+                                 rules=["volume:get_all_snapshots"])
     @decorators.idempotent_id('e4edf0c0-2cd3-420f-b8ab-4d98a0718608')
     def test_list_snapshots(self):
         """List snapshots with params."""
@@ -115,7 +115,7 @@
 
     @decorators.idempotent_id('f3155d8e-45ee-45c9-910d-18c0242229e1')
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_all_snapshots")
+                                 rules=["volume:get_all_snapshots"])
     def test_list_snapshots_details(self):
         """List snapshots details with params."""
         params = {'name': self.snapshot['name']}
@@ -125,7 +125,7 @@
     @decorators.idempotent_id('dd37f388-2731-446d-a78f-676997ebb04a')
     @rbac_rule_validation.action(
         service="cinder",
-        rule="volume_extension:extended_snapshot_attributes")
+        rules=["volume_extension:extended_snapshot_attributes"])
     def test_list_snapshots_details_with_extended_attributes(self):
         """List snapshots details with extended attributes."""
         expected_attrs = ('os-extended-snapshot-attributes:project_id',
@@ -136,5 +136,5 @@
             resp = self._list_by_param_values(with_detail=True, **params)
         for expected_attr in expected_attrs:
             if expected_attr not in resp[0]:
-                raise rbac_exceptions.RbacMalformedResponse(
+                raise rbac_exceptions.RbacMissingAttributeResponseBody(
                     attribute=expected_attr)
diff --git a/patrole_tempest_plugin/tests/unit/fixtures.py b/patrole_tempest_plugin/tests/unit/fixtures.py
index 4e3387e..78e87e5 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
@@ -65,19 +66,23 @@
     def setUp(self):
         super(RbacUtilsFixture, self).setUp()
 
-        self.useFixture(ConfPatcher(rbac_test_role='member', group='patrole'))
+        self.useFixture(ConfPatcher(rbac_test_roles=['member'],
+                                    group='patrole'))
         self.useFixture(ConfPatcher(
             admin_role='admin', auth_version='v3', group='identity'))
+        self.useFixture(ConfPatcher(
+            api_v3=True, group='identity-feature-enabled'))
 
         test_obj_kwargs = {
             'os_primary.credentials.user_id': self.USER_ID,
             'os_primary.credentials.tenant_id': self.PROJECT_ID,
             'os_primary.credentials.project_id': self.PROJECT_ID,
-            'get_identity_version.return_value': 'v3'
         }
         self.mock_test_obj = mock.Mock(
             __name__='patrole_unit_test', spec=test.BaseTestCase,
-            os_primary=mock.Mock(), **test_obj_kwargs)
+            os_primary=mock.Mock(),
+            get_auth_providers=mock.Mock(return_value=[mock.Mock()]),
+            **test_obj_kwargs)
 
         # Mock out functionality that can't be used by unit tests. Mocking out
         # time.sleep is a test optimization.
@@ -88,7 +93,7 @@
         mock_admin_mgr = mock.patch.object(
             clients, 'Manager', spec=clients.Manager,
             roles_v3_client=mock.Mock(), roles_client=mock.Mock()).start()
-        self.roles_v3_client = mock_admin_mgr.return_value.roles_v3_client
+        self.admin_roles_client = mock_admin_mgr.return_value.roles_v3_client
 
         self.set_roles(['admin', 'member'], [])
 
@@ -115,6 +120,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.
 
@@ -138,6 +154,6 @@
                       for role in roles_on_project]
         }
 
-        self.roles_v3_client.list_roles.return_value = available_roles
-        self.roles_v3_client.list_user_roles_on_project.return_value = (
+        self.admin_roles_client.list_roles.return_value = available_roles
+        self.admin_roles_client.list_user_roles_on_project.return_value = (
             available_project_roles)
diff --git a/patrole_tempest_plugin/tests/unit/resources/custom_rbac_policy.yaml b/patrole_tempest_plugin/tests/unit/resources/custom_rbac_policy.yaml
new file mode 100644
index 0000000..444bd2e
--- /dev/null
+++ b/patrole_tempest_plugin/tests/unit/resources/custom_rbac_policy.yaml
@@ -0,0 +1,13 @@
+---
+even_rule: role:two or role:four or role:six or role:eight
+odd_rule: role:one or role:three or role:five or role:seven or role:nine
+zero_rule: role:zero
+prime_rule: role:one or role:two or role:three or role:five or role:seven
+all_rule: ''
+
+policy_action_1: rule:even_rule
+policy_action_2: rule:odd_rule
+policy_action_3: rule:zero_rule
+policy_action_4: rule:prime_rule
+policy_action_5: rule:all_rule
+policy_action_6: role:eight
diff --git a/patrole_tempest_plugin/tests/unit/resources/rbac_roles.yaml b/patrole_tempest_plugin/tests/unit/resources/rbac_roles.yaml
index c5436d0..357e0e6 100644
--- a/patrole_tempest_plugin/tests/unit/resources/rbac_roles.yaml
+++ b/patrole_tempest_plugin/tests/unit/resources/rbac_roles.yaml
@@ -4,3 +4,7 @@
     - _member_
   test:create2:
     - test_member
+  test:create3:
+    - test_member, _member_
+  test:create4:
+    - test_member, !_member_
\ No newline at end of file
diff --git a/patrole_tempest_plugin/tests/unit/test_hacking.py b/patrole_tempest_plugin/tests/unit/test_hacking.py
index 6096c24..a0ace76 100644
--- a/patrole_tempest_plugin/tests/unit/test_hacking.py
+++ b/patrole_tempest_plugin/tests/unit/test_hacking.py
@@ -256,3 +256,53 @@
         self.assertTrue(checks.no_client_alias_in_test_cases(
             "  cls.client",
             "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+
+    def no_extension_rbac_test_suffix_in_plugin_test_class_name(self):
+        check = checks.no_extension_rbac_test_suffix_in_plugin_test_class_name
+
+        # Passing cases: these do not inherit from "ExtRbacTest" base class.
+        self.assertFalse(check(
+            "class FakeRbacTest(BaseFakeRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertFalse(check(
+            "class FakeRbacTest(base.BaseFakeRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+
+        # Passing cases: these **do** end in correct test class suffix.
+        self.assertFalse(check(
+            "class FakeExtRbacTest(BaseFakeExtRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertFalse(check(
+            "class FakeExtRbacTest(base.BaseFakeExtRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+
+        # Passing cases: plugin base class inherits from another base class.
+        self.assertFalse(check(
+            "class BaseFakeExtRbacTest(base.BaseFakeRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertFalse(check(
+            "class BaseFakeExtRbacTest(BaseFakeRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+
+        # Failing cases: these **do not** end in correct test class suffix.
+        # Case 1: RbacTest subclass doesn't end in ExtRbacTest.
+        self.assertTrue(check(
+            "class FakeRbacTest(base.BaseFakeExtRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertTrue(check(
+            "class FakeRbacTest(BaseFakeExtRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertTrue(check(
+            "class FakeRbacTest(BaseFakeNetworkExtRbacTest)",
+            "./patrole_tempest_plugin/tests/api/network/fake_test_rbac.py"))
+        # Case 2: ExtRbacTest subclass doesn't inherit from
+        # BaseExtRbacTest.
+        self.assertTrue(check(
+            "class FakeExtRbacTest(base.BaseFakeRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertTrue(check(
+            "class FakeExtRbacTest(BaseFakeRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
+        self.assertTrue(check(
+            "class FakeNeutronExtRbacTest(BaseFakeNeutronRbacTest)",
+            "./patrole_tempest_plugin/tests/api/fake_test_rbac.py"))
diff --git a/patrole_tempest_plugin/tests/unit/test_policy_authority.py b/patrole_tempest_plugin/tests/unit/test_policy_authority.py
index d396a29..6a4d219 100644
--- a/patrole_tempest_plugin/tests/unit/test_policy_authority.py
+++ b/patrole_tempest_plugin/tests/unit/test_policy_authority.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import json
 import mock
 import os
 
@@ -61,11 +60,14 @@
         self.tenant_policy_file = os.path.join(current_directory,
                                                'resources',
                                                'tenant_rbac_policy.json')
-        self.conf_policy_path = os.path.join(
+        self.conf_policy_path_json = os.path.join(
             current_directory, 'resources', '%s.json')
 
+        self.conf_policy_path_yaml = os.path.join(
+            current_directory, 'resources', '%s.yaml')
+
         self.useFixture(fixtures.ConfPatcher(
-            custom_policy_files=[self.conf_policy_path], group='patrole'))
+            custom_policy_files=[self.conf_policy_path_json], group='patrole'))
         self.useFixture(fixtures.ConfPatcher(
             api_v3=True, api_v2=False, group='identity-feature-enabled'))
 
@@ -74,13 +76,18 @@
             if attr in dir(policy_authority.PolicyAuthority):
                 delattr(policy_authority.PolicyAuthority, attr)
 
-    def _get_fake_policy_rule(self, name, rule):
-        fake_rule = mock.Mock(check=rule, __name__='foo')
-        fake_rule.name = name
-        return fake_rule
+    @staticmethod
+    def _get_fake_policies(rules):
+        fake_rules = []
+        rules = policy_authority.policy.Rules.from_dict(rules)
+        for name, check in rules.items():
+            fake_rule = mock.Mock(check=check, __name__='foo')
+            fake_rule.name = name
+            fake_rules.append(fake_rule)
+        return fake_rules
 
     @mock.patch.object(policy_authority, 'LOG', autospec=True)
-    def test_custom_policy(self, m_log):
+    def _test_custom_policy(self, *args):
         default_roles = ['zero', 'one', 'two', 'three', 'four',
                          'five', 'six', 'seven', 'eight', 'nine']
 
@@ -101,9 +108,80 @@
 
         for rule, role_list in expected.items():
             for role in role_list:
-                self.assertTrue(authority.allowed(rule, role))
+                self.assertTrue(authority.allowed(rule, [role]))
             for role in set(default_roles) - set(role_list):
-                self.assertFalse(authority.allowed(rule, role))
+                self.assertFalse(authority.allowed(rule, [role]))
+
+    @mock.patch.object(policy_authority, 'LOG', autospec=True)
+    def _test_custom_multi_roles_policy(self, *args):
+        default_roles = ['zero', 'one', 'two', 'three', 'four',
+                         'five', 'six', 'seven', 'eight', 'nine']
+
+        test_tenant_id = mock.sentinel.tenant_id
+        test_user_id = mock.sentinel.user_id
+        authority = policy_authority.PolicyAuthority(
+            test_tenant_id, test_user_id, "custom_rbac_policy")
+
+        expected = {
+            'policy_action_1': ['two', 'four', 'six', 'eight'],
+            'policy_action_2': ['one', 'three', 'five', 'seven', 'nine'],
+            'policy_action_4': ['one', 'two', 'three', 'five', 'seven'],
+            'policy_action_5': ['zero', 'one', 'two', 'three', 'four', 'five',
+                                'six', 'seven', 'eight', 'nine'],
+        }
+
+        for rule, role_list in expected.items():
+            allowed_roles_lists = [roles for roles in [
+                role_list[len(role_list) // 2:],
+                role_list[:len(role_list) // 2]] if roles]
+            for test_roles in allowed_roles_lists:
+                self.assertTrue(authority.allowed(rule, test_roles))
+
+            disallowed_roles = list(set(default_roles) - set(role_list))
+            disallowed_roles_lists = [roles for roles in [
+                disallowed_roles[len(disallowed_roles) // 2:],
+                disallowed_roles[:len(disallowed_roles) // 2]] if roles]
+            for test_roles in disallowed_roles_lists:
+                self.assertFalse(authority.allowed(rule, test_roles))
+
+    def test_empty_rbac_test_roles(self):
+        test_tenant_id = mock.sentinel.tenant_id
+        test_user_id = mock.sentinel.user_id
+        authority = policy_authority.PolicyAuthority(
+            test_tenant_id, test_user_id, "custom_rbac_policy")
+
+        disallowed_for_empty_roles = ['policy_action_1', 'policy_action_2',
+                                      'policy_action_3', 'policy_action_4',
+                                      'policy_action_6']
+
+        # Due to "policy_action_5": "rule:all_rule" / "all_rule": ""
+        allowed_for_empty_roles = ['policy_action_5']
+
+        for rule in disallowed_for_empty_roles:
+            self.assertFalse(authority.allowed(rule, []))
+
+        for rule in allowed_for_empty_roles:
+            self.assertTrue(authority.allowed(rule, []))
+
+    def test_custom_policy_json(self):
+        # The CONF.patrole.custom_policy_files has a path to JSON file by
+        # default, so we don't need to use ConfPatcher here.
+        self._test_custom_policy()
+
+    def test_custom_policy_yaml(self):
+        self.useFixture(fixtures.ConfPatcher(
+            custom_policy_files=[self.conf_policy_path_yaml], group='patrole'))
+        self._test_custom_policy()
+
+    def test_custom_multi_roles_policy_json(self):
+        # The CONF.patrole.custom_policy_files has a path to JSON file by
+        # default, so we don't need to use ConfPatcher here.
+        self._test_custom_multi_roles_policy()
+
+    def test_custom_multi_roles_policy_yaml(self):
+        self.useFixture(fixtures.ConfPatcher(
+            custom_policy_files=[self.conf_policy_path_yaml], group='patrole'))
+        self._test_custom_multi_roles_policy()
 
     def test_admin_policy_file_with_admin_role(self):
         test_tenant_id = mock.sentinel.tenant_id
@@ -111,18 +189,18 @@
         authority = policy_authority.PolicyAuthority(
             test_tenant_id, test_user_id, "admin_rbac_policy")
 
-        role = 'admin'
+        roles = ['admin']
         allowed_rules = [
             'admin_rule', 'is_admin_rule', 'alt_admin_rule'
         ]
         disallowed_rules = ['non_admin_rule']
 
         for rule in allowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertTrue(allowed)
 
         for rule in disallowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertFalse(allowed)
 
     def test_admin_policy_file_with_member_role(self):
@@ -131,7 +209,7 @@
         authority = policy_authority.PolicyAuthority(
             test_tenant_id, test_user_id, "admin_rbac_policy")
 
-        role = 'Member'
+        roles = ['Member']
         allowed_rules = [
             'non_admin_rule'
         ]
@@ -139,11 +217,11 @@
             'admin_rule', 'is_admin_rule', 'alt_admin_rule']
 
         for rule in allowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertTrue(allowed)
 
         for rule in disallowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertFalse(allowed)
 
     def test_alt_admin_policy_file_with_context_is_admin(self):
@@ -152,28 +230,28 @@
         authority = policy_authority.PolicyAuthority(
             test_tenant_id, test_user_id, "alt_admin_rbac_policy")
 
-        role = 'fake_admin'
+        roles = ['fake_admin']
         allowed_rules = ['non_admin_rule']
         disallowed_rules = ['admin_rule']
 
         for rule in allowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertTrue(allowed)
 
         for rule in disallowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertFalse(allowed)
 
-        role = 'super_admin'
+        roles = ['super_admin']
         allowed_rules = ['admin_rule']
         disallowed_rules = ['non_admin_rule']
 
         for rule in allowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertTrue(allowed)
 
         for rule in disallowed_rules:
-            allowed = authority.allowed(rule, role)
+            allowed = authority.allowed(rule, roles)
             self.assertFalse(allowed)
 
     def test_tenant_user_policy(self):
@@ -191,17 +269,17 @@
         # Check whether Member role can perform expected actions.
         allowed_rules = ['rule1', 'rule2', 'rule3', 'rule4']
         for rule in allowed_rules:
-            allowed = authority.allowed(rule, 'Member')
+            allowed = authority.allowed(rule, ['Member'])
             self.assertTrue(allowed)
 
         disallowed_rules = ['admin_tenant_rule', 'admin_user_rule']
         for disallowed_rule in disallowed_rules:
-            self.assertFalse(authority.allowed(disallowed_rule, 'Member'))
+            self.assertFalse(authority.allowed(disallowed_rule, ['Member']))
 
         # Check whether admin role can perform expected actions.
         allowed_rules.extend(disallowed_rules)
         for rule in allowed_rules:
-            allowed = authority.allowed(rule, 'admin')
+            allowed = authority.allowed(rule, ['admin'])
             self.assertTrue(allowed)
 
         # Check whether _try_rule is called with the correct target dictionary.
@@ -226,7 +304,7 @@
             }
 
             for rule in allowed_rules:
-                allowed = authority.allowed(rule, 'Member')
+                allowed = authority.allowed(rule, ['Member'])
                 self.assertTrue(allowed)
                 mock_try_rule.assert_called_once_with(
                     rule, expected_target, expected_access_data, mock.ANY)
@@ -270,12 +348,12 @@
 
         fake_rule = 'fake_rule'
         expected_message = (
-            "Policy action \"{0}\" not found in policy file: {1} or among "
-            "registered policy in code defaults for service.").format(
-            fake_rule, self.custom_policy_file)
+            'Policy action "{0}" not found in policy files: {1} or among '
+            'registered policy in code defaults for {2} service.').format(
+            fake_rule, [self.custom_policy_file], "custom_rbac_policy")
 
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
-                              authority.allowed, fake_rule, None)
+                              authority.allowed, fake_rule, [None])
         self.assertIn(expected_message, str(e))
         m_log.debug.assert_called_once_with(expected_message)
 
@@ -292,28 +370,26 @@
                mock.sentinel.error)})
 
         expected_message = (
-            "Policy action \"{0}\" not found in policy file: {1} or among "
-            "registered policy in code defaults for service.").format(
-            mock.sentinel.rule, self.custom_policy_file)
+            'Policy action "[{0}]" not found in policy files: {1} or among '
+            'registered policy in code defaults for {2} service.').format(
+            mock.sentinel.rule, [self.custom_policy_file],
+            "custom_rbac_policy")
 
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
-                              authority.allowed, mock.sentinel.rule, None)
+                              authority.allowed, [mock.sentinel.rule], [None])
         self.assertIn(expected_message, str(e))
         m_log.debug.assert_called_once_with(expected_message)
 
     @mock.patch.object(policy_authority, 'stevedore', autospec=True)
-    def test_get_policy_data_from_file_and_from_code(self, mock_stevedore):
-        fake_policy_rules = [
-            self._get_fake_policy_rule('code_policy_action_1',
-                                       'rule:code_rule_1'),
-            self._get_fake_policy_rule('code_policy_action_2',
-                                       'rule:code_rule_2'),
-            self._get_fake_policy_rule('code_policy_action_3',
-                                       'rule:code_rule_3'),
-        ]
+    def test_get_rules_from_file_and_from_code(self, mock_stevedore):
+        fake_policy_rules = self._get_fake_policies({
+            'code_policy_action_1': 'rule:code_rule_1',
+            'code_policy_action_2': 'rule:code_rule_2',
+            'code_policy_action_3': 'rule:code_rule_3',
+        })
 
         mock_manager = mock.Mock(obj=fake_policy_rules, __name__='foo')
-        mock_manager.configure_mock(name='fake_service')
+        mock_manager.configure_mock(name='tenant_rbac_policy')
         mock_stevedore.named.NamedExtensionManager.return_value = [
             mock_manager
         ]
@@ -323,10 +399,10 @@
         authority = policy_authority.PolicyAuthority(
             test_tenant_id, test_user_id, "tenant_rbac_policy")
 
-        policy_data = authority._get_policy_data('fake_service')
-        self.assertIsInstance(policy_data, str)
+        rules = authority.get_rules()
+        self.assertIsInstance(rules, policy_authority.policy.Rules)
 
-        actual_policy_data = json.loads(policy_data)
+        actual_policy_data = {k: str(v) for k, v in rules.items()}
         expected_policy_data = {
             "code_policy_action_1": "rule:code_rule_1",
             "code_policy_action_2": "rule:code_rule_2",
@@ -335,26 +411,25 @@
             "rule2": "tenant_id:%(tenant_id)s",
             "rule3": "project_id:%(project_id)s",
             "rule4": "user_id:%(user_id)s",
-            "admin_tenant_rule": "role:admin and tenant_id:%(tenant_id)s",
-            "admin_user_rule": "role:admin and user_id:%(user_id)s"
+            "admin_tenant_rule": "(role:admin and tenant_id:%(tenant_id)s)",
+            "admin_user_rule": "(role:admin and user_id:%(user_id)s)"
         }
 
         self.assertEqual(expected_policy_data, actual_policy_data)
 
     @mock.patch.object(policy_authority, 'stevedore', autospec=True)
-    def test_get_policy_data_from_file_and_from_code_with_overwrite(
+    def test_get_rules_from_file_and_from_code_with_overwrite(
             self, mock_stevedore):
         # The custom policy file should overwrite default rules rule1 and rule2
         # that are defined in code.
-        fake_policy_rules = [
-            self._get_fake_policy_rule('rule1', 'rule:code_rule_1'),
-            self._get_fake_policy_rule('rule2', 'rule:code_rule_2'),
-            self._get_fake_policy_rule('code_policy_action_3',
-                                       'rule:code_rule_3'),
-        ]
+        fake_policy_rules = self._get_fake_policies({
+            'rule1': 'rule:code_rule_1',
+            'rule2': 'rule:code_rule_2',
+            'code_policy_action_3': 'rule:code_rule_3',
+        })
 
         mock_manager = mock.Mock(obj=fake_policy_rules, __name__='foo')
-        mock_manager.configure_mock(name='fake_service')
+        mock_manager.configure_mock(name='tenant_rbac_policy')
         mock_stevedore.named.NamedExtensionManager.return_value = [
             mock_manager
         ]
@@ -364,81 +439,51 @@
 
         authority = policy_authority.PolicyAuthority(
             test_tenant_id, test_user_id, 'tenant_rbac_policy')
-        policy_data = authority._get_policy_data('fake_service')
-        self.assertIsInstance(policy_data, str)
+        rules = authority.get_rules()
+        self.assertIsInstance(rules, policy_authority.policy.Rules)
 
-        actual_policy_data = json.loads(policy_data)
+        actual_policy_data = {k: str(v) for k, v in rules.items()}
         expected_policy_data = {
             "code_policy_action_3": "rule:code_rule_3",
             "rule1": "tenant_id:%(network:tenant_id)s",
             "rule2": "tenant_id:%(tenant_id)s",
             "rule3": "project_id:%(project_id)s",
             "rule4": "user_id:%(user_id)s",
-            "admin_tenant_rule": "role:admin and tenant_id:%(tenant_id)s",
-            "admin_user_rule": "role:admin and user_id:%(user_id)s"
+            "admin_tenant_rule": "(role:admin and tenant_id:%(tenant_id)s)",
+            "admin_user_rule": "(role:admin and user_id:%(user_id)s)"
         }
 
         self.assertEqual(expected_policy_data, actual_policy_data)
 
     @mock.patch.object(policy_authority, 'stevedore', autospec=True)
-    def test_get_policy_data_cannot_find_policy(self, mock_stevedore):
+    def test_get_rules_cannot_find_policy(self, mock_stevedore):
         mock_stevedore.named.NamedExtensionManager.return_value = None
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               policy_authority.PolicyAuthority,
                               None, None, 'test_service')
 
         expected_error = (
-            'Policy file for {0} service was not found among the registered '
+            'Policy files for {0} service were not found among the registered '
             'in-code policies or in any of the possible policy files: {1}.'
             .format('test_service',
                     [CONF.patrole.custom_policy_files[0] % 'test_service']))
         self.assertIn(expected_error, str(e))
 
-    @mock.patch.object(policy_authority, 'json', autospec=True)
+    @mock.patch.object(policy_authority.policy, 'parse_file_contents',
+                       autospec=True)
     @mock.patch.object(policy_authority, 'stevedore', autospec=True)
-    def test_get_policy_data_without_valid_policy(self, mock_stevedore,
-                                                  mock_json):
-        test_policy_action = mock.Mock(check='rule:bar', __name__='foo')
-        test_policy_action.configure_mock(name='foo')
-
-        test_policy = mock.Mock(obj=[test_policy_action], __name__='foo')
-        test_policy.configure_mock(name='test_service')
-
-        mock_stevedore.named.NamedExtensionManager\
-            .return_value = [test_policy]
-
-        mock_json.dumps.side_effect = ValueError
-
-        e = self.assertRaises(rbac_exceptions.RbacParsingException,
-                              policy_authority.PolicyAuthority,
-                              None, None, 'test_service')
-
-        expected_error = "Policy file for {0} service is invalid."\
-                         .format("test_service")
-        self.assertIn(expected_error, str(e))
-
-        mock_stevedore.named.NamedExtensionManager.assert_called_once_with(
-            'oslo.policy.policies',
-            names=['test_service'],
-            on_load_failure_callback=None,
-            invoke_on_load=True,
-            warn_on_missing_entrypoint=False)
-
-    @mock.patch.object(policy_authority, 'json', autospec=True)
-    @mock.patch.object(policy_authority, 'stevedore', autospec=True)
-    def test_get_policy_data_from_file_not_json(self, mock_stevedore,
-                                                mock_json):
+    def test_get_rules_without_valid_policy(self, mock_stevedore,
+                                            mock_parse_file_contents):
         mock_stevedore.named.NamedExtensionManager.return_value = None
-        mock_json.loads.side_effect = ValueError
+        mock_parse_file_contents.side_effect = ValueError
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               policy_authority.PolicyAuthority,
                               None, None, 'tenant_rbac_policy')
 
         expected_error = (
-            'Policy file for {0} service was not found among the registered '
-            'in-code policies or in any of the possible policy files: {1}.'
-            .format('tenant_rbac_policy', [CONF.patrole.custom_policy_files[0]
-                                           % 'tenant_rbac_policy']))
+            'Policy files for {0} service were not found among the registered '
+            'in-code policies or in any of the possible policy files:'
+            .format('tenant_rbac_policy'))
         self.assertIn(expected_error, str(e))
 
     def test_discover_policy_files(self):
@@ -450,56 +495,62 @@
                       dir(policy_authority.PolicyAuthority))
         self.assertIn('policy_files', dir(policy_parser))
         self.assertIn('tenant_rbac_policy', policy_parser.policy_files)
-        self.assertEqual(self.conf_policy_path % 'tenant_rbac_policy',
+        self.assertEqual([self.conf_policy_path_json % 'tenant_rbac_policy'],
                          policy_parser.policy_files['tenant_rbac_policy'])
 
     @mock.patch.object(policy_authority, 'policy', autospec=True)
-    @mock.patch.object(policy_authority.PolicyAuthority, '_get_policy_data',
+    @mock.patch.object(policy_authority.PolicyAuthority, 'get_rules',
                        autospec=True)
     @mock.patch.object(policy_authority, 'clients', autospec=True)
     @mock.patch.object(policy_authority, 'os', autospec=True)
-    def test_discover_policy_files_with_many_invalid_one_valid(self, m_os,
-                                                               m_creds, *args):
+    @mock.patch.object(policy_authority, 'glob', autospec=True)
+    def test_discover_policy_files_with_many_invalid_one_valid(self, m_glob,
+                                                               m_os, m_creds,
+                                                               *args):
+        service = 'test_service'
+        custom_policy_files = ['foo/%s', 'bar/%s', 'baz/%s']
+        m_glob.iglob.side_effect = [iter([path % service])
+                                    for path in custom_policy_files]
         # Only the 3rd path is valid.
-        m_os.path.isfile.side_effect = [False, False, True, False]
+        m_os.path.isfile.side_effect = [False, False, True]
 
         # Ensure the outer for loop runs only once in `discover_policy_files`.
         m_creds.Manager().identity_services_v3_client.\
             list_services.return_value = {
-                'services': [{'name': 'test_service'}]}
+                'services': [{'name': service}]}
 
         # The expected policy will be 'baz/test_service'.
         self.useFixture(fixtures.ConfPatcher(
-            custom_policy_files=['foo/%s', 'bar/%s', 'baz/%s'],
+            custom_policy_files=custom_policy_files,
             group='patrole'))
 
         policy_parser = policy_authority.PolicyAuthority(
-            None, None, 'test_service')
+            None, None, service)
 
         # Ensure that "policy_files" is set at class and instance levels.
-        self.assertIn('policy_files',
-                      dir(policy_authority.PolicyAuthority))
-        self.assertIn('policy_files', dir(policy_parser))
-        self.assertIn('test_service', policy_parser.policy_files)
-        self.assertEqual('baz/test_service',
-                         policy_parser.policy_files['test_service'])
+        self.assertTrue(hasattr(policy_authority.PolicyAuthority,
+                                'policy_files'))
+        self.assertTrue(hasattr(policy_parser, 'policy_files'))
+        self.assertEqual(['baz/%s' % service],
+                         policy_parser.policy_files[service])
 
     def test_discover_policy_files_with_no_valid_files(self):
         expected_error = (
-            'Policy file for {0} service was not found among the registered '
+            'Policy files for {0} service were not found among the registered '
             'in-code policies or in any of the possible policy files: {1}.'
-            .format('test_service', [self.conf_policy_path % 'test_service']))
+            .format('test_service',
+                    [self.conf_policy_path_json % 'test_service']))
 
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               policy_authority.PolicyAuthority,
                               None, None, 'test_service')
         self.assertIn(expected_error, str(e))
 
-        self.assertIn('policy_files',
-                      dir(policy_authority.PolicyAuthority))
-        self.assertNotIn(
-            'test_service',
-            policy_authority.PolicyAuthority.policy_files.keys())
+        self.assertTrue(hasattr(policy_authority.PolicyAuthority,
+                                'policy_files'))
+        self.assertEqual(
+            [],
+            policy_authority.PolicyAuthority.policy_files['test_service'])
 
     def _test_validate_service(self, v2_services, v3_services,
                                expected_failure=False, expected_services=None):
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..9e547b8 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,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from __future__ import absolute_import
+
+import functools
 import mock
 from oslo_config import cfg
 
+import fixtures
 from tempest.lib import exceptions
 from tempest import manager
 from tempest import test
@@ -23,7 +27,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
 
@@ -42,11 +46,38 @@
                                project_id=mock.sentinel.project_id)
         setattr(self.mock_test_args.os_primary, 'credentials', mock_creds)
 
+        self.test_roles = ['member']
         self.useFixture(
-            fixtures.ConfPatcher(rbac_test_role='Member', group='patrole'))
+            patrole_fixtures.ConfPatcher(rbac_test_roles=self.test_roles,
+                                         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 BaseRBACMultiRoleRuleValidationTest(base.TestCase):
+
+    def setUp(self):
+        super(BaseRBACMultiRoleRuleValidationTest, self).setUp()
+        self.mock_test_args = mock.Mock(spec=test.BaseTestCase)
+        self.mock_test_args.os_primary = mock.Mock(spec=manager.Manager)
+        self.mock_test_args.rbac_utils = mock.Mock(
+            spec_set=rbac_utils.RbacUtils)
+
+        # Setup credentials for mock client manager.
+        mock_creds = mock.Mock(user_id=mock.sentinel.user_id,
+                               project_id=mock.sentinel.project_id)
+        setattr(self.mock_test_args.os_primary, 'credentials', mock_creds)
+
+        self.test_roles = ['member', 'anotherrole']
+        self.useFixture(
+            patrole_fixtures.ConfPatcher(rbac_test_roles=self.test_roles,
+                                         group='patrole'))
+        # Disable patrole log for unit tests.
+        self.useFixture(
+            patrole_fixtures.ConfPatcher(enable_reporting=False,
+                                         group='patrole_log'))
 
 
 class RBACRuleValidationTest(BaseRBACRuleValidationTest):
@@ -54,6 +85,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,
@@ -69,7 +106,6 @@
             pass
 
         test_policy(self.mock_test_args)
-        mock_log.warning.assert_not_called()
         mock_log.error.assert_not_called()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
@@ -88,7 +124,6 @@
             raise exceptions.Forbidden()
 
         test_policy(self.mock_test_args)
-        mock_log.warning.assert_not_called()
         mock_log.error.assert_not_called()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
@@ -108,8 +143,8 @@
         def test_policy(*args):
             raise exceptions.Forbidden()
 
-        test_re = ("Role Member was not allowed to perform the following "
-                   "actions: \[%s\].*" % (mock.sentinel.action))
+        test_re = ("User with roles \['member'\] was not allowed to perform "
+                   "the following actions: \[%s\].*" % (mock.sentinel.action))
         self.assertRaisesRegex(
             rbac_exceptions.RbacUnderPermissionException, test_re, test_policy,
             self.mock_test_args)
@@ -117,86 +152,66 @@
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
-    def test_rule_validation_rbac_malformed_response_positive(
+    def test_rule_validation_rbac_failed_response_body_positive(
             self, mock_authority, mock_log):
-        """Test RbacMalformedResponse error is thrown without permission passes.
+        """Test BasePatroleResponseBodyException error is thrown without
+        permission passes.
 
-        Positive test case: if RbacMalformedResponse is thrown and the user is
-        not allowed to perform the action, then this is a success.
+        Positive test case: if subclass of BasePatroleResponseBodyException is
+        thrown and the user is not allowed to perform the action, then this is
+        a success.
         """
         mock_authority.PolicyAuthority.return_value.allowed.return_value =\
             False
 
-        @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
-        def test_policy(*args):
-            raise rbac_exceptions.RbacMalformedResponse()
+        def _do_test(exception_cls, **kwargs):
+            @rbac_rv.action(mock.sentinel.service,
+                            rules=[mock.sentinel.action])
+            def test_policy(*args):
+                raise exception_cls(**kwargs)
 
-        mock_log.error.assert_not_called()
-        mock_log.warning.assert_not_called()
+            mock_log.error.assert_not_called()
+            mock_log.warning.assert_not_called()
+
+        _do_test(rbac_exceptions.RbacMissingAttributeResponseBody,
+                 attribute=mock.sentinel.attr)
+        _do_test(rbac_exceptions.RbacPartialResponseBody,
+                 body=mock.sentinel.body)
+        _do_test(rbac_exceptions.RbacEmptyResponseBody)
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
-    def test_rule_validation_rbac_malformed_response_negative(
+    def test_rule_validation_soft_authorization_exceptions(
             self, mock_authority, mock_log):
-        """Test RbacMalformedResponse error is thrown with permission fails.
+        """Test RbacUnderPermissionException error is thrown when any of the
+        soft authorization-related exceptions are raised by a test.
 
-        Negative test case: if RbacMalformedResponse is thrown and the user is
-        allowed to perform the action, then this is an expected failure.
+        Negative test case: if subclass of BasePatroleResponseBodyException is
+        thrown and the user is allowed to perform the action, then this is an
+        expected failure.
         """
         mock_authority.PolicyAuthority.return_value.allowed.return_value = True
 
-        @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
-        def test_policy(*args):
-            raise rbac_exceptions.RbacMalformedResponse()
+        def _do_test(exception_cls, **kwargs):
+            @rbac_rv.action(mock.sentinel.service,
+                            rules=[mock.sentinel.action])
+            def test_policy(*args):
+                raise exception_cls(**kwargs)
 
-        test_re = ("Role Member was not allowed to perform the following "
-                   "actions: \[%s\].*" % (mock.sentinel.action))
-        self.assertRaisesRegex(
-            rbac_exceptions.RbacUnderPermissionException, test_re, test_policy,
-            self.mock_test_args)
-        self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
+            test_re = (".*User with roles \[%s\] was not allowed to "
+                       "perform the following actions: \[%s\].*" % (
+                           ', '.join("'%s'" % r for r in self.test_roles),
+                           mock.sentinel.action))
+            self.assertRaisesRegex(
+                rbac_exceptions.RbacUnderPermissionException, test_re,
+                test_policy, self.mock_test_args)
+            self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
 
-    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
-    def test_rule_validation_rbac_conflicting_policies_positive(
-            self, mock_authority, mock_log):
-        """Test RbacConflictingPolicies error is thrown without permission passes.
-
-        Positive test case: if RbacConflictingPolicies is thrown and the user
-        is not allowed to perform the action, then this is a success.
-        """
-        mock_authority.PolicyAuthority.return_value.allowed.return_value =\
-            False
-
-        @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
-        def test_policy(*args):
-            raise rbac_exceptions.RbacConflictingPolicies()
-
-        mock_log.error.assert_not_called()
-        mock_log.warning.assert_not_called()
-
-    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
-    def test_rule_validation_rbac_conflicting_policies_negative(self,
-                                                                mock_authority,
-                                                                mock_log):
-        """Test RbacConflictingPolicies error is thrown with permission fails.
-
-        Negative test case: if RbacConflictingPolicies is thrown and the user
-        is allowed to perform the action, then this is an expected failure.
-        """
-        mock_authority.PolicyAuthority.return_value.allowed.return_value = True
-
-        @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
-        def test_policy(*args):
-            raise rbac_exceptions.RbacConflictingPolicies()
-
-        test_re = ("Role Member was not allowed to perform the following "
-                   "actions: \[%s\].*" % (mock.sentinel.action))
-        self.assertRaisesRegex(
-            rbac_exceptions.RbacUnderPermissionException, test_re, test_policy,
-            self.mock_test_args)
-        self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
+        _do_test(rbac_exceptions.RbacMissingAttributeResponseBody,
+                 attribute=mock.sentinel.attr)
+        _do_test(rbac_exceptions.RbacPartialResponseBody,
+                 body=mock.sentinel.body)
+        _do_test(rbac_exceptions.RbacEmptyResponseBody)
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
@@ -211,7 +226,7 @@
            exception.
         """
         @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action],
-                        expected_error_code=404)
+                        expected_error_codes=[404])
         def test_policy(*args):
             raise exceptions.Forbidden('Test message')
 
@@ -241,13 +256,13 @@
         policy_names = ['foo:bar']
 
         @rbac_rv.action(mock.sentinel.service, rules=policy_names,
-                        expected_error_code=404)
+                        expected_error_codes=[404])
         def test_policy(*args):
             raise exceptions.NotFound()
 
         expected_errors = [
-            ("Role Member was not allowed to perform the following "
-             "actions: \['%s'\].*" % policy_names[0]),
+            ("User with roles \['member'\] was not allowed to perform the "
+             "following actions: \['%s'\].*" % policy_names[0]),
             None
         ]
 
@@ -269,7 +284,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)
@@ -294,7 +309,7 @@
             pass
 
         @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action],
-                        expected_error_code=404)
+                        expected_error_codes=[404])
         def test_policy_expect_not_found(*args):
             pass
 
@@ -334,7 +349,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)
@@ -380,11 +395,97 @@
             mock_log.error.reset_mock()
 
 
+class RBACMultiRoleRuleValidationTest(BaseRBACMultiRoleRuleValidationTest,
+                                      RBACRuleValidationTest):
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_forbidden_negative(self, mock_authority,
+                                                mock_log):
+        """Test RbacUnderPermissionException error is thrown and have
+        permission fails.
+
+        Negative test case: if Forbidden is thrown and the user should be
+        allowed to perform the action, then the RbacUnderPermissionException
+        exception should be raised.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value = True
+
+        @rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
+        def test_policy(*args):
+            raise exceptions.Forbidden()
+
+        test_re = ("User with roles \['member', 'anotherrole'\] was not "
+                   "allowed to perform the following actions: \[%s\].*" %
+                   (mock.sentinel.action))
+        self.assertRaisesRegex(
+            rbac_exceptions.RbacUnderPermissionException, test_re, test_policy,
+            self.mock_test_args)
+        self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
+
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_expect_not_found_and_raise_not_found(self, mock_authority,
+                                                  mock_log):
+        """Test that expecting 404 and getting 404 works for all scenarios.
+
+        Tests the following scenarios:
+        1) Test no permission and 404 is expected and 404 is thrown succeeds.
+        2) Test have permission and 404 is expected and 404 is thrown fails.
+
+        In both cases, a LOG.warning is called with the "irregular message"
+        that signals to user that a 404 was expected and caught.
+        """
+        policy_names = ['foo:bar']
+
+        @rbac_rv.action(mock.sentinel.service, rules=policy_names,
+                        expected_error_codes=[404])
+        def test_policy(*args):
+            raise exceptions.NotFound()
+
+        expected_errors = [
+            ("User with roles \['member', 'anotherrole'\] was not allowed to "
+             "perform the following actions: \['%s'\].*" % policy_names[0]),
+            None
+        ]
+
+        for pos, allowed in enumerate([True, False]):
+            mock_authority.PolicyAuthority.return_value.allowed\
+                .return_value = allowed
+
+            error_re = expected_errors[pos]
+
+            if error_re:
+                self.assertRaisesRegex(
+                    rbac_exceptions.RbacUnderPermissionException, error_re,
+                    test_policy, self.mock_test_args)
+                self.assertRegex(mock_log.error.mock_calls[0][1][0], error_re)
+            else:
+                test_policy(self.mock_test_args)
+                mock_log.error.assert_not_called()
+
+            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",
+                test_policy.__name__,
+                ', '.join(policy_names),
+                mock.sentinel.service)
+
+            mock_log.warning.reset_mock()
+            mock_log.error.reset_mock()
+
+
 class RBACRuleValidationLoggingTest(BaseRBACRuleValidationTest):
     """Test class for validating the RBAC log, dedicated to just logging
     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 +493,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 +512,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']
@@ -429,9 +532,80 @@
             "Allowed",
             "Allowed")
 
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_with_callable_rule(self, mock_authority,
+                                                mock_log):
+        """Test that a callable as the rule is evaluated correctly."""
+        mock_authority.PolicyAuthority.return_value.allowed.return_value = True
+
+        @rbac_rv.action(mock.sentinel.service,
+                        rules=[lambda: mock.sentinel.action])
+        def test_policy(*args):
+            pass
+
+        test_policy(self.mock_test_args)
+
+        policy_authority = mock_authority.PolicyAuthority.return_value
+        policy_authority.allowed.assert_called_with(
+            mock.sentinel.action,
+            CONF.patrole.rbac_test_roles)
+
+        mock_log.error.assert_not_called()
+
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_with_conditional_callable_rule(
+            self, mock_authority, mock_log):
+        """Test that a complex callable with conditional logic as the rule is
+        evaluated correctly.
+        """
+        mock_authority.PolicyAuthority.return_value.allowed.return_value = True
+
+        def partial_func(x):
+            return "foo" if x == "bar" else "qux"
+        foo_callable = functools.partial(partial_func, "bar")
+        bar_callable = functools.partial(partial_func, "baz")
+
+        @rbac_rv.action(mock.sentinel.service,
+                        rules=[foo_callable])
+        def test_foo_policy(*args):
+            pass
+
+        @rbac_rv.action(mock.sentinel.service,
+                        rules=[bar_callable])
+        def test_bar_policy(*args):
+            pass
+
+        test_foo_policy(self.mock_test_args)
+        policy_authority = mock_authority.PolicyAuthority.return_value
+        policy_authority.allowed.assert_called_with(
+            "foo",
+            CONF.patrole.rbac_test_roles)
+        policy_authority.allowed.reset_mock()
+
+        test_bar_policy(self.mock_test_args)
+        policy_authority = mock_authority.PolicyAuthority.return_value
+        policy_authority.allowed.assert_called_with(
+            "qux",
+            CONF.patrole.rbac_test_roles)
+
+        mock_log.error.assert_not_called()
+
+
+class RBACMultiRoleRuleValidationLoggingTest(
+    BaseRBACMultiRoleRuleValidationTest, RBACRuleValidationLoggingTest):
+    pass
+
 
 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."""
@@ -446,15 +620,26 @@
                           test_policy, self.mock_test_args)
 
 
+class RBACMultiRoleRuleValidationNegativeTest(
+    BaseRBACMultiRoleRuleValidationTest, RBACRuleValidationNegativeTest):
+    pass
+
+
 class RBACRuleValidationTestMultiPolicy(BaseRBACRuleValidationTest):
     """Test suite for validating multi-policy support for the
     ``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([
-            mock.call(rule, CONF.patrole.rbac_test_role) for rule in rules
+            mock.call(rule, CONF.patrole.rbac_test_roles) for rule in rules
         ])
 
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
@@ -566,10 +751,10 @@
         mock_authority.PolicyAuthority.return_value.allowed\
             .return_value = True
 
-        error_re = ("Role Member was not allowed to perform the following "
-                    "actions: %s. Expected allowed actions: %s. Expected "
-                    "disallowed actions: []." % (rules, rules)).replace(
-                        '[', '\[').replace(']', '\]')
+        error_re = ("User with roles ['member'] was not allowed to perform "
+                    "the following actions: %s. Expected allowed actions: %s. "
+                    "Expected disallowed actions: []." %
+                    (rules, rules)).replace('[', '\[').replace(']', '\]')
         self.assertRaisesRegex(
             rbac_exceptions.RbacUnderPermissionException, error_re,
             test_policy, self.mock_test_args)
@@ -641,31 +826,24 @@
         _do_test([True, False, False, True], 'mock.sentinel.action2')
         _do_test([True, False, True, False], 'mock.sentinel.action2')
 
-    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    def test_prepare_multi_policy_allowed_usages(self, mock_log):
+    def test_prepare_multi_policy_allowed_usages(self):
 
-        def _do_test(rule, rules, ecode, ecodes, exp_rules, exp_ecodes):
-            rule_list, ec_list = rbac_rv._prepare_multi_policy(rule, rules,
-                                                               ecode, ecodes)
+        def _do_test(rules, ecodes, exp_rules, exp_ecodes):
+            rule_list, ec_list = rbac_rv._prepare_multi_policy(rules, ecodes)
             self.assertEqual(rule_list, exp_rules)
             self.assertEqual(ec_list, exp_ecodes)
 
-        # Validate that using deprecated values: rule and expected_error_code
-        # are converted into rules = [rule] and expected_error_codes =
-        # [expected_error_code]
-        _do_test("rule1", None, 403, None, ["rule1"], [403])
-
-        # Validate that rules = [rule] and expected_error_codes defaults to
-        # 403 when no values are provided.
-        _do_test("rule1", None, None, None, ["rule1"], [403])
+        # Validate that expected_error_codes defaults to 403 when no values
+        # are provided.
+        _do_test(["rule1"], None, ["rule1"], [403])
 
         # Validate that `len(rules) == len(expected_error_codes)` works when
         # both == 1.
-        _do_test(None, ["rule1"], None, [403], ["rule1"], [403])
+        _do_test(["rule1"], [403], ["rule1"], [403])
 
         # Validate that `len(rules) == len(expected_error_codes)` works when
         # both are > 1.
-        _do_test(None, ["rule1", "rule2"], None, [403, 404],
+        _do_test(["rule1", "rule2"], [403, 404],
                  ["rule1", "rule2"], [403, 404])
 
         # Validate that when only a default expected_error_code argument is
@@ -675,36 +853,243 @@
         #     @rbac_rv.action(service, rules=[<rule>, <rule>])
         #     def test_policy(*args):
         #        ...
-        _do_test(None, ["rule1", "rule2"], 403, None,
+        _do_test(["rule1", "rule2"], None,
                  ["rule1", "rule2"], [403, 403])
 
-        # Validate that the deprecated values are ignored when new values are
-        # provided.
-        _do_test("rule3", ["rule1", "rule2"], 404, [403, 403],
-                 ["rule1", "rule2"], [403, 403])
-        mock_log.debug.assert_any_call(
-            "The `rules` argument will be used instead of `rule`.")
-        mock_log.debug.assert_any_call(
-            "The `exp_error_codes` argument will be used instead of "
-            "`exp_error_code`.")
-
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     def test_prepare_multi_policy_disallowed_usages(self, mock_log):
 
-        def _do_test(rule, rules, ecode, ecodes):
-            rule_list, ec_list = rbac_rv._prepare_multi_policy(rule, rules,
-                                                               ecode, ecodes)
+        def _do_test(rules, ecodes):
+            rule_list, ec_list = rbac_rv._prepare_multi_policy(rules, ecodes)
 
         error_re = ("The `expected_error_codes` list is not the same length"
                     " as the `rules` list.")
         # When len(rules) > 1 then len(expected_error_codes) must be same len.
         self.assertRaisesRegex(ValueError, error_re, _do_test,
-                               None, ["rule1", "rule2"], None, [403])
+                               ["rule1", "rule2"], [403])
         # When len(expected_error_codes) > 1 len(rules) must be same len.
-        self.assertRaisesRegex(ValueError, error_re, _do_test,
-                               None, ["rule1"], None, [403, 404])
+        self.assertRaisesRegex(ValueError, error_re, _do_test, ["rule1"],
+                               [403, 404])
         error_re = ("The `rules` list must be provided if using the "
                     "`expected_error_codes` list.")
         # When expected_error_codes is provided rules must be as well.
-        self.assertRaisesRegex(ValueError, error_re, _do_test,
-                               None, None, None, [404])
+        self.assertRaisesRegex(ValueError, error_re, _do_test, None, [404])
+
+
+class RBACMultiRoleRuleValidationTestMultiPolicy(
+    BaseRBACMultiRoleRuleValidationTest, RBACRuleValidationTestMultiPolicy):
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+    def test_rule_validation_multi_policy_forbidden_failure(
+            self, mock_authority, mock_log):
+        """Test that when the expected result is authorized and the test
+        fails (with a Forbidden error code) that the overall evaluation
+        results in a RbacUnderPermissionException getting raised.
+        """
+
+        # NOTE: Avoid mock.sentinel here due to weird sorting with them.
+        rules = ['action1', 'action2', 'action3']
+
+        @rbac_rv.action(mock.sentinel.service, rules=rules,
+                        expected_error_codes=[403, 403, 403])
+        def test_policy(*args):
+            raise exceptions.Forbidden()
+
+        mock_authority.PolicyAuthority.return_value.allowed\
+            .return_value = True
+
+        error_re = ("User with roles ['member', 'anotherrole'] was not "
+                    "allowed to perform the following actions: %s. Expected "
+                    "allowed actions: %s. Expected disallowed actions: []." %
+                    (rules, rules)).replace('[', '\[').replace(']', '\]')
+        self.assertRaisesRegex(
+            rbac_exceptions.RbacUnderPermissionException, error_re,
+            test_policy, self.mock_test_args)
+        self.assertRegex(mock_log.error.mock_calls[0][1][0], error_re)
+        self._assert_policy_authority_called_with(rules, mock_authority)
+
+
+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. RbacPartialResponseBody) 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 4937318..bd13e34 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -52,14 +52,14 @@
         self.rbac_utils.override_role()
 
         mock_test_obj = self.rbac_utils.mock_test_obj
-        roles_client = self.rbac_utils.roles_v3_client
+        roles_client = self.rbac_utils.admin_roles_client
         mock_time = self.rbac_utils.mock_time
 
         roles_client.create_user_role_on_project.assert_called_once_with(
             self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID, 'admin_id')
-        mock_test_obj.os_primary.auth_provider.clear_auth\
+        mock_test_obj.get_auth_providers()[0].clear_auth\
             .assert_called_once_with()
-        mock_test_obj.os_primary.auth_provider.set_auth\
+        mock_test_obj.get_auth_providers()[0].set_auth\
             .assert_called_once_with()
         mock_time.sleep.assert_called_once_with(1)
 
@@ -67,7 +67,7 @@
         self.rbac_utils.set_roles(['admin', 'member'], 'admin')
         self.rbac_utils.override_role()
 
-        roles_client = self.rbac_utils.roles_v3_client
+        roles_client = self.rbac_utils.admin_roles_client
         mock_time = self.rbac_utils.mock_time
 
         roles_client.create_user_role_on_project.assert_not_called()
@@ -77,7 +77,7 @@
         self.rbac_utils.override_role(True)
 
         mock_test_obj = self.rbac_utils.mock_test_obj
-        roles_client = self.rbac_utils.roles_v3_client
+        roles_client = self.rbac_utils.admin_roles_client
         mock_time = self.rbac_utils.mock_time
 
         roles_client.create_user_role_on_project.assert_has_calls([
@@ -86,9 +86,9 @@
             mock.call(self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID,
                       'member_id')
         ])
-        mock_test_obj.os_primary.auth_provider.clear_auth.assert_has_calls(
+        mock_test_obj.get_auth_providers()[0].clear_auth.assert_has_calls(
             [mock.call()] * 2)
-        mock_test_obj.os_primary.auth_provider.set_auth.assert_has_calls(
+        mock_test_obj.get_auth_providers()[0].set_auth.assert_has_calls(
             [mock.call()] * 2)
         mock_time.sleep.assert_has_calls([mock.call(1)] * 2)
 
@@ -96,7 +96,7 @@
         self.rbac_utils.set_roles(['admin', 'member'], 'member')
         self.rbac_utils.override_role(True)
 
-        roles_client = self.rbac_utils.roles_v3_client
+        roles_client = self.rbac_utils.admin_roles_client
         mock_time = self.rbac_utils.mock_time
 
         roles_client.create_user_role_on_project.assert_has_calls([
@@ -109,7 +109,7 @@
         self.rbac_utils.override_role(True, False)
 
         mock_test_obj = self.rbac_utils.mock_test_obj
-        roles_client = self.rbac_utils.roles_v3_client
+        roles_client = self.rbac_utils.admin_roles_client
         mock_time = self.rbac_utils.mock_time
 
         roles_client.create_user_role_on_project.assert_has_calls([
@@ -120,9 +120,9 @@
             mock.call(self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID,
                       'admin_id')
         ])
-        mock_test_obj.os_primary.auth_provider.clear_auth.assert_has_calls(
+        mock_test_obj.get_auth_providers()[0].clear_auth.assert_has_calls(
             [mock.call()] * 3)
-        mock_test_obj.os_primary.auth_provider.set_auth.assert_has_calls(
+        mock_test_obj.get_auth_providers()[0].set_auth.assert_has_calls(
             [mock.call()] * 3)
         mock_time.sleep.assert_has_calls([mock.call(1)] * 3)
 
@@ -133,7 +133,7 @@
         self.rbac_utils.set_roles(['admin', 'member'], ['member', 'random'])
         self.rbac_utils.override_role()
 
-        roles_client = self.rbac_utils.roles_v3_client
+        roles_client = self.rbac_utils.admin_roles_client
 
         roles_client.list_user_roles_on_project.assert_called_once_with(
             self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID)
@@ -169,7 +169,8 @@
         mock_override_role.assert_called_once_with(_rbac_utils, test_obj,
                                                    False)
 
-    @mock.patch.object(rbac_utils.RbacUtils, '_override_role', autospec=True)
+    @mock.patch.object(rbac_utils.RbacUtils, '_override_role',
+                       autospec=True)
     def test_override_role_context_manager_simulate_fail(self,
                                                          mock_override_role):
         """Validate that expected override_role calls are made when switching
@@ -208,11 +209,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 +233,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/patrole_tempest_plugin/tests/unit/test_requirements_authority.py b/patrole_tempest_plugin/tests/unit/test_requirements_authority.py
index 1fb9636..94af81f 100644
--- a/patrole_tempest_plugin/tests/unit/test_requirements_authority.py
+++ b/patrole_tempest_plugin/tests/unit/test_requirements_authority.py
@@ -17,19 +17,29 @@
 from tempest.lib import exceptions
 from tempest.tests import base
 
+from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import requirements_authority as req_auth
 
 
-class RequirementsAuthorityTest(base.TestCase):
+class BaseRequirementsAuthorityTest(base.TestCase):
     def setUp(self):
-        super(RequirementsAuthorityTest, self).setUp()
+        super(BaseRequirementsAuthorityTest, self).setUp()
         self.rbac_auth = req_auth.RequirementsAuthority()
         self.current_directory = os.path.dirname(os.path.realpath(__file__))
         self.yaml_test_file = os.path.join(self.current_directory,
                                            'resources',
                                            'rbac_roles.yaml')
-        self.expected_result = {'test:create': ['test_member', '_member_'],
-                                'test:create2': ['test_member']}
+        self.expected_result = {'test:create': [['test_member'], ['_member_']],
+                                'test:create2': [['test_member']],
+                                'test:create3': [['test_member', '_member_']],
+                                'test:create4': [['test_member', '!_member_']]}
+        self.expected_rbac_map = {'test:create': ['test_member', '_member_'],
+                                  'test:create2': ['test_member'],
+                                  'test:create3': ['test_member, _member_'],
+                                  'test:create4': ['test_member, !_member_']}
+
+
+class RequirementsAuthorityTest(BaseRequirementsAuthorityTest):
 
     def test_requirements_auth_init(self):
         rbac_auth = req_auth.RequirementsAuthority(self.yaml_test_file, 'Test')
@@ -38,37 +48,38 @@
     def test_auth_allowed_empty_roles(self):
         self.rbac_auth.roles_dict = None
         self.assertRaises(exceptions.InvalidConfiguration,
-                          self.rbac_auth.allowed, "", "")
+                          self.rbac_auth.allowed, "", [""])
 
     def test_auth_allowed_role_in_api(self):
-        self.rbac_auth.roles_dict = {'api': ['_member_']}
-        self.assertTrue(self.rbac_auth.allowed("api", "_member_"))
+        self.rbac_auth.roles_dict = {'rule': [['_member_']]}
+        self.assertTrue(self.rbac_auth.allowed("rule", ["_member_"]))
 
     def test_auth_allowed_role_not_in_api(self):
-        self.rbac_auth.roles_dict = {'api': ['_member_']}
-        self.assertFalse(self.rbac_auth.allowed("api", "support_member"))
+        self.rbac_auth.roles_dict = {'rule': [['_member_']]}
+        self.assertFalse(self.rbac_auth.allowed("rule", "support_member"))
 
-    def test_parser_get_allowed_except_keyerror(self):
-        self.rbac_auth.roles_dict = {}
-        self.assertRaises(KeyError, self.rbac_auth.allowed,
-                          "api", "support_member")
+    def test_parser_get_allowed_invalid_rule_raises_parsing_exception(self):
+        self.rbac_auth.roles_dict = {"foo": "bar"}
+        self.assertRaises(rbac_exceptions.RbacParsingException,
+                          self.rbac_auth.allowed, "baz", "support_member")
 
     def test_parser_init(self):
         req_auth.RequirementsParser(self.yaml_test_file)
-        self.assertEqual([{'Test': self.expected_result}],
+        self.assertEqual([{'Test': self.expected_rbac_map}],
                          req_auth.RequirementsParser.Inner._rbac_map)
 
     def test_parser_role_in_api(self):
         req_auth.RequirementsParser.Inner._rbac_map = \
-            [{'Test': self.expected_result}]
+            [{'Test': self.expected_rbac_map}]
         self.rbac_auth.roles_dict = req_auth.RequirementsParser.parse("Test")
 
         self.assertEqual(self.expected_result, self.rbac_auth.roles_dict)
-        self.assertTrue(self.rbac_auth.allowed("test:create2", "test_member"))
+        self.assertTrue(
+            self.rbac_auth.allowed("test:create2", ["test_member"]))
 
     def test_parser_role_not_in_api(self):
         req_auth.RequirementsParser.Inner._rbac_map = \
-            [{'Test': self.expected_result}]
+            [{'Test': self.expected_rbac_map}]
         self.rbac_auth.roles_dict = req_auth.RequirementsParser.parse("Test")
 
         self.assertEqual(self.expected_result, self.rbac_auth.roles_dict)
@@ -76,10 +87,102 @@
 
     def test_parser_except_invalid_configuration(self):
         req_auth.RequirementsParser.Inner._rbac_map = \
-            [{'Test': self.expected_result}]
+            [{'Test': self.expected_rbac_map}]
         self.rbac_auth.roles_dict = \
             req_auth.RequirementsParser.parse("Failure")
 
-        self.assertIsNone(self.rbac_auth.roles_dict)
+        self.assertFalse(self.rbac_auth.roles_dict)
         self.assertRaises(exceptions.InvalidConfiguration,
-                          self.rbac_auth.allowed, "", "")
+                          self.rbac_auth.allowed, "", [""])
+
+    def test_auth_allowed_exclamation_mark_syntax_single_role(self):
+        """Ensure that exclamation mark in front of role is dropped, and not
+        considered as part of role itself.
+        """
+
+        self.rbac_auth.roles_dict = {'rule': [['!admin']]}
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member"]))
+        self.assertTrue(self.rbac_auth.allowed("rule", ["!admin"]))
+        self.assertFalse(self.rbac_auth.allowed("rule", ["admin"]))
+
+
+class RequirementsAuthorityMultiRoleTest(BaseRequirementsAuthorityTest):
+
+    def test_auth_allowed_exclamation_mark_syntax_multi_role(self):
+        """Ensure that exclamation mark in front of role is dropped, and not
+        considered as part of role itself.
+        """
+
+        self.rbac_auth.roles_dict = {'rule': [['member', '!admin']]}
+        self.assertFalse(self.rbac_auth.allowed("rule", ["member", "admin"]))
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member", "!admin"]))
+
+    def test_auth_allowed_single_rule_scenario(self):
+        # member and support and not admin and not manager
+        self.rbac_auth.roles_dict = {'rule': [['member', 'support',
+                                               '!admin', '!manager']]}
+
+        # User is member and support and not manager or admin
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member",
+                                                        "support"]))
+
+        # User is member and not manager or admin, but not support
+        self.assertFalse(self.rbac_auth.allowed("rule", ["member"]))
+
+        # User is support and not manager or admin, but not member
+        self.assertFalse(self.rbac_auth.allowed("rule", ["support"]))
+
+        # User is member and support and not manager, but have admin role
+        self.assertFalse(self.rbac_auth.allowed("rule", ["member",
+                                                         "support",
+                                                         "admin"]))
+
+        # User is member and not manager, but have admin role and not support
+        self.assertFalse(self.rbac_auth.allowed("rule", ["member",
+                                                         "admin"]))
+
+        # User is member and support, but have manager and admin roles
+        self.assertFalse(self.rbac_auth.allowed("rule", ["member",
+                                                         "support",
+                                                         "admin",
+                                                         "manager"]))
+
+    def test_auth_allowed_multi_rule_scenario(self):
+        rules = [
+            ['member', 'support', '!admin', '!manager'],
+            ['member', 'admin'],
+            ["manager"]
+        ]
+        self.rbac_auth.roles_dict = {'rule': rules}
+
+        # Not a single role allows viewer
+        self.assertFalse(self.rbac_auth.allowed("rule", ["viewer"]))
+        # We have no rule that allows support and admin
+        self.assertFalse(self.rbac_auth.allowed("rule", ["support",
+                                                         "admin"]))
+        # There is no rule that requires member without additional requirements
+        self.assertFalse(self.rbac_auth.allowed("rule", ["member"]))
+
+        # Pass with rules[2]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["manager"]))
+        # Pass with rules[0]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member",
+                                                        "support"]))
+        # Pass with rules[1]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member",
+                                                        "admin"]))
+        # Pass with rules[2]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["manager",
+                                                        "admin"]))
+        # Pass with rules[1]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member",
+                                                        "support",
+                                                        "admin"]))
+        # Pass with rules[1]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["member",
+                                                        "support",
+                                                        "admin",
+                                                        "manager"]))
+        # Pass with rules[2]
+        self.assertTrue(self.rbac_auth.allowed("rule", ["admin",
+                                                        "manager"]))
diff --git a/releasenotes/notes/add-neutron-tempest-plugin-clients-c031e232021b390c.yaml b/releasenotes/notes/add-neutron-tempest-plugin-clients-c031e232021b390c.yaml
new file mode 100644
index 0000000..91d3f20
--- /dev/null
+++ b/releasenotes/notes/add-neutron-tempest-plugin-clients-c031e232021b390c.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    In order to strive toward complete test coverage for the services it
+    tests, Patrole now offers RBAC coverage for the APIs included in
+    neutron-tempest-plugin. If this plugin is not installed or enabled, then
+    Patrole will skip those tests.
diff --git a/releasenotes/notes/break-up-rbac-malformed-exception-into-discrete-exceptions-92aedb99d0a13f58.yaml b/releasenotes/notes/break-up-rbac-malformed-exception-into-discrete-exceptions-92aedb99d0a13f58.yaml
new file mode 100644
index 0000000..0a93b64
--- /dev/null
+++ b/releasenotes/notes/break-up-rbac-malformed-exception-into-discrete-exceptions-92aedb99d0a13f58.yaml
@@ -0,0 +1,25 @@
+---
+features:
+  - |
+    The exception class ``RbacMalformedException`` has been broken up into the
+    following discrete exceptions:
+
+    * ``RbacMissingAttributeResponseBody`` - incomplete means that the
+      response body (for show or list) is missing certain attributes
+    * ``RbacPartialResponseBody`` - partial means that a list response
+      only returned a subset of the possible results available.
+    * ``RbacEmptyResponseBody`` - empty means that the show or list
+      response body is entirely empty
+
+    Each of the exception classes above deals with a different type of failure
+    related to a soft authorization failure. This means that, rather than a
+    403 error code getting returned by the server, the response body is
+    incomplete in some way.
+upgrade:
+  - |
+    The exception class ``RbacMalformedException`` has been removed. Use one
+    of the following exception classes instead:
+
+    * ``RbacMissingAttributeResponseBody``
+    * ``RbacPartialResponseBody``
+    * ``RbacEmptyResponseBody``
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/deprecate-enable-rbac-option-1e499bb0914cdee8.yaml b/releasenotes/notes/deprecate-enable-rbac-option-1e499bb0914cdee8.yaml
new file mode 100644
index 0000000..7354c4f
--- /dev/null
+++ b/releasenotes/notes/deprecate-enable-rbac-option-1e499bb0914cdee8.yaml
@@ -0,0 +1,13 @@
+---
+deprecations:
+  - |
+    The ``[patrole].enable_rbac`` option is deprecated and will be removed
+    during the "S" release. This is a legacy option that was meaningful
+    downstream when Patrole was a suite of tests inside Tempest itself. Now
+    that Patrole exists upstream as a Tempest plugin, it is paradoxical to
+    install the Patrole plugin yet have an option that allows all Patrole
+    tests to be skipped. This option is at odds with current Patrole
+    architecture.
+
+    To skip RBAC tests going forward, with Patrole Tempest plugin already
+    installed, use an appropriate regex.
diff --git a/releasenotes/notes/deprecate-roles-client-in-rbac-utils-087eda0658d18fa9.yaml b/releasenotes/notes/deprecate-roles-client-in-rbac-utils-087eda0658d18fa9.yaml
new file mode 100644
index 0000000..c42da8a
--- /dev/null
+++ b/releasenotes/notes/deprecate-roles-client-in-rbac-utils-087eda0658d18fa9.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+  - |
+    Patrole will only support the v3 Tempest roles client for role
+    overriding operations. Support for the v2 version has been dropped
+    because the Keystone v2 API is slated for removal.
diff --git a/releasenotes/notes/multi-role-rbac-7f597c004a558956.yaml b/releasenotes/notes/multi-role-rbac-7f597c004a558956.yaml
new file mode 100644
index 0000000..20c6c0e
--- /dev/null
+++ b/releasenotes/notes/multi-role-rbac-7f597c004a558956.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    We have replaced CONF.patrole.rbac_test_role with
+    CONF.patrole.rbac_test_roles, where instead of single role we can specify
+    list of roles to be assigned to test user. This way we may run rbac tests
+    for scenarios that requires user to have more that a single role.
+deprecations:
+  - |
+    Config parameter CONF.rbac_test_role is deprecated in favor of
+    CONF.rbac_test_roles that implements a list of roles instead of single role.
diff --git a/releasenotes/notes/multiple-policy-files-9aa7f7583283739e.yaml b/releasenotes/notes/multiple-policy-files-9aa7f7583283739e.yaml
new file mode 100644
index 0000000..a3555e6
--- /dev/null
+++ b/releasenotes/notes/multiple-policy-files-9aa7f7583283739e.yaml
@@ -0,0 +1,17 @@
+---
+features:
+  - |
+    In order to implement the tests for plugins which do not maintain the
+    ``policy.json`` with full list of the policy rules and provide policy file
+    with only their own policy rules, the Patrole should be able to load and
+    merge multiple policy files for any of the services.
+
+    - Discovery all policy files for each of the services.
+      The updated ``discover_policy_files`` function picks all candidate paths
+      found out of the potential paths in the ``[patrole].custom_policy_files``
+      config option. Using ``glob.glob()`` function makes it possible to use
+      the patterns like '\*.json' to discover the policy files.
+
+    - Loading and merging a data from multiple policy files.
+      Patrole loads a data from each of the discovered policy files for a
+      service and merge the data from all files.
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/notes/remove-deprecated-rules-expected-error-codes-params-52071a83113934fd.yaml b/releasenotes/notes/remove-deprecated-rules-expected-error-codes-params-52071a83113934fd.yaml
new file mode 100644
index 0000000..16b2e03
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-rules-expected-error-codes-params-52071a83113934fd.yaml
@@ -0,0 +1,13 @@
+---
+upgrade:
+  - |
+    The following deprecated parameters in ``rbac_rule_validation.action``
+    decorator:
+
+    * ``rule``
+    * ``expected_error_code``
+
+    have been removed. Use the non-deprecated versions instead:
+
+    * ``rules``
+    * ``expected_error_codes``
diff --git a/releasenotes/notes/removed-keystone-policies-stein-feature-flag-6cfebbf64ed525d7.yaml b/releasenotes/notes/removed-keystone-policies-stein-feature-flag-6cfebbf64ed525d7.yaml
new file mode 100644
index 0000000..3bed287
--- /dev/null
+++ b/releasenotes/notes/removed-keystone-policies-stein-feature-flag-6cfebbf64ed525d7.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Added new feature flag called ``removed_keystone_policies_stein`` under
+    the configuration group ``[policy-feature-enabled]`` for skipping Keystone
+    tests whose policies were removed in Stein. This feature flag is currently
+    applied to credentials-related policies, e.g.:
+    identity:[create|update|get|delete]_credential
diff --git a/releasenotes/notes/requirements-authority-multi-role-support-0fe53fc49567e595.yaml b/releasenotes/notes/requirements-authority-multi-role-support-0fe53fc49567e595.yaml
new file mode 100644
index 0000000..ffbae0a
--- /dev/null
+++ b/releasenotes/notes/requirements-authority-multi-role-support-0fe53fc49567e595.yaml
@@ -0,0 +1,37 @@
+---
+features:
+  - |
+    The ``requirements_authority`` module now supports the following 3 cases:
+
+    * logical or operation of roles (existing functionality)
+    * logical and operation of roles (new functionality)
+    * logical not operation of roles (new functionality)
+
+    .. code-block:: yaml
+
+        <service_foo>:
+          <logical_or_example>:
+            - <allowed_role_1>
+            - <allowed_role_2>
+          <logical_and_example>:
+            - <allowed_role_3>, <allowed_role_4>
+        <service_bar>:
+          <logical_not_example>:
+            - <!disallowed_role_5>
+
+    Each item under ``logical_or_example`` is "logical OR"-ed together. Each
+    role in the comma-separated string under ``logical_and_example`` is
+    "logical AND"-ed together. And each item prefixed with "!" under
+    ``logical_not_example`` is "logical negated".
+
+    This allows for expressing many more complex cases using the
+    ``requirements_authority`` YAML syntax. For example, the policy rule
+    (i.e. what may exist in a ``policy.yaml`` file)::
+
+        "foo_rule: (role:a and not role:b) or role:c"
+
+    May now be expressed using the YAML syntax as::
+
+        foo_rule:
+            - a, !b
+            - c
diff --git a/releasenotes/notes/volume-type-encryption-policy-granularity-141ac283b9c0778e.yaml b/releasenotes/notes/volume-type-encryption-policy-granularity-141ac283b9c0778e.yaml
new file mode 100644
index 0000000..4aeb107
--- /dev/null
+++ b/releasenotes/notes/volume-type-encryption-policy-granularity-141ac283b9c0778e.yaml
@@ -0,0 +1,19 @@
+---
+features:
+  - |
+    Added new Cinder feature flag (``CONF.policy_feature_enabled.added_cinder_policies_stein``)
+    for the following newly introduced granular Cinder policies:
+
+    - ``volume_extension:volume_type_encryption:create``
+    - ``volume_extension:volume_type_encryption:get``
+    - ``volume_extension:volume_type_encryption:update``
+    - ``volume_extension:volume_type_encryption:delete``
+
+    The corresponding Patrole test cases are modified to support
+    the granularity.  The test cases also support backward
+    compatibility with the old single rule:
+    ``volume_extension:volume_type_encryption``
+
+    The ``rules`` parameter in ``rbac_rule_validation.action``
+    decorator now also accepts a list of callables; each callable
+    should return a policy action (str).
diff --git a/releasenotes/notes/yaml-policy-file-support-278d3edf64f98d69.yaml b/releasenotes/notes/yaml-policy-file-support-278d3edf64f98d69.yaml
new file mode 100644
index 0000000..e333377
--- /dev/null
+++ b/releasenotes/notes/yaml-policy-file-support-278d3edf64f98d69.yaml
@@ -0,0 +1,7 @@
+---
+features:
+- |
+  Patrole now supports parsing custom YAML policy files, the new policy file
+  extension since Ocata. The function ``_get_policy_data`` has been renamed to
+  ``get_rules`` and been changed to re-use ``oslo_policy.policy.Rules.load``
+  function.
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/setup.cfg b/setup.cfg
index 02ce831..77a039a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,20 +26,6 @@
 [upload_sphinx]
 upload-dir = doc/build/html
 
-[compile_catalog]
-directory = patrole/locale
-domain = patrole
-
-[update_catalog]
-domain = patrole
-output_dir = patrole/locale
-input_file = patrole/locale/patrole.pot
-
-[extract_messages]
-keywords = _ gettext ngettext l_ lazy_gettext
-mapping_file = babel.cfg
-output_file = patrole/locale/patrole.pot
-
 [build_releasenotes]
 all_files = 1
 build-dir = releasenotes/build
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..bc829d2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-minversion = 1.6
+minversion = 2.0
 envlist = pep8,py35,py27
 skipsdist = True
 
@@ -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