Merge "Add missing v3 token related testcases"
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index 1180836..a6f30e7 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -21,33 +21,61 @@
 RbacGroup = [
     cfg.StrOpt('rbac_test_role',
                default='admin',
-               help="The current RBAC role against which to run"
-                    " Patrole tests."),
+               help="""The current RBAC role against which to run Patrole
+tests."""),
     cfg.BoolOpt('enable_rbac',
                 default=True,
                 help="Enables RBAC tests."),
     cfg.BoolOpt('strict_policy_check',
                 default=False,
-                help="If true, throws RbacParsingException for"
-                     " policies which don't exist. If false, "
-                     "throws skipException."),
+                help="""If true, throws RbacParsingException for policies which
+don't exist or are not included in the service's policy file. If false, throws
+skipException."""),
     # 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
+    # other hosts. It may be possible to leverage the v3 identity policy API.
+    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.
+"""),
     cfg.StrOpt('cinder_policy_file',
                default='/etc/cinder/policy.json',
-               help="Location of the neutron policy file."),
+               help="""Location of the Cinder policy file. Assumed to be on
+the same host as Patrole.""",
+               deprecated_for_removal=True,
+               deprecated_reason="It is better to use `custom_policy_files` "
+                                 "which supports any OpenStack service."),
     cfg.StrOpt('glance_policy_file',
                default='/etc/glance/policy.json',
-               help="Location of the glance policy file."),
+               help="""Location of the Glance policy file. Assumed to be on
+the same host as Patrole.""",
+               deprecated_for_removal=True,
+               deprecated_reason="It is better to use `custom_policy_files` "
+                                 "which supports any OpenStack service."),
     cfg.StrOpt('keystone_policy_file',
                default='/etc/keystone/policy.json',
-               help="Location of the keystone policy file."),
+               help="""Location of the custom Keystone policy file. Assumed to
+be on the same host as Patrole.""",
+               deprecated_for_removal=True,
+               deprecated_reason="It is better to use `custom_policy_files` "
+                                 "which supports any OpenStack service."),
     cfg.StrOpt('neutron_policy_file',
                default='/etc/neutron/policy.json',
-               help="Location of the neutron policy file."),
+               help="""Location of the Neutron policy file. Assumed to be on
+the same host as Patrole.""",
+               deprecated_for_removal=True,
+               deprecated_reason="It is better to use `custom_policy_files` "
+                                 "which supports any OpenStack service."),
     cfg.StrOpt('nova_policy_file',
                default='/etc/nova/policy.json',
-               help="Location of the nova policy file."),
+               help="""Location of the custom Nova policy file. Assumed to be
+on the same host as Patrole.""",
+               deprecated_for_removal=True,
+               deprecated_reason="It is better to use `custom_policy_files` "
+                                 "which supports any OpenStack service."),
     cfg.BoolOpt('test_custom_requirements',
                 default=False,
                 help="""
diff --git a/patrole_tempest_plugin/rbac_exceptions.py b/patrole_tempest_plugin/rbac_exceptions.py
index aa3135e..5ccb216 100644
--- a/patrole_tempest_plugin/rbac_exceptions.py
+++ b/patrole_tempest_plugin/rbac_exceptions.py
@@ -16,25 +16,25 @@
 from tempest.lib import exceptions
 
 
-class RbacActionFailed (exceptions.ClientRestClientException):
+class RbacActionFailed(exceptions.ClientRestClientException):
     message = "Rbac action failed"
 
 
-class RbacResourceSetupFailed (exceptions.TempestException):
+class RbacResourceSetupFailed(exceptions.TempestException):
     message = "Rbac resource setup failed"
 
 
-class RbacOverPermission (exceptions.TempestException):
+class RbacOverPermission(exceptions.TempestException):
     message = "Action performed that should not be permitted"
 
 
-class RbacInvalidService (exceptions.TempestException):
+class RbacInvalidService(exceptions.TempestException):
     message = "Attempted to test an invalid service"
 
 
-class RbacParsingException (exceptions.TempestException):
+class RbacParsingException(exceptions.TempestException):
     message = "Attempted to test an invalid policy file or action"
 
 
-class RbacInvalidErrorCode (exceptions.TempestException):
+class RbacInvalidErrorCode(exceptions.TempestException):
     message = "Unsupported error code passed in test"
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 17a626c..41871cf 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -58,26 +58,27 @@
         the custom policy file over the default policy implementation is
         prioritized.
 
