Merge "Add support for testing custom RBAC requirements"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 33c2cc3..ddb1d45 100755
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -23,7 +23,7 @@
 extensions = [
     'sphinx.ext.autodoc',
     #'sphinx.ext.intersphinx',
-    'oslosphinx'
+    'openstackdocstheme'
 ]
 
 # autodoc generation is a bit aggressive and a nuisance when doing heavy
@@ -55,8 +55,16 @@
 # The theme to use for HTML and HTML Help pages.  Major themes that come with
 # Sphinx are currently 'default' and 'sphinxdoc'.
 # html_theme_path = ["."]
-# html_theme = '_theme'
 # html_static_path = ['static']
+html_theme = 'openstackdocs'
+
+# openstackdocstheme options
+repository_name = 'openstack/patrole'
+bug_project = 'patrole'
+bug_tag = ''
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
 
 # Output file base name for HTML help builder.
 htmlhelp_basename = '%sdoc' % project
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index c7e0b2b..c088ce7 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -134,6 +134,20 @@
 
 
 def _is_authorized(test_obj, service, rule_name, extra_target_data):
+    """Validates whether current RBAC role has permission to do policy action.
+
+    :param test_obj: type BaseTestCase (tempest base test class)
+    :param service: the OpenStack service that enforces ``rule_name``
+    :param rule_name: the name of the policy action
+    :param extra_target_data: dictionary with unresolved string literals that
+        reference nested BaseTestCase attributes
+    :returns: True if the current RBAC role can perform the policy action else
+        False
+    :raises RbacParsingException: if ``CONF.rbac.strict_policy_check`` is
+        enabled and the ``rule_name`` does not exist in the system
+    :raises skipException: if ``CONF.rbac.strict_policy_check`` is
+        disabled and the ``rule_name`` does not exist in the system
+    """
     try:
         project_id = test_obj.auth_provider.credentials.project_id
         user_id = test_obj.auth_provider.credentials.user_id
@@ -224,7 +238,8 @@
     :param test_obj: type BaseTestCase (tempest base test class)
     :param extra_target_data: dictionary with unresolved string literals that
         reference nested BaseTestCase attributes
