Merge "RBAC test for Keystone v3 OS-ENDPOINT-POLICY API"
diff --git a/.zuul.yaml b/.zuul.yaml
index 387c042..089ba6e 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,7 +1,13 @@
 - job:
     name: patrole-base
-    parent: legacy-dsvm-base
+    parent: devstack-tempest
+    description: Patrole base job for admin and Member roles.
+    required-projects:
+      - name: openstack/tempest
+      - name: openstack/patrole
     timeout: 7800
+    roles:
+      - zuul: openstack-dev/devstack
     irrelevant-files:
       - ^(test-|)requirements.txt$
       - ^.*\.rst$
@@ -9,10 +15,17 @@
       - ^patrole/patrole_tempest_plugin/tests/unit/.*$
       - ^releasenotes/.*
       - ^setup.cfg$
-    required-projects:
-      - openstack-infra/devstack-gate
-      - openstack/patrole
-      - openstack/tempest
+    vars:
+      devstack_localrc:
+        TEMPEST_PLUGINS: "'{{ ansible_user_dir }}/src/git.openstack.org/openstack/patrole'"
+      devstack_plugins:
+        patrole: git://git.openstack.org/openstack/patrole.git
+      devstack_services:
+        tempest: true
+        neutron: true
+      tempest_concurrency: 2
+      tempest_test_regex: (?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)
+      tox_envlist: all-plugin
 
 - job:
     name: patrole-base-multinode
@@ -33,14 +46,18 @@
 - job:
     name: patrole-admin
     parent: patrole-base
-    run: playbooks/patrole-admin/run.yaml
-    post-run: playbooks/patrole-admin/post.yaml
+    description: Patrole job for admin role.
+    vars:
+      devstack_localrc:
+        RBAC_TEST_ROLE: admin
 
 - job:
     name: patrole-member
     parent: patrole-base
-    run: playbooks/patrole-member/run.yaml
-    post-run: playbooks/patrole-member/post.yaml
+    description: Patrole job for Member role.
+    vars:
+      devstack_localrc:
+        RBAC_TEST_ROLE: Member
 
 - job:
     name: patrole-multinode-admin
@@ -61,8 +78,20 @@
 - job:
     name: patrole-py35-member
     parent: patrole-base
-    run: playbooks/patrole-py35-member/run.yaml
-    post-run: playbooks/patrole-py35-member/post.yaml
+    description: Patrole py3 job for Member role.
+    vars:
+      devstack_localrc:
+        # Use Member for py3 because arguably negative testing is more
+        # important than admin, which is already covered by patrole-admin job.
+        RBAC_TEST_ROLE: Member
+        USE_PYTHON3: true
+      devstack_services:
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        # Without Swift, c-bak cannot run (in the gate at least).
+        c-bak: false
 
 - project:
     check:
@@ -72,8 +101,10 @@
         - patrole-py35-member
         - patrole-multinode-admin
         - patrole-multinode-member
+        - openstack-tox-lower-constraints
     gate:
       jobs:
         - patrole-admin
         - patrole-member
         - patrole-py35-member
+        - openstack-tox-lower-constraints
diff --git a/HACKING.rst b/HACKING.rst
index a94b47c..056c86a 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -39,25 +39,17 @@
 - [P103] ``self.client`` must not be used as a client alias; this allows for
          code that is more maintainable and easier to read
 
-Role Switching
---------------
+Role Overriding
+---------------
 
-Correct role switching is vital to correct RBAC testing within Patrole. If a
-test does not call ``rbac_utils.switch_role`` with ``toggle_rbac_role=True``
-within the RBAC test, then the test is *not* a valid RBAC test: The API
-endpoint under test will be performed with admin credentials, which is always
-wrong unless ``CONF.patrole.rbac_test_role`` is admin.
+Correct role overriding is vital to correct RBAC testing within Patrole. If a
+test does not call ``rbac_utils.override_role`` within the RBAC test, followed
+by the API endpoint that enforces the expected policy action, then the test is
+**not** a valid Patrole test: The API endpoint under test will be performed
+with admin role, which is always wrong unless ``CONF.patrole.rbac_test_role``
+is also admin.
 
-.. note::
+.. todo::
 
-    Switching back to the admin role for setup and clean up is automatically
-    performed. Toggling ``switch_role`` with ``toggle_rbac_role=False`` within
-    the context of a test should *never* be performed and doing so will likely
-    result in an error being thrown.
-..
-
-Patrole does not have a hacking check for role switching, but does use a
-built-in mechanism for verifying that role switching is being correctly
-executed across tests. If a test does not call ``switch_role`` with
-``toggle_rbac_role=True``, then an ``RbacResourceSetupFailed`` exception
-will be raised.
+    Patrole does not have a hacking check for role overriding, but one may be
+    added in the future.
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..012efb2
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,6 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+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
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 8ec0013..ce799ad 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -35,21 +35,6 @@
 Given the value of ``enable_rbac``, enables or disables Patrole tests. If
 ``enable_rbac`` is ``False``, then Patrole tests are skipped.
 