-        :param project_id: type uuid
-        :param user_id: type uuid
-        :param service: type string
-        :param path: type string
+        :param uuid project_id: project_id of object performing API call
+        :param uuid user_id: user_id of object performing API call
+        :param string service: service of the policy file
+        :param dict extra_target_data: dictionary containing additional object
+            data needed by oslo.policy to validate generic checks
         """
 
         if extra_target_data is None:
             extra_target_data = {}
 
-        # First check if the service is valid.
         self.validate_service(service)
 
-        # Use default path in /etc/<service_name/policy.json if no path
-        # is provided.
-        path = getattr(CONF.rbac, '%s_policy_file' % str(service), None)
-        if not path:
-            LOG.info("No config option found for %s,"
-                     " using default path", str(service))
-            path = os.path.join('/etc', service, 'policy.json')
-        self.path = path
+        # Prioritize dynamically searching for policy files over relying on
+        # deprecated service-specific policy file locations.
+        if CONF.rbac.custom_policy_files:
+            self.discover_policy_files()
+            self.path = self.policy_files.get(service)
+        else:
+            self.path = getattr(CONF.rbac, '%s_policy_file' % str(service),
+                                None)
+
         self.rules = policy.Rules.load(self._get_policy_data(service),
                                        'default')
         self.project_id = project_id
@@ -86,7 +87,7 @@
 
     @classmethod
     def validate_service(cls, service):
-        """Validate whether the service passed to ``init`` exists."""
+        """Validate whether the service passed to ``__init__`` exists."""
         service = service.lower().strip() if service else None
 
         # Cache the list of available services in memory to avoid needlessly
@@ -102,6 +103,19 @@
             raise rbac_exceptions.RbacInvalidService(
                 "%s is NOT a valid service." % 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
+        # out of the potential paths in ``CONF.rbac.custom_policy_files``.
+        if not hasattr(cls, 'policy_files'):
+            cls.policy_files = {}
+            for service in cls.available_services:
+                for candidate_path in CONF.rbac.custom_policy_files:
+                    if os.path.isfile(candidate_path % service):
+                        cls.policy_files.setdefault(service,
+                                                    candidate_path % service)
+
     def allowed(self, rule_name, role):
         is_admin_context = self._is_admin_context(role)
         is_allowed = self._allowed(
@@ -115,8 +129,8 @@
         mgr_policy_data = {}
         policy_data = {}
 
-        # Check whether policy file exists.
-        if os.path.isfile(self.path):
+        # Check whether policy file exists and attempt to read it.
+        if self.path and os.path.isfile(self.path):
             try:
                 with open(self.path, 'r') as policy_file:
                     file_policy_data = policy_file.read()
@@ -158,8 +172,11 @@
         elif mgr_policy_data:
             policy_data = mgr_policy_data
         else:
-            error_message = 'Policy file for {0} service neither found in '\
-                            'code nor at {1}.'.format(service, self.path)
+            error_message = (
+                'Policy file for {0} service neither found in code nor at {1}.'
+                .format(service, [loc % service for loc in
+                                  CONF.rbac.custom_policy_files])
+            )
             raise rbac_exceptions.RbacParsingException(error_message)
 
         try:
diff --git a/patrole_tempest_plugin/tests/api/compute/rbac_base.py b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
index 0be42e7..0a3a1f5 100644
--- a/patrole_tempest_plugin/tests/api/compute/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
@@ -37,7 +37,9 @@
         super(BaseV2ComputeRbacTest, cls).setup_clients()
         cls.auth_provider = cls.os_primary.auth_provider
         cls.rbac_utils = rbac_utils.RbacUtils(cls)
+
         cls.hosts_client = cls.os_primary.hosts_client
+        cls.tenant_usages_client = cls.os_primary.tenant_usages_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_attach_interfaces_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_attach_interfaces_rbac.py
deleted file mode 100644
index 1ad8861..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_attach_interfaces_rbac.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#    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 waiters
-from tempest import config
-from tempest.lib.common.utils import test_utils
-from tempest.lib import decorators
-from tempest import test
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-CONF = config.CONF
-
-
-class AttachInterfacesRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(AttachInterfacesRbacTest, cls).skip_checks()
-        if not test.is_extension_enabled('os-attach-interfaces', 'compute'):
-            msg = ("%s skipped as os-attach-interfaces not "
-                   "enabled." % cls.__name__)
-            raise cls.skipException(msg)
-        if not CONF.compute_feature_enabled.interface_attach:
-            raise cls.skipException(
-                "%s skipped as interface attachment is not available"
-                % cls.__name__)
-        if not CONF.service_available.neutron:
-            raise cls.skipException(
-                '%s skipped as Neutron is required' % cls.__name__)
-
-    @classmethod
-    def setup_credentials(cls):
-        cls.prepare_instance_network()
-        super(AttachInterfacesRbacTest, cls).setup_credentials()
-
-    @classmethod
-    def resource_setup(cls):
-        super(AttachInterfacesRbacTest, cls).resource_setup()
-        cls.server = cls.create_test_server(wait_until='ACTIVE')
-
-    def _attach_interface_to_server(self):
-        interface = self.interfaces_client.create_interface(
-            self.server['id'])['interfaceAttachment']
-        waiters.wait_for_interface_status(
-            self.interfaces_client, self.server['id'], interface['port_id'],
-            'ACTIVE')
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.os_admin.interfaces_client.delete_interface,
-            self.server['id'], interface['port_id'])
-        return interface
-
-    @decorators.idempotent_id('ddf53cb6-4a0a-4e5a-91e3-6c32aaa3b9b6')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-attach-interfaces")
-    def test_list_interfaces(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.interfaces_client.list_interfaces(
-            self.server['id'])['interfaceAttachments']
-
-    @decorators.idempotent_id('d2d3a24d-4738-4bce-a287-36d664746cde')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-attach-interfaces:create")
-    def test_create_interface(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self._attach_interface_to_server()
-
-    @decorators.idempotent_id('55b05692-ed44-4608-a84c-cd4219c82799')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-attach-interfaces:delete")
-    def test_delete_interface(self):
-        interface = self._attach_interface_to_server()
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.interfaces_client.delete_interface(self.server['id'],
-                                                interface['port_id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_config_drive_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_config_drive_rbac.py
deleted file mode 100644
index 794e0d2..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_config_drive_rbac.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#    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.lib import decorators
-from tempest import test
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-
-class ConfigDriveRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def setup_clients(cls):
-        super(ConfigDriveRbacTest, cls).setup_clients()
-        cls.client = cls.servers_client
-
-    @classmethod
-    def skip_checks(cls):
-        super(ConfigDriveRbacTest, cls).skip_checks()
-        if not test.is_extension_enabled('os-config-drive', 'compute'):
-            msg = "%s skipped as os-config-drive extension not enabled." \
-                  % cls.__name__
-            raise cls.skipException(msg)
-
-    @decorators.idempotent_id('55c62ef7-b72b-4970-acc6-05b0a4316e5d')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-config-drive")
-    def test_create_test_server_with_config_drive(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        # NOTE(felipemonteiro): This policy action is always enforced,
-        # regardless whether the config_drive flag is set to true or false.
-        # However, it has been explicitly set to true below, in case that this
-        # behavior ever changes in the future.
-        self.create_test_server(config_drive=True)
diff --git a/patrole_tempest_plugin/tests/api/compute/test_ips_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_ips_rbac.py
deleted file mode 100644
index c42459a..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_ips_rbac.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#    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 import config
-from tempest.lib import decorators
-from tempest import test
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-CONF = config.CONF
-
-
-class IpsRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(IpsRbacTest, cls).skip_checks()
-        if not test.is_extension_enabled('os-ips', 'compute'):
-            msg = "%s skipped as os-ips not enabled." % cls.__name__
-            raise cls.skipException(msg)
-        if not CONF.service_available.neutron:
-            raise cls.skipException(
-                '%s skipped as Neutron is required' % cls.__name__)
-
-    @classmethod
-    def setup_credentials(cls):
-        cls.prepare_instance_network()
-        super(IpsRbacTest, cls).setup_credentials()
-
-    @classmethod
-    def resource_setup(cls):
-        super(IpsRbacTest, cls).resource_setup()
-        cls.server = cls.create_test_server(wait_until='ACTIVE')
-
-    @decorators.idempotent_id('6886d360-0d86-4760-b1a3-882d81fbebcc')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:ips:index")
-    def test_list_addresses(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.list_addresses(self.server['id'])['addresses']
-
-    @decorators.idempotent_id('fa43e7e5-0db9-48eb-9c6b-c11eb766b8e4')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:ips:show")
-    def test_list_addresses_by_network(self):
-        addresses = self.servers_client.list_addresses(
-            self.server['id'])['addresses']
-        address = next(iter(addresses))
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.list_addresses_by_network(
-            self.server['id'], address)[address]
diff --git a/patrole_tempest_plugin/tests/api/compute/test_multinic_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_multinic_rbac.py
deleted file mode 100644
index e409269..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_multinic_rbac.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# 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 import config
-from tempest.lib import decorators
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-CONF = config.CONF
-
-
-class MultinicRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(MultinicRbacTest, cls).skip_checks()
-        if not CONF.service_available.neutron:
-            raise cls.skipException("Neutron is required")
-        if not CONF.compute_feature_enabled.interface_attach:
-            raise cls.skipException("Interface attachment is not available.")
-
-    @classmethod
-    def setup_credentials(cls):
-        # This test class requires network and subnet
-        cls.set_network_resources(network=True, subnet=True)
-        super(MultinicRbacTest, cls).setup_credentials()
-
-    @classmethod
-    def resource_setup(cls):
-        super(MultinicRbacTest, cls).resource_setup()
-        cls.server = cls.create_test_server(wait_until='ACTIVE')
-
-    @rbac_rule_validation.action(
-        service="nova", rule="os_compute_api:os-multinic")
-    @decorators.idempotent_id('bd3e2c74-130a-40f0-8085-124d93fe67da')
-    def test_add_fixed_ip(self):
-        """Add fixed IP to server."""
-        interfaces = (self.interfaces_client.list_interfaces(self.server['id'])
-                      ['interfaceAttachments'])
-        if interfaces:
-            network_id = interfaces[0]['net_id']
-        else:
-            network_id = self.interfaces_client.create_interface(
-                self.server['id'])['interfaceAttachment']['net_id']
-
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.add_fixed_ip(self.server['id'],
-                                         networkId=network_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 ffbc530..b115ab5 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
@@ -155,7 +155,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._test_stop_server()
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:servers:start")
@@ -165,7 +165,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._test_start_server()
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:servers:resize")
@@ -176,7 +176,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._test_resize_server(self.flavor_ref_alt)
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:servers:revert_resize")
@@ -188,7 +188,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._test_revert_resize_server()
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:servers:confirm_resize")
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 842d8b1..a7f16a1 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
@@ -18,7 +18,6 @@
 from tempest.common import waiters
 from tempest import config
 from tempest.lib import decorators
-from tempest import test
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.compute import rbac_base as base
@@ -63,7 +62,7 @@
             if host_record['service'] == 'compute'
         ]
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
                           'Cold migration not available.')
     @rbac_rule_validation.action(
@@ -77,7 +76,7 @@
         waiters.wait_for_server_status(self.admin_servers_client,
                                        server['id'], 'VERIFY_RESIZE')
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
                           'Live migration feature is not enabled.')
     @rbac_rule_validation.action(
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 d4c856d..c1bf9f7 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
@@ -18,10 +18,12 @@
 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 tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest import test
 
+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
 
@@ -83,7 +85,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.inject_network_info(self.server_id)
 
-    @test.attr(type=['slow'])
+    @decorators.attr(type=['slow'])
     @test.requires_ext(extension='os-admin-actions', service='compute')
     @rbac_rule_validation.action(
         service="nova",
@@ -112,6 +114,33 @@
         waiters.wait_for_server_status(
             self.os_admin.servers_client, self.server_id, 'ACTIVE')
 
+    @test.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")
+    def test_list_servers_with_details_config_drive(self):
+        """Test list servers with config_drive property in response body."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        body = self.servers_client.list_servers(detail=True)['servers']
+        # If the first server contains "config_drive", then all the others do.
+        if 'config_drive' not in body[0]:
+            raise rbac_exceptions.RbacActionFailed(
+                '"config_drive" attribute not found in response body.')
+
+    @test.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")
+    def test_show_server_config_drive(self):
+        """Test show server with config_drive property in response body."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        body = self.servers_client.show_server(self.server_id)['server']
+        if 'config_drive' not in body:
+            raise rbac_exceptions.RbacActionFailed(
+                '"config_drive" attribute not found in response body.')
+
     @test.requires_ext(extension='os-deferred-delete', service='compute')
     @decorators.idempotent_id('189bfed4-1e6d-475c-bb8c-d57e60895391')
     @rbac_rule_validation.action(
@@ -210,12 +239,34 @@
     def test_show_server_usage(self):
         """Test show server usage, part of os-server-usage.
 
