baremetal: implement set_node_power_state in the proxy

This call was implemented in the shade part, but not in the baremetal
proxy. This change implements it, and makes the share part use it.
As a side effect, soft power actions (from API 1.27) are now supported.

Change-Id: I6f2f0aa7717c0f9423d6a3ddc163c4fc0d8152d0
This commit is contained in:
Dmitry Tantsur 2019-02-13 12:43:12 +01:00
parent dc092757d1
commit b7b7353e55
6 changed files with 93 additions and 37 deletions

View File

@ -22,6 +22,7 @@ Node Operations
.. automethod:: openstack.baremetal.v1._proxy.Proxy.get_node
.. automethod:: openstack.baremetal.v1._proxy.Proxy.find_node
.. automethod:: openstack.baremetal.v1._proxy.Proxy.nodes
.. automethod:: openstack.baremetal.v1._proxy.Proxy.set_node_power_state
.. automethod:: openstack.baremetal.v1._proxy.Proxy.set_node_provision_state
.. automethod:: openstack.baremetal.v1._proxy.Proxy.wait_for_nodes_provision_state
.. automethod:: openstack.baremetal.v1._proxy.Proxy.wait_for_node_reservation

View File

@ -339,6 +339,19 @@ class Proxy(proxy.Proxy):
{'nodes': ', '.join(n.id for n in remaining),
'target': expected_state})
def set_node_power_state(self, node, target):
"""Run an action modifying node's power state.
This call is asynchronous, it will return success as soon as the Bare
Metal service acknowledges the request.
: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.
"""
self._get_resource(_node.Node, node).set_power_state(self, target)
def wait_for_node_reservation(self, node, timeout=None):
"""Wait for a lock on the node to be released.

View File

@ -457,6 +457,41 @@ 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):
"""Run an action modifying this node's power state.
This call is asynchronous, it will return success as soon as the Bare
Metal service acknowledges the request.
: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.
"""
session = self._get_session(session)
if target.startswith("soft "):
version = '1.27'
else:
version = None
version = utils.pick_microversion(session, version)
# TODO(dtantsur): server timeout support
body = {'target': target}
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'states', 'power')
response = session.put(
request.url, json=body,
headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
msg = ("Failed to set power state for bare metal node {node} "
"to {target}".format(node=self.id, target=target))
exceptions.raise_from_response(response, error_message=msg)
def attach_vif(self, session, vif_id, retry_on_conflict=True):
"""Attach a VIF to the node.

View File

@ -10141,40 +10141,6 @@ class _OpenStackCloudMixin(_normalize.Normalizer):
"""
self.set_machine_maintenance_state(name_or_id, False)
def _set_machine_power_state(self, name_or_id, state):
"""Set machine power state to on or off
This private method allows a user to turn power on or off to
a node via the Baremetal API.
:params string name_or_id: A string representing the baremetal
node to have power turned to an "on"
state.
:params string state: A value of "on", "off", or "reboot" that is
passed to the baremetal API to be asserted to
the machine. In the case of the "reboot" state,
Ironic will return the host to the "on" state.
:raises: OpenStackCloudException on operation error or.
:returns: None
"""
msg = ("Error setting machine power state to {state} on node "
"{node}").format(state=state, node=name_or_id)
url = '/nodes/{name_or_id}/states/power'.format(name_or_id=name_or_id)
if 'reboot' in state:
desired_state = 'rebooting'
else:
desired_state = 'power {state}'.format(state=state)
payload = {'target': desired_state}
_utils._call_client_and_retry(self._baremetal_client.put,
url,
retry_on=[409, 503],
json=payload,
error_message=msg,
microversion="1.6")
return None
def set_machine_power_on(self, name_or_id):
"""Activate baremetal machine power
@ -10188,7 +10154,7 @@ class _OpenStackCloudMixin(_normalize.Normalizer):
:returns: None
"""
self._set_machine_power_state(name_or_id, 'on')
self.baremetal.set_node_power_state(name_or_id, 'power on')
def set_machine_power_off(self, name_or_id):
"""De-activate baremetal machine power
@ -10203,7 +10169,7 @@ class _OpenStackCloudMixin(_normalize.Normalizer):
:returns:
"""
self._set_machine_power_state(name_or_id, 'off')
self.baremetal.set_node_power_state(name_or_id, 'power off')
def set_machine_power_reboot(self, name_or_id):
"""De-activate baremetal machine power
@ -10220,7 +10186,7 @@ class _OpenStackCloudMixin(_normalize.Normalizer):
:returns: None
"""
self._set_machine_power_state(name_or_id, 'reboot')
self.baremetal.set_node_power_state(name_or_id, 'rebooting')
def activate_node(self, uuid, configdrive=None,
wait=False, timeout=1200):

View File

@ -117,6 +117,19 @@ class TestBareMetalNode(base.BaseBaremetalTest):
wait=True)
self.assertEqual(node.provision_state, 'available')
def test_node_power_state(self):
node = self.create_node()
self.assertIsNone(node.power_state)
self.conn.baremetal.set_node_power_state(node, 'power on')
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')
node = self.conn.baremetal.get_node(node.id)
self.assertEqual('power off', node.power_state)
def test_node_validate(self):
node = self.create_node()
# Fake hardware passes validation for all interfaces

View File

@ -487,3 +487,31 @@ class TestNodeWaitForReservation(base.TestCase):
self.node.wait_for_reservation,
self.session, timeout=0.001)
mock_fetch.assert_called_with(self.node, self.session)
@mock.patch.object(exceptions, 'raise_from_response', mock.Mock())
class TestNodeSetPowerState(base.TestCase):
def setUp(self):
super(TestNodeSetPowerState, self).setUp()
self.node = node.Node(**FAKE)
self.session = mock.Mock(spec=adapter.Adapter,
default_microversion=None)
def test_power_on(self):
self.node.set_power_state(self.session, 'power on')
self.session.put.assert_called_once_with(
'nodes/%s/states/power' % FAKE['uuid'],
json={'target': 'power on'},
headers=mock.ANY,
microversion=None,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
def test_soft_power_on(self):
self.node.set_power_state(self.session, 'soft power off')
self.session.put.assert_called_once_with(
'nodes/%s/states/power' % FAKE['uuid'],
json={'target': 'soft power off'},
headers=mock.ANY,
microversion='1.27',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)