Merge "Improve patrole core documentation"
diff --git a/contrib/post_test_hook.sh b/contrib/post_test_hook.sh
index 085b5b1..db48fc2 100644
--- a/contrib/post_test_hook.sh
+++ b/contrib/post_test_hook.sh
@@ -28,16 +28,12 @@
 TEMPEST_COMMAND="sudo -H -u tempest tox"
 
 DEVSTACK_GATE_TEMPEST_REGEX="(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)"
+DEVSTACK_GATE_TEMPEST_HEAT_REGEX="(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api\.orchestration)"
 DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX="(?=.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)"
 
 # Import devstack function 'iniset'.
 source $BASE/new/devstack/functions
 
-# Use uuid tokens for faster test runs
-KEYSTONE_CONF=/etc/keystone/keystone.conf
-iniset $KEYSTONE_CONF token provider uuid
-sudo service apache2 restart
-
 # First argument is expected to contain value equal either to 'admin' or
 # 'member' (both lower-case).
 RBAC_ROLE=$1
@@ -50,30 +46,47 @@
 # environment is "multinode" or not (empty string).
 TYPE=$2
 
-# Set enable_rbac=True under [rbac] section in tempest.conf
-iniset $TEMPEST_CONFIG rbac enable_rbac True
-# Set rbac_test_role=$RBAC_ROLE under [rbac] section in tempest.conf
-iniset $TEMPEST_CONFIG rbac rbac_test_role $RBAC_ROLE
-# Set strict_policy_check=False under [rbac] section in tempest.conf
-iniset $TEMPEST_CONFIG rbac strict_policy_check False
-# Set additional, necessary CONF values
-iniset $TEMPEST_CONFIG auth use_dynamic_credentials True
-iniset $TEMPEST_CONFIG auth tempest_roles Member
-iniset $TEMPEST_CONFIG identity auth_version v3
+function set_uuid_tokens() {
+    # Use uuid tokens for faster test runs
+    KEYSTONE_CONF=/etc/keystone/keystone.conf
+    iniset $KEYSTONE_CONF token provider uuid
+    sudo service apache2 restart
+}
 
-# Give permissions back to Tempest.
-sudo chown -R tempest:stack $BASE/new/tempest
-sudo chown -R tempest:stack $BASE/data/tempest
+function setup_config() {
+    # Set enable_rbac=True under [rbac] section in tempest.conf
+    iniset $TEMPEST_CONFIG rbac enable_rbac True
+    # Set rbac_test_role=$RBAC_ROLE under [rbac] section in tempest.conf
+    iniset $TEMPEST_CONFIG rbac rbac_test_role $RBAC_ROLE
+    # Set strict_policy_check=False under [rbac] section in tempest.conf
+    iniset $TEMPEST_CONFIG rbac strict_policy_check False
+    # Set additional, necessary CONF values
+    iniset $TEMPEST_CONFIG auth use_dynamic_credentials True
+    iniset $TEMPEST_CONFIG auth tempest_roles Member
+    iniset $TEMPEST_CONFIG identity auth_version v3
+}
 
-set -o errexit
+function run_tests() {
+    # Give permissions back to Tempest.
+    sudo chown -R tempest:stack $BASE/new/tempest
+    sudo chown -R tempest:stack $BASE/data/tempest
 
-# cd into Tempest directory before executing tox.
-cd $BASE/new/tempest
+    set -o errexit
 
-if [[ "$TYPE" == "multinode" ]]; then
-    $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
-else
-    $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
-fi
+    # cd into Tempest directory before executing tox.
+    cd $BASE/new/tempest
 
-sudo -H -u tempest .tox/all-plugin/bin/tempest list-plugins
+    if [[ "$TYPE" == "multinode" ]]; then
+        $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
+    elif [[ "$TYPE" == "heat" ]]; then
+        $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_HEAT_REGEX --concurrency=$TEMPEST_CONCURRENCY
+    else
+        $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
+    fi
+
+    sudo -H -u tempest .tox/all-plugin/bin/tempest list-plugins
+}
+
+set_uuid_tokens
+setup_config
+run_tests
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 38bed7c..69a9842 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -101,7 +101,7 @@
             try:
                 file_policy_data = json.loads(file_policy_data)
             except ValueError:
-                pass
+                file_policy_data = None
 
         # Check whether policy actions are defined in code. Nova and Keystone,
         # for example, define their default policy actions in code.
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index a47db68..81266af 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
@@ -351,3 +351,32 @@
             LOG.info("host_status attribute not returned when role doesn't "
                      "have permission to access it.")
             raise rbac_exceptions.RbacActionFailed