-        TODO(felipemonteiro): Once multiple policy test is supported, this
+        TODO(felipemonteiro): Once multiple policy testing is supported, this
         test can be combined with the generic test for showing a server.
         """
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.show_server(self.server_id)
 
+    @test.requires_ext(extension='os-simple-tenant-usage', service='compute')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="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."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.tenant_usages_client.list_tenant_usages()
+
+    @test.requires_ext(extension='os-simple-tenant-usage', service='compute')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="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."""
+        tenant_id = self.auth_provider.credentials.tenant_id
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.tenant_usages_client.show_tenant_usage(tenant_id=tenant_id)
+
     @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
                           "Suspend compute feature is not available.")
     @decorators.idempotent_id('b775930f-237c-431c-83ae-d33ed1b9700b')
@@ -246,3 +297,149 @@
         self.servers_client.resume_server(self.server_id)
         waiters.wait_for_server_status(
             self.os_admin.servers_client, self.server_id, 'ACTIVE')
+
+
+class MiscPolicyActionsNetworkRbacTest(rbac_base.BaseV2ComputeRbacTest):
+    """Test multiple policy actions that require a server to be created.
+
+    Only applies to:
+      * policy "families" that require server creation
+      * small policy "families" -- i.e. containing one to three policies
+      * tests that require network resources
+    """
+
+    @classmethod
+    def skip_checks(cls):
+        super(MiscPolicyActionsNetworkRbacTest, cls).skip_checks()
+        # All tests below require Neutron availability.
+        if not CONF.service_available.neutron:
+            raise cls.skipException(
+                '%s skipped as Neutron is required' % cls.__name__)
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(MiscPolicyActionsNetworkRbacTest, cls).setup_credentials()
+
+    @classmethod
+    def resource_setup(cls):
+        super(MiscPolicyActionsNetworkRbacTest, cls).resource_setup()
+        cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+    def _attach_interface_to_server(self):
+        interface = self.interfaces_client.create_interface(
+            self.server['id'])['interfaceAttachment']
+        waiters.wait_for_interface_status(
+            self.os_admin.interfaces_client, self.server['id'],
+            interface['port_id'], 'ACTIVE')
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.interfaces_client.delete_interface,
+            self.server['id'], interface['port_id'])
+        return interface
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+                          "Interface attachment is not available.")
+    @test.requires_ext(extension='os-attach-interfaces', service='compute')
+    @decorators.idempotent_id('ddf53cb6-4a0a-4e5a-91e3-6c32aaa3b9b6')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-attach-interfaces")
+    def test_list_interfaces(self):
+        """Test list interfaces, part of os-attach-interfaces."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.interfaces_client.list_interfaces(
+            self.server['id'])['interfaceAttachments']
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+                          "Interface attachment is not available.")
+    @test.requires_ext(extension='os-attach-interfaces', service='compute')
+    @decorators.idempotent_id('d2d3a24d-4738-4bce-a287-36d664746cde')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-attach-interfaces:create")
+    def test_create_interface(self):
+        """Test create interface, part of os-attach-interfaces."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self._attach_interface_to_server()
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+                          "Interface attachment is not available.")
+    @test.requires_ext(extension='os-attach-interfaces', service='compute')
+    @decorators.idempotent_id('55b05692-ed44-4608-a84c-cd4219c82799')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="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()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.interfaces_client.delete_interface(self.server['id'],
+                                                interface['port_id'])
+
+    @decorators.idempotent_id('6886d360-0d86-4760-b1a3-882d81fbebcc')
+    @test.requires_ext(extension='os-ips', service='compute')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:ips:index")
+    def test_list_addresses(self):
+        """Test list server addresses, part of ips policy family."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.list_addresses(self.server['id'])['addresses']
+
+    @decorators.idempotent_id('fa43e7e5-0db9-48eb-9c6b-c11eb766b8e4')
+    @test.requires_ext(extension='os-ips', service='compute')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="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'])[
+            'addresses']
+        address = next(iter(addresses))
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.list_addresses_by_network(
+            self.server['id'], address)[address]
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+                          "Interface attachment is not available.")
+    @test.requires_ext(extension='os-multinic', service='compute')
+    @rbac_rule_validation.action(
+        service="nova", rule="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."""
+        interfaces = (self.interfaces_client.list_interfaces(self.server['id'])
+                      ['interfaceAttachments'])
+        if interfaces:
+            network_id = interfaces[0]['net_id']
+        else:
+            network_id = self.interfaces_client.create_interface(
+                self.server['id'])['interfaceAttachment']['net_id']
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.add_fixed_ip(self.server['id'],
+                                         networkId=network_id)
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="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.
+
+        If Neutron is available, then call the API and expect it to fail
+        with a 400 BadRequest (policy enforcement is done before that happens).
+
+        For more information, see:
+        https://developer.openstack.org/api-ref/compute/#servers-virtual-interfaces-servers-os-virtual-interfaces-deprecated
+        """
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        if CONF.service_available.neutron:
+            msg = "Listing virtual interfaces is not supported by this cloud."
+            with self.assertRaisesRegex(lib_exc.BadRequest, msg):
+                self.servers_client.list_virtual_interfaces(self.server['id'])
+        else:
+            self.servers_client.list_virtual_interfaces(self.server['id'])
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 c6ac1c5..f5d9dc0 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
@@ -219,7 +219,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.list_security_groups_by_server(self.server['id'])
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type=["slow"])
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:os-security-groups")
@@ -233,7 +233,7 @@
                         self.servers_client.remove_security_group,
                         self.server['id'], name=sg_name)
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type=["slow"])
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:os-security-groups")
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py
deleted file mode 100644
index 1480444..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# 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 import config
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base as base
-
-CONF = config.CONF
-
-
-class ServerVirtualInterfacesRbacTest(base.BaseV2ComputeRbacTest):
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-virtual-interfaces")
-    @decorators.idempotent_id('fc719ae3-0f73-4689-8378-1b841f0f2818')
-    def test_list_virtual_interfaces(self):
-        server = self.create_test_server(wait_until='ACTIVE')
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-
-        if CONF.service_available.neutron:
-            msg = "Listing virtual interfaces is not supported by this cloud."
-            with self.assertRaisesRegex(exceptions.BadRequest, msg):
-                self.servers_client.list_virtual_interfaces(server['id'])
-        else:
-            self.servers_client.list_virtual_interfaces(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 98dbee5..943cb69 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
@@ -17,7 +17,6 @@
 from tempest import config
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
-from tempest import test
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.compute import rbac_base
@@ -30,7 +29,7 @@
     @classmethod
     def setup_clients(cls):
         super(ServerVolumeAttachmentRbacTest, cls).setup_clients()
-        cls.volumes_client = cls.os_primary.volumes_client
+        cls.volumes_client = cls.os_primary.volumes_client_latest
 
     @classmethod
     def skip_checks(cls):
@@ -68,11 +67,12 @@
     @decorators.idempotent_id('997df9c2-6e54-47b6-ab74-e4fdb500f385')
     def test_show_volume_attachment(self):
         attachment = self.attach_volume(self.server, self.volume)
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.show_volume_attachment(
             self.server['id'], attachment['id'])
 
-    @test.attr(type='slow')
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:os-volumes-attachments:update")
@@ -80,6 +80,7 @@
     def test_update_volume_attachment(self):
         attachment = self.attach_volume(self.server, self.volume)
         alt_volume = self.create_volume()
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.update_attached_volume(
             self.server['id'], attachment['id'], volumeId=alt_volume['id'])
@@ -102,6 +103,7 @@
     @decorators.idempotent_id('12b03e90-d087-46af-9c4d-507d021c4984')
     def test_delete_volume_attachment(self):
         self.attach_volume(self.server, self.volume)
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.detach_volume(self.server['id'], self.volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
diff --git a/patrole_tempest_plugin/tests/api/compute/test_simple_tenant_usage_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_simple_tenant_usage_rbac.py
deleted file mode 100644
index ae2a0fd..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_simple_tenant_usage_rbac.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#    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.lib import decorators
-from tempest import test
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-
-class SimpleTenantUsageRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def setup_clients(cls):
-        super(SimpleTenantUsageRbacTest, cls).setup_clients()
-        cls.tenant_usages_client = cls.os_primary.tenant_usages_client
-
-    @classmethod
-    def skip_checks(cls):
-        super(SimpleTenantUsageRbacTest, cls).skip_checks()
-        if not test.is_extension_enabled('os-simple-tenant-usage', 'compute'):
-            msg = ("%s skipped as os-simple-tenant-usage not "
-                   "enabled." % cls.__name__)
-            raise cls.skipException(msg)
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-simple-tenant-usage:list")
-    @decorators.idempotent_id('2aef094f-0452-4df6-a66a-0ec22a92b16e')
-    def test_simple_tenant_usage_list(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.tenant_usages_client.list_tenant_usages()
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-simple-tenant-usage:show")
-    @decorators.idempotent_id('fe7eacda-15c4-4bf7-93ef-1091c4546a9d')
-    def test_simple_tenant_usage_show(self):
-        # A server must be created in order for usage activity to exist; else
-        # the validation method in the API call throws an error.
-        self.create_test_server(wait_until='ACTIVE')['id']
-        tenant_id = self.auth_provider.credentials.tenant_id
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.tenant_usages_client.show_tenant_usage(tenant_id=tenant_id)
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 b775872..0935c95 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_volume_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_volume_rbac.py
@@ -34,6 +34,11 @@
     max_microversion = '2.35'
 
     @classmethod
+    def setup_clients(cls):
+        super(VolumeRbacTest, cls).setup_clients()
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
+
+    @classmethod
     def resource_setup(cls):
         super(VolumeRbacTest, cls).resource_setup()
         cls.volume = cls.create_volume()
@@ -56,7 +61,7 @@
             size=CONF.volume.volume_size)['volume']
         # Use the admin volumes client to wait, because waiting involves
         # calling show API action which enforces a different policy.
-        waiters.wait_for_volume_resource_status(self.os_admin.volumes_client,
+        waiters.wait_for_volume_resource_status(self.admin_volumes_client,
                                                 volume['id'], 'available')
         # Use non-deprecated volumes_client for deletion.
         self.addCleanup(self.volumes_client.delete_volume, volume['id'])
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 e7b73b6..00c9f55 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
@@ -73,7 +73,7 @@
 
     @decorators.idempotent_id('5c16368d-1485-4c28-9803-db3fa3510623')
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:check_endpoint_group")
+                                 rule="identity:get_endpoint_group")
     def test_check_endpoint_group(self):
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.endpoint_groups_client.check_endpoint_group(
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 c25af25..0532b2d 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
@@ -16,7 +16,6 @@
 from tempest import config
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
-from tempest import test
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.identity import rbac_base
@@ -77,7 +76,7 @@
                               trustee_user_id=self.trustee_user_id)
 
     @decorators.idempotent_id('bd72d22a-6e11-4840-bd93-17b382e7f0e0')
-    @test.attr(type=['negative'])
+    @decorators.attr(type=['negative'])
     @rbac_rule_validation.action(
         service="keystone",
         rule="identity:create_trust",
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 b666a2d..e8e3ef8 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
@@ -33,6 +33,7 @@
     def setup_clients(cls):
         super(VolumesActionsRbacTest, cls).setup_clients()
         cls.admin_image_client = cls.os_admin.image_client_v2
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
 
     @classmethod
     def resource_setup(cls):
@@ -54,7 +55,7 @@
             server['id'], volumeId=volume_id,
             device='/dev/%s' % CONF.compute.volume_device_name)
         waiters.wait_for_volume_resource_status(
-            self.os_admin.volumes_client, volume_id, 'in-use')
+            self.admin_volumes_client, volume_id, 'in-use')
         self.addCleanup(self._detach_volume, volume_id)
 
     def _detach_volume(self, volume_id=None):
@@ -63,7 +64,7 @@
 
         self.volumes_client.detach_volume(volume_id)
         waiters.wait_for_volume_resource_status(
-            self.os_admin.volumes_client, volume_id, 'available')
+            self.admin_volumes_client, volume_id, 'available')
 
     @test.services('compute')
     @rbac_rule_validation.action(service="cinder", rule="volume:attach")
@@ -73,8 +74,8 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._attach_volume(server)
 
-    @test.attr(type=["slow"])
     @test.services('compute')
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder", rule="volume:detach")
     @decorators.idempotent_id('5a042f6a-688b-42e6-a02e-fe5c47b89b07')
     def test_detach_volume_from_instance(self):
@@ -84,7 +85,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._detach_volume()
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type=["slow"])
     @test.services('image')
     @rbac_rule_validation.action(
         service="cinder",
@@ -106,7 +107,7 @@
                         image_id)
         waiters.wait_for_image_status(self.admin_image_client, image_id,
                                       'active')
-        waiters.wait_for_volume_resource_status(self.os_admin.volumes_client,
+        waiters.wait_for_volume_resource_status(self.admin_volumes_client,
                                                 self.volume['id'], 'available')
 
     @rbac_rule_validation.action(service="cinder",
@@ -161,7 +162,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.volumes_client.retype_volume(volume['id'], new_type=vol_type)
         waiters.wait_for_volume_retype(
-            self.os_admin.volumes_client, volume['id'], vol_type)
+            self.admin_volumes_client, volume['id'], vol_type)
 
     @rbac_rule_validation.action(
         service="cinder",
@@ -204,7 +205,7 @@
         self.volumes_client.force_detach_volume(
             volume['id'], connector=None,
             attachment_id=attachment['attachment_id'])
-        waiters.wait_for_volume_resource_status(self.os_admin.volumes_client,
+        waiters.wait_for_volume_resource_status(self.admin_volumes_client,
                                                 volume['id'], 'available')
 
 
@@ -221,8 +222,9 @@
     def setup_clients(cls):
         super(VolumesActionsV310RbacTest, cls).setup_clients()
         cls.admin_image_client = cls.os_admin.image_client_v2
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type=["slow"])
     @test.services('image')
     @rbac_rule_validation.action(
         service="cinder",
@@ -243,7 +245,7 @@
                         image_id)
         waiters.wait_for_image_status(self.admin_image_client, image_id,
                                       'active')
-        waiters.wait_for_volume_resource_status(self.os_admin.volumes_client,
+        waiters.wait_for_volume_resource_status(self.admin_volumes_client,
                                                 volume['id'], 'available')
 
 
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 6e9812b..851d468 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
@@ -29,7 +29,7 @@
     @classmethod
     def setup_clients(cls):
         super(VolumeQuotasRbacTest, cls).setup_clients()
-        cls.quotas_client = cls.os_primary.volume_quotas_client
+        cls.quotas_client = cls.os_primary.volume_quotas_v2_client
 
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume_extension:quotas:show")
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 212482c..405d02b 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
@@ -23,13 +23,11 @@
 
 class VolumesTransfersRbacTest(rbac_base.BaseVolumeRbacTest):
 
-    credentials = ['primary', 'admin']
-
     @classmethod
     def setup_clients(cls):
         super(VolumesTransfersRbacTest, cls).setup_clients()
         cls.transfers_client = cls.os_primary.volume_transfers_v2_client
-        cls.adm_volumes_client = cls.os_admin.volumes_v2_client
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
 
     @classmethod
     def resource_setup(cls):
@@ -43,7 +41,7 @@
         test_utils.call_and_ignore_notfound_exc(
             self.transfers_client.delete_volume_transfer, transfer['id'])
         waiters.wait_for_volume_resource_status(
-            self.adm_volumes_client, self.volume['id'], 'available')
+            self.admin_volumes_client, self.volume['id'], 'available')
 
     def _create_transfer(self):
         transfer = self.transfers_client.create_volume_transfer(
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 97eaab7..210901c 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
@@ -54,23 +54,26 @@
                 self.vol_type['id'], self.spec_key)
 
     @decorators.idempotent_id('76c36be2-2b6c-4acf-9aac-c9dc5c17cdbe')
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:types_extra_specs")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:types_extra_specs:index")
     def test_list_volume_types_extra_specs(self):
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.volume_types_client.list_volume_types_extra_specs(
             self.vol_type['id'])['extra_specs']
 
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:types_extra_specs")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:types_extra_specs:create")
     @decorators.idempotent_id('eea40251-990b-49b0-99ae-10e4585b479b')
     def test_create_volume_type_extra_specs(self):
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self._create_volume_type_extra_specs(ignore_not_found=True)
 
     @decorators.idempotent_id('e2dcc9c6-2fef-431d-afaf-92b45bc76d1a')
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:types_extra_specs")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:types_extra_specs:show")
     def test_show_volume_type_extra_specs(self):
         self._create_volume_type_extra_specs()
 
@@ -79,8 +82,9 @@
             self.vol_type['id'], self.spec_key)
 
     @decorators.idempotent_id('93001912-f938-41c7-8787-62dc7010fd52')
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:types_extra_specs")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:types_extra_specs:delete")
     def test_delete_volume_type_extra_specs(self):
         self._create_volume_type_extra_specs(ignore_not_found=True)
 
@@ -89,8 +93,9 @@
             self.vol_type['id'], self.spec_key)
 
     @decorators.idempotent_id('0a444437-7402-4fbe-a18a-93af2ee00618')
-    @rbac_rule_validation.action(service="cinder",
-                                 rule="volume_extension:types_extra_specs")
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="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_volumes_backup_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
index e6944cc..01c1b08 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
@@ -21,7 +21,6 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
-from tempest import test
 
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.volume import rbac_base
@@ -41,6 +40,11 @@
         if not CONF.volume_feature_enabled.backup:
             raise cls.skipException("Cinder backup feature disabled")
 
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesBackupsRbacTest, cls).setup_clients()
+        cls.admin_backups_client = cls.os_admin.backups_v2_client
+
     def _decode_url(self, backup_url):
         return json.loads(base64.decode_as_text(backup_url))
 
@@ -53,7 +57,7 @@
         backup.update(changes)
         return self._encode_backup(backup)
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:create")
     @decorators.idempotent_id('6887ec94-0bcf-4ab7-b30f-3808a4b5a2a5')
@@ -61,7 +65,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.create_backup(volume_id=self.volume['id'])
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:get")
     @decorators.idempotent_id('abd92bdd-b0fb-4dc4-9cfc-de9e968f8c8a')
@@ -95,21 +99,22 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.backups_client.reset_backup_status(backup_id=backup['id'],
                                                 status='error')
-        waiters.wait_for_volume_resource_status(self.os_admin.backups_client,
+        waiters.wait_for_volume_resource_status(self.admin_backups_client,
                                                 backup['id'], 'error')
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:restore")
     @decorators.idempotent_id('9c794bf9-2446-4f41-8fe0-80b71e757f9d')
     def test_restore_backup(self):
         backup = self.create_backup(volume_id=self.volume['id'])
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         restore = self.backups_client.restore_backup(backup['id'])['restore']
         waiters.wait_for_volume_resource_status(
-            self.os_admin.backups_client, restore['backup_id'], 'available')
+            self.admin_backups_client, restore['backup_id'], 'available')
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:delete")
     @decorators.idempotent_id('d5d0c6a2-413d-437e-a73f-4bf2b41a20ed')
@@ -121,7 +126,7 @@
             volume_id=self.volume['id'])['backup']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.backups_client.delete_backup, backup['id'])
-        waiters.wait_for_volume_resource_status(self.os_admin.backups_client,
+        waiters.wait_for_volume_resource_status(self.admin_backups_client,
                                                 backup['id'], 'available')
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
@@ -129,7 +134,7 @@
         # Wait for deletion so error isn't thrown during clean up.
         self.backups_client.wait_for_resource_deletion(backup['id'])
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:backup-export")
     @decorators.idempotent_id('e984ec8d-e8eb-485c-98bc-f1856020303c')
@@ -138,7 +143,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.backups_client.export_backup(backup['id'])['backup-record']
 
-    @test.attr(type=["slow"])
+    @decorators.attr(type='slow')
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:backup-import")
     @decorators.idempotent_id('1e70f039-4556-44cc-9cc1-edf2b7ed648b')
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 5709669..971e079 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
@@ -26,6 +26,11 @@
 class VolumesExtendRbacTest(rbac_base.BaseVolumeRbacTest):
 
     @classmethod
+    def setup_clients(cls):
+        super(VolumesExtendRbacTest, cls).setup_clients()
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
+
+    @classmethod
     def resource_setup(cls):
         super(VolumesExtendRbacTest, cls).resource_setup()
         # Create a test shared volume for tests
@@ -40,7 +45,7 @@
         self.volumes_client.extend_volume(self.volume['id'],
                                           new_size=extend_size)
         waiters.wait_for_volume_resource_status(
-            self.os_admin.volumes_client, self.volume['id'], 'available')
+            self.admin_volumes_client, self.volume['id'], 'available')
 
 
 class VolumesExtendV3RbacTest(VolumesExtendRbacTest):
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 8e71ee8..bea3a46 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
@@ -42,6 +42,7 @@
     def setup_clients(cls):
         super(VolumesManageRbacTest, cls).setup_clients()
         cls.volume_manage_client = cls.os_primary.volume_manage_v2_client
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
 
     def _manage_volume(self, org_volume):
         # Manage volume
@@ -59,14 +60,14 @@
         new_volume_id = self.volume_manage_client.manage_volume(
             **new_volume_ref)['volume']['id']
 
-        waiters.wait_for_volume_resource_status(self.os_admin.volumes_client,
+        waiters.wait_for_volume_resource_status(self.admin_volumes_client,
                                                 new_volume_id, 'available')
         self.addCleanup(self.delete_volume,
                         self.volumes_client, new_volume_id)
 
     def _unmanage_volume(self, volume):
         self.volumes_client.unmanage_volume(volume['id'])
-        self.volumes_client.wait_for_resource_deletion(volume['id'])
+        self.admin_volumes_client.wait_for_resource_deletion(volume['id'])
 
     @rbac_rule_validation.action(
         service="cinder",
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 422a3db..13ced96 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
@@ -32,6 +32,11 @@
             raise cls.skipException("Cinder volume snapshots are disabled")
 
     @classmethod
+    def setup_clients(cls):
+        super(VolumesSnapshotRbacTest, cls).setup_clients()
+        cls.admin_snapshots_client = cls.os_admin.snapshots_v2_client
+
+    @classmethod
     def resource_setup(cls):
         super(VolumesSnapshotRbacTest, cls).resource_setup()
         # Create a test shared volume for tests
@@ -77,8 +82,7 @@
         self.snapshots_client.update_snapshot(
             self.snapshot['id'], **params)['snapshot']
         waiters.wait_for_volume_resource_status(
-            self.os_admin.snapshots_client,
-            self.snapshot['id'], 'available')
+            self.admin_snapshots_client, self.snapshot['id'], 'available')
 
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:get_all_snapshots")
@@ -99,7 +103,7 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         # Delete the snapshot
         self.snapshots_client.delete_snapshot(temp_snapshot['id'])
-        self.os_admin.snapshots_client.wait_for_resource_deletion(
+        self.admin_snapshots_client.wait_for_resource_deletion(
             temp_snapshot['id'])
 
 
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
index 09de6bf..e3a4429 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -28,12 +28,21 @@
 
 class RbacPolicyTest(base.TestCase):
 
+    services = {
+        'services': [
+            {'name': 'custom_rbac_policy'},
+            {'name': 'admin_rbac_policy'},
+            {'name': 'alt_admin_rbac_policy'},
+            {'name': 'tenant_rbac_policy'},
+            {'name': 'test_service'}
+        ]
+    }
+
     def setUp(self):
         super(RbacPolicyTest, self).setUp()
-        self.mock_admin_mgr = mock.patch.object(
-            rbac_policy_parser, 'credentials').start()
-        self.mock_path = mock.patch.object(
-            rbac_policy_parser, 'os').start()
+        mock_admin_mgr = self.patchobject(rbac_policy_parser, 'credentials')
+        mock_admin_mgr.AdminManager().identity_services_v3_client.\
+            list_services.return_value = self.services
 
         current_directory = os.path.dirname(os.path.realpath(__file__))
         self.custom_policy_file = os.path.join(current_directory,
@@ -48,35 +57,13 @@
         self.tenant_policy_file = os.path.join(current_directory,
                                                'resources',
                                                'tenant_rbac_policy.json')
-        services = {
-            'services': [
-                {'name': 'cinder', 'links': 'link', 'enabled': True,
-                 'type': 'volume', 'id': 'id',
-                 'description': 'description'},
-                {'name': 'glance', 'links': 'link', 'enabled': True,
-                 'type': 'image', 'id': 'id',
-                 'description': 'description'},
-                {'name': 'nova', 'links': 'link', 'enabled': True,
-                 'type': 'compute', 'id': 'id',
-                 'description': 'description'},
-                {'name': 'keystone', 'links': 'link', 'enabled': True,
-                 'type': 'identity', 'id': 'id',
-                 'description': 'description'},
-                {'name': 'heat', 'links': 'link', 'enabled': True,
-                 'type': 'orchestration', 'id': 'id',
-                 'description': 'description'},
-                {'name': 'neutron', 'links': 'link', 'enabled': True,
-                 'type': 'networking', 'id': 'id',
-                 'description': 'description'},
-                {'name': 'test_service', 'links': 'link', 'enabled': True,
-                 'type': 'unit_test', 'id': 'id',
-                 'description': 'description'}
-            ]
-        }
 
-        self.mock_admin_mgr.AdminManager.return_value.\
-            identity_services_v3_client.list_services.return_value = \
-            services
+        CONF.set_override(
+            'custom_policy_files',
+            [os.path.join(current_directory, 'resources', '%s.json')],
+            group='rbac')
+        self.addCleanup(CONF.clear_override, 'custom_policy_files',
+                        group='rbac')
 
     def _get_fake_policy_rule(self, name, rule):
         fake_rule = mock.Mock(check=rule)
@@ -90,9 +77,8 @@
 
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.custom_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "custom_rbac_policy")
 
         expected = {
             'policy_action_1': ['two', 'four', 'six', 'eight'],
@@ -113,9 +99,8 @@
     def test_admin_policy_file_with_admin_role(self):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.admin_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "admin_rbac_policy")
 
         role = 'admin'
         allowed_rules = [
@@ -134,9 +119,8 @@
     def test_admin_policy_file_with_member_role(self):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.admin_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "admin_rbac_policy")
 
         role = 'Member'
         allowed_rules = [
@@ -153,12 +137,11 @@
             allowed = parser.allowed(rule, role)
             self.assertFalse(allowed)
 
-    def test_admin_policy_file_with_context_is_admin(self):
+    def test_alt_admin_policy_file_with_context_is_admin(self):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.alt_admin_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "alt_admin_rbac_policy")
 
         role = 'fake_admin'
         allowed_rules = ['non_admin_rule']
@@ -193,9 +176,8 @@
         """
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.tenant_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "tenant_rbac_policy")
 
         # Check whether Member role can perform expected actions.
         allowed_rules = ['rule1', 'rule2', 'rule3', 'rule4']
