From 2e9c34b4cc6d41b3d84c00b80658c14a62d27670 Mon Sep 17 00:00:00 2001 From: Peter Piela Date: Sun, 29 Oct 2017 18:41:32 -0400 Subject: [PATCH] Added support for injecting non-maskable interrupts A a new action 'Inject NMI' has been added to node actions dropdown menu in the 'Node Details' page. Closes-Bug: #1713519 Change-Id: I01e9d7f352119c201d701127bb08fa2f0607a214 --- ironic_ui/api/ironic.py | 12 +++++++++++ ironic_ui/api/ironic_rest_api.py | 17 +++++++++++++++ .../ironic/ironic.backend-mock.service.js | 18 ++++++++++++++++ .../dashboard/admin/ironic/ironic.service.js | 21 +++++++++++++++++++ .../admin/ironic/ironic.service.spec.js | 21 +++++++++++++++++++ .../node-details/node-details.controller.js | 11 ++++++++++ .../node-details.controller.spec.js | 13 ++++++++++++ .../ironic/node-details/node-details.html | 8 +++++++ .../notes/inject-nmi-0320453eaf1bda9b.yaml | 6 ++++++ 9 files changed, 127 insertions(+) create mode 100644 releasenotes/notes/inject-nmi-0320453eaf1bda9b.yaml diff --git a/ironic_ui/api/ironic.py b/ironic_ui/api/ironic.py index c93a0e71..66af1761 100755 --- a/ironic_ui/api/ironic.py +++ b/ironic_ui/api/ironic.py @@ -259,6 +259,18 @@ def node_get_supported_boot_devices(request, node_id): return result.get('supported_boot_devices', []) +def node_inject_nmi(request, node_id): + """Inject Non-Masking Interrupts into a specified node. + + :param request: HTTP request. + :param node_id: The UUID or name of the node. + :return: Empty response. + + http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.inject_nmi + """ + return ironicclient(request).node.inject_nmi(node_id) + + def driver_list(request): """Retrieve a list of drivers. diff --git a/ironic_ui/api/ironic_rest_api.py b/ironic_ui/api/ironic_rest_api.py index a75325e7..e7e6d442 100755 --- a/ironic_ui/api/ironic_rest_api.py +++ b/ironic_ui/api/ironic_rest_api.py @@ -467,3 +467,20 @@ class DriverDetails(generic.View): :return: Dictionary of details """ return ironic.driver_details(request, driver_name) + + +@urls.register +class InjectNmi(generic.View): + + url_regex = r'ironic/nodes/(?P{})/management/inject_nmi$'. \ + format(LOGICAL_NAME_PATTERN) + + @rest_utils.ajax(data_required=True) + def put(self, request, node_id): + """Inject Non-Masking Interrupts into a specified node. + + :param request: HTTP request. + :param node_id: Node name or uuid. + :return: Empty response. + """ + return ironic.node_inject_nmi(request, node_id) diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js index 9e1b16cc..7aa2db27 100644 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js @@ -662,6 +662,24 @@ return [status, null]; }); + // Inject NMI + $httpBackend.whenPUT(/\/api\/ironic\/nodes\/(.+)\/management\/inject_nmi/, + undefined, + undefined, + ['nodeId']) + .respond(function(method, url, data, headers, params) { + var status, response; + if (angular.isDefined(nodes[params.nodeId])) { + status = responseCode.EMPTY_RESPONSE; + response = ''; + } else { + status = responseCode.INTERNAL_SERVER_ERROR; + response = 'Node ' + params.nodeId + + ' could not be found. (HTTP 404)'; + } + return [status, response]; + }); + // Validate the interfaces associated with a specified node $httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/validate$/, undefined, diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js index 87bdbc1a..6e79ad78 100755 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js @@ -53,6 +53,7 @@ getPortsWithNode: getPortsWithNode, getBootDevice: getBootDevice, getSupportedBootDevices: getSupportedBootDevices, + injectNmi: injectNmi, nodeGetConsole: nodeGetConsole, nodeSetConsoleMode: nodeSetConsoleMode, nodeSetMaintenance: nodeSetMaintenance, @@ -354,6 +355,26 @@ }); } + /** + * @description Inject Non-Masking Interrupts into a specified node + * + * http://developer.openstack.org/api-ref/baremetal/#inject-nmi + * + * @param {string} nodeId – UUID or logical name of a node. + * @return {promise} Promise. No content on success. + */ + function injectNmi(nodeId) { + return apiService.put('/api/ironic/nodes/' + nodeId + + '/management/inject_nmi') + .catch(function(response) { + var msg = interpolate(gettext('Unable to inject NMI: %s'), + [response.data], + false); + toastService.add('error', msg); + return $q.reject(msg); + }); + } + /** * @description Create an Ironic node * diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js index ce833f14..7ed820ae 100644 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js @@ -34,6 +34,7 @@ 'getPortsWithNode', 'getBootDevice', 'getSupportedBootDevices', + 'injectNmi', 'nodeGetConsole', 'nodeSetBootDevice', 'nodeSetConsoleMode', @@ -662,6 +663,26 @@ ironicBackendMockService.flush(); }); + it('injectNmi', function() { + createNode({driver: defaultDriver}) + .then(function(node) { + return ironicAPI.injectNmi(node.uuid); + }) + .then(function(response) { + expect(response.status).toBe(204); + expect(response.data).toBe(''); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); + + it('injectNmi - nonexistent node', function() { + ironicAPI.injectNmi(0) + .then(failTest); + + ironicBackendMockService.flush(); + }); }); }); })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js index 25e59b37..25b9ac33 100755 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js @@ -104,6 +104,7 @@ ctrl.refresh = refresh; ctrl.toggleConsoleMode = toggleConsoleMode; ctrl.deletePortgroups = deletePortgroups; + ctrl.injectNmi = injectNmi; $scope.emptyObject = function(obj) { return angular.isUndefined(obj) || Object.keys(obj).length === 0; @@ -353,5 +354,15 @@ ctrl.refresh(); }); } + + /** + * @name horizon.dashboard.admin.ironic.NodeDetailsController.injectNmi + * @description Inject non-masking interrupts into the current node + * + * @return {void} + */ + function injectNmi() { + ironic.injectNmi(ctrl.node.uuid); + } } })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.spec.js b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.spec.js index abe2aedd..0592c066 100755 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.spec.js @@ -255,5 +255,18 @@ expect(ctrl.node['' + interfaceName + '_interface']).toBeDefined(); expect(ctrl.nodeValidation[0].hw_interface).toEqual(hwInterface); }); + + it('should have injectNmi', function () { + var ctrl; + createNode() + .then(function(node) { + ctrl = createController(node); + }) + .catch(function() { + fail(); + }); + ironicBackendMockService.flush(); + expect(ctrl.injectNmi).toBeDefined(); + }); }); })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html index e8e3eb9d..55890dfb 100644 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html @@ -70,6 +70,14 @@ callback="ctrl.toggleConsoleMode"> {$ ctrl.node.console_enabled ? 'Disable console' : 'Enable console' | translate $} +
  • + + {$ "Inject NMI" | translate $} + +
  • diff --git a/releasenotes/notes/inject-nmi-0320453eaf1bda9b.yaml b/releasenotes/notes/inject-nmi-0320453eaf1bda9b.yaml new file mode 100644 index 00000000..d1c1d491 --- /dev/null +++ b/releasenotes/notes/inject-nmi-0320453eaf1bda9b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds support for injecting non-maskable interrupts into a node. A new + action ``Inject NMI`` has been added to the node actions dropdown menu + in the ``Node Details`` page.