[Fix gate] Fix failing identity v2 admin tests

This patch fixes the failing identity tests that
belong to the v2 admin API [0].

Due to recent changes to Keystone and DevStack,
the v2 admin API tests fail if run outright.

Tempest now skips the v2 admin tests uness
``CONF.identity_feature_enabled.api_v2_admin`` is true [1];
Patrole should do the same.

This patch makes the following changes:
  - Skips identity v2 admin tests unless
    ``CONF.identity_feature_enabled.api_v2_admin`` is true
  - Removes superfluous tempest.conf overrides from
    post_test_hook
  - Updates ``rbac_utils.switch_role`` to ensure that admin
    identity credentials are properly configured and that
    the ``roles_v3_client`` is always used
  - Refactors test_projects_rbac in v2 identity because
    the API belongs to both the admin and non-admin API,
    which was causing OverPermission error to be thrown.

[0] https://developer.openstack.org/api-ref/identity/v2-admin/
[1] https://review.openstack.org/#/c/458844/

Change-Id: Ic698d0b2cf669793aaad6aff972ba155ef993e4a
diff --git a/contrib/post_test_hook.sh b/contrib/post_test_hook.sh
index db48fc2..af7c856 100644
--- a/contrib/post_test_hook.sh
+++ b/contrib/post_test_hook.sh
@@ -61,9 +61,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/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index 6a5ed5e..a225c7d 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -29,7 +29,7 @@
 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.
 
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/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.