Merge "[docs] Add information about supported & stable tests"
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index daf03e4..97d246f 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -26,7 +26,6 @@
 
 from patrole_tempest_plugin import policy_authority
 from patrole_tempest_plugin import rbac_exceptions
-from patrole_tempest_plugin import rbac_utils
 from patrole_tempest_plugin import requirements_authority
 
 CONF = config.CONF
@@ -37,8 +36,7 @@
 RBACLOG = logging.getLogger('rbac_reporting')
 
 
-def action(service, rule='', admin_only=False, expected_error_code=403,
-           extra_target_data=None):
+def action(service, rule='', expected_error_code=403, extra_target_data=None):
     """A decorator for verifying OpenStack policy enforcement.
 
     A decorator which allows for positive and negative RBAC testing. Given:
@@ -77,10 +75,6 @@
 
             Patrole currently only supports custom JSON policy files.
 
-    :param admin_only: Skips over ``oslo.policy`` check because the policy
-        action defined by ``rule`` is not enforced by the service's policy
-        enforcement engine. For example, Keystone v2 performs an admin check
-        for most of its endpoints. If True, ``rule`` is effectively ignored.
     :param expected_error_code: Overrides default value of 403 (Forbidden)
         with endpoint-specific error code. Currently only supports 403 and 404.
         Support for 404 is needed because some services, like Neutron,
@@ -131,7 +125,7 @@
                     'an instance of `tempest.test.BaseTestCase`.')
 
             allowed = _is_authorized(test_obj, service, rule,
-                                     extra_target_data, admin_only)
+                                     extra_target_data)
 
             expected_exception, irregular_msg = _get_exception_type(
                 expected_error_code)
@@ -188,7 +182,7 @@
     return decorator
 
 
-def _is_authorized(test_obj, service, rule, extra_target_data, admin_only):
+def _is_authorized(test_obj, service, rule, extra_target_data):
     """Validates whether current RBAC role has permission to do policy action.
 
     :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
@@ -200,10 +194,6 @@
         ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
         performing matching against attributes that are sent along with the API
         calls.
-    :param admin_only: Skips over ``oslo.policy`` check because the policy
-        action defined by ``rule`` is not enforced by the service's policy
-        enforcement engine. For example, Keystone v2 performs an admin check
-        for most of its endpoints. If True, ``rule`` is effectively ignored.
 
     :returns: True if the current RBAC role can perform the policy action,
         else False.
@@ -216,12 +206,6 @@
         the ``rule`` does not exist in the system.
     """
 
-    if admin_only:
-        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))
-        return rbac_utils.is_admin()
-
     try:
         project_id = test_obj.os_primary.credentials.project_id
         user_id = test_obj.os_primary.credentials.user_id
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py b/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
similarity index 76%
rename from patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
rename to patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
index 9c65c14..1dee46b 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 from oslo_log import log
+
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -26,18 +27,34 @@
 LOG = log.getLogger(__name__)
 
 
-class NetworksMultiProviderRbacTest(base.BaseNetworkRbacTest):
+class NetworkSegmentsRbacTest(base.BaseNetworkRbacTest):
 
     @classmethod
     def skip_checks(cls):
-        super(NetworksMultiProviderRbacTest, cls).skip_checks()
+        super(NetworkSegmentsRbacTest, cls).skip_checks()
         if not utils.is_extension_enabled('multi-provider', 'network'):
             msg = "multi-provider extension not enabled."
             raise cls.skipException(msg)
 
+    @classmethod
+    def resource_setup(cls):
+        super(NetworkSegmentsRbacTest, cls).resource_setup()
+        # Find the network type that is supported by the current cloud by
+        # checking which network type other networks currently have. This is
+        # done because there is no tempest.conf option enumerating supported
+        # network types.
+        networks = cls.networks_client.list_networks()['networks']
+        network_types = [n['provider:network_type'] for n in networks
+                         if n['provider:network_type'] != 'flat']
+        if not network_types:
+            raise cls.skipException(
+                'Could not find network with provider:network_type that is '
+                'not "flat".')
+        cls.network_type = network_types[0]
+
     def _create_network_segments(self):
-        segments = [{"provider:network_type": "gre"},
-                    {"provider:network_type": "gre"}]
+        segments = [{'provider:network_type': self.network_type},
+                    {'provider:network_type': self.network_type}]
 
         body = self.networks_client.create_network(
             name=data_utils.rand_name(self.__class__.__name__),
@@ -68,7 +85,7 @@
         RBAC test for the neutron update_network:segments policy
         """
         network = self._create_network_segments()
