Merge "Fix test_migration_live throwing AttributeError."
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index 96f1c3f..76064f1 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -30,20 +30,11 @@
 
 #. [auth] section updates ::
 
-       # Set tempest role to admin so all APIs are accessible
-       tempest_roles = admin
-
-       # Allows test cases to create/destroy tenants and users. This
-       # option enables isolated test cases and better parallel
-       # execution, but also requires that OpenStack Identity API
-       # admin credentials are known. (boolean value)
-       allow_tenant_isolation = True
-
        # Allows test cases to create/destroy projects and users. This option
        # requires that OpenStack Identity API admin credentials are known. If
        # false, isolated test cases and parallel execution, can still be
        # achieved configuring a list of test accounts (boolean value)
-       use_dynamic_credentials = False
+       use_dynamic_credentials = True
 
 #. [rbac] section updates ::
 
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index db648df..70fe0b7 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -13,15 +13,15 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import oslo_utils.uuidutils as uuid_utils
-import six
+import sys
+import testtools
 import time
 
-from tempest.common import credentials_factory
-from tempest import config
-from tempest.test import BaseTestCase
-
 from oslo_log import log as logging
+import oslo_utils.uuidutils as uuid_utils
+import six
+
+from tempest import config
 
 from patrole_tempest_plugin import rbac_exceptions
 
@@ -42,51 +42,41 @@
 @six.add_metaclass(Singleton)
 class RbacUtils(object):
 
-    def __init__(cls):
-        creds_provider = credentials_factory.get_credentials_provider(
-            name=__name__,
-            force_tenant_isolation=True,
-            identity_version=BaseTestCase.get_identity_version())
+    # References the last value of `switch_to_rbac_role` that was passed to
+    # `switch_role`. Used for ensuring that `switch_role` is correctly used
+    # in a test file, so that false positives are prevented. The key used
+    # to index into the dictionary is the module path plus class name, which is
+    # unique.
+    switch_role_history = {}
+    admin_role_id = None
+    rbac_role_id = None
 
-        cls.creds_client = creds_provider.creds_client
-        cls.available_roles = cls.creds_client.roles_client.list_roles()
-        cls.admin_role_id = cls.rbac_role_id = None
-        for item in cls.available_roles['roles']:
-            if item['name'] == CONF.rbac.rbac_test_role:
-                cls.rbac_role_id = item['id']
-            if item['name'] == 'admin':
-                cls.admin_role_id = item['id']
+    def switch_role(self, test_obj, switchToRbacRole=False):
+        self.user_id = test_obj.auth_provider.credentials.user_id
+        self.project_id = test_obj.auth_provider.credentials.tenant_id
+        self.token = test_obj.auth_provider.get_token()
+        self.identity_version = test_obj.get_identity_version()
 
-    def switch_role(cls, test_obj, switchToRbacRole=None):
+        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
+
         LOG.debug('Switching role to: %s', switchToRbacRole)
-        # Check if admin and rbac roles exist.
-        if not cls.admin_role_id or not cls.rbac_role_id:
-            msg = ("Defined 'rbac_role' or 'admin' role does not exist"
-                   " in the system.")
-            raise rbac_exceptions.RbacResourceSetupFailed(msg)
-
-        if not isinstance(switchToRbacRole, bool):
-            msg = ("Wrong value for parameter 'switchToRbacRole' is passed."
-                   " It should be either 'True' or 'False'.")
-            raise rbac_exceptions.RbacResourceSetupFailed(msg)
 
         try:
-            user_id = test_obj.auth_provider.credentials.user_id
-            project_id = test_obj.auth_provider.credentials.tenant_id
+            if not self.admin_role_id or not self.rbac_role_id:
+                self._get_roles()
 
-            cls._clear_user_roles(user_id, project_id)
+            rbac_utils._validate_switch_role(self, test_obj, switchToRbacRole)
 
             if switchToRbacRole:
-                cls.creds_client.roles_client.create_user_role_on_project(
-                    project_id, user_id, cls.rbac_role_id)
+                self._add_role_to_user(self.rbac_role_id)
             else:
-                cls.creds_client.roles_client.create_user_role_on_project(
-                    project_id, user_id, cls.admin_role_id)
-
+                self._add_role_to_user(self.admin_role_id)
         except Exception as exp:
             LOG.error(exp)
             raise
