Merge "Add RBAC test for updating volume group"
diff --git a/.testr.conf b/.testr.conf
index 6d83b3c..87d049d 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,6 +2,6 @@
 test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
              OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
              OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
-             ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
+             ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./patrole_tempest_plugin/tests/unit} $LISTOPT $IDOPTION
 test_id_option=--load-list $IDFILE
 test_list_option=--list
diff --git a/HACKING.rst b/HACKING.rst
index 5281b53..f89910b 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,15 +1,15 @@
 Patrole Style Commandments
 ==========================
 
-- Step 1: Read the OpenStack Style Commandments: `<https://docs.openstack.org/developer/hacking/>`__
-- Step 2: Review Tempest's Style Commandments: `<https://docs.openstack.org/developer/tempest/HACKING.html>`__
+- Step 1: Read the OpenStack Style Commandments: `<https://docs.openstack.org/hacking/latest/>`__
+- Step 2: Review Tempest's Style Commandments: `<https://docs.openstack.org/tempest/latest/HACKING.html>`__
 - Step 3: Read on
 
 Patrole Specific Commandments
 ------------------------------
 
 Patrole borrows the following commandments from Tempest; refer to
-`Tempest's Commandments <https://docs.openstack.org/developer/tempest/HACKING.html>`__
+`Tempest's Commandments <https://docs.openstack.org/tempest/latest/HACKING.html>`__
 for more information:
 
 .. note::
diff --git a/patrole_tempest_plugin/rbac_exceptions.py b/patrole_tempest_plugin/rbac_exceptions.py
index 5ccb216..5ee65ae 100644
--- a/patrole_tempest_plugin/rbac_exceptions.py
+++ b/patrole_tempest_plugin/rbac_exceptions.py
@@ -16,8 +16,25 @@
 from tempest.lib import exceptions
 
 
-class RbacActionFailed(exceptions.ClientRestClientException):
-    message = "Rbac action failed"
+class RbacConflictingPolicies(exceptions.TempestException):
+    message = ("Conflicting policies preventing this action from being "
+               "performed.")
+
+
+class RbacMalformedResponse(exceptions.TempestException):
+    message = ("The response body is missing the expected %(attribute)s due "
+               "to policy enforcement failure.")
+
+    def __init__(self, empty=False, extra_attr=False, **kwargs):
+        if empty:
+            self.message = ("The response body is empty due to policy "
+                            "enforcement failure.")
+            kwargs = {}
+        if extra_attr:
+            self.message = ("The response body contained an unexpected "
+                            "attribute due to policy enforcement failure.")
+            kwargs = {}
+        super(RbacMalformedResponse, self).__init__(**kwargs)
 
 
 class RbacResourceSetupFailed(exceptions.TempestException):
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 254bb18..88e9faa 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -20,7 +20,7 @@
 from oslo_log import log as logging
 from oslo_policy import policy
 import stevedore
-
+from tempest import clients
 from tempest.common import credentials_factory as credentials
 from tempest import config
 
@@ -93,7 +93,8 @@
         # Cache the list of available services in memory to avoid needlessly
         # doing an API call every time.
         if not hasattr(cls, 'available_services'):
-            admin_mgr = credentials.AdminManager()
+            admin_mgr = clients.Manager(
+                credentials.get_configured_admin_credentials())
             services_client = (admin_mgr.identity_services_v3_client
                                if CONF.identity_feature_enabled.api_v3
                                else admin_mgr.identity_services_client)
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index 51b9d92..c7bd38b 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -25,6 +25,7 @@
 
 from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_policy_parser
+from patrole_tempest_plugin import rbac_utils
 from patrole_tempest_plugin import requirements_authority
 
 CONF = config.CONF
@@ -85,7 +86,7 @@
                 LOG.info("As admin_only is True, only admin role should be "
                          "allowed to perform the API. Skipping oslo.policy "
                          "check for policy action {0}.".format(rule))
-                allowed = test_obj.rbac_utils.is_admin
+                allowed = rbac_utils.is_admin()
             else:
                 allowed = _is_authorized(test_obj, service, rule,
                                          extra_target_data)
@@ -100,7 +101,9 @@
                 LOG.error(msg)
                 raise exceptions.NotFound(
                     "%s RbacInvalidService was: %s" % (msg, e))
-            except (expected_exception, rbac_exceptions.RbacActionFailed) as e:
+            except (expected_exception,
+                    rbac_exceptions.RbacConflictingPolicies,
+                    rbac_exceptions.RbacMalformedResponse) as e:
                 if irregular_msg:
                     LOG.warning(irregular_msg.format(rule, service))
                 if allowed:
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 9d7a807..5543cbb 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -19,7 +19,7 @@
 import time
 
 from oslo_log import log as logging