-        new_segments = [{"provider:network_type": "gre"}]
+        new_segments = [{'provider:network_type': self.network_type}]
 
         with self.rbac_utils.override_role(self):
             self.networks_client.update_network(network['id'],
@@ -92,7 +109,7 @@
         # If user does not have access to the network segments attribute,
         # no NotFound or Forbidden exception are thrown.  Instead,
         # the response will have an empty network body only.
-        if len(response_network) == 0:
+        if not response_network:
             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.")
diff --git a/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
index 62ccc9e..f10e41b 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
@@ -82,3 +82,13 @@
         vol_type_id = self._create_volume_type_encryption()
         with self.rbac_utils.override_role(self):
             self.encryption_types_client.show_encryption_type(vol_type_id)
+
+    @decorators.idempotent_id('d4ed3cf8-52b2-4fa2-910d-e405361f0881')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:volume_type_encryption")
+    def test_show_encryption_specs_item(self):
+        vol_type_id = self._create_volume_type_encryption()
+        with self.rbac_utils.override_role(self):
+            self.encryption_types_client.show_encryption_specs_item(
+                vol_type_id, 'provider')
diff --git a/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py
index 976d756..78cfd9a 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_limits_rbac.py
@@ -15,6 +15,7 @@
 
 from tempest.lib import decorators
 
+from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.volume import rbac_base
 
@@ -26,5 +27,20 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="limits_extension:used_limits")
     def test_show_limits(self):
+        # It is enough to check whether any of the following keys below
+        # are in the response body under ['limits']['absolute'], but no harm
+        # in checking for them all.
+        expected_keys = {
+            'totalVolumesUsed',
+            'totalGigabytesUsed',
+            'totalSnapshotsUsed',
+            'totalBackupsUsed',
+            'totalBackupGigabytesUsed'
+        }
+
         with self.rbac_utils.override_role(self):
-            self.volume_limits_client.show_limits()
+            absolute_limits = self.volume_limits_client.show_limits()[
+                'limits']['absolute']
+        for key in expected_keys:
+            if key not in absolute_limits:
+                raise rbac_exceptions.RbacMalformedResponse(attribute=key)
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
index 768372f..8a50141 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
@@ -17,6 +17,7 @@
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
+from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.volume import rbac_base
 
@@ -31,19 +32,14 @@
         cls.volume = cls.create_volume()
         cls.image_id = CONF.compute.image_ref
 
-    def setUp(self):
-        super(VolumeMetadataV3RbacTest, self).setUp()
-        self._add_metadata(self.volume)
-
-    def tearDown(self):
-        self.volumes_client.update_volume_metadata(self.volume['id'], {})
-        super(VolumeMetadataV3RbacTest, self).tearDown()
-
     def _add_metadata(self, volume):
-        # Create metadata for the volume
+        # Create metadata for the volume.
         metadata = {"key1": "value1"}
         self.volumes_client.create_volume_metadata(
             self.volume['id'], metadata)['metadata']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.volumes_client.delete_volume_metadata_item,
+                        self.volume['id'], "key1")
 
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:create_volume_metadata")
@@ -60,10 +56,22 @@
             self.volumes_client.show_volume_metadata(
                 self.volume['id'])['metadata']
 
+    @decorators.idempotent_id('5c0f4c19-b448-4f51-9224-dad5faddc3bb')
+    @rbac_rule_validation.action(service="cinder",
+                                 rule="volume:get_volume_metadata")
+    def test_show_volume_metadata_item(self):
+        self._add_metadata(self.volume)
+
+        with self.rbac_utils.override_role(self):
+            self.volumes_client.show_volume_metadata_item(
+                self.volume['id'], "key1")
+
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:delete_volume_metadata")
     @decorators.idempotent_id('7498dfc1-9db2-4423-ad20-e6dcb25d1beb')
     def test_delete_volume_metadata_item(self):
+        self._add_metadata(self.volume)
+
         with self.rbac_utils.override_role(self):
             self.volumes_client.delete_volume_metadata_item(self.volume['id'],
                                                             "key1")
@@ -72,6 +80,7 @@
                                  rule="volume:update_volume_metadata")
     @decorators.idempotent_id('8ce2ff80-99ba-49ae-9bb1-7e96729ee5af')
     def test_update_volume_metadata_item(self):