@@ -254,7 +236,7 @@
                           service)
 
         m_log.debug.assert_called_once_with(
-            '%s is NOT a valid service.', 'invalid_service')
+            '%s is NOT a valid service.', service)
 
     @mock.patch.object(rbac_policy_parser, 'LOG', autospec=True)
     def test_service_is_none_raises_exception(self, m_log):
@@ -274,9 +256,8 @@
     def test_invalid_policy_rule_throws_rbac_parsing_exception(self, m_log):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.custom_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "custom_rbac_policy")
 
         fake_rule = 'fake_rule'
         expected_message = "Policy action: {0} not found in policy file: {1}."\
@@ -291,10 +272,9 @@
     def test_unknown_exception_throws_rbac_parsing_exception(self, m_log):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.custom_policy_file
 
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "custom_rbac_policy")
         parser.rules = mock.MagicMock(
             **{'__getitem__.return_value.side_effect': Exception(
                mock.sentinel.error)})
@@ -327,12 +307,10 @@
 
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.tenant_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
+            test_tenant_id, test_user_id, "tenant_rbac_policy")
 
         policy_data = parser._get_policy_data('fake_service')
-
         self.assertIsInstance(policy_data, str)
 
         actual_policy_data = json.loads(policy_data)
@@ -346,7 +324,6 @@
             "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"
