Merge "Rename rbac_policy_parser to policy_authority"
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 5736645..81fefb2 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -221,9 +221,19 @@
 
 @six.add_metaclass(abc.ABCMeta)
 class RbacAuthority(object):
-    # TODO(rb560u): Add documentation explaining what this class is for
+    """Class for validating whether a given role can perform a policy action.
+
+    Any class that extends ``RbacAuthority`` provides the logic for determining
+    whether a role has permissions to execute a policy action.
+    """
 
     @abc.abstractmethod
-    def allowed(self, rule_name, role):
-        """Determine whether the role should be able to perform the API"""
-        return
+    def allowed(self, rule, role):
+        """Determine whether the role should be able to perform the API.
+
+        :param rule: The name of the policy enforced by the API.
+        :param role: The role used to determine whether ``rule`` can be
+            executed.
+        :returns: True if the ``role`` has permissions to execute
+            ``rule``, else False.
+        """
diff --git a/patrole_tempest_plugin/tests/api/compute/rbac_base.py b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
index 3807ae9..bab193e 100644
--- a/patrole_tempest_plugin/tests/api/compute/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
@@ -53,9 +53,12 @@
         for flavor in cls.flavors:
             test_utils.call_and_ignore_notfound_exc(
                 cls.flavors_client.delete_flavor, flavor['id'])
+        for flavor in cls.flavors:
+            test_utils.call_and_ignore_notfound_exc(
+                cls.flavors_client.wait_for_resource_deletion, flavor['id'])
 
     @classmethod
-    def _create_flavor(cls, **kwargs):
+    def create_flavor(cls, **kwargs):
         flavor_kwargs = {
             "name": data_utils.rand_name(cls.__name__ + '-flavor'),
             "ram": data_utils.rand_int_id(1, 10),
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
index b196d93..26c9957 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
@@ -38,7 +38,7 @@
     @classmethod
     def resource_setup(cls):
         super(FlavorAccessRbacTest, cls).resource_setup()
-        cls.flavor_id = cls._create_flavor(is_public=False)['id']
+        cls.flavor_id = cls.create_flavor(is_public=False)['id']
         cls.public_flavor_id = CONF.compute.flavor_ref
         cls.tenant_id = cls.os_primary.credentials.tenant_id
 
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py
index e59fd78..031d8ad 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_extra_specs_rbac.py
@@ -34,7 +34,7 @@
     @classmethod
     def resource_setup(cls):
         super(FlavorExtraSpecsRbacTest, cls).resource_setup()
-        cls.flavor = cls._create_flavor()
+        cls.flavor = cls.create_flavor()
 
     @classmethod
     def resource_cleanup(cls):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py
new file mode 100644
index 0000000..519a55a
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_manage_rbac.py
@@ -0,0 +1,53 @@
+#    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_rule_validation
+from patrole_tempest_plugin.tests.api.compute import rbac_base
+
+
+class FlavorManageRbacTest(rbac_base.BaseV2ComputeRbacTest):
+
+    # Need admin to wait for resource deletion below to avoid test role
+    # having to pass extra policies.
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    def skip_checks(cls):
+        super(FlavorManageRbacTest, cls).skip_checks()
+        if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+            msg = "OS-FLV-EXT-DATA extension not enabled."
+            raise cls.skipException(msg)
+
+    @decorators.idempotent_id('a4e7faec-7a4b-4809-9856-90d5b747ca35')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-flavor-manage:create")
+    def test_create_flavor_manage(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.create_flavor()
+
+    @decorators.idempotent_id('782e988e-061b-4c40-896f-a77c70c2b057')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-flavor-manage:delete")
+    def test_delete_flavor_manage(self):
+        flavor_id = self.create_flavor()['id']
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.flavors_client.delete_flavor(flavor_id)
+        self.os_admin.flavors_client.wait_for_resource_deletion(flavor_id)
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 b64eef8..63dee63 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
@@ -42,6 +42,8 @@
     Only applies to:
       * policy "families" that require server creation
       * small policy "families" -- i.e. containing one to three policies
+
+    Tests are ordered by policy name.
     """
 
     credentials = ['primary', 'admin']
@@ -167,41 +169,36 @@
         # Force-deleting a server enforces os-deferred-delete.
         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')
+    @decorators.idempotent_id('d873740a-7b10-40a9-943d-7cc18115370e')
+    @test.requires_ext(extension='OS-EXT-AZ', service='compute')
     @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'])
+        rule="os_compute_api:os-extended-availability-zone")
+    def test_list_servers_with_details_extended_availability_zone(self):
+        """Test list servers OS-EXT-AZ:availability_zone attr in resp body."""
+        expected_attr = 'OS-EXT-AZ:availability_zone'
 
-    @test.requires_ext(extension='os-instance-actions', service='compute')
-    @decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        body = self.servers_client.list_servers(detail=True)['servers']
+        # If the first server contains `expected_attr`, then all the others do.
+        if expected_attr not in body[0]:
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute=expected_attr)
+
+    @decorators.idempotent_id('727e5360-770a-4b9c-8015-513a40216635')
+    @test.requires_ext(extension='OS-EXT-AZ', service='compute')
     @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']
+        rule="os_compute_api:os-extended-availability-zone")
+    def test_show_server_extended_availability_zone(self):
+        """Test show server OS-EXT-AZ:availability_zone attr in resp body."""
+        expected_attr = 'OS-EXT-AZ:availability_zone'
 
         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:
+        body = self.servers_client.show_server(self.server['id'])['server']
+        if expected_attr not in body:
             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.RbacMalformedResponse(
-                attribute='events.traceback')
+                attribute=expected_attr)
 
     @decorators.idempotent_id('82053c27-3134-4003-9b55-bc9fafdb0e3b')
     @test.requires_ext(extension='OS-EXT-STS', service='compute')
@@ -237,30 +234,33 @@
                 raise rbac_exceptions.RbacMalformedResponse(
                     attribute=attr)
 
-    @decorators.idempotent_id('d873740a-7b10-40a9-943d-7cc18115370e')
-    @test.requires_ext(extension='OS-EXT-AZ', service='compute')
+    @decorators.idempotent_id('21e39cbe-6c32-48fc-80dd-3e1fece6053f')
+    @test.requires_ext(extension='os-extended-volumes', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-extended-availability-zone")
-    def test_list_servers_with_details_extended_availability_zone(self):
-        """Test list servers OS-EXT-AZ:availability_zone attr in resp body."""
-        expected_attr = 'OS-EXT-AZ:availability_zone'
+        rule="os_compute_api:os-extended-volumes")
+    def test_list_servers_with_details_extended_volumes(self):
+        """Test list servers os-extended-volumes:volumes_attached attr in resp
+        body.
+        """
+        expected_attr = 'os-extended-volumes:volumes_attached'
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         body = self.servers_client.list_servers(detail=True)['servers']
-        # If the first server contains `expected_attr`, then all the others do.
         if expected_attr not in body[0]:
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
-    @decorators.idempotent_id('727e5360-770a-4b9c-8015-513a40216635')
-    @test.requires_ext(extension='OS-EXT-AZ', service='compute')
+    @decorators.idempotent_id('7f163708-0d25-4138-8512-dfdd72a92989')
+    @test.requires_ext(extension='os-extended-volumes', service='compute')
     @rbac_rule_validation.action(
         service="nova",
-        rule="os_compute_api:os-extended-availability-zone")
-    def test_show_server_extended_availability_zone(self):
-        """Test show server OS-EXT-AZ:availability_zone attr in resp body."""
-        expected_attr = 'OS-EXT-AZ:availability_zone'
+        rule="os_compute_api:os-extended-volumes")
+    def test_show_server_extended_volumes(self):
+        """Test show server os-extended-volumes:volumes_attached attr in resp
+        body.
+        """
+        expected_attr = 'os-extended-volumes:volumes_attached'
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         body = self.servers_client.show_server(self.server['id'])['server']
@@ -268,6 +268,42 @@
             raise rbac_exceptions.RbacMalformedResponse(
                 attribute=expected_attr)
 
+    @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.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.RbacMalformedResponse(
+                attribute='events.traceback')
+
     @rbac_rule_validation.action(
         service="nova",
         rule="os_compute_api:os-lock-server:lock")
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 9ed9eb6..45a5cda 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -296,8 +296,7 @@
                         distributed=False)
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="delete_router",
-                                 expected_error_code=404)
+                                 rule="delete_router")
     @decorators.idempotent_id('c0634dd5-0467-48f7-a4ae-1014d8edb2a7')
     def test_delete_router(self):
         """Delete Router
@@ -309,10 +308,9 @@
         self.routers_client.delete_router(router['id'])
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="add_router_interface",
-                                 expected_error_code=404)
+                                 rule="add_router_interface")
     @decorators.idempotent_id('a0627778-d68d-4913-881b-e345360cca19')