-    :returns: dictionary with resolved BaseTestCase attributes
+    :returns: dictionary containing additional object data needed by
+        oslo.policy to validate generic checks
     """
     attr_value = test_obj
     formatted_target_data = {}
diff --git a/patrole_tempest_plugin/tests/api/compute/test_admin_password_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_admin_password_rbac.py
deleted file mode 100644
index 38b5f1a..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_admin_password_rbac.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# 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.
-
-import testtools
-
-from tempest.common import waiters
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-CONF = config.CONF
-
-
-class AdminPasswordRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @testtools.skipUnless(CONF.compute_feature_enabled.change_password,
-                          'Change password not available.')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-admin-password")
-    @decorators.idempotent_id('908a7d59-3a66-441c-94cf-38e57ed14956')
-    def test_change_server_password(self):
-        server_id = self.create_test_server(wait_until='ACTIVE')['id']
-
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.change_password(
-            server_id, adminPass=data_utils.rand_password())
-        waiters.wait_for_server_status(
-            self.os_admin.servers_client, server_id, 'ACTIVE')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_lock_server_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_lock_server_rbac.py
deleted file mode 100644
index 1daf305..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_lock_server_rbac.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# 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 patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base as base
-
-
-class ComputeLockServersRbacTest(base.BaseV2ComputeRbacTest):
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-lock-server:lock")
-    @decorators.idempotent_id('b81e10fb-1864-498f-8c1d-5175c6fec5fb')
-    def test_lock_server(self):
-        server = self.create_test_server(wait_until='ACTIVE')
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.lock_server(server['id'])
-        self.addCleanup(self.servers_client.unlock_server, server['id'])
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-lock-server:unlock")
-    @decorators.idempotent_id('d50ef8e8-4bce-11e7-b114-b2f933d5fe66')
-    def test_unlock_server(self):
-        server = self.create_test_server(wait_until='ACTIVE')
-        self.servers_client.lock_server(server['id'])
-        self.addCleanup(self.servers_client.unlock_server, server['id'])
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.unlock_server(server['id'])
-
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-lock-server:unlock:unlock_override")
-    @decorators.idempotent_id('40dfeef9-73ee-48a9-be19-a219875de457')
-    def test_unlock_server_override(self):
-        server = self.create_test_server(wait_until='ACTIVE')
-        # In order to trigger the unlock:unlock_override policy instead
-        # of the unlock policy, the server must be locked by a different
-        # user than the one who is attempting to unlock it.
-        self.os_admin.servers_client.lock_server(server['id'])
-        self.addCleanup(self.servers_client.unlock_server, server['id'])
-
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.unlock_server(server['id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py
new file mode 100644
index 0000000..a7d8997
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/compute/test_quota_class_sets_rbac.py
@@ -0,0 +1,84 @@
+#    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.common import tempest_fixtures as fixtures
+from tempest.lib.common.utils import data_utils
+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 QuotaClassesRbacTest(rbac_base.BaseV2ComputeRbacTest):
+
+    def setUp(self):
+        # All test cases in this class need to externally lock on doing
+        # anything with default quota values.
+        self.useFixture(fixtures.LockFixture('compute_quotas'))
+        super(QuotaClassesRbacTest, self).setUp()
+
+    @classmethod
+    def skip_checks(cls):
+        super(QuotaClassesRbacTest, cls).skip_checks()
+        if not test.is_extension_enabled('os-quota-class-sets', 'compute'):
+            msg = "%s skipped as os-quota-class-sets extension not enabled."\
+                  % cls.__name__
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(QuotaClassesRbacTest, cls).setup_clients()
+        cls.quota_classes_client = cls.os_primary.quota_classes_client
+        cls.identity_projects_client = cls.os_primary.projects_client
+
+    @classmethod
+    def resource_setup(cls):
+        super(QuotaClassesRbacTest, cls).resource_setup()
+        # Create a project with its own quota.
+        project_name = data_utils.rand_name(cls.__name__ + '-Project')
+        cls.project_id = cls.identity_projects_client.create_project(
+            project_name)['project']['id']
+
+    @classmethod
+    def resource_cleanup(cls):
+        cls.identity_projects_client.delete_project(
+            cls.project_id)
+        super(QuotaClassesRbacTest, cls).resource_cleanup()
+
+    @decorators.idempotent_id('c10198ed-9df2-440e-a49b-367dadc6de94')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-quota-class-sets:show")
+    def test_show_quota_class_set(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.quota_classes_client.show_quota_class_set('default')[
+            'quota_class_set']
+
+    @decorators.idempotent_id('81889e69-efd2-4e96-bb4c-ee3b646b9755')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-quota-class-sets:update")
+    def test_update_quota_class_set(self):
+        # Update the pre-existing quotas for the project_id.
+        quota_class_set = self.quota_classes_client.show_quota_class_set(
+            self.project_id)['quota_class_set']
+        quota_class_set.pop('id')
+        for quota, default in quota_class_set.items():
+            quota_class_set[quota] = default + 100
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.quota_classes_client.update_quota_class_set(
+            self.project_id, **quota_class_set)['quota_class_set']
diff --git a/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py
index dc30d94..a4527c3 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_quota_sets_rbac.py
@@ -33,7 +33,7 @@
     def skip_checks(cls):
         super(QuotaSetsRbacTest, cls).skip_checks()
         if not test.is_extension_enabled('os-quota-sets', 'compute'):
-            msg = "%s skipped as quotas extension not enabled."\
+            msg = "%s skipped as os-quota-sets extension not enabled."\
                   % cls.__name__
             raise cls.skipException(msg)
 
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 cb826e3..d4c856d 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
@@ -13,7 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest import test
@@ -21,6 +25,8 @@
 from patrole_tempest_plugin import rbac_rule_validation
 from patrole_tempest_plugin.tests.api.compute import rbac_base
 
+CONF = config.CONF
+
 
 class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
     """Test multiple policy actions that require a server to be created.
@@ -88,6 +94,24 @@
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.reset_network(self.server_id)
 
