Merge "Change rbac_utils.RbacUtils is_admin to function"
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/doc/source/sampleconf.rst b/doc/source/sampleconf.rst
index c262f2d..43933db 100644
--- a/doc/source/sampleconf.rst
+++ b/doc/source/sampleconf.rst
@@ -36,7 +36,7 @@
     # are.
     test_custom_requirements = False
 
-    File path of the yaml file that defines your RBAC requirements. This
+    # File path of the yaml file that defines your RBAC requirements. This
     # file must be located on the same host that Patrole runs on. The yaml
     # file should be written as follows:
     custom_requirements_file = patrole/requirements.txt
diff --git a/patrole_tempest_plugin/tests/api/compute/test_instance_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_instance_actions_rbac.py
deleted file mode 100644
index b81d2ab..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_instance_actions_rbac.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#    Copyright 2017 AT&T Corporation.
-#    All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from tempest.lib import decorators
-from tempest import test
-
-from patrole_tempest_plugin import rbac_exceptions
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-
-class InstanceActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(InstanceActionsRbacTest, cls).skip_checks()
-        if not test.is_extension_enabled('os-instance-actions', 'compute'):
-            raise cls.skipException(
-                '%s skipped as os-instance-actions not enabled' % cls.__name__)
-
-    @classmethod
-    def resource_setup(cls):
-        super(InstanceActionsRbacTest, cls).resource_setup()
-        cls.server = cls.create_test_server(wait_until='ACTIVE')
-        cls.request_id = cls.server.response['x-compute-request-id']
-
-    @decorators.idempotent_id('9d1b131d-407e-4fa3-8eef-eb2c4526f1da')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-instance-actions")
-    def test_list_instance_actions(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.list_instance_actions(self.server['id'])
-
-    @decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-instance-actions:events")
-    def test_get_instance_action(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        instance_action = self.servers_client.show_instance_action(
-            self.server['id'], self.request_id)['instanceAction']
-        if 'events' not in instance_action:
-            raise rbac_exceptions.RbacActionFailed
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 1866119..e942f08 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
@@ -49,21 +49,30 @@
     @classmethod
     def resource_setup(cls):
         super(MiscPolicyActionsRbacTest, cls).resource_setup()
-        cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
+        cls.server = cls.create_test_server(wait_until='ACTIVE')
 
     def setUp(self):
         super(MiscPolicyActionsRbacTest, self).setUp()
         try:
             waiters.wait_for_server_status(self.servers_client,
-                                           self.server_id, 'ACTIVE')
+                                           self.server['id'], 'ACTIVE')
         except lib_exc.NotFound:
             # If the server was found to be deleted by a previous test,
             # a new one is built
-            server = self.create_test_server(wait_until='ACTIVE')
-            self.__class__.server_id = server['id']
+            self.__class__.server = self.create_test_server(
+                wait_until='ACTIVE')
         except Exception:
             # Rebuilding the server in case something happened during a test
-            self.__class__.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server = self._rebuild_server(self.server['id'])
+
+    def _rebuild_server(self, server_id):
+        # Destroy an existing server and creates a new one.
+        if server_id:
+            self.delete_server(server_id)
+
+        self.password = data_utils.rand_password()
+        return self.create_test_server(
+            wait_until='ACTIVE', adminPass=self.password)
 
     @test.requires_ext(extension='os-admin-actions', service='compute')
     @rbac_rule_validation.action(
@@ -73,8 +82,8 @@
     def test_reset_server_state(self):
         """Test reset server state, part of os-admin-actions."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.reset_state(self.server_id, state='error')
-        self.addCleanup(self.servers_client.reset_state, self.server_id,
+        self.servers_client.reset_state(self.server['id'], state='error')
+        self.addCleanup(self.servers_client.reset_state, self.server['id'],
                         state='active')
 
     @test.requires_ext(extension='os-admin-actions', service='compute')
@@ -85,7 +94,7 @@
     def test_inject_network_info(self):
         """Test inject network info, part of os-admin-actions."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.inject_network_info(self.server_id)
+        self.servers_client.inject_network_info(self.server['id'])
 
     @decorators.attr(type=['slow'])
     @test.requires_ext(extension='os-admin-actions', service='compute')
@@ -96,7 +105,7 @@
     def test_reset_network(self):
         """Test reset network, part of os-admin-actions."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.reset_network(self.server_id)
+        self.servers_client.reset_network(self.server['id'])
 
     @testtools.skipUnless(CONF.compute_feature_enabled.change_password,
                           'Change password not available.')
@@ -106,15 +115,16 @@
     @decorators.idempotent_id('908a7d59-3a66-441c-94cf-38e57ed14956')
     def test_change_server_password(self):
         """Test change admin password, part of os-admin-password."""
-        original_password = self.servers_client.show_password(self.server_id)
+        original_password = self.servers_client.show_password(
+            self.server['id'])
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.change_password(
-            self.server_id, adminPass=data_utils.rand_password())
-        self.addCleanup(self.servers_client.change_password, self.server_id,
+            self.server['id'], adminPass=data_utils.rand_password())
+        self.addCleanup(self.servers_client.change_password, self.server['id'],
                         adminPass=original_password)
         waiters.wait_for_server_status(
-            self.os_admin.servers_client, self.server_id, 'ACTIVE')
+            self.os_admin.servers_client, self.server['id'], 'ACTIVE')
 
     @test.requires_ext(extension='os-config-drive', service='compute')
     @decorators.idempotent_id('2c82e819-382d-4d6f-87f0-a45954cbbc64')
@@ -138,7 +148,7 @@
     def test_show_server_config_drive(self):
         """Test show server with config_drive property in response body."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        body = self.servers_client.show_server(self.server_id)['server']
+        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.')
@@ -152,7 +162,44 @@
         """Test force delete server, part of os-deferred-delete."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         # Force-deleting a server enforces os-deferred-delete.
-        self.servers_client.force_delete_server(self.server_id)
+        self.servers_client.force_delete_server(self.server['id'])
+
+    @test.requires_ext(extension='os-instance-actions', service='compute')
+    @decorators.idempotent_id('9d1b131d-407e-4fa3-8eef-eb2c4526f1da')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-instance-actions")
+    def test_list_instance_actions(self):
+        """Test list instance actions, part of os-instance-actions."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.list_instance_actions(self.server['id'])
+
+    @test.requires_ext(extension='os-instance-actions', service='compute')
+    @decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-instance-actions:events")
+    def test_show_instance_action(self):
+        """Test show instance action, part of os-instance-actions.
+
+        Expect "events" details to be included in the response body.
+        """
+        # NOTE: "os_compute_api:os-instance-actions" is also enforced.
+        request_id = self.server.response['x-compute-request-id']
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        instance_action = self.servers_client.show_instance_action(
+            self.server['id'], request_id)['instanceAction']
+
+        if 'events' not in instance_action:
+            raise rbac_exceptions.RbacActionFailed(
+                '"%s" attribute not found in response body.' % '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')
 
     @rbac_rule_validation.action(
         service="nova",
@@ -161,8 +208,8 @@
     def test_lock_server(self):
         """Test lock server, part of os-lock-server."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.lock_server(self.server_id)
-        self.addCleanup(self.servers_client.unlock_server, self.server_id)
+        self.servers_client.lock_server(self.server['id'])
+        self.addCleanup(self.servers_client.unlock_server, self.server['id'])
 
     @rbac_rule_validation.action(
         service="nova",
@@ -170,11 +217,11 @@
     @decorators.idempotent_id('d50ef8e8-4bce-11e7-b114-b2f933d5fe66')
     def test_unlock_server(self):
         """Test unlock server, part of os-lock-server."""
-        self.servers_client.lock_server(self.server_id)
-        self.addCleanup(self.servers_client.unlock_server, self.server_id)
+        self.servers_client.lock_server(self.server['id'])
+        self.addCleanup(self.servers_client.unlock_server, self.server['id'])
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.unlock_server(self.server_id)
+        self.servers_client.unlock_server(self.server['id'])
 
     @rbac_rule_validation.action(
         service="nova",
@@ -187,11 +234,11 @@
         of the unlock policy, the server must be locked by a different
         user than the one who is attempting to unlock it.
         """
-        self.os_admin.servers_client.lock_server(self.server_id)
-        self.addCleanup(self.servers_client.unlock_server, self.server_id)
+        self.os_admin.servers_client.lock_server(self.server['id'])
+        self.addCleanup(self.servers_client.unlock_server, self.server['id'])
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.unlock_server(self.server_id)
+        self.servers_client.unlock_server(self.server['id'])
 
     @test.requires_ext(extension='os-rescue', service='compute')
     @rbac_rule_validation.action(
@@ -201,7 +248,7 @@
     def test_rescue_server(self):
         """Test rescue server, part of os-rescue."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.rescue_server(self.server_id)
+        self.servers_client.rescue_server(self.server['id'])
 
     @test.requires_ext(extension='os-server-diagnostics', service='compute')
     @rbac_rule_validation.action(
@@ -211,7 +258,7 @@
     def test_show_server_diagnostics(self):
         """Test show server diagnostics, part of os-server-diagnostics."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.show_server_diagnostics(self.server_id)
+        self.servers_client.show_server_diagnostics(self.server['id'])
 
     @test.requires_ext(extension='os-server-password', service='compute')
     @decorators.idempotent_id('aaf43f78-c178-4581-ac18-14afd3f1f6ba')
@@ -221,7 +268,7 @@
     def test_delete_server_password(self):
         """Test delete server password, part of os-server-password."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.delete_password(self.server_id)
+        self.servers_client.delete_password(self.server['id'])
 
     @test.requires_ext(extension='os-server-password', service='compute')
     @rbac_rule_validation.action(
@@ -231,7 +278,7 @@
     def test_get_server_password(self):
         """Test show server password, part of os-server-password."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.show_password(self.server_id)
+        self.servers_client.show_password(self.server['id'])
 
     @test.requires_ext(extension='OS-SRV-USG', service='compute')
     @rbac_rule_validation.action(
@@ -245,7 +292,7 @@
         test can be combined with the generic test for showing a server.
         """
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.show_server(self.server_id)
+        self.servers_client.show_server(self.server['id'])
 
     @test.requires_ext(extension='os-simple-tenant-usage', service='compute')
     @rbac_rule_validation.action(
@@ -278,10 +325,10 @@
     def test_suspend_server(self):
         """Test suspend server, part of os-suspend-server."""
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.suspend_server(self.server_id)
-        self.addCleanup(self.servers_client.resume_server, self.server_id)
+        self.servers_client.suspend_server(self.server['id'])
+        self.addCleanup(self.servers_client.resume_server, self.server['id'])
         waiters.wait_for_server_status(
-            self.os_admin.servers_client, self.server_id, 'SUSPENDED')
+            self.os_admin.servers_client, self.server['id'], 'SUSPENDED')
 
     @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
                           "Suspend compute feature is not available.")
@@ -291,14 +338,14 @@
         rule="os_compute_api:os-suspend-server:resume")
     def test_resume_server(self):
         """Test resume server, part of os-suspend-server."""
-        self.servers_client.suspend_server(self.server_id)
-        waiters.wait_for_server_status(self.servers_client, self.server_id,
+        self.servers_client.suspend_server(self.server['id'])
+        waiters.wait_for_server_status(self.servers_client, self.server['id'],
                                        'SUSPENDED')
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.resume_server(self.server_id)
+        self.servers_client.resume_server(self.server['id'])
         waiters.wait_for_server_status(
-            self.os_admin.servers_client, self.server_id, 'ACTIVE')
+            self.os_admin.servers_client, self.server['id'], 'ACTIVE')
 
 
 class MiscPolicyActionsNetworkRbacTest(rbac_base.BaseV2ComputeRbacTest):