blob: 11ce859554b694a86379e57d38634f397fd9792e [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',
cidcf168b82024-09-11 18:25:56 +010041 'volume_target', 'chassis', 'deploy_template', 'runbook']
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
cidcf168b82024-09-11 18:25:56 +0100371 @creates('runbook')
372 def create_runbook(cls, name, **kwargs):
373 """Wrapper utility for creating test runbook.
374
375 :param name: The name of the runbook.
376 :return: A tuple with the server response and the created runbook.
377 """
378 resp, body = cls.client.create_runbook(name=name, **kwargs)
379
380 return resp, body
381
382 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900383 def delete_chassis(cls, chassis_id):
384 """Deletes a chassis having the specified UUID.
385
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300386 :param chassis_id: The unique identifier of the chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900387 :return: Server response.
388
389 """
390
391 resp, body = cls.client.delete_chassis(chassis_id)
392
393 if chassis_id in cls.created_objects['chassis']:
394 cls.created_objects['chassis'].remove(chassis_id)
395
396 return resp
397
398 @classmethod
399 def delete_node(cls, node_id):
400 """Deletes a node having the specified UUID.
401
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300402 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900403 :return: Server response.
404
405 """
406
407 resp, body = cls.client.delete_node(node_id)
408
409 if node_id in cls.created_objects['node']:
410 cls.created_objects['node'].remove(node_id)
411
412 return resp
413
414 @classmethod
415 def delete_port(cls, port_id):
416 """Deletes a port having the specified UUID.
417
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300418 :param port_id: The unique identifier of the port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900419 :return: Server response.
420
421 """
422
423 resp, body = cls.client.delete_port(port_id)
424
425 if port_id in cls.created_objects['port']:
426 cls.created_objects['port'].remove(port_id)
427
428 return resp
429
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200430 @classmethod
431 def delete_portgroup(cls, portgroup_ident):
432 """Deletes a port group having the specified UUID or name.
433
434 :param portgroup_ident: The name or UUID of the port group.
435 :return: Server response.
436 """
437 resp, body = cls.client.delete_portgroup(portgroup_ident)
438
439 if portgroup_ident in cls.created_objects['portgroup']:
440 cls.created_objects['portgroup'].remove(portgroup_ident)
441
442 return resp
443
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700444 @classmethod
445 def delete_volume_connector(cls, volume_connector_id):
446 """Deletes a volume connector having the specified UUID.
447
448 :param volume_connector_id: The UUID of the volume connector.
449 :return: Server response.
450 """
451 resp, body = cls.client.delete_volume_connector(volume_connector_id)
452
453 if volume_connector_id in cls.created_objects['volume_connector']:
454 cls.created_objects['volume_connector'].remove(
455 volume_connector_id)
456
457 return resp
458
459 @classmethod
460 def delete_volume_target(cls, volume_target_id):
461 """Deletes a volume target having the specified UUID.
462
463 :param volume_target_id: The UUID of the volume target.
464 :return: Server response.
465 """
466 resp, body = cls.client.delete_volume_target(volume_target_id)
467
468 if volume_target_id in cls.created_objects['volume_target']:
469 cls.created_objects['volume_target'].remove(volume_target_id)
470
471 return resp
472
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000473 @classmethod
474 def delete_deploy_template(cls, deploy_template_ident):
475 """Deletes a deploy template having the specified name or UUID.
476
477 :param deploy_template_ident: Name or UUID of the deploy template.
478 :return: Server response.
479 """
480 resp, body = cls.client.delete_deploy_template(deploy_template_ident)
481
482 if deploy_template_ident in cls.created_objects['deploy_template']:
483 cls.created_objects['deploy_template'].remove(
484 deploy_template_ident)
485
486 return resp
487
cidcf168b82024-09-11 18:25:56 +0100488 @classmethod
489 def delete_runbook(cls, runbook_ident):
490 """Deletes a runbook having the specified name or UUID.
491
492 :param runbook_ident: Name or UUID of the runbook.
493 :return: Server response.
494 """
495 resp, body = cls.client.delete_runbook(runbook_ident)
496
497 if runbook_ident in cls.created_objects['runbook']:
498 cls.created_objects['runbook'].remove(runbook_ident)
499
500 return resp
501
Yuiko Takadab6527002015-12-07 11:49:12 +0900502 def validate_self_link(self, resource, uuid, link):
503 """Check whether the given self link formatted correctly."""
504 expected_link = "{base}/{pref}/{res}/{uuid}".format(
Andrey Shestakov7cdfac32017-02-06 14:44:23 +0200505 base=self.client.base_url.rstrip('/'),
Yuiko Takadab6527002015-12-07 11:49:12 +0900506 pref=self.client.uri_prefix,
507 res=resource,
508 uuid=uuid)
509 self.assertEqual(expected_link, link)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200510
511 @classmethod
512 @creates('allocation')
513 def create_allocation(cls, resource_class, **kwargs):
514 """Wrapper utility for creating test allocations.
515
516 :param resource_class: Resource class to request.
517 :param kwargs: Other fields to pass.
518 :return: A tuple with the server response and the created allocation.
519 """
520 resp, body = cls.client.create_allocation(resource_class, **kwargs)
521 return resp, body
Julia Kregere4756402022-05-18 12:41:29 -0700522
523
524class BaseBaremetalRBACTest(BaseBaremetalTest):
525
526 # Unless otherwise superceeded by a version, RBAC tests generally start at
527 # version 1.70 as that is when System scope and the delineation occured.
528 min_microversion = '1.70'
529
530 @classmethod
531 def skip_checks(cls):
532 super(BaseBaremetalRBACTest, cls).skip_checks()
533 if not CONF.enforce_scope.ironic:
534 raise cls.skipException('RBAC tests for Ironic are not enabled.')