+
+
+class ServerActionsV214RbacTest(rbac_base.BaseV2ComputeRbacTest):
+
+    min_microversion = '2.14'
+    max_microversion = 'latest'
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServerActionsV214RbacTest, cls).setup_clients()
+        cls.client = cls.servers_client
+
+    @classmethod
+    def resource_setup(cls):
+        cls.set_validation_resources()
+        super(ServerActionsV214RbacTest, cls).resource_setup()
+        cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-evacuate")
+    @decorators.idempotent_id('78ecef3c-faff-412a-83be-47651963eb21')
+    def test_evacuate_server(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.assertRaisesRegex(lib_exc.NotFound,
+                               "Compute host fake-host not found.",
+                               self.client.evacuate_server,
+                               self.server_id,
+                               host='fake-host')
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 b0dd179..0906222 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -384,3 +384,89 @@
         }
 
         self.assertEqual(expected_policy_data, actual_policy_data)
+
+    @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
+    def test_get_policy_data_cannot_find_policy(self, mock_stevedore,
+                                                mock_creds):
+        mock_stevedore.named.NamedExtensionManager.return_value = None
+        mock_creds.AdminManager.return_value.identity_services_v3_client.\
+            list_services.return_value = {
+                'services': [{'name': 'test_service'}]}
+
+        e = self.assertRaises(rbac_exceptions.RbacParsingException,
+                              rbac_policy_parser.RbacPolicyParser,
+                              None, None, 'test_service', None)
+
+        expected_error = \
+            'Policy file for {0} service neither found in code '\
+            'nor at {1}.'.format('test_service',
+                                 '/etc/test_service/policy.json')
+
+        self.assertIn(expected_error, str(e))
+
+    @mock.patch.object(rbac_policy_parser, 'os', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'json', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
+    def test_get_policy_data_without_valid_policy(self, mock_stevedore,
+                                                  mock_credentials, mock_json,
+                                                  mock_os):
+        mock_os.path.isfile.return_value = False
+
+        test_policy_action = mock.Mock(check='rule:bar')
+        test_policy_action.configure_mock(name='foo')
+
+        test_policy = mock.Mock(obj=[test_policy_action])
+        test_policy.configure_mock(name='test_service')
+
+        mock_stevedore.named.NamedExtensionManager\
+            .return_value = [test_policy]
+
+        mock_credentials.AdminManager.return_value.identity_services_v3_client.\
+            list_services.return_value = {
+                'services': [{'name': 'test_service'}]
+            }
+
+        mock_json.dumps.side_effect = ValueError
+
+        e = self.assertRaises(rbac_exceptions.RbacParsingException,
+                              rbac_policy_parser.RbacPolicyParser,
+                              None, None, 'test_service', None)
+
+        expected_error = "Policy file for {0} service is invalid."\
+                         .format("test_service")
+
+        self.assertIn(expected_error, str(e))
+
+        mock_stevedore.named.NamedExtensionManager.assert_called_once_with(
+            'oslo.policy.policies',
+            names=['test_service'],
+            on_load_failure_callback=None,
+            invoke_on_load=True,
+            warn_on_missing_entrypoint=False)
+
+    @mock.patch.object(rbac_policy_parser, 'json', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+    @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
+    def test_get_policy_data_from_file_not_json(self, mock_stevedore,
+                                                mock_credentials,
+                                                mock_json):
+
+        mock_credentials.AdminManager.return_value.identity_services_v3_client.\
+            list_services.return_value = {
+                'services': [{'name': 'test_service'}]
+            }
+        mock_stevedore.named.NamedExtensionManager.return_value = None
+        mock_json.loads.side_effect = ValueError
+
+        e = self.assertRaises(rbac_exceptions.RbacParsingException,
+                              rbac_policy_parser.RbacPolicyParser,
+                              None, None, 'test_service',
+                              self.tenant_policy_file)
+
+        expected_error = 'Policy file for {0} service neither found in code '\
+                         'nor at {1}.'.format('test_service',
+                                              self.tenant_policy_file)
+
+        self.assertIn(expected_error, str(e))
diff --git a/tox.ini b/tox.ini
index e123d64..a004c6e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,6 +28,10 @@
 commands = {posargs}
 
 [testenv:cover]
+commands = rm -rf *.pyc
+           rm -rf cover
+           rm -f .coverage
+           nosetests {posargs}
 setenv = VIRTUAL_ENV={envdir}
          NOSE_WITH_COVERAGE=1
          NOSE_COVER_BRANCHES=1
@@ -36,7 +40,7 @@
          NOSE_COVER_HTML_DIR={toxinidir}/cover
          NOSE_WHERE=patrole_tempest_plugin/tests/unit
 whitelist_externals = nosetests
-commands = nosetests {posargs}
+                      rm
 
 [testenv:docs]
 commands = python setup.py build_sphinx