Use get_service_clients framework with basic Secure RBAC

The ironic tempest plugin was an early plugin and manually
invoked override plugin clients and then attached them in
the setup_clients method. However, the newer format is to
use get_service_clients, which creates and attach client
classes using the prepared credentials supplied by the
credentials attribute on the test classes.

In order to support even the most basic testing handling
and testing of Scope Enforcement as part of Secure RBAC,
then the we need to leverage the newer (last 3-4 years)
model of instantiating and leveraging clients in tempest.
This is because we need to be able to get a system scoped
admin token to be able to test actions as a system scoped
admin user. Not to be confused with "admin", which is
project scoped.

This newer style of client support does necessitate some
legacy style or direct client invocations to be retooled
so they do not attempt to directly invoke without the
required context.

Additionally, to support even the most basic handling of
the Secure RBAC's effort, we need to be able to know
when to leverage *and* then leverage that client.
We do that through the enforce_scope parameter
in upstream tempest.

Depends-On: https://review.opendev.org/c/openstack/tempest/+/798130
Change-Id: I5188fc756f1b524e9d1b32ef0474e29a9cf90b57
This commit is contained in:
Julia Kreger 2021-06-22 10:27:56 -07:00
parent 12236ec3bb
commit 3a07c4d5ae
11 changed files with 97 additions and 37 deletions

View File

