Merge "Remove heat tests from patrole"
diff --git a/contrib/post_test_hook.sh b/contrib/post_test_hook.sh
index f294dab..e934cc4 100644
--- a/contrib/post_test_hook.sh
+++ b/contrib/post_test_hook.sh
@@ -60,9 +60,7 @@
     # Set strict_policy_check=False under [rbac] section in tempest.conf
     iniset $TEMPEST_CONFIG rbac strict_policy_check False
     # Set additional, necessary CONF values
-    iniset $TEMPEST_CONFIG auth use_dynamic_credentials True
     iniset $TEMPEST_CONFIG auth tempest_roles Member
-    iniset $TEMPEST_CONFIG identity auth_version v3
 }
 
 function run_tests() {
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index b532d63..e342dd8 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -50,3 +50,13 @@
     # not found in the policy.json. Otherwise, they throw a
     # skipException.
     strict_policy_check = False
+
+    # The following config options set the location of the service's
+    # policy file. For services that have their policy in code (e.g.,
+    # Nova), this would be the location of a custom policy.json, if
+    # one exists.
+    cinder_policy_file = /etc/cinder/policy.json
+    glance_policy_file = /etc/glance/policy.json
+    keystone_policy_file = /etc/keystone/policy.json
+    neutron_policy_file = /etc/neutron/policy.json
+    nova_policy_file = /etc/nova/policy.json
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index dfb6cef..cb00269 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -30,5 +30,20 @@
                 default=False,
                 help="If true, throws RbacParsingException for"
                      " policies which don't exist. If false, "
-                     "throws skipException.")
+                     "throws skipException."),
+    cfg.StrOpt('cinder_policy_file',
+               default='/etc/cinder/policy.json',
+               help="Location of the neutron policy file."),
+    cfg.StrOpt('glance_policy_file',
+               default='/etc/glance/policy.json',
+               help="Location of the glance policy file."),
+    cfg.StrOpt('keystone_policy_file',
+               default='/etc/keystone/policy.json',
+               help="Location of the keystone policy file."),
+    cfg.StrOpt('neutron_policy_file',
+               default='/etc/neutron/policy.json',
+               help="Location of the neutron policy file."),
+    cfg.StrOpt('nova_policy_file',
+               default='/etc/nova/policy.json',
+               help="Location of the nova policy file.")
 ]
diff --git a/patrole_tempest_plugin/rbac_auth.py b/patrole_tempest_plugin/rbac_auth.py
deleted file mode 100644
index ae497f3..0000000
--- a/patrole_tempest_plugin/rbac_auth.py
+++ /dev/null
@@ -1,49 +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.
-
-import testtools
-
-from oslo_log import log as logging
-
-from tempest import config
-
-from patrole_tempest_plugin import rbac_exceptions
-from patrole_tempest_plugin import rbac_policy_parser
-
-LOG = logging.getLogger(__name__)
-CONF = config.CONF
-
-
-class RbacAuthority(object):
-    def __init__(self, project_id, user_id, service, extra_target_data):
-        self.policy_parser = rbac_policy_parser.RbacPolicyParser(
-            project_id, user_id, service, extra_target_data=extra_target_data)
-
-    def get_permission(self, rule_name, role):
-        try:
-            is_allowed = self.policy_parser.allowed(rule_name, role)
-            if is_allowed:
-                LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule_name,
-                          role)
-            else:
-                LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!",
-                          rule_name, role)
-            return is_allowed
-        except rbac_exceptions.RbacParsingException as e:
-            if CONF.rbac.strict_policy_check:
-                raise e
-            else:
-                raise testtools.TestCase.skipException(str(e))
-        return False
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 2686736..e68921f 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -40,7 +40,7 @@
     each role, whether a given rule is allowed using oslo policy.
     """
 
-    def __init__(self, project_id, user_id, service=None, path=None,
+    def __init__(self, project_id, user_id, service=None,
                  extra_target_data={}):
         """Initialization of Rbac Policy Parser.
 
@@ -76,7 +76,12 @@
 
         # Use default path in /etc/<service_name/policy.json if no path
         # is provided.
-        self.path = path or os.path.join('/etc', service, 'policy.json')
+        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
         self.rules = policy.Rules.load(self._get_policy_data(service),
                                        'default')
         self.project_id = project_id
@@ -98,12 +103,18 @@
 
         # Check whether policy file exists.
         if os.path.isfile(self.path):
-            with open(self.path, 'r') as policy_file:
-                file_policy_data = policy_file.read()
             try:
+                with open(self.path, 'r') as policy_file:
+                    file_policy_data = policy_file.read()
                 file_policy_data = json.loads(file_policy_data)
-            except ValueError:
-                file_policy_data = None
+            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 = {}
 
         # Check whether policy actions are defined in code. Nova and Keystone,
         # for example, define their default policy actions in code.
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index 6a5ed5e..4382259 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -15,6 +15,7 @@
 
 import logging
 import sys
+import testtools
 
 import six
 
@@ -22,14 +23,14 @@
 from tempest.lib import exceptions
 from tempest import test
 
-from patrole_tempest_plugin import rbac_auth
 from patrole_tempest_plugin import rbac_exceptions
+from patrole_tempest_plugin import rbac_policy_parser
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
 