-
         finally:
             # NOTE(felipemonteiro): These two comments below are copied from
             # tempest.api.identity.v2/v3.test_users.
@@ -99,17 +89,84 @@
             # precise to the second. Sleep to ensure we are passing the second
             # boundary before attempting to authenticate. If token is of type
             # uuid, then do not sleep.
-            if not uuid_utils.is_uuid_like(cls.creds_client.
-                                           identity_client.token):
+            if not uuid_utils.is_uuid_like(self.token):
                 time.sleep(1)
             test_obj.auth_provider.set_auth()
 
-    def _clear_user_roles(cls, user_id, tenant_id):
-        roles = cls.creds_client.roles_client.list_user_roles_on_project(
-            tenant_id, user_id)['roles']
+    def _add_role_to_user(self, role_id):
+        role_already_present = self._clear_user_roles(role_id)
+        if role_already_present:
+            return
+
+        self.roles_client.create_user_role_on_project(
+            self.project_id, self.user_id, role_id)
+
+    def _clear_user_roles(self, role_id):
+        roles = self.roles_client.list_user_roles_on_project(
+            self.project_id, self.user_id)['roles']
+
+        # If the user already has the role that is required, return early.
+        role_ids = [role['id'] for role in roles]
+        if role_ids == [role_id]:
+            return True
 
         for role in roles:
-            cls.creds_client.roles_client.delete_role_from_user_on_project(
-                tenant_id, user_id, role['id'])
+            self.roles_client.delete_role_from_user_on_project(
+                self.project_id, self.user_id, role['id'])
+
+        return False
+
+    def _validate_switch_role(self, test_obj, switchToRbacRole):
+        """Validates that the rbac role passed to `switch_role` is legal.
+
+        Throws an error for the following improper usages of `switch_role`:
+            * `switch_role` is not called with a boolean value
+            * `switch_role` is never called in a test file, except in tearDown
+            * `switch_role` is called with the same boolean value twice
+        """
+        if not isinstance(switchToRbacRole, bool):
+            raise rbac_exceptions.RbacResourceSetupFailed(
+                'switchToRbacRole must be a boolean value.')
+
+        # The unique key is the combination of module path plus class name.
+        class_name = test_obj.__name__ if isinstance(test_obj, type) else \
+            test_obj.__class__.__name__
+        module_name = test_obj.__module__
+        key = '%s.%s' % (module_name, class_name)
+
+        self.switch_role_history.setdefault(key, None)
+
+        if self.switch_role_history[key] == switchToRbacRole:
+            # If the test was skipped, then this is a legitimate use case,
+            # so do not throw an exception.
+            exc_value = sys.exc_info()[1]
+            if not isinstance(exc_value, testtools.TestCase.skipException):
+                self.switch_role_history[key] = False
+                error_message = '`switchToRbacRole` must not be called with '\
+                    'the same bool value twice. Make sure that you included '\
+                    'a rbac_utils.switch_role method call inside the test.'
+                LOG.error(error_message)
+                raise rbac_exceptions.RbacResourceSetupFailed(error_message)
+        else:
+            self.switch_role_history[key] = switchToRbacRole
+
+    def _get_roles(self):
+        available_roles = self.roles_client.list_roles()
+        admin_role_id = rbac_role_id = None
+
+        for role in available_roles['roles']:
+            if role['name'] == CONF.rbac.rbac_test_role:
+                rbac_role_id = role['id']
+            if role['name'] == 'admin':
+                admin_role_id = role['id']
+
+        if not admin_role_id or not rbac_role_id:
+            msg = "Role with name 'admin' does not exist in the system."\
+                if not admin_role_id else "Role defined by rbac_test_role "\
+                "does not exist in the system."
+            raise rbac_exceptions.RbacResourceSetupFailed(msg)
+
+        self.admin_role_id = admin_role_id
+        self.rbac_role_id = rbac_role_id
 
 rbac_utils = RbacUtils
diff --git a/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
index 0de5e47..76120f6 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
@@ -13,16 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest import config
-from tempest import test
-
 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 AgentsRbacTest(rbac_base.BaseV2ComputeRbacTest):
 