@ -18,6 +18,8 @@ from oslo_config import cfg
from tempest import config # noqa
# NOTE(TheJulia): The following options are loaded into a tempest
# plugin configuration option via plugin.py.
ironic_service_option = cfg.BoolOpt('ironic',
default=False,
help='Whether or not ironic is expected '
@ -28,6 +30,18 @@ inspector_service_option = cfg.BoolOpt("ironic_inspector",
help="Whether or not ironic-inspector "
"is expected to be available")
ironic_scope_enforcement = cfg.BoolOpt('ironic',
default=False,
help='Wheter or not ironic is '
'exepcted to enforce auth '
'scope.')
inspector_scope_enforcement = cfg.BoolOpt('ironic_inspector',
default=False,
help='Whether or not '
'ironic-inspector is expected '
'to enforce auth scope.')
baremetal_group = cfg.OptGroup(name='baremetal',
title='Baremetal provisioning service options',
help='When enabling baremetal tests, Nova '
@ -38,6 +52,8 @@ baremetal_group = cfg.OptGroup(name='baremetal',
'live_migration, pause, rescue, resize, '
'shelve, snapshot, and suspend')
# The bulk of the embedded configuration is below.
baremetal_introspection_group = cfg.OptGroup(
name="baremetal_introspection",
title="Baremetal introspection service options",

View File

@ -42,7 +42,7 @@ LOG = log.getLogger(__name__)
class ScenarioTest(tempest.test.BaseTestCase):
"""Base class for scenario tests. Uses tempest own clients. """
credentials = ['primary']
credentials = ['primary', 'admin', 'system_admin']
@classmethod
def setup_clients(cls):
@ -92,7 +92,7 @@ class ScenarioTest(tempest.test.BaseTestCase):
def _create_port(self, network_id, client=None, namestart='port-quotatest',
**kwargs):
if not client:
client = self.ports_client
client = self.os_primary.ports_client
name = data_utils.rand_name(namestart)
result = client.create_port(
name=name,
@ -106,7 +106,7 @@ class ScenarioTest(tempest.test.BaseTestCase):
def create_keypair(self, client=None):
if not client:
client = self.keypairs_client
client = self.os_primary.keypairs_client
name = data_utils.rand_name(self.__class__.__name__)
# We don't need to create a keypair by pubkey in scenario
body = client.create_keypair(name=name)
@ -254,12 +254,13 @@ class ScenarioTest(tempest.test.BaseTestCase):
if not CONF.compute_feature_enabled.console_output:
LOG.debug('Console output not supported, cannot log')
return
client = self.os_primary.servers_client
if not servers:
servers = self.servers_client.list_servers()
servers = client.list_servers()
servers = servers['servers']
for server in servers:
try:
console_output = self.servers_client.get_console_output(
console_output = client.get_console_output(
server['id'])['output']
LOG.debug('Console output for %s\nbody=\n%s',
server['id'], console_output)
@ -277,12 +278,12 @@ class ScenarioTest(tempest.test.BaseTestCase):
LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
server_id, image, preserve_ephemeral)
self.servers_client.rebuild_server(
self.os_primary.servers_client.rebuild_server(
server_id=server_id, image_ref=image,
preserve_ephemeral=preserve_ephemeral,
**rebuild_kwargs)
if wait:
waiters.wait_for_server_status(self.servers_client,
waiters.wait_for_server_status(self.os_primary.servers_client,
server_id, 'ACTIVE')
def ping_ip_address(self, ip_address, should_succeed=True,
@ -357,12 +358,13 @@ class ScenarioTest(tempest.test.BaseTestCase):
if not pool_name:
pool_name = CONF.network.floating_network_name
floating_ip = (self.compute_floating_ips_client.
client = self.os_primary.compute_floating_ips_client
floating_ip = (client.
create_floating_ip(pool=pool_name)['floating_ip'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.compute_floating_ips_client.delete_floating_ip,
client.delete_floating_ip,
floating_ip['id'])
self.compute_floating_ips_client.associate_floating_ip_to_server(
client.associate_floating_ip_to_server(
floating_ip['ip'], thing['id'])
return floating_ip
@ -423,7 +425,7 @@ class ScenarioTest(tempest.test.BaseTestCase):
routes traffic to the public network.
"""
if not client:
client = self.routers_client
client = self.os_primary.routers_client
if not tenant_id:
tenant_id = client.tenant_id
router_id = CONF.network.public_router_id
@ -443,7 +445,7 @@ class ScenarioTest(tempest.test.BaseTestCase):
def _create_router(self, client=None, tenant_id=None,
namestart='router-smoke'):
if not client:
client = self.routers_client
client = self.os_primary.routers_client
if not tenant_id:
tenant_id = client.tenant_id
name = data_utils.rand_name(namestart)
@ -470,7 +472,7 @@ class NetworkScenarioTest(ScenarioTest):
"""
credentials = ['primary', 'admin']
credentials = ['primary', 'admin', 'system_admin']
@classmethod
def skip_checks(cls):
@ -483,9 +485,9 @@ class NetworkScenarioTest(ScenarioTest):
namestart='network-smoke-',
port_security_enabled=True):
if not networks_client:
networks_client = self.networks_client
networks_client = self.os_primary.networks_client
if not tenant_id:
tenant_id = networks_client.tenant_id
tenant_id = self.os_primary.networks_client.tenant_id
name = data_utils.rand_name(namestart)
network_kwargs = dict(name=name, tenant_id=tenant_id)
# Neutron disables port security by default so we have to check the
@ -542,7 +544,7 @@ class NetworkScenarioTest(ScenarioTest):
if not external_network_id:
external_network_id = CONF.network.public_network_id
if not client:
client = self.floating_ips_client
client = self.os_primary.floating_ips_client
if not port_id:
port_id, ip4 = self._get_server_port_id_and_ip4(thing)
else:

View File

@ -43,8 +43,43 @@ class IronicTempestPlugin(plugins.TempestPlugin):
group='service_available')
conf.register_opt(project_config.inspector_service_option,
group='service_available')
conf.register_opt(project_config.ironic_scope_enforcement,
group='enforce_scope')
conf.register_opt(project_config.inspector_scope_enforcement,
group='enforce_scope')
for group, option in _opts:
config.register_opt_group(conf, group, option)
def get_opt_lists(self):
return [(group.name, option) for group, option in _opts]
def get_service_clients(self):
ironic_config = config.service_client_config(
project_config.baremetal_group.name
)
baremetal_client = {
'name': 'baremetal',
'service_version': 'baremetal.v1',
'module_path': 'ironic_tempest_plugin.services.baremetal.v1.'
'json.baremetal_client',
'client_names': [
'BaremetalClient',
],
}
baremetal_client.update(ironic_config)
inspector_config = config.service_client_config(
project_config.baremetal_introspection_group.name
)
inspector_client = {
'name': 'introspection',
'service_version': 'baremetal_introspection.v1',
'module_path': 'ironic_tempest_plugin.services.'
'introspection_client',
'client_names': [
'BaremetalIntrospectionClient',
],
}
inspector_client.update(inspector_config)
return [baremetal_client, inspector_client]

View File

@ -17,7 +17,6 @@ from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
from ironic_tempest_plugin import clients
from ironic_tempest_plugin.common import waiters
from ironic_tempest_plugin.services.baremetal import base
from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
@ -58,7 +57,7 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
test.BaseTestCase):
"""Base class for Baremetal API tests."""
credentials = ['admin']
credentials = ['admin', 'system_admin']
@classmethod
def skip_checks(cls):
@ -86,12 +85,16 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
CONF.baremetal.min_microversion))
cls.services_microversion = {
CONF.baremetal.catalog_type: cls.request_microversion}
super(BaseBaremetalTest, cls).setup_credentials()
@classmethod
def setup_clients(cls):
super(BaseBaremetalTest, cls).setup_clients()
cls.client = clients.Manager().baremetal_client
if CONF.enforce_scope.ironic:
cls.client = cls.os_system_admin.baremetal.BaremetalClient()
else:
cls.client = cls.os_admin.baremetal.BaremetalClient()
@classmethod
def resource_setup(cls):

View File

@ -22,7 +22,6 @@ from tempest.lib.common import api_version_utils
from tempest.lib.common.utils.linux import remote_client
from tempest.lib import exceptions as lib_exc
from ironic_tempest_plugin import clients
from ironic_tempest_plugin.common import utils
from ironic_tempest_plugin.common import waiters as ironic_waiters
from ironic_tempest_plugin import manager
@ -74,7 +73,7 @@ class BaremetalProvisionStates(object):
class BaremetalScenarioTest(manager.ScenarioTest):
credentials = ['primary', 'admin']
credentials = ['primary', 'admin', 'system_admin']
min_microversion = None
max_microversion = api_version_utils.LATEST_MICROVERSION
@ -93,8 +92,11 @@ class BaremetalScenarioTest(manager.ScenarioTest):
@classmethod
def setup_clients(cls):
super(BaremetalScenarioTest, cls).setup_clients()
cls.baremetal_client = clients.Manager().baremetal_client
if CONF.enforce_scope.ironic:
client = cls.os_system_admin.baremetal.BaremetalClient()
else:
client = cls.os_admin.baremetal.BaremetalClient()
cls.baremetal_client = client
@classmethod
def resource_setup(cls):
@ -172,7 +174,7 @@ class BaremetalScenarioTest(manager.ScenarioTest):
def boot_instance(self, clients=None, keypair=None,
net_id=None, fixed_ip=None, **create_kwargs):
if clients is None:
servers_client = self.servers_client
servers_client = self.os_primary.servers_client
else:
servers_client = clients.servers_client
if keypair is None:
@ -222,7 +224,7 @@ class BaremetalScenarioTest(manager.ScenarioTest):
def terminate_instance(self, instance, servers_client=None):
if servers_client is None:
servers_client = self.servers_client
servers_client = self.os_primary.servers_client
node = self.get_node(instance_id=instance['id'])
servers_client.delete_server(instance['id'])
@ -239,7 +241,7 @@ class BaremetalScenarioTest(manager.ScenarioTest):
servers_client=None):
"""Rescue the instance, verify we can ping and SSH."""
if servers_client is None:
servers_client = self.servers_client
servers_client = self.os_primary.servers_client
rescuing_instance = servers_client.rescue_server(instance['id'])
rescue_password = rescuing_instance['adminPass']
@ -265,8 +267,8 @@ class BaremetalScenarioTest(manager.ScenarioTest):
def unrescue_instance(self, instance, node, server_ip,
servers_client=None):
if servers_client is None:
servers_client = self.servers_client
self.servers_client.unrescue_server(instance['id'])
servers_client = self.os_primary.servers_client
self.os_primary.servers_client.unrescue_server(instance['id'])
self.wait_provisioning_state(
node['uuid'],
BaremetalProvisionStates.ACTIVE,

View File

@ -31,7 +31,7 @@ CONF = config.CONF
class BaremetalStandaloneManager(bm.BaremetalScenarioTest,
manager.NetworkScenarioTest):
credentials = ['primary', 'admin']
credentials = ['primary', 'admin', 'system_admin']
# NOTE(vsaienko): Standalone tests are using v1/node/<node_ident>/vifs to
# attach VIF to a node.
min_microversion = '1.28'

View File

@ -23,7 +23,6 @@ from tempest.lib import exceptions as lib_exc
import ironic_tempest_plugin
from ironic_tempest_plugin import exceptions
from ironic_tempest_plugin.services import introspection_client
from ironic_tempest_plugin.tests.api.admin.api_microversion_fixture import \
APIMicroversionFixture as IronicMicroversionFixture
from ironic_tempest_plugin.tests.scenario.baremetal_manager import \
@ -40,7 +39,7 @@ class InspectorScenarioTest(BaremetalScenarioTest):
wait_provisioning_state_interval = 15
credentials = ['primary', 'admin']
credentials = ['admin', 'system_admin', 'primary']
ironic_api_version = LATEST_MICROVERSION
@ -53,8 +52,11 @@ class InspectorScenarioTest(BaremetalScenarioTest):
@classmethod
def setup_clients(cls):
super(InspectorScenarioTest, cls).setup_clients()
inspector_manager = introspection_client.Manager()
cls.introspection_client = inspector_manager.introspection_client
if CONF.enforce_scope.ironic_inspector:
oscli = cls.os_system_admin.introspection
else:
oscli = cls.os_admin.introspection
cls.introspection_client = oscli.BaremetalIntrospectionClient()
def setUp(self):
super(InspectorScenarioTest, self).setUp()

View File

@ -45,7 +45,7 @@ class BaremetalBasicOps(baremetal_manager.BaremetalScenarioTest):
expected state transitions
"""
credentials = ['primary', 'admin']
credentials = ['primary', 'admin', 'system_admin']
TEST_RESCUE_MODE = False
image_ref = None

View File

@ -34,7 +34,7 @@ class BaremetalBFV(baremetal_manager.BaremetalScenarioTest):
* Delete instance
"""
credentials = ['primary', 'admin']
credentials = ['primary', 'admin', 'system_admin']
min_microversion = '1.32'

View File

@ -36,7 +36,7 @@ class BaremetalMultitenancy(baremetal_manager.BaremetalScenarioTest,
* Delete both instances
"""
credentials = ['primary', 'alt', 'admin']
credentials = ['primary', 'alt', 'admin', 'system_admin']
@classmethod
def skip_checks(cls):

View File

@ -36,7 +36,7 @@ class BaremetalSingleTenant(baremetal_manager.BaremetalScenarioTest,
* Delete both instances
"""
credentials = ['primary', 'alt', 'admin']
credentials = ['primary', 'alt', 'admin', 'system_admin']
@classmethod
def skip_checks(cls):