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