@@ -31,7 +27,7 @@
         super(AgentsRbacTest, cls).skip_checks()
         if not test.is_extension_enabled('os-agents', 'compute'):
             raise cls.skipException(
-                '%s skipped as no compute extensions enabled' % cls.__name__)
+                '%s skipped as os-agents not enabled' % cls.__name__)
 
     @rbac_rule_validation.action(
         service="nova", rule="os_compute_api:os-agents")
diff --git a/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
new file mode 100644
index 0000000..674eeb1
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
@@ -0,0 +1,167 @@
+# 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.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.compute import rbac_base
+
+CONF = config.CONF
+
+
+class ImagesV235RbacTest(rbac_base.BaseV2ComputeRbacTest):
+    """RBAC tests for the Nova images client.
+
+    These APIs are proxy calls to the Image service. Consequently, no nova
+    policy actions are enforced; instead, only glance policy actions are
+    enforced. As such, these tests check that only glance policy actions are
+    executed.
+    """
+
+    # These tests will fail with a 404 starting from microversion 2.36.
+    min_microversion = '2.10'
+    max_microversion = '2.35'
+
+    @classmethod
+    def skip_checks(cls):
+        super(ImagesV235RbacTest, cls).skip_checks()
+        if not CONF.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(ImagesV235RbacTest, cls).setup_clients()
+        cls.client = cls.compute_images_client
+        cls.glance_image_client = cls.os.image_client_v2
+
+    @classmethod
+    def resource_setup(cls):
+        super(ImagesV235RbacTest, cls).resource_setup()
+        cls.image = cls.glance_image_client.create_image(
+            name=data_utils.rand_name('image'))
+
+    @classmethod
+    def resource_cleanup(cls):
+        cls.glance_image_client.delete_image(cls.image['id'])
+        super(ImagesV235RbacTest, cls).resource_cleanup()
+
+    @decorators.idempotent_id('b861f302-b72b-4055-81db-c62ff30b136d')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="get_images")
+    def test_list_images(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.list_images()
+
+    @decorators.idempotent_id('4365ae0f-15ee-4b54-a527-1679faaed140')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="get_images")
+    def test_list_images_with_details(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.list_images(detail=True)
+
+    @decorators.idempotent_id('886dfcae-51bf-4610-9e52-82d7189524c2')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="get_image")
+    def test_show_image_details(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.show_image(self.image['id'])
+
+    @decorators.idempotent_id('dbe09d4c-e615-48cb-b908-a06a0f410a8e')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="get_image")
+    def test_show_image_metadata_item(self):
+        self.client.set_image_metadata(self.image['id'], meta={'foo': 'bar'})
+        self.addCleanup(self.client.delete_image_metadata_item,
+                        self.image['id'], key='foo')
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.show_image_metadata_item(self.image['id'], key='foo')
+
+    @decorators.idempotent_id('59f66079-d564-47e8-81b0-03c2e84d339e')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="get_image")
+    def test_list_image_metadata(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.list_image_metadata(self.image['id'])
+
+    @decorators.idempotent_id('5888c7aa-0803-46d4-a3fb-5d4729465cd5')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="delete_image")
+    def test_delete_image(self):
+        image = self.glance_image_client.create_image(
+            name=data_utils.rand_name('image'))
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.glance_image_client.delete_image, image['id'])
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.delete_image(image['id'])
+
+    @decorators.idempotent_id('575604aa-909f-4b1b-a5a5-cfae1f63044b')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="modify_image")
+    def test_create_image_metadata(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        # NOTE(felipemonteiro): Although the name of the client function
+        # appears wrong, it's actually correct: update_image_metadata does an
+        # http post.
+        self.client.update_image_metadata(self.image['id'],
+                                          meta={'foo': 'bar'})
+        self.addCleanup(self.client.delete_image_metadata_item,
+                        self.image['id'], key='foo')
+
+    @decorators.idempotent_id('fb8c4eb6-00e5-454c-b8bc-0e801ec369f1')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="modify_image")
+    def test_update_image_metadata(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.set_image_metadata(self.image['id'], meta={'foo': 'bar'})
+        self.addCleanup(self.client.delete_image_metadata_item,
+                        self.image['id'], key='foo')
+
+    @decorators.idempotent_id('9c7c2036-af9b-49a8-8ba1-09b027ee5def')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="modify_image")
+    def test_update_image_metadata_item(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.set_image_metadata_item(self.image['id'],
+                                            meta={'foo': 'bar'}, key='foo')
+        self.addCleanup(self.client.delete_image_metadata_item,
+                        self.image['id'], key='foo')
+
+    @decorators.idempotent_id('5f0dc4e6-0761-4613-9bde-0a6acdc78f46')
+    @rbac_rule_validation.action(
+        service="glance",
+        rule="modify_image")
+    def test_delete_image_metadata_item(self):
+        self.client.set_image_metadata(self.image['id'], meta={'foo': 'bar'})
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.client.delete_image_metadata_item,
+                        self.image['id'], key='foo')
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.delete_image_metadata_item(self.image['id'], key='foo')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_multinic_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_multinic_rbac.py
new file mode 100644
index 0000000..158fcae
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/compute/test_multinic_rbac.py
@@ -0,0 +1,69 @@
+# 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 MultinicV210RbacTest(rbac_base.BaseV2ComputeRbacTest):
+
+    min_microversion = '2.10'
+    max_microversion = '2.36'
+
+    @classmethod
+    def setup_clients(cls):
+        super(MultinicV210RbacTest, cls).setup_clients()
+        cls.client = cls.servers_client
+
+    @classmethod
+    def skip_checks(cls):
+        super(MultinicV210RbacTest, 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(MultinicV210RbacTest, cls).setup_credentials()
+
+    @classmethod
+    def resource_setup(cls):
+        super(MultinicV210RbacTest, 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, switchToRbacRole=True)
+        self.client.add_fixed_ip(self.server['id'],
+                                 networkId=network_id)
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py
index 5c1acce..44504cf 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py
@@ -43,12 +43,14 @@
         cls.rbac_utils.switch_role(cls, switchToRbacRole=False)
 
         cls.creds_client = cls.os.credentials_client
+        cls.consumers_client = cls.os.oauth_consumers_client
         cls.domains_client = cls.os.domains_client
         cls.endpoints_client = cls.os.endpoints_v3_client
         cls.groups_client = cls.os.groups_client
         cls.projects_client = cls.os.projects_client
         cls.policies_client = cls.os.policies_client
         cls.regions_client = cls.os.regions_client
+        cls.role_assignments_client = cls.os.role_assignments_client
         cls.roles_client = cls.os.roles_v3_client
         cls.services_client = cls.os.identity_services_v3_client
         cls.users_client = cls.os.users_v3_client
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
new file mode 100644
index 0000000..62a8084
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
@@ -0,0 +1,76 @@
+# 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.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.identity.v3 import rbac_base
+
+
+class IdentityConsumersV3AdminRbacTest(rbac_base.BaseIdentityV3RbacAdminTest):
+
+    def _create_consumer(self):
+        description = data_utils.rand_name('test_create_consumer')
+        consumer = self.consumers_client.create_consumer(
+            description)['consumer']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.consumers_client.delete_consumer,
+                        consumer['id'])
+        return consumer
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:create_consumer")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d970')
+    def test_create_consumer(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self._create_consumer()
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:delete_consumer")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d971')
+    def test_delete_consumer(self):
+        consumer = self._create_consumer()
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.consumers_client.delete_consumer(consumer['id'])
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:update_consumer")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d972')
+    def test_update_consumer(self):
+        consumer = self._create_consumer()
+        new_description = data_utils.rand_name('test_update_consumer')
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.consumers_client.update_consumer(consumer['id'],
+                                              new_description)
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:get_consumer")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d973')
+    def test_show_consumer(self):
+        consumer = self._create_consumer()
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.consumers_client.show_consumer(consumer['id'])
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:list_consumers")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d975')
+    def test_list_consumers(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.consumers_client.list_consumers()
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py
new file mode 100644
index 0000000..878edcb
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_role_assignments_rbac.py
@@ -0,0 +1,47 @@
+# 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 patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.identity.v3 import rbac_base
+
+
+class IdentityRoleAssignmentsV3AdminRbacTest(
+        rbac_base.BaseIdentityV3RbacAdminTest):
+
+    @classmethod
+    def setup_clients(cls):
+        super(IdentityRoleAssignmentsV3AdminRbacTest, cls).setup_clients()
+        cls.client = cls.role_assignments_client
+
+    @decorators.idempotent_id('afe57adb-1b9c-43d9-84a9-f0cf4c94e416')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:list_role_assignments")
+    def test_list_role_assignments(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.list_role_assignments()['role_assignments']
+
+    @decorators.idempotent_id('36c7a990-857e-415c-8717-38d7200a9894')
+    @rbac_rule_validation.action(
+        service="keystone",
+        rule="identity:list_role_assignments_for_tree")
+    def test_list_role_assignments_for_tree(self):
+        project = self.setup_test_project()
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.client.list_role_assignments(
+            include_subtree=True, **{'scope.project.id': project['id']})[
+            'role_assignments']
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py
index 4708b3f..40f7e3c 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_roles_rbac.py
@@ -30,6 +30,7 @@
         cls.project = cls.setup_test_project()
         cls.group = cls.setup_test_group()
         cls.role = cls.setup_test_role()
+        cls.implies_role = cls.setup_test_role()
         cls.user = cls.setup_test_user()
 
     @rbac_rule_validation.action(service="keystone",
@@ -267,3 +268,68 @@
         self.roles_client.list_group_roles_on_domain(
             self.domain['id'],
             self.group['id'])
+
+    @decorators.idempotent_id('2aef3eaa-8156-4962-a01d-c9bb0e499e15')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:create_implied_role")
+    def test_create_role_inference_rule(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.roles_client.create_role_inference_rule(
+            self.role['id'], self.implies_role['id'])['role_inference']
+        self.addCleanup(self.roles_client.delete_role_inference_rule,
+                        self.role['id'], self.implies_role['id'])
+
+    @decorators.idempotent_id('83f997b2-55c4-4894-b1f2-e175b19d1fa5')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:get_implied_role")
+    def test_show_role_inference_rule(self):
+        self.roles_client.create_role_inference_rule(
+            self.role['id'], self.implies_role['id'])
+        self.addCleanup(self.roles_client.delete_role_inference_rule,
+                        self.role['id'], self.implies_role['id'])
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.roles_client.show_role_inference_rule(
+            self.role['id'], self.implies_role['id'])['role_inference']
+
+    @decorators.idempotent_id('f7bb39bf-0b06-468e-a8b0-60a4fb1f258d')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:list_implied_roles")
+    def test_list_role_inferences_rules(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.roles_client.list_role_inferences_rules(self.role['id'])[
+            'role_inference']
+
+    @decorators.idempotent_id('eca2d502-09bb-45cd-9773-bce2e7bcddd1')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:check_implied_role")
+    def test_check_role_inference_rule(self):
+        self.roles_client.create_role_inference_rule(
+            self.role['id'], self.implies_role['id'])
+        self.addCleanup(self.roles_client.delete_role_inference_rule,
+                        self.role['id'], self.implies_role['id'])
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.roles_client.check_role_inference_rule(
+            self.role['id'], self.implies_role['id'])
+
+    @decorators.idempotent_id('13a5db1e-dd4a-4ca1-81ec-d5452aaaf54b')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:delete_implied_role")
+    def test_delete_role_inference_rule(self):
+        self.roles_client.create_role_inference_rule(
+            self.role['id'], self.implies_role['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.roles_client.delete_role_inference_rule,
+                        self.role['id'], self.implies_role['id'])
+
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.roles_client.delete_role_inference_rule(
+            self.role['id'], self.implies_role['id'])
+
+    @decorators.idempotent_id('05869f2b-4dd4-425a-905e-eec9a6f06374')
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:list_role_inference_rules")
+    def test_list_all_role_inference_rules(self):
+        self.rbac_utils.switch_role(self, switchToRbacRole=True)
+        self.roles_client.list_all_role_inference_rules()['role_inferences']
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py
index 7d99d55..c53480d 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py
@@ -40,10 +40,6 @@
         cls.image_client = cls.os.image_client_v2
         cls.image_member_client = cls.os.image_member_client_v2
 
-    def setUp(self):
-        self.rbac_utils.switch_role(self, switchToRbacRole=False)
-        super(ImagesMemberRbacTest, self).setUp()
-
     @rbac_rule_validation.action(service="glance",
                                  rule="add_member")
     @decorators.idempotent_id('b1b85ace-6484-11e6-881e-080027d0d606')
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index add1770..692c0b9 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import mock
+import testtools
 
 from tempest import config
 from tempest.lib import exceptions as lib_exc
@@ -27,62 +28,87 @@
 
 class RBACUtilsTest(base.TestCase):
 
-    @mock.patch.object(rbac_utils, 'time', autospec=True)
-    def setUp(self, _):
+    def setUp(self):
         super(RBACUtilsTest, self).setUp()
-        self.mock_creds_provider = mock.patch.object(
-            rbac_utils, 'credentials_factory', autospec=True).start()
-
         available_roles = {
             'roles': [
                 {'name': 'admin', 'id': 'admin_id'},
                 {'name': 'Member', 'id': 'member_id'}
             ]
         }
-        self.mock_creds_provider.get_credentials_provider.return_value.\
-            creds_client.roles_client.list_roles.return_value = \
-            available_roles
-        self.addCleanup(mock.patch.stopall)
-
-        CONF.set_override('rbac_test_role', 'Member', group='rbac',
-                          enforce_type=True)
-        self.addCleanup(CONF.clear_override, 'rbac_test_role', group='rbac')
 
         # Because rbac_utils is a singleton, reset all of its role-related
         # parameters to the correct values for each test run.
         self.rbac_utils = rbac_utils.rbac_utils()
-        self.rbac_utils.available_roles = available_roles
+        self.rbac_utils.switch_role_history = {}
         self.rbac_utils.admin_role_id = 'admin_id'
         self.rbac_utils.rbac_role_id = 'member_id'
 
-    def test_initialization_with_missing_admin_role(self):
-        self.rbac_utils.admin_role_id = None
-        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
-                              self.rbac_utils.switch_role, None)
-        self.assertIn("Defined 'rbac_role' or 'admin' role does not exist"
-                      " in the system.", e.__str__())
+        self.mock_test_obj = mock.Mock()
+        self.mock_test_obj.auth_provider = mock.Mock(
+            **{'credentials.user_id': mock.sentinel.user_id,
+               'credentials.tenant_id': mock.sentinel.project_id})
+        self.mock_test_obj.os_admin = mock.Mock(
+            **{'roles_v3_client.list_roles.return_value': available_roles})
 
-    def test_initialization_with_missing_rbac_role(self):
+        CONF.set_override('rbac_test_role', 'Member', group='rbac',
+                          enforce_type=True)
+        CONF.set_override('auth_version', 'v3', group='identity',
+                          enforce_type=True)
+
+        self.addCleanup(CONF.clear_override, 'rbac_test_role', group='rbac')
+        self.addCleanup(CONF.clear_override, 'auth_version', group='identity')
+        self.addCleanup(mock.patch.stopall)
+
+    def _mock_list_user_roles_on_project(self, return_value):
+        self.mock_test_obj.admin_manager = mock.Mock(
+            **{'roles_client.list_user_roles_on_project.'
+               'return_value': {'roles': [{'id': return_value}]}})
+
+    @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
+                       autospec=True, return_value=False)
+    def test_initialization_with_missing_admin_role(self, _):
+        self.mock_test_obj.os_admin = mock.Mock(
+            **{'roles_v3_client.list_roles.return_value':
+               {'roles': [{'name': 'Member', 'id': 'member_id'}]}})
+        self.rbac_utils.admin_role_id = None
         self.rbac_utils.rbac_role_id = None
         e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
-                              self.rbac_utils.switch_role, None)
-        self.assertIn("Defined 'rbac_role' or 'admin' role does not exist"
-                      " in the system.", e.__str__())
+                              self.rbac_utils.switch_role, self.mock_test_obj,
+                              True)
+        self.assertIn("Role with name 'admin' does not exist in the system.",
+                      e.__str__())
+
+    @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
+                       autospec=True, return_value=False)
+    def test_initialization_with_missing_rbac_role(self, _):
+        self.mock_test_obj.os_admin = mock.Mock(
+            **{'roles_v3_client.list_roles.return_value':
+               {'roles': [{'name': 'admin', 'id': 'admin_id'}]}})
+        self.rbac_utils.admin_role_id = None
+        self.rbac_utils.rbac_role_id = None
+        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
+                              self.rbac_utils.switch_role, self.mock_test_obj,
+                              True)
+        self.assertIn("Role defined by rbac_test_role does not exist in the "
+                      "system.", e.__str__())
 
     def test_clear_user_roles(self):
-        self.rbac_utils.creds_client = mock.Mock()
-        creds_client = self.rbac_utils.creds_client
-        creds_client.roles_client.list_user_roles_on_project.return_value = {
+        roles_client = self.mock_test_obj.os_admin.roles_v3_client
+        roles_client.list_user_roles_on_project.return_value = {
             'roles': [{'id': 'admin_id'}, {'id': 'member_id'}]
         }
 
-        self.rbac_utils._clear_user_roles(mock.sentinel.user_id,
-                                          mock.sentinel.project_id)
+        self.rbac_utils.roles_client = roles_client
+        self.rbac_utils.project_id = mock.sentinel.project_id
+        self.rbac_utils.user_id = mock.sentinel.user_id
 
-        creds_client.roles_client.list_user_roles_on_project.\
+        self.rbac_utils._clear_user_roles(None)
+
+        roles_client.list_user_roles_on_project.\
             assert_called_once_with(mock.sentinel.project_id,
                                     mock.sentinel.user_id)
-        creds_client.roles_client.delete_role_from_user_on_project.\
+        roles_client.delete_role_from_user_on_project.\
             assert_has_calls([
                 mock.call(mock.sentinel.project_id, mock.sentinel.user_id,
                           'admin_id'),
@@ -91,65 +117,109 @@
             ])
 
     @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
-                       autospec=True)
-    def test_rbac_utils_switch_role_to_admin(self, mock_clear_user_roles):
-        mock_test_object = mock.Mock()
-        mock_test_object.auth_provider.credentials.user_id = \
-            mock.sentinel.user_id
-        mock_test_object.auth_provider.credentials.tenant_id = \
-            mock.sentinel.project_id
+                       autospec=True, return_value=False)
+    @mock.patch.object(rbac_utils, 'time', autospec=True)
+    def test_rbac_utils_switch_role_to_admin_role(self, mock_time,
+                                                  mock_clear_user_roles):
+        self.rbac_utils.prev_switch_role = True
+        self._mock_list_user_roles_on_project('admin_id')
+        roles_client = self.mock_test_obj.os_admin.roles_v3_client
 