-def action(service, rule, admin_only=False, expected_error_code=403,
+def action(service, rule='', admin_only=False, expected_error_code=403,
            extra_target_data={}):
     """A decorator which does a policy check and matches it against test run.
 
@@ -62,18 +63,15 @@
     :raises RbacOverPermission: for bullet (3) above.
     """
     def decorator(func):
+        role = CONF.rbac.rbac_test_role
+
         def wrapper(*args, **kwargs):
-            try:
-                caller_ref = None
-                if args and isinstance(args[0], test.BaseTestCase):
-                    caller_ref = args[0]
-                project_id = caller_ref.auth_provider.credentials.project_id
-                user_id = caller_ref.auth_provider.credentials.user_id
-            except AttributeError as e:
-                msg = ("{0}: project_id/user_id not found in "
-                       "cls.auth_provider.credentials".format(e))
-                LOG.error(msg)
-                raise rbac_exceptions.RbacResourceSetupFailed(msg)
+            if args and isinstance(args[0], test.BaseTestCase):
+                test_obj = args[0]
+            else:
+                raise rbac_exceptions.RbacResourceSetupFailed(
+                    '`rbac_rule_validation` decorator can only be applied to '
+                    'an instance of `tempest.test.BaseTestCase`.')
 
             if admin_only:
                 LOG.info("As admin_only is True, only admin role should be "
@@ -81,33 +79,28 @@
                          "check for policy action {0}.".format(rule))
                 allowed = CONF.rbac.rbac_test_role == 'admin'
             else:
-                authority = rbac_auth.RbacAuthority(
-                    project_id, user_id, service,
-                    _format_extra_target_data(caller_ref, extra_target_data))
-                allowed = authority.get_permission(
-                    rule, CONF.rbac.rbac_test_role)
+                allowed = _is_authorized(test_obj, service, rule,
+                                         extra_target_data)
 
             expected_exception, irregular_msg = _get_exception_type(
                 expected_error_code)
 
             try:
-                func(*args)
+                func(*args, **kwargs)
             except rbac_exceptions.RbacInvalidService as e:
                 msg = ("%s is not a valid service." % service)
                 LOG.error(msg)
                 raise exceptions.NotFound(
-                    "%s RbacInvalidService was: %s" %
-                    (msg, e))
+                    "%s RbacInvalidService was: %s" % (msg, e))
             except (expected_exception, rbac_exceptions.RbacActionFailed) as e:
                 if irregular_msg:
                     LOG.warning(irregular_msg.format(rule, service))
                 if allowed:
                     msg = ("Role %s was not allowed to perform %s." %
-                           (CONF.rbac.rbac_test_role, rule))
+                           (role, rule))
                     LOG.error(msg)
                     raise exceptions.Forbidden(
-                        "%s exception was: %s" %
-                        (msg, e))
+                        "%s exception was: %s" % (msg, e))
             except Exception as e:
                 exc_info = sys.exc_info()
                 error_details = exc_info[1].__str__()
@@ -119,21 +112,58 @@
             else:
                 if not allowed:
                     LOG.error("Role %s was allowed to perform %s" %
-                              (CONF.rbac.rbac_test_role, rule))
+                              (role, rule))
                     raise rbac_exceptions.RbacOverPermission(
                         "OverPermission: Role %s was allowed to perform %s" %
-                        (CONF.rbac.rbac_test_role, rule))
+                        (role, rule))
             finally:
-                caller_ref.rbac_utils.switch_role(caller_ref,
-                                                  toggle_rbac_role=False)
-        return wrapper
+                test_obj.rbac_utils.switch_role(test_obj,
+                                                toggle_rbac_role=False)
+
+        _wrapper = testtools.testcase.attr(role)(wrapper)
+        return _wrapper
     return decorator
 
 
+def _is_authorized(test_obj, service, rule_name, extra_target_data):
+    try:
+        project_id = test_obj.auth_provider.credentials.project_id
+        user_id = test_obj.auth_provider.credentials.user_id
+    except AttributeError as e:
+        msg = ("{0}: project_id/user_id not found in "
+               "cls.auth_provider.credentials".format(e))
+        LOG.error(msg)
+        raise rbac_exceptions.RbacResourceSetupFailed(msg)
+
+    try:
+        role = CONF.rbac.rbac_test_role
+        formatted_target_data = _format_extra_target_data(
+            test_obj, extra_target_data)
+        policy_parser = rbac_policy_parser.RbacPolicyParser(
+            project_id, user_id, service,
+            extra_target_data=formatted_target_data)
+        is_allowed = policy_parser.allowed(rule_name, role)
+
+        if is_allowed:
+            LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule_name,
+                      role)
+        else:
+            LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!",
+                      rule_name, role)
+        return is_allowed
+    except rbac_exceptions.RbacParsingException as e:
+        if CONF.rbac.strict_policy_check:
+            raise e
+        else:
+            raise testtools.TestCase.skipException(str(e))
+    return False
+
+
 def _get_exception_type(expected_error_code):
     expected_exception = None
     irregular_msg = None
     supported_error_codes = [403, 404]
+
     if expected_error_code == 403:
         expected_exception = exceptions.Forbidden
     elif expected_error_code == 404:
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index d952014..55a5599 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -20,6 +20,7 @@
 import oslo_utils.uuidutils as uuid_utils
 import six
 
+from tempest.common import credentials_factory as credentials
 from tempest import config
 
 from patrole_tempest_plugin import rbac_exceptions
@@ -56,10 +57,12 @@
         self.token = test_obj.auth_provider.get_token()
         self.identity_version = test_obj.get_identity_version()
 