+        self._add_metadata(self.volume)
         updated_metadata_item = {"key1": "value1_update"}
         with self.rbac_utils.override_role(self):
             self.volumes_client.update_volume_metadata_item(
@@ -81,11 +90,48 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:update_volume_metadata")
     def test_update_volume_metadata(self):
+        self._add_metadata(self.volume)
         updated_metadata = {"key1": "value1"}
         with self.rbac_utils.override_role(self):
             self.volumes_client.update_volume_metadata(self.volume['id'],
                                                        updated_metadata)
 
+    @decorators.idempotent_id('39e8f82c-f1fc-4905-bf47-177ce2f71bb9')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:volume_image_metadata")
+    def test_list_volumes_details_image_metadata(self):
+        self.volumes_client.update_volume_image_metadata(
+            self.volume['id'], image_id=self.image_id)
+        self.addCleanup(self.volumes_client.delete_volume_image_metadata,
+                        self.volume['id'], 'image_id')
+
+        with self.rbac_utils.override_role(self):
+            resp_body = self.volumes_client.list_volumes(detail=True)[
+                'volumes']
+        expected_attr = 'volume_image_metadata'
+        if expected_attr not in resp_body[0]:
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute=expected_attr)
+
+    @decorators.idempotent_id('53f94d52-0dd5-42cf-a3a4-59b35150b3d5')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:volume_image_metadata")
+    def test_show_volume_details_image_metadata(self):
+        self.volumes_client.update_volume_image_metadata(
+            self.volume['id'], image_id=self.image_id)
+        self.addCleanup(self.volumes_client.delete_volume_image_metadata,
+                        self.volume['id'], 'image_id')
+
+        with self.rbac_utils.override_role(self):
+            resp_body = self.volumes_client.show_volume(self.volume['id'])[
+                'volume']
+        expected_attr = 'volume_image_metadata'
+        if expected_attr not in resp_body:
+            raise rbac_exceptions.RbacMalformedResponse(
+                attribute=expected_attr)
+
     @decorators.idempotent_id('a9d9e825-5ea3-42e6-96f3-7ac4e97b2ed0')
     @rbac_rule_validation.action(
         service="cinder",
diff --git a/releasenotes/notes/remove-admin-only-kwarg-919f1a4797318a33.yaml b/releasenotes/notes/remove-admin-only-kwarg-919f1a4797318a33.yaml
new file mode 100644
index 0000000..21b8eb0
--- /dev/null
+++ b/releasenotes/notes/remove-admin-only-kwarg-919f1a4797318a33.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+  - |
+    The ``admin_only`` kwarg has been removed from ``rbac_rule_validation``
+    decorator because it is no longer used by any tests. Besides that,
+    it should not be used because Patrole is dedicated to RBAC testing and
+    an admin-only check is not RBAC because it does not use ``oslo.policy``
+    library.
diff --git a/releasenotes/notes/start-of-queens-support-6c379f2b9cafbf31.yaml b/releasenotes/notes/start-of-queens-support-6c379f2b9cafbf31.yaml
new file mode 100644
index 0000000..588266b
--- /dev/null
+++ b/releasenotes/notes/start-of-queens-support-6c379f2b9cafbf31.yaml
@@ -0,0 +1,11 @@
+---
+prelude: >
+    This release marks the start of Queens release support in Patrole.
+other:
+    - OpenStack Releases supported after this release are **Queens**
+      and **Pike**.
+
+      The release under current development of this tag is Rocky, meaning
+      that every Patrole commit is also tested against master during the Rocky
+      cycle. However, this does not necessarily mean that using Patrole as of
+      this tag will work against a Rocky (or future release) cloud.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 11e1ac4..ce29994 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -1,9 +1,11 @@
-======================
- Patrole Release Notes
-======================
+=====================
+Patrole Release Notes
+=====================
 
 .. toctree::
    :maxdepth: 1
 
    unreleased
+   v0.3.0
+   v0.2.0
    v0.1.0
diff --git a/releasenotes/source/v0.2.0.rst b/releasenotes/source/v0.2.0.rst
new file mode 100644
index 0000000..415988e
--- /dev/null
+++ b/releasenotes/source/v0.2.0.rst
@@ -0,0 +1,6 @@
+====================
+v0.2.0 Release Notes
+====================
+
+.. release-notes:: 0.2.0 Release Notes
+   :version: 0.2.0
diff --git a/releasenotes/source/v0.3.0.rst b/releasenotes/source/v0.3.0.rst
new file mode 100644
index 0000000..5b6f04a
--- /dev/null
+++ b/releasenotes/source/v0.3.0.rst
@@ -0,0 +1,6 @@
+====================
+v0.3.0 Release Notes
+====================
+
+.. release-notes:: 0.3.0 Release Notes
+   :version: 0.3.0
diff --git a/requirements.txt b/requirements.txt
index 35c6038..cc13aa9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@
 # process, which may cause wedges in the gate later.
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 oslo.log>=3.36.0 # Apache-2.0
-oslo.config>=5.1.0 # Apache-2.0
+oslo.config>=5.2.0 # Apache-2.0
 oslo.policy>=1.30.0 # Apache-2.0
 tempest>=17.1.0 # Apache-2.0
 stevedore>=1.20.0 # Apache-2.0
diff --git a/test-requirements.txt b/test-requirements.txt
index add2388..475d1e5 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,7 +3,7 @@
 # process, which may cause wedges in the gate later.
 hacking>=1.0.0 # Apache-2.0
 
-sphinx!=1.6.6,>=1.6.2 # BSD
+sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
 openstackdocstheme>=1.18.1 # Apache-2.0
 reno>=2.5.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD