diff --git a/doc/source/user/proxies/baremetal.rst b/doc/source/user/proxies/baremetal.rst index d8978c0ff..da35cdcab 100644 --- a/doc/source/user/proxies/baremetal.rst +++ b/doc/source/user/proxies/baremetal.rst @@ -16,10 +16,18 @@ Node Operations ^^^^^^^^^^^^^^^ .. autoclass:: openstack.baremetal.v1._proxy.Proxy :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_power_state, - wait_for_node_reservation, set_node_maintenance, unset_node_maintenance + :members: nodes, create_node, find_node, get_node, update_node, patch_node, delete_node, + set_node_provision_state, set_node_boot_device, set_node_boot_mode, + set_node_secure_boot, inject_nmi_to_node, wait_for_nodes_provision_state, + set_node_power_state, wait_for_node_power_state, + wait_for_node_reservation, validate_node, set_node_maintenance, + unset_node_maintenance, delete_node, list_node_vendor_passthru + +Node Trait Operations +^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: openstack.baremetal.v1._proxy.Proxy + :noindex: + :members: add_node_trait, remove_node_trait, set_node_traits Port Operations ^^^^^^^^^^^^^^^ diff --git a/openstack/baremetal/v1/_common.py b/openstack/baremetal/v1/_common.py index 54f1862e7..3cbb68ba8 100644 --- a/openstack/baremetal/v1/_common.py +++ b/openstack/baremetal/v1/_common.py @@ -68,6 +68,9 @@ STATE_VERSIONS = { VIF_VERSION = '1.28' """API version in which the VIF operations were introduced.""" +INJECT_NMI_VERSION = '1.29' +"""API vresion in which support for injecting NMI was introduced.""" + CONFIG_DRIVE_REBUILD_VERSION = '1.35' """API version in which rebuild accepts a configdrive.""" diff --git a/openstack/baremetal/v1/_proxy.py b/openstack/baremetal/v1/_proxy.py index 1722f5ac8..0285fc773 100644 --- a/openstack/baremetal/v1/_proxy.py +++ b/openstack/baremetal/v1/_proxy.py @@ -445,6 +445,21 @@ class Proxy(proxy.Proxy): res = self._get_resource(_node.Node, node) return res.set_secure_boot(self, target) + def inject_nmi_to_node(self, node): + """Inject NMI to node. + + Injects a non-maskable interrupt (NMI) message to the node. This is + used when response time is critical, such as during non-recoverable + hardware errors. In addition, virsh inject-nmi is useful for triggering + a crashdump in Windows guests. + + :param node: The value can be the name or ID of a node or a + :class:`~openstack.baremetal.v1.node.Node` instance. + :return: None + """ + res = self._get_resource(_node.Node, node) + res.inject_nmi(self) + def wait_for_nodes_provision_state(self, nodes, expected_state, timeout=None, abort_on_failed_state=True, diff --git a/openstack/baremetal/v1/node.py b/openstack/baremetal/v1/node.py index 0a3a1385d..276540abc 100644 --- a/openstack/baremetal/v1/node.py +++ b/openstack/baremetal/v1/node.py @@ -602,6 +602,34 @@ class Node(_common.ListMixin, resource.Resource): "the last error is %(error)s" % {'node': self.id, 'error': self.last_error}) + def inject_nmi(self, session): + """Inject NMI. + + :param session: The session to use for making this request. + :return: None + """ + session = self._get_session(session) + version = self._assert_microversion_for( + session, + 'commit', + _common.INJECT_NMI_VERSION, + ) + request = self._prepare_request(requires_id=True) + request.url = utils.urljoin(request.url, 'management', 'inject_nmi') + + body = {} + + response = session.put( + request.url, + json=body, + headers=request.headers, + microversion=version, + retriable_status_codes=_common.RETRIABLE_STATUS_CODES, + ) + + msg = ("Failed to inject NMI to node {node}".format(node=self.id)) + exceptions.raise_from_response(response, error_message=msg) + def set_power_state(self, session, target, wait=False, timeout=None): """Run an action modifying this node's power state. diff --git a/openstack/tests/unit/baremetal/v1/test_node.py b/openstack/tests/unit/baremetal/v1/test_node.py index 3d001497b..73386fd3e 100644 --- a/openstack/tests/unit/baremetal/v1/test_node.py +++ b/openstack/tests/unit/baremetal/v1/test_node.py @@ -590,6 +590,35 @@ class TestNodeWaitForReservation(base.TestCase): mock_fetch.assert_called_with(self.node, self.session) +@mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) +class TestNodeInjectNMI(base.TestCase): + + def setUp(self): + super().setUp() + self.node = node.Node(**FAKE) + self.session = mock.Mock(spec=adapter.Adapter) + self.session.default_microversion = '1.29' + self.node = node.Node(**FAKE) + + def test_inject_nmi(self): + self.node.inject_nmi(self.session) + self.session.put.assert_called_once_with( + 'nodes/%s/management/inject_nmi' % FAKE['uuid'], + json={}, + headers=mock.ANY, + microversion='1.29', + retriable_status_codes=_common.RETRIABLE_STATUS_CODES, + ) + + def test_incompatible_microversion(self): + self.session.default_microversion = '1.28' + self.assertRaises( + exceptions.NotSupported, + self.node.inject_nmi, + self.session, + ) + + @mock.patch.object(node.Node, '_assert_microversion_for', _fake_assert) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) class TestNodeSetPowerState(base.TestCase): diff --git a/releasenotes/notes/node-inject-nmi-53d12681026e0b6c.yaml b/releasenotes/notes/node-inject-nmi-53d12681026e0b6c.yaml new file mode 100644 index 000000000..41f065451 --- /dev/null +++ b/releasenotes/notes/node-inject-nmi-53d12681026e0b6c.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds ``inject_nmi`` ``openstack.baremetal.v1.Node``. + - | + Adds ``inject_nmi_to_node`` to the baremetal Proxy.