-        if self.identity_version.endswith('v3'):
-            self.roles_client = test_obj.os_admin.roles_v3_client
-        else:
-            self.roles_client = test_obj.os_admin.roles_client
+        if not credentials.is_admin_available(
+                identity_version=self.identity_version):
+            msg = "Missing Identity Admin API credentials in configuration."
+            raise rbac_exceptions.RbacResourceSetupFailed(msg)
+
+        self.roles_client = test_obj.os_admin.roles_v3_client
 
         LOG.debug('Switching role to: %s', toggle_rbac_role)
 
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/v2/rbac_base.py
index a85d508..bc07675 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/rbac_base.py
@@ -23,20 +23,30 @@
 CONF = config.CONF
 
 
-class BaseIdentityV2RbacTest(base.BaseIdentityV2Test):
+class BaseIdentityV2AdminRbacTest(base.BaseIdentityV2Test):
+    """Base test class for the Identity v2 admin API.
+
+    Keystone's v2 API is split into two APIs: an admin and non-admin API. RBAC
+    testing is only provided for the admin API. Instead of policy enforcement,
+    these APIs execute ``self.assert_admin(request)``, which checks that the
+    request object has ``context_is_admin``. For more details, see the
+    implementation of ``assert_admin`` in ``keystone.common.wsgi``.
+    """
 
     credentials = ['admin', 'primary']
 
     @classmethod
     def skip_checks(cls):
-        super(BaseIdentityV2RbacTest, cls).skip_checks()
+        super(BaseIdentityV2AdminRbacTest, cls).skip_checks()
         if not CONF.rbac.enable_rbac:
             raise cls.skipException(
                 "%s skipped as RBAC testing not enabled" % cls.__name__)
+        if not CONF.identity_feature_enabled.api_v2_admin:
+            raise cls.skipException('Identity v2 admin not available')
 
     @classmethod
     def setup_clients(cls):
-        super(BaseIdentityV2RbacTest, cls).setup_clients()
+        super(BaseIdentityV2AdminRbacTest, cls).setup_clients()
         cls.auth_provider = cls.os_primary.auth_provider
 
         cls.rbac_utils = rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_endpoints_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_endpoints_rbac.py
index dea7f0b..f16d0aa 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_endpoints_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_endpoints_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.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
@@ -21,19 +20,17 @@
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.identity.v2 import rbac_base
 
-CONF = config.CONF
 
-
-class IdentityEndpointsV2RbacTest(rbac_base.BaseIdentityV2RbacTest):
+class IdentityEndpointsV2AdminRbacTest(rbac_base.BaseIdentityV2AdminRbacTest):
 
     @classmethod
     def setup_clients(cls):
-        super(IdentityEndpointsV2RbacTest, cls).setup_clients()
+        super(IdentityEndpointsV2AdminRbacTest, cls).setup_clients()
         cls.endpoints_client = cls.os_primary.endpoints_client
 
     @classmethod
     def resource_setup(cls):
-        super(IdentityEndpointsV2RbacTest, cls).resource_setup()
+        super(IdentityEndpointsV2AdminRbacTest, cls).resource_setup()
         cls.region = data_utils.rand_name('region')
         cls.public_url = data_utils.rand_url()
         cls.admin_url = data_utils.rand_url()
@@ -54,7 +51,6 @@
         return endpoint
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_endpoint",
                                  admin_only=True)
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd124')
     def test_create_endpoint(self):
@@ -68,7 +64,6 @@
         self._create_endpoint()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_endpoint",
                                  admin_only=True)
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd125')
     def test_delete_endpoint(self):