-        self.rbac_utils.creds_client = mock.Mock()
-        creds_client = self.rbac_utils.creds_client
+        self.rbac_utils.switch_role(self.mock_test_obj, False)
 
-        self.rbac_utils.switch_role(mock_test_object, False)
-
-        creds_client.roles_client.create_user_role_on_project.\
+        roles_client.create_user_role_on_project.\
             assert_called_once_with(mock.sentinel.project_id,
                                     mock.sentinel.user_id,
                                     'admin_id')
         mock_clear_user_roles.assert_called_once_with(
-            self.rbac_utils, mock.sentinel.user_id, mock.sentinel.project_id)
-        mock_test_object.auth_provider.clear_auth.assert_called_once_with()
-        mock_test_object.auth_provider.set_auth.assert_called_once_with()
+            self.rbac_utils, 'admin_id')
+        self.mock_test_obj.auth_provider.clear_auth.assert_called_once_with()
+        self.mock_test_obj.auth_provider.set_auth.assert_called_once_with()
+        mock_time.sleep.assert_called_once_with(1)
 
     @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
-                       autospec=True)
-    def test_rbac_utils_switch_role_to_rbac_role(self, mock_clear_user_roles):
-        mock_test_object = mock.Mock()
-        mock_test_object.auth_provider.credentials.user_id = \
-            mock.sentinel.user_id
-        mock_test_object.auth_provider.credentials.tenant_id = \
-            mock.sentinel.project_id
+                       autospec=True, return_value=False)
+    @mock.patch.object(rbac_utils, 'time', autospec=True)
+    def test_rbac_utils_switch_role_to_rbac_role(self, mock_time,
+                                                 mock_clear_user_roles):
+        self._mock_list_user_roles_on_project('member_id')
+        roles_client = self.mock_test_obj.os_admin.roles_v3_client
 