+    @testtools.skipUnless(CONF.compute_feature_enabled.change_password,
+                          'Change password not available.')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-admin-password")
+    @decorators.idempotent_id('908a7d59-3a66-441c-94cf-38e57ed14956')
+    def test_change_server_password(self):
+        """Test change admin password, part of os-admin-password."""
+        original_password = self.servers_client.show_password(self.server_id)
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.change_password(
+            self.server_id, adminPass=data_utils.rand_password())
+        self.addCleanup(self.servers_client.change_password, self.server_id,
+                        adminPass=original_password)
+        waiters.wait_for_server_status(
+            self.os_admin.servers_client, self.server_id, 'ACTIVE')
+
     @test.requires_ext(extension='os-deferred-delete', service='compute')
     @decorators.idempotent_id('189bfed4-1e6d-475c-bb8c-d57e60895391')
     @rbac_rule_validation.action(
@@ -99,6 +123,45 @@
         # Force-deleting a server enforces os-deferred-delete.
         self.servers_client.force_delete_server(self.server_id)
 
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-lock-server:lock")
+    @decorators.idempotent_id('b81e10fb-1864-498f-8c1d-5175c6fec5fb')
+    def test_lock_server(self):
+        """Test lock server, part of os-lock-server."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.lock_server(self.server_id)
+        self.addCleanup(self.servers_client.unlock_server, self.server_id)
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-lock-server:unlock")
+    @decorators.idempotent_id('d50ef8e8-4bce-11e7-b114-b2f933d5fe66')
+    def test_unlock_server(self):
+        """Test unlock server, part of os-lock-server."""
+        self.servers_client.lock_server(self.server_id)
+        self.addCleanup(self.servers_client.unlock_server, self.server_id)
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.unlock_server(self.server_id)
+
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-lock-server:unlock:unlock_override")
+    @decorators.idempotent_id('40dfeef9-73ee-48a9-be19-a219875de457')
+    def test_unlock_server_override(self):
+        """Test force unlock server, part of os-lock-server.
+
+        In order to trigger the unlock:unlock_override policy instead
+        of the unlock policy, the server must be locked by a different
+        user than the one who is attempting to unlock it.
+        """
+        self.os_admin.servers_client.lock_server(self.server_id)
+        self.addCleanup(self.servers_client.unlock_server, self.server_id)
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.unlock_server(self.server_id)
+
     @test.requires_ext(extension='os-rescue', service='compute')
     @rbac_rule_validation.action(
         service="nova",
@@ -152,3 +215,34 @@
         """
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.servers_client.show_server(self.server_id)
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
+                          "Suspend compute feature is not available.")
+    @decorators.idempotent_id('b775930f-237c-431c-83ae-d33ed1b9700b')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-suspend-server:suspend")
+    def test_suspend_server(self):
+        """Test suspend server, part of os-suspend-server."""
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.suspend_server(self.server_id)
+        self.addCleanup(self.servers_client.resume_server, self.server_id)
+        waiters.wait_for_server_status(
+            self.os_admin.servers_client, self.server_id, 'SUSPENDED')
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
+                          "Suspend compute feature is not available.")
+    @decorators.idempotent_id('4d90bd02-11f8-45b1-a8a1-534665584675')
+    @rbac_rule_validation.action(
+        service="nova",
+        rule="os_compute_api:os-suspend-server:resume")
+    def test_resume_server(self):
+        """Test resume server, part of os-suspend-server."""
+        self.servers_client.suspend_server(self.server_id)
+        waiters.wait_for_server_status(self.servers_client, self.server_id,
+                                       'SUSPENDED')
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.servers_client.resume_server(self.server_id)
+        waiters.wait_for_server_status(
+            self.os_admin.servers_client, self.server_id, 'ACTIVE')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_suspend_server_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_suspend_server_rbac.py
deleted file mode 100644
index 06ae4cf..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_suspend_server_rbac.py
+++ /dev/null
@@ -1,76 +0,0 @@
-#    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.common import waiters
-from tempest import config
-from tempest.lib import decorators
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-CONF = config.CONF
-
-
-class SuspendServerRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(SuspendServerRbacTest, cls).skip_checks()
-        if not CONF.compute_feature_enabled.suspend:
-            msg = "%s skipped as suspend compute feature is not available." \
-                  % cls.__name__
-            raise cls.skipException(msg)
-
-    @classmethod
-    def resource_setup(cls):
-        super(SuspendServerRbacTest, cls).resource_setup()
-        cls.server = cls.create_test_server(wait_until='ACTIVE')
-
-    def tearDown(self):
-        # Guarantee that the server is active during each test run.
-        vm_state = self.servers_client.show_server(
-            self.server['id'])['server']['OS-EXT-STS:vm_state'].upper()
-        if vm_state != 'ACTIVE':
-            self.servers_client.resume_server(self.server['id'])
-            waiters.wait_for_server_status(self.servers_client,
-                                           self.server['id'],
-                                           'ACTIVE')
-
-        super(SuspendServerRbacTest, self).tearDown()
-
-    @decorators.idempotent_id('b775930f-237c-431c-83ae-d33ed1b9700b')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-suspend-server:suspend")
-    def test_suspend_server(self):
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.suspend_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client, self.server['id'],
-                                       'SUSPENDED')
-
-    @decorators.idempotent_id('4d90bd02-11f8-45b1-a8a1-534665584675')
-    @rbac_rule_validation.action(
-        service="nova",
-        rule="os_compute_api:os-suspend-server:resume")
-    def test_resume_server(self):
-        self.servers_client.suspend_server(self.server['id'])
-        waiters.wait_for_server_status(self.servers_client, self.server['id'],
-                                       'SUSPENDED')
-
-        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
-        self.servers_client.resume_server(self.server['id'])
-        waiters.wait_for_server_status(self.os_admin.servers_client,
-                                       self.server['id'],
-                                       'ACTIVE')
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index 2998dbe..1ed081e 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -242,6 +242,7 @@
         cls.services_client = cls.os_primary.identity_services_v3_client
         cls.trusts_client = cls.os_primary.trusts_client
         cls.users_client = cls.os_primary.users_v3_client
