blob: 59f2f9469d3277188355b9d50a4896bd7d236dc2 [file] [log] [blame]
Yuiko Takadab6527002015-12-07 11:49:12 +09001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
Takashi Kajinami6bddaab2022-05-10 00:58:56 +090013import functools
14
Julia Kregercda96d52023-01-20 08:05:41 -080015from oslo_log import log as logging
Yuiko Takadab6527002015-12-07 11:49:12 +090016from tempest import config
Yuiko Takadaff785002015-12-17 15:56:42 +090017from tempest.lib.common import api_version_utils
Lenny Verkhovsky88625042016-03-08 17:44:00 +020018from tempest.lib.common.utils import data_utils
19from tempest.lib import exceptions as lib_exc
Yuiko Takadab6527002015-12-07 11:49:12 +090020from tempest import test
Yuiko Takadab6527002015-12-07 11:49:12 +090021
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +020022from ironic_tempest_plugin.common import waiters
Mark Goddard6f2e72c2019-02-15 12:23:34 +000023from ironic_tempest_plugin.services.baremetal import base
Yuiko Takadaff785002015-12-17 15:56:42 +090024from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
Yuiko Takadab6527002015-12-07 11:49:12 +090025
Julia Kregercda96d52023-01-20 08:05:41 -080026
27LOG = logging.getLogger(__name__)
Yuiko Takadab6527002015-12-07 11:49:12 +090028CONF = config.CONF
29
30
31# NOTE(adam_g): The baremetal API tests exercise operations such as enroll
32# node, power on, power off, etc. Testing against real drivers (ie, IPMI)
33# will require passing driver-specific data to Tempest (addresses,
34# credentials, etc). Until then, only support testing against the fake driver,
35# which has no external dependencies.
Dmitry Tantsur893b1a92018-04-26 16:12:45 +020036SUPPORTED_DRIVERS = ['fake', 'fake-hardware']
Yuiko Takadab6527002015-12-07 11:49:12 +090037
38# NOTE(jroll): resources must be deleted in a specific order, this list
39# defines the resource types to clean up, and the correct order.
Mark Goddard6f2e72c2019-02-15 12:23:34 +000040RESOURCE_TYPES = ['port', 'portgroup', 'node', 'volume_connector',
41 'volume_target', 'chassis', 'deploy_template']
Yuiko Takadab6527002015-12-07 11:49:12 +090042
43
44def creates(resource):
45 """Decorator that adds resources to the appropriate cleanup list."""
46
47 def decorator(f):
Takashi Kajinami6bddaab2022-05-10 00:58:56 +090048 @functools.wraps(f)
Yuiko Takadab6527002015-12-07 11:49:12 +090049 def wrapper(cls, *args, **kwargs):
50 resp, body = f(cls, *args, **kwargs)
51
52 if 'uuid' in body:
53 cls.created_objects[resource].add(body['uuid'])
54
55 return resp, body
56 return wrapper
57 return decorator
58
59
Yuiko Takadaff785002015-12-17 15:56:42 +090060class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
61 test.BaseTestCase):
Yuiko Takadab6527002015-12-07 11:49:12 +090062 """Base class for Baremetal API tests."""
63
Julia Kreger3a07c4d2021-06-22 10:27:56 -070064 credentials = ['admin', 'system_admin']
Yuiko Takadab6527002015-12-07 11:49:12 +090065
66 @classmethod
67 def skip_checks(cls):
68 super(BaseBaremetalTest, cls).skip_checks()
Jim Rollenhagen1c11bdf2016-11-29 16:57:30 -050069 if not CONF.service_available.ironic:
70 raise cls.skipException('Ironic is not enabled.')
Yuiko Takadab6527002015-12-07 11:49:12 +090071 if CONF.baremetal.driver not in SUPPORTED_DRIVERS:
72 skip_msg = ('%s skipped as Ironic driver %s is not supported for '
73 'testing.' %
74 (cls.__name__, CONF.baremetal.driver))
75 raise cls.skipException(skip_msg)
76
Yuiko Takadaff785002015-12-17 15:56:42 +090077 cfg_min_version = CONF.baremetal.min_microversion
78 cfg_max_version = CONF.baremetal.max_microversion
Julia Kreger0eb9ae72024-03-26 08:47:26 -070079
80 # Check versions and skip based upon *configuration*
Yuiko Takadaff785002015-12-17 15:56:42 +090081 api_version_utils.check_skip_with_microversion(cls.min_microversion,
82 cls.max_microversion,
83 cfg_min_version,
84 cfg_max_version)
85
86 @classmethod
87 def setup_credentials(cls):
88 cls.request_microversion = (
89 api_version_utils.select_request_microversion(
90 cls.min_microversion,
91 CONF.baremetal.min_microversion))
92 cls.services_microversion = {
93 CONF.baremetal.catalog_type: cls.request_microversion}
Julia Kreger3a07c4d2021-06-22 10:27:56 -070094
Yuiko Takadaff785002015-12-17 15:56:42 +090095 super(BaseBaremetalTest, cls).setup_credentials()
96
Yuiko Takadab6527002015-12-07 11:49:12 +090097 @classmethod
98 def setup_clients(cls):
99 super(BaseBaremetalTest, cls).setup_clients()
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700100 if CONF.enforce_scope.ironic:
101 cls.client = cls.os_system_admin.baremetal.BaremetalClient()
102 else:
103 cls.client = cls.os_admin.baremetal.BaremetalClient()
Yuiko Takadab6527002015-12-07 11:49:12 +0900104
Julia Kreger0eb9ae72024-03-26 08:47:26 -0700105 # Skip the test if the class version doesn't match.
106 if cls.min_microversion or cls.max_microversion:
107 api_min, api_max = cls.client.get_min_max_api_microversions()
108 api_version_utils.check_skip_with_microversion(
109 cls.min_microversion,
110 cls.max_microversion,
111 api_min,
112 api_max)
113
Yuiko Takadab6527002015-12-07 11:49:12 +0900114 @classmethod
115 def resource_setup(cls):
116 super(BaseBaremetalTest, cls).resource_setup()
Yuiko Takadaff785002015-12-17 15:56:42 +0900117 cls.request_microversion = (
118 api_version_utils.select_request_microversion(
119 cls.min_microversion,
120 CONF.baremetal.min_microversion))
Yuiko Takadab6527002015-12-07 11:49:12 +0900121 cls.driver = CONF.baremetal.driver
122 cls.power_timeout = CONF.baremetal.power_timeout
Yuiko Takadaff785002015-12-17 15:56:42 +0900123 cls.unprovision_timeout = CONF.baremetal.unprovision_timeout
Yuiko Takadab6527002015-12-07 11:49:12 +0900124 cls.created_objects = {}
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200125 for resource in RESOURCE_TYPES + ['allocation']:
Yuiko Takadab6527002015-12-07 11:49:12 +0900126 cls.created_objects[resource] = set()
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200127 cls.deployed_nodes = set()
Yuiko Takadab6527002015-12-07 11:49:12 +0900128
129 @classmethod
130 def resource_cleanup(cls):
131 """Ensure that all created objects get destroyed."""
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000132 # Use the requested microversion for cleanup to ensure we can delete
133 # resources.
134 base.set_baremetal_api_microversion(cls.request_microversion)
Yuiko Takadab6527002015-12-07 11:49:12 +0900135 try:
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200136 for node in cls.deployed_nodes:
137 try:
138 cls.set_node_provision_state(node, 'deleted',
139 ['available', None])
140 except lib_exc.BadRequest:
Julia Kregercda96d52023-01-20 08:05:41 -0800141 LOG.warning('Cleanup: Failed to unprovision node: %s',
142 node)
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200143
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200144 # Delete allocations explicitly after unprovisioning instances, but
145 # before deleting nodes.
146 for allocation in cls.created_objects['allocation']:
147 try:
148 cls.client.delete_allocation(allocation)
149 except lib_exc.NotFound:
Julia Kregercda96d52023-01-20 08:05:41 -0800150 LOG.warning('Cleanup: Failed to delete allocation: %s',
151 allocation)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200152
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100153 for node in cls.created_objects['node']:
154 try:
155 cls.client.update_node(node, instance_uuid=None)
156 except lib_exc.TempestException:
Julia Kregercda96d52023-01-20 08:05:41 -0800157 LOG.warning('Cleanup: Failed to delete node: %s',
158 node)
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100159
Yuiko Takadab6527002015-12-07 11:49:12 +0900160 for resource in RESOURCE_TYPES:
161 uuids = cls.created_objects[resource]
162 delete_method = getattr(cls.client, 'delete_%s' % resource)
163 for u in uuids:
164 delete_method(u, ignore_errors=lib_exc.NotFound)
165 finally:
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000166 base.reset_baremetal_api_microversion()
Yuiko Takadab6527002015-12-07 11:49:12 +0900167 super(BaseBaremetalTest, cls).resource_cleanup()
168
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200169 def _assertExpected(self, expected, actual):
Ruby Loo6abadd42017-02-27 15:45:03 +0000170 """Check if expected keys/values exist in actual response body.
171
172 Check if the expected keys and values are in the actual response body.
173 It will not check the keys 'created_at' and 'updated_at', since they
174 will always have different values. Asserts if any expected key (or
175 corresponding value) is not in the actual response.
176
177 Note: this method has an underscore even though it is used outside of
178 this class, in order to distinguish this method from the more standard
179 assertXYZ methods.
180
181 :param expected: dict of key-value pairs that are expected to be in
182 'actual' dict.
183 :param actual: dict of key-value pairs.
184
185 """
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200186 for key, value in expected.items():
187 if key not in ('created_at', 'updated_at'):
188 self.assertIn(key, actual)
189 self.assertEqual(value, actual[key])
190
Yuiko Takadaff785002015-12-17 15:56:42 +0900191 def setUp(self):
192 super(BaseBaremetalTest, self).setUp()
193 self.useFixture(api_microversion_fixture.APIMicroversionFixture(
194 self.request_microversion))
195
Yuiko Takadab6527002015-12-07 11:49:12 +0900196 @classmethod
197 @creates('chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500198 def create_chassis(cls, description=None, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900199 """Wrapper utility for creating test chassis.
200
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300201 :param description: A description of the chassis. If not supplied,
Yuiko Takadab6527002015-12-07 11:49:12 +0900202 a random value will be generated.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000203 :return: A tuple with the server response and the created chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900204
205 """
206 description = description or data_utils.rand_name('test-chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500207 resp, body = cls.client.create_chassis(description=description,
208 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900209 return resp, body
210
211 @classmethod
212 @creates('node')
Sam Betts6f083ce2018-07-03 14:41:39 +0100213 def create_node(cls, chassis_id, cpu_arch='x86_64', cpus=8, local_gb=10,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100214 memory_mb=4096, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900215 """Wrapper utility for creating test baremetal nodes.
216
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300217 :param chassis_id: The unique identifier of the chassis.
Sam Betts6f083ce2018-07-03 14:41:39 +0100218 :param cpu_arch: CPU architecture of the node. Default: x86_64.
Yuiko Takadab6527002015-12-07 11:49:12 +0900219 :param cpus: Number of CPUs. Default: 8.
220 :param local_gb: Disk size. Default: 10.
221 :param memory_mb: Available RAM. Default: 4096.
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100222 :param kwargs: Other optional node fields.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000223 :return: A tuple with the server response and the created node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900224
225 """
226 resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch,
227 cpus=cpus, local_gb=local_gb,
228 memory_mb=memory_mb,
Kyrylo Romanenko96876912017-02-24 18:14:46 +0200229 driver=cls.driver,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100230 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900231
232 return resp, body
233
234 @classmethod
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200235 def set_node_provision_state(cls, node_id, target, expected, timeout=None,
236 interval=None):
237 """Sets the node's provision state.
238
239 :param node_id: The unique identifier of the node.
240 :param target: Target provision state.
241 :param expected: Expected final provision state or list of states.
242 :param timeout: The timeout for reaching the expected state.
243 Defaults to client.build_timeout.
244 :param interval: An interval between show_node calls for status check.
245 Defaults to client.build_interval.
246 """
247 cls.client.set_node_provision_state(node_id, target)
248 waiters.wait_for_bm_node_status(cls.client, node_id,
249 'provision_state', expected,
250 timeout=timeout, interval=interval)
251
252 @classmethod
253 def provide_node(cls, node_id, cleaning_timeout=None):
254 """Make the node available.
255
256 :param node_id: The unique identifier of the node.
257 :param cleaning_timeout: The timeout to wait for cleaning.
258 Defaults to client.build_timeout.
259 """
260 _, body = cls.client.show_node(node_id)
261 current_state = body['provision_state']
262 if current_state == 'enroll':
263 cls.set_node_provision_state(node_id, 'manage', 'manageable',
264 timeout=60, interval=1)
265 current_state = 'manageable'
266 if current_state == 'manageable':
267 cls.set_node_provision_state(node_id, 'provide',
268 ['available', None],
269 timeout=cleaning_timeout)
270 current_state = 'available'
271 if current_state not in ('available', None):
272 raise RuntimeError("Cannot reach state 'available': node %(node)s "
273 "is in unexpected state %(state)s" %
274 {'node': node_id, 'state': current_state})
275
276 @classmethod
277 def deploy_node(cls, node_id, cleaning_timeout=None, deploy_timeout=None):
278 """Deploy the node.
279
280 :param node_id: The unique identifier of the node.
281 :param cleaning_timeout: The timeout to wait for cleaning.
282 Defaults to client.build_timeout.
283 :param deploy_timeout: The timeout to wait for deploy.
284 Defaults to client.build_timeout.
285 """
286 cls.provide_node(node_id, cleaning_timeout=cleaning_timeout)
287 cls.set_node_provision_state(node_id, 'active', 'active',
288 timeout=deploy_timeout)
289 cls.deployed_nodes.add(node_id)
290
291 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900292 @creates('port')
Mark Goddard53bb68c2017-06-05 11:35:28 +0100293 def create_port(cls, node_id, address, extra=None, uuid=None,
294 portgroup_uuid=None, physical_network=None):
Yuiko Takadab6527002015-12-07 11:49:12 +0900295 """Wrapper utility for creating test ports.
296
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300297 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900298 :param address: MAC address of the port.
299 :param extra: Meta data of the port. If not supplied, an empty
300 dictionary will be created.
301 :param uuid: UUID of the port.
Mark Goddard53bb68c2017-06-05 11:35:28 +0100302 :param portgroup_uuid: The UUID of a portgroup of which this port is a
303 member.
304 :param physical_network: The physical network to which the port is
305 attached.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000306 :return: A tuple with the server response and the created port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900307
308 """
309 extra = extra or {}
310 resp, body = cls.client.create_port(address=address, node_id=node_id,
Mark Goddard53bb68c2017-06-05 11:35:28 +0100311 extra=extra, uuid=uuid,
312 portgroup_uuid=portgroup_uuid,
313 physical_network=physical_network)
Yuiko Takadab6527002015-12-07 11:49:12 +0900314
315 return resp, body
316
317 @classmethod
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200318 @creates('portgroup')
319 def create_portgroup(cls, node_uuid, **kwargs):
320 """Wrapper utility for creating test port groups.
321
322 :param node_uuid: The unique identifier of the node.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000323 :return: A tuple with the server response and the created port group.
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200324 """
325 resp, body = cls.client.create_portgroup(node_uuid=node_uuid, **kwargs)
326
327 return resp, body
328
329 @classmethod
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700330 @creates('volume_connector')
331 def create_volume_connector(cls, node_uuid, **kwargs):
332 """Wrapper utility for creating test volume connector.
333
334 :param node_uuid: The unique identifier of the node.
335 :return: A tuple with the server response and the created volume
336 connector.
337 """
338 resp, body = cls.client.create_volume_connector(node_uuid=node_uuid,
339 **kwargs)
340
341 return resp, body
342
343 @classmethod
344 @creates('volume_target')
345 def create_volume_target(cls, node_uuid, **kwargs):
346 """Wrapper utility for creating test volume target.
347
348 :param node_uuid: The unique identifier of the node.
349 :return: A tuple with the server response and the created volume
350 target.
351 """
352 resp, body = cls.client.create_volume_target(node_uuid=node_uuid,
353 **kwargs)
354
355 return resp, body
356
357 @classmethod
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000358 @creates('deploy_template')
359 def create_deploy_template(cls, name, **kwargs):
360 """Wrapper utility for creating test deploy template.
361
362 :param name: The name of the deploy template.
363 :return: A tuple with the server response and the created deploy
364 template.
365 """
366 resp, body = cls.client.create_deploy_template(name=name, **kwargs)
367
368 return resp, body
369
370 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900371 def delete_chassis(cls, chassis_id):
372 """Deletes a chassis having the specified UUID.
373
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300374 :param chassis_id: The unique identifier of the chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900375 :return: Server response.
376
377 """
378
379 resp, body = cls.client.delete_chassis(chassis_id)
380
381 if chassis_id in cls.created_objects['chassis']:
382 cls.created_objects['chassis'].remove(chassis_id)
383
384 return resp
385
386 @classmethod
387 def delete_node(cls, node_id):
388 """Deletes a node having the specified UUID.
389
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300390 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900391 :return: Server response.
392
393 """
394
395 resp, body = cls.client.delete_node(node_id)
396
397 if node_id in cls.created_objects['node']:
398 cls.created_objects['node'].remove(node_id)
399
400 return resp
401
402 @classmethod
403 def delete_port(cls, port_id):
404 """Deletes a port having the specified UUID.
405
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300406 :param port_id: The unique identifier of the port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900407 :return: Server response.
408
409 """
410
411 resp, body = cls.client.delete_port(port_id)
412
413 if port_id in cls.created_objects['port']:
414 cls.created_objects['port'].remove(port_id)
415
416 return resp
417
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200418 @classmethod
419 def delete_portgroup(cls, portgroup_ident):
420 """Deletes a port group having the specified UUID or name.
421
422 :param portgroup_ident: The name or UUID of the port group.
423 :return: Server response.
424 """
425 resp, body = cls.client.delete_portgroup(portgroup_ident)
426
427 if portgroup_ident in cls.created_objects['portgroup']:
428 cls.created_objects['portgroup'].remove(portgroup_ident)
429
430 return resp
431
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700432 @classmethod
433 def delete_volume_connector(cls, volume_connector_id):
434 """Deletes a volume connector having the specified UUID.
435
436 :param volume_connector_id: The UUID of the volume connector.
437 :return: Server response.
438 """
439 resp, body = cls.client.delete_volume_connector(volume_connector_id)
440
441 if volume_connector_id in cls.created_objects['volume_connector']:
442 cls.created_objects['volume_connector'].remove(
443 volume_connector_id)
444
445 return resp
446
447 @classmethod
448 def delete_volume_target(cls, volume_target_id):
449 """Deletes a volume target having the specified UUID.
450
451 :param volume_target_id: The UUID of the volume target.
452 :return: Server response.
453 """
454 resp, body = cls.client.delete_volume_target(volume_target_id)
455
456 if volume_target_id in cls.created_objects['volume_target']:
457 cls.created_objects['volume_target'].remove(volume_target_id)
458
459 return resp
460
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000461 @classmethod
462 def delete_deploy_template(cls, deploy_template_ident):
463 """Deletes a deploy template having the specified name or UUID.
464
465 :param deploy_template_ident: Name or UUID of the deploy template.
466 :return: Server response.
467 """
468 resp, body = cls.client.delete_deploy_template(deploy_template_ident)
469
470 if deploy_template_ident in cls.created_objects['deploy_template']:
471 cls.created_objects['deploy_template'].remove(
472 deploy_template_ident)
473
474 return resp
475
Yuiko Takadab6527002015-12-07 11:49:12 +0900476 def validate_self_link(self, resource, uuid, link):
477 """Check whether the given self link formatted correctly."""
478 expected_link = "{base}/{pref}/{res}/{uuid}".format(
Andrey Shestakov7cdfac32017-02-06 14:44:23 +0200479 base=self.client.base_url.rstrip('/'),
Yuiko Takadab6527002015-12-07 11:49:12 +0900480 pref=self.client.uri_prefix,
481 res=resource,
482 uuid=uuid)
483 self.assertEqual(expected_link, link)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200484
485 @classmethod
486 @creates('allocation')
487 def create_allocation(cls, resource_class, **kwargs):
488 """Wrapper utility for creating test allocations.
489
490 :param resource_class: Resource class to request.
491 :param kwargs: Other fields to pass.
492 :return: A tuple with the server response and the created allocation.
493 """
494 resp, body = cls.client.create_allocation(resource_class, **kwargs)
495 return resp, body
Julia Kregere4756402022-05-18 12:41:29 -0700496
497
498class BaseBaremetalRBACTest(BaseBaremetalTest):
499
500 # Unless otherwise superceeded by a version, RBAC tests generally start at
501 # version 1.70 as that is when System scope and the delineation occured.
502 min_microversion = '1.70'
503
504 @classmethod
505 def skip_checks(cls):
506 super(BaseBaremetalRBACTest, cls).skip_checks()
507 if not CONF.enforce_scope.ironic:
508 raise cls.skipException('RBAC tests for Ironic are not enabled.')