-Strict Policy Check
--------------------
-
-Currently, many services define their "default" rule to be "anyone allowed".
-If a policy action is not explicitly defined in a policy file, then
-``oslo.policy`` will fall back to the "default" rule. This implies that if
-there's a typo in a policy action specified in a Patrole test, ``oslo.policy``
-can report that the ``rbac_test_role`` will be able to perform the
-non-existent policy action. For a testing framework, this is undesirable
-behavior.
-
-Hence, ``strict_policy_check``, if ``True``, will throw an error in the event
-that a non-existent or bogus policy action is passed to a Patrole test. If
-``False``, however, a ``self.skipException`` will be raised.
-
 Custom Policy Files
 -------------------
 
@@ -70,4 +55,3 @@
 
     Patrole currently does not support policy files located on a host different
     than the one on which it is running.
-..
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 308c12c..be3264e 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -14,6 +14,7 @@
    installation
    configuration
    usage
+   testing
    sampleconf
 
 Developer's Guide
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index c244152..b9cc924 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -10,12 +10,13 @@
 At the command line::
 
     $ git clone http://git.openstack.org/openstack/patrole
-    $ sudo pip install patrole
+    $ sudo pip install ./patrole
 
 Or, if you have virtualenvwrapper installed::
 
-    $ mkvirtualenv patrole
-    $ sudo pip install patrole
+    $ mkvirtualenv patrole_env
+    $ workon patrole_env
+    $ pip install ./patrole
 
 Or to install from the source::
 
diff --git a/doc/source/testing.rst b/doc/source/testing.rst
new file mode 100644
index 0000000..d61c78d
--- /dev/null
+++ b/doc/source/testing.rst
@@ -0,0 +1,51 @@
+.. _patrole-testing:
+
+===============
+Patrole Testing
+===============
+
+Testing Scope
+=============
+
+Patrole testing scope is strictly confined to Role-Based Access Control
+(RBAC). In OpenStack, ``oslo.policy`` is the RBAC library used by all
+major services. Thus, Patrole is concerned with validating that public API
+endpoints are correctly using ``oslo.policy`` for authorization.
+
+In other words, all tests in Patrole are RBAC tests.
+
+Stable Tests
+============
+
+In the discussion below, "correct" means that a test is consistent with
+a service's API-to-policy mapping and "stable" means that a test should
+require minimal maintenance for the supported releases.
+
+Present
+-------
+
+During the Queens release, a `governance spec`_ was pushed to support policy
+in code, which documents the mapping between APIs and each of their policies.
+
+This documentation is an important prerequisite for ensuring that Patrole
+tests for a given service are correct. This mapping can be referenced to
+confirm that Patrole's assumed mapping for a test is correct. For
+example, Nova has implemented policy in code which can be used to verify
+that Patrole's Nova RBAC tests use the same mapping.
+
+If a given service does not have policy in code, this implies that it is
+*more likely* that the RBAC tests for that service are inconsistent with the
+*intended* policy mapping. Until that service implements policy in code, it
+is difficult for Patrole maintainers to verify that tests for that service
+are correct.
+
+Future
+------
+
+Once all services that Patrole tests have implemented policy in code --
+and once Patrole has updated all its tests in accordance with the policy in
+code documentation -- then Patrole tests can guaranteed to be stable.
+
+This stability will be denoted with a 1.0 version release.
+
+.. _governance spec: https://governance.openstack.org/tc/goals/queens/policy-in-code.html
diff --git a/etc/patrole.conf.sample b/etc/patrole.conf.sample
index cafdf8a..ed2b07c 100644
--- a/etc/patrole.conf.sample
+++ b/etc/patrole.conf.sample
@@ -14,18 +14,6 @@
 # Enables RBAC tests. (boolean value)
 #enable_rbac = true
 
-# DEPRECATED: If true, throws RbacParsingException for policies which
-# don't exist or are not included in the service's policy file. If
-# false, throws
-# skipException. (boolean value)
-# This option is deprecated for removal.
-# Its value may be silently ignored in the future.
-# Reason: This option allows for the possibility
-# of false positives. As a testing framework, Patrole should fail any
-# test that
-# passes in an invalid policy.
-#strict_policy_check = true
-
 # List of the paths to search for policy files. Each
 # policy path assumes that the service name is included in the path
 # once. Also