+        cls.oauth_token_client = cls.os_primary.oauth_token_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
index 5e21338..d3e17f1 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_consumers_rbac.py
@@ -25,7 +25,7 @@
 
     def _create_consumer(self):
         description = data_utils.rand_name(
-            self.__class__.__name__ + '-test_consumer')
+            self.__class__.__name__ + '-IdentityConsumer')
         consumer = self.consumers_client.create_consumer(
             description)['consumer']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
@@ -54,12 +54,12 @@
     @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d972')
     def test_update_consumer(self):
         consumer = self._create_consumer()
-        new_description = data_utils.rand_name(
-            self.__class__.__name__ + '-test_consumer')
+        updated_description = data_utils.rand_name(
+            self.__class__.__name__ + '-IdentityConsumer')
 
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.consumers_client.update_consumer(consumer['id'],
-                                              new_description)
+                                              updated_description)
 
     @rbac_rule_validation.action(service="keystone",
                                  rule="identity:get_consumer")
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py
new file mode 100644
index 0000000..78ee78a
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_oauth_tokens_rbac.py
@@ -0,0 +1,138 @@
+# 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.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+
+from tempest import config
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.identity import rbac_base
+
+CONF = config.CONF
+
+
+class IdentityOAuthTokensV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(IdentityOAuthTokensV3RbacTest, cls).resource_setup()
+        # Authorize token on admin role since primary user has admin
+        # credentials before switching roles. Populate role_ids with admin
+        # role id.
+        cls.role_ids = [cls.get_role_by_name(CONF.identity.admin_role)['id']]
+        cls.project_id = cls.auth_provider.credentials.project_id
+        cls.user_id = cls.auth_provider.credentials.user_id
+
+    def _create_consumer(self):
+        description = data_utils.rand_name(
+            self.__class__.__name__ + '-Consumer')
+        consumer = self.consumers_client.create_consumer(
+            description)['consumer']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.consumers_client.delete_consumer,
+                        consumer['id'])
+        return consumer
+
+    def _create_consumer_and_request_token(self):
+        # Create consumer
+        consumer = self._create_consumer()
+
+        # Create request token
+        request_token = self.oauth_token_client.create_request_token(
+            consumer['id'], consumer['secret'], self.project_id)
+
+        return consumer, request_token
+
+    def _create_access_token(self):
+        consumer, request_token = self._create_consumer_and_request_token()
+
+        # Authorize request token
+        resp = self.oauth_token_client.authorize_request_token(
+            request_token['oauth_token'], self.role_ids)['token']
+        auth_verifier = resp['oauth_verifier']
+
+        # Create access token
+        body = self.oauth_token_client.create_access_token(
+            consumer['id'],
+            consumer['secret'],
+            request_token['oauth_token'],
+            request_token['oauth_token_secret'],
+            auth_verifier)
+        access_key = body['oauth_token']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.oauth_token_client.revoke_access_token,
+                        self.user_id, access_key)
+
+        return access_key
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:authorize_request_token")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d976')
+    def test_authorize_request_token(self):
+        _, request_token = self._create_consumer_and_request_token()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.oauth_token_client.authorize_request_token(
+            request_token['oauth_token'],
+            self.role_ids)
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:get_access_token")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d977')
+    def test_get_access_token(self):
+        access_token = self._create_access_token()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.oauth_token_client.get_access_token(self.user_id,
+                                                 access_token)
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:get_access_token_role")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d980')
+    def test_get_access_token_role(self):
+        access_token = self._create_access_token()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.oauth_token_client.get_access_token_role(
+            self.user_id, access_token, self.role_ids[0])
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:list_access_tokens")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d979')
+    def test_list_access_tokens(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.oauth_token_client.list_access_tokens(self.user_id)
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:list_access_token_roles")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d978')
+    def test_list_access_token_roles(self):
+        access_token = self._create_access_token()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.oauth_token_client.list_access_token_roles(
+            self.user_id, access_token)
+
+    @rbac_rule_validation.action(service="keystone",
+                                 rule="identity:delete_access_token")
+    @decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d981')
+    def test_revoke_access_token(self):
+        access_token = self._create_access_token()
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.oauth_token_client.revoke_access_token(
+            self.user_id, access_token)
diff --git a/patrole_tempest_plugin/tests/api/volume/rbac_base.py b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
index 1d390b7..953a834 100644
--- a/patrole_tempest_plugin/tests/api/volume/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
@@ -47,6 +47,8 @@
         }
         cls.volume_hosts_client, cls.volume_types_client = \
             version_checker[cls._api_version]
