Merge "Adds tests to cover QOS policy"
diff --git a/.zuul.yaml b/.zuul.yaml
index 5701eb4..fe4bd4f 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -2,8 +2,8 @@
name: patrole-base
parent: devstack-tempest
description: |
- Patrole base job for admin and member roles. This job executes RBAC tests
- for all the "core" services that Tempest covers, excluding Swift.
+ Patrole base job for admin and member roles. This job executes RBAC tests
+ for all the "core" services that Tempest covers, excluding Swift.
required-projects:
- name: openstack/tempest
- name: openstack/patrole
@@ -25,6 +25,7 @@
devstack_services:
tempest: true
neutron: true
+ neutron-trunk: true
tempest_concurrency: 2
tempest_test_regex: (?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)
tox_envlist: all-plugin
@@ -86,6 +87,11 @@
RBAC_TEST_ROLE: member
- job:
+ name: patrole-member-rocky
+ parent: patrole-member
+ override-checkout: stable/rocky
+
+- job:
name: patrole-member-queens
parent: patrole-member
override-checkout: stable/queens
@@ -133,16 +139,15 @@
name: patrole-plugin-base
parent: patrole-base
description: |
- Patrole plugin job for admin and member roles which
- runs RBAC tests for neutron-tempest-plugin APIs (if the plugin is installed).
+ Patrole plugin job for admin and member roles which
+ runs RBAC tests for neutron-tempest-plugin APIs (if the plugin is installed).
required-projects:
- name: openstack/tempest
- name: openstack/patrole
- name: openstack/neutron-tempest-plugin
vars:
devstack_localrc:
- TEMPEST_PLUGINS: "'/opt/stack/patrole
- /opt/stack/neutron-tempest-plugin'"
+ TEMPEST_PLUGINS: "'/opt/stack/patrole /opt/stack/neutron-tempest-plugin'"
devstack_plugins:
neutron: git://git.openstack.org/openstack/neutron.git
patrole: git://git.openstack.org/openstack/patrole.git
@@ -171,19 +176,33 @@
tempest_test_regex: (?=.*PluginRbacTest)(^patrole_tempest_plugin\.tests\.api)
- project:
+ templates:
+ - openstack-cover-jobs
+ - openstack-lower-constraints-jobs
+ - openstack-python36-jobs
+ - openstack-python-jobs
+ - openstack-python35-jobs
+ - check-requirements
+ - publish-openstack-docs-pti
+ - release-notes-jobs-python3
check:
jobs:
- patrole-admin
- patrole-member
+ - patrole-member-rocky
- patrole-member-queens
- patrole-member-pike
- patrole-py35-member
- patrole-multinode-admin
- patrole-multinode-member
- - openstack-tox-lower-constraints
- patrole-plugin-admin
- patrole-plugin-member
gate:
jobs:
- patrole-admin
- patrole-member
+ periodic-stable:
+ jobs:
+ - patrole-member-rocky
+ - patrole-member-queens
+ - patrole-member-pike
diff --git a/README.rst b/README.rst
index 2028536..fdcbc6b 100644
--- a/README.rst
+++ b/README.rst
@@ -36,7 +36,7 @@
Design Principles
-----------------
-As a `Tempest plugin`_, Patrole borrows some `design principles`_ from Tempest,
+As a `Tempest plugin`_, Patrole borrows some design principles from `Tempest design principles`_,
but not all, as its testing scope is confined to policies.
* *Stability*. Patrole uses OpenStack public interfaces. Tests in Patrole
@@ -76,7 +76,7 @@
* *Self-testing*. Patrole should be self-testing.
.. _Tempest plugin: https://docs.openstack.org/tempest/latest/plugin.html
-.. _design principles: https://docs.openstack.org/tempest/latest/overview.html#design-principles
+.. _Tempest design principles: https://docs.openstack.org/tempest/latest/overview.html#design-principles
.. _policy in code: https://specs.openstack.org/openstack/oslo-specs/specs/newton/policy-in-code.html
.. _Nova repository: https://github.com/openstack/nova/tree/master/nova/policies
.. _Keystone repository: https://github.com/openstack/keystone/tree/master/keystone/common/policies
@@ -120,7 +120,7 @@
Quickstart
----------
To run Patrole, you must first have `Tempest`_ installed and configured
-properly. Please reference Tempest's `Quickstart`_ guide to do so. Follow all
+properly. Please reference `Tempest_quickstart`_ guide to do so. Follow all
the steps outlined therein. Afterward, proceed with the steps below.
#. You first need to install Patrole. This is done with pip after you check out
@@ -139,7 +139,7 @@
#. Next you must properly configure Patrole, which is relatively
straightforward. For details on configuring Patrole refer to the
- :ref:`patrole-configuration`.
+ `Patrole Configuration <https://docs.openstack.org/patrole/latest/configuration.html#patrole-configuration>`_.
#. Once the configuration is done you're now ready to run Patrole. This can
be done using the `tempest_run`_ command. This can be done by running::
@@ -170,14 +170,14 @@
#. Log information from tests is captured in ``tempest.log`` under the Tempest
repository. Some Patrole debugging information is captured in that log
- related to expected test results and :ref:`role-overriding`.
+ related to expected test results and `Role Overriding <https://docs.openstack.org/patrole/latest/framework/rbac_utils.html#role-overriding>`_.
More detailed RBAC testing log output is emitted to ``patrole.log`` under
the Patrole repository. To configure Patrole's logging, see the
- :ref:`patrole-configuration` guide.
+ `Patrole Configuration Guide <https://docs.openstack.org/patrole/latest/configuration.html#patrole-configuration>`_.
.. _Tempest: https://github.com/openstack/tempest
-.. _Quickstart: https://docs.openstack.org/tempest/latest/overview.html#quickstart
+.. _Tempest_quickstart: https://docs.openstack.org/tempest/latest/overview.html#quickstart
.. _tempest_run: https://docs.openstack.org/tempest/latest/run.html
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
.. _ostestr: https://docs.openstack.org/os-testr/latest/
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index bd0068b..01be7d6 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -26,15 +26,20 @@
iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_attach_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_reserve_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_unreserve_policy False
+
+ # These policies were removed in Stein but are available in Pike.
+ iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
fi
if [[ ${DEVSTACK_SERIES} == 'queens' ]]; then
if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
RBAC_TEST_ROLE="Member"
fi
+
+ # These policies were removed in Stein but are available in Queens.
+ iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
fi
- iniset $TEMPEST_CONFIG patrole enable_rbac True
iniset $TEMPEST_CONFIG patrole rbac_test_role $RBAC_TEST_ROLE
}
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index ebc8a1d..df05eaa 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -24,16 +24,6 @@
default='admin',
help="""The current RBAC role against which to run
Patrole tests."""),
- cfg.BoolOpt('enable_rbac',
- default=True,
- deprecated_for_removal=True,
- deprecated_reason="""This is a legacy option that was
-meaningful when Patrole existed downstream as a suite of tests inside Tempest.
-Installing the Patrole plugin necessarily means that RBAC tests should be run.
-This option is paradoxical with the Tempest plugin architecture.
-""",
- deprecated_since='R',
- help="Enables Patrole RBAC tests."),
cfg.ListOpt('custom_policy_files',
default=['/etc/%s/policy.json'],
help="""List of the paths to search for policy files. Each
@@ -160,7 +150,17 @@
default=True,
help="""Is the Cinder policy
"volume_extension:volume_actions:unreserve" available in the cloud? This policy
-was changed in a backwards-incompatible way.""")
+was changed in a backwards-incompatible way."""),
+ # *** Include feature flags for groups of policies below. ***
+ # Best practice is to capture new policies, removed policies, renamed
+ # policies in a group, per release.
+ #
+ # TODO(felipemonteiro): Remove these feature flags once Stein is EOL.
+ cfg.BoolOpt('removed_nova_policies_stein',
+ default=True,
+ help="""Are the Nova API extension policies available in the
+cloud (e.g. os_compute_api:os-extended-availability-zone)? These policies were
+removed in Stein because Nova API extension concept was removed in Pike."""),
]
diff --git a/patrole_tempest_plugin/rbac_exceptions.py b/patrole_tempest_plugin/rbac_exceptions.py
index 809a7ed..3958e17 100644
--- a/patrole_tempest_plugin/rbac_exceptions.py
+++ b/patrole_tempest_plugin/rbac_exceptions.py
@@ -16,12 +16,16 @@
from tempest.lib import exceptions
-class RbacConflictingPolicies(exceptions.TempestException):
+class BasePatroleException(exceptions.TempestException):
+ message = "An unknown RBAC exception occurred"
+
+
+class RbacConflictingPolicies(BasePatroleException):
message = ("Conflicting policies preventing this action from being "
"performed.")
-class RbacMalformedResponse(exceptions.TempestException):
+class RbacMalformedResponse(BasePatroleException):
message = ("The response body is missing the expected %(attribute)s due "
"to policy enforcement failure.")
@@ -37,25 +41,25 @@
super(RbacMalformedResponse, self).__init__(**kwargs)
-class RbacResourceSetupFailed(exceptions.TempestException):
+class RbacResourceSetupFailed(BasePatroleException):
message = "RBAC resource setup failed"
-class RbacOverPermissionException(exceptions.TempestException):
+class RbacOverPermissionException(BasePatroleException):
"""Raised when the expected result is failure but the actual result is
pass.
"""
message = "Unauthorized action was allowed to be performed"
-class RbacUnderPermissionException(exceptions.TempestException):
+class RbacUnderPermissionException(BasePatroleException):
"""Raised when the expected result is pass but the actual result is
failure.
"""
message = "Authorized action was not allowed to be performed"
-class RbacExpectedWrongException(exceptions.TempestException):
+class RbacExpectedWrongException(BasePatroleException):
"""Raised when the expected exception does not match the actual exception
raised, when both are instances of Forbidden or NotFound, indicating
the test provides a wrong argument to `expected_error_codes`.
@@ -64,16 +68,30 @@
"instead. Actual exception: %(exception)s")
-class RbacInvalidServiceException(exceptions.TempestException):
+class RbacInvalidServiceException(BasePatroleException):
"""Raised when an invalid service is passed to ``rbac_rule_validation``
decorator.
"""
message = "Attempted to test an invalid service"
-class RbacParsingException(exceptions.TempestException):
+class RbacParsingException(BasePatroleException):
message = "Attempted to test an invalid policy file or action"
-class RbacInvalidErrorCode(exceptions.TempestException):
+class RbacInvalidErrorCode(BasePatroleException):
message = "Unsupported error code passed in test"
+
+
+class RbacOverrideRoleException(BasePatroleException):
+ """Raised when override_role is used incorrectly or fails somehow.
+
+ Used for safeguarding against false positives that might occur when the
+ expected exception isn't raised inside the ``override_role`` context.
+ Specifically, when:
+
+ * ``override_role`` isn't called
+ * an exception is raised before ``override_role`` context
+ * an exception is raised after ``override_role`` context
+ """
+ message = "Override role failure or incorrect usage"
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index e6d1e80..a7927fc 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -110,7 +110,7 @@
c) if both api_action1 and api_action2 fail, then the expected error
code is the first error seen (404).
- If an error code is missing from the list, it is defaulted to 403.
+ If it is not passed, then it is defaulted to 403.
:param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
generic check names, whose values are string literals that reference
@@ -180,6 +180,7 @@
expected_exception, irregular_msg = _get_exception_type(
exp_error_code)
+ caught_exception = None
test_status = 'Allowed'
try:
@@ -193,13 +194,16 @@
LOG.error(msg)
except (expected_exception,
rbac_exceptions.RbacConflictingPolicies,
- rbac_exceptions.RbacMalformedResponse) as e:
+ rbac_exceptions.RbacMalformedResponse) as actual_exception:
+ caught_exception = actual_exception
test_status = 'Denied'
+
if irregular_msg:
LOG.warning(irregular_msg,
test_func.__name__,
', '.join(rules),
service)
+
if allowed:
msg = ("Role %s was not allowed to perform the following "
"actions: %s. Expected allowed actions: %s. "
@@ -209,8 +213,10 @@
sorted(disallowed_rules)))
LOG.error(msg)
raise rbac_exceptions.RbacUnderPermissionException(
- "%s Exception was: %s" % (msg, e))
+ "%s Exception was: %s" % (msg, actual_exception))
except Exception as actual_exception:
+ caught_exception = actual_exception
+
if _check_for_expected_mismatch_exception(expected_exception,
actual_exception):
LOG.error('Expected and actual exceptions do not match. '
@@ -249,6 +255,14 @@
"Allowed" if allowed else "Denied",
test_status)
+ # Sanity-check that ``override_role`` was called to eliminate
+ # false-positives and bad test flows resulting from exceptions
+ # getting raised too early, too late or not at all, within
+ # the scope of an RBAC test.
+ _validate_override_role_called(
+ test_obj,
+ actual_exception=caught_exception)
+
return wrapper
return decorator
@@ -389,7 +403,7 @@
irregular_msg = ("NotFound exception was caught for test %s. Expected "
"policies which may have caused the error: %s. The "
"service %s throws a 404 instead of a 403, which is "
- "irregular.")
+ "irregular")
return expected_exception, irregular_msg
@@ -431,8 +445,63 @@
def _check_for_expected_mismatch_exception(expected_exception,
actual_exception):
+ """Checks that ``expected_exception`` matches ``actual_exception``.
+
+ Since Patrole must handle 403/404 it is important that the expected and
+ actual error codes match.
+
+ :param excepted_exception: Expected exception for test.
+ :param actual_exception: Actual exception raised by test.
+ :returns: True if match, else False.
+ :rtype: boolean
+ """
permission_exceptions = (lib_exc.Forbidden, lib_exc.NotFound)
if isinstance(actual_exception, permission_exceptions):
if not isinstance(actual_exception, expected_exception.__class__):
return True
return False
+
+
+def _validate_override_role_called(test_obj, actual_exception):
+ """Validates that :func:`rbac_utils.RbacUtils.override_role` is called
+ during each Patrole test.
+
+ Useful for validating that the expected exception isn't raised too early
+ (before ``override_role`` call) or too late (after ``override_call``) or
+ at all (which is a bad test).
+
+ :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
+ :param actual_exception: Actual exception raised by test.
+ :raises RbacOverrideRoleException: If ``override_role`` isn't called, is
+ called too early, or is called too late.
+ """
+ called = test_obj._validate_override_role_called()
+ base_msg = ('This error is unrelated to RBAC and is due to either '
+ 'an API or override role failure. Exception: %s' %
+ actual_exception)
+
+ if not called:
+ if actual_exception is not None:
+ msg = ('Caught exception (%s) but it was raised before the '
+ '`override_role` context. ' % actual_exception.__class__)
+ else:
+ msg = 'Test missing required `override_role` call. '
+ msg += base_msg
+ LOG.error(msg)
+ raise rbac_exceptions.RbacOverrideRoleException(msg)
+ else:
+ exc_caught_in_ctx = test_obj._validate_override_role_caught_exc()
+ # This block is only executed if ``override_role`` is called. If
+ # an exception is raised and the exception wasn't raised in the
+ # ``override_role`` context and if the exception isn't a valid
+ # exception type (instance of ``BasePatroleException``), then this is
+ # a legitimate error.
+ if (not exc_caught_in_ctx and
+ actual_exception is not None and
+ not isinstance(actual_exception,
+ rbac_exceptions.BasePatroleException)):
+ msg = ('Caught exception (%s) but it was raised after the '
+ '`override_role` context. ' % actual_exception.__class__)
+ msg += base_msg
+ LOG.error(msg)
+ raise rbac_exceptions.RbacOverrideRoleException(msg)
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 9a9f864..366e033 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -14,10 +14,10 @@
# under the License.
from contextlib import contextmanager
+import sys
import time
from oslo_log import log as logging
-from oslo_log import versionutils
from oslo_utils import excutils
from tempest import clients
@@ -95,11 +95,17 @@
# if the API call above threw an exception, any code below this
# point in the test is not executed.
"""
+ test_obj._set_override_role_called()
self._override_role(test_obj, True)
try:
# Execute the test.
yield
finally:
+ # Check whether an exception was raised. If so, remember that
+ # for future validation.
+ exc = sys.exc_info()[0]
+ if exc is not None:
+ test_obj._set_override_role_caught_exc()
# This code block is always executed, no matter the result of the
# test. Automatically switch back to the admin role for test clean
# up.
@@ -222,6 +228,11 @@
cls.setup_rbac_utils()
"""
+ # Shows if override_role was called.
+ __override_role_called = False
+ # Shows if exception raised during override_role.
+ __override_role_caught_exc = False
+
@classmethod
def get_auth_providers(cls):
"""Returns list of auth_providers used within test.
@@ -232,21 +243,36 @@
return [cls.os_primary.auth_provider]
@classmethod
- def skip_rbac_checks(cls):
- if not CONF.patrole.enable_rbac:
- deprecation_msg = ("The `[patrole].enable_rbac` option is "
- "deprecated and will be removed in the S "
- "release. Patrole tests will always be enabled "
- "following installation of the Patrole Tempest "
- "plugin. Use a regex to skip tests.")
- versionutils.report_deprecated_feature(LOG, deprecation_msg)
- raise cls.skipException(
- 'Patrole testing not enabled so skipping %s.' % cls.__name__)
-
- @classmethod
def setup_rbac_utils(cls):
cls.rbac_utils = RbacUtils(cls)
+ def _set_override_role_called(self):
+ """Helper for tracking whether ``override_role`` was called."""
+ self.__override_role_called = True
+
+ def _set_override_role_caught_exc(self):
+ """Helper for tracking whether exception was thrown inside
+ ``override_role``.
+ """
+ self.__override_role_caught_exc = True
+
+ def _validate_override_role_called(self):
+ """Idempotently validate that ``override_role`` is called and reset
+ its value to False for sequential tests.
+ """
+ was_called = self.__override_role_called
+ self.__override_role_called = False
+ return was_called
+
+ def _validate_override_role_caught_exc(self):
+ """Idempotently validate that exception was caught inside
+ ``override_role``, so that, by process of elimination, it can be
+ determined whether one was thrown outside (which is invalid).
+ """
+ caught_exception = self.__override_role_caught_exc
+ self.__override_role_caught_exc = False
+ return caught_exception
+
def is_admin():
"""Verifies whether the current test role equals the admin role.
diff --git a/patrole_tempest_plugin/tests/api/compute/rbac_base.py b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
index 18d2f48..ab4551e 100644
--- a/patrole_tempest_plugin/tests/api/compute/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
@@ -12,24 +12,16 @@
# under the License.
from tempest.api.compute import base as compute_base
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from patrole_tempest_plugin import rbac_utils
-CONF = config.CONF
-
class BaseV2ComputeRbacTest(rbac_utils.RbacUtilsMixin,
compute_base.BaseV2ComputeTest):
@classmethod
- def skip_checks(cls):
- super(BaseV2ComputeRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseV2ComputeRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
index d6364c9..a99ddbd 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_access_rbac.py
@@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest import config
+import testtools
+from tempest import config
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
@@ -34,6 +35,8 @@
cls.public_flavor_id = CONF.compute.flavor_ref
cls.tenant_id = cls.os_primary.credentials.tenant_id
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('a2bd3740-765d-4c95-ac98-9e027378c75e')
@rbac_rule_validation.action(
service="nova",
@@ -50,6 +53,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=expected_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('dd388146-9750-4124-82ba-62deff1052bb')
@rbac_rule_validation.action(
service="nova",
diff --git a/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py
index fbc03cf..b4531af 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_flavor_rxtx_rbac.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.common import utils
from tempest import config
from tempest.lib import decorators
@@ -33,6 +35,8 @@
msg = "os-flavor-rxtx extension not enabled."
raise cls.skipException(msg)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('5e1fd9f0-9a08-485a-ad9c-0fc66e4d64b7')
@rbac_rule_validation.action(
service="nova",
@@ -44,6 +48,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute='rxtx_factor')
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('70c55a07-c843-4627-a29d-ba78673c1e63')
@rbac_rule_validation.action(
service="nova",
diff --git a/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
index f36b8ec..c988128 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_images_rbac.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.common import image as common_image
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -20,6 +22,7 @@
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
+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
@@ -245,18 +248,67 @@
# https://developer.openstack.org/api-ref/compute/#images-deprecated
max_microversion = '2.35'
+ @classmethod
+ def skip_checks(cls):
+ super(ImageSizeRbacTest, cls).skip_checks()
+ if not CONF.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(ImageSizeRbacTest, cls).setup_clients()
+ if CONF.image_feature_enabled.api_v2:
+ cls.glance_image_client = cls.os_primary.image_client_v2
+ elif CONF.image_feature_enabled.api_v1:
+ cls.glance_image_client = cls.os_primary.image_client
+ else:
+ raise lib_exc.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
+
+ @classmethod
+ def resource_setup(cls):
+ super(ImageSizeRbacTest, cls).resource_setup()
+ params = {'name': data_utils.rand_name(cls.__name__ + '-image')}
+ if CONF.image_feature_enabled.api_v1:
+ params = {'headers': common_image.image_meta_to_headers(**params)}
+
+ cls.image = cls.glance_image_client.create_image(**params)
+ cls.addClassResourceCleanup(
+ cls.glance_image_client.wait_for_resource_deletion,
+ cls.image['id'])
+ cls.addClassResourceCleanup(
+ cls.glance_image_client.delete_image, cls.image['id'])
+
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('fe34d2a6-5743-45bf-8f92-a1d703d7c7ab')
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:image-size")
- def test_list_images(self):
+ def test_show_image_includes_image_size(self):
with self.rbac_utils.override_role(self):
- self.compute_images_client.list_images()
+ body = self.compute_images_client.show_image(self.image['id'])[
+ 'image']
+ expected_attr = 'OS-EXT-IMG-SIZE:size'
+ if expected_attr not in body:
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute=expected_attr)
+
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('08342c7d-297d-42ee-b398-90fce2443792')
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:image-size")
- def test_list_images_with_details(self):
+ def test_list_images_with_details_includes_image_size(self):
with self.rbac_utils.override_role(self):
- self.compute_images_client.list_images(detail=True)
+ body = self.compute_images_client.list_images(detail=True)[
+ 'images']
+
+ expected_attr = 'OS-EXT-IMG-SIZE:size'
+ if expected_attr not in body[0]:
+ raise rbac_exceptions.RbacMalformedResponse(
+ attribute=expected_attr)
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 d97f382..5681799 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
@@ -129,6 +129,8 @@
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'ACTIVE')
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@utils.requires_ext(extension='os-config-drive', service='compute')
@decorators.idempotent_id('2c82e819-382d-4d6f-87f0-a45954cbbc64')
@rbac_rule_validation.action(
@@ -144,6 +146,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=expected_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@utils.requires_ext(extension='os-config-drive', service='compute')
@decorators.idempotent_id('55c62ef7-b72b-4970-acc6-05b0a4316e5d')
@rbac_rule_validation.action(
@@ -169,6 +173,8 @@
# Force-deleting a server enforces os-deferred-delete.
self.servers_client.force_delete_server(self.server['id'])
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('d873740a-7b10-40a9-943d-7cc18115370e')
@utils.requires_ext(extension='OS-EXT-AZ', service='compute')
@rbac_rule_validation.action(
@@ -185,6 +191,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=expected_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('727e5360-770a-4b9c-8015-513a40216635')
@utils.requires_ext(extension='OS-EXT-AZ', service='compute')
@rbac_rule_validation.action(
@@ -200,6 +208,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=expected_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('4aa5d93e-4887-468a-8eb4-b6eca0ca6437')
@utils.requires_ext(extension='OS-EXT-SRV-ATTR', service='compute')
@rbac_rule_validation.action(
@@ -222,6 +232,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=whole_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('2ed7aee2-94b2-4a9f-ae63-a51b7f94fe30')
@utils.requires_ext(extension='OS-EXT-SRV-ATTR', service='compute')
@rbac_rule_validation.action(
@@ -244,6 +256,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=whole_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('82053c27-3134-4003-9b55-bc9fafdb0e3b')
@utils.requires_ext(extension='OS-EXT-STS', service='compute')
@rbac_rule_validation.action(
@@ -261,6 +275,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('7d2620a5-eea1-4a8b-96ea-86ad77a73fc8')
@utils.requires_ext(extension='OS-EXT-STS', service='compute')
@rbac_rule_validation.action(
@@ -278,6 +294,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('21e39cbe-6c32-48fc-80dd-3e1fece6053f')
@utils.requires_ext(extension='os-extended-volumes', service='compute')
@rbac_rule_validation.action(
@@ -295,6 +313,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute=expected_attr)
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@decorators.idempotent_id('7f163708-0d25-4138-8512-dfdd72a92989')
@utils.requires_ext(extension='os-extended-volumes', service='compute')
@rbac_rule_validation.action(
@@ -348,6 +368,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute='events.traceback')
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:os-keypairs")
@@ -360,6 +382,8 @@
raise rbac_exceptions.RbacMalformedResponse(
attribute='key_name')
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:os-keypairs")
@@ -469,6 +493,8 @@
with self.rbac_utils.override_role(self):
self.servers_client.show_password(self.server['id'])
+ @testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
+ "This API extension policy was removed in Stein")
@utils.requires_ext(extension='OS-SRV-USG', service='compute')
@rbac_rule_validation.action(
service="nova",
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index 91b3d1e..44f5962 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -16,13 +16,11 @@
from oslo_log import log as logging
from tempest.api.identity import base
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from patrole_tempest_plugin import rbac_utils
-CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -30,11 +28,6 @@
base.BaseIdentityTest):
@classmethod
- def skip_checks(cls):
- super(BaseIdentityRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseIdentityRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
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 00d522c..da5d4cd 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
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -21,8 +20,6 @@
from patrole_tempest_plugin import rbac_utils
from patrole_tempest_plugin.tests.api.identity import rbac_base
-CONF = config.CONF
-
class IdentityTokenV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
diff --git a/patrole_tempest_plugin/tests/api/image/rbac_base.py b/patrole_tempest_plugin/tests/api/image/rbac_base.py
index 954790d..becd564 100644
--- a/patrole_tempest_plugin/tests/api/image/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/image/rbac_base.py
@@ -12,22 +12,14 @@
# under the License.
from tempest.api.image import base as image_base
-from tempest import config
from patrole_tempest_plugin import rbac_utils
-CONF = config.CONF
-
class BaseV2ImageRbacTest(rbac_utils.RbacUtilsMixin,
image_base.BaseV2ImageTest):
@classmethod
- def skip_checks(cls):
- super(BaseV2ImageRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseV2ImageRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py b/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py
index 952c41f..4b5fd08 100644
--- a/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/test_images_member_rbac.py
@@ -68,8 +68,8 @@
self.alt_tenant_id)
@rbac_rule_validation.action(service="glance",
- rule="get_member",
- expected_error_code=404)
+ rules=["get_member"],
+ expected_error_codes=[404])
@decorators.idempotent_id('c01fd308-6484-11e6-881e-080027d0d606')
def test_show_image_member(self):
diff --git a/patrole_tempest_plugin/tests/api/network/rbac_base.py b/patrole_tempest_plugin/tests/api/network/rbac_base.py
index 9d3e28b..6102347 100644
--- a/patrole_tempest_plugin/tests/api/network/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/network/rbac_base.py
@@ -14,22 +14,14 @@
# under the License.
from tempest.api.network import base as network_base
-from tempest import config
from patrole_tempest_plugin import rbac_utils
-CONF = config.CONF
-
class BaseNetworkRbacTest(rbac_utils.RbacUtilsMixin,
network_base.BaseNetworkTest):
@classmethod
- def skip_checks(cls):
- super(BaseNetworkRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseNetworkRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py b/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
index 2756a10..7567275 100644
--- a/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_agents_rbac.py
@@ -38,8 +38,8 @@
@decorators.idempotent_id('f88e38e0-ab52-4b97-8ffa-48a27f9d199b')
@rbac_rule_validation.action(service="neutron",
- rule="get_agent",
- expected_error_code=404)
+ rules=["get_agent"],
+ expected_error_codes=[404])
def test_show_agent(self):
"""Show agent test.
diff --git a/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py b/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
index cdc9852..f8ef0bb 100644
--- a/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_flavors_rbac.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_serialization import jsonutils as json
+
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
@@ -116,3 +118,70 @@
with self.rbac_utils.override_role(self):
self.ntp_client.list_flavors()
+
+
+class FlavorsServiceProfilePluginRbacTest(base.BaseNetworkPluginRbacTest):
+ @classmethod
+ def resource_setup(cls):
+ super(FlavorsServiceProfilePluginRbacTest, cls).resource_setup()
+ providers = cls.ntp_client.list_service_providers()
+ if not providers["service_providers"]:
+ raise cls.skipException("No service_providers available.")
+ cls.service_type = providers["service_providers"][0]["service_type"]
+
+ cls.flavor_id = cls.create_flavor()
+ cls.service_profile_id = cls.create_service_profile()
+
+ @classmethod
+ def create_flavor(cls):
+ flavor = cls.ntp_client.create_flavor(service_type=cls.service_type)
+ flavor_id = flavor["flavor"]["id"]
+ cls.addClassResourceCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ cls.ntp_client.delete_flavor, flavor_id)
+ return flavor_id
+
+ @classmethod
+ def create_service_profile(cls):
+ service_profile = cls.ntp_client.create_service_profile(
+ metainfo=json.dumps({'foo': 'bar'}))
+ service_profile_id = service_profile["service_profile"]["id"]
+ cls.addClassResourceCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ cls.ntp_client.delete_service_profile, service_profile_id)
+ return service_profile_id
+
+ def create_flavor_service_profile(self, flavor_id, service_profile_id):
+ self.ntp_client.create_flavor_service_profile(
+ flavor_id, service_profile_id)
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.ntp_client.delete_flavor_service_profile,
+ flavor_id, service_profile_id)
+
+ @decorators.idempotent_id('aa84b4c5-0dd6-4c34-aa81-3a76507f9b81')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_flavor_service_profile"])
+ def test_create_flavor_service_profile(self):
+ """Create flavor_service_profile.
+
+ RBAC test for the neutron "create_flavor_service_profile" policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.create_flavor_service_profile(self.flavor_id,
+ self.service_profile_id)
+
+ @decorators.idempotent_id('3b680d9e-946a-4670-ab7f-0e4576675833')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["delete_flavor_service_profile"])
+ def test_delete_flavor_service_profile(self):
+ """Delete flavor_service_profile.
+
+ RBAC test for the neutron "delete_flavor_service_profile" policy
+ """
+ self.create_flavor_service_profile(self.flavor_id,
+ self.service_profile_id)
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.delete_flavor_service_profile(
+ self.flavor_id, self.service_profile_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py b/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py
index ed52c34..8a02149 100644
--- a/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_floating_ips_rbac.py
@@ -76,8 +76,7 @@
@rbac_rule_validation.action(
service="neutron",
rules=["create_floatingip",
- "create_floatingip:floating_ip_address"],
- expected_error_codes=[403, 403])
+ "create_floatingip:floating_ip_address"])
@decorators.idempotent_id('a8bb826a-403d-4130-a55d-120a0a660806')
def test_create_floating_ip_floatingip_address(self):
"""Create floating IP with address.
@@ -105,8 +104,8 @@
floating_ip['id'], port_id=None)
@rbac_rule_validation.action(service="neutron",
- rule="get_floatingip",
- expected_error_code=404)
+ rules=["get_floatingip"],
+ expected_error_codes=[404])
@decorators.idempotent_id('f8846fd0-c976-48fe-a148-105303931b32')
def test_show_floating_ip(self):
"""Show floating IP.
diff --git a/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py b/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py
index adab1e6..db099a1 100644
--- a/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_metering_label_rules_rbac.py
@@ -74,8 +74,8 @@
self._create_metering_label_rule(self.label)
@rbac_rule_validation.action(service="neutron",
- rule="get_metering_label_rule",
- expected_error_code=404)
+ rules=["get_metering_label_rule"],
+ expected_error_codes=[404])
@decorators.idempotent_id('e21b40c3-d44d-412f-84ea-836ca8603bcb')
def test_show_metering_label_rule(self):
"""Show metering label rule.
diff --git a/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py b/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py
index 0231868..0e10f5b 100644
--- a/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_metering_labels_rbac.py
@@ -58,8 +58,8 @@
self._create_metering_label()
@rbac_rule_validation.action(service="neutron",
- rule="get_metering_label",
- expected_error_code=404)
+ rules=["get_metering_label"],
+ expected_error_codes=[404])
@decorators.idempotent_id('c57f6636-c702-4755-8eac-5e73bc1f7d14')
def test_show_metering_label(self):
"""Show metering label.
diff --git a/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py b/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
index 0097c7b..c985111 100644
--- a/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_network_segments_rbac.py
@@ -67,8 +67,7 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_network",
- "create_network:segments"],
- expected_error_codes=[403, 403])
+ "create_network:segments"])
@decorators.idempotent_id('9e1d0c3d-92e3-40e3-855e-bfbb72ea6e0b')
def test_create_network_segments(self):
"""Create network with segments.
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 72674f6..2e69f89 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -110,8 +110,7 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_network",
- "create_network:is_default"],
- expected_error_codes=[403, 403])
+ "create_network:is_default"])
@decorators.idempotent_id('28602661-5ac7-407e-b739-e393f619f5e3')
def test_create_network_is_default(self):
@@ -129,8 +128,7 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_network",
- "create_network:shared"],
- expected_error_codes=[403, 403])
+ "create_network:shared"])
@decorators.idempotent_id('ccabf2a9-28c8-44b2-80e6-ffd65d43eef2')
def test_create_network_shared(self):
@@ -144,8 +142,7 @@
@utils.requires_ext(extension='external-net', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["create_network",
- "create_network:router:external"],
- expected_error_codes=[403, 403])
+ "create_network:router:external"])
@decorators.idempotent_id('51adf2a7-739c-41e0-8857-3b4c460cbd24')
def test_create_network_router_external(self):
@@ -160,8 +157,7 @@
@rbac_rule_validation.action(
service="neutron",
rules=["create_network",
- "create_network:provider:physical_network"],
- expected_error_codes=[403, 403])
+ "create_network:provider:physical_network"])
@decorators.idempotent_id('76783fed-9ff3-4499-a0d1-82d99eec364e')
def test_create_network_provider_physical_network(self):
@@ -184,8 +180,7 @@
@rbac_rule_validation.action(
service="neutron",
rules=["create_network",
- "create_network:provider:network_type"],
- expected_error_codes=[403, 403])
+ "create_network:provider:network_type"])
@decorators.idempotent_id('3c42f7b8-b80c-44ef-8fa4-69ec4b1836bc')
def test_create_network_provider_network_type(self):
@@ -200,8 +195,7 @@
@rbac_rule_validation.action(
service="neutron",
rules=["create_network",
- "create_network:provider:segmentation_id"],
- expected_error_codes=[403, 403])
+ "create_network:provider:segmentation_id"])
@decorators.idempotent_id('b9decb7b-68ef-4504-b99b-41edbf7d2af5')
def test_create_network_provider_segmentation_id(self):
@@ -338,8 +332,8 @@
str(exc))
@rbac_rule_validation.action(service="neutron",
- rule="get_network",
- expected_error_code=404)
+ rules=["get_network"],
+ expected_error_codes=[404])
@decorators.idempotent_id('0eb62d04-338a-4ff4-a8fa-534e52110534')
def test_show_network(self):
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 2cf3cd6..175d051 100644
--- a/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_ports_rbac.py
@@ -69,8 +69,7 @@
@decorators.idempotent_id('045ee797-4962-4913-b96a-5d7ea04099e7')
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:device_owner"],
- expected_error_codes=[403, 403])
+ "create_port:device_owner"])
def test_create_port_device_owner(self):
with self.rbac_utils.override_role(self):
self.create_port(self.network,
@@ -79,8 +78,7 @@
@decorators.idempotent_id('c4fa8844-f5ef-4daa-bfa2-b89897dfaedf')
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:port_security_enabled"],
- expected_error_codes=[403, 403])
+ "create_port:port_security_enabled"])
def test_create_port_security_enabled(self):
with self.rbac_utils.override_role(self):
self.create_port(self.network, port_security_enabled=True)
@@ -88,8 +86,7 @@
@utils.requires_ext(extension='binding', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:binding:host_id"],
- expected_error_codes=[403, 403])
+ "create_port:binding:host_id"])
@decorators.idempotent_id('a54bd6b8-a7eb-4101-bfe8-093930b0d660')
def test_create_port_binding_host_id(self):
@@ -102,8 +99,7 @@
@utils.requires_ext(extension='binding', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:binding:profile"],
- expected_error_codes=[403, 403])
+ "create_port:binding:profile"])
@decorators.idempotent_id('98fa38ab-c2ed-46a0-99f0-59f18cbd257a')
def test_create_port_binding_profile(self):
@@ -120,8 +116,7 @@
'"create_port:fixed_ips:ip_address" must be available in the cloud.')
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:fixed_ips:ip_address"],
- expected_error_codes=[403, 403])
+ "create_port:fixed_ips:ip_address"])
@decorators.idempotent_id('2551e10d-006a-413c-925a-8c6f834c09ac')
def test_create_port_fixed_ips_ip_address(self):
@@ -137,8 +132,7 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:mac_address"],
- expected_error_codes=[403, 403])
+ "create_port:mac_address"])
@decorators.idempotent_id('aee6d0be-a7f3-452f-aefc-796b4eb9c9a8')
def test_create_port_mac_address(self):
@@ -150,8 +144,7 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_port",
- "create_port:allowed_address_pairs"],
- expected_error_codes=[403, 403])
+ "create_port:allowed_address_pairs"])
@decorators.idempotent_id('b638d1f4-d903-4ca8-aa2a-6fd603c5ec3a')
def test_create_port_allowed_address_pairs(self):
@@ -166,8 +159,8 @@
self.create_port(**post_body)
@rbac_rule_validation.action(service="neutron",
- rule="get_port",
- expected_error_code=404)
+ rules=["get_port"],
+ expected_error_codes=[404])
@decorators.idempotent_id('a9d41cb8-78a2-4b97-985c-44e4064416f4')
def test_show_port(self):
with self.rbac_utils.override_role(self):
diff --git a/patrole_tempest_plugin/tests/api/network/test_rbac_policies_rbac.py b/patrole_tempest_plugin/tests/api/network/test_rbac_policies_rbac.py
new file mode 100644
index 0000000..a8813e7
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_rbac_policies_rbac.py
@@ -0,0 +1,111 @@
+# Copyright 2018 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 test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class RbacPoliciesPluginRbacTest(base.BaseNetworkPluginRbacTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(RbacPoliciesPluginRbacTest, cls).resource_setup()
+ cls.tenant_id = cls.os_primary.credentials.tenant_id
+ cls.network_id = cls.create_network()['id']
+
+ def create_rbac_policy(self, tenant_id, network_id):
+ policy = self.ntp_client.create_rbac_policy(
+ target_tenant=self.tenant_id,
+ object_type="network",
+ object_id=self.network_id,
+ action="access_as_shared"
+ )
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.ntp_client.delete_rbac_policy, policy["rbac_policy"]["id"])
+
+ return policy["rbac_policy"]["id"]
+
+ @decorators.idempotent_id('effd9545-99ad-4c3c-92dd-ea422602c868')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_rbac_policy",
+ "create_rbac_policy:target_tenant"])
+ def test_create_rbac_policy(self):
+ """Create RBAC policy.
+
+ RBAC test for the neutron "create_rbac_policy" policy
+
+ We can't validate "create_rbac_policy:target_tenant" for all cases
+ since if "restrict_wildcard" rule is modified then Patrole won't be
+ able to determine the correct result since that requires relying on
+ Neutron's custom FieldCheck oslo.policy rule.
+ """
+
+ with self.rbac_utils.override_role(self):
+ self.create_rbac_policy(self.tenant_id, self.network_id)
+
+ @decorators.idempotent_id('f5d836d8-3b64-412d-a283-ee29761017f3')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_rbac_policy",
+ "update_rbac_policy",
+ "update_rbac_policy:target_tenant"],
+ expected_error_codes=[404, 403, 403])
+ def test_update_rbac_policy(self):
+ """Update RBAC policy.
+
+ RBAC test for the neutron "update_rbac_policy" policy
+
+ We can't validate "create_rbac_policy:target_tenant" for all cases
+ since if "restrict_wildcard" rule is modified then Patrole won't be
+ able to determine the correct result since that requires relying on
+ Neutron's custom FieldCheck oslo.policy rule.
+ """
+ policy_id = self.create_rbac_policy(self.tenant_id, self.network_id)
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.update_rbac_policy(
+ policy_id, target_tenant=self.tenant_id)
+
+ @decorators.idempotent_id('9308ab18-426c-41b7-bce5-11081f7dd259')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_rbac_policy"],
+ expected_error_codes=[404])
+ def test_show_rbac_policy(self):
+ """Show RBAC policy.
+
+ RBAC test for the neutron "get_rbac_policy" policy
+ """
+ policy_id = self.create_rbac_policy(self.tenant_id, self.network_id)
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.show_rbac_policy(policy_id)
+
+ @decorators.idempotent_id('54aa9bce-efea-47fb-b0e4-12012f82f285')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_rbac_policy",
+ "delete_rbac_policy"],
+ expected_error_codes=[404, 403])
+ def test_delete_rbac_policy(self):
+ """Delete RBAC policy.
+
+ RBAC test for the neutron "delete_rbac_policy" policy
+ """
+ policy_id = self.create_rbac_policy(self.tenant_id, self.network_id)
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.delete_rbac_policy(policy_id)
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 a3d973d..3d7631a 100644
--- a/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_routers_rbac.py
@@ -72,8 +72,7 @@
@utils.requires_ext(extension='l3-ha', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["create_router",
- "create_router:ha"],
- expected_error_codes=[403, 403])
+ "create_router:ha"])
def test_create_high_availability_router(self):
"""Create high-availability router
@@ -88,8 +87,7 @@
@utils.requires_ext(extension='dvr', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["create_router",
- "create_router:distributed"],
- expected_error_codes=[403, 403])
+ "create_router:distributed"])
def test_create_distributed_router(self):
"""Create distributed router
@@ -104,8 +102,7 @@
@rbac_rule_validation.action(
service="neutron",
rules=["create_router",
- "create_router:external_gateway_info:enable_snat"],
- expected_error_codes=[403, 403])
+ "create_router:external_gateway_info:enable_snat"])
@decorators.idempotent_id('3c5acd49-0ec7-4109-ab51-640557b48ebc')
def test_create_router_enable_snat(self):
"""Create Router Snat
@@ -126,8 +123,7 @@
@rbac_rule_validation.action(
service="neutron",
rules=["create_router",
- "create_router:external_gateway_info:external_fixed_ips"],
- expected_error_codes=[403, 403])
+ "create_router:external_gateway_info:external_fixed_ips"])
@decorators.idempotent_id('d0354369-a040-4349-b869-645c8aed13cd')
def test_create_router_external_fixed_ips(self):
"""Create Router Fixed IPs
@@ -151,8 +147,8 @@
router['router']['id'])
@rbac_rule_validation.action(service="neutron",
- rule="get_router",
- expected_error_code=404)
+ rules=["get_router"],
+ expected_error_codes=[404])
@decorators.idempotent_id('bfbdbcff-f115-4d3e-8cd5-6ada33fd0e21')
def test_show_router(self):
"""Get Router
diff --git a/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py b/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py
index 1cf841d..4536fdb 100644
--- a/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_security_groups_rbac.py
@@ -78,8 +78,8 @@
self._create_security_group()
@rbac_rule_validation.action(service="neutron",
- rule="get_security_group",
- expected_error_code=404)
+ rules=["get_security_group"],
+ expected_error_codes=[404])
@decorators.idempotent_id('56335e77-aef2-4b54-86c7-7f772034b585')
def test_show_security_group(self):
@@ -149,8 +149,8 @@
sec_group_rule['id'])
@rbac_rule_validation.action(service="neutron",
- rule="get_security_group_rule",
- expected_error_code=404)
+ rules=["get_security_group_rule"],
+ expected_error_codes=[404])
@decorators.idempotent_id('84b4038c-261e-4a94-90d5-c885739ab0d5')
def test_show_security_group_rule(self):
diff --git a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
index 124b59a..7d02271 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
@@ -65,8 +65,7 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_subnetpool",
- "create_subnetpool:shared"],
- expected_error_codes=[403, 403])
+ "create_subnetpool:shared"])
@decorators.idempotent_id('cf730989-0d47-40bc-b39a-99e7de484723')
def test_create_subnetpool_shared(self):
"""Create subnetpool shared.
@@ -77,8 +76,8 @@
self._create_subnetpool(shared=True)
@rbac_rule_validation.action(service="neutron",
- rule="get_subnetpool",
- expected_error_code=404)
+ rules=["get_subnetpool"],
+ expected_error_codes=[404])
@decorators.idempotent_id('4f5aee26-0507-4b6d-b44c-3128a25094d2')
def test_show_subnetpool(self):
"""Show subnetpool.
@@ -107,8 +106,7 @@
@decorators.idempotent_id('a16f4e5c-0675-415f-b636-00af00638693')
@rbac_rule_validation.action(service="neutron",
rules=["update_subnetpool",
- "update_subnetpool:is_default"],
- expected_error_codes=[403, 403])
+ "update_subnetpool:is_default"])
def test_update_subnetpool_is_default(self):
"""Update default subnetpool.
diff --git a/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py b/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
index 77d4b42..93d79a9 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnets_rbac.py
@@ -50,8 +50,8 @@
@decorators.idempotent_id('c02618e7-bb20-4abd-83c8-6eec2af08752')
@rbac_rule_validation.action(service="neutron",
- rule="get_subnet",
- expected_error_code=404)
+ rules=["get_subnet"],
+ expected_error_codes=[404])
def test_show_subnet(self):
"""Show subnet.
diff --git a/patrole_tempest_plugin/tests/api/network/test_trunks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_trunks_rbac.py
new file mode 100644
index 0000000..063fd55
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_trunks_rbac.py
@@ -0,0 +1,85 @@
+# Copyright 2018 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 utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class TrunksPluginRbacTest(base.BaseNetworkPluginRbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(TrunksPluginRbacTest, cls).skip_checks()
+ if not utils.is_extension_enabled('trunk', 'network'):
+ msg = "trunk extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(TrunksPluginRbacTest, cls).resource_setup()
+ cls.network = cls.create_network()
+ cls.port_id = cls.create_port(cls.network)["id"]
+
+ def create_trunk(self, port_id):
+ trunk = self.ntp_client.create_trunk(port_id, [])
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.ntp_client.delete_trunk, trunk["trunk"]['id'])
+
+ return trunk
+
+ @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08130')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_trunk"])
+ def test_create_trunk(self):
+ """Create trunk.
+
+ RBAC test for the neutron "create_trunk" policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.create_trunk(self.port_id)
+
+ @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08131')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_trunk"],
+ expected_error_codes=[404])
+ def test_show_trunk(self):
+ """Show trunk.
+
+ RBAC test for the neutron "get_trunk" policy
+ """
+ trunk = self.create_trunk(self.port_id)
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.show_trunk(trunk['trunk']['id'])
+
+ @decorators.idempotent_id('c02618e7-bb20-1a3a-83c8-6eec2af08132')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_trunk",
+ "delete_trunk"],
+ expected_error_codes=[404, 403])
+ def test_delete_trunk(self):
+ """Delete trunk.
+
+ RBAC test for the neutron "delete_trunk" policy
+ """
+ trunk = self.create_trunk(self.port_id)
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.delete_trunk(trunk['trunk']['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/rbac_base.py b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
index bac173e..14b3151 100644
--- a/patrole_tempest_plugin/tests/api/volume/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
@@ -13,14 +13,11 @@
from tempest.api.volume import base as vol_base
from tempest.common import waiters
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from patrole_tempest_plugin import rbac_utils
-CONF = config.CONF
-
class BaseVolumeRbacTest(rbac_utils.RbacUtilsMixin,
vol_base.BaseVolumeTest):
@@ -32,11 +29,6 @@
_api_version = 3
@classmethod
- def skip_checks(cls):
- super(BaseVolumeRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseVolumeRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/unit/fixtures.py b/patrole_tempest_plugin/tests/unit/fixtures.py
index 1c47985..4552224 100644
--- a/patrole_tempest_plugin/tests/unit/fixtures.py
+++ b/patrole_tempest_plugin/tests/unit/fixtures.py
@@ -16,6 +16,7 @@
"""Fixtures for Patrole tests."""
from __future__ import absolute_import
+from contextlib import contextmanager
import fixtures
import mock
import time
@@ -117,6 +118,17 @@
new_role = 'member' if role_toggle else 'admin'
self.set_roles(['admin', 'member'], [new_role])
+ @contextmanager
+ def real_override_role(self, test_obj):
+ """Actual call to ``override_role``.
+
+ Useful for ensuring all the necessary mocks are performed before
+ the method in question is called.
+ """
+ _rbac_utils = rbac_utils.RbacUtils(test_obj)
+ with _rbac_utils.override_role(test_obj):
+ yield
+
def set_roles(self, roles, roles_on_project=None):
"""Set the list of available roles in the system.
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 1bf5510..fe36f2c 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -12,9 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+from __future__ import absolute_import
+
import mock
from oslo_config import cfg
+import fixtures
from tempest.lib import exceptions
from tempest import manager
from tempest import test
@@ -23,7 +26,7 @@
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_rule_validation as rbac_rv
from patrole_tempest_plugin import rbac_utils
-from patrole_tempest_plugin.tests.unit import fixtures
+from patrole_tempest_plugin.tests.unit import fixtures as patrole_fixtures
CONF = cfg.CONF
@@ -43,10 +46,12 @@
setattr(self.mock_test_args.os_primary, 'credentials', mock_creds)
self.useFixture(
- fixtures.ConfPatcher(rbac_test_role='Member', group='patrole'))
+ patrole_fixtures.ConfPatcher(rbac_test_role='Member',
+ group='patrole'))
# Disable patrole log for unit tests.
self.useFixture(
- fixtures.ConfPatcher(enable_reporting=False, group='patrole_log'))
+ patrole_fixtures.ConfPatcher(enable_reporting=False,
+ group='patrole_log'))
class RBACRuleValidationTest(BaseRBACRuleValidationTest):
@@ -54,6 +59,12 @@
``rbac_rule_validation`` decorator.
"""
+ def setUp(self):
+ super(RBACRuleValidationTest, self).setUp()
+ # This behavior is tested in separate test class below.
+ self.useFixture(fixtures.MockPatchObject(
+ rbac_rv, '_validate_override_role_called'))
+
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_rule_validation_have_permission_no_exc(self, mock_authority,
@@ -269,7 +280,7 @@
mock_log.warning.assert_called_with(
"NotFound exception was caught for test %s. Expected policies "
"which may have caused the error: %s. The service %s throws a "
- "404 instead of a 403, which is irregular.",
+ "404 instead of a 403, which is irregular",
test_policy.__name__,
', '.join(policy_names),
mock.sentinel.service)
@@ -334,7 +345,7 @@
expected_irregular_msg = (
"NotFound exception was caught for test %s. Expected policies "
"which may have caused the error: %s. The service %s throws a "
- "404 instead of a 403, which is irregular.")
+ "404 instead of a 403, which is irregular")
actual_exception, actual_irregular_msg = \
rbac_rv._get_exception_type(404)
@@ -385,6 +396,12 @@
Patrole RBAC validation work flows.
"""
+ def setUp(self):
+ super(RBACRuleValidationLoggingTest, self).setUp()
+ # This behavior is tested in separate test class below.
+ self.useFixture(fixtures.MockPatchObject(
+ rbac_rv, '_validate_override_role_called'))
+
@mock.patch.object(rbac_rv, 'RBACLOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_rbac_report_logging_disabled(self, mock_authority, mock_rbaclog):
@@ -392,7 +409,8 @@
is False
"""
self.useFixture(
- fixtures.ConfPatcher(enable_reporting=False, group='patrole_log'))
+ patrole_fixtures.ConfPatcher(enable_reporting=False,
+ group='patrole_log'))
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
@@ -410,7 +428,8 @@
True
"""
self.useFixture(
- fixtures.ConfPatcher(enable_reporting=True, group='patrole_log'))
+ patrole_fixtures.ConfPatcher(enable_reporting=True,
+ group='patrole_log'))
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
policy_names = ['foo:bar', 'baz:qux']
@@ -432,6 +451,12 @@
class RBACRuleValidationNegativeTest(BaseRBACRuleValidationTest):
+ def setUp(self):
+ super(RBACRuleValidationNegativeTest, self).setUp()
+ # This behavior is tested in separate test class below.
+ self.useFixture(fixtures.MockPatchObject(
+ rbac_rv, '_validate_override_role_called'))
+
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_rule_validation_invalid_service_raises_exc(self, mock_authority):
"""Test that invalid service raises the appropriate exception."""
@@ -451,6 +476,12 @@
``rbac_rule_validation`` decorator.
"""
+ def setUp(self):
+ super(RBACRuleValidationTestMultiPolicy, self).setUp()
+ # This behavior is tested in separate test class below.
+ self.useFixture(fixtures.MockPatchObject(
+ rbac_rv, '_validate_override_role_called'))
+
def _assert_policy_authority_called_with(self, rules, mock_authority):
m_authority = mock_authority.PolicyAuthority.return_value
m_authority.allowed.assert_has_calls([
@@ -708,3 +739,189 @@
# When expected_error_codes is provided rules must be as well.
self.assertRaisesRegex(ValueError, error_re, _do_test,
None, None, None, [404])
+
+
+class RBACOverrideRoleValidationTest(BaseRBACRuleValidationTest):
+ """Class for validating that untimely exceptions (outside
+ ``override_role`` is called) result in test failures.
+
+ This regression tests false positives caused by test exceptions matching
+ the expected exception before or after the ``override_role`` context is
+ called. Also tests case where ``override_role`` is never called which is
+ an invalid Patrole test.
+
+ """
+
+ def setUp(self):
+ super(RBACOverrideRoleValidationTest, self).setUp()
+
+ # Mixin automatically initializes __override_role_called to False.
+ class FakeRbacTest(rbac_utils.RbacUtilsMixin, test.BaseTestCase):
+ def runTest(self):
+ pass
+
+ # Stub out problematic function calls.
+ FakeRbacTest.os_primary = mock.Mock(spec=manager.Manager)
+ FakeRbacTest.rbac_utils = self.useFixture(
+ patrole_fixtures.RbacUtilsFixture())
+ mock_creds = mock.Mock(user_id=mock.sentinel.user_id,
+ project_id=mock.sentinel.project_id)
+ setattr(FakeRbacTest.os_primary, 'credentials', mock_creds)
+ setattr(FakeRbacTest.os_primary, 'auth_provider', mock.Mock())
+
+ self.parent_class = FakeRbacTest
+
+ @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+ def test_rule_validation_override_role_called_inside_ctx(self,
+ mock_authority):
+ """Test success case when the expected exception is raised within the
+ override_role context.
+ """
+ mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+ False
+
+ class ChildRbacTest(self.parent_class):
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+ expected_error_codes=[404])
+ def test_called(self_):
+ with self_.rbac_utils.real_override_role(self_):
+ raise exceptions.NotFound()
+
+ child_test = ChildRbacTest()
+ child_test.test_called()
+
+ @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+ def test_rule_validation_override_role_patrole_exception_ignored(
+ self, mock_authority):
+ """Test success case where Patrole exception is raised (which is
+ valid in case of e.g. RbacMalformedException) after override_role
+ passes.
+ """
+ mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+ True
+
+ class ChildRbacTest(self.parent_class):
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+ expected_error_codes=[404])
+ def test_called(self_):
+ with self_.rbac_utils.real_override_role(self_):
+ pass
+ # Instances of BasePatroleException don't count as they are
+ # part of the validation work flow.
+ raise rbac_exceptions.BasePatroleException()
+
+ child_test = ChildRbacTest()
+ self.assertRaises(rbac_exceptions.BasePatroleException,
+ child_test.test_called)
+
+ @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+ def test_rule_validation_override_role_called_before_ctx(self,
+ mock_authority):
+ """Test failure case when an exception that happens before
+ ``override_role`` context, even if it is the expected exception,
+ raises ``RbacOverrideRoleException``.
+ """
+ mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+ False
+
+ # This behavior should work for supported (NotFound/Forbidden) and
+ # miscellaneous exceptions alike.
+ for exception_type in (exceptions.NotFound,
+ Exception):
+ class ChildRbacTest(self.parent_class):
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+ expected_error_codes=[404])
+ def test_called_before(self_):
+ raise exception_type()
+
+ child_test = ChildRbacTest()
+ test_re = ".*before.*"
+ self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+ test_re, child_test.test_called_before)
+
+ @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+ def test_rule_validation_override_role_called_after_ctx(self,
+ mock_authority):
+ """Test failure case when an exception that happens before
+ ``override_role`` context, even if it is the expected exception,
+ raises ``RbacOverrideRoleException``.
+ """
+ mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+ False
+
+ # This behavior should work for supported (NotFound/Forbidden) and
+ # miscellaneous exceptions alike.
+ for exception_type in (exceptions.NotFound,
+ Exception):
+ class ChildRbacTest(self.parent_class):
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+ expected_error_codes=[404])
+ def test_called_after(self_):
+ with self_.rbac_utils.real_override_role(self_):
+ pass
+ # Simulates a test tearDown failure or some such.
+ raise exception_type()
+
+ child_test = ChildRbacTest()
+ test_re = ".*after.*"
+ self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+ test_re, child_test.test_called_after)
+
+ @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+ def test_rule_validation_override_role_never_called(self, mock_authority):
+ """Test failure case where override_role is **never** called."""
+ mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+ False
+
+ class ChildRbacTest(self.parent_class):
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule"],
+ expected_error_codes=[404])
+ def test_never_called(self_):
+ pass
+
+ child_test = ChildRbacTest()
+ test_re = ".*missing required `override_role` call.*"
+ self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+ test_re, child_test.test_never_called)
+
+ @mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
+ def test_rule_validation_override_role_sequential_test_calls(
+ self, mock_authority):
+ """Test success/failure scenarios above across sequential test calls.
+ """
+ mock_authority.PolicyAuthority.return_value.allowed.return_value =\
+ False
+
+ class ChildRbacTest(self.parent_class):
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule1"],
+ expected_error_codes=[404])
+ def test_called(self_):
+ with self_.rbac_utils.real_override_role(self_):
+ raise exceptions.NotFound()
+
+ @rbac_rv.action(mock.sentinel.service, rules=["fake:rule2"],
+ expected_error_codes=[404])
+ def test_called_before(self_):
+ raise exceptions.NotFound()
+
+ test_re = ".*before.*"
+
+ # Test case where override role is called in first test but *not* in
+ # second test.
+ child_test1 = ChildRbacTest()
+ child_test1.test_called()
+ self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+ test_re, child_test1.test_called_before)
+
+ # Test case where override role is *not* called in first test but is
+ # in second test.
+ child_test2 = ChildRbacTest()
+ self.assertRaisesRegex(rbac_exceptions.RbacOverrideRoleException,
+ test_re, child_test2.test_called_before)
+ child_test2.test_called()
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index c5264aa..5132079 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -208,11 +208,6 @@
class FakeRbacTest(rbac_utils.RbacUtilsMixin, test.BaseTestCase):
@classmethod
- def skip_checks(cls):
- super(FakeRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(FakeRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
@@ -237,21 +232,3 @@
self.assertTrue(hasattr(child_test, 'rbac_utils'))
self.assertIsInstance(child_test.rbac_utils, rbac_utils.RbacUtils)
-
- def test_skip_rbac_checks(self):
- """Validate that the child class is skipped if `[patrole] enable_rbac`
- is False and that the child class's name is in the skip message.
- """
- self.useFixture(patrole_fixtures.ConfPatcher(enable_rbac=False,
- group='patrole'))
-
- class ChildRbacTest(self.parent_class):
- pass
-
- child_test = ChildRbacTest()
-
- with testtools.ExpectedException(
- testtools.TestCase.skipException,
- value_re=('Patrole testing not enabled so skipping %s.'
- % ChildRbacTest.__name__)):
- child_test.setUpClass()
diff --git a/releasenotes/notes/check-expected-errors-only-in-override-role-f7109a73f5ff70e2.yaml b/releasenotes/notes/check-expected-errors-only-in-override-role-f7109a73f5ff70e2.yaml
new file mode 100644
index 0000000..e0ac744
--- /dev/null
+++ b/releasenotes/notes/check-expected-errors-only-in-override-role-f7109a73f5ff70e2.yaml
@@ -0,0 +1,19 @@
+---
+features:
+ - |
+ Add new exception called ``RbacOverrideRoleException``. Used for
+ safeguarding against false positives that might occur when the expected
+ exception isn't raised inside the ``override_role`` context. Specifically,
+ when:
+
+ * ``override_role`` isn't called
+ * an exception is raised before ``override_role`` context
+ * an exception is raised after ``override_role`` context
+fixes:
+ - |
+ Previously, the ``rbac_rule_validation.action`` decorator could catch
+ expected exceptions with no regard to where the error happened. Such
+ behavior could cause false-positive results. To prevent this from
+ happening from now on, if an exception happens outside of the
+ ``override_role`` context, it will cause
+ ``rbac_exceptions.RbacOverrideRoleException`` to be raised.
diff --git a/releasenotes/notes/patrole-rocky-release-e6f36691306bec7e.yaml b/releasenotes/notes/patrole-rocky-release-e6f36691306bec7e.yaml
new file mode 100644
index 0000000..22c4958
--- /dev/null
+++ b/releasenotes/notes/patrole-rocky-release-e6f36691306bec7e.yaml
@@ -0,0 +1,14 @@
+---
+prelude: >
+ This release is to tag the Patrole for OpenStack Rocky release.
+ After this release, Patrole will support below OpenStack Releases:
+
+ * Rocky
+ * Queens
+ * Pike
+
+ Current development of Patrole is for OpenStack Stein development
+ cycle. Every Patrole commit is also tested against master during
+ the Stein cycle. However, this does not necessarily mean that using
+ Patrole as of this tag will work against a Stein (or future release)
+ cloud.
diff --git a/releasenotes/notes/remove-deprecated-api-extensions-policies-fca3d31c7f5f1f6c.yaml b/releasenotes/notes/remove-deprecated-api-extensions-policies-fca3d31c7f5f1f6c.yaml
new file mode 100644
index 0000000..925791f
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-api-extensions-policies-fca3d31c7f5f1f6c.yaml
@@ -0,0 +1,23 @@
+---
+features:
+ - |
+ A new policy feature flag called
+ ``[policy_feature_flag].removed_nova_policies_stein`` has been added to
+ Patrole's config to handle Nova API extension policies removed in Stein.
+
+ The policy feature flag is applied to tests that validate response bodies
+ for expected attributes previously returned for the following policies
+ that passed authorization:
+
+ - os_compute_api:os-config-drive
+ - os_compute_api:os-extended-availability-zone
+ - os_compute_api:os-extended-status
+ - os_compute_api:os-extended-volumes
+ - os_compute_api:os-keypairs
+ - os_compute_api:os-server-usage
+ - os_compute_api:os-flavor-rxtx
+ - os_compute_api:os-flavor-access (only from /flavors APIs)
+ - os_compute_api:image-size
+
+ Note that not all removed policies are included above because test coverage
+ is missing for them (like os_compute_api:os-security-groups).
diff --git a/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml b/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml
new file mode 100644
index 0000000..53b1710
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ Remove deprecated ``[patrole].enable_rbac`` configuration option. To skip
+ Patrole tests going forward, use an appropriate regex.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index ce29994..eb061a4 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
:maxdepth: 1
unreleased
+ v0.4.0
v0.3.0
v0.2.0
v0.1.0
diff --git a/releasenotes/source/v0.4.0.rst b/releasenotes/source/v0.4.0.rst
new file mode 100644
index 0000000..2ed32ff
--- /dev/null
+++ b/releasenotes/source/v0.4.0.rst
@@ -0,0 +1,6 @@
+====================
+v0.4.0 Release Notes
+====================
+
+.. release-notes:: 0.4.0 Release Notes
+ :version: 0.4.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 9085c07..a08c27a 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -8,3 +8,4 @@
nose>=1.3.7 # LGPL
nosexcover>=1.0.10 # BSD
oslotest>=3.2.0 # Apache-2.0
+bandit>=1.5 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index a09822f..ea9abf1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -22,8 +22,12 @@
[testenv:pep8]
basepython = python3
-commands = flake8 {posargs}
- check-uuid --package patrole_tempest_plugin.tests.api
+deps =
+ -r{toxinidir}/test-requirements.txt
+commands =
+ flake8 {posargs}
+ bandit -r patrole_tempest_plugin -x patrole_tempest_plugin/tests -n 5
+ check-uuid --package patrole_tempest_plugin.tests.api
[testenv:uuidgen]
basepython = python3