Merge "Creates config options for policy.json paths"
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index b532d63..e342dd8 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -50,3 +50,13 @@
     # not found in the policy.json. Otherwise, they throw a
     # skipException.
     strict_policy_check = False
+
+    # The following config options set the location of the service's
+    # policy file. For services that have their policy in code (e.g.,
+    # Nova), this would be the location of a custom policy.json, if
+    # one exists.
+    cinder_policy_file = /etc/cinder/policy.json
+    glance_policy_file = /etc/glance/policy.json
+    keystone_policy_file = /etc/keystone/policy.json
+    neutron_policy_file = /etc/neutron/policy.json
+    nova_policy_file = /etc/nova/policy.json
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index dfb6cef..cb00269 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -30,5 +30,20 @@
                 default=False,
                 help="If true, throws RbacParsingException for"
                      " policies which don't exist. If false, "
-                     "throws skipException.")
+                     "throws skipException."),
+    cfg.StrOpt('cinder_policy_file',
+               default='/etc/cinder/policy.json',
+               help="Location of the neutron policy file."),
+    cfg.StrOpt('glance_policy_file',
+               default='/etc/glance/policy.json',
+               help="Location of the glance policy file."),
+    cfg.StrOpt('keystone_policy_file',
+               default='/etc/keystone/policy.json',
+               help="Location of the keystone policy file."),
+    cfg.StrOpt('neutron_policy_file',
+               default='/etc/neutron/policy.json',
+               help="Location of the neutron policy file."),
+    cfg.StrOpt('nova_policy_file',
+               default='/etc/nova/policy.json',
+               help="Location of the nova policy file.")
 ]
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 2686736..e68921f 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -40,7 +40,7 @@
     each role, whether a given rule is allowed using oslo policy.
     """
 
-    def __init__(self, project_id, user_id, service=None, path=None,
+    def __init__(self, project_id, user_id, service=None,
                  extra_target_data={}):
         """Initialization of Rbac Policy Parser.
 
@@ -76,7 +76,12 @@
 
         # Use default path in /etc/<service_name/policy.json if no path
         # is provided.
-        self.path = path or os.path.join('/etc', service, 'policy.json')
+        path = getattr(CONF.rbac, '%s_policy_file' % str(service), None)
+        if not path:
+            LOG.info("No config option found for %s,"
+                     " using default path" % str(service))
+            path = os.path.join('/etc', service, 'policy.json')
+        self.path = path
         self.rules = policy.Rules.load(self._get_policy_data(service),
                                        'default')
         self.project_id = project_id
@@ -98,12 +103,18 @@
 
         # Check whether policy file exists.
         if os.path.isfile(self.path):
-            with open(self.path, 'r') as policy_file:
-                file_policy_data = policy_file.read()
             try:
+                with open(self.path, 'r') as policy_file:
+                    file_policy_data = policy_file.read()
                 file_policy_data = json.loads(file_policy_data)
-            except ValueError:
-                file_policy_data = None
+            except (IOError, ValueError) as e:
+                msg = "Failed to read policy file for service. "
+                if isinstance(e, IOError):
+                    msg += "Please check that policy path exists."
+                else:
+                    msg += "JSON may be improperly formatted."
+                LOG.debug(msg)
+                file_policy_data = {}
 
         # 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/unit/test_rbac_policy_parser.py b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
index 0906222..6889b44 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -32,6 +32,8 @@
         super(RbacPolicyTest, self).setUp()
         self.mock_admin_mgr = mock.patch.object(
             rbac_policy_parser, 'credentials').start()