-
         }
 
         self.assertEqual(expected_policy_data, actual_policy_data)
@@ -371,12 +348,10 @@
 
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-        self.mock_path.path.join.return_value = self.tenant_policy_file
+
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test_service")
-
+            test_tenant_id, test_user_id, 'tenant_rbac_policy')
         policy_data = parser._get_policy_data('fake_service')
-
         self.assertIsInstance(policy_data, str)
 
         actual_policy_data = json.loads(policy_data)
@@ -392,23 +367,18 @@
 
         self.assertEqual(expected_policy_data, actual_policy_data)
 
-    @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
     @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
-    def test_get_policy_data_cannot_find_policy(self, mock_stevedore,
-                                                mock_creds):
+    def test_get_policy_data_cannot_find_policy(self, mock_stevedore):
         mock_stevedore.named.NamedExtensionManager.return_value = None
-        mock_creds.AdminManager.return_value.identity_services_v3_client.\
-            list_services.return_value = {
-                'services': [{'name': 'test_service'}]}
-        self.mock_path.path.join.return_value = '/etc/test_service/policy.json'
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               rbac_policy_parser.RbacPolicyParser,
                               None, None, 'test_service')
 
         expected_error = \
             'Policy file for {0} service neither found in code '\
-            'nor at {1}.'.format('test_service',
-                                 '/etc/test_service/policy.json')
+            'nor at {1}.'.format(
+                'test_service',
+                [CONF.rbac.custom_policy_files[0] % 'test_service'])
 
         self.assertIn(expected_error, str(e))
 
