blob: c541dd39e2671041b8b16fb330ac0b6e0b62b9da [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
Yuiko Takadab6527002015-12-07 11:49:12 +090015from tempest import config
Yuiko Takadaff785002015-12-17 15:56:42 +090016from tempest.lib.common import api_version_utils
Lenny Verkhovsky88625042016-03-08 17:44:00 +020017from tempest.lib.common.utils import data_utils
18from tempest.lib import exceptions as lib_exc
Yuiko Takadab6527002015-12-07 11:49:12 +090019from tempest import test
Yuiko Takadab6527002015-12-07 11:49:12 +090020
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +020021from ironic_tempest_plugin.common import waiters
Mark Goddard6f2e72c2019-02-15 12:23:34 +000022from ironic_tempest_plugin.services.baremetal import base
Yuiko Takadaff785002015-12-17 15:56:42 +090023from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
Yuiko Takadab6527002015-12-07 11:49:12 +090024
25CONF = config.CONF
26
27
28# NOTE(adam_g): The baremetal API tests exercise operations such as enroll
29# node, power on, power off, etc. Testing against real drivers (ie, IPMI)
30# will require passing driver-specific data to Tempest (addresses,
31# credentials, etc). Until then, only support testing against the fake driver,
32# which has no external dependencies.
Dmitry Tantsur893b1a92018-04-26 16:12:45 +020033SUPPORTED_DRIVERS = ['fake', 'fake-hardware']
Yuiko Takadab6527002015-12-07 11:49:12 +090034
35# NOTE(jroll): resources must be deleted in a specific order, this list
36# defines the resource types to clean up, and the correct order.
Mark Goddard6f2e72c2019-02-15 12:23:34 +000037RESOURCE_TYPES = ['port', 'portgroup', 'node', 'volume_connector',
38 'volume_target', 'chassis', 'deploy_template']
Yuiko Takadab6527002015-12-07 11:49:12 +090039
40
41def creates(resource):
42 """Decorator that adds resources to the appropriate cleanup list."""
43
44 def decorator(f):
Takashi Kajinami6bddaab2022-05-10 00:58:56 +090045 @functools.wraps(f)
Yuiko Takadab6527002015-12-07 11:49:12 +090046 def wrapper(cls, *args, **kwargs):
47 resp, body = f(cls, *args, **kwargs)
48
49 if 'uuid' in body:
50 cls.created_objects[resource].add(body['uuid'])
51
52 return resp, body
53 return wrapper
54 return decorator
55
56
Yuiko Takadaff785002015-12-17 15:56:42 +090057class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
58 test.BaseTestCase):
Yuiko Takadab6527002015-12-07 11:49:12 +090059 """Base class for Baremetal API tests."""
60
Julia Kreger3a07c4d2021-06-22 10:27:56 -070061 credentials = ['admin', 'system_admin']
Yuiko Takadab6527002015-12-07 11:49:12 +090062
63 @classmethod
64 def skip_checks(cls):
65 super(BaseBaremetalTest, cls).skip_checks()
Jim Rollenhagen1c11bdf2016-11-29 16:57:30 -050066 if not CONF.service_available.ironic:
67 raise cls.skipException('Ironic is not enabled.')
Yuiko Takadab6527002015-12-07 11:49:12 +090068 if CONF.baremetal.driver not in SUPPORTED_DRIVERS:
69 skip_msg = ('%s skipped as Ironic driver %s is not supported for '
70 'testing.' %
71 (cls.__name__, CONF.baremetal.driver))
72 raise cls.skipException(skip_msg)
73
Yuiko Takadaff785002015-12-17 15:56:42 +090074 cfg_min_version = CONF.baremetal.min_microversion
75 cfg_max_version = CONF.baremetal.max_microversion
76 api_version_utils.check_skip_with_microversion(cls.min_microversion,
77 cls.max_microversion,
78 cfg_min_version,
79 cfg_max_version)
80
81 @classmethod
82 def setup_credentials(cls):
83 cls.request_microversion = (
84 api_version_utils.select_request_microversion(
85 cls.min_microversion,
86 CONF.baremetal.min_microversion))
87 cls.services_microversion = {
88 CONF.baremetal.catalog_type: cls.request_microversion}
Julia Kreger3a07c4d2021-06-22 10:27:56 -070089
Yuiko Takadaff785002015-12-17 15:56:42 +090090 super(BaseBaremetalTest, cls).setup_credentials()
91
Yuiko Takadab6527002015-12-07 11:49:12 +090092 @classmethod
93 def setup_clients(cls):
94 super(BaseBaremetalTest, cls).setup_clients()
Julia Kreger3a07c4d2021-06-22 10:27:56 -070095 if CONF.enforce_scope.ironic:
96 cls.client = cls.os_system_admin.baremetal.BaremetalClient()
97 else:
98 cls.client = cls.os_admin.baremetal.BaremetalClient()
Yuiko Takadab6527002015-12-07 11:49:12 +090099
100 @classmethod
101 def resource_setup(cls):
102 super(BaseBaremetalTest, cls).resource_setup()
Yuiko Takadaff785002015-12-17 15:56:42 +0900103 cls.request_microversion = (
104 api_version_utils.select_request_microversion(
105 cls.min_microversion,
106 CONF.baremetal.min_microversion))
Yuiko Takadab6527002015-12-07 11:49:12 +0900107 cls.driver = CONF.baremetal.driver
108 cls.power_timeout = CONF.baremetal.power_timeout
Yuiko Takadaff785002015-12-17 15:56:42 +0900109 cls.unprovision_timeout = CONF.baremetal.unprovision_timeout
Yuiko Takadab6527002015-12-07 11:49:12 +0900110 cls.created_objects = {}
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200111 for resource in RESOURCE_TYPES + ['allocation']:
Yuiko Takadab6527002015-12-07 11:49:12 +0900112 cls.created_objects[resource] = set()
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200113 cls.deployed_nodes = set()
Yuiko Takadab6527002015-12-07 11:49:12 +0900114
115 @classmethod
116 def resource_cleanup(cls):
117 """Ensure that all created objects get destroyed."""
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000118 # Use the requested microversion for cleanup to ensure we can delete
119 # resources.
120 base.set_baremetal_api_microversion(cls.request_microversion)
Yuiko Takadab6527002015-12-07 11:49:12 +0900121 try:
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200122 for node in cls.deployed_nodes:
123 try:
124 cls.set_node_provision_state(node, 'deleted',
125 ['available', None])
126 except lib_exc.BadRequest:
127 pass
128
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200129 # Delete allocations explicitly after unprovisioning instances, but
130 # before deleting nodes.
131 for allocation in cls.created_objects['allocation']:
132 try:
133 cls.client.delete_allocation(allocation)
134 except lib_exc.NotFound:
135 pass
136
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100137 for node in cls.created_objects['node']:
138 try:
139 cls.client.update_node(node, instance_uuid=None)
140 except lib_exc.TempestException:
141 pass
142
Yuiko Takadab6527002015-12-07 11:49:12 +0900143 for resource in RESOURCE_TYPES:
144 uuids = cls.created_objects[resource]
145 delete_method = getattr(cls.client, 'delete_%s' % resource)
146 for u in uuids:
147 delete_method(u, ignore_errors=lib_exc.NotFound)
148 finally:
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000149 base.reset_baremetal_api_microversion()
Yuiko Takadab6527002015-12-07 11:49:12 +0900150 super(BaseBaremetalTest, cls).resource_cleanup()
151
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200152 def _assertExpected(self, expected, actual):
Ruby Loo6abadd42017-02-27 15:45:03 +0000153 """Check if expected keys/values exist in actual response body.
154
155 Check if the expected keys and values are in the actual response body.
156 It will not check the keys 'created_at' and 'updated_at', since they
157 will always have different values. Asserts if any expected key (or
158 corresponding value) is not in the actual response.
159
160 Note: this method has an underscore even though it is used outside of
161 this class, in order to distinguish this method from the more standard
162 assertXYZ methods.
163
164 :param expected: dict of key-value pairs that are expected to be in
165 'actual' dict.
166 :param actual: dict of key-value pairs.
167
168 """
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200169 for key, value in expected.items():
170 if key not in ('created_at', 'updated_at'):
171 self.assertIn(key, actual)
172 self.assertEqual(value, actual[key])
173
Yuiko Takadaff785002015-12-17 15:56:42 +0900174 def setUp(self):
175 super(BaseBaremetalTest, self).setUp()
176 self.useFixture(api_microversion_fixture.APIMicroversionFixture(
177 self.request_microversion))
178
Yuiko Takadab6527002015-12-07 11:49:12 +0900179 @classmethod
180 @creates('chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500181 def create_chassis(cls, description=None, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900182 """Wrapper utility for creating test chassis.
183
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300184 :param description: A description of the chassis. If not supplied,
Yuiko Takadab6527002015-12-07 11:49:12 +0900185 a random value will be generated.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000186 :return: A tuple with the server response and the created chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900187
188 """
189 description = description or data_utils.rand_name('test-chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500190 resp, body = cls.client.create_chassis(description=description,
191 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900192 return resp, body
193
194 @classmethod
195 @creates('node')
Sam Betts6f083ce2018-07-03 14:41:39 +0100196 def create_node(cls, chassis_id, cpu_arch='x86_64', cpus=8, local_gb=10,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100197 memory_mb=4096, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900198 """Wrapper utility for creating test baremetal nodes.
199
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300200 :param chassis_id: The unique identifier of the chassis.
Sam Betts6f083ce2018-07-03 14:41:39 +0100201 :param cpu_arch: CPU architecture of the node. Default: x86_64.
Yuiko Takadab6527002015-12-07 11:49:12 +0900202 :param cpus: Number of CPUs. Default: 8.
203 :param local_gb: Disk size. Default: 10.
204 :param memory_mb: Available RAM. Default: 4096.
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100205 :param kwargs: Other optional node fields.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000206 :return: A tuple with the server response and the created node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900207
208 """
209 resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch,
210 cpus=cpus, local_gb=local_gb,
211 memory_mb=memory_mb,
Kyrylo Romanenko96876912017-02-24 18:14:46 +0200212 driver=cls.driver,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100213 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900214
215 return resp, body
216
217 @classmethod
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200218 def set_node_provision_state(cls, node_id, target, expected, timeout=None,
219 interval=None):
220 """Sets the node's provision state.
221
222 :param node_id: The unique identifier of the node.
223 :param target: Target provision state.
224 :param expected: Expected final provision state or list of states.
225 :param timeout: The timeout for reaching the expected state.
226 Defaults to client.build_timeout.
227 :param interval: An interval between show_node calls for status check.
228 Defaults to client.build_interval.
229 """
230 cls.client.set_node_provision_state(node_id, target)
231 waiters.wait_for_bm_node_status(cls.client, node_id,
232 'provision_state', expected,
233 timeout=timeout, interval=interval)
234
235 @classmethod
236 def provide_node(cls, node_id, cleaning_timeout=None):
237 """Make the node available.
238
239 :param node_id: The unique identifier of the node.
240 :param cleaning_timeout: The timeout to wait for cleaning.
241 Defaults to client.build_timeout.
242 """
243 _, body = cls.client.show_node(node_id)
244 current_state = body['provision_state']
245 if current_state == 'enroll':
246 cls.set_node_provision_state(node_id, 'manage', 'manageable',
247 timeout=60, interval=1)
248 current_state = 'manageable'
249 if current_state == 'manageable':
250 cls.set_node_provision_state(node_id, 'provide',
251 ['available', None],
252 timeout=cleaning_timeout)
253 current_state = 'available'
254 if current_state not in ('available', None):
255 raise RuntimeError("Cannot reach state 'available': node %(node)s "
256 "is in unexpected state %(state)s" %
257 {'node': node_id, 'state': current_state})
258
259 @classmethod
260 def deploy_node(cls, node_id, cleaning_timeout=None, deploy_timeout=None):
261 """Deploy the node.
262
263 :param node_id: The unique identifier of the node.
264 :param cleaning_timeout: The timeout to wait for cleaning.
265 Defaults to client.build_timeout.
266 :param deploy_timeout: The timeout to wait for deploy.
267 Defaults to client.build_timeout.
268 """
269 cls.provide_node(node_id, cleaning_timeout=cleaning_timeout)
270 cls.set_node_provision_state(node_id, 'active', 'active',
271 timeout=deploy_timeout)
272 cls.deployed_nodes.add(node_id)
273
274 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900275 @creates('port')
Mark Goddard53bb68c2017-06-05 11:35:28 +0100276 def create_port(cls, node_id, address, extra=None, uuid=None,
277 portgroup_uuid=None, physical_network=None):
Yuiko Takadab6527002015-12-07 11:49:12 +0900278 """Wrapper utility for creating test ports.
279
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300280 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900281 :param address: MAC address of the port.
282 :param extra: Meta data of the port. If not supplied, an empty
283 dictionary will be created.
284 :param uuid: UUID of the port.
Mark Goddard53bb68c2017-06-05 11:35:28 +0100285 :param portgroup_uuid: The UUID of a portgroup of which this port is a
286 member.
287 :param physical_network: The physical network to which the port is
288 attached.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000289 :return: A tuple with the server response and the created port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900290
291 """
292 extra = extra or {}
293 resp, body = cls.client.create_port(address=address, node_id=node_id,
Mark Goddard53bb68c2017-06-05 11:35:28 +0100294 extra=extra, uuid=uuid,
295 portgroup_uuid=portgroup_uuid,
296 physical_network=physical_network)
Yuiko Takadab6527002015-12-07 11:49:12 +0900297
298 return resp, body
299
300 @classmethod
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200301 @creates('portgroup')
302 def create_portgroup(cls, node_uuid, **kwargs):
303 """Wrapper utility for creating test port groups.
304
305 :param node_uuid: The unique identifier of the node.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000306 :return: A tuple with the server response and the created port group.
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200307 """
308 resp, body = cls.client.create_portgroup(node_uuid=node_uuid, **kwargs)
309
310 return resp, body
311
312 @classmethod
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700313 @creates('volume_connector')
314 def create_volume_connector(cls, node_uuid, **kwargs):
315 """Wrapper utility for creating test volume connector.
316
317 :param node_uuid: The unique identifier of the node.
318 :return: A tuple with the server response and the created volume
319 connector.
320 """
321 resp, body = cls.client.create_volume_connector(node_uuid=node_uuid,
322 **kwargs)
323
324 return resp, body
325
326 @classmethod
327 @creates('volume_target')
328 def create_volume_target(cls, node_uuid, **kwargs):
329 """Wrapper utility for creating test volume target.
330
331 :param node_uuid: The unique identifier of the node.
332 :return: A tuple with the server response and the created volume
333 target.
334 """
335 resp, body = cls.client.create_volume_target(node_uuid=node_uuid,
336 **kwargs)
337
338 return resp, body
339
340 @classmethod
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000341 @creates('deploy_template')
342 def create_deploy_template(cls, name, **kwargs):
343 """Wrapper utility for creating test deploy template.
344
345 :param name: The name of the deploy template.
346 :return: A tuple with the server response and the created deploy
347 template.
348 """
349 resp, body = cls.client.create_deploy_template(name=name, **kwargs)
350
351 return resp, body
352
353 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900354 def delete_chassis(cls, chassis_id):
355 """Deletes a chassis having the specified UUID.
356
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300357 :param chassis_id: The unique identifier of the chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900358 :return: Server response.
359
360 """
361
362 resp, body = cls.client.delete_chassis(chassis_id)
363
364 if chassis_id in cls.created_objects['chassis']:
365 cls.created_objects['chassis'].remove(chassis_id)
366
367 return resp
368
369 @classmethod
370 def delete_node(cls, node_id):
371 """Deletes a node having the specified UUID.
372
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300373 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900374 :return: Server response.
375
376 """
377
378 resp, body = cls.client.delete_node(node_id)
379
380 if node_id in cls.created_objects['node']:
381 cls.created_objects['node'].remove(node_id)
382
383 return resp
384
385 @classmethod
386 def delete_port(cls, port_id):
387 """Deletes a port having the specified UUID.
388
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300389 :param port_id: The unique identifier of the port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900390 :return: Server response.
391
392 """
393
394 resp, body = cls.client.delete_port(port_id)
395
396 if port_id in cls.created_objects['port']:
397 cls.created_objects['port'].remove(port_id)
398
399 return resp
400
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200401 @classmethod
402 def delete_portgroup(cls, portgroup_ident):
403 """Deletes a port group having the specified UUID or name.
404
405 :param portgroup_ident: The name or UUID of the port group.
406 :return: Server response.
407 """
408 resp, body = cls.client.delete_portgroup(portgroup_ident)
409
410 if portgroup_ident in cls.created_objects['portgroup']:
411 cls.created_objects['portgroup'].remove(portgroup_ident)
412
413 return resp
414
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700415 @classmethod
416 def delete_volume_connector(cls, volume_connector_id):
417 """Deletes a volume connector having the specified UUID.
418
419 :param volume_connector_id: The UUID of the volume connector.
420 :return: Server response.
421 """
422 resp, body = cls.client.delete_volume_connector(volume_connector_id)
423
424 if volume_connector_id in cls.created_objects['volume_connector']:
425 cls.created_objects['volume_connector'].remove(
426 volume_connector_id)
427
428 return resp
429
430 @classmethod
431 def delete_volume_target(cls, volume_target_id):
432 """Deletes a volume target having the specified UUID.
433
434 :param volume_target_id: The UUID of the volume target.
435 :return: Server response.
436 """
437 resp, body = cls.client.delete_volume_target(volume_target_id)
438
439 if volume_target_id in cls.created_objects['volume_target']:
440 cls.created_objects['volume_target'].remove(volume_target_id)
441
442 return resp
443
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000444 @classmethod
445 def delete_deploy_template(cls, deploy_template_ident):
446 """Deletes a deploy template having the specified name or UUID.
447
448 :param deploy_template_ident: Name or UUID of the deploy template.
449 :return: Server response.
450 """
451 resp, body = cls.client.delete_deploy_template(deploy_template_ident)
452
453 if deploy_template_ident in cls.created_objects['deploy_template']:
454 cls.created_objects['deploy_template'].remove(
455 deploy_template_ident)
456
457 return resp
458
Yuiko Takadab6527002015-12-07 11:49:12 +0900459 def validate_self_link(self, resource, uuid, link):
460 """Check whether the given self link formatted correctly."""
461 expected_link = "{base}/{pref}/{res}/{uuid}".format(
Andrey Shestakov7cdfac32017-02-06 14:44:23 +0200462 base=self.client.base_url.rstrip('/'),
Yuiko Takadab6527002015-12-07 11:49:12 +0900463 pref=self.client.uri_prefix,
464 res=resource,
465 uuid=uuid)
466 self.assertEqual(expected_link, link)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200467
468 @classmethod
469 @creates('allocation')
470 def create_allocation(cls, resource_class, **kwargs):
471 """Wrapper utility for creating test allocations.
472
473 :param resource_class: Resource class to request.
474 :param kwargs: Other fields to pass.
475 :return: A tuple with the server response and the created allocation.
476 """
477 resp, body = cls.client.create_allocation(resource_class, **kwargs)
478 return resp, body