+        cls.groups_client = cls.os_primary.groups_v3_client
+        cls.group_types_client = cls.os_primary.group_types_v3_client
 
     @classmethod
     def resource_setup(cls):
@@ -56,6 +58,8 @@
     @classmethod
     def resource_cleanup(cls):
         super(BaseVolumeRbacTest, cls).resource_cleanup()
+        # Allow volumes to be cleared first, so only clear volume types
+        # after super's resource_cleanup.
         cls.clear_volume_types()
 
     @classmethod
@@ -64,15 +68,33 @@
         name = name or data_utils.rand_name(cls.__name__ + '-volume-type')
         volume_type = cls.volume_types_client.create_volume_type(
             name=name, **kwargs)['volume_type']
-        cls.volume_types.append(volume_type['id'])
+        cls.volume_types.append(volume_type)
         return volume_type
 
+    def create_group_type(self, name=None, ignore_notfound=False, **kwargs):
+        """Create a test group-type"""
+        name = name or data_utils.rand_name(
+            self.__class__.__name__ + '-group-type')
+        group_type = self.group_types_client.create_group_type(
+            name=name, **kwargs)['group_type']
+
+        if ignore_notfound:
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.group_types_client.delete_group_type,
+                            group_type['id'])
+        else:
+            self.addCleanup(self.group_types_client.delete_group_type,
+                            group_type['id'])
+
+        return group_type
+
     @classmethod
     def clear_volume_types(cls):
         for vol_type in cls.volume_types:
             test_utils.call_and_ignore_notfound_exc(
-                cls.volume_types_client.delete_volume_type, vol_type)
+                cls.volume_types_client.delete_volume_type, vol_type['id'])
 
         for vol_type in cls.volume_types:
             test_utils.call_and_ignore_notfound_exc(
-                cls.volume_types_client.wait_for_resource_deletion, vol_type)
+                cls.volume_types_client.wait_for_resource_deletion,
+                vol_type['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
new file mode 100644
index 0000000..6b07aaa
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
@@ -0,0 +1,143 @@
+# 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.common import waiters
+from tempest.lib.common.utils import data_utils
+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
+
+
+class GroupsV3RbacTest(rbac_base.BaseVolumeRbacTest):
+    _api_version = 3
+    min_microversion = '3.14'
+    max_microversion = 'latest'
+
+    def setUp(self):
+        super(GroupsV3RbacTest, self).setUp()
+        self.volume_type_id = self.create_volume_type()['id']
+        self.group_type_id = self.create_group_type()['id']
+
+    def _create_group(self, name=None, ignore_notfound=False, **kwargs):
+        group_name = name or data_utils.rand_name(
+            self.__class__.__name__ + '-Group')
+        group = self.groups_client.create_group(name=group_name, **kwargs)[
+            'group']
+        waiters.wait_for_volume_resource_status(
+            self.groups_client, group['id'], 'available')
+
+        if ignore_notfound:
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self._delete_group, group['id'])
+        else:
+            self.addCleanup(self._delete_group, group['id'])
+
+        return group
+
+    def _delete_group(self, group_id, delete_volumes=True):
+        self.groups_client.delete_group(group_id, delete_volumes)
+        self.groups_client.wait_for_resource_deletion(group_id)
+
+    @decorators.idempotent_id('43235328-66ae-424f-bc7f-f709c0ca268c')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:create")
+    def test_create_group(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self._create_group(ignore_notfound=True,
+                           group_type=self.group_type_id,
+                           volume_types=[self.volume_type_id])
+
+    @decorators.idempotent_id('9dc34a62-ae3e-439e-92b6-9389ea4c2863')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:get")
+    def test_show_group(self):
+        group = self._create_group(group_type=self.group_type_id,
+                                   volume_types=[self.volume_type_id])
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.groups_client.show_group(group['id'])
+
+    @decorators.idempotent_id('db43841b-a173-4317-acfc-f83e4e48e4ee')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:get_all")
+    def test_list_groups(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.groups_client.list_groups()['groups']
+
+    @decorators.idempotent_id('5378da93-9c26-4ad4-b039-0555e2b8f668')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:get_all")
+    def test_list_groups_with_details(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.groups_client.list_groups(detail=True)['groups']
+
+    @decorators.idempotent_id('66fda391-5774-42a9-a018-80b34e57ab76')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:delete")
+    def test_delete_group(self):
+        group = self._create_group(ignore_notfound=True,
+                                   group_type=self.group_type_id,
+                                   volume_types=[self.volume_type_id])
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.groups_client.delete_group(group['id'])
+
+
+class GroupTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
+    _api_version = 3
+    min_microversion = '3.11'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('2820f12c-4681-4c7f-b28d-e6925637dff6')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:group_types_manage")
+    def test_create_group_type(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.create_group_type(ignore_notfound=True)
+
+    @decorators.idempotent_id('a5f88c26-df7c-4f21-a3ae-7a4c2d6212b4')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:access_group_types_specs")
+    def test_create_group_type_group_specs(self):
+        # TODO(felipemonteiro): Combine with ``test_create_group_type``
+        # once multiple policy testing is supported. This policy is
+        # only enforced after "group:group_types_manage".
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        group_type = self.create_group_type(ignore_notfound=True)
+
+        if 'group_specs' not in group_type:
+            raise rbac_exceptions.RbacActionFailed(
+                'Policy %s does not return %s in response body.' %
+                ('group:access_group_types_specs', 'group_specs'))
+
+    @decorators.idempotent_id('f77f8156-4fc9-4f02-be15-8930f748e10c')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="group:group_types_manage")
+    def test_delete_group_type(self):
+        goup_type = self.create_group_type(ignore_notfound=True)
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.group_types_client.delete_group_type(goup_type['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
index 8bb92f4..b666a2d 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
@@ -155,10 +155,13 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="volume:retype")
     def test_volume_retype(self):
-        volume = self.create_volume()
         vol_type = self.create_volume_type()['name']
+        volume = self.create_volume()
+
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.volumes_client.retype_volume(volume['id'], new_type=vol_type)
+        waiters.wait_for_volume_retype(
+            self.os_admin.volumes_client, volume['id'], vol_type)
 
     @rbac_rule_validation.action(
         service="cinder",
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
index dfe6495..e6944cc 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
@@ -57,7 +57,7 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:create")
     @decorators.idempotent_id('6887ec94-0bcf-4ab7-b30f-3808a4b5a2a5')
-    def test_volume_backup_create(self):
+    def test_create_backup(self):
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.create_backup(volume_id=self.volume['id'])
 
@@ -65,7 +65,7 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:get")
     @decorators.idempotent_id('abd92bdd-b0fb-4dc4-9cfc-de9e968f8c8a')
-    def test_volume_backup_get(self):
+    def test_show_backup(self):
         backup = self.create_backup(volume_id=self.volume['id'])
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.backups_client.show_backup(backup['id'])
@@ -73,15 +73,36 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:get_all")
     @decorators.idempotent_id('4d18f0f0-7e01-4007-b622-dedc859b22f6')
-    def test_volume_backup_list(self):
+    def test_list_backups(self):
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.backups_client.list_backups()
 
+    @decorators.idempotent_id('dbd69865-876f-4835-b70e-7341153fb162')
+    @rbac_rule_validation.action(service="cinder",
+                                 rule="backup:get_all")
+    def test_list_backups_with_details(self):
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.backups_client.list_backups(detail=True)
+
+    @decorators.idempotent_id('50f43bde-205e-438e-9a05-5eac07fc3d63')
+    @rbac_rule_validation.action(
+        service="cinder",
+        rule="volume_extension:backup_admin_actions:reset_status")
+    def test_reset_backup_status(self):
+        volume = self.create_volume()
+        backup = self.create_backup(volume_id=volume['id'])
+
+        self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+        self.backups_client.reset_backup_status(backup_id=backup['id'],
+                                                status='error')
+        waiters.wait_for_volume_resource_status(self.os_admin.backups_client,
+                                                backup['id'], 'error')
+
     @test.attr(type=["slow"])
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:restore")
     @decorators.idempotent_id('9c794bf9-2446-4f41-8fe0-80b71e757f9d')
-    def test_volume_backup_restore(self):
+    def test_restore_backup(self):
         backup = self.create_backup(volume_id=self.volume['id'])
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         restore = self.backups_client.restore_backup(backup['id'])['restore']
@@ -92,7 +113,7 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:delete")
     @decorators.idempotent_id('d5d0c6a2-413d-437e-a73f-4bf2b41a20ed')
-    def test_volume_backup_delete(self):
+    def test_delete_backup(self):
         # Do not call the create_backup in Tempest's base volume class, because
         # it doesn't use ``test_utils.call_and_ignore_notfound_exc`` for clean
         # up.
@@ -112,7 +133,7 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:backup-export")
     @decorators.idempotent_id('e984ec8d-e8eb-485c-98bc-f1856020303c')
-    def test_volume_backup_export(self):
+    def test_export_backup(self):
         backup = self.create_backup(volume_id=self.volume['id'])
         self.rbac_utils.switch_role(self, toggle_rbac_role=True)
         self.backups_client.export_backup(backup['id'])['backup-record']
@@ -121,7 +142,7 @@
     @rbac_rule_validation.action(service="cinder",
                                  rule="backup:backup-import")
     @decorators.idempotent_id('1e70f039-4556-44cc-9cc1-edf2b7ed648b')
-    def test_volume_backup_import(self):
+    def test_import_backup(self):
         backup = self.create_backup(volume_id=self.volume['id'])
         export_backup = self.backups_client.export_backup(
             backup['id'])['backup-record']
diff --git a/releasenotes/notes/more-volume-backup-tests-c3f10aa245df2a4b.yaml b/releasenotes/notes/more-volume-backup-tests-c3f10aa245df2a4b.yaml
new file mode 100644
index 0000000..8d71130
--- /dev/null
+++ b/releasenotes/notes/more-volume-backup-tests-c3f10aa245df2a4b.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add additional RBAC tests to ``VolumesBackupsRbacTest``, providing coverage
+    for "volume_extension:backup_admin_actions:reset_status".
diff --git a/releasenotes/notes/rbac-tests-for-quota-class-sets-20d874b185902308.yaml b/releasenotes/notes/rbac-tests-for-quota-class-sets-20d874b185902308.yaml
new file mode 100644
index 0000000..96e280a
--- /dev/null
+++ b/releasenotes/notes/rbac-tests-for-quota-class-sets-20d874b185902308.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add RBAC tests for compute quota class sets API, providing coverage for
+    the following policy actions:
+
+      * os_compute_api:os-quota-class-sets:show
+      * os_compute_api:os-quota-class-sets:update
diff --git a/releasenotes/notes/test_oauth_tokens_rbac-13e1d3b5decbaf79.yaml b/releasenotes/notes/test_oauth_tokens_rbac-13e1d3b5decbaf79.yaml
new file mode 100644
index 0000000..d7c5d5b
--- /dev/null
+++ b/releasenotes/notes/test_oauth_tokens_rbac-13e1d3b5decbaf79.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - Add test_oauth_tokens_rbac.py with RBAC test cases related to
+    the OS-OAUTH1 Keystone v3 extension API.
diff --git a/releasenotes/notes/volume-v3-groups-rbac-tests-60bddf6fa509545d.yaml b/releasenotes/notes/volume-v3-groups-rbac-tests-60bddf6fa509545d.yaml
new file mode 100644
index 0000000..92b1123
--- /dev/null
+++ b/releasenotes/notes/volume-v3-groups-rbac-tests-60bddf6fa509545d.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - |
+    Add RBAC tests for the volume v3 groups and group types APIs, providing
+    coverage for the following policy actions:
+
+    * group:create
+    * group:get
+    * group:get_all
+    * group:delete
+    * group:group_types_manage
+    * group:access_group_types_specs
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index c8439f6..7444c35 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -38,7 +38,7 @@
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'oslosphinx',
+    'openstackdocstheme',
     'reno.sphinxext',
 ]
 
@@ -111,7 +111,7 @@
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = 'openstackdocs'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
@@ -149,7 +149,7 @@
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-# html_last_updated_fmt = '%b %d, %Y'
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
@@ -191,6 +191,11 @@
 # Output file base name for HTML help builder.
 htmlhelp_basename = 'PatroleReleaseNotesdoc'
 
+# openstackdocstheme options
+repository_name = 'openstack/patrole'
+bug_project = 'patrole'
+bug_tag = ''
+
 
 # -- Options for LaTeX output ---------------------------------------------
 
diff --git a/requirements.txt b/requirements.txt
index 6871057..126a3dc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,10 +2,10 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
-pbr>=1.8 # Apache-2.0
-urllib3>=1.15.1 # MIT
-oslo.log>=3.11.0 # Apache-2.0
-oslo.config>=3.22.0  # Apache-2.0
-oslo.policy>=1.17.0  # Apache-2.0
-tempest>=14.0.0  # Apache-2.0
-stevedore>=1.20.0  # Apache-2.0
+pbr!=2.1.0,>=2.0.0 # Apache-2.0
+urllib3>=1.21.1 # MIT
+oslo.log>=3.22.0 # Apache-2.0
+oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
+oslo.policy>=1.23.0 # Apache-2.0
+tempest>=14.0.0 # Apache-2.0
+stevedore>=1.20.0 # Apache-2.0
diff --git a/setup.py b/setup.py
index f730546..566d844 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,4 @@
-# Copyright 2017 ATT Corporation.
-# All Rights Reserved.
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -26,5 +25,5 @@
     pass
 
 setuptools.setup(
-    setup_requires=['pbr>=1.8'],
+    setup_requires=['pbr>=2.0.0'],
     pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
index 7c97fa7..3e03437 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,16 +1,16 @@
 # 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.
-hacking>=0.12.0,!=0.13.0,<0.14  # Apache-2.0
+hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 
-sphinx>=1.2.1,!=1.3b1,<1.4  # BSD
-oslosphinx>=4.7.0 # Apache-2.0
-reno>=1.8.0 # Apache-2.0
+sphinx>=1.6.2 # BSD
+openstackdocstheme>=1.11.0 # Apache-2.0
+reno!=2.3.1,>=1.8.0 # Apache-2.0
 mock>=2.0 # BSD
-coverage>=4.0 # Apache-2.0
+coverage!=4.4,>=4.0 # Apache-2.0
 nose # LGPL
 nosexcover # BSD
 oslotest>=1.10.0 # Apache-2.0
-oslo.policy>=1.17.0  # Apache-2.0
-oslo.log>=3.11.0 # Apache-2.0
-tempest>=12.1.0  # Apache-2.0
+oslo.policy>=1.23.0 # Apache-2.0
+oslo.log>=3.22.0 # Apache-2.0
+tempest>=14.0.0 # Apache-2.0