Support waiting for bare metal power states
Use the new functionality to fix a race in the functional job. Depends-On: https://review.opendev.org/757293 Change-Id: Icc881acebd72ed75e654677d2d5dbff63c969298
This commit is contained in:
parent
8c44ff176c
commit
97f7095abd
@ -18,8 +18,8 @@ Node Operations
|
||||
:noindex:
|
||||
:members: nodes, find_node, get_node, create_node, update_node, patch_node, delete_node,
|
||||
validate_node, set_node_power_state, set_node_provision_state,
|
||||
wait_for_nodes_provision_state, wait_for_node_reservation,
|
||||
set_node_maintenance, unset_node_maintenance
|
||||
wait_for_nodes_provision_state, wait_for_node_power_state,
|
||||
wait_for_node_reservation, set_node_maintenance, unset_node_maintenance
|
||||
|
||||
Port Operations
|
||||
^^^^^^^^^^^^^^^
|
||||
|
@ -11,6 +11,14 @@ The ``Node`` class inherits from :class:`~openstack.resource.Resource`.
|
||||
.. autoclass:: openstack.baremetal.v1.node.Node
|
||||
:members:
|
||||
|
||||
The PowerAction Class
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``PowerAction`` enumeration represents known power actions.
|
||||
|
||||
.. autoclass:: openstack.baremetal.v1.node.PowerAction
|
||||
:members:
|
||||
|
||||
The ValidationResult Class
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -49,6 +49,15 @@ EXPECTED_STATES = {
|
||||
}
|
||||
"""Mapping of provisioning actions to expected stable states."""
|
||||
|
||||
EXPECTED_POWER_STATES = {
|
||||
'power on': 'power on',
|
||||
'power off': 'power off',
|
||||
'rebooting': 'power on',
|
||||
'soft power off': 'power off',
|
||||
'soft rebooting': 'power on',
|
||||
}
|
||||
"""Mapping of target power states to expected power states."""
|
||||
|
||||
STATE_VERSIONS = {
|
||||
'enroll': '1.11',
|
||||
'manageable': '1.4',
|
||||
|
@ -442,7 +442,7 @@ class Proxy(proxy.Proxy):
|
||||
else:
|
||||
return _node.WaitResult(finished, failed, remaining)
|
||||
|
||||
def set_node_power_state(self, node, target):
|
||||
def set_node_power_state(self, node, target, wait=False, timeout=None):
|
||||
"""Run an action modifying node's power state.
|
||||
|
||||
This call is asynchronous, it will return success as soon as the Bare
|
||||
@ -450,10 +450,30 @@ class Proxy(proxy.Proxy):
|
||||
|
||||
:param node: The value can be the name or ID of a node or a
|
||||
:class:`~openstack.baremetal.v1.node.Node` instance.
|
||||
:param target: Target power state, e.g. "rebooting", "power on".
|
||||
See the Bare Metal service documentation for available actions.
|
||||
:param target: Target power state, one of
|
||||
:class:`~openstack.baremetal.v1.node.PowerAction` or a string.
|
||||
:param wait: Whether to wait for the node to get into the expected
|
||||
state.
|
||||
:param timeout: If ``wait`` is set to ``True``, specifies how much (in
|
||||
seconds) to wait for the expected state to be reached. The value of
|
||||
``None`` (the default) means no client-side timeout.
|
||||
"""
|
||||
self._get_resource(_node.Node, node).set_power_state(self, target)
|
||||
self._get_resource(_node.Node, node).set_power_state(
|
||||
self, target, wait=wait, timeout=timeout)
|
||||
|
||||
def wait_for_node_power_state(self, node, expected_state, timeout=None):
|
||||
"""Wait for the node to reach the power state.
|
||||
|
||||
:param node: The value can be the name or ID of a node or a
|
||||
:class:`~openstack.baremetal.v1.node.Node` instance.
|
||||
:param timeout: How much (in seconds) to wait for the target state
|
||||
to be reached. The value of ``None`` (the default) means
|
||||
no timeout.
|
||||
|
||||
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
|
||||
"""
|
||||
res = self._get_resource(_node.Node, node)
|
||||
return res.wait_for_power_state(self, expected_state, timeout=timeout)
|
||||
|
||||
def wait_for_node_reservation(self, node, timeout=None):
|
||||
"""Wait for a lock on the node to be released.
|
||||
|
@ -11,6 +11,7 @@
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import enum
|
||||
|
||||
from openstack.baremetal.v1 import _common
|
||||
from openstack import exceptions
|
||||
@ -32,6 +33,24 @@ class ValidationResult:
|
||||
self.reason = reason
|
||||
|
||||
|
||||
class PowerAction(enum.Enum):
|
||||
"""Mapping from an action to a target power state."""
|
||||
|
||||
POWER_ON = 'power on'
|
||||
"""Power on the node."""
|
||||
|
||||
POWER_OFF = 'power off'
|
||||
"""Power off the node (using hard power off)."""
|
||||
REBOOT = 'rebooting'
|
||||
"""Reboot the node (using hard power off)."""
|
||||
|
||||
SOFT_POWER_OFF = 'soft power off'
|
||||
"""Power off the node using soft power off."""
|
||||
|
||||
SOFT_REBOOT = 'soft rebooting'
|
||||
"""Reboot the node using soft power off."""
|
||||
|
||||
|
||||
class WaitResult(collections.namedtuple('WaitResult',
|
||||
['success', 'failure', 'timeout'])):
|
||||
"""A named tuple representing a result of waiting for several nodes.
|
||||
@ -416,6 +435,34 @@ class Node(_common.ListMixin, resource.Resource):
|
||||
else:
|
||||
return self.fetch(session)
|
||||
|
||||
def wait_for_power_state(self, session, expected_state, timeout=None):
|
||||
"""Wait for the node to reach the expected power state.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||
:param expected_state: The expected power state to reach.
|
||||
:param timeout: If ``wait`` is set to ``True``, specifies how much (in
|
||||
seconds) to wait for the expected state to be reached. The value of
|
||||
``None`` (the default) means no client-side timeout.
|
||||
|
||||
:return: This :class:`Node` instance.
|
||||
:raises: :class:`~openstack.exceptions.ResourceTimeout` on timeout.
|
||||
"""
|
||||
for count in utils.iterate_timeout(
|
||||
timeout,
|
||||
"Timeout waiting for node %(node)s to reach "
|
||||
"power state '%(state)s'" % {'node': self.id,
|
||||
'state': expected_state}):
|
||||
self.fetch(session)
|
||||
if self.power_state == expected_state:
|
||||
return self
|
||||
|
||||
session.log.debug(
|
||||
'Still waiting for node %(node)s to reach power state '
|
||||
'"%(target)s", the current state is "%(state)s"',
|
||||
{'node': self.id, 'target': expected_state,
|
||||
'state': self.power_state})
|
||||
|
||||
def wait_for_provision_state(self, session, expected_state, timeout=None,
|
||||
abort_on_failed_state=True):
|
||||
"""Wait for the node to reach the expected state.
|
||||
@ -532,8 +579,7 @@ class Node(_common.ListMixin, resource.Resource):
|
||||
"the last error is %(error)s" %
|
||||
{'node': self.id, 'error': self.last_error})
|
||||
|
||||
# TODO(dtantsur): waiting for power state
|
||||
def set_power_state(self, session, target):
|
||||
def set_power_state(self, session, target, wait=False, timeout=None):
|
||||
"""Run an action modifying this node's power state.
|
||||
|
||||
This call is asynchronous, it will return success as soon as the Bare
|
||||
@ -541,9 +587,22 @@ class Node(_common.ListMixin, resource.Resource):
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||
:param target: Target power state, e.g. "rebooting", "power on".
|
||||
See the Bare Metal service documentation for available actions.
|
||||
:param target: Target power state, as a :class:`PowerAction` or
|
||||
a string.
|
||||
:param wait: Whether to wait for the expected power state to be
|
||||
reached.
|
||||
:param timeout: Timeout (in seconds) to wait for the target state to be
|
||||
reached. If ``None``, wait without timeout.
|
||||
"""
|
||||
if isinstance(target, PowerAction):
|
||||
target = target.value
|
||||
if wait:
|
||||
try:
|
||||
expected = _common.EXPECTED_POWER_STATES[target]
|
||||
except KeyError:
|
||||
raise ValueError("Cannot use target power state %s with wait, "
|
||||
"the expected state is not known" % target)
|
||||
|
||||
session = self._get_session(session)
|
||||
|
||||
if target.startswith("soft "):
|
||||
@ -567,6 +626,9 @@ class Node(_common.ListMixin, resource.Resource):
|
||||
"to {target}".format(node=self.id, target=target))
|
||||
exceptions.raise_from_response(response, error_message=msg)
|
||||
|
||||
if wait:
|
||||
self.wait_for_power_state(session, expected, timeout=timeout)
|
||||
|
||||
def attach_vif(self, session, vif_id, retry_on_conflict=True):
|
||||
"""Attach a VIF to the node.
|
||||
|
||||
|
@ -177,12 +177,11 @@ class TestBareMetalNode(base.BaseBaremetalTest):
|
||||
node = self.create_node()
|
||||
self.assertIsNone(node.power_state)
|
||||
|
||||
self.conn.baremetal.set_node_power_state(node, 'power on')
|
||||
self.conn.baremetal.set_node_power_state(node, 'power on', wait=True)
|
||||
node = self.conn.baremetal.get_node(node.id)
|
||||
# Fake nodes react immediately to power requests.
|
||||
self.assertEqual('power on', node.power_state)
|
||||
|
||||
self.conn.baremetal.set_node_power_state(node, 'power off')
|
||||
self.conn.baremetal.set_node_power_state(node, 'power off', wait=True)
|
||||
node = self.conn.baremetal.get_node(node.id)
|
||||
self.assertEqual('power off', node.power_state)
|
||||
|
||||
|
@ -811,3 +811,30 @@ class TestNodePatch(base.TestCase):
|
||||
self.assertIn('1.45', commit_args)
|
||||
self.assertEqual(commit_kwargs['retry_on_conflict'], True)
|
||||
mock_patch.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch('time.sleep', lambda _t: None)
|
||||
@mock.patch.object(node.Node, 'fetch', autospec=True)
|
||||
class TestNodeWaitForPowerState(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestNodeWaitForPowerState, self).setUp()
|
||||
self.node = node.Node(**FAKE)
|
||||
self.session = mock.Mock()
|
||||
|
||||
def test_success(self, mock_fetch):
|
||||
self.node.power_state = 'power on'
|
||||
|
||||
def _get_side_effect(_self, session):
|
||||
self.node.power_state = 'power off'
|
||||
self.assertIs(session, self.session)
|
||||
|
||||
mock_fetch.side_effect = _get_side_effect
|
||||
|
||||
node = self.node.wait_for_power_state(self.session, 'power off')
|
||||
self.assertIs(node, self.node)
|
||||
|
||||
def test_timeout(self, mock_fetch):
|
||||
self.node.power_state = 'power on'
|
||||
self.assertRaises(exceptions.ResourceTimeout,
|
||||
self.node.wait_for_power_state,
|
||||
self.session, 'power off', timeout=0.001)
|
||||
|
4
releasenotes/notes/power-wait-751083852f958cb4.yaml
Normal file
4
releasenotes/notes/power-wait-751083852f958cb4.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Support waiting for bare metal power states.
|
Loading…
Reference in New Issue
Block a user