diff --git a/lower-constraints.txt b/lower-constraints.txt
new file mode 100644
index 0000000..a5ff1ca
--- /dev/null
+++ b/lower-constraints.txt
@@ -0,0 +1,84 @@
+alabaster==0.7.10
+appdirs==1.4.3
+asn1crypto==0.24.0
+Babel==2.5.3
+bcrypt==3.1.4
+certifi==2018.1.18
+cffi==1.11.5
+chardet==3.0.4
+cliff==2.11.0
+cmd2==0.8.1
+coverage==4.5.1
+cryptography==2.1.4
+debtcollector==1.19.0
+docutils==0.14
+dulwich==0.19.0
+extras==1.0.0
+fasteners==0.14.1
+fixtures==3.0.0
+flake8==2.5.5
+future==0.16.0
+hacking==1.0.0
+idna==2.6
+imagesize==1.0.0
+iso8601==0.1.12
+Jinja2==2.10
+jsonschema==2.6.0
+keystoneauth1==3.4.0
+linecache2==1.0.0
+MarkupSafe==1.0
+mccabe==0.2.1
+mock==2.0.0
+monotonic==1.4
+mox3==0.25.0
+msgpack==0.5.6
+netaddr==0.7.19
+netifaces==0.10.6
+nose==1.3.7
+nosexcover==1.0.11
+openstackdocstheme==1.20.0
+os-client-config==1.29.0
+oslo.concurrency==3.26.0
+oslo.config==5.2.0
+oslo.context==2.20.0
+oslo.i18n==3.20.0
+oslo.log==3.37.0
+oslo.policy==1.34.0
+oslo.serialization==2.25.0
+oslo.utils==3.36.0
+oslotest==3.3.0
+paramiko==2.4.1
+pbr==3.1.1
+pep8==1.5.7
+prettytable==0.7.2
+pyasn1==0.4.2
+pycparser==2.18
+pyflakes==0.8.1
+Pygments==2.2.0
+pyinotify==0.9.6
+PyNaCl==1.2.1
+pyparsing==2.2.0
+pyperclip==1.6.0
+python-dateutil==2.7.0
+python-mimeparse==1.6.0
+python-subunit==1.2.0
+pytz==2018.3
+PyYAML==3.12
+reno==2.7.0
+requests==2.18.4
+requestsexceptions==1.4.0
+rfc3986==1.1.0
+six==1.11.0
+snowballstemmer==1.2.1
+Sphinx==1.6.5
+sphinxcontrib-websupport==1.0.1
+stestr==2.0.0
+stevedore==1.28.0
+tempest==18.0.0
+testrepository==0.0.20
+testtools==2.3.0
+traceback2==1.4.0
+unittest2==1.1.0
+urllib3==1.22
+voluptuous==0.11.1
+wrapt==1.10.11
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index 8ac2a20..0077d19 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -27,15 +27,6 @@
     cfg.BoolOpt('enable_rbac',
                 default=True,
                 help="Enables RBAC tests."),