-import oslo_utils.uuidutils as uuid_utils
+from oslo_utils import excutils
 import testtools
 
 from tempest.common import credentials_factory as credentials
@@ -39,15 +39,13 @@
     seamlessly swap between admin credentials, needed for setup and clean up,
     and primary credentials, needed to perform the API call which does
     policy enforcement. The primary credentials always cycle between roles
-    defined by ``CONF.identity.admin_role`` and `CONF.rbac.rbac_test_role``.
+    defined by ``[identity] admin_role`` and ``[rbac] rbac_test_role``.
     """
 
     def __init__(self, test_obj):
         """Constructor for ``RbacUtils``.
 
-        :param test_obj: A Tempest test instance.
-        :type test_obj: tempest.lib.base.BaseTestCase or
-            tempest.test.BaseTestCase
+        :param test_obj: An instance of `tempest.test.BaseTestCase`.
         """
         # Since we are going to instantiate a client manager with
         # admin credentials, first check if admin is available.
@@ -80,56 +78,83 @@
 
         Switch the role used by `os_primary` credentials to:
           * admin if `toggle_rbac_role` is False
-          * `CONF.rbac.rbac_test_role` if `toggle_rbac_role` is True
+          * `[rbac] rbac_test_role` if `toggle_rbac_role` is True
 
-        :param test_obj: test object of type tempest.lib.base.BaseTestCase
-        :param toggle_rbac_role: role to switch `os_primary` Tempest creds to
+        :param test_obj: An instance of `tempest.test.BaseTestCase`.
+        :param toggle_rbac_role: Role to switch `os_primary` Tempest creds to.
+        :returns: None.
+        :raises RbacResourceSetupFailed: If admin or test roles are missing. Or
+            if `toggle_rbac_role` is not a boolean value or role validation
+            fails.
         """
         self.user_id = test_obj.os_primary.credentials.user_id
         self.project_id = test_obj.os_primary.credentials.tenant_id
         self.token = test_obj.os_primary.auth_provider.get_token()
 
         LOG.debug('Switching role to: %s.', toggle_rbac_role)
+        role_already_present = False
+
         try:
-            if not self.admin_role_id or not self.rbac_role_id:
-                self._get_roles()
+            if not all([self.admin_role_id, self.rbac_role_id]):
+                self._get_roles_by_name()
 
             self._validate_switch_role(test_obj, toggle_rbac_role)
 
-            if toggle_rbac_role:
-                self._add_role_to_user(self.rbac_role_id)
-            else:
-                self._add_role_to_user(self.admin_role_id)
+            target_role = (
+                self.rbac_role_id if toggle_rbac_role else self.admin_role_id)
+            role_already_present = self._list_and_clear_user_roles_on_project(
+                target_role)
+
+            # Do not switch roles if `target_role` already exists.
+            if not role_already_present:
+                self._create_user_role_on_project(target_role)
         except Exception as exp:
-            LOG.exception(exp)
-            raise
+            with excutils.save_and_reraise_exception():
+                LOG.exception(exp)
         finally:
             test_obj.os_primary.auth_provider.clear_auth()
             # Fernet tokens are not subsecond aware so sleep to ensure we are
             # passing the second boundary before attempting to authenticate.
-            #
-            # FIXME(felipemonteiro): Rather than skipping sleep if the token
-            # is not uuid, this should instead be skipped if the token is not
-            # Fernet.
-            if not uuid_utils.is_uuid_like(self.token):
+            # Only sleep if a token revocation occurred as a result of role
+            # switching. This will optimize test runtime in the case where
+            # ``[identity] admin_role`` == ``[rbac] rbac_test_role``.
+            if not role_already_present:
                 time.sleep(1)
             test_obj.os_primary.auth_provider.set_auth()
 
-    def _add_role_to_user(self, role_id):
-        role_already_present = self._clear_user_roles(role_id)
-        if role_already_present:
-            return
+    def _get_roles_by_name(self):
+        available_roles = self.admin_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'] == CONF.identity.admin_role:
+                admin_role_id = role['id']
+
+        if not all([admin_role_id, rbac_role_id]):
+            msg = ("Roles defined by `[rbac] rbac_test_role` and `[identity] "
+                   "admin_role` must be defined in the system.")
+            raise rbac_exceptions.RbacResourceSetupFailed(msg)
+
+        self.admin_role_id = admin_role_id
+        self.rbac_role_id = rbac_role_id
+
+    def _create_user_role_on_project(self, role_id):
         self.admin_roles_client.create_user_role_on_project(
             self.project_id, self.user_id, role_id)
 