@@ -416,8 +386,6 @@
     @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
     def test_get_policy_data_without_valid_policy(self, mock_stevedore,
                                                   mock_json):
-        self.mock_path.path.isfile.return_value = False
-
         test_policy_action = mock.Mock(check='rule:bar')
         test_policy_action.configure_mock(name='foo')
 
@@ -450,13 +418,12 @@
                                                 mock_json):
         mock_stevedore.named.NamedExtensionManager.return_value = None
         mock_json.loads.side_effect = ValueError
-        self.mock_path.path.join.return_value = self.tenant_policy_file
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               rbac_policy_parser.RbacPolicyParser,
-                              None, None, 'test_service')
+                              None, None, 'tenant_rbac_policy')
 
-        expected_error = 'Policy file for {0} service neither found in code '\
-                         'nor at {1}.'.format('test_service',
-                                              self.tenant_policy_file)
-
+        expected_error = (
+            'Policy file for {0} service neither found in code nor at {1}.'
+            .format('tenant_rbac_policy', [CONF.rbac.custom_policy_files[0]
+                                           % 'tenant_rbac_policy']))
         self.assertIn(expected_error, str(e))
diff --git a/releasenotes/notes/dynamic-policy-file-discovery-104cbfc64b55d605.yaml b/releasenotes/notes/dynamic-policy-file-discovery-104cbfc64b55d605.yaml
new file mode 100644
index 0000000..59019cf
--- /dev/null
+++ b/releasenotes/notes/dynamic-policy-file-discovery-104cbfc64b55d605.yaml
@@ -0,0 +1,22 @@
+---
+features:
+  - |
+    Add new configuration option ``[rbac] custom_policy_files``,
+    allowing users to specify list of the paths to search for custom
+    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.
+deprecations:
+  - |
+    Deprecate the following configuration options from ``[rbac]`` group:
+
+    * cinder_policy_file
+    * glance_policy_file
+    * keystone_policy_file
+    * neutron_policy_file
+    * nova_policy_file
+
+    It is better to use ``[rbac] custom_policy_files`` which supports
+    any OpenStack service.
diff --git a/requirements.txt b/requirements.txt
index 126a3dc..cd6a577 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,5 +7,5 @@
 oslo.log>=3.22.0 # Apache-2.0
 oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
 oslo.policy>=1.23.0 # Apache-2.0
-tempest>=14.0.0 # Apache-2.0
+tempest>=16.1.0 # Apache-2.0
 stevedore>=1.20.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 6fd4b8a..212db9e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,6 +27,7 @@
 source-dir = doc/source
 build-dir = doc/build
 all_files = 1
+warning-is-error = 1
 
 [upload_sphinx]
 upload-dir = doc/build/html
diff --git a/test-requirements.txt b/test-requirements.txt
index 3e03437..177d0fd 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -13,4 +13,4 @@
 oslotest>=1.10.0 # Apache-2.0
 oslo.policy>=1.23.0 # Apache-2.0
 oslo.log>=3.22.0 # Apache-2.0
-tempest>=14.0.0 # Apache-2.0
+tempest>=16.1.0 # Apache-2.0