-        self.rbac_utils.creds_client = mock.Mock()
-        creds_client = self.rbac_utils.creds_client
+        self.rbac_utils.switch_role(self.mock_test_obj, True)
 
-        self.rbac_utils.switch_role(mock_test_object, True)
-
-        creds_client.roles_client.create_user_role_on_project.\
+        roles_client.create_user_role_on_project.\
             assert_called_once_with(mock.sentinel.project_id,
                                     mock.sentinel.user_id,
                                     'member_id')
         mock_clear_user_roles.assert_called_once_with(
-            self.rbac_utils, mock.sentinel.user_id, mock.sentinel.project_id)
-        mock_test_object.auth_provider.clear_auth.assert_called_once_with()
-        mock_test_object.auth_provider.set_auth.assert_called_once_with()
+            self.rbac_utils, 'member_id')
+        self.mock_test_obj.auth_provider.clear_auth.assert_called_once_with()
+        self.mock_test_obj.auth_provider.set_auth.assert_called_once_with()
+        mock_time.sleep.assert_called_once_with(1)
 
-    def test_rbac_utils_switch_roles_with_invalid_value(self):
-        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
-                              self.rbac_utils.switch_role, None)
-        self.assertIn("Wrong value for parameter 'switchToRbacRole' is passed."
-                      " It should be either 'True' or 'False'.", e.__str__())
+    def test_RBAC_utils_switch_roles_without_boolean_value(self):
+        self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
+                          self.rbac_utils.switch_role, self.mock_test_obj,
+                          "admin")
+        self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
+                          self.rbac_utils.switch_role, self.mock_test_obj,
+                          None)
 
     @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