-    def _clear_user_roles(self, role_id):
+    def _list_and_clear_user_roles_on_project(self, role_id):
         roles = self.admin_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]:
+
+        # NOTE(felipemonteiro): We do not use ``role_id in role_ids`` here to
+        # avoid over-permission errors: if the current list of roles on the
+        # project includes "admin" and "Member", and we are switching to the
+        # "Member" role, then we must delete the "admin" role. Thus, we only
+        # return early if the user's roles on the project are an exact match.
+        if [role_id] == role_ids:
             return True
 
         for role in roles:
@@ -139,11 +164,11 @@
         return False
 
     def _validate_switch_role(self, test_obj, toggle_rbac_role):
-        """Validates that the rbac role passed to `switch_role` is legal.
+        """Validates that the test 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 never called inside a test, except in tearDown
             * `switch_role` is called with the same boolean value twice
 
         If a `skipException` is thrown then this is a legitimate reason why
@@ -151,7 +176,7 @@
         """
         if not isinstance(toggle_rbac_role, bool):
             raise rbac_exceptions.RbacResourceSetupFailed(
-                'toggle_rbac_role must be a boolean value.')
+                '`toggle_rbac_role` 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 \
@@ -176,32 +201,13 @@
         else:
             self.switch_role_history[key] = toggle_rbac_role
 
-    def _get_roles(self):
-        available_roles = self.admin_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'] == CONF.identity.admin_role:
-                admin_role_id = role['id']
+def is_admin():
+    """Verifies whether the current test role equals the admin role.
 
-        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
-
-    @property
-    def is_admin(self):
-        """Verifies whether the current test role equals the admin role.
-
-        :returns: True if ``rbac_test_role`` is the admin role.
-        """
-        return CONF.rbac.rbac_test_role == CONF.identity.admin_role
+    :returns: True if ``rbac_test_role`` is the admin role.
+    """
+    return CONF.rbac.rbac_test_role == CONF.identity.admin_role
 
 
 @six.add_metaclass(abc.ABCMeta)
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 f355358..4712ed0 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
@@ -13,6 +13,8 @@
 #    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 tempest import test
 
@@ -29,6 +31,16 @@
             raise cls.skipException(
                 '%s skipped as os-agents not enabled' % cls.__name__)
 
+    def _param_helper(self, **kwargs):
+        rand_key = 'architecture'
+        if rand_key in kwargs:
+            # NOTE: The rand_name is for avoiding agent conflicts.
+            # If you try to create an agent with the same hypervisor,
+            # os and architecture as an existing agent, Nova will return
+            # an HTTPConflict or HTTPServerError.
+            kwargs[rand_key] = data_utils.rand_name(kwargs[rand_key])
+        return kwargs
+
     @rbac_rule_validation.action(
         service="nova", rule="os_compute_api:os-agents")
     @decorators.idempotent_id('d1bc6d97-07f5-4f45-ac29-1c619a6a7e27')
@@ -48,3 +60,41 @@
         body = self.agents_client.create_agent(**params)['agent']
         self.addCleanup(self.agents_client.delete_agent,
                         body['agent_id'])
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-agents")
+    @decorators.idempotent_id('b22f2681-9ffb-439b-b240-dae503e41020')
+    def test_update_agent(self):
+        params = self._param_helper(
+            hypervisor='common', os='linux',
+            architecture='x86_64', version='7.0',
+            url='xxx://xxxx/xxx/xxx',
+            md5hash='add6bb58e139be103324d04d82d8f545')
+        body = self.agents_client.create_agent(**params)['agent']
+        self.addCleanup(self.agents_client.delete_agent,
+                        body['agent_id'])
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        update_params = self._param_helper(
+            version='8.0',
+            url='xxx://xxxx/xxx/xxx2',
+            md5hash='add6bb58e139be103324d04d82d8f547')
+        self.agents_client.update_agent(body['agent_id'], **update_params)
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-agents")
+    @decorators.idempotent_id('c5042af8-0682-43b0-abc4-bf33349e23dd')
+    def test_delete_agent(self):
+        params = self._param_helper(
+            hypervisor='common', os='linux',
+            architecture='x86_64', version='7.0',
+            url='xxx://xxxx/xxx/xxx',
+            md5hash='add6bb58e139be103324d04d82d8f545')
+        body = self.agents_client.create_agent(**params)['agent']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.agents_client.delete_agent,
+                        body['agent_id'])
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.agents_client.delete_agent(body['agent_id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index cb32eec..0fd5c63 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
@@ -340,6 +340,5 @@
         server = self.servers_client.show_server(self.server_id)['server']
 
         if 'host_status' not in server:
-            LOG.info("host_status attribute not returned when role doesn't "
-                     "have permission to access it.")
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='host_status')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
index e942f08..b1956c2 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
@@ -137,8 +137,8 @@
         body = self.servers_client.list_servers(detail=True)['servers']
         # If the first server contains "config_drive", then all the others do.
         if 'config_drive' not in body[0]:
-            raise rbac_exceptions.RbacActionFailed(
-                '"config_drive" attribute not found in response body.')
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='config_drive')
 
     @test.requires_ext(extension='os-config-drive', service='compute')
     @decorators.idempotent_id('55c62ef7-b72b-4970-acc6-05b0a4316e5d')
@@ -150,8 +150,8 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         body = self.servers_client.show_server(self.server['id'])['server']
         if 'config_drive' not in body:
-            raise rbac_exceptions.RbacActionFailed(
-                '"config_drive" attribute not found in response body.')
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute="config_drive")
 
     @test.requires_ext(extension='os-deferred-delete', service='compute')
     @decorators.idempotent_id('189bfed4-1e6d-475c-bb8c-d57e60895391')
@@ -192,14 +192,47 @@
             self.server['id'], request_id)['instanceAction']
 
         if 'events' not in instance_action:
-            raise rbac_exceptions.RbacActionFailed(
-                '"%s" attribute not found in response body.' % 'events')
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='events')
         # Microversion 2.51+ returns 'events' always, but not 'traceback'. If
         # 'traceback' is also present then policy enforcement passed.
         if 'traceback' not in instance_action['events'][0]:
-            raise rbac_exceptions.RbacActionFailed(
-                '"%s" attribute not found in response body.'
-                % 'events.traceback')
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='events.traceback')
+
+    @decorators.idempotent_id('82053c27-3134-4003-9b55-bc9fafdb0e3b')
+    @test.requires_ext(extension='OS-EXT-STS', service='compute')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-extended-status")
+    def test_list_servers_extended_status(self):
+        """Test list servers with extended properties in response body."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        body = self.servers_client.list_servers(detail=True)['servers']
+
+        expected_attrs = ('OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state',
+                          'OS-EXT-STS:power_state')
+        for attr in expected_attrs:
+            if attr not in body[0]:
+                raise rbac_exceptions.RbacMalformedResponse(
+                    attribute=attr)
+
+    @decorators.idempotent_id('7d2620a5-eea1-4a8b-96ea-86ad77a73fc8')
+    @test.requires_ext(extension='OS-EXT-STS', service='compute')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-extended-status")
+    def test_show_server_extended_status(self):
+        """Test show server with extended properties in response body."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        body = self.servers_client.show_server(self.server['id'])['server']
+
+        expected_attrs = ('OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state',
+                          'OS-EXT-STS:power_state')
+        for attr in expected_attrs:
+            if attr not in body:
+                raise rbac_exceptions.RbacMalformedResponse(
+                    attribute=attr)
 
     @rbac_rule_validation.action(
         service="nova",
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
index 5539221..10ea801 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
@@ -184,7 +184,7 @@
             # Some other policy may have blocked it.
             LOG.info("ServerFault exception caught. Some other policy "
                      "blocked updating of server")
-            raise rbac_exceptions.RbacActionFailed(e)
+            raise rbac_exceptions.RbacConflictingPolicies(e)
 
 
 class SecurtiyGroupsRbacTest(base.BaseV2ComputeRbacTest):
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 e733e26..db1f6e6 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
@@ -18,6 +18,7 @@
 
 from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin import rbac_utils
 from patrole_tempest_plugin.tests.api.identity import rbac_base
 
 CONF = config.CONF
@@ -112,7 +113,7 @@
         admin-scoped tenants, raise ``RbacActionFailed`` exception otherwise.
         """
         tenants_client = self.os_admin.tenants_client if \
-            self.rbac_utils.is_admin else self.os_primary.tenants_client
+            rbac_utils.is_admin() else self.os_primary.tenants_client
         admin_tenant_id = self.os_admin.credentials.project_id
         non_admin_tenant_id = self.os_primary.credentials.project_id
 
@@ -121,10 +122,8 @@
 
         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``.")
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute="admin tenant id")
         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``.")
+            raise rbac_exceptions.RbacMalformedResponse(
+                extra_attr=True)
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
index 90952a8..18e5bf1 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
@@ -18,6 +18,7 @@
 from tempest.lib import exceptions as lib_exc
 
 from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin import rbac_utils
 from patrole_tempest_plugin.tests.api.identity import rbac_base
 
 CONF = config.CONF