-    def test_add_router_interfaces(self):
+    def test_add_router_interface(self):
         """Add Router Interface
 
         RBAC test for the neutron add_router_interface policy
@@ -331,10 +329,9 @@
             subnet_id=subnet['id'])
 
     @rbac_rule_validation.action(service="neutron",
-                                 rule="remove_router_interface",
-                                 expected_error_code=404)
+                                 rule="remove_router_interface")
     @decorators.idempotent_id('ff2593a4-2bff-4c27-97d3-dd3702b27dfb')
-    def test_remove_router_interfaces(self):
+    def test_remove_router_interface(self):
         """Remove Router Interface
 
         RBAC test for the neutron remove_router_interface policy
diff --git a/releasenotes/notes/flavor-manage-rbac-tests-eb78439316d67ab2.yaml b/releasenotes/notes/flavor-manage-rbac-tests-eb78439316d67ab2.yaml
new file mode 100644
index 0000000..0fbf24f
--- /dev/null
+++ b/releasenotes/notes/flavor-manage-rbac-tests-eb78439316d67ab2.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add test coverage for the os-flavor-manage compute API, which includes
+    tests for the following policy actions:
+
+      * "os_compute_api:os-flavor-manage:create"
+      * "os_compute_api:os-flavor-manage:delete"
diff --git a/releasenotes/notes/rbac-tests-for-compute-extended-volumes-7f3ccab122d22737.yaml b/releasenotes/notes/rbac-tests-for-compute-extended-volumes-7f3ccab122d22737.yaml
new file mode 100644
index 0000000..f7eb02d
--- /dev/null
+++ b/releasenotes/notes/rbac-tests-for-compute-extended-volumes-7f3ccab122d22737.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add RBAC tests for os-extended-volumes:volumes_attached policies, which
+    validate that "os-extended-volumes:volumes_attached" is returned in the
+    response body.