-    cfg.BoolOpt('strict_policy_check',
-                default=True,
-                deprecated_for_removal=True,
-                deprecated_reason="""This option allows for the possibility
-of false positives. As a testing framework, Patrole should fail any test that
-passes in an invalid policy.""",
-                help="""If true, throws RbacParsingException for policies which
-don't exist or are not included in the service's policy file. If false, throws
-skipException."""),
     # TODO(rb560u): There needs to be support for reading these JSON files from
     # other hosts. It may be possible to leverage the v3 identity policy API.
     cfg.ListOpt('custom_policy_files',
diff --git a/patrole_tempest_plugin/policy_authority.py b/patrole_tempest_plugin/policy_authority.py
index 6851942..99348b9 100644
--- a/patrole_tempest_plugin/policy_authority.py
+++ b/patrole_tempest_plugin/policy_authority.py
@@ -158,6 +158,8 @@
 
         :param string rule_name: Rule to be checked using ``oslo.policy``.
         :param bool is_admin: Whether admin context is used.
+        :raises RbacParsingException: If `rule_name`` does not exist in the
+            cloud (in policy file or among registered in-code policy defaults).
         """
         is_admin_context = self._is_admin_context(role)
         is_allowed = self._allowed(
@@ -215,9 +217,11 @@
             policy_data = mgr_policy_data
         else:
             error_message = (
-                'Policy file for {0} service neither found in code nor at {1}.'
-                .format(service, [loc % service for loc in
-                                  CONF.patrole.custom_policy_files])
+                'Policy file for {0} service was not found among the '
+                'registered in-code policies or in any of the possible policy '
+                'files: {1}.'.format(service,
+                                     [loc % service for loc in
+                                      CONF.patrole.custom_policy_files])
             )
             raise rbac_exceptions.RbacParsingException(error_message)
 
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index 97d246f..d3213cf 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -13,9 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import functools
 import logging
 import sys
-import testtools
 
 from oslo_utils import excutils
 import six
@@ -105,9 +105,9 @@
         @rbac_rule_validation.action(
             service="nova", rule="os_compute_api:os-agents")
         def test_list_agents_rbac(self):
-            # The call to `switch_role` is mandatory.
-            self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-            self.agents_client.list_agents()
+            # The call to `override_role` is mandatory.
+            with self.rbac_utils.override_role(self):
+                self.agents_client.list_agents()
     """
 
     if extra_target_data is None:
@@ -116,6 +116,7 @@
     def decorator(test_func):
         role = CONF.patrole.rbac_test_role
 
+        @functools.wraps(test_func)
         def wrapper(*args, **kwargs):
             if args and isinstance(args[0], test.BaseTestCase):
                 test_obj = args[0]
@@ -163,8 +164,7 @@
                     LOG.error(msg)
             else:
                 if not allowed:
-                    LOG.error("Role %s was allowed to perform %s",
-                              role, rule)
+                    LOG.error("Role %s was allowed to perform %s", role, rule)
                     raise rbac_exceptions.RbacOverPermission(
                         "OverPermission: Role %s was allowed to perform %s" %
                         (role, rule))
@@ -177,8 +177,7 @@
                         "Allowed" if allowed else "Denied",
                         test_status)
 
-        _wrapper = testtools.testcase.attr(role)(wrapper)
-        return _wrapper
+        return wrapper
     return decorator
 
 
@@ -200,10 +199,6 @@
 
     :raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing
         from the `auth_provider` attribute in `test_obj`.
-    :raises RbacParsingException: if ``[patrole] strict_policy_check`` is True
-        and the ``rule`` does not exist in the system.
-    :raises skipException: If ``[patrole] strict_policy_check`` is False and
-        the ``rule`` does not exist in the system.
     """
 
     try:
@@ -215,33 +210,27 @@
         LOG.error(msg)
         raise rbac_exceptions.RbacResourceSetupFailed(msg)
 
-    try:
-        role = CONF.patrole.rbac_test_role
-        # Test RBAC against custom requirements. Otherwise use oslo.policy.
-        if CONF.patrole.test_custom_requirements:
-            authority = requirements_authority.RequirementsAuthority(
-                CONF.patrole.custom_requirements_file, service)
-        else:
-            formatted_target_data = _format_extra_target_data(
-                test_obj, extra_target_data)
-            authority = policy_authority.PolicyAuthority(
-                project_id, user_id, service,
-                extra_target_data=formatted_target_data)
-        is_allowed = authority.allowed(rule, role)
+    role = CONF.patrole.rbac_test_role
+    # Test RBAC against custom requirements. Otherwise use oslo.policy.
+    if CONF.patrole.test_custom_requirements:
+        authority = requirements_authority.RequirementsAuthority(
+            CONF.patrole.custom_requirements_file, service)
+    else:
+        formatted_target_data = _format_extra_target_data(
+            test_obj, extra_target_data)
+        authority = policy_authority.PolicyAuthority(
+            project_id, user_id, service,
+            extra_target_data=formatted_target_data)
+    is_allowed = authority.allowed(rule, role)
 
-        if is_allowed:
-            LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule,
-                      role)
-        else:
-            LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!",
-                      rule, role)
-        return is_allowed
-    except rbac_exceptions.RbacParsingException as e:
-        if CONF.patrole.strict_policy_check:
-            raise e
-        else:
-            raise testtools.TestCase.skipException(str(e))
-    return False
+    if is_allowed:
+        LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule,
+                  role)
+    else:
+        LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!",
+                  rule, role)
+
+    return is_allowed
 
 
 def _get_exception_type(expected_error_code=403):
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 49cb5e1..1ada69b 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -15,7 +15,6 @@
 
 import abc
 from contextlib import contextmanager
-import debtcollector.removals
 import six
 import time
 
@@ -107,20 +106,6 @@
             # up.
             self._override_role(test_obj, False)
 
-    @debtcollector.removals.remove(removal_version='Rocky')
-    def switch_role(self, test_obj, toggle_rbac_role):
-        """Switch the role used by `os_primary` Tempest credentials.
-
-        Switch the role used by `os_primary` credentials to:
-
-        * admin if `toggle_rbac_role` is False
-        * `CONF.patrole.rbac_test_role` if `toggle_rbac_role` is True
-
-        :param test_obj: instance of :py:class:`tempest.test.BaseTestCase`
-        :param toggle_rbac_role: role to switch `os_primary` Tempest creds to
-        """
-        self._override_role(test_obj, toggle_rbac_role)
-
     def _override_role(self, test_obj, toggle_rbac_role=False):
         """Private helper for overriding ``os_primary`` Tempest credentials.
 
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_snapshots_metadata_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py
index 226411f..1c5fb2e 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_snapshots_metadata_rbac.py
@@ -47,15 +47,6 @@
         self.snapshots_client.create_snapshot_metadata(
             self.snapshot_id, metadata)['metadata']
 
-    @rbac_rule_validation.action(
-        service="cinder",
-        rule="volume_extension:extended_snapshot_attributes")
-    @decorators.idempotent_id('c9cbec1c-edfe-46b8-825b-7b6ac0a58c25')
-    def test_create_snapshot_metadata(self):
-        # Create metadata for the snapshot
-        with self.rbac_utils.override_role(self):
-            self._create_test_snapshot_metadata()
-
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:get_snapshot_metadata")
     @decorators.idempotent_id('f6912bb1-62e6-483d-bcd0-e98c1641f4c3')
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py
index df4fd10..7d721c4 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_snapshots_rbac.py
@@ -17,6 +17,7 @@
 from tempest import config
 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
 
@@ -39,20 +40,15 @@
         # Create a test shared snapshot for tests
         cls.snapshot = cls.create_snapshot(cls.volume['id'])
 
-    def _list_by_param_values(self, params, with_detail=False):
-        # Perform list or list_details action with given params
-        # and validates result.
-        if with_detail:
-            self.snapshots_client.list_snapshots(
-                detail=True, params=params)['snapshots']
-        else:
-            self.snapshots_client.list_snapshots(
-                params=params)['snapshots']
+    def _list_by_param_values(self, with_detail=False, **params):
+        # Perform list or list_details action with given params.
+        return self.snapshots_client.list_snapshots(
+            detail=with_detail, **params)['snapshots']
 
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:create_snapshot")
     @decorators.idempotent_id('ac7b2ee5-fbc0-4360-afc2-de8fa4881ede')
-    def test_snapshot_create(self):
+    def test_create_snapshot(self):
         # Create a temp snapshot
         with self.rbac_utils.override_role(self):
             self.create_snapshot(self.volume['id'])
@@ -60,16 +56,33 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:get_snapshot")
     @decorators.idempotent_id('93a11b40-1ba8-44d6-a196-f8d97220f796')
-    def test_snapshot_get(self):
+    def test_show_snapshot(self):
         # Get the snapshot
         with self.rbac_utils.override_role(self):
-            self.snapshots_client.show_snapshot(self.snapshot
-                                                ['id'])['snapshot']
+            self.snapshots_client.show_snapshot(
+                self.snapshot['id'])['snapshot']
+
+    @decorators.idempotent_id('5d6f5f21-9293-4f2a-8f44-cabdc24d92cb')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:extended_snapshot_attributes")
+    def test_show_snapshot_with_extended_attributes(self):
+        """List snapshots with extended attributes."""
+        expected_attrs = ('os-extended-snapshot-attributes:project_id',
+                          'os-extended-snapshot-attributes:progress')
+
+        with self.rbac_utils.override_role(self):
+            resp = self.snapshots_client.show_snapshot(
+                self.snapshot['id'])['snapshot']
+        for expected_attr in expected_attrs:
+            if expected_attr not in resp:
+                raise rbac_exceptions.RbacMalformedResponse(
+                    attribute=expected_attr)
 
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:update_snapshot")
     @decorators.idempotent_id('53fe8ee3-3bea-4ae8-a979-3c98ea72f620')
-    def test_snapshot_update(self):
+    def test_update_snapshot(self):
         new_desc = 'This is the new description of snapshot.'
         params = {'description': new_desc}
         # Updates snapshot with new values
@@ -80,19 +93,9 @@
             self.snapshots_client, self.snapshot['id'], 'available')
 
     @rbac_rule_validation.action(service="cinder",
-                                 rule="volume:get_all_snapshots")
-    @decorators.idempotent_id('e4edf0c0-2cd3-420f-b8ab-4d98a0718608')
-    def test_snapshots_get_all(self):
-        """list snapshots with params."""
-        # Verify list snapshots by display_name filter
-        params = {'name': self.snapshot['name']}
-        with self.rbac_utils.override_role(self):
-            self._list_by_param_values(params)
-
-    @rbac_rule_validation.action(service="cinder",
                                  rule="volume:delete_snapshot")
     @decorators.idempotent_id('c7fe54ec-3b70-4772-ba11-f166d95888a3')
-    def test_snapshot_delete(self):
+    def test_delete_snapshot(self):
         # Create a temp snapshot
         temp_snapshot = self.create_snapshot(self.volume['id'])
         with self.rbac_utils.override_role(self):
@@ -100,3 +103,38 @@
             self.snapshots_client.delete_snapshot(temp_snapshot['id'])
         self.snapshots_client.wait_for_resource_deletion(
             temp_snapshot['id'])
+
+    @rbac_rule_validation.action(service="cinder",
+                                 rule="volume:get_all_snapshots")
+    @decorators.idempotent_id('e4edf0c0-2cd3-420f-b8ab-4d98a0718608')
+    def test_list_snapshots(self):
+        """List snapshots with params."""
+        params = {'name': self.snapshot['name']}
+        with self.rbac_utils.override_role(self):
+            self._list_by_param_values(**params)
+
+    @decorators.idempotent_id('f3155d8e-45ee-45c9-910d-18c0242229e1')
+    @rbac_rule_validation.action(service="cinder",
+                                 rule="volume:get_all_snapshots")
+    def test_list_snapshots_details(self):
+        """List snapshots details with params."""
+        params = {'name': self.snapshot['name']}
+        with self.rbac_utils.override_role(self):
+            self._list_by_param_values(with_detail=True, **params)
+
+    @decorators.idempotent_id('dd37f388-2731-446d-a78f-676997ebb04a')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:extended_snapshot_attributes")
+    def test_list_snapshots_details_with_extended_attributes(self):
+        """List snapshots details with extended attributes."""
+        expected_attrs = ('os-extended-snapshot-attributes:project_id',
+                          'os-extended-snapshot-attributes:progress')
+        params = {'name': self.snapshot['name']}
+
+        with self.rbac_utils.override_role(self):
+            resp = self._list_by_param_values(with_detail=True, **params)
+        for expected_attr in expected_attrs:
+            if expected_attr not in resp[0]:
+                raise rbac_exceptions.RbacMalformedResponse(
+                    attribute=expected_attr)
diff --git a/patrole_tempest_plugin/tests/unit/test_policy_authority.py b/patrole_tempest_plugin/tests/unit/test_policy_authority.py
index d2074e7..3e2cc4c 100644
--- a/patrole_tempest_plugin/tests/unit/test_policy_authority.py
+++ b/patrole_tempest_plugin/tests/unit/test_policy_authority.py
@@ -387,12 +387,11 @@
                               policy_authority.PolicyAuthority,
                               None, None, 'test_service')
 
-        expected_error = \
-            'Policy file for {0} service neither found in code '\
-            'nor at {1}.'.format(
-                'test_service',
-                [CONF.patrole.custom_policy_files[0] % 'test_service'])
-
+        expected_error = (
+            'Policy file for {0} service was not found among the registered '
+            'in-code policies or in any of the possible policy files: {1}.'
+            .format('test_service',
+                    [CONF.patrole.custom_policy_files[0] % 'test_service']))
         self.assertIn(expected_error, str(e))
 
     @mock.patch.object(policy_authority, 'json', autospec=True)
@@ -436,7 +435,8 @@
                               None, None, 'tenant_rbac_policy')
 
         expected_error = (
-            'Policy file for {0} service neither found in code nor at {1}.'
+            'Policy file for {0} service was not found among the registered '
+            'in-code policies or in any of the possible policy files: {1}.'
             .format('tenant_rbac_policy', [CONF.patrole.custom_policy_files[0]
                                            % 'tenant_rbac_policy']))
         self.assertIn(expected_error, str(e))
@@ -485,9 +485,10 @@
                          policy_parser.policy_files['test_service'])
 
     def test_discover_policy_files_with_no_valid_files(self):
-        expected_error = ("Policy file for test_service service neither found "
-                          "in code nor at %s." %
-                          [self.conf_policy_path % 'test_service'])
+        expected_error = (
+            'Policy file for {0} service was not found among the registered '
+            'in-code policies or in any of the possible policy files: {1}.'
+            .format('test_service', [self.conf_policy_path % 'test_service']))
 
         e = self.assertRaises(rbac_exceptions.RbacParsingException,
                               policy_authority.PolicyAuthority,
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index 82f0428..85547e1 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -13,7 +13,6 @@
 #    under the License.
 
 import mock
-import testtools
 
 from tempest.lib import exceptions
 from tempest import manager
@@ -297,12 +296,8 @@
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
     def test_invalid_policy_rule_raises_parsing_exception(
             self, mock_authority):
-        """Test that invalid policy action causes test to be fail with
-        ``[patrole] strict_policy_check`` set to True.
+        """Test that invalid policy action causes test to raise an exception.
         """
-        self.useFixture(
-            fixtures.ConfPatcher(strict_policy_check=True, group='patrole'))
-
         mock_authority.PolicyAuthority.return_value.allowed.\
             side_effect = rbac_exceptions.RbacParsingException
 
@@ -319,30 +314,6 @@
             mock.sentinel.service, extra_target_data={})
 
     @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
-    def test_invalid_policy_rule_raises_skip_exception(
-            self, mock_authority):
-        """Test that invalid policy action causes test to be skipped with
-        ``[patrole] strict_policy_check`` set to False.
-        """
-        self.useFixture(
-            fixtures.ConfPatcher(strict_policy_check=False, group='patrole'))
-
-        mock_authority.PolicyAuthority.return_value.allowed.side_effect = (
-            rbac_exceptions.RbacParsingException)
-
-        @rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
-        def test_policy(*args):
-            pass
-
-        error_re = 'Attempted to test an invalid policy file or action'
-        self.assertRaisesRegex(testtools.TestCase.skipException, error_re,
-                               test_policy, self.mock_test_args)
-
-        mock_authority.PolicyAuthority.assert_called_once_with(
-            mock.sentinel.project_id, mock.sentinel.user_id,
-            mock.sentinel.service, extra_target_data={})
-
-    @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
     def test_get_exception_type_404(self, _):
         """Test that getting a 404 exception type returns NotFound."""
         expected_exception = exceptions.NotFound
diff --git a/playbooks/patrole-admin/post.yaml b/playbooks/patrole-admin/post.yaml
deleted file mode 100644
index dac8753..0000000
--- a/playbooks/patrole-admin/post.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-- hosts: primary
-  tasks:
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*nose_results.html
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*testr_results.html.gz
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/.testrepository/tmp*
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*testrepository.subunit.gz
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}/tox'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/.tox/*/log/*
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/logs/**
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
diff --git a/playbooks/patrole-admin/run.yaml b/playbooks/patrole-admin/run.yaml
deleted file mode 100644
index 57f208d..0000000
--- a/playbooks/patrole-admin/run.yaml
+++ /dev/null
@@ -1,60 +0,0 @@
-- hosts: all
-  name: Autoconverted job legacy-tempest-dsvm-patrole-admin from old job gate-tempest-dsvm-patrole-admin-ubuntu-xenial
-  tasks:
-
-    - name: Ensure legacy workspace directory
-      file:
-        path: '{{ ansible_user_dir }}/workspace'
-        state: directory
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          cat > clonemap.yaml << EOF
-          clonemap:
-            - name: openstack-infra/devstack-gate
-              dest: devstack-gate
-          EOF
-          /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
-              git://git.openstack.org \
-              openstack-infra/devstack-gate
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          cat << 'EOF' >>"/tmp/dg-local.conf"
-          [[local|localrc]]
-          enable_plugin patrole git://git.openstack.org/openstack/patrole
-          TEMPEST_PLUGINS='/opt/stack/new/patrole'
-          # Needed by Patrole devstack plugin
-          RBAC_TEST_ROLE=admin
-          EOF
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          export PYTHONUNBUFFERED=true
-          export DEVSTACK_GATE_TEMPEST=1
-          export DEVSTACK_GATE_NEUTRON=1
-          export DEVSTACK_GATE_TEMPEST_REGEX='(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)'
-          export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
-          export TEMPEST_CONCURRENCY=2
-          export PROJECTS="openstack/patrole $PROJECTS"
-          export BRANCH_OVERRIDE=default
-          if [ "$BRANCH_OVERRIDE" != "default" ] ; then
-              export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
-          fi
-          cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
-          ./safe-devstack-vm-gate-wrap.sh
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
diff --git a/playbooks/patrole-member/post.yaml b/playbooks/patrole-member/post.yaml
deleted file mode 100644
index dac8753..0000000
--- a/playbooks/patrole-member/post.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-- hosts: primary
-  tasks:
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*nose_results.html
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*testr_results.html.gz
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/.testrepository/tmp*
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*testrepository.subunit.gz
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}/tox'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/.tox/*/log/*
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/logs/**
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
diff --git a/playbooks/patrole-member/run.yaml b/playbooks/patrole-member/run.yaml
deleted file mode 100644
index b95467f..0000000
--- a/playbooks/patrole-member/run.yaml
+++ /dev/null
@@ -1,61 +0,0 @@
-- hosts: all
-  name: Autoconverted job legacy-tempest-dsvm-patrole-member from old job gate-tempest-dsvm-patrole-member-ubuntu-xenial
-  tasks:
-
-    - name: Ensure legacy workspace directory
-      file:
-        path: '{{ ansible_user_dir }}/workspace'
-        state: directory
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          cat > clonemap.yaml << EOF
-          clonemap:
-            - name: openstack-infra/devstack-gate
-              dest: devstack-gate
-          EOF
-          /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
-              git://git.openstack.org \
-              openstack-infra/devstack-gate
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          cat << 'EOF' >>"/tmp/dg-local.conf"
-          [[local|localrc]]
-          enable_plugin patrole git://git.openstack.org/openstack/patrole
-          TEMPEST_PLUGINS='/opt/stack/new/patrole'
-          # Needed by Patrole devstack plugin
-          RBAC_TEST_ROLE=member
-          EOF
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          export PYTHONUNBUFFERED=true
-          export DEVSTACK_GATE_TEMPEST=1
-          export DEVSTACK_GATE_NEUTRON=1
-          export DEVSTACK_GATE_TEMPEST_REGEX='(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)'
-          export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
-          export TEMPEST_CONCURRENCY=2
-          export PROJECTS="openstack/patrole $PROJECTS"
-          export BRANCH_OVERRIDE=default
-          if [ "$BRANCH_OVERRIDE" != "default" ] ; then
-              export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
-          fi
-          cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
-          ./safe-devstack-vm-gate-wrap.sh
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
diff --git a/playbooks/patrole-py35-member/post.yaml b/playbooks/patrole-py35-member/post.yaml
deleted file mode 100644
index dac8753..0000000
--- a/playbooks/patrole-py35-member/post.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-- hosts: primary
-  tasks:
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*nose_results.html
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*testr_results.html.gz
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/.testrepository/tmp*
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=**/*testrepository.subunit.gz
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}/tox'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/.tox/*/log/*
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
-
-    - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
-      synchronize:
-        src: '{{ ansible_user_dir }}/workspace/'
-        dest: '{{ zuul.executor.log_root }}'
-        mode: pull
-        copy_links: true
-        verify_host: true
-        rsync_opts:
-          - --include=/logs/**
-          - --include=*/
-          - --exclude=*
-          - --prune-empty-dirs
diff --git a/playbooks/patrole-py35-member/run.yaml b/playbooks/patrole-py35-member/run.yaml
deleted file mode 100644
index e895702..0000000
--- a/playbooks/patrole-py35-member/run.yaml
+++ /dev/null
@@ -1,70 +0,0 @@
-- hosts: all
-  name: Autoconverted job legacy-tempest-dsvm-patrole-py35-member from old job gate-tempest-dsvm-patrole-py35-member-ubuntu-xenial
-  tasks:
-
-    - name: Ensure legacy workspace directory
-      file:
-        path: '{{ ansible_user_dir }}/workspace'
-        state: directory
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          cat > clonemap.yaml << EOF
-          clonemap:
-            - name: openstack-infra/devstack-gate
-              dest: devstack-gate
-          EOF
-          /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
-              git://git.openstack.org \
-              openstack-infra/devstack-gate
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          cat << 'EOF' >>"/tmp/dg-local.conf"
-          [[local|localrc]]
-          enable_plugin patrole git://git.openstack.org/openstack/patrole
-          TEMPEST_PLUGINS='/opt/stack/new/patrole'
-          # Needed by Patrole devstack plugin
-          RBAC_TEST_ROLE=member
-          # Swift is not ready for python3 yet
-          disable_service s-account
-          disable_service s-container
-          disable_service s-object
-          disable_service s-proxy
-          # Without Swift, c-bak cannot run (in the Gate at least)
-          disable_service c-bak
-          EOF
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
-
-    - shell:
-        cmd: |
-          set -e
-          set -x
-          export PYTHONUNBUFFERED=true
-          export DEVSTACK_GATE_USE_PYTHON3=True
-          # Ensure that tempest set up is executed, but do not automatically
-          # execute tempest tests; they are executed in post_test_hook.
-          export DEVSTACK_GATE_TEMPEST=1
-          export DEVSTACK_GATE_NEUTRON=1
-          export DEVSTACK_GATE_TEMPEST_REGEX='(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)'
-          export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
-          export TEMPEST_CONCURRENCY=2
-          export PROJECTS="openstack/patrole $PROJECTS"
-          export BRANCH_OVERRIDE=default
-          if [ "$BRANCH_OVERRIDE" != "default" ] ; then
-              export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
-          fi
-          cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
-          ./safe-devstack-vm-gate-wrap.sh
-        executable: /bin/bash
-        chdir: '{{ ansible_user_dir }}/workspace'
-      environment: '{{ zuul | zuul_legacy_vars }}'
diff --git a/releasenotes/notes/remove-deprecated-switch-role-148c9a5c6796857f.yaml b/releasenotes/notes/remove-deprecated-switch-role-148c9a5c6796857f.yaml
new file mode 100644
index 0000000..b303d09
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-switch-role-148c9a5c6796857f.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    The ``switch_role`` method in ``rbac_utils`` module has been removed
+    because it is a clunky way of manipulating Tempest roles to achieve
+    RBAC testing. Use ``override_role`` instead.
diff --git a/releasenotes/notes/remove-strict-policy-check-480e3d664f7b2d96.yaml b/releasenotes/notes/remove-strict-policy-check-480e3d664f7b2d96.yaml
new file mode 100644
index 0000000..37c5e1e
--- /dev/null
+++ b/releasenotes/notes/remove-strict-policy-check-480e3d664f7b2d96.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    The ``[patrole].strict_policy_check`` was deprecated during the Queens
+    release cycle. It is removed in this release cycle because Patrole should
+    always fail on invalid policies.
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/setup.cfg b/setup.cfg
index 143d3aa..02ce831 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -23,12 +23,6 @@
 packages =
     patrole_tempest_plugin
 
-[build_sphinx]
-source-dir = doc/source
-build-dir = doc/build
-all_files = 1
-warning-is-error = 1
-
 [upload_sphinx]
 upload-dir = doc/build/html
 
diff --git a/test-requirements.txt b/test-requirements.txt
index add2388..d6e73bd 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,10 +2,6 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 hacking>=1.0.0 # Apache-2.0
-
-sphinx!=1.6.6,>=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
 mock>=2.0.0 # BSD
 coverage!=4.4,>=4.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index d5e3b91..cca09d0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -46,11 +46,24 @@
                       rm
 
 [testenv:docs]
-commands = python setup.py build_sphinx
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/doc/requirements.txt
+commands =
+  rm -rf doc/build
+  sphinx-build -W -b html doc/source doc/build/html
+whitelist_externals = rm
 
 [testenv:releasenotes]
+deps =
+  -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/doc/requirements.txt
 commands =
+  rm -rf releasenotes/build
   sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+whitelist_externals = rm
 
 [testenv:debug]
 commands = oslo_debug_helper -t patrole_tempest_plugin/tests {posargs}
@@ -78,3 +91,10 @@
 
 [hacking]
 local-check-factory = patrole_tempest_plugin.hacking.checks.factory
+
+[testenv:lower-constraints]
+basepython = python3
+deps =
+  -c{toxinidir}/lower-constraints.txt
+  -r{toxinidir}/test-requirements.txt
+  -r{toxinidir}/requirements.txt