@@ -31,8 +32,8 @@
     def skip_checks(cls):
         super(IdentityTokenV3RbacTest, cls).skip_checks()
         # In case of admin, the positive testcase would be used, hence
-        # skipping negative testcase
-        if CONF.rbac.rbac_test_role == CONF.identity.admin_role:
+        # skipping negative testcase.
+        if rbac_utils.is_admin():
             raise cls.skipException(
                 "Skipped as admin role doesn't require negative testing")
 
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
index 01cf0fd..9d2b67a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
@@ -95,6 +95,5 @@
         if len(response_network) == 0:
             LOG.info("NotFound or Forbidden exception are not thrown when "
                      "role doesn't have access to the endpoint. Instead, "
-                     "the response will have an empty network body. "
-                     "This is irregular and should be fixed.")
-            raise rbac_exceptions.RbacActionFailed
+                     "the response will have an empty network body.")
+            raise rbac_exceptions.RbacMalformedResponse(True)
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
index b42be93..148804e 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -238,7 +238,7 @@
             self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(True)
 
     @test.requires_ext(extension='provider', service='network')
     @rbac_rule_validation.action(service="neutron",
@@ -257,7 +257,7 @@
             self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(empty=True)
 
     @test.requires_ext(extension='provider', service='network')
     @rbac_rule_validation.action(service="neutron",
@@ -276,7 +276,7 @@
             self.network['id'], **kwargs)['network']
 
         if len(retrieved_network) == 0:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(empty=True)
 
         key = retrieved_network.get('provider:segmentation_id', "NotFound")
         self.assertNotEqual(key, "NotFound")
diff --git a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
index cec860c..888f879 100644
--- a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
@@ -170,7 +170,8 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='binding:vif_type')
 
     @test.requires_ext(extension='binding', service='network')
     @rbac_rule_validation.action(service="neutron",
@@ -188,7 +189,8 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='binding:vif_details')
 
     @test.requires_ext(extension='binding', service='network')
     @rbac_rule_validation.action(service="neutron",
@@ -208,7 +210,8 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='binding:host_id')
 
     @test.requires_ext(extension='binding', service='network')
     @rbac_rule_validation.action(service="neutron",
@@ -229,7 +232,8 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if fields[0] not in retrieved_port:
-            raise rbac_exceptions.RbacActionFailed
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='binding:profile')
 
     @rbac_rule_validation.action(service="neutron",
                                  rule="update_port")
diff --git a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
index 6aec5d1..9ed9eb6 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -173,8 +173,8 @@
 
         # Rather than throwing a 403, the field is not present, so raise exc.
         if 'distributed' not in retrieved_fields:
-            raise rbac_exceptions.RbacActionFailed(
-                '"distributed" parameter not present in response body')
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='distributed')
 
     @rbac_rule_validation.action(
         service="neutron", rule="update_router")
diff --git a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
index 606b58a..20f20a5 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
@@ -140,9 +140,8 @@
         group_type = self.create_group_type(ignore_notfound=True)
 
         if 'group_specs' not in group_type:
-            raise rbac_exceptions.RbacActionFailed(
-                'Policy %s does not return %s in response body.' %
-                ('group:access_group_types_specs', 'group_specs'))
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute='group_specs')
 
     @decorators.idempotent_id('f77f8156-4fc9-4f02-be15-8930f748e10c')
     @rbac_rule_validation.action(
diff --git a/patrole_tempest_plugin/tests/unit/fixtures.py b/patrole_tempest_plugin/tests/unit/fixtures.py
index f25e05d..ed50e15 100644
--- a/patrole_tempest_plugin/tests/unit/fixtures.py
+++ b/patrole_tempest_plugin/tests/unit/fixtures.py
@@ -53,15 +53,11 @@
 
 
 class RbacUtilsFixture(fixtures.Fixture):
-    """Fixture for RbacUtils class."""
+    """Fixture for `RbacUtils` class."""
 
     USER_ID = mock.sentinel.user_id
     PROJECT_ID = mock.sentinel.project_id
 
-    def __init__(self, **kwargs):
-        super(RbacUtilsFixture, self).__init__()
-        self.available_roles = None
-
     def setUp(self):
         super(RbacUtilsFixture, self).setUp()
 
@@ -81,29 +77,54 @@
         self.roles_v3_client = (
             self.mock_test_obj.get_client_manager.return_value.roles_v3_client)
 
+        self.set_roles(['admin', 'member'], [])
+
     def switch_role(self, *role_toggles):
         """Instantiate `rbac_utils.RbacUtils` and call `switch_role`.
 
         Create an instance of `rbac_utils.RbacUtils` and call `switch_role`
         for each boolean value in `role_toggles`. The number of calls to
-        `switch_role` is always 1 + len(role_toggles) because the
+        `switch_role` is always 1 + len(`role_toggles`) because the
         `rbac_utils.RbacUtils` constructor automatically calls `switch_role`.
 
         :param role_toggles: the list of boolean values iterated over and
             passed to `switch_role`.
         """
-        if not self.available_roles:
-            self.set_roles('admin', 'member')
-
         self.fake_rbac_utils = rbac_utils.RbacUtils(self.mock_test_obj)
+
         for role_toggle in role_toggles:
             self.fake_rbac_utils.switch_role(self.mock_test_obj, role_toggle)
+            # NOTE(felipemonteiro): Simulate that a role switch has occurred
+            # by updating the user's current role to the new role. This means
+            # that all API actions involved during a role switch -- listing,
+            # deleting and adding roles -- are executed, making it easier to
+            # assert that mock calls were called as expected.
+            new_role = 'member' if role_toggle else 'admin'
+            self.set_roles(['admin', 'member'], [new_role])
 
-    def set_roles(self, *roles):
-        """Set the list of available roles in the system to `roles`."""
-        self.available_roles = {
+    def set_roles(self, roles, roles_on_project=None):
+        """Set the list of available roles in the system.
+
+        :param roles: List of roles returned by ``list_roles``.
+        :param roles_on_project: List of roles returned by
+            ``list_user_roles_on_project``.
+        :returns: None.
+        """
+        if not roles_on_project:
+            roles_on_project = []
+        if not isinstance(roles, list):
+            roles = [roles]
+        if not isinstance(roles_on_project, list):
+            roles_on_project = [roles_on_project]
+
+        available_roles = {
             'roles': [{'name': role, 'id': '%s_id' % role} for role in roles]
         }
-        self.roles_v3_client.list_user_roles_on_project.return_value =\
-            self.available_roles
-        self.roles_v3_client.list_roles.return_value = self.available_roles
+        available_project_roles = {
+            'roles': [{'name': role, 'id': '%s_id' % role}
+                      for role in roles_on_project]
+        }
+
+        self.roles_v3_client.list_roles.return_value = available_roles
+        self.roles_v3_client.list_user_roles_on_project.return_value = (
+            available_project_roles)
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 7ce925a..36fa045 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -40,11 +40,11 @@
 
     def setUp(self):
         super(RbacPolicyTest, self).setUp()
-
-        m_creds = self.patchobject(rbac_policy_parser, 'credentials')
-        m_creds.AdminManager().identity_services_client.list_services.\
+        self.patchobject(rbac_policy_parser, 'credentials')
+        m_creds = self.patchobject(rbac_policy_parser, 'clients')
+        m_creds.Manager().identity_services_client.list_services.\
             return_value = self.services
-        m_creds.AdminManager().identity_services_v3_client.list_services.\
+        m_creds.Manager().identity_services_v3_client.list_services.\
             return_value = self.services
 
         current_directory = os.path.dirname(os.path.realpath(__file__))
@@ -458,7 +458,7 @@
     @mock.patch.object(rbac_policy_parser, 'policy', autospec=True)
     @mock.patch.object(rbac_policy_parser.RbacPolicyParser, '_get_policy_data',
                        autospec=True)
-    @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'clients', autospec=True)
     @mock.patch.object(rbac_policy_parser, 'os', autospec=True)
     def test_discover_policy_files_with_many_invalid_one_valid(self, m_os,
                                                                m_creds, *args):
@@ -466,7 +466,7 @@
         m_os.path.isfile.side_effect = [False, False, True, False]
 
         # Ensure the outer for loop runs only once in `discover_policy_files`.
-        m_creds.AdminManager().identity_services_v3_client.\
+        m_creds.Manager().identity_services_v3_client.\
             list_services.return_value = {
                 'services': [{'name': 'test_service'}]}
 
@@ -504,10 +504,10 @@
 
     def _test_validate_service(self, v2_services, v3_services,
                                expected_failure=False, expected_services=None):
-        with mock.patch.object(rbac_policy_parser, 'credentials') as m_creds:
-            m_creds.AdminManager().identity_services_client.list_services.\
+        with mock.patch.object(rbac_policy_parser, 'clients') as m_creds:
+            m_creds.Manager().identity_services_client.list_services.\
                 return_value = v2_services
-            m_creds.AdminManager().identity_services_v3_client.list_services.\
+            m_creds.Manager().identity_services_v3_client.list_services.\
                 return_value = v3_services
 
         test_tenant_id = mock.sentinel.tenant_id
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 7ce5548..a9acf1c 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -108,16 +108,17 @@
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     @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.
+    def test_rule_validation_rbac_malformed_response_positive(self,
+                                                              mock_policy,
+                                                              mock_log):
+        """Test RbacMalformedResponse error is thrown without permission passes.
 
-        Positive test case: if RbacActionFailed is thrown and the user is not
-        allowed to perform the action, then this is a success.
+        Positive test case: if RbacMalformedResponse is thrown and the user is
+        not allowed to perform the action, then this is a success.
         """
         decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
         mock_function = mock.Mock()
-        mock_function.side_effect = rbac_exceptions.RbacActionFailed
+        mock_function.side_effect = rbac_exceptions.RbacMalformedResponse
         wrapper = decorator(mock_function)
 
         mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
@@ -130,16 +131,65 @@
 
     @mock.patch.object(rbac_rv, 'LOG', autospec=True)
     @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.
+    def test_rule_validation_rbac_malformed_response_negative(self,
+                                                              mock_policy,
+                                                              mock_log):
+        """Test RbacMalformedResponse error is thrown with permission fails.
 
-        Negative test case: if RbacActionFailed is thrown and the user is
+        Negative test case: if RbacMalformedResponse is thrown and the user is
         allowed to perform the action, then this is an expected failure.
         """
         decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
         mock_function = mock.Mock()
-        mock_function.side_effect = rbac_exceptions.RbacActionFailed
+        mock_function.side_effect = rbac_exceptions.RbacMalformedResponse
+        wrapper = decorator(mock_function)
+
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
+
+        e = self.assertRaises(exceptions.Forbidden, wrapper, self.mock_args)
+        self.assertIn(
+            "Role Member was not allowed to perform sentinel.action.",
+            e.__str__())
+
+        mock_log.error.assert_called_once_with("Role Member was not allowed to"
+                                               " perform sentinel.action.")
+
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_rbac_conflicting_policies_positive(self,
+                                                                mock_policy,
+                                                                mock_log):
+        """Test RbacConflictingPolicies error is thrown without permission passes.
+
+        Positive test case: if RbacConflictingPolicies is thrown and the user
+        is not allowed to perform the action, then this is a success.
+        """
+        decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
+        mock_function = mock.Mock()
+        mock_function.side_effect = rbac_exceptions.RbacConflictingPolicies
+        wrapper = decorator(mock_function)
+
+        mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
+
+        result = wrapper(self.mock_args)
+
+        self.assertIsNone(result)
+        mock_log.error.assert_not_called()
+        mock_log.warning.assert_not_called()
+
+    @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+    @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+    def test_rule_validation_rbac_conflicting_policies_negative(self,
+                                                                mock_policy,
+                                                                mock_log):
+        """Test RbacConflictingPolicies error is thrown with permission fails.
+
+        Negative test case: if RbacConflictingPolicies is thrown and the user
+        is allowed to perform the action, then this is an expected failure.
+        """
+        decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
+        mock_function = mock.Mock()
+        mock_function.side_effect = rbac_exceptions.RbacConflictingPolicies
         wrapper = decorator(mock_function)
 
         mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index 79503b1..540283a 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -34,17 +34,19 @@
 
     def test_switch_role_with_missing_admin_role(self):
         self.rbac_utils.set_roles('member')
-        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
-                              self.rbac_utils.switch_role, True)
-        self.assertIn("Role with name 'admin' does not exist in the system.",
-                      e.__str__())
+        error_re = (
+            'Roles defined by `\[rbac\] rbac_test_role` and `\[identity\] '
+            'admin_role` must be defined in the system.')
+        self.assertRaisesRegex(rbac_exceptions.RbacResourceSetupFailed,
+                               error_re, self.rbac_utils.switch_role)
 
     def test_switch_role_with_missing_rbac_role(self):
         self.rbac_utils.set_roles('admin')
-        e = self.assertRaises(rbac_exceptions.RbacResourceSetupFailed,
-                              self.rbac_utils.switch_role, True)
-        self.assertIn("Role defined by rbac_test_role does not exist in the "
-                      "system.", e.__str__())
+        error_re = (
+            'Roles defined by `\[rbac\] rbac_test_role` and `\[identity\] '
+            'admin_role` must be defined in the system.')
+        self.assertRaisesRegex(rbac_exceptions.RbacResourceSetupFailed,
+                               error_re, self.rbac_utils.switch_role)
 
     def test_switch_role_to_admin_role(self):
         self.rbac_utils.switch_role()
@@ -61,6 +63,16 @@
             .assert_called_once_with()
         mock_time.sleep.assert_called_once_with(1)
 
+    def test_switch_role_to_admin_role_avoids_role_switch(self):
+        self.rbac_utils.set_roles(['admin', 'member'], 'admin')
+        self.rbac_utils.switch_role()
+
+        roles_client = self.rbac_utils.roles_v3_client
+        mock_time = self.rbac_utils.mock_time
+
+        roles_client.create_user_role_on_project.assert_not_called()
+        mock_time.sleep.assert_not_called()
+
     def test_switch_role_to_member_role(self):
         self.rbac_utils.switch_role(True)
 
