Merge "Remove duplicate test_unmanage_volume test"
diff --git a/.zuul.yaml b/.zuul.yaml
index 2619ed7..1eab464 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,7 +1,7 @@
- job:
name: patrole-base
parent: devstack-tempest
- description: Patrole base job for admin and Member roles.
+ description: Patrole base job for admin and member roles.
required-projects:
- name: openstack/tempest
- name: openstack/patrole
@@ -54,7 +54,7 @@
- job:
name: patrole-member
parent: patrole-base
- description: Patrole job for Member role.
+ description: Patrole job for member role.
# This currently works from stable/pike onward.
branches:
- master
@@ -62,7 +62,7 @@
- stable/pike
vars:
devstack_localrc:
- RBAC_TEST_ROLE: Member
+ RBAC_TEST_ROLE: member
- job:
name: patrole-member-queens
@@ -93,12 +93,12 @@
- job:
name: patrole-py35-member
parent: patrole-base
- description: Patrole py3 job for Member role.
+ description: Patrole py35 job for member role.
vars:
devstack_localrc:
- # Use Member for py3 because arguably negative testing is more
+ # Use member for py35 because arguably negative testing is more
# important than admin, which is already covered by patrole-admin job.
- RBAC_TEST_ROLE: Member
+ RBAC_TEST_ROLE: member
USE_PYTHON3: true
devstack_services:
s-account: false
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index d56c963..bd0068b 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -13,16 +13,13 @@
function install_patrole_tempest_plugin {
setup_package $PATROLE_DIR -e
- if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
- RBAC_TEST_ROLE="Member"
- fi
-
- iniset $TEMPEST_CONFIG patrole enable_rbac True
- iniset $TEMPEST_CONFIG patrole rbac_test_role $RBAC_TEST_ROLE
-
if [[ ${DEVSTACK_SERIES} == 'pike' ]]; then
+ if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
+ RBAC_TEST_ROLE="Member"
+ fi
+
# Policies used by Patrole testing that were changed in a backwards-incompatible way.
- # TODO(fmontei): Remove these once stable/pike becomes EOL.
+ # TODO(felipemonteiro): Remove these once stable/pike becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled create_port_fixed_ips_ip_address_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled update_port_fixed_ips_ip_address_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled limits_extension_used_limits_policy False
@@ -30,6 +27,15 @@
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
fi
+
+ if [[ ${DEVSTACK_SERIES} == 'queens' ]]; then
+ if [[ "$RBAC_TEST_ROLE" == "member" ]]; then
+ RBAC_TEST_ROLE="Member"
+ fi
+ fi
+
+ iniset $TEMPEST_CONFIG patrole enable_rbac True
+ iniset $TEMPEST_CONFIG patrole rbac_test_role $RBAC_TEST_ROLE
}
if is_service_enabled tempest; then
diff --git a/doc/source/framework/policy_authority.rst b/doc/source/framework/policy_authority.rst
index 7cd4421..822c7b6 100644
--- a/doc/source/framework/policy_authority.rst
+++ b/doc/source/framework/policy_authority.rst
@@ -9,7 +9,8 @@
This module is only called for calculating the "Expected" result if
``[patrole] test_custom_requirements`` is ``False``.
-Using the Policy Authority Module, policy verification is performed by:
+Using the :class:`~patrole_tempest_plugin.policy_authority.PolicyAuthority`
+class, policy verification is performed by:
#. Pooling together the default `in-code` policy rules.
#. Overriding the defaults with custom policy rules located in a policy.json,
@@ -22,9 +23,40 @@
#. Performing a call with all necessary data to ``oslo.policy`` and returning
the expected result back to ``rbac_rule_validation`` decorator.
+When to use
+-----------
+
+This :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority` class
+can be used to validate the default OpenStack policy configuration. It
+is recommended that this approach be used for RBAC validation for clouds that
+use little to no policy customizations or overrides.
+
+This validation approach should be used when:
+
+* Validating the out-of-the-box policy-in-code OpenStack policy configuration.
+
+ It is important that the default OpenStack policy configuration be validated
+ before deploying OpenStack into production. Bugs exist in software and the
+ earlier they can be caught and prevented (via CI/CD, for example), the
+ better. Patrole continues to be used to identify default policy bugs
+ across OpenStack services.
+
+* Validating policy reliably and accurately.
+
+ Relying on ``oslo.policy`` to compute the expected test results provides
+ accurate tests, without the hassle of having to reinvent the wheel. Since
+ OpenStack APIs use ``oslo.policy`` for policy enforcement, it makes sense
+ to compute expected results by using the same library, ensuring test
+ reliability.
+
+* Continuously validating policy changes to OpenStack projects under
+ development by gating them against Patrole CI/CD jobs run by `Zuul`_.
+
+.. _Zuul: https://docs.openstack.org/infra/zuul/
+
Implementation
--------------
.. automodule:: patrole_tempest_plugin.policy_authority
:members:
- :special-members:
+ :undoc-members:
diff --git a/doc/source/framework/rbac_authority.rst b/doc/source/framework/rbac_authority.rst
new file mode 100644
index 0000000..84c372b
--- /dev/null
+++ b/doc/source/framework/rbac_authority.rst
@@ -0,0 +1,37 @@
+.. rbac-authority:
+
+RBAC Authority Module
+=====================
+
+Overview
+--------
+
+This module implements an abstract class that is implemented by the classes
+below. Each implementation is used by the :ref:`rbac-validation` framework
+to determine each expected test result.
+
+:ref:`policy-authority`
+-----------------------
+
+The *default* :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority`
+implementation class which is used for policy validation. Uses ``oslo.policy``
+to determine the expected test result.
+
+All Patrole `Zuul`_ gates use this
+:class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority` class by default.
+
+.. _Zuul: https://docs.openstack.org/infra/zuul/
+
+:ref:`requirements-authority`
+-----------------------------
+
+Optional :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority`
+implementation class which is used for policy validation. It uses a high-level
+requirements-driven approach to validating RBAC in Patrole.
+
+Implementation
+--------------
+
+.. automodule:: patrole_tempest_plugin.rbac_authority
+ :members:
+ :undoc-members:
diff --git a/doc/source/framework/requirements_authority.rst b/doc/source/framework/requirements_authority.rst
new file mode 100644
index 0000000..6c4fcc0
--- /dev/null
+++ b/doc/source/framework/requirements_authority.rst
@@ -0,0 +1,105 @@
+.. _requirements-authority:
+
+Requirements Authority Module
+=============================
+
+Overview
+--------
+
+Requirements-driven approach to declaring the expected RBAC test results
+referenced by Patrole. Uses a high-level YAML syntax to crystallize policy
+requirements concisely and unambiguously.
+
+.. note::
+
+ The :ref:`custom-requirements-file` is required to use this validation
+ approach and, currently, must be manually generated.
+
+This validation approach can be toggled on by setting the
+``[patrole].test_custom_requirements`` configuration option to ``True``;
+see :ref:`patrole-configuration` for more information.
+
+When to use
+-----------
+
+This :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority` class
+can be used to achieve a requirements-driven approach to validating an
+OpenStack cloud's RBAC implementation. Using this approach, Patrole computes
+expected test results by performing lookups against a
+:ref:`custom-requirements-file` which precisely defines the cloud's RBAC
+requirements.
+
+Using a high-level declarative language, the requirements are captured
+unambiguously in the :ref:`custom-requirements-file`, allowing operators to
+validate their requirements against their OpenStack cloud.
+
+This validation approach should be used when:
+
+* The cloud has heavily customized policy files that require careful validation
+ against one's requirements.
+
+ Heavily customized policy files can contain relatively nuanced/technical
+ syntax that impinges upon the goal of using a clear and concise syntax
+ present in the :ref:`custom-requirements-file` to drive RBAC validation.
+
+* The cloud has non-OpenStack services that require RBAC validation but which
+ don't leverage the ``oslo.policy`` framework.
+
+ Services like `Contrail`_ that are present in an OpenStack-based cloud that
+ interface with OpenStack services like Neutron also require RBAC validation.
+ The requirements-driven approach to RBAC validation is framework-agnostic
+ and so can work with any policy engine.
+
+* Expected results are captured as clear-cut, unambiguous requirements.
+
+ Validating a cloud's RBAC against high-level, clear-cut requirements is
+ a valid use case. Relying on ``oslo.policy`` validating customized policy
+ files is not sufficient to satisfy this use case.
+
+As mentioned above, the trade-off with this approach is having to manually
+generate the :ref:`custom-requirements-file`. There is currently no
+tooling to automatically do this.
+
+.. _Contrail: https://github.com/Juniper/contrail-controller/wiki/RBAC
+
+.. _custom-requirements-file:
+
+Custom Requirements File
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+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:
+
+.. code-block:: yaml
+
+ <service_foo>:
+ <api_action_a>:
+ - <allowed_role_1>
+ - <allowed_role_2>
+ - <allowed_role_3>
+ <api_action_b>:
+ - <allowed_role_2>
+ - <allowed_role_4>
+ <service_bar>:
+ <api_action_c>:
+ - <allowed_role_3>
+
+Where:
+
+service = the service that is being tested (Cinder, Nova, etc.).
+
+api_action = the policy action that is being tested. Examples:
+
+* volume:create
+* os_compute_api:servers:start
+* add_image
+
+allowed_role = the ``oslo.policy`` role that is allowed to perform the API.
+
+Implementation
+--------------
+
+.. automodule:: patrole_tempest_plugin.requirements_authority
+ :members:
+ :undoc-members:
diff --git a/doc/source/index.rst b/doc/source/index.rst
index d964845..8368262 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -47,7 +47,9 @@
framework/overview
framework/rbac_validation
+ framework/rbac_authority
framework/policy_authority
+ framework/requirements_authority
framework/rbac_utils
Indices and tables
diff --git a/etc/patrole.conf.sample b/etc/patrole.conf.sample
index 518d38a..8e7931b 100644
--- a/etc/patrole.conf.sample
+++ b/etc/patrole.conf.sample
@@ -29,9 +29,10 @@
#
# This option determines whether Patrole should run against a
# ``custom_requirements_file`` which defines RBAC requirements. The
-# purpose of setting this flag to True is to verify that RBAC policy
+# purpose of setting this flag to ``True`` is to verify that RBAC
+# policy
# is in accordance to requirements. The idea is that the
-# ``custom_requirements_file`` perfectly defines what the RBAC
+# ``custom_requirements_file`` precisely defines what the RBAC
# requirements are.
#
# Here are the possible outcomes when running the Patrole tests
@@ -57,28 +58,32 @@
# file must be located on the same host that Patrole runs on. The YAML
# file should be written as follows:
#
-# ```
-# <service_foo>:
-# <api_action_x>:
-# - <allowed_role_a>
-# - <allowed_role_b>
-# - <allowed_role_c>
-# <api_action_y>:
-# - <allowed_role_d>
-# - <allowed_role_e>
-# <service_bar>:
-# <api_action_z>:
-# - <allowed_role_b>
-# ```
+# .. code-block:: yaml
+#
+# <service_foo>:
+# <api_action_a>:
+# - <allowed_role_1>
+# - <allowed_role_2>
+# - <allowed_role_3>
+# <api_action_b>:
+# - <allowed_role_2>
+# - <allowed_role_4>
+# <service_bar>:
+# <api_action_c>:
+# - <allowed_role_3>
#
# Where:
#
-# service = the service that is being tested (Cinder, Nova, etc.)
+# service = the service that is being tested (Cinder, Nova, etc.).
+#
# api_action = the policy action that is being tested. Examples:
-# - volume:create
-# - os_compute_api:servers:start
-# - add_image
-# allowed_role = the Keystone role that is allowed to perform the API.
+#
+# * volume:create
+# * os_compute_api:servers:start
+# * add_image
+#
+# allowed_role = the ``oslo.policy`` role that is allowed to perform
+# the API.
# (string value)
#custom_requirements_file = <None>
diff --git a/patrole_tempest_plugin/README.rst b/patrole_tempest_plugin/README.rst
deleted file mode 100644
index d678422..0000000
--- a/patrole_tempest_plugin/README.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-==============================
-Tempest Integration of Patrole
-==============================
-
-This directory contains Tempest tests to cover the Patrole project.
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index f379859..ee7a6c5 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -42,9 +42,9 @@
help="""
This option determines whether Patrole should run against a
``custom_requirements_file`` which defines RBAC requirements. The
-purpose of setting this flag to True is to verify that RBAC policy
+purpose of setting this flag to ``True`` is to verify that RBAC policy
is in accordance to requirements. The idea is that the
-``custom_requirements_file`` perfectly defines what the RBAC requirements are.
+``custom_requirements_file`` precisely defines what the RBAC requirements are.
Here are the possible outcomes when running the Patrole tests against
a ``custom_requirements_file``:
@@ -67,28 +67,31 @@
file must be located on the same host that Patrole runs on. The YAML
file should be written as follows:
-```
-<service_foo>:
- <api_action_x>:
- - <allowed_role_a>
- - <allowed_role_b>
- - <allowed_role_c>
- <api_action_y>:
- - <allowed_role_d>
- - <allowed_role_e>
-<service_bar>:
- <api_action_z>:
- - <allowed_role_b>
-```
+.. code-block:: yaml
+
+ <service_foo>:
+ <api_action_a>:
+ - <allowed_role_1>
+ - <allowed_role_2>
+ - <allowed_role_3>
+ <api_action_b>:
+ - <allowed_role_2>
+ - <allowed_role_4>
+ <service_bar>:
+ <api_action_c>:
+ - <allowed_role_3>
Where:
-service = the service that is being tested (Cinder, Nova, etc.)
+service = the service that is being tested (Cinder, Nova, etc.).
+
api_action = the policy action that is being tested. Examples:
- - volume:create
- - os_compute_api:servers:start
- - add_image
-allowed_role = the Keystone role that is allowed to perform the API.
+
+* volume:create
+* os_compute_api:servers:start
+* add_image
+
+allowed_role = the ``oslo.policy`` role that is allowed to perform the API.
""")
]
diff --git a/patrole_tempest_plugin/policy_authority.py b/patrole_tempest_plugin/policy_authority.py
index 9499bf6..b813f88 100644
--- a/patrole_tempest_plugin/policy_authority.py
+++ b/patrole_tempest_plugin/policy_authority.py
@@ -156,9 +156,9 @@
def allowed(self, rule_name, role):
"""Checks if a given rule in a policy is allowed with given role.
- :param string rule_name: Rule to be checked using ``oslo.policy``.
- :param bool is_admin: Whether admin context is used.
- :raises RbacParsingException: If `rule_name`` does not exist in the
+ :param string rule_name: Policy name to pass to``oslo.policy``.
+ :param string role: Role to validate for authorization.
+ :raises RbacParsingException: If ``rule_name`` does not exist in the
cloud (in policy file or among registered in-code policy defaults).
"""
is_admin_context = self._is_admin_context(role)
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 2ef88ca..6c40aa1 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -147,18 +147,25 @@
test_obj.os_primary.auth_provider.set_auth()
def _get_roles_by_name(self):
- available_roles = self.admin_roles_client.list_roles()
- admin_role_id = rbac_role_id = None
+ available_roles = self.admin_roles_client.list_roles()['roles']
+ role_map = {r['name']: r['id'] for r in available_roles}
+ LOG.debug('Available roles: %s', list(role_map.keys()))
- for role in available_roles['roles']:
- if role['name'] == CONF.patrole.rbac_test_role:
- rbac_role_id = role['id']
- if role['name'] == CONF.identity.admin_role:
- admin_role_id = role['id']
+ admin_role_id = role_map.get(CONF.identity.admin_role)
+ rbac_role_id = role_map.get(CONF.patrole.rbac_test_role)
if not all([admin_role_id, rbac_role_id]):
- msg = ("Roles defined by `[patrole] rbac_test_role` and "
- "`[identity] admin_role` must be defined in the system.")
+ missing_roles = []
+ msg = ("Could not find `[patrole] rbac_test_role` or "
+ "`[identity] admin_role`, both of which are required for "
+ "RBAC testing.")
+ if not admin_role_id:
+ missing_roles.append(CONF.identity.admin_role)
+ if not rbac_role_id:
+ missing_roles.append(CONF.patrole.rbac_test_role)
+ msg += " Following roles were not found: %s." % (
+ ", ".join(missing_roles))
+ msg += " Available roles: %s." % ", ".join(list(role_map.keys()))
raise rbac_exceptions.RbacResourceSetupFailed(msg)
self.admin_role_id = admin_role_id
@@ -226,4 +233,6 @@
:returns: True if ``rbac_test_role`` is the admin role.
"""
+ # TODO(felipemonteiro): Make this more robust via a context is admin
+ # lookup.
return CONF.patrole.rbac_test_role == CONF.identity.admin_role
diff --git a/patrole_tempest_plugin/requirements_authority.py b/patrole_tempest_plugin/requirements_authority.py
index 683a7eb..75df9f4 100644
--- a/patrole_tempest_plugin/requirements_authority.py
+++ b/patrole_tempest_plugin/requirements_authority.py
@@ -16,14 +16,17 @@
from oslo_log import log as logging
+from tempest import config
from tempest.lib import exceptions
from patrole_tempest_plugin.rbac_authority import RbacAuthority
+CONF = config.CONF
LOG = logging.getLogger(__name__)
class RequirementsParser(object):
+ """A class that parses a custom requirements file."""
_inner = None
class Inner(object):
@@ -40,6 +43,27 @@
@staticmethod
def parse(component):
+ """Parses a requirements file with the following format:
+
+ .. code-block:: yaml
+
+ <service_foo>:
+ <api_action_a>:
+ - <allowed_role_1>
+ - <allowed_role_2>
+ - <allowed_role_3>
+ <api_action_b>:
+ - <allowed_role_2>
+ - <allowed_role_4>
+ <service_bar>:
+ <api_action_c>:
+ - <allowed_role_3>
+
+ :param str component: Name of the OpenStack service to be validated.
+ :returns: The dictionary that maps each policy action to the list
+ of allowed roles, for the given ``component``.
+ :rtype: dict
+ """
try:
for section in RequirementsParser.Inner._rbac_map:
if component in section:
@@ -51,13 +75,39 @@
class RequirementsAuthority(RbacAuthority):
+ """A class that uses a custom requirements file to validate RBAC."""
+
def __init__(self, filepath=None, component=None):
- if filepath is not None and component is not None:
+ """This class can be used to achieve a requirements-driven approach to
+ validating an OpenStack cloud's RBAC implementation. Using this
+ approach, Patrole computes expected test results by performing lookups
+ against a custom requirements file which precisely defines the cloud's
+ RBAC requirements.
+
+ :param str filepath: Path where the custom requirements file lives.
+ Defaults to ``[patrole].custom_requirements_file``.
+ :param str component: Name of the OpenStack service to be validated.
+ """
+ filepath = filepath or CONF.patrole.custom_requirements_file
+
+ if component is not None:
self.roles_dict = RequirementsParser(filepath).parse(component)
else:
self.roles_dict = None
def allowed(self, rule_name, role):
+ """Checks if a given rule in a policy is allowed with given role.
+
+ :param string rule_name: Rule to be checked using provided requirements
+ file specified by ``[patrole].custom_requirements_file``. Must be
+ a key present in this file, under the appropriate component.
+ :param string role: Role to validate against custom requirements file.
+ :returns: True if ``role`` is allowed to perform ``rule_name``, else
+ False.
+ :rtype: bool
+ :raises KeyError: If ``rule_name`` does not exist among the keyed
+ policy names in the custom requirements file.
+ """
if self.roles_dict is None:
raise exceptions.InvalidConfiguration(
"Roles dictionary parsed from requirements YAML file is "
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 adb5a6c..1fe52e9 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
@@ -33,6 +33,14 @@
class ServerActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
+ # admin credentials used for waiters which invokes a show API call
+ credentials = ['primary', 'admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerActionsRbacTest, cls).setup_clients()
+ cls.admin_servers_client = cls.os_admin.servers_client
+
@classmethod
def resource_setup(cls):
super(ServerActionsRbacTest, cls).resource_setup()
@@ -58,17 +66,24 @@
def _stop_server(self):
self.servers_client.stop_server(self.server_id)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'SHUTOFF')
+ self.admin_servers_client, self.server_id, 'SHUTOFF')
def _resize_server(self, flavor):
+ status = self.admin_servers_client. \
+ show_server(self.server_id)['server']['status']
+ if status == 'RESIZED':
+ return
self.servers_client.resize_server(self.server_id, flavor)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'VERIFY_RESIZE')
+ self.admin_servers_client, self.server_id, 'VERIFY_RESIZE')
def _confirm_resize_server(self):
- self.servers_client.confirm_resize_server(self.server_id)
+ status = self.admin_servers_client. \
+ show_server(self.server_id)['server']['status']
+ if status == 'VERIFY_RESIZE':
+ self.servers_client.confirm_resize_server(self.server_id)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'ACTIVE')
+ self.admin_servers_client, self.server_id, 'ACTIVE')
def _shelve_server(self):
self.servers_client.shelve_server(self.server_id)
@@ -77,12 +92,13 @@
self.server_id)
offload_time = CONF.compute.shelved_offload_time
if offload_time >= 0:
- waiters.wait_for_server_status(self.servers_client,
+ waiters.wait_for_server_status(self.admin_servers_client,
self.server_id,
'SHELVED_OFFLOADED',
extra_timeout=offload_time)
else:
- waiters.wait_for_server_status(self.servers_client, self.server_id,
+ waiters.wait_for_server_status(self.admin_servers_client,
+ self.server_id,
'SHELVED')
def _pause_server(self):
@@ -91,7 +107,7 @@
self.servers_client.unpause_server,
self.server_id)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'PAUSED')
+ self.admin_servers_client, self.server_id, 'PAUSED')
def _cleanup_server_actions(self, function, server_id, **kwargs):
server = self.servers_client.show_server(server_id)['server']
@@ -179,6 +195,7 @@
self._resize_server(self.flavor_ref_alt)
self.addCleanup(self._confirm_resize_server)
self.addCleanup(self._resize_server, self.flavor_ref)
+ self.addCleanup(self._confirm_resize_server)
with self.rbac_utils.override_role(self):
self._confirm_resize_server()
@@ -276,6 +293,7 @@
self.compute_images_client.delete_image, image['id'])
@decorators.idempotent_id('9fdd4630-731c-4f7c-bce5-69fa3792c52a')
+ @decorators.attr(type='slow')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
'Snapshotting not available, backup not possible.')
@utils.services('image')
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 58a829d..13faca1 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
@@ -570,8 +570,7 @@
@classmethod
def resource_setup(cls):
def _cleanup_ports(network_id):
- ports = cls.ports_client.\
- list_ports(network_id=network_id)['ports']
+ ports = cls.ports_client.list_ports(network_id=network_id)['ports']
for port in ports:
test_utils.call_and_ignore_notfound_exc(
cls.ports_client.delete_port,
@@ -666,6 +665,9 @@
self.interfaces_client, self.server['id'],
interface['port_id'], 'ACTIVE')
self.addCleanup(
+ waiters.wait_for_interface_detach, self.interfaces_client,
+ self.server['id'], interface['port_id'])
+ self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.interfaces_client.delete_interface,
self.server['id'], interface['port_id'])
@@ -684,6 +686,8 @@
with self.rbac_utils.override_role(self):
self.interfaces_client.delete_interface(self.server['id'],
interface['port_id'])
+ waiters.wait_for_interface_detach(
+ self.interfaces_client, self.server['id'], interface['port_id'])
@decorators.idempotent_id('6886d360-0d86-4760-b1a3-882d81fbebcc')
@utils.requires_ext(extension='os-ips', service='compute')
@@ -723,47 +727,16 @@
if interfaces:
network_id = interfaces[0]['net_id']
else:
- network_id = self.interfaces_client.create_interface(
- self.server['id'])['interfaceAttachment']['net_id']
+ interface = self.interfaces_client.create_interface(
+ self.server['id'])['interfaceAttachment']
+ network_id = interface['net_id']
+ self.addCleanup(
+ waiters.wait_for_interface_detach, self.interfaces_client,
+ self.server['id'], interface['port_id'])
+ self.addCleanup(
+ self.interfaces_client.delete_interface,
+ self.server['id'], interface['port_id'])
with self.rbac_utils.override_role(self):
self.servers_client.add_fixed_ip(self.server['id'],
networkId=network_id)
-
-
-class VirtualInterfacesRbacTest(rbac_base.BaseV2ComputeRbacTest):
- # The compute os-virtual-interfaces API is deprecated from the Microversion
- # 2.44 onward. For more information, see:
- # https://developer.openstack.org/api-ref/compute/#servers-virtual-interfaces-servers-os-virtual-interfaces-deprecated
- max_microversion = '2.43'
-
- @classmethod
- def setup_credentials(cls):
- # This test needs a network and a subnet
- cls.set_network_resources(network=True, subnet=True)
- super(VirtualInterfacesRbacTest, cls).setup_credentials()
-
- @classmethod
- def resource_setup(cls):
- super(VirtualInterfacesRbacTest, cls).resource_setup()
- cls.server = cls.create_test_server(wait_until='ACTIVE')
-
- @rbac_rule_validation.action(
- service="nova",
- rule="os_compute_api:os-virtual-interfaces")
- @decorators.idempotent_id('fc719ae3-0f73-4689-8378-1b841f0f2818')
- def test_list_virtual_interfaces(self):
- """Test list virtual interfaces, part of os-virtual-interfaces.
-
- If Neutron is available, then call the API and expect it to fail
- with a 400 BadRequest (policy enforcement is done before that happens).
- """
- with self.rbac_utils.override_role(self):
- if CONF.service_available.neutron:
- msg = ("Listing virtual interfaces is not supported by this "
- "cloud.")
- with self.assertRaisesRegex(lib_exc.BadRequest, msg):
- self.servers_client.list_virtual_interfaces(
- self.server['id'])
- else:
- self.servers_client.list_virtual_interfaces(self.server['id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
index b803fe3..a510d1e 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
@@ -25,6 +25,10 @@
CONF = config.CONF
+# FIXME(felipemonteiro): `@decorators.attr(type='slow')` are added to tests
+# below to in effect cause the tests to be non-voting in Zuul due to a high
+# rate of spurious failures related to volume attachments. This will be
+# revisited at a later date.
class ServerVolumeAttachmentRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
@@ -53,6 +57,7 @@
with self.rbac_utils.override_role(self):
self.servers_client.list_volume_attachments(self.server['id'])
+ @decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:os-volumes-attachments:create")
@@ -61,6 +66,7 @@
with self.rbac_utils.override_role(self):
self.attach_volume(self.server, self.volume)
+ @decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:os-volumes-attachments:show")
diff --git a/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
new file mode 100644
index 0000000..ae77a34
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
@@ -0,0 +1,64 @@
+# 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 import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.compute import rbac_base
+
+CONF = config.CONF
+
+
+# TODO(rb560u): Remove this test class once the nova queens branch goes into
+# extended maintenance mode.
+class VirtualInterfacesRbacTest(rbac_base.BaseV2ComputeRbacTest):
+ # The compute os-virtual-interfaces API is deprecated from the Microversion
+ # 2.44 onward. For more information, see:
+ # https://developer.openstack.org/api-ref/compute/#servers-virtual-interfaces-servers-os-virtual-interfaces-deprecated
+ depends_on_nova_network = True
+ max_microversion = '2.43'
+
+ @classmethod
+ def setup_credentials(cls):
+ # This test needs a network and a subnet
+ cls.set_network_resources(network=True, subnet=True)
+ super(VirtualInterfacesRbacTest, cls).setup_credentials()
+
+ @classmethod
+ def resource_setup(cls):
+ super(VirtualInterfacesRbacTest, cls).resource_setup()
+ cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-virtual-interfaces")
+ @decorators.idempotent_id('fc719ae3-0f73-4689-8378-1b841f0f2818')
+ def test_list_virtual_interfaces(self):
+ """Test list virtual interfaces, part of os-virtual-interfaces.
+
+ If Neutron is available, then call the API and expect it to fail
+ with a 400 BadRequest (policy enforcement is done before that happens).
+ """
+ with self.rbac_utils.override_role(self):
+ if CONF.service_available.neutron:
+ msg = ("Listing virtual interfaces is not supported by this "
+ "cloud.")
+ with self.assertRaisesRegex(lib_exc.BadRequest, msg):
+ self.servers_client.list_virtual_interfaces(
+ self.server['id'])
+ else:
+ self.servers_client.list_virtual_interfaces(self.server['id'])
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index 90fa6aa..91b3d1e 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -118,6 +118,8 @@
@classmethod
def setup_clients(cls):
super(BaseIdentityV3RbacTest, cls).setup_clients()
+ cls.application_credentials_client = \
+ cls.os_primary.application_credentials_client
cls.creds_client = cls.os_primary.credentials_client
cls.consumers_client = cls.os_primary.oauth_consumers_client
cls.domains_client = cls.os_primary.domains_client
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py
new file mode 100644
index 0000000..c7a6033
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_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 import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.identity import rbac_base
+
+
+CONF = config.CONF
+
+
+class ApplicationCredentialsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(ApplicationCredentialsV3RbacTest, cls).skip_checks()
+ if not CONF.identity_feature_enabled.application_credentials:
+ raise cls.skipException("Application credentials are not available"
+ " in this environment")
+
+ @classmethod
+ def resource_setup(cls):
+ super(ApplicationCredentialsV3RbacTest, cls).resource_setup()
+ cls.user_id = cls.os_primary.credentials.user_id
+
+ def _create_application_credential(self, name=None, **kwargs):
+ name = name or data_utils.rand_name('application_credential')
+ application_credential = (
+ self.application_credentials_client.create_application_credential(
+ self.user_id, name=name, **kwargs))['application_credential']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.application_credentials_client.delete_application_credential,
+ self.user_id,
+ application_credential['id'])
+ return application_credential
+
+ @decorators.idempotent_id('b53bee14-e9df-4929-b257-6def76c12e4d')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:create_application_credential")
+ def test_create_application_credential(self):
+ with self.rbac_utils.override_role(self):
+ self._create_application_credential()
+
+ @decorators.idempotent_id('58b3c3a0-5ad0-44f7-8da7-0736f71f7168')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:list_application_credentials")
+ def test_list_application_credentials(self):
+ with self.rbac_utils.override_role(self):
+ self.application_credentials_client.list_application_credentials(
+ user_id=self.user_id)
+
+ @decorators.idempotent_id('d7b13968-a8a6-47fd-8e1d-7cc7f565c7f8')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:get_application_credential")
+ def test_show_application_credential(self):
+ app_cred = self._create_application_credential()
+ with self.rbac_utils.override_role(self):
+ self.application_credentials_client.show_application_credential(
+ user_id=self.user_id, application_credential_id=app_cred['id'])
+
+ @decorators.idempotent_id('521b7c0f-1dd5-47a6-ae95-95c0323d7735')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:delete_application_credential")
+ def test_delete_application_credential(self):
+ app_cred = self._create_application_credential()
+ with self.rbac_utils.override_role(self):
+ self.application_credentials_client.delete_application_credential(
+ user_id=self.user_id, application_credential_id=app_cred['id'])
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 84ce2c7..932683d 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -294,63 +294,6 @@
with self.rbac_utils.override_role(self):
self.networks_client.delete_network(network['id'])
- @rbac_rule_validation.action(service="neutron",
- rule="create_subnet")
- @decorators.idempotent_id('44f42aaf-8a9a-4678-868a-b8fe82689554')
- def test_create_subnet(self):
-
- """Create Subnet Test
-
- RBAC test for the neutron create_subnet policy
- """
- network = self._create_network()
-
- with self.rbac_utils.override_role(self):
- self.create_subnet(network, enable_dhcp=False)
-
- @rbac_rule_validation.action(service="neutron",
- rule="get_subnet")
- @decorators.idempotent_id('eb88be84-2465-482b-a40b-5201acb41152')
- def test_show_subnet(self):
-
- """Show Subnet Test
-
- RBAC test for the neutron get_subnet policy
- """
- with self.rbac_utils.override_role(self):
- self.subnets_client.show_subnet(self.subnet['id'])
-
- @rbac_rule_validation.action(service="neutron",
- rule="update_subnet")
- @decorators.idempotent_id('1bfeaec5-83b9-4140-8138-93a0a9d04cee')
- def test_update_subnet(self):
-
- """Update Subnet Test
-
- RBAC test for the neutron update_subnet policy
- """
- updated_name = data_utils.rand_name(
- self.__class__.__name__ + '-Network')
-
- with self.rbac_utils.override_role(self):
- self.subnets_client.update_subnet(self.subnet['id'],
- name=updated_name)
-
- @rbac_rule_validation.action(service="neutron",
- rule="delete_subnet")
- @decorators.idempotent_id('1ad1400f-dc84-4edb-9674-b33bbfb0d3e3')
- def test_delete_subnet(self):
-
- """Delete Subnet Test
-
- RBAC test for the neutron delete_subnet policy
- """
- network = self._create_network()
- subnet = self.create_subnet(network, enable_dhcp=False)
-
- with self.rbac_utils.override_role(self):
- self.subnets_client.delete_subnet(subnet['id'])
-
@utils.requires_ext(extension='dhcp_agent_scheduler', service='network')
@decorators.idempotent_id('b524f19f-fbb4-4d11-a85d-03bfae17bf0e')
@rbac_rule_validation.action(service="neutron",
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
index d69f1e9..a8c1727 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
@@ -73,6 +73,7 @@
'"volume_extension:volume_actions:attach" must be available in the '
'cloud.')
@utils.services('compute')
+ @decorators.attr(type='slow')
@rbac_rule_validation.action(
service="cinder",
rule="volume_extension:volume_actions:attach")
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index 5e730d3..4937318 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -36,17 +36,15 @@
def test_override_role_with_missing_admin_role(self):
self.rbac_utils.set_roles('member')
- error_re = (
- 'Roles defined by `\[patrole\] rbac_test_role` and `\[identity\] '
- 'admin_role` must be defined in the system.')
+ error_re = (".*Following roles were not found: admin. Available "
+ "roles: member.")
self.assertRaisesRegex(rbac_exceptions.RbacResourceSetupFailed,
error_re, self.rbac_utils.override_role)
def test_override_role_with_missing_rbac_role(self):
self.rbac_utils.set_roles('admin')
- error_re = (
- 'Roles defined by `\[patrole\] rbac_test_role` and `\[identity\] '
- 'admin_role` must be defined in the system.')
+ error_re = (".*Following roles were not found: member. Available "
+ "roles: admin.")
self.assertRaisesRegex(rbac_exceptions.RbacResourceSetupFailed,
error_re, self.rbac_utils.override_role)