blob: d80ba18b683cd7911571a9508706a198bf005969 [file] [log] [blame]
Yuiko Takadab6527002015-12-07 11:49:12 +09001# All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
dbiletskiy64cc44e2025-09-16 10:09:22 +020015import time
16
Julia Kreger32737082020-04-16 16:55:28 -070017from oslo_log import log
Vladyslav Drok0cad0442016-12-14 12:48:20 +020018from tempest import config
wangxiyuandbd33dc2017-02-10 09:40:50 +080019from tempest.lib.common.utils import test_utils
Lenny Verkhovsky88625042016-03-08 17:44:00 +020020from tempest.lib import exceptions as lib_exc
Yuiko Takadab6527002015-12-07 11:49:12 +090021
Vladyslav Drok0cad0442016-12-14 12:48:20 +020022from ironic_tempest_plugin.common import utils
23
Julia Kreger32737082020-04-16 16:55:28 -070024LOG = log.getLogger(__name__)
25
Vladyslav Drok0cad0442016-12-14 12:48:20 +020026CONF = config.CONF
27
28
29def _determine_and_check_timeout_interval(timeout, default_timeout,
30 interval, default_interval):
31 if timeout is None:
32 timeout = default_timeout
33 if interval is None:
34 interval = default_interval
Takashi Kajinami6bddaab2022-05-10 00:58:56 +090035 if (not isinstance(timeout, int)
36 or not isinstance(interval, int)
Riccardo Pittau441c5062020-03-30 15:06:28 +020037 or timeout < 0 or interval < 0):
Vladyslav Drok0cad0442016-12-14 12:48:20 +020038 raise AssertionError(
39 'timeout and interval should be >= 0 or None, current values are: '
40 '%(timeout)s, %(interval)s respectively. If timeout and/or '
41 'interval are None, the default_timeout and default_interval are '
42 'used, and they should be integers >= 0, current values are: '
43 '%(default_timeout)s, %(default_interval)s respectively.' % dict(
44 timeout=timeout, interval=interval,
45 default_timeout=default_timeout,
46 default_interval=default_interval)
47 )
48 return timeout, interval
49
Yuiko Takadab6527002015-12-07 11:49:12 +090050
Vladyslav Drok0ac08c82016-12-13 19:51:20 +020051def wait_for_bm_node_status(client, node_id, attr, status, timeout=None,
Dmitry Tantsur4e2116d2019-08-27 17:01:58 +020052 interval=None, abort_on_error_state=False):
Yuiko Takadab6527002015-12-07 11:49:12 +090053 """Waits for a baremetal node attribute to reach given status.
54
Vladyslav Drok0ac08c82016-12-13 19:51:20 +020055 :param client: an instance of tempest plugin BaremetalClient.
56 :param node_id: identifier of the node.
57 :param attr: node's API-visible attribute to check status of.
Vladyslav Drok0cad0442016-12-14 12:48:20 +020058 :param status: desired status. Can be a list of statuses.
Vladyslav Drok0ac08c82016-12-13 19:51:20 +020059 :param timeout: the timeout after which the check is considered as failed.
60 Defaults to client.build_timeout.
61 :param interval: an interval between show_node calls for status check.
62 Defaults to client.build_interval.
Dmitry Tantsur4e2116d2019-08-27 17:01:58 +020063 :param abort_on_error_state: whether to abort waiting if the node reaches
64 an error state.
Vladyslav Drok0ac08c82016-12-13 19:51:20 +020065
66 The client should have a show_node(node_id) method to get the node.
Yuiko Takadab6527002015-12-07 11:49:12 +090067 """
Vladyslav Drok0cad0442016-12-14 12:48:20 +020068 timeout, interval = _determine_and_check_timeout_interval(
69 timeout, client.build_timeout, interval, client.build_interval)
Vladyslav Drok0ac08c82016-12-13 19:51:20 +020070
Vladyslav Drok0cad0442016-12-14 12:48:20 +020071 if not isinstance(status, list):
72 status = [status]
Yuiko Takadab6527002015-12-07 11:49:12 +090073
Vladyslav Drok0cad0442016-12-14 12:48:20 +020074 def is_attr_in_status():
75 node = utils.get_node(client, node_id=node_id)
76 if node[attr] in status:
77 return True
Vasyl Saienkof49032d2025-04-11 13:07:20 +000078 elif not node['provision_state']:
79 return False
Dmitry Tantsur4e2116d2019-08-27 17:01:58 +020080 elif (abort_on_error_state
Julia Kreger982d1772022-03-30 16:40:44 -070081 and (node['provision_state'].endswith(' failed')
82 or node['provision_state'] == 'error')):
Julia Kreger32737082020-04-16 16:55:28 -070083 msg = ('Node %(node)s reached failure state %(state)s while '
84 'waiting for %(attr)s=%(expected)s. '
85 'Error: %(error)s' %
86 {'node': node_id, 'state': node['provision_state'],
87 'attr': attr, 'expected': status,
88 'error': node.get('last_error')})
89 LOG.debug(msg)
90 raise lib_exc.TempestException(msg)
Vladyslav Drok0cad0442016-12-14 12:48:20 +020091 return False
Yuiko Takadab6527002015-12-07 11:49:12 +090092
Vladyslav Drok0cad0442016-12-14 12:48:20 +020093 if not test_utils.call_until_true(is_attr_in_status, timeout,
94 interval):
95 message = ('Node %(node_id)s failed to reach %(attr)s=%(status)s '
96 'within the required time (%(timeout)s s).' %
97 {'node_id': node_id,
98 'attr': attr,
99 'status': status,
100 'timeout': timeout})
101 caller = test_utils.find_test_caller()
102 if caller:
103 message = '(%s) %s' % (caller, message)
Julia Kreger32737082020-04-16 16:55:28 -0700104 LOG.debug(message)
Vladyslav Drok0cad0442016-12-14 12:48:20 +0200105 raise lib_exc.TimeoutException(message)
Vladyslav Drok0ac08c82016-12-13 19:51:20 +0200106
Vladyslav Drok0cad0442016-12-14 12:48:20 +0200107
108def wait_node_instance_association(client, instance_uuid, timeout=None,
109 interval=None):
110 """Waits for a node to be associated with instance_id.
111
112 :param client: an instance of tempest plugin BaremetalClient.
113 :param instance_uuid: UUID of the instance.
114 :param timeout: the timeout after which the check is considered as failed.
115 Defaults to CONF.baremetal.association_timeout.
116 :param interval: an interval between show_node calls for status check.
117 Defaults to client.build_interval.
118 """
119 timeout, interval = _determine_and_check_timeout_interval(
120 timeout, CONF.baremetal.association_timeout,
121 interval, client.build_interval)
122
123 def is_some_node_associated():
124 node = utils.get_node(client, instance_uuid=instance_uuid)
125 return node is not None
126
127 if not test_utils.call_until_true(is_some_node_associated, timeout,
128 interval):
Vladyslav Drok401fd462017-03-13 16:33:52 +0000129 msg = ('Timed out waiting to get Ironic node by instance UUID '
130 '%(instance_uuid)s within the required time (%(timeout)s s).'
131 % {'instance_uuid': instance_uuid, 'timeout': timeout})
Vladyslav Drok0cad0442016-12-14 12:48:20 +0200132 raise lib_exc.TimeoutException(msg)
Dmitry Tantsur47ff4892019-02-08 17:24:46 +0100133
134
135def wait_for_allocation(client, allocation_ident, timeout=15, interval=1,
136 expect_error=False):
137 """Wait for the allocation to become active.
138
139 :param client: an instance of tempest plugin BaremetalClient.
140 :param allocation_ident: UUID or name of the allocation.
141 :param timeout: the timeout after which the allocation is considered as
142 failed. Defaults to 15 seconds.
143 :param interval: an interval between show_allocation calls.
144 Defaults to 1 second.
145 :param expect_error: if True, return successfully even in case of an error.
146 """
147 result = [None] # a mutable object to modify in the closure
148
149 def check():
150 result[0] = client.show_allocation(allocation_ident)
151 allocation = result[0][1]
152
153 if allocation['state'] == 'error' and not expect_error:
154 raise lib_exc.TempestException(
155 "Allocation %(ident)s failed: %(error)s" %
156 {'ident': allocation_ident,
157 'error': allocation.get('last_error')})
158 else:
159 return allocation['state'] != 'allocating'
160
161 if not test_utils.call_until_true(check, timeout, interval):
162 msg = ('Timed out waiting for the allocation %s to become active' %
163 allocation_ident)
164 raise lib_exc.TimeoutException(msg)
165
166 return result[0]
Julia Kreger982d1772022-03-30 16:40:44 -0700167
168
169def wait_node_value_in_field(client, node_id, field, value,
170 raise_if_insufficent_access=True,
171 timeout=None, interval=None):
172 """Waits for a node to have a field value appear.
173
174 :param client: an instance of tempest plugin BaremetalClient.
175 :param node_id: the UUID of the node
176 :param field: the field in the node object to examine
177 :param value: the value/key with-in the field to look for.
178 :param timeout: the timeout after which the check is considered as failed.
179 :param interval: an interval between show_node calls for status check.
180 """
181
182 def is_field_updated():
183 node = utils.get_node(client, node_id=node_id)
184 field_value = node[field]
185 if raise_if_insufficent_access and '** Redacted' in field_value:
186 msg = ('Unable to see contents of redacted field '
Doug Goldsteina736e822025-02-02 11:27:26 -0500187 'indicating insufficient access to execute this test.')
Julia Kreger982d1772022-03-30 16:40:44 -0700188 raise lib_exc.InsufficientAPIAccess(msg)
189 return value in field_value
190
191 if not test_utils.call_until_true(is_field_updated, timeout,
192 interval):
193 msg = ('Timed out waiting to get Ironic node by node_id '
194 '%(node_id)s within the required time (%(timeout)s s). '
195 'Field value %(value) did not appear in field %(field)s.'
196 % {'node_id': node_id, 'timeout': timeout,
197 'field': field, 'value': value})
198 raise lib_exc.TimeoutException(msg)
dbiletskiy64cc44e2025-09-16 10:09:22 +0200199
200
201def wait_for_deleted_status_or_not_found(
202 show_client, id, status_key, check_interval, check_timeout,
203 root_tag=None, **kwargs):
204 """Waits for an object to reach a DELETED status or be not found (404).
205
206 :param show_client: The tempest service client show method.
207 Ex. cls.os_primary.servers_client.show_server
208 :param id: The id of the object to query.
209 :param status_key: The key of the status field in the response.
210 Ex. provisioning_status
211 :param check_interval: How often to check the status, in seconds.
212 :param check_timeout: The maximum time, in seconds, to check the status.
213 :param root_tag: The root tag on the response to remove, if any.
214 :raises CommandFailed: Raised if the object goes into ERROR and ERROR was
215 not the desired status.
216 :raises TimeoutException: The object did not achieve the status or ERROR in
217 the check_timeout period.
218 :returns: None
219 """
220 start = int(time.time())
221 LOG.info(
222 "Waiting for %s status to update to DELETED or be not found (404)",
223 show_client.__name__,
224 )
225 while True:
226 try:
227 response = show_client(id, **kwargs)
228 except lib_exc.NotFound:
229 LOG.info("Object with ID %s is not found.", id)
230 return
231
232 if root_tag:
233 object_details = response[root_tag]
234 else:
235 object_details = response
236
237 if object_details[status_key] == 'DELETED':
238 LOG.info(
239 "%s's status updated to DELETED.",
240 show_client.__name__,
241 )
242 return
243 if int(time.time()) - start >= check_timeout:
244 message = (
245 '{name} {field} failed to update to DELETED or become not '
246 'found (404) within the required time {timeout}. Current '
247 'status of {name}: {status}'.format(
248 name=show_client.__name__,
249 timeout=check_timeout,
250 status=object_details[status_key],
251 field=status_key
252 ))
253 caller = test_utils.find_test_caller()
254 if caller:
255 message = '({caller}) {message}'.format(caller=caller,
256 message=message)
257 raise lib_exc.TimeoutException(message)
258
259 time.sleep(check_interval)