blob: d78f74a2eb8914977309a115f27221937e428925 [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
John L. Villalovosd88fe9f2018-02-01 16:24:43 -080013import six
Yuiko Takadab6527002015-12-07 11:49:12 +090014from tempest import config
Yuiko Takadaff785002015-12-17 15:56:42 +090015from tempest.lib.common import api_version_utils
Lenny Verkhovsky88625042016-03-08 17:44:00 +020016from tempest.lib.common.utils import data_utils
17from tempest.lib import exceptions as lib_exc
Yuiko Takadab6527002015-12-07 11:49:12 +090018from tempest import test
Yuiko Takadab6527002015-12-07 11:49:12 +090019
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +020020from ironic_tempest_plugin.common import waiters
Mark Goddard6f2e72c2019-02-15 12:23:34 +000021from ironic_tempest_plugin.services.baremetal import base
Yuiko Takadaff785002015-12-17 15:56:42 +090022from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
Yuiko Takadab6527002015-12-07 11:49:12 +090023
24CONF = config.CONF
25
26
27# NOTE(adam_g): The baremetal API tests exercise operations such as enroll
28# node, power on, power off, etc. Testing against real drivers (ie, IPMI)
29# will require passing driver-specific data to Tempest (addresses,
30# credentials, etc). Until then, only support testing against the fake driver,
31# which has no external dependencies.
Dmitry Tantsur893b1a92018-04-26 16:12:45 +020032SUPPORTED_DRIVERS = ['fake', 'fake-hardware']
Yuiko Takadab6527002015-12-07 11:49:12 +090033
34# NOTE(jroll): resources must be deleted in a specific order, this list
35# defines the resource types to clean up, and the correct order.
Mark Goddard6f2e72c2019-02-15 12:23:34 +000036RESOURCE_TYPES = ['port', 'portgroup', 'node', 'volume_connector',
37 'volume_target', 'chassis', 'deploy_template']
Yuiko Takadab6527002015-12-07 11:49:12 +090038
39
40def creates(resource):
41 """Decorator that adds resources to the appropriate cleanup list."""
42
43 def decorator(f):
John L. Villalovosd88fe9f2018-02-01 16:24:43 -080044 @six.wraps(f)
Yuiko Takadab6527002015-12-07 11:49:12 +090045 def wrapper(cls, *args, **kwargs):
46 resp, body = f(cls, *args, **kwargs)
47
48 if 'uuid' in body:
49 cls.created_objects[resource].add(body['uuid'])
50
51 return resp, body
52 return wrapper
53 return decorator
54
55
Yuiko Takadaff785002015-12-17 15:56:42 +090056class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
57 test.BaseTestCase):
Yuiko Takadab6527002015-12-07 11:49:12 +090058 """Base class for Baremetal API tests."""
59
Julia Kreger3a07c4d2021-06-22 10:27:56 -070060 credentials = ['admin', 'system_admin']
Yuiko Takadab6527002015-12-07 11:49:12 +090061
62 @classmethod
63 def skip_checks(cls):
64 super(BaseBaremetalTest, cls).skip_checks()
Jim Rollenhagen1c11bdf2016-11-29 16:57:30 -050065 if not CONF.service_available.ironic:
66 raise cls.skipException('Ironic is not enabled.')
Yuiko Takadab6527002015-12-07 11:49:12 +090067 if CONF.baremetal.driver not in SUPPORTED_DRIVERS:
68 skip_msg = ('%s skipped as Ironic driver %s is not supported for '
69 'testing.' %
70 (cls.__name__, CONF.baremetal.driver))
71 raise cls.skipException(skip_msg)
72
Yuiko Takadaff785002015-12-17 15:56:42 +090073 cfg_min_version = CONF.baremetal.min_microversion
74 cfg_max_version = CONF.baremetal.max_microversion
75 api_version_utils.check_skip_with_microversion(cls.min_microversion,
76 cls.max_microversion,
77 cfg_min_version,
78 cfg_max_version)
79
80 @classmethod
81 def setup_credentials(cls):
82 cls.request_microversion = (
83 api_version_utils.select_request_microversion(
84 cls.min_microversion,
85 CONF.baremetal.min_microversion))
86 cls.services_microversion = {
87 CONF.baremetal.catalog_type: cls.request_microversion}
Julia Kreger3a07c4d2021-06-22 10:27:56 -070088
Yuiko Takadaff785002015-12-17 15:56:42 +090089 super(BaseBaremetalTest, cls).setup_credentials()
90
Yuiko Takadab6527002015-12-07 11:49:12 +090091 @classmethod
92 def setup_clients(cls):
93 super(BaseBaremetalTest, cls).setup_clients()
Julia Kreger3a07c4d2021-06-22 10:27:56 -070094 if CONF.enforce_scope.ironic:
95 cls.client = cls.os_system_admin.baremetal.BaremetalClient()
96 else:
97 cls.client = cls.os_admin.baremetal.BaremetalClient()
Yuiko Takadab6527002015-12-07 11:49:12 +090098
99 @classmethod
100 def resource_setup(cls):
101 super(BaseBaremetalTest, cls).resource_setup()
Yuiko Takadaff785002015-12-17 15:56:42 +0900102 cls.request_microversion = (
103 api_version_utils.select_request_microversion(
104 cls.min_microversion,
105 CONF.baremetal.min_microversion))
Yuiko Takadab6527002015-12-07 11:49:12 +0900106 cls.driver = CONF.baremetal.driver
107 cls.power_timeout = CONF.baremetal.power_timeout
Yuiko Takadaff785002015-12-17 15:56:42 +0900108 cls.unprovision_timeout = CONF.baremetal.unprovision_timeout
Yuiko Takadab6527002015-12-07 11:49:12 +0900109 cls.created_objects = {}
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200110 for resource in RESOURCE_TYPES + ['allocation']:
Yuiko Takadab6527002015-12-07 11:49:12 +0900111 cls.created_objects[resource] = set()
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200112 cls.deployed_nodes = set()
Yuiko Takadab6527002015-12-07 11:49:12 +0900113
114 @classmethod
115 def resource_cleanup(cls):
116 """Ensure that all created objects get destroyed."""
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000117 # Use the requested microversion for cleanup to ensure we can delete
118 # resources.
119 base.set_baremetal_api_microversion(cls.request_microversion)
Yuiko Takadab6527002015-12-07 11:49:12 +0900120 try:
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200121 for node in cls.deployed_nodes:
122 try:
123 cls.set_node_provision_state(node, 'deleted',
124 ['available', None])
125 except lib_exc.BadRequest:
126 pass
127
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200128 # Delete allocations explicitly after unprovisioning instances, but
129 # before deleting nodes.
130 for allocation in cls.created_objects['allocation']:
131 try:
132 cls.client.delete_allocation(allocation)
133 except lib_exc.NotFound:
134 pass
135
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100136 for node in cls.created_objects['node']:
137 try:
138 cls.client.update_node(node, instance_uuid=None)
139 except lib_exc.TempestException:
140 pass
141
Yuiko Takadab6527002015-12-07 11:49:12 +0900142 for resource in RESOURCE_TYPES:
143 uuids = cls.created_objects[resource]
144 delete_method = getattr(cls.client, 'delete_%s' % resource)
145 for u in uuids:
146 delete_method(u, ignore_errors=lib_exc.NotFound)
147 finally:
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000148 base.reset_baremetal_api_microversion()
Yuiko Takadab6527002015-12-07 11:49:12 +0900149 super(BaseBaremetalTest, cls).resource_cleanup()
150
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200151 def _assertExpected(self, expected, actual):
Ruby Loo6abadd42017-02-27 15:45:03 +0000152 """Check if expected keys/values exist in actual response body.
153
154 Check if the expected keys and values are in the actual response body.
155 It will not check the keys 'created_at' and 'updated_at', since they
156 will always have different values. Asserts if any expected key (or
157 corresponding value) is not in the actual response.
158
159 Note: this method has an underscore even though it is used outside of
160 this class, in order to distinguish this method from the more standard
161 assertXYZ methods.
162
163 :param expected: dict of key-value pairs that are expected to be in
164 'actual' dict.
165 :param actual: dict of key-value pairs.
166
167 """
Kyrylo Romanenko62c0c092017-02-20 18:22:27 +0200168 for key, value in expected.items():
169 if key not in ('created_at', 'updated_at'):
170 self.assertIn(key, actual)
171 self.assertEqual(value, actual[key])
172
Yuiko Takadaff785002015-12-17 15:56:42 +0900173 def setUp(self):
174 super(BaseBaremetalTest, self).setUp()
175 self.useFixture(api_microversion_fixture.APIMicroversionFixture(
176 self.request_microversion))
177
Yuiko Takadab6527002015-12-07 11:49:12 +0900178 @classmethod
179 @creates('chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500180 def create_chassis(cls, description=None, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900181 """Wrapper utility for creating test chassis.
182
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300183 :param description: A description of the chassis. If not supplied,
Yuiko Takadab6527002015-12-07 11:49:12 +0900184 a random value will be generated.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000185 :return: A tuple with the server response and the created chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900186
187 """
188 description = description or data_utils.rand_name('test-chassis')
SofiiaAndriichenko3c833962016-12-09 10:12:18 -0500189 resp, body = cls.client.create_chassis(description=description,
190 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900191 return resp, body
192
193 @classmethod
194 @creates('node')
Sam Betts6f083ce2018-07-03 14:41:39 +0100195 def create_node(cls, chassis_id, cpu_arch='x86_64', cpus=8, local_gb=10,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100196 memory_mb=4096, **kwargs):
Yuiko Takadab6527002015-12-07 11:49:12 +0900197 """Wrapper utility for creating test baremetal nodes.
198
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300199 :param chassis_id: The unique identifier of the chassis.
Sam Betts6f083ce2018-07-03 14:41:39 +0100200 :param cpu_arch: CPU architecture of the node. Default: x86_64.
Yuiko Takadab6527002015-12-07 11:49:12 +0900201 :param cpus: Number of CPUs. Default: 8.
202 :param local_gb: Disk size. Default: 10.
203 :param memory_mb: Available RAM. Default: 4096.
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100204 :param kwargs: Other optional node fields.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000205 :return: A tuple with the server response and the created node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900206
207 """
208 resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch,
209 cpus=cpus, local_gb=local_gb,
210 memory_mb=memory_mb,
Kyrylo Romanenko96876912017-02-24 18:14:46 +0200211 driver=cls.driver,
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100212 **kwargs)
Yuiko Takadab6527002015-12-07 11:49:12 +0900213
214 return resp, body
215
216 @classmethod
Dmitry Tantsur0325dbd2018-10-24 15:13:46 +0200217 def set_node_provision_state(cls, node_id, target, expected, timeout=None,
218 interval=None):
219 """Sets the node's provision state.
220
221 :param node_id: The unique identifier of the node.
222 :param target: Target provision state.
223 :param expected: Expected final provision state or list of states.
224 :param timeout: The timeout for reaching the expected state.
225 Defaults to client.build_timeout.
226 :param interval: An interval between show_node calls for status check.
227 Defaults to client.build_interval.
228 """
229 cls.client.set_node_provision_state(node_id, target)
230 waiters.wait_for_bm_node_status(cls.client, node_id,
231 'provision_state', expected,
232 timeout=timeout, interval=interval)
233
234 @classmethod
235 def provide_node(cls, node_id, cleaning_timeout=None):
236 """Make the node available.
237
238 :param node_id: The unique identifier of the node.
239 :param cleaning_timeout: The timeout to wait for cleaning.
240 Defaults to client.build_timeout.
241 """
242 _, body = cls.client.show_node(node_id)
243 current_state = body['provision_state']
244 if current_state == 'enroll':
245 cls.set_node_provision_state(node_id, 'manage', 'manageable',
246 timeout=60, interval=1)
247 current_state = 'manageable'
248 if current_state == 'manageable':
249 cls.set_node_provision_state(node_id, 'provide',
250 ['available', None],
251 timeout=cleaning_timeout)
252 current_state = 'available'
253 if current_state not in ('available', None):
254 raise RuntimeError("Cannot reach state 'available': node %(node)s "
255 "is in unexpected state %(state)s" %
256 {'node': node_id, 'state': current_state})
257
258 @classmethod
259 def deploy_node(cls, node_id, cleaning_timeout=None, deploy_timeout=None):
260 """Deploy the node.
261
262 :param node_id: The unique identifier of the node.
263 :param cleaning_timeout: The timeout to wait for cleaning.
264 Defaults to client.build_timeout.
265 :param deploy_timeout: The timeout to wait for deploy.
266 Defaults to client.build_timeout.
267 """
268 cls.provide_node(node_id, cleaning_timeout=cleaning_timeout)
269 cls.set_node_provision_state(node_id, 'active', 'active',
270 timeout=deploy_timeout)
271 cls.deployed_nodes.add(node_id)
272
273 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900274 @creates('port')
Mark Goddard53bb68c2017-06-05 11:35:28 +0100275 def create_port(cls, node_id, address, extra=None, uuid=None,
276 portgroup_uuid=None, physical_network=None):
Yuiko Takadab6527002015-12-07 11:49:12 +0900277 """Wrapper utility for creating test ports.
278
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300279 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900280 :param address: MAC address of the port.
281 :param extra: Meta data of the port. If not supplied, an empty
282 dictionary will be created.
283 :param uuid: UUID of the port.
Mark Goddard53bb68c2017-06-05 11:35:28 +0100284 :param portgroup_uuid: The UUID of a portgroup of which this port is a
285 member.
286 :param physical_network: The physical network to which the port is
287 attached.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000288 :return: A tuple with the server response and the created port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900289
290 """
291 extra = extra or {}
292 resp, body = cls.client.create_port(address=address, node_id=node_id,
Mark Goddard53bb68c2017-06-05 11:35:28 +0100293 extra=extra, uuid=uuid,
294 portgroup_uuid=portgroup_uuid,
295 physical_network=physical_network)
Yuiko Takadab6527002015-12-07 11:49:12 +0900296
297 return resp, body
298
299 @classmethod
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200300 @creates('portgroup')
301 def create_portgroup(cls, node_uuid, **kwargs):
302 """Wrapper utility for creating test port groups.
303
304 :param node_uuid: The unique identifier of the node.
Kyrylo Romanenko8e1be642017-03-20 16:01:33 +0000305 :return: A tuple with the server response and the created port group.
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200306 """
307 resp, body = cls.client.create_portgroup(node_uuid=node_uuid, **kwargs)
308
309 return resp, body
310
311 @classmethod
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700312 @creates('volume_connector')
313 def create_volume_connector(cls, node_uuid, **kwargs):
314 """Wrapper utility for creating test volume connector.
315
316 :param node_uuid: The unique identifier of the node.
317 :return: A tuple with the server response and the created volume
318 connector.
319 """
320 resp, body = cls.client.create_volume_connector(node_uuid=node_uuid,
321 **kwargs)
322
323 return resp, body
324
325 @classmethod
326 @creates('volume_target')
327 def create_volume_target(cls, node_uuid, **kwargs):
328 """Wrapper utility for creating test volume target.
329
330 :param node_uuid: The unique identifier of the node.
331 :return: A tuple with the server response and the created volume
332 target.
333 """
334 resp, body = cls.client.create_volume_target(node_uuid=node_uuid,
335 **kwargs)
336
337 return resp, body
338
339 @classmethod
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000340 @creates('deploy_template')
341 def create_deploy_template(cls, name, **kwargs):
342 """Wrapper utility for creating test deploy template.
343
344 :param name: The name of the deploy template.
345 :return: A tuple with the server response and the created deploy
346 template.
347 """
348 resp, body = cls.client.create_deploy_template(name=name, **kwargs)
349
350 return resp, body
351
352 @classmethod
Yuiko Takadab6527002015-12-07 11:49:12 +0900353 def delete_chassis(cls, chassis_id):
354 """Deletes a chassis having the specified UUID.
355
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300356 :param chassis_id: The unique identifier of the chassis.
Yuiko Takadab6527002015-12-07 11:49:12 +0900357 :return: Server response.
358
359 """
360
361 resp, body = cls.client.delete_chassis(chassis_id)
362
363 if chassis_id in cls.created_objects['chassis']:
364 cls.created_objects['chassis'].remove(chassis_id)
365
366 return resp
367
368 @classmethod
369 def delete_node(cls, node_id):
370 """Deletes a node having the specified UUID.
371
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300372 :param node_id: The unique identifier of the node.
Yuiko Takadab6527002015-12-07 11:49:12 +0900373 :return: Server response.
374
375 """
376
377 resp, body = cls.client.delete_node(node_id)
378
379 if node_id in cls.created_objects['node']:
380 cls.created_objects['node'].remove(node_id)
381
382 return resp
383
384 @classmethod
385 def delete_port(cls, port_id):
386 """Deletes a port having the specified UUID.
387
Kyrylo Romanenkodbda6492016-09-14 14:58:54 +0300388 :param port_id: The unique identifier of the port.
Yuiko Takadab6527002015-12-07 11:49:12 +0900389 :return: Server response.
390
391 """
392
393 resp, body = cls.client.delete_port(port_id)
394
395 if port_id in cls.created_objects['port']:
396 cls.created_objects['port'].remove(port_id)
397
398 return resp
399
Kyrylo Romanenko2193e542017-02-21 18:47:53 +0200400 @classmethod
401 def delete_portgroup(cls, portgroup_ident):
402 """Deletes a port group having the specified UUID or name.
403
404 :param portgroup_ident: The name or UUID of the port group.
405 :return: Server response.
406 """
407 resp, body = cls.client.delete_portgroup(portgroup_ident)
408
409 if portgroup_ident in cls.created_objects['portgroup']:
410 cls.created_objects['portgroup'].remove(portgroup_ident)
411
412 return resp
413
Nguyen Hung Phuongca69ffe2017-06-13 14:22:10 +0700414 @classmethod
415 def delete_volume_connector(cls, volume_connector_id):
416 """Deletes a volume connector having the specified UUID.
417
418 :param volume_connector_id: The UUID of the volume connector.
419 :return: Server response.
420 """
421 resp, body = cls.client.delete_volume_connector(volume_connector_id)
422
423 if volume_connector_id in cls.created_objects['volume_connector']:
424 cls.created_objects['volume_connector'].remove(
425 volume_connector_id)
426
427 return resp
428
429 @classmethod
430 def delete_volume_target(cls, volume_target_id):
431 """Deletes a volume target having the specified UUID.
432
433 :param volume_target_id: The UUID of the volume target.
434 :return: Server response.
435 """
436 resp, body = cls.client.delete_volume_target(volume_target_id)
437
438 if volume_target_id in cls.created_objects['volume_target']:
439 cls.created_objects['volume_target'].remove(volume_target_id)
440
441 return resp
442
Mark Goddard6f2e72c2019-02-15 12:23:34 +0000443 @classmethod
444 def delete_deploy_template(cls, deploy_template_ident):
445 """Deletes a deploy template having the specified name or UUID.
446
447 :param deploy_template_ident: Name or UUID of the deploy template.
448 :return: Server response.
449 """
450 resp, body = cls.client.delete_deploy_template(deploy_template_ident)
451
452 if deploy_template_ident in cls.created_objects['deploy_template']:
453 cls.created_objects['deploy_template'].remove(
454 deploy_template_ident)
455
456 return resp
457
Yuiko Takadab6527002015-12-07 11:49:12 +0900458 def validate_self_link(self, resource, uuid, link):
459 """Check whether the given self link formatted correctly."""
460 expected_link = "{base}/{pref}/{res}/{uuid}".format(
Andrey Shestakov7cdfac32017-02-06 14:44:23 +0200461 base=self.client.base_url.rstrip('/'),
Yuiko Takadab6527002015-12-07 11:49:12 +0900462 pref=self.client.uri_prefix,
463 res=resource,
464 uuid=uuid)
465 self.assertEqual(expected_link, link)
Dmitry Tantsuraac618b2019-04-18 12:40:15 +0200466
467 @classmethod
468 @creates('allocation')
469 def create_allocation(cls, resource_class, **kwargs):
470 """Wrapper utility for creating test allocations.
471
472 :param resource_class: Resource class to request.
473 :param kwargs: Other fields to pass.
474 :return: A tuple with the server response and the created allocation.
475 """
476 resp, body = cls.client.create_allocation(resource_class, **kwargs)
477 return resp, body