Merge "RBAC tests for os-extended-status policies"
diff --git a/.testr.conf b/.testr.conf
index 6d83b3c..87d049d 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,6 +2,6 @@
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
- ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
+ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./patrole_tempest_plugin/tests/unit} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
diff --git a/HACKING.rst b/HACKING.rst
index 5281b53..f89910b 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,15 +1,15 @@
Patrole Style Commandments
==========================
-- Step 1: Read the OpenStack Style Commandments: `<https://docs.openstack.org/developer/hacking/>`__
-- Step 2: Review Tempest's Style Commandments: `<https://docs.openstack.org/developer/tempest/HACKING.html>`__
+- Step 1: Read the OpenStack Style Commandments: `<https://docs.openstack.org/hacking/latest/>`__
+- Step 2: Review Tempest's Style Commandments: `<https://docs.openstack.org/tempest/latest/HACKING.html>`__
- Step 3: Read on
Patrole Specific Commandments
------------------------------
Patrole borrows the following commandments from Tempest; refer to
-`Tempest's Commandments <https://docs.openstack.org/developer/tempest/HACKING.html>`__
+`Tempest's Commandments <https://docs.openstack.org/tempest/latest/HACKING.html>`__
for more information:
.. note::
diff --git a/doc/source/sampleconf.rst b/doc/source/sampleconf.rst
index c262f2d..43933db 100644
--- a/doc/source/sampleconf.rst
+++ b/doc/source/sampleconf.rst
@@ -36,7 +36,7 @@
# are.
test_custom_requirements = False
- File path of the yaml file that defines your RBAC requirements. This
+ # File path of the yaml file that defines your RBAC requirements. This
# file must be located on the same host that Patrole runs on. The yaml
# file should be written as follows:
custom_requirements_file = patrole/requirements.txt
diff --git a/patrole_tempest_plugin/rbac_exceptions.py b/patrole_tempest_plugin/rbac_exceptions.py
index 5ccb216..5ee65ae 100644
--- a/patrole_tempest_plugin/rbac_exceptions.py
+++ b/patrole_tempest_plugin/rbac_exceptions.py
@@ -16,8 +16,25 @@
from tempest.lib import exceptions
-class RbacActionFailed(exceptions.ClientRestClientException):
- message = "Rbac action failed"
+class RbacConflictingPolicies(exceptions.TempestException):
+ message = ("Conflicting policies preventing this action from being "
+ "performed.")
+
+
+class RbacMalformedResponse(exceptions.TempestException):
+ message = ("The response body is missing the expected %(attribute)s due "
+ "to policy enforcement failure.")
+
+ def __init__(self, empty=False, extra_attr=False, **kwargs):
+ if empty:
+ self.message = ("The response body is empty due to policy "
+ "enforcement failure.")
+ kwargs = {}
+ if extra_attr:
+ self.message = ("The response body contained an unexpected "
+ "attribute due to policy enforcement failure.")
+ kwargs = {}
+ super(RbacMalformedResponse, self).__init__(**kwargs)
class RbacResourceSetupFailed(exceptions.TempestException):
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 254bb18..88e9faa 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -20,7 +20,7 @@
from oslo_log import log as logging
from oslo_policy import policy
import stevedore
-
+from tempest import clients
from tempest.common import credentials_factory as credentials
from tempest import config
@@ -93,7 +93,8 @@
# Cache the list of available services in memory to avoid needlessly
# doing an API call every time.
if not hasattr(cls, 'available_services'):
- admin_mgr = credentials.AdminManager()
+ admin_mgr = clients.Manager(
+ credentials.get_configured_admin_credentials())
services_client = (admin_mgr.identity_services_v3_client
if CONF.identity_feature_enabled.api_v3
else admin_mgr.identity_services_client)
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index 51b9d92..c7bd38b 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -25,6 +25,7 @@
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_policy_parser
+from patrole_tempest_plugin import rbac_utils
from patrole_tempest_plugin import requirements_authority
CONF = config.CONF
@@ -85,7 +86,7 @@
LOG.info("As admin_only is True, only admin role should be "
"allowed to perform the API. Skipping oslo.policy "
"check for policy action {0}.".format(rule))
- allowed = test_obj.rbac_utils.is_admin
+ allowed = rbac_utils.is_admin()
else:
allowed = _is_authorized(test_obj, service, rule,
extra_target_data)
@@ -100,7 +101,9 @@
LOG.error(msg)
raise exceptions.NotFound(
"%s RbacInvalidService was: %s" % (msg, e))
- except (expected_exception, rbac_exceptions.RbacActionFailed) as e:
+ except (expected_exception,
+ rbac_exceptions.RbacConflictingPolicies,
+ rbac_exceptions.RbacMalformedResponse) as e:
if irregular_msg:
LOG.warning(irregular_msg.format(rule, service))
if allowed:
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 9d7a807..29d41d3 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -195,13 +195,13 @@
self.admin_role_id = admin_role_id
self.rbac_role_id = rbac_role_id
- @property
- def is_admin(self):
- """Verifies whether the current test role equals the admin role.
- :returns: True if ``rbac_test_role`` is the admin role.
- """
- return CONF.rbac.rbac_test_role == CONF.identity.admin_role
+def is_admin():
+ """Verifies whether the current test role equals the admin role.
+
+ :returns: True if ``rbac_test_role`` is the admin role.
+ """
+ return CONF.rbac.rbac_test_role == CONF.identity.admin_role
@six.add_metaclass(abc.ABCMeta)
diff --git a/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
index f355358..4712ed0 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_agents_rbac.py
@@ -13,6 +13,8 @@
# 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.lib import decorators
from tempest import test
@@ -29,6 +31,16 @@
raise cls.skipException(
'%s skipped as os-agents not enabled' % cls.__name__)
+ def _param_helper(self, **kwargs):
+ rand_key = 'architecture'
+ if rand_key in kwargs:
+ # NOTE: The rand_name is for avoiding agent conflicts.
+ # If you try to create an agent with the same hypervisor,
+ # os and architecture as an existing agent, Nova will return
+ # an HTTPConflict or HTTPServerError.
+ kwargs[rand_key] = data_utils.rand_name(kwargs[rand_key])
+ return kwargs
+
@rbac_rule_validation.action(
service="nova", rule="os_compute_api:os-agents")
@decorators.idempotent_id('d1bc6d97-07f5-4f45-ac29-1c619a6a7e27')
@@ -48,3 +60,41 @@
body = self.agents_client.create_agent(**params)['agent']
self.addCleanup(self.agents_client.delete_agent,
body['agent_id'])
+
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-agents")
+ @decorators.idempotent_id('b22f2681-9ffb-439b-b240-dae503e41020')
+ def test_update_agent(self):
+ params = self._param_helper(
+ hypervisor='common', os='linux',
+ architecture='x86_64', version='7.0',
+ url='xxx://xxxx/xxx/xxx',
+ md5hash='add6bb58e139be103324d04d82d8f545')
+ body = self.agents_client.create_agent(**params)['agent']
+ self.addCleanup(self.agents_client.delete_agent,
+ body['agent_id'])
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ update_params = self._param_helper(
+ version='8.0',
+ url='xxx://xxxx/xxx/xxx2',
+ md5hash='add6bb58e139be103324d04d82d8f547')
+ self.agents_client.update_agent(body['agent_id'], **update_params)
+
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-agents")
+ @decorators.idempotent_id('c5042af8-0682-43b0-abc4-bf33349e23dd')
+ def test_delete_agent(self):
+ params = self._param_helper(
+ hypervisor='common', os='linux',
+ architecture='x86_64', version='7.0',
+ url='xxx://xxxx/xxx/xxx',
+ md5hash='add6bb58e139be103324d04d82d8f545')
+ body = self.agents_client.create_agent(**params)['agent']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.agents_client.delete_agent,
+ body['agent_id'])
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.agents_client.delete_agent(body['agent_id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_instance_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_instance_actions_rbac.py
deleted file mode 100644
index b81d2ab..0000000
--- a/patrole_tempest_plugin/tests/api/compute/test_instance_actions_rbac.py
+++ /dev/null
@@ -1,56 +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 tempest import test
-
-from patrole_tempest_plugin import rbac_exceptions
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.compute import rbac_base
-
-
-class InstanceActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
-
- @classmethod
- def skip_checks(cls):
- super(InstanceActionsRbacTest, cls).skip_checks()
- if not test.is_extension_enabled('os-instance-actions', 'compute'):
- raise cls.skipException(
- '%s skipped as os-instance-actions not enabled' % cls.__name__)
-
- @classmethod
- def resource_setup(cls):
- super(InstanceActionsRbacTest, cls).resource_setup()
- cls.server = cls.create_test_server(wait_until='ACTIVE')
- cls.request_id = cls.server.response['x-compute-request-id']
-
- @decorators.idempotent_id('9d1b131d-407e-4fa3-8eef-eb2c4526f1da')
- @rbac_rule_validation.action(
- service="nova",
- rule="os_compute_api:os-instance-actions")
- def test_list_instance_actions(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.list_instance_actions(self.server['id'])
-
- @decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
- @rbac_rule_validation.action(
- service="nova",
- rule="os_compute_api:os-instance-actions:events")
- def test_get_instance_action(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- instance_action = self.servers_client.show_instance_action(
- self.server['id'], self.request_id)['instanceAction']
- if 'events' not in instance_action:
- raise rbac_exceptions.RbacActionFailed
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index cb32eec..0fd5c63 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
@@ -340,6 +340,5 @@
server = self.servers_client.show_server(self.server_id)['server']
if 'host_status' not in server:
- LOG.info("host_status attribute not returned when role doesn't "
- "have permission to access it.")
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='host_status')
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 3041fb2..b1956c2 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
@@ -49,21 +49,30 @@
@classmethod
def resource_setup(cls):
super(MiscPolicyActionsRbacTest, cls).resource_setup()
- cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
+ cls.server = cls.create_test_server(wait_until='ACTIVE')
def setUp(self):
super(MiscPolicyActionsRbacTest, self).setUp()
try:
waiters.wait_for_server_status(self.servers_client,
- self.server_id, 'ACTIVE')
+ self.server['id'], 'ACTIVE')
except lib_exc.NotFound:
# If the server was found to be deleted by a previous test,
# a new one is built
- server = self.create_test_server(wait_until='ACTIVE')
- self.__class__.server_id = server['id']
+ self.__class__.server = self.create_test_server(
+ wait_until='ACTIVE')
except Exception:
# Rebuilding the server in case something happened during a test
- self.__class__.server_id = self.rebuild_server(self.server_id)
+ self.__class__.server = self._rebuild_server(self.server['id'])
+
+ def _rebuild_server(self, server_id):
+ # Destroy an existing server and creates a new one.
+ if server_id:
+ self.delete_server(server_id)
+
+ self.password = data_utils.rand_password()
+ return self.create_test_server(
+ wait_until='ACTIVE', adminPass=self.password)
@test.requires_ext(extension='os-admin-actions', service='compute')
@rbac_rule_validation.action(
@@ -73,8 +82,8 @@
def test_reset_server_state(self):
"""Test reset server state, part of os-admin-actions."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.reset_state(self.server_id, state='error')
- self.addCleanup(self.servers_client.reset_state, self.server_id,
+ self.servers_client.reset_state(self.server['id'], state='error')
+ self.addCleanup(self.servers_client.reset_state, self.server['id'],
state='active')
@test.requires_ext(extension='os-admin-actions', service='compute')
@@ -85,7 +94,7 @@
def test_inject_network_info(self):
"""Test inject network info, part of os-admin-actions."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.inject_network_info(self.server_id)
+ self.servers_client.inject_network_info(self.server['id'])
@decorators.attr(type=['slow'])
@test.requires_ext(extension='os-admin-actions', service='compute')
@@ -96,7 +105,7 @@
def test_reset_network(self):
"""Test reset network, part of os-admin-actions."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.reset_network(self.server_id)
+ self.servers_client.reset_network(self.server['id'])
@testtools.skipUnless(CONF.compute_feature_enabled.change_password,
'Change password not available.')
@@ -106,15 +115,16 @@
@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)
+ 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,
+ 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')
+ self.os_admin.servers_client, self.server['id'], 'ACTIVE')
@test.requires_ext(extension='os-config-drive', service='compute')
@decorators.idempotent_id('2c82e819-382d-4d6f-87f0-a45954cbbc64')
@@ -127,8 +137,8 @@
body = self.servers_client.list_servers(detail=True)['servers']
# If the first server contains "config_drive", then all the others do.
if 'config_drive' not in body[0]:
- raise rbac_exceptions.RbacActionFailed(
- '"config_drive" attribute not found in response body.')
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='config_drive')
@test.requires_ext(extension='os-config-drive', service='compute')
@decorators.idempotent_id('55c62ef7-b72b-4970-acc6-05b0a4316e5d')
@@ -138,10 +148,10 @@
def test_show_server_config_drive(self):
"""Test show server with config_drive property in response body."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- body = self.servers_client.show_server(self.server_id)['server']
+ body = self.servers_client.show_server(self.server['id'])['server']
if 'config_drive' not in body:
- raise rbac_exceptions.RbacActionFailed(
- '"config_drive" attribute not found in response body.')
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute="config_drive")
@test.requires_ext(extension='os-deferred-delete', service='compute')
@decorators.idempotent_id('189bfed4-1e6d-475c-bb8c-d57e60895391')
@@ -152,7 +162,43 @@
"""Test force delete server, part of os-deferred-delete."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
# Force-deleting a server enforces os-deferred-delete.
- self.servers_client.force_delete_server(self.server_id)
+ self.servers_client.force_delete_server(self.server['id'])
+
+ @test.requires_ext(extension='os-instance-actions', service='compute')
+ @decorators.idempotent_id('9d1b131d-407e-4fa3-8eef-eb2c4526f1da')
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-instance-actions")
+ def test_list_instance_actions(self):
+ """Test list instance actions, part of os-instance-actions."""
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.servers_client.list_instance_actions(self.server['id'])
+
+ @test.requires_ext(extension='os-instance-actions', service='compute')
+ @decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-instance-actions:events")
+ def test_show_instance_action(self):
+ """Test show instance action, part of os-instance-actions.
+
+ Expect "events" details to be included in the response body.
+ """
+ # NOTE: "os_compute_api:os-instance-actions" is also enforced.
+ request_id = self.server.response['x-compute-request-id']
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ instance_action = self.servers_client.show_instance_action(
+ self.server['id'], request_id)['instanceAction']
+
+ if 'events' not in instance_action:
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='events')
+ # Microversion 2.51+ returns 'events' always, but not 'traceback'. If
+ # 'traceback' is also present then policy enforcement passed.
+ if 'traceback' not in instance_action['events'][0]:
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='events.traceback')
@decorators.idempotent_id('82053c27-3134-4003-9b55-bc9fafdb0e3b')
@test.requires_ext(extension='OS-EXT-STS', service='compute')
@@ -195,8 +241,8 @@
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)
+ self.servers_client.lock_server(self.server['id'])
+ self.addCleanup(self.servers_client.unlock_server, self.server['id'])
@rbac_rule_validation.action(
service="nova",
@@ -204,11 +250,11 @@
@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.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)
+ self.servers_client.unlock_server(self.server['id'])
@rbac_rule_validation.action(
service="nova",
@@ -221,11 +267,11 @@
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.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)
+ self.servers_client.unlock_server(self.server['id'])
@test.requires_ext(extension='os-rescue', service='compute')
@rbac_rule_validation.action(
@@ -235,7 +281,7 @@
def test_rescue_server(self):
"""Test rescue server, part of os-rescue."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.rescue_server(self.server_id)
+ self.servers_client.rescue_server(self.server['id'])
@test.requires_ext(extension='os-server-diagnostics', service='compute')
@rbac_rule_validation.action(
@@ -245,7 +291,7 @@
def test_show_server_diagnostics(self):
"""Test show server diagnostics, part of os-server-diagnostics."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.show_server_diagnostics(self.server_id)
+ self.servers_client.show_server_diagnostics(self.server['id'])
@test.requires_ext(extension='os-server-password', service='compute')
@decorators.idempotent_id('aaf43f78-c178-4581-ac18-14afd3f1f6ba')
@@ -255,7 +301,7 @@
def test_delete_server_password(self):
"""Test delete server password, part of os-server-password."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.delete_password(self.server_id)
+ self.servers_client.delete_password(self.server['id'])
@test.requires_ext(extension='os-server-password', service='compute')
@rbac_rule_validation.action(
@@ -265,7 +311,7 @@
def test_get_server_password(self):
"""Test show server password, part of os-server-password."""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.show_password(self.server_id)
+ self.servers_client.show_password(self.server['id'])
@test.requires_ext(extension='OS-SRV-USG', service='compute')
@rbac_rule_validation.action(
@@ -279,7 +325,7 @@
test can be combined with the generic test for showing a server.
"""
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.servers_client.show_server(self.server_id)
+ self.servers_client.show_server(self.server['id'])
@test.requires_ext(extension='os-simple-tenant-usage', service='compute')
@rbac_rule_validation.action(
@@ -312,10 +358,10 @@
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)
+ 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')
+ self.os_admin.servers_client, self.server['id'], 'SUSPENDED')
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
"Suspend compute feature is not available.")
@@ -325,14 +371,14 @@
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,
+ 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)
+ self.servers_client.resume_server(self.server['id'])
waiters.wait_for_server_status(
- self.os_admin.servers_client, self.server_id, 'ACTIVE')
+ self.os_admin.servers_client, self.server['id'], 'ACTIVE')
class MiscPolicyActionsNetworkRbacTest(rbac_base.BaseV2ComputeRbacTest):
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
index 5539221..10ea801 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_rbac.py
@@ -184,7 +184,7 @@
# Some other policy may have blocked it.
LOG.info("ServerFault exception caught. Some other policy "
"blocked updating of server")
- raise rbac_exceptions.RbacActionFailed(e)
+ raise rbac_exceptions.RbacConflictingPolicies(e)
class SecurtiyGroupsRbacTest(base.BaseV2ComputeRbacTest):
diff --git a/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py b/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
index e733e26..db1f6e6 100644
--- a/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v2/test_projects_rbac.py
@@ -18,6 +18,7 @@
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin import rbac_utils
from patrole_tempest_plugin.tests.api.identity import rbac_base
CONF = config.CONF
@@ -112,7 +113,7 @@
admin-scoped tenants, raise ``RbacActionFailed`` exception otherwise.
"""
tenants_client = self.os_admin.tenants_client if \
- self.rbac_utils.is_admin else self.os_primary.tenants_client
+ rbac_utils.is_admin() else self.os_primary.tenants_client
admin_tenant_id = self.os_admin.credentials.project_id
non_admin_tenant_id = self.os_primary.credentials.project_id
@@ -121,10 +122,8 @@
tenant_ids = [t['id'] for t in tenants]
if admin_tenant_id not in tenant_ids:
- raise rbac_exceptions.RbacActionFailed(
- "The admin tenant id was not returned by the call to "
- "``list_tenants``.")
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute="admin tenant id")
if non_admin_tenant_id in tenant_ids:
- raise rbac_exceptions.RbacActionFailed(
- "The non-admin tenant id was returned by the call to "
- "``list_tenants``.")
+ raise rbac_exceptions.RbacMalformedResponse(
+ extra_attr=True)
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
index 90952a8..18e5bf1 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_tokens_negative_rbac.py
@@ -18,6 +18,7 @@
from tempest.lib import exceptions as lib_exc
from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin import rbac_utils
from patrole_tempest_plugin.tests.api.identity import rbac_base
CONF = config.CONF
@@ -31,8 +32,8 @@
def skip_checks(cls):
super(IdentityTokenV3RbacTest, cls).skip_checks()
# In case of admin, the positive testcase would be used, hence
- # skipping negative testcase
- if CONF.rbac.rbac_test_role == CONF.identity.admin_role:
+ # skipping negative testcase.
+ if rbac_utils.is_admin():
raise cls.skipException(
"Skipped as admin role doesn't require negative testing")
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
index 01cf0fd..9d2b67a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_multiprovider_rbac.py
@@ -95,6 +95,5 @@
if len(response_network) == 0:
LOG.info("NotFound or Forbidden exception are not thrown when "
"role doesn't have access to the endpoint. Instead, "
- "the response will have an empty network body. "
- "This is irregular and should be fixed.")
- raise rbac_exceptions.RbacActionFailed
+ "the response will have an empty network body.")
+ raise rbac_exceptions.RbacMalformedResponse(True)
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
index b42be93..148804e 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -238,7 +238,7 @@
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(True)
@test.requires_ext(extension='provider', service='network')
@rbac_rule_validation.action(service="neutron",
@@ -257,7 +257,7 @@
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(empty=True)
@test.requires_ext(extension='provider', service='network')
@rbac_rule_validation.action(service="neutron",
@@ -276,7 +276,7 @@
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(empty=True)
key = retrieved_network.get('provider:segmentation_id', "NotFound")
self.assertNotEqual(key, "NotFound")
diff --git a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
index cec860c..888f879 100644
--- a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
@@ -170,7 +170,8 @@
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='binding:vif_type')
@test.requires_ext(extension='binding', service='network')
@rbac_rule_validation.action(service="neutron",
@@ -188,7 +189,8 @@
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='binding:vif_details')
@test.requires_ext(extension='binding', service='network')
@rbac_rule_validation.action(service="neutron",
@@ -208,7 +210,8 @@
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='binding:host_id')
@test.requires_ext(extension='binding', service='network')
@rbac_rule_validation.action(service="neutron",
@@ -229,7 +232,8 @@
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
- raise rbac_exceptions.RbacActionFailed
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='binding:profile')
@rbac_rule_validation.action(service="neutron",
rule="update_port")
diff --git a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
index 6aec5d1..9ed9eb6 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -173,8 +173,8 @@
# Rather than throwing a 403, the field is not present, so raise exc.
if 'distributed' not in retrieved_fields:
- raise rbac_exceptions.RbacActionFailed(
- '"distributed" parameter not present in response body')
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='distributed')
@rbac_rule_validation.action(
service="neutron", rule="update_router")
diff --git a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
index 6b07aaa..cdd2261 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_groups_rbac.py
@@ -128,9 +128,8 @@
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'))
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute='group_specs')
@decorators.idempotent_id('f77f8156-4fc9-4f02-be15-8930f748e10c')
@rbac_rule_validation.action(
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
index 7ce925a..36fa045 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -40,11 +40,11 @@
def setUp(self):
super(RbacPolicyTest, self).setUp()
-
- m_creds = self.patchobject(rbac_policy_parser, 'credentials')
- m_creds.AdminManager().identity_services_client.list_services.\
+ self.patchobject(rbac_policy_parser, 'credentials')
+ m_creds = self.patchobject(rbac_policy_parser, 'clients')
+ m_creds.Manager().identity_services_client.list_services.\
return_value = self.services
- m_creds.AdminManager().identity_services_v3_client.list_services.\
+ m_creds.Manager().identity_services_v3_client.list_services.\
return_value = self.services
current_directory = os.path.dirname(os.path.realpath(__file__))
@@ -458,7 +458,7 @@
@mock.patch.object(rbac_policy_parser, 'policy', autospec=True)
@mock.patch.object(rbac_policy_parser.RbacPolicyParser, '_get_policy_data',
autospec=True)
- @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'clients', autospec=True)
@mock.patch.object(rbac_policy_parser, 'os', autospec=True)
def test_discover_policy_files_with_many_invalid_one_valid(self, m_os,
m_creds, *args):
@@ -466,7 +466,7 @@
m_os.path.isfile.side_effect = [False, False, True, False]
# Ensure the outer for loop runs only once in `discover_policy_files`.
- m_creds.AdminManager().identity_services_v3_client.\
+ m_creds.Manager().identity_services_v3_client.\
list_services.return_value = {
'services': [{'name': 'test_service'}]}
@@ -504,10 +504,10 @@
def _test_validate_service(self, v2_services, v3_services,
expected_failure=False, expected_services=None):
- with mock.patch.object(rbac_policy_parser, 'credentials') as m_creds:
- m_creds.AdminManager().identity_services_client.list_services.\
+ with mock.patch.object(rbac_policy_parser, 'clients') as m_creds:
+ m_creds.Manager().identity_services_client.list_services.\
return_value = v2_services
- m_creds.AdminManager().identity_services_v3_client.list_services.\
+ m_creds.Manager().identity_services_v3_client.list_services.\
return_value = v3_services
test_tenant_id = mock.sentinel.tenant_id
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index 7ce5548..a9acf1c 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -108,16 +108,17 @@
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
- def test_rule_validation_rbac_action_failed_positive(self, mock_policy,
- mock_log):
- """Test RbacActionFailed error is thrown without permission passes.
+ def test_rule_validation_rbac_malformed_response_positive(self,
+ mock_policy,
+ mock_log):
+ """Test RbacMalformedResponse error is thrown without permission passes.
- Positive test case: if RbacActionFailed is thrown and the user is not
- allowed to perform the action, then this is a success.
+ Positive test case: if RbacMalformedResponse is thrown and the user is
+ not allowed to perform the action, then this is a success.
"""
decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
mock_function = mock.Mock()
- mock_function.side_effect = rbac_exceptions.RbacActionFailed
+ mock_function.side_effect = rbac_exceptions.RbacMalformedResponse
wrapper = decorator(mock_function)
mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
@@ -130,16 +131,65 @@
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
- def test_rule_validation_rbac_action_failed_negative(self, mock_policy,
- mock_log):
- """Test RbacActionFailed error is thrown with permission fails.
+ def test_rule_validation_rbac_malformed_response_negative(self,
+ mock_policy,
+ mock_log):
+ """Test RbacMalformedResponse error is thrown with permission fails.
- Negative test case: if RbacActionFailed is thrown and the user is
+ Negative test case: if RbacMalformedResponse is thrown and the user is
allowed to perform the action, then this is an expected failure.
"""
decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
mock_function = mock.Mock()
- mock_function.side_effect = rbac_exceptions.RbacActionFailed
+ mock_function.side_effect = rbac_exceptions.RbacMalformedResponse
+ wrapper = decorator(mock_function)
+
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
+
+ e = self.assertRaises(exceptions.Forbidden, wrapper, self.mock_args)
+ self.assertIn(
+ "Role Member was not allowed to perform sentinel.action.",
+ e.__str__())
+
+ mock_log.error.assert_called_once_with("Role Member was not allowed to"
+ " perform sentinel.action.")
+
+ @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_rbac_conflicting_policies_positive(self,
+ mock_policy,
+ mock_log):
+ """Test RbacConflictingPolicies error is thrown without permission passes.
+
+ Positive test case: if RbacConflictingPolicies is thrown and the user
+ is not allowed to perform the action, then this is a success.
+ """
+ decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
+ mock_function = mock.Mock()
+ mock_function.side_effect = rbac_exceptions.RbacConflictingPolicies
+ wrapper = decorator(mock_function)
+
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
+
+ result = wrapper(self.mock_args)
+
+ self.assertIsNone(result)
+ mock_log.error.assert_not_called()
+ mock_log.warning.assert_not_called()
+
+ @mock.patch.object(rbac_rv, 'LOG', autospec=True)
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_rbac_conflicting_policies_negative(self,
+ mock_policy,
+ mock_log):
+ """Test RbacConflictingPolicies error is thrown with permission fails.
+
+ Negative test case: if RbacConflictingPolicies is thrown and the user
+ is allowed to perform the action, then this is an expected failure.
+ """
+ decorator = rbac_rv.action(mock.sentinel.service, mock.sentinel.action)
+ mock_function = mock.Mock()
+ mock_function.side_effect = rbac_exceptions.RbacConflictingPolicies
wrapper = decorator(mock_function)
mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
diff --git a/releasenotes/notes/agents-ca4a5e232ce242a5.yaml b/releasenotes/notes/agents-ca4a5e232ce242a5.yaml
new file mode 100644
index 0000000..3cd9646
--- /dev/null
+++ b/releasenotes/notes/agents-ca4a5e232ce242a5.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added tests to test_agents_rbac.py for PUT and
+ DELETE endpoints.
diff --git a/test-whitelist.txt b/test-whitelist.txt
deleted file mode 100644
index 162992a..0000000
--- a/test-whitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-patrole_tempest_plugin.tests.unit.test*
diff --git a/tox.ini b/tox.ini
index d2e83e9..41e893c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,6 +8,9 @@
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
+ OS_TEST_PATH=./patrole_tempest_plugin/tests/unit
+ LANGUAGE=en_US
+ LC_ALL=en_US.utf-8
PYTHONWARNINGS=default::DeprecationWarning
passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH OS_TEST_PATH http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
whitelist_externals = find
@@ -15,7 +18,7 @@
-r{toxinidir}/test-requirements.txt
commands =
find . -type f -name "*.pyc" -delete
- ostestr {posargs} --whitelist-file test-whitelist.txt
+ ostestr {posargs}
[testenv:pep8]
commands = flake8 {posargs}