-                       autospec=True)
+                       autospec=True, return_value=False)
+    def test_rbac_utils_switch_roles_with_false_value_twice(self, _):
+        self._mock_list_user_roles_on_project('admin_id')
+        self.rbac_utils.switch_role(self.mock_test_obj, False)
+        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
+                              self.rbac_utils.switch_role, self.mock_test_obj,
+                              False)
+        self.assertIn(
+            '`switchToRbacRole` must not be called with the same bool value '
+            'twice. Make sure that you included a rbac_utils.switch_role '
+            'method call inside the test.', str(e))
+
+    @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
+                       autospec=True, return_value=False)
+    def test_rbac_utils_switch_roles_with_true_value_twice(self, _):
+        self._mock_list_user_roles_on_project('admin_id')
+        self.rbac_utils.switch_role(self.mock_test_obj, True)
+        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
+                              self.rbac_utils.switch_role, self.mock_test_obj,
+                              True)
+        self.assertIn(
+            '`switchToRbacRole` must not be called with the same bool value '
+            'twice. Make sure that you included a rbac_utils.switch_role '
+            'method call inside the test.', str(e))
+
+    @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
+                       autospec=True, return_value=False)
+    @mock.patch.object(rbac_utils, 'LOG', autospec=True)
+    @mock.patch.object(rbac_utils, 'sys', autospec=True)
+    def test_rbac_utils_switch_roles_with_skip_exception(self, mock_sys,
+                                                         mock_log, _):
+        self._mock_list_user_roles_on_project('member_id')
+
+        mock_skip_exception = mock.Mock(spec=testtools.TestCase.skipException)
+        mock_sys.exc_info.return_value = [None, mock_skip_exception]
+
+        # Ordinarily switching to the same role would result in an error,
+        # but because the skipException is thrown before the test finishes,
+        # this is not treated as a failure.
+        self.rbac_utils.switch_role(self.mock_test_obj, False)
+        self.rbac_utils.switch_role(self.mock_test_obj, False)
+        mock_log.error.assert_not_called()
+
+        self.rbac_utils.switch_role(self.mock_test_obj, True)
+        self.rbac_utils.switch_role(self.mock_test_obj, True)
+        mock_log.error.assert_not_called()
+
+    @mock.patch.object(rbac_utils.rbac_utils, '_clear_user_roles',
+                       autospec=True, return_value=False)
     def test_rbac_utils_switch_role_except_exception(self,
                                                      mock_clear_user_roles):
-        self.rbac_utils.creds_client = mock.Mock()
-        creds_client = self.rbac_utils.creds_client
-        creds_client.roles_client.create_user_role_on_project.side_effect =\
+        roles_client = self.mock_test_obj.os_admin.roles_v3_client
+        roles_client.create_user_role_on_project.side_effect =\
             lib_exc.NotFound
 
         self.assertRaises(lib_exc.NotFound, self.rbac_utils.switch_role,
-                          mock.Mock(), True)
+                          self.mock_test_obj, True)
diff --git a/setup.cfg b/setup.cfg
index a8732dc..09b71eb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -18,6 +18,7 @@
     Programming Language :: Python :: 3
     Programming Language :: Python :: 3.3
     Programming Language :: Python :: 3.4
+    Programming Language :: Python :: 3.5
 
 [files]
 packages =