blob: 7744aee899ee56a5d6e9c137d6d32b956fc99c4c [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',
abhibongalefa630482025-06-04 13:30:29 +010041 'volume_target', 'chassis', 'deploy_template',
42 'runbook', 'inspection_rule']
Yuiko Takadab6527002015-12-07 11:49:12 +090043
44
45def creates(resource):
46 """Decorator that adds resources to the appropriate cleanup list."""
47
48 def decorator(f):
Takashi Kajinami6bddaab2022-05-10 00:58:56 +090049 @functools.wraps(f)
Yuiko Takadab6527002015-12-07 11:49:12 +090050 def wrapper(cls, *args, **kwargs):
51 resp, body = f(cls, *args, **kwargs)
52
53 if 'uuid' in body:
54 cls.created_objects[resource].add(body['uuid'])
55
56 return resp, body
57 return wrapper
58 return decorator
59
60
Yuiko Takadaff785002015-12-17 15:56:42 +090061class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
62 test.BaseTestCase):
Yuiko Takadab6527002015-12-07 11:49:12 +090063 """Base class for Baremetal API tests."""
64
Julia Kreger3a07c4d2021-06-22 10:27:56 -070065 credentials = ['admin', 'system_admin']
Yuiko Takadab6527002015-12-07 11:49:12 +090066
67 @classmethod
68 def skip_checks(cls):
69 super(BaseBaremetalTest, cls).skip_checks()
Jim Rollenhagen1c11bdf2016-11-29 16:57:30 -050070 if not CONF.service_available.ironic:
71 raise cls.skipException('Ironic is not enabled.')
Yuiko Takadab6527002015-12-07 11:49:12 +090072 if CONF.baremetal.driver not in SUPPORTED_DRIVERS:
73 skip_msg = ('%s skipped as Ironic driver %s is not supported for '
74 'testing.' %
75 (cls.__name__, CONF.baremetal.driver))
76 raise cls.skipException(skip_msg)
77
Yuiko Takadaff785002015-12-17 15:56:42 +090078 cfg_min_version = CONF.baremetal.min_microversion
79 cfg_max_version = CONF.baremetal.max_microversion
Julia Kreger0eb9ae72024-03-26 08:47:26 -070080
81 # Check versions and skip based upon *configuration*
Yuiko Takadaff785002015-12-17 15:56:42 +090082 api_version_utils.check_skip_with_microversion(cls.min_microversion,
83 cls.max_microversion,
84 cfg_min_version,
85 cfg_max_version)
86
87 @classmethod
88 def setup_credentials(cls):
89 cls.request_microversion = (
90 api_version_utils.select_request_microversion(
91 cls.min_microversion,
92 CONF.baremetal.min_microversion))
93 cls.services_microversion = {
94 CONF.baremetal.catalog_type: cls.request_microversion}
Julia Kreger3a07c4d2021-06-22 10:27:56 -070095
Yuiko Takadaff785002015-12-17 15:56:42 +090096 super(BaseBaremetalTest, cls).setup_credentials()
97
Yuiko Takadab6527002015-12-07 11:49:12 +090098 @classmethod
99 def setup_clients(cls):
100 super(BaseBaremetalTest, cls).setup_clients()
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700101 if CONF.enforce_scope.ironic:
102 cls.client = cls.os_system_admin.baremetal.BaremetalClient()
103 else:
104 cls.client = cls.os_admin.baremetal.BaremetalClient()
Yuiko Takadab6527002015-12-07 11:49:12 +0900105
Julia Kreger0eb9ae72024-03-26 08:47:26 -0700106 # Skip the test if the class version doesn't match.
107 if cls.min_microversion or cls.max_microversion:
108 api_min, api_max = cls.client.get_min_max_api_microversions()
109 api_version_utils.check_skip_with_microversion(
110 cls.min_microversion,
111 cls.max_microversion,
112 api_min,
113 api_max)
114
Yuiko Takadab6527002015-12-07 11:49:12 +0900115 @classmethod
116 def resource_setup(cls):
117 super(BaseBaremetalTest, cls).resource_setup()
Yuiko Takadaff785002015-12-17 15:56:42 +0900118 cls.request_microversion = (
119 api_version_utils.select_request_microversion(
120 cls.min_microversion,
121 CONF.baremetal.min_microversion))
Yuiko Takadab6527002015-12-07 11:49:12 +0900122 cls.driver = CONF.baremetal.driver
123 cls.power_timeout = CONF.baremetal.power_timeout
Yuiko Takadaff785002015-12-17 15:56:42 +0900124 cls.unprovision_timeout = CONF.baremetal.unprovision_timeout
Yuiko Takadab6527002015-12-07 11:49:12 +0900125 cls.created_objects = {}
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200126 for resource in RESOURCE_TYPES + ['allocation']:
Yuiko Takadab6527002015-12-07 11:49:12 +0900127 cls.created_objects[resource] = set()
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200128 cls.deployed_nodes = set()
Yuiko Takadab6527002015-12-07 11:49:12 +0900129
130 @classmethod
131 def resource_cleanup(cls):
132 """Ensure that all created objects get destroyed."""
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000133 # Use the requested microversion for cleanup to ensure we can delete
134 # resources.
135 base.set_baremetal_api_microversion(cls.request_microversion)
Yuiko Takadab6527002015-12-07 11:49:12 +0900136 try:
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200137 for node in cls.deployed_nodes:
138 try:
139 cls.set_node_provision_state(node, 'deleted',
140 ['available', None])
141 except lib_exc.BadRequest:
Julia Kregercda96d52023-01-20 08:05:41 -0800142 LOG.warning('Cleanup: Failed to unprovision node: %s',
143 node)
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200144
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200145 # Delete allocations explicitly after unprovisioning instances, but
146 # before deleting nodes.
147 for allocation in cls.created_objects['allocation']:
148 try:
149 cls.client.delete_allocation(allocation)
150 except lib_exc.NotFound:
Julia Kregercda96d52023-01-20 08:05:41 -0800151 LOG.warning('Cleanup: Failed to delete allocation: %s',
152 allocation)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200153
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100154 for node in cls.created_objects['node']:
155 try:
156 cls.client.update_node(node, instance_uuid=None)
157 except lib_exc.TempestException:
Julia Kregercda96d52023-01-20 08:05:41 -0800158 LOG.warning('Cleanup: Failed to delete node: %s',
159 node)
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100160
Yuiko Takadab6527002015-12-07 11:49:12 +0900161 for resource in RESOURCE_TYPES:
162 uuids = cls.created_objects[resource]
163 delete_method = getattr(cls.client, 'delete_%s' % resource)
164 for u in uuids:
165 delete_method(u, ignore_errors=lib_exc.NotFound)
166 finally:
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000167 base.reset_baremetal_api_microversion()
Yuiko Takadab6527002015-12-07 11:49:12 +0900168 super(BaseBaremetalTest, cls).resource_cleanup()
169
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200170 def _assertExpected(self, expected, actual):
Ruby Loo6abadd42017-02-27 15:45:03 +0000171 """Check if expected keys/values exist in actual response body.
172
173 Check if the expected keys and values are in the actual response body.
174 It will not check the keys 'created_at' and 'updated_at', since they
175 will always have different values. Asserts if any expected key (or
176 corresponding value) is not in the actual response.
177
178 Note: this method has an underscore even though it is used outside of
179 this class, in order to distinguish this method from the more standard
180 assertXYZ methods.
181
182 :param expected: dict of key-value pairs that are expected to be in
183 'actual' dict.
184 :param actual: dict of key-value pairs.
185
186 """
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200187 for key, value in expected.items():
188 if key not in ('created_at', 'updated_at'):
189 self.assertIn(key, actual)
190 self.assertEqual(value, actual[key])
191
Yuiko Takadaff785002015-12-17 15:56:42 +0900192 def setUp(self):
193 super(BaseBaremetalTest, self).setUp()
194 self.useFixture(api_microversion_fixture.APIMicroversionFixture(
195 self.request_microversion))
196
Yuiko Takadab6527002015-12-07 11:49:12 +0900197 @classmethod
198 @creates('chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500199 def create_chassis(cls, description=None, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900200 """Wrapper utility for creating test chassis.
201
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300202 :param description: A description of the chassis. If not supplied,
Yuiko Takadab6527002015-12-07 11:49:12 +0900203 a random value will be generated.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000204 :return: A tuple with the server response and the created chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900205
206 """
207 description = description or data_utils.rand_name('test-chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500208 resp, body = cls.client.create_chassis(description=description,
209 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900210 return resp, body
211
212 @classmethod
213 @creates('node')
Sam Betts6f083ce2018-07-03 14:41:39 +0100214 def create_node(cls, chassis_id, cpu_arch='x86_64', cpus=8, local_gb=10,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100215 memory_mb=4096, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900216 """Wrapper utility for creating test baremetal nodes.
217
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300218 :param chassis_id: The unique identifier of the chassis.
Sam Betts6f083ce2018-07-03 14:41:39 +0100219 :param cpu_arch: CPU architecture of the node. Default: x86_64.
Yuiko Takadab6527002015-12-07 11:49:12 +0900220 :param cpus: Number of CPUs. Default: 8.
221 :param local_gb: Disk size. Default: 10.
222 :param memory_mb: Available RAM. Default: 4096.
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100223 :param kwargs: Other optional node fields.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000224 :return: A tuple with the server response and the created node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900225
226 """
227 resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch,
228 cpus=cpus, local_gb=local_gb,
229 memory_mb=memory_mb,
Kyrylo Romanenko96876912017-02-24 18:14:46 +0200230 driver=cls.driver,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100231 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900232
233 return resp, body
234
235 @classmethod
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200236 def set_node_provision_state(cls, node_id, target, expected, timeout=None,
237 interval=None):
238 """Sets the node's provision state.
239
240 :param node_id: The unique identifier of the node.
241 :param target: Target provision state.
242 :param expected: Expected final provision state or list of states.
243 :param timeout: The timeout for reaching the expected state.
244 Defaults to client.build_timeout.
245 :param interval: An interval between show_node calls for status check.
246 Defaults to client.build_interval.
247 """
248 cls.client.set_node_provision_state(node_id, target)
249 waiters.wait_for_bm_node_status(cls.client, node_id,
250 'provision_state', expected,
251 timeout=timeout, interval=interval)
252
253 @classmethod
254 def provide_node(cls, node_id, cleaning_timeout=None):
255 """Make the node available.
256
257 :param node_id: The unique identifier of the node.
258 :param cleaning_timeout: The timeout to wait for cleaning.
259 Defaults to client.build_timeout.
260 """
261 _, body = cls.client.show_node(node_id)
262 current_state = body['provision_state']
263 if current_state == 'enroll':
264 cls.set_node_provision_state(node_id, 'manage', 'manageable',
265 timeout=60, interval=1)
266 current_state = 'manageable'
267 if current_state == 'manageable':
268 cls.set_node_provision_state(node_id, 'provide',
269 ['available', None],
270 timeout=cleaning_timeout)
271 current_state = 'available'
272 if current_state not in ('available', None):
273 raise RuntimeError("Cannot reach state 'available': node %(node)s "
274 "is in unexpected state %(state)s" %
275 {'node': node_id, 'state': current_state})
276
277 @classmethod
278 def deploy_node(cls, node_id, cleaning_timeout=None, deploy_timeout=None):
279 """Deploy the node.
280
281 :param node_id: The unique identifier of the node.
282 :param cleaning_timeout: The timeout to wait for cleaning.
283 Defaults to client.build_timeout.
284 :param deploy_timeout: The timeout to wait for deploy.
285 Defaults to client.build_timeout.
286 """
287 cls.provide_node(node_id, cleaning_timeout=cleaning_timeout)
288 cls.set_node_provision_state(node_id, 'active', 'active',
289 timeout=deploy_timeout)
290 cls.deployed_nodes.add(node_id)
291
292 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900293 @creates('port')
Mark Goddard53bb68c2017-06-05 11:35:28 +0100294 def create_port(cls, node_id, address, extra=None, uuid=None,
295 portgroup_uuid=None, physical_network=None):
Yuiko Takadab6527002015-12-07 11:49:12 +0900296 """Wrapper utility for creating test ports.
297
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300298 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900299 :param address: MAC address of the port.
300 :param extra: Meta data of the port. If not supplied, an empty
301 dictionary will be created.
302 :param uuid: UUID of the port.
Mark Goddard53bb68c2017-06-05 11:35:28 +0100303 :param portgroup_uuid: The UUID of a portgroup of which this port is a
304 member.
305 :param physical_network: The physical network to which the port is
306 attached.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000307 :return: A tuple with the server response and the created port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900308
309 """
310 extra = extra or {}
311 resp, body = cls.client.create_port(address=address, node_id=node_id,
Mark Goddard53bb68c2017-06-05 11:35:28 +0100312 extra=extra, uuid=uuid,
313 portgroup_uuid=portgroup_uuid,
314 physical_network=physical_network)
Yuiko Takadab6527002015-12-07 11:49:12 +0900315
316 return resp, body
317
318 @classmethod
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200319 @creates('portgroup')
320 def create_portgroup(cls, node_uuid, **kwargs):
321 """Wrapper utility for creating test port groups.
322
323 :param node_uuid: The unique identifier of the node.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000324 :return: A tuple with the server response and the created port group.
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200325 """
326 resp, body = cls.client.create_portgroup(node_uuid=node_uuid, **kwargs)
327
328 return resp, body
329
330 @classmethod
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700331 @creates('volume_connector')
332 def create_volume_connector(cls, node_uuid, **kwargs):
333 """Wrapper utility for creating test volume connector.
334
335 :param node_uuid: The unique identifier of the node.
336 :return: A tuple with the server response and the created volume
337 connector.
338 """
339 resp, body = cls.client.create_volume_connector(node_uuid=node_uuid,
340 **kwargs)
341
342 return resp, body
343
344 @classmethod
345 @creates('volume_target')
346 def create_volume_target(cls, node_uuid, **kwargs):
347 """Wrapper utility for creating test volume target.
348
349 :param node_uuid: The unique identifier of the node.
350 :return: A tuple with the server response and the created volume
351 target.
352 """
353 resp, body = cls.client.create_volume_target(node_uuid=node_uuid,
354 **kwargs)
355
356 return resp, body
357
358 @classmethod
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000359 @creates('deploy_template')
360 def create_deploy_template(cls, name, **kwargs):
361 """Wrapper utility for creating test deploy template.
362
363 :param name: The name of the deploy template.
364 :return: A tuple with the server response and the created deploy
365 template.
366 """
367 resp, body = cls.client.create_deploy_template(name=name, **kwargs)
368
369 return resp, body
370
371 @classmethod
cidcf168b82024-09-11 18:25:56 +0100372 @creates('runbook')
373 def create_runbook(cls, name, **kwargs):
374 """Wrapper utility for creating test runbook.
375
376 :param name: The name of the runbook.
377 :return: A tuple with the server response and the created runbook.
378 """
379 resp, body = cls.client.create_runbook(name=name, **kwargs)
380
381 return resp, body
382
383 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900384 def delete_chassis(cls, chassis_id):
385 """Deletes a chassis having the specified UUID.
386
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300387 :param chassis_id: The unique identifier of the chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900388 :return: Server response.
389
390 """
391
392 resp, body = cls.client.delete_chassis(chassis_id)
393
394 if chassis_id in cls.created_objects['chassis']:
395 cls.created_objects['chassis'].remove(chassis_id)
396
397 return resp
398
399 @classmethod
400 def delete_node(cls, node_id):
401 """Deletes a node having the specified UUID.
402
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300403 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900404 :return: Server response.
405
406 """
407
408 resp, body = cls.client.delete_node(node_id)
409
410 if node_id in cls.created_objects['node']:
411 cls.created_objects['node'].remove(node_id)
412
413 return resp
414
415 @classmethod
416 def delete_port(cls, port_id):
417 """Deletes a port having the specified UUID.
418
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300419 :param port_id: The unique identifier of the port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900420 :return: Server response.
421
422 """
423
424 resp, body = cls.client.delete_port(port_id)
425
426 if port_id in cls.created_objects['port']:
427 cls.created_objects['port'].remove(port_id)
428
429 return resp
430
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200431 @classmethod
432 def delete_portgroup(cls, portgroup_ident):
433 """Deletes a port group having the specified UUID or name.
434
435 :param portgroup_ident: The name or UUID of the port group.
436 :return: Server response.
437 """
438 resp, body = cls.client.delete_portgroup(portgroup_ident)
439
440 if portgroup_ident in cls.created_objects['portgroup']:
441 cls.created_objects['portgroup'].remove(portgroup_ident)
442
443 return resp
444
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700445 @classmethod
446 def delete_volume_connector(cls, volume_connector_id):
447 """Deletes a volume connector having the specified UUID.
448
449 :param volume_connector_id: The UUID of the volume connector.
450 :return: Server response.
451 """
452 resp, body = cls.client.delete_volume_connector(volume_connector_id)
453
454 if volume_connector_id in cls.created_objects['volume_connector']:
455 cls.created_objects['volume_connector'].remove(
456 volume_connector_id)
457
458 return resp
459
460 @classmethod
461 def delete_volume_target(cls, volume_target_id):
462 """Deletes a volume target having the specified UUID.
463
464 :param volume_target_id: The UUID of the volume target.
465 :return: Server response.
466 """
467 resp, body = cls.client.delete_volume_target(volume_target_id)
468
469 if volume_target_id in cls.created_objects['volume_target']:
470 cls.created_objects['volume_target'].remove(volume_target_id)
471
472 return resp
473
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000474 @classmethod
475 def delete_deploy_template(cls, deploy_template_ident):
476 """Deletes a deploy template having the specified name or UUID.
477
478 :param deploy_template_ident: Name or UUID of the deploy template.
479 :return: Server response.
480 """
481 resp, body = cls.client.delete_deploy_template(deploy_template_ident)
482
483 if deploy_template_ident in cls.created_objects['deploy_template']:
484 cls.created_objects['deploy_template'].remove(
485 deploy_template_ident)
486
487 return resp
488
cidcf168b82024-09-11 18:25:56 +0100489 @classmethod
490 def delete_runbook(cls, runbook_ident):
491 """Deletes a runbook having the specified name or UUID.
492
493 :param runbook_ident: Name or UUID of the runbook.
494 :return: Server response.
495 """
496 resp, body = cls.client.delete_runbook(runbook_ident)
497
498 if runbook_ident in cls.created_objects['runbook']:
499 cls.created_objects['runbook'].remove(runbook_ident)
500
501 return resp
502
Yuiko Takadab6527002015-12-07 11:49:12 +0900503 def validate_self_link(self, resource, uuid, link):
504 """Check whether the given self link formatted correctly."""
505 expected_link = "{base}/{pref}/{res}/{uuid}".format(
Andrey Shestakov7cdfac32017-02-06 14:44:23 +0200506 base=self.client.base_url.rstrip('/'),
Yuiko Takadab6527002015-12-07 11:49:12 +0900507 pref=self.client.uri_prefix,
508 res=resource,
509 uuid=uuid)
510 self.assertEqual(expected_link, link)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200511
512 @classmethod
513 @creates('allocation')
514 def create_allocation(cls, resource_class, **kwargs):
515 """Wrapper utility for creating test allocations.
516
517 :param resource_class: Resource class to request.
518 :param kwargs: Other fields to pass.
519 :return: A tuple with the server response and the created allocation.
520 """
521 resp, body = cls.client.create_allocation(resource_class, **kwargs)
522 return resp, body
Julia Kregere4756402022-05-18 12:41:29 -0700523
abhibongalefa630482025-06-04 13:30:29 +0100524 @classmethod
525 @creates('inspection_rule')
526 def create_inspection_rule(cls, rule_uuid, payload):
527 """Wrapper utility for creating Inspection rule.
528
529 :param rule_uuid: UUID of the Inspection rule.
530 :param payload: Inspection rule other fields.
531 :return: Server response.
532 """
533 if rule_uuid is not None:
534 payload['uuid'] = rule_uuid
535
536 resp, body = cls.client.create_inspection_rule(payload)
537
538 return resp, body
539
540 @classmethod
541 def delete_inspection_rule(cls, rule_uuid):
542 """Delete a inspection rules having the specified UUID.
543
544 :param rule_uuid: UUID of the Inspection rule.
545 """
546 resp, body = cls.client.delete_inspection_rule(rule_uuid)
547
548 return resp
549
Julia Kregere4756402022-05-18 12:41:29 -0700550
551class BaseBaremetalRBACTest(BaseBaremetalTest):
552
Doug Goldsteina736e822025-02-02 11:27:26 -0500553 # Unless otherwise superseded by a version, RBAC tests generally start at
554 # version 1.70 as that is when System scope and the delineation occurred.
Julia Kregere4756402022-05-18 12:41:29 -0700555 min_microversion = '1.70'
556
557 @classmethod
558 def skip_checks(cls):
559 super(BaseBaremetalRBACTest, cls).skip_checks()
560 if not CONF.enforce_scope.ironic:
561 raise cls.skipException('RBAC tests for Ironic are not enabled.')