@@ -80,6 +92,19 @@
             [mock.call()] * 2)
         mock_time.sleep.assert_has_calls([mock.call(1)] * 2)
 
+    def test_switch_role_to_member_role_avoids_role_switch(self):
+        self.rbac_utils.set_roles(['admin', 'member'], 'member')
+        self.rbac_utils.switch_role(True)
+
+        roles_client = self.rbac_utils.roles_v3_client
+        mock_time = self.rbac_utils.mock_time
+
+        roles_client.create_user_role_on_project.assert_has_calls([
+            mock.call(self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID,
+                      'admin_id')
+        ])
+        mock_time.sleep.assert_called_once_with(1)
+
     def test_switch_role_to_member_role_then_admin_role(self):
         self.rbac_utils.switch_role(True, False)
 
@@ -137,19 +162,22 @@
         self.assertIn(expected_error_message, str(e))
 
     def test_clear_user_roles(self):
-        self.rbac_utils.switch_role(True)
+        # NOTE(felipemonteiro): Set the user's roles on the project to
+        # include 'random' to coerce a role switch, or else it will be
+        # skipped.
+        self.rbac_utils.set_roles(['admin', 'member'], ['member', 'random'])
+        self.rbac_utils.switch_role()
 
         roles_client = self.rbac_utils.roles_v3_client
 
-        roles_client.list_user_roles_on_project.assert_has_calls([
-            mock.call(self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID)
-        ] * 2)
+        roles_client.list_user_roles_on_project.assert_called_once_with(
+            self.rbac_utils.PROJECT_ID, self.rbac_utils.USER_ID)
         roles_client.delete_role_from_user_on_project.\
             assert_has_calls([
                 mock.call(mock.sentinel.project_id, mock.sentinel.user_id,
-                          'admin_id'),
+                          'member_id'),
                 mock.call(mock.sentinel.project_id, mock.sentinel.user_id,
-                          'member_id')] * 2)
+                          'random_id')])
 
     @mock.patch.object(rbac_utils, 'LOG', autospec=True)
     @mock.patch.object(rbac_utils, 'sys', autospec=True)
diff --git a/releasenotes/notes/agents-ca4a5e232ce242a5.yaml b/releasenotes/notes/agents-ca4a5e232ce242a5.yaml
new file mode 100644
index 0000000..3cd9646
--- /dev/null
+++ b/releasenotes/notes/agents-ca4a5e232ce242a5.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added tests to test_agents_rbac.py for PUT and
+    DELETE endpoints.
diff --git a/releasenotes/notes/rbac-tests-for-compute-extended-status-ef00256e58b66223.yaml b/releasenotes/notes/rbac-tests-for-compute-extended-status-ef00256e58b66223.yaml
new file mode 100644
index 0000000..6cf82e6
--- /dev/null
+++ b/releasenotes/notes/rbac-tests-for-compute-extended-status-ef00256e58b66223.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    Add RBAC tests for os_compute_api:os-extended-status, which validate that
+    the following attributes:
+
+        - OS-EXT-STS:task_state
+        - OS-EXT-STS:vm_state
+        - OS-EXT-STS:power_state
+
+    are present in the relevant response bodies.
diff --git a/test-whitelist.txt b/test-whitelist.txt
deleted file mode 100644
index 162992a..0000000
--- a/test-whitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-patrole_tempest_plugin.tests.unit.test*
diff --git a/tox.ini b/tox.ini
index d2e83e9..41e893c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,6 +8,9 @@
 install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
 setenv =
    VIRTUAL_ENV={envdir}
+   OS_TEST_PATH=./patrole_tempest_plugin/tests/unit
+   LANGUAGE=en_US
+   LC_ALL=en_US.utf-8
    PYTHONWARNINGS=default::DeprecationWarning
 passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH OS_TEST_PATH http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
 whitelist_externals = find
@@ -15,7 +18,7 @@
        -r{toxinidir}/test-requirements.txt
 commands =
     find . -type f -name "*.pyc" -delete
-    ostestr {posargs} --whitelist-file test-whitelist.txt
+    ostestr {posargs}
 
 [testenv:pep8]
 commands = flake8 {posargs}