+        self.mock_path = mock.patch.object(
+            rbac_policy_parser, 'os').start()
 
         current_directory = os.path.dirname(os.path.realpath(__file__))
         self.custom_policy_file = os.path.join(current_directory,
@@ -88,8 +90,9 @@
 
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
+        self.mock_path.path.join.return_value = self.custom_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.custom_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         expected = {
             'policy_action_1': ['two', 'four', 'six', 'eight'],
@@ -110,8 +113,9 @@
     def test_admin_policy_file_with_admin_role(self):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
+        self.mock_path.path.join.return_value = self.admin_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.admin_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         role = 'admin'
         allowed_rules = [
@@ -130,8 +134,9 @@
     def test_admin_policy_file_with_member_role(self):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
+        self.mock_path.path.join.return_value = self.admin_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.admin_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         role = 'Member'
         allowed_rules = [
@@ -151,8 +156,9 @@
     def test_admin_policy_file_with_context_is_admin(self):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
+        self.mock_path.path.join.return_value = self.alt_admin_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.alt_admin_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         role = 'fake_admin'
         allowed_rules = ['non_admin_rule']
@@ -187,8 +193,9 @@
         """
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
+        self.mock_path.path.join.return_value = self.tenant_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.tenant_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         # Check whether Member role can perform expected actions.
         allowed_rules = ['rule1', 'rule2', 'rule3', 'rule4']
@@ -268,9 +275,9 @@
     def test_invalid_policy_rule_throws_rbac_parsing_exception(self, m_log):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-
+        self.mock_path.path.join.return_value = self.custom_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.custom_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         fake_rule = 'fake_rule'
         expected_message = "Policy action: {0} not found in policy file: {1}."\
@@ -285,9 +292,9 @@
     def test_unknown_exception_throws_rbac_parsing_exception(self, m_log):
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-
+        self.mock_path.path.join.return_value = self.custom_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.custom_policy_file)
+            test_tenant_id, test_user_id, "test")
         parser.rules = mock.MagicMock(
             **{'__getitem__.return_value.side_effect': Exception(
                mock.sentinel.error)})
@@ -320,9 +327,9 @@
 
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-
+        self.mock_path.path.join.return_value = self.tenant_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.tenant_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         policy_data = parser._get_policy_data('fake_service')
 
@@ -364,9 +371,9 @@
 
         test_tenant_id = mock.sentinel.tenant_id
         test_user_id = mock.sentinel.user_id
-
+        self.mock_path.path.join.return_value = self.tenant_policy_file
         parser = rbac_policy_parser.RbacPolicyParser(
-            test_tenant_id, test_user_id, "test", self.tenant_policy_file)
+            test_tenant_id, test_user_id, "test")
 
         policy_data = parser._get_policy_data('fake_service')
 
@@ -393,10 +400,10 @@
         mock_creds.AdminManager.return_value.identity_services_v3_client.\
             list_services.return_value = {
                 'services': [{'name': 'test_service'}]}
-
+        self.mock_path.path.join.return_value = '/etc/test_service/policy.json'
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               rbac_policy_parser.RbacPolicyParser,
-                              None, None, 'test_service', None)
+                              None, None, 'test_service')
 
         expected_error = \
             'Policy file for {0} service neither found in code '\
@@ -405,14 +412,12 @@
 
         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
+                                                  mock_credentials, mock_json):
+        self.mock_path.path.isfile.return_value = False
 
         test_policy_action = mock.Mock(check='rule:bar')
         test_policy_action.configure_mock(name='foo')
@@ -432,7 +437,7 @@
 
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               rbac_policy_parser.RbacPolicyParser,
-                              None, None, 'test_service', None)
+                              None, None, 'test_service')
 
         expected_error = "Policy file for {0} service is invalid."\
                          .format("test_service")
@@ -459,11 +464,10 @@
             }
         mock_stevedore.named.NamedExtensionManager.return_value = None
         mock_json.loads.side_effect = ValueError
-
+        self.mock_path.path.join.return_value = self.tenant_policy_file
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               rbac_policy_parser.RbacPolicyParser,
-                              None, None, 'test_service',
-                              self.tenant_policy_file)
+                              None, None, 'test_service')
 
         expected_error = 'Policy file for {0} service neither found in code '\
                          'nor at {1}.'.format('test_service',
diff --git a/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml b/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml
new file mode 100644
index 0000000..3e63c9d
--- /dev/null
+++ b/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Refactored framework to remove unused "path"
+    argument. Added config options to allow the path
+    to the policy.json files for Nova, Keystone, Cinder,
+    Neutron, and Glance to be configured without needing
+    to manually change code.