@@ -83,7 +78,6 @@
         self.endpoints_client.delete_endpoint(endpoint['endpoint']['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_endpoints",
                                  admin_only=True)
     @decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd126')
     def test_list_endpoints(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
index 6853b64..a557bb8 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
@@ -16,16 +16,16 @@
 from tempest import config
 from tempest.lib import decorators
 
+from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.identity.v2 import rbac_base
 
 CONF = config.CONF
 
 
-class IdentityProjectV2RbacTest(rbac_base.BaseIdentityV2RbacTest):
+class IdentityProjectV2AdminRbacTest(rbac_base.BaseIdentityV2AdminRbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_project",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d904')
     def test_create_project(self):
@@ -39,7 +39,6 @@
         self._create_tenant()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_project",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d905')
     def test_update_project(self):
@@ -55,7 +54,6 @@
                                           description="Changed description")
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_project",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d906')
     def test_delete_project(self):
@@ -70,7 +68,6 @@
         self.tenants_client.delete_tenant(tenant['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_project",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d907')
     def test_get_project(self):
@@ -86,20 +83,6 @@
         self.tenants_client.show_tenant(tenant['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_projects",
-                                 admin_only=True)
-    @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d908')
-    def test_get_all_projects(self):
-
-        """List All Projects Test
-
-        RBAC test for Identity 2.0 list_tenants
-        """
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.tenants_client.list_tenants()
-
-    @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_user_projects",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d909')
     def test_list_project_users(self):
@@ -112,3 +95,37 @@
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.tenants_client.list_tenant_users(tenant['id'])
+
+    @rbac_rule_validation.action(service="keystone",
+                                 admin_only=True)
+    @decorators.idempotent_id('0f148510-63bf-11e6-b348-080044d0d908')
+    def test_list_all_projects(self):
+
+        """List All Projects Test
+
+        RBAC test for Identity 2.0 list_tenants (admin endpoint)
+
+        There are two separate APIs for listing tenants in the Keystone
+        v2 API: one for admin and one for non-admin. The ``os_admin`` client
+        calls the admin endpoint and the ``os_primary`` client calls the
+        non-admin endpoint. To ensure that the admin endpoint only returns
+        admin-scoped tenants, raise ``RbacActionFailed`` exception otherwise.
+        """
+        tenants_client = self.os_admin.tenants_client if \
+            CONF.identity.admin_role == CONF.rbac.rbac_test_role else \
+            self.os_primary.tenants_client
+        admin_tenant_id = self.os_admin.auth_provider.credentials.project_id
+        non_admin_tenant_id = self.auth_provider.credentials.project_id
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        tenants = tenants_client.list_tenants()['tenants']
+
+        tenant_ids = [t['id'] for t in tenants]
+        if admin_tenant_id not in tenant_ids:
+            raise rbac_exceptions.RbacActionFailed(
+                "The admin tenant id was not returned by the call to "
+                "``list_tenants``.")
+        if non_admin_tenant_id in tenant_ids:
+            raise rbac_exceptions.RbacActionFailed(
+                "The non-admin tenant id was returned by the call to "
+                "``list_tenants``.")
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_roles_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_roles_rbac.py
index 7b21194..a1ec5c6 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_roles_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_roles_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.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
@@ -21,14 +20,12 @@
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.identity.v2 import rbac_base
 
-CONF = config.CONF
 
-
-class IdentityRoleV2RbacTest(rbac_base.BaseIdentityV2RbacTest):
+class IdentityRolesV2AdminRbacTest(rbac_base.BaseIdentityV2AdminRbacTest):
 
     @classmethod
     def setup_clients(cls):
-        super(IdentityRoleV2RbacTest, cls).setup_clients()
+        super(IdentityRolesV2AdminRbacTest, cls).setup_clients()
         cls.roles_client = cls.os_primary.roles_client
 
     def _create_role(self):
@@ -53,7 +50,6 @@
             tenant['id'], user['id'], role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_role",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d904')
     def test_create_role(self):
@@ -67,7 +63,6 @@
         self._create_role()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_role",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d905')
     def test_delete_role(self):
@@ -82,7 +77,6 @@
         self.roles_client.delete_role(role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_role",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d906')
     def test_show_role(self):
@@ -97,7 +91,6 @@
         self.roles_client.show_role(role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_roles",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d907')
     def test_list_roles(self):
@@ -110,7 +103,6 @@
         self.roles_client.list_roles()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:add_role_to_user",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d908')
     def test_create_role_on_project(self):
@@ -124,7 +116,6 @@
         self._create_role_on_project(tenant, user, role)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:remove_role_from_user",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d909')
     def test_delete_role_from_user_on_project(self):
@@ -141,7 +132,6 @@
             tenant['id'], user['id'], role['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_user_roles",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-8674-080044d0d90a')
     def test_list_user_roles_on_project(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_services_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_services_rbac.py
index c9803ec..ad47fd2 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_services_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_services_rbac.py
@@ -13,24 +13,20 @@
 #    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.identity.v2 import rbac_base
 
-CONF = config.CONF
 
-
-class IdentityServicesV2RbacTest(rbac_base.BaseIdentityV2RbacTest):
+class IdentityServicesV2AdminRbacTest(rbac_base.BaseIdentityV2AdminRbacTest):
 
     @classmethod
     def setup_clients(cls):
-        super(IdentityServicesV2RbacTest, cls).setup_clients()
+        super(IdentityServicesV2AdminRbacTest, cls).setup_clients()
         cls.services_client = cls.os_primary.identity_services_client
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_service",
                                  admin_only=True)
     @decorators.idempotent_id('370050f6-d271-4fb4-abc5-4de1d6dfbad2')
     def test_create_service(self):
@@ -42,7 +38,6 @@
         self._create_service()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_service",
                                  admin_only=True)
     @decorators.idempotent_id('f6c64fc3-6a1f-423e-af91-e411add3a384')
     def test_delete_service(self):
@@ -56,7 +51,6 @@
         self.services_client.delete_service(service_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_service",
                                  admin_only=True)
     @decorators.idempotent_id('504d62bb-97d7-445e-9d6d-b1945a7c9e08')
     def test_show_service(self):
@@ -70,7 +64,6 @@
         self.services_client.show_service(service_id)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:list_services",
                                  admin_only=True)
     @decorators.idempotent_id('d7dc461d-51ad-48e0-9cd2-33add1b88de9')
     def test_list_services(self):
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_users_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_users_rbac.py
index 48f3d11..f90680d 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_users_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_users_rbac.py
@@ -20,10 +20,9 @@
 from patrole_tempest_plugin.tests.api.identity.v2 import rbac_base
 
 
-class IdentityUserV2RbacTest(rbac_base.BaseIdentityV2RbacTest):
+class IdentityUsersV2AdminRbacTest(rbac_base.BaseIdentityV2AdminRbacTest):
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:create_user",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d904')
     def test_create_user(self):
@@ -31,7 +30,6 @@
         self._create_user()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:update_user",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d905')
     def test_update_user(self):
@@ -41,7 +39,6 @@
         self.users_client.update_user(user['id'], email="changedUser@xyz.com")
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:set_user_enabled",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d9a1')
     def test_update_user_enabled(self):
@@ -51,7 +48,6 @@
         self.users_client.update_user_enabled(user['id'], enabled=True)
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:delete_user",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d906')
     def test_delete_user(self):
@@ -61,7 +57,6 @@
         self.users_client.delete_user(user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_users",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d907')
     def test_list_users(self):
@@ -69,7 +64,6 @@
         self.users_client.list_users()
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:get_user",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d908')
     def test_show_user(self):
@@ -79,7 +73,6 @@
         self.users_client.show_user(user['id'])
 
     @rbac_rule_validation.action(service="keystone",
-                                 rule="identity:change_password",
                                  admin_only=True)
     @decorators.idempotent_id('0f148510-63bf-11e6-1342-080044d0d909')
     def test_update_user_password(self):
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 956727b..7380531 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
@@ -20,11 +20,11 @@
 from patrole_tempest_plugin.tests.api.identity.v3 import rbac_base
 
 
-class IdentityUserV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
+class IdentityUserV3AdminRbacTest(rbac_base.BaseIdentityV3RbacTest):
 
     @classmethod
     def resource_setup(cls):
-        super(IdentityUserV3RbacTest, cls).resource_setup()
+        super(IdentityUserV3AdminRbacTest, cls).resource_setup()
         cls.default_user_id = cls.auth_provider.credentials.user_id
 
     @rbac_rule_validation.action(service="keystone",
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py
index 94c704f..552a137 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py
@@ -23,6 +23,21 @@
 
 class ImageResourceTypesRbacTest(rbac_base.BaseV2ImageRbacTest):
 
+    @classmethod
+    def resource_setup(cls):
+        super(ImageResourceTypesRbacTest, cls).resource_setup()
+        cls.namespace_name = data_utils.rand_name('test-ns')
+        cls.namespaces_client.create_namespace(
+            namespace=cls.namespace_name,
+            protected=False)
+
+    @classmethod
+    def resource_cleanup(cls):
+        test_utils.call_and_ignore_notfound_exc(
+            cls.namespaces_client.delete_namespace,
+            cls.namespace_name)
+        super(ImageResourceTypesRbacTest, cls).resource_setup()
+
     @rbac_rule_validation.action(service="glance",
                                  rule="list_metadef_resource_types")
     @decorators.idempotent_id('0416fc4d-cfdc-447b-88b6-d9f1dd0382f7')
@@ -42,15 +57,15 @@
 
         RBAC test for the glance get_metadef_resource_type policy.
         """
-        namespace_name = data_utils.rand_name('test-ns')
-        self.namespaces_client.create_namespace(
-            namespace=namespace_name,
-            protected=False)
-        self.addCleanup(
-            test_utils.call_and_ignore_notfound_exc,
-            self.namespaces_client.delete_namespace,
-            namespace_name)
-
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.resource_types_client.list_resource_type_association(
-            namespace_name)
+            self.namespace_name)
+
+    @rbac_rule_validation.action(service="glance",
+                                 rule="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()
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.resource_types_client.create_resource_type_association(
+            self.namespace_name, name=type_name)
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 416fb4d..16e77ed 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
@@ -158,6 +158,48 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.client.retype_volume(volume['id'], new_type=vol_type)
 
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="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()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.client.reset_volume_status(volume['id'], status='error')
+
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="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()
+        self.client.reset_volume_status(volume['id'], status='error')
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.client.force_delete_volume(volume['id'])
+        self.client.wait_for_resource_deletion(volume['id'])
+
+    @decorators.idempotent_id('48bd302b-950a-4830-840c-3158246ecdcc')
+    @test.services('compute')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:volume_admin_actions:force_detach")
+    def test_force_detach_volume_from_instance(self):
+        server = self._create_server()
+        self._attach_volume(server)
+        attachment = self.volumes_client.show_volume(
+            self.volume['id'])['volume']['attachments'][0]
+
+        # Reset volume's status to error.
+        self.volumes_client.reset_volume_status(self.volume['id'],
+                                                status='error')
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.volumes_client.force_detach_volume(
+            self.volume['id'], connector=None,
+            attachment_id=attachment['attachment_id'])
+
 
 class VolumesActionsV3RbacTest(VolumesActionsRbacTest):
     _api_version = 3
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 50973b8..8c04a8d 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
@@ -116,18 +116,6 @@
         self.backups_client.delete_backup(backup['id'])
         self.backups_client.wait_for_resource_deletion(backup['id'])
 
-    @decorators.idempotent_id('48325aaa-13f5-4ba3-96a3-24b6c9d77b6d')
-    @test.attr(type=["slow"])
-    @rbac_rule_validation.action(
-        service="cinder",
-        rule="volume_extension:backup_admin_actions:force_delete")
-    def test_volume_backup_force_delete(self):
-        backup = self._create_backup(volume_id=self.volume['id'])
-
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.backups_client.force_delete_backup(backup['id'])
-        self.backups_client.wait_for_resource_deletion(backup['id'])
-
     @test.attr(type=["slow"])
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:backup-export")
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
new file mode 100644
index 0000000..7a9d7ba
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py
@@ -0,0 +1,113 @@
+# 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 data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.volume import rbac_base
+
+CONF = config.CONF
+
+
+class VolumesManageRbacTest(rbac_base.BaseVolumeRbacTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumesManageRbacTest, cls).skip_checks()
+
+        if not CONF.volume_feature_enabled.manage_volume:
+            raise cls.skipException("Manage volume tests are disabled")
+
+        if len(CONF.volume.manage_volume_ref) != 2:
+            raise cls.skipException("Manage volume ref is not correctly "
+                                    "configured")
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesManageRbacTest, cls).setup_clients()
+        cls.volume_manage_client = cls.os_primary.volume_manage_v2_client
+
+    def _manage_volume(self, org_volume):
+        # Manage volume
+        new_volume_name = data_utils.rand_name(
+            self.__class__.__name__ + '-volume')
+
+        new_volume_ref = {
+            'name': new_volume_name,
+            'host': org_volume['os-vol-host-attr:host'],
+            'ref': {CONF.volume.manage_volume_ref[0]:
+                    CONF.volume.manage_volume_ref[1] % org_volume['id']},
+            'volume_type': org_volume['volume_type'],
+            'availability_zone': org_volume['availability_zone']}
+
+        new_volume_id = self.volume_manage_client.manage_volume(
+            **new_volume_ref)['volume']['id']
+
+        waiters.wait_for_volume_resource_status(self.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'])
+
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:volume_manage")
+    @decorators.idempotent_id('114f9708-939b-407e-aeac-d21ebfabaad3')
+    def test_volume_manage(self):
+        volume_id = self.create_volume()['id']
+        volume = self.volumes_client.show_volume(volume_id)['volume']
+
+        # By default, the volume is managed after creation.  We need to
+        # unmanage the volume first before testing manage volume.
+        self._unmanage_volume(volume)
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        try:
+            self._manage_volume(volume)
+        except exceptions.Forbidden as e:
+            # Since the test role under test does not have permission to
+            # manage the volume, Forbidden exception is thrown and the
+            # manageable list will not be cleaned up. Therefore, we need to
+            # re-manage the volume at the end of the test case for proper
+            # resource clean up.
+            self.addCleanup(self._manage_volume, volume)
+            raise exceptions.Forbidden(e)
+
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:volume_unmanage")
+    @decorators.idempotent_id('d5d72abe-60bc-45ac-a8f2-c21b24f0b5d6')
+    def test_volume_unmanage(self):
+        volume_id = self.create_volume()['id']
+        volume = self.volumes_client.show_volume(volume_id)['volume']
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self._unmanage_volume(volume)
+
+        # In order to clean up the manageable list, we need to re-manage the
+        # volume after the test.  The _manage_volume method will set up the
+        # proper resource cleanup
+        self.addCleanup(self._manage_volume, volume)
+
+
+class VolumesManageV3RbacTest(VolumesManageRbacTest):
+    _api_version = 3
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
deleted file mode 100644
index b10f5b3..0000000
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
+++ /dev/null
@@ -1,60 +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 oslo_log import log as logging
-
-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
-LOG = logging.getLogger(__name__)
-
-
-class VolumesRbacTest(rbac_base.BaseVolumeRbacTest):
-
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesRbacTest, cls).setup_clients()
-        cls.client = cls.volumes_client
-
-    @rbac_rule_validation.action(
-        service="cinder",
-        rule="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()
-        # Test volume reset status : available->error->available
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.client.reset_volume_status(volume['id'], status='error')
-        self.client.reset_volume_status(volume['id'], status='available')
-
-    @rbac_rule_validation.action(
-        service="cinder",
-        rule="volume_extension:volume_admin_actions:force_delete")
-    @decorators.idempotent_id('a312a937-6abf-4b91-a950-747086cbce48')
-    def test_volume_force_delete_when_volume_is_error(self):
-        volume = self.create_volume()
-        self.client.reset_volume_status(volume['id'], status='error')
-        # Test force delete when status of volume is error
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.client.force_delete_volume(volume['id'])
-        self.client.wait_for_resource_deletion(volume['id'])
-
-
-class VolumesV3RbacTest(VolumesRbacTest):
-    _api_version = 3
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 0906222..6889b44 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -32,6 +32,8 @@
         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()
 
         current_directory = os.path.dirname(os.path.realpath(__file__))
         self.custom_policy_file = os.path.join(current_directory,
@@ -88,8 +90,9 @@
 
         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", self.custom_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         expected = {
             'policy_action_1': ['two', 'four', 'six', 'eight'],
@@ -110,8 +113,9 @@
     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", self.admin_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         role = 'admin'
         allowed_rules = [
@@ -130,8 +134,9 @@
     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", self.admin_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         role = 'Member'
         allowed_rules = [
@@ -151,8 +156,9 @@
     def test_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", self.alt_admin_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         role = 'fake_admin'
         allowed_rules = ['non_admin_rule']
@@ -187,8 +193,9 @@
         """
         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", self.tenant_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         # Check whether Member role can perform expected actions.
         allowed_rules = ['rule1', 'rule2', 'rule3', 'rule4']
@@ -268,9 +275,9 @@
     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", self.custom_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         fake_rule = 'fake_rule'
         expected_message = "Policy action: {0} not found in policy file: {1}."\
@@ -285,9 +292,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", self.custom_policy_file)
+            test_tenant_id, test_user_id, "test")
         parser.rules = mock.MagicMock(
             **{'__getitem__.return_value.side_effect': Exception(
                mock.sentinel.error)})
@@ -320,9 +327,9 @@
 
         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", self.tenant_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         policy_data = parser._get_policy_data('fake_service')
 
@@ -364,9 +371,9 @@
 
         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", self.tenant_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         policy_data = parser._get_policy_data('fake_service')
 
@@ -393,10 +400,10 @@
         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', None)
+                              None, None, 'test_service')
 
         expected_error = \
             'Policy file for {0} service neither found in code '\
@@ -405,14 +412,12 @@
 
         self.assertIn(expected_error, str(e))
 
-    @mock.patch.object(rbac_policy_parser, 'os', autospec=True)
     @mock.patch.object(rbac_policy_parser, 'json', autospec=True)
     @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
     @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
     def test_get_policy_data_without_valid_policy(self, mock_stevedore,
-                                                  mock_credentials, mock_json,
-                                                  mock_os):
-        mock_os.path.isfile.return_value = False
+                                                  mock_credentials, 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')
@@ -432,7 +437,7 @@
 
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               rbac_policy_parser.RbacPolicyParser,
-                              None, None, 'test_service', None)
+                              None, None, 'test_service')
 
         expected_error = "Policy file for {0} service is invalid."\
                          .format("test_service")
@@ -459,11 +464,10 @@
             }
         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',
-                              self.tenant_policy_file)
+                              None, None, 'test_service')
 
         expected_error = 'Policy file for {0} service neither found in code '\
                          'nor at {1}.'.format('test_service',
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 174945e..41af3b2 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -19,7 +19,6 @@
 from tempest import test
 from tempest.tests import base
 
-from patrole_tempest_plugin import rbac_auth
 from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation as rbac_rv
 
@@ -43,8 +42,9 @@
         self.addCleanup(CONF.clear_override, 'rbac_test_role', group='rbac')
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_rule_validation_have_permission_no_exc(self, mock_auth, mock_log):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_have_permission_no_exc(self, mock_policy,
+                                                    mock_log):
         """Test that having permission and no exception thrown is success.
 
         Positive test case success scenario.
@@ -54,9 +54,7 @@
         mock_function = mock.Mock()
         wrapper = decorator(mock_function)
 
-        mock_permission = mock.Mock()
-        mock_permission.get_permission.return_value = True
-        mock_auth.return_value = mock_permission
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
 
         result = wrapper(self.mock_args)
 
@@ -65,8 +63,8 @@
         mock_log.error.assert_not_called()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_rule_validation_lack_permission_throw_exc(self, mock_auth,
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_lack_permission_throw_exc(self, mock_policy,
                                                        mock_log):
         """Test that having no permission and exception thrown is success.
 
@@ -78,9 +76,7 @@
         mock_function.side_effect = exceptions.Forbidden
         wrapper = decorator(mock_function)
 
-        mock_permission = mock.Mock()
-        mock_permission.get_permission.return_value = False
-        mock_auth.return_value = mock_permission
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
 
         result = wrapper(self.mock_args)
 
@@ -89,8 +85,8 @@
         mock_log.error.assert_not_called()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_rule_validation_forbidden_negative(self, mock_auth, mock_log):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_forbidden_negative(self, mock_policy, mock_log):
         """Test Forbidden error is thrown and have permission fails.
 
         Negative test case: if Forbidden is thrown and the user should be
@@ -102,9 +98,7 @@
         mock_function.side_effect = exceptions.Forbidden
         wrapper = decorator(mock_function)
 
-        mock_permission = mock.Mock()
-        mock_permission.get_permission.return_value = True
-        mock_auth.return_value = mock_permission
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
 
         e = self.assertRaises(exceptions.Forbidden, wrapper, self.mock_args)
         self.assertIn(
@@ -114,8 +108,8 @@
                                                " perform sentinel.action.")
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_rule_validation_rbac_action_failed_positive(self, mock_auth,
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_rbac_action_failed_positive(self, mock_policy,
                                                          mock_log):
         """Test RbacActionFailed error is thrown without permission passes.
 
@@ -127,9 +121,7 @@
         mock_function.side_effect = rbac_exceptions.RbacActionFailed
         wrapper = decorator(mock_function)
 
-        mock_permission = mock.Mock()
-        mock_permission.get_permission.return_value = False
-        mock_auth.return_value = mock_permission
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
 
         result = wrapper(self.mock_args)
 
@@ -138,8 +130,8 @@
         mock_log.warning.assert_not_called()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_rule_validation_rbac_action_failed_negative(self, mock_auth,
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_rbac_action_failed_negative(self, mock_policy,
                                                          mock_log):
         """Test RbacActionFailed error is thrown with permission fails.
 
@@ -151,9 +143,7 @@
         mock_function.side_effect = rbac_exceptions.RbacActionFailed
         wrapper = decorator(mock_function)
 
-        mock_permission = mock.Mock()
-        mock_permission.get_permission.return_value = True
-        mock_auth.return_value = mock_permission
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
 
         e = self.assertRaises(exceptions.Forbidden, wrapper, self.mock_args)
         self.assertIn(
@@ -164,8 +154,9 @@
                                                " perform sentinel.action.")
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_expect_not_found_but_raises_forbidden(self, mock_auth, mock_log):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_expect_not_found_but_raises_forbidden(self, mock_policy,
+                                                   mock_log):
         """Test that expecting 404 but getting 403 works for all scenarios.
 
         Tests the following scenarios:
@@ -186,9 +177,8 @@
                          "NotFound, which was not thrown."
 
         for permission in [True, False]:
-            mock_permission = mock.Mock()
-            mock_permission.get_permission.return_value = permission
-            mock_auth.return_value = mock_permission
+            mock_policy.RbacPolicyParser.return_value.allowed.return_value =\
+                permission
 
             e = self.assertRaises(exceptions.Forbidden, wrapper,
                                   self.mock_args)
@@ -197,8 +187,8 @@
             mock_log.error.reset_mock()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_expect_not_found_and_raise_not_found(self, mock_auth, mock_log):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_expect_not_found_and_raise_not_found(self, mock_policy, mock_log):
         """Test that expecting 404 and getting 404 works for all scenarios.
 
         Tests the following scenarios:
@@ -220,9 +210,8 @@
         ]
 
         for pos, permission in enumerate([True, False]):
-            mock_permission = mock.Mock()
-            mock_permission.get_permission.return_value = permission
-            mock_auth.return_value = mock_permission
+            mock_policy.RbacPolicyParser.return_value.allowed.return_value =\
+                permission
 
             expected_error = expected_errors[pos]
 
@@ -245,8 +234,8 @@
             mock_log.error.reset_mock()
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_rule_validation_overpermission_negative(self, mock_auth,
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_overpermission_negative(self, mock_policy,
                                                      mock_log):
         """Test that OverPermission is correctly handled.
 
@@ -258,9 +247,7 @@
         mock_function = mock.Mock()
         wrapper = decorator(mock_function)
 
-        mock_permission = mock.Mock()
-        mock_permission.get_permission.return_value = False
-        mock_auth.return_value = mock_permission
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
 
         e = self.assertRaises(rbac_exceptions.RbacOverPermission, wrapper,
                               self.mock_args)
@@ -270,7 +257,7 @@
         mock_log.error.assert_called_once_with(
             "Role Member was allowed to perform sentinel.action")
 
-    @mock.patch.object(rbac_auth, 'rbac_policy_parser', autospec=True)
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
     def test_invalid_policy_rule_throws_parsing_exception(
             self, mock_rbac_policy_parser):
         """Test that invalid policy action causes test to be skipped."""
@@ -295,8 +282,8 @@
             mock.sentinel.project_id, mock.sentinel.user_id,
             mock.sentinel.service, extra_target_data={})
 
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_get_exception_type_404(self, mock_auth):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_get_exception_type_404(self, mock_policy):
         """Test that getting a 404 exception type returns NotFound."""
         expected_exception = exceptions.NotFound
         expected_irregular_msg = ("NotFound exception was caught for policy "
@@ -309,8 +296,8 @@
         self.assertEqual(expected_exception, actual_exception)
         self.assertEqual(expected_irregular_msg, actual_irregular_msg)
 
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_get_exception_type_403(self, mock_auth):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_get_exception_type_403(self, mock_policy):
         """Test that getting a 404 exception type returns Forbidden."""
         expected_exception = exceptions.Forbidden
         expected_irregular_msg = None
@@ -322,8 +309,9 @@
         self.assertEqual(expected_irregular_msg, actual_irregular_msg)
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_exception_thrown_when_type_is_not_int(self, mock_auth, mock_log):
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_exception_thrown_when_type_is_not_int(self, mock_policy,
+                                                   mock_log):
         """Test that non-integer exception type raises error."""
         self.assertRaises(rbac_exceptions.RbacInvalidErrorCode,
                           rbac_rv._get_exception_type, "403")
@@ -333,8 +321,8 @@
                                                "codes: [403, 404]")
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
-    @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
-    def test_exception_thrown_when_type_is_403_or_404(self, mock_auth,
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_exception_thrown_when_type_is_403_or_404(self, mock_policy,
                                                       mock_log):
         """Test that unsupported exceptions throw error."""
         invalid_exceptions = [200, 400, 500]
diff --git a/releasenotes/notes/add-metadef-resource-type-7973621c5e8fff7f.yaml b/releasenotes/notes/add-metadef-resource-type-7973621c5e8fff7f.yaml
new file mode 100644
index 0000000..61bec83
--- /dev/null
+++ b/releasenotes/notes/add-metadef-resource-type-7973621c5e8fff7f.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Adds test for glance's
+    add_metadef_resource_type_association policy.
diff --git a/releasenotes/notes/admin-only-identity-v2-admin-6f382e38d7a690a4.yaml b/releasenotes/notes/admin-only-identity-v2-admin-6f382e38d7a690a4.yaml
new file mode 100644
index 0000000..750a9f1
--- /dev/null
+++ b/releasenotes/notes/admin-only-identity-v2-admin-6f382e38d7a690a4.yaml
@@ -0,0 +1,12 @@
+---
+fixes:
+  - |
+    Removed ``rule`` kwarg from ``rbac_rule_validation`` decorator for identity
+    v2 admin tests, because the identity v2 admin API does not do policy
+    enforcement, and instead checks whether the request object has
+    ``context_is_admin``.
+other:
+  - |
+    Updated the class names for identity v2 tests to include the "Admin"
+    substring, to convey the fact that these tests are only intended
+    to test the v2 admin API, not the v2 API.
diff --git a/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml b/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml
new file mode 100644
index 0000000..3e63c9d
--- /dev/null
+++ b/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Refactored framework to remove unused "path"
+    argument. Added config options to allow the path
+    to the policy.json files for Nova, Keystone, Cinder,
+    Neutron, and Glance to be configured without needing
+    to manually change code.
diff --git a/releasenotes/notes/merge-rbac-auth-with-rbac-rule-validation-5d7c286788a95ee9.yaml b/releasenotes/notes/merge-rbac-auth-with-rbac-rule-validation-5d7c286788a95ee9.yaml
new file mode 100644
index 0000000..b96c73a
--- /dev/null
+++ b/releasenotes/notes/merge-rbac-auth-with-rbac-rule-validation-5d7c286788a95ee9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Merges `rbac_auth` with `rbac_rule_validation`, because `rbac_auth`
+    decentralized logic from `rbac_rule_validation` without providing any
+    authentication-related utility. This change facilitates code maintenance
+    and code readability.