From 41488737f6d163816d5c88f2ed1e14838337ffd7 Mon Sep 17 00:00:00 2001 From: Ramamani Yeleswarapu Date: Tue, 13 Jun 2017 10:58:08 -0700 Subject: [PATCH] Set current boot device on a node This would also include collecting the possible boot devices. Change-Id: I49c47169d5c511f992480c60f64062183064bfc6 Closes-Bug: #1671567 --- ironic_ui/api/ironic.py | 30 ++++ ironic_ui/api/ironic_rest_api.py | 32 ++++ .../bootdevice/bootdevice.controller.js | 65 ++++++++ .../bootdevice/bootdevice.controller.spec.js | 97 ++++++++++++ .../admin/ironic/bootdevice/bootdevice.html | 58 +++++++ .../ironic/bootdevice/bootdevice.service.js | 68 +++++++++ .../bootdevice/bootdevice.service.spec.js | 142 ++++++++++++++++++ .../ironic/ironic.backend-mock.service.js | 81 +++++++++- .../dashboard/admin/ironic/ironic.service.js | 60 +++++++- .../admin/ironic/ironic.service.spec.js | 85 +++++++++-- .../node-details/node-details.controller.js | 3 + .../ironic/node-details/node-details.html | 8 + .../node-details/sections/configuration.html | 24 +-- .../ironic/node-list/node-list.controller.js | 3 + .../admin/ironic/node-list/node-list.html | 8 + 15 files changed, 733 insertions(+), 31 deletions(-) create mode 100644 ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.spec.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.html create mode 100644 ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.spec.js diff --git a/ironic_ui/api/ironic.py b/ironic_ui/api/ironic.py index ad160743..83250c0e 100755 --- a/ironic_ui/api/ironic.py +++ b/ironic_ui/api/ironic.py @@ -1,6 +1,7 @@ # # Copyright 2015, 2016 Hewlett Packard Enterprise Development Company LP # Copyright 2016 Cray Inc. +# Copyright 2017 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -229,6 +230,35 @@ def node_get_boot_device(request, node_id): return ironicclient(request).node.get_boot_device(node_id) +def node_set_boot_device(request, node_id, device, persistent): + """Set the boot device for a specified node. + + :param request: HTTP request. + :param node_id: The UUID or name of the node. + :param device: boot device. + :param persistent: True or False. + :return: null. + + http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_boot_device + """ + return ironicclient(request).node.set_boot_device(node_id, + device, + persistent) + + +def node_get_supported_boot_devices(request, node_id): + """Get the list of supported boot devices for a specified node. + + :param request: HTTP request. + :param node_id: The UUID or name of the node. + :return: List of supported boot devices (strings) + + http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get_boot_device + """ + result = ironicclient(request).node.get_supported_boot_devices(node_id) + return result.get('supported_boot_devices', []) + + 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 03f12f5b..7e20cc6b 100755 --- a/ironic_ui/api/ironic_rest_api.py +++ b/ironic_ui/api/ironic_rest_api.py @@ -1,6 +1,7 @@ # # Copyright 2015, 2016 Hewlett Packard Enterprise Development Company LP # Copyright 2016 Cray Inc. +# Copyright 2017 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -277,6 +278,37 @@ class BootDevice(generic.View): """ return ironic.node_get_boot_device(request, node_id) + @rest_utils.ajax(data_required=True) + def put(self, request, node_id): + """Set the boot device for a specific node + + :param request: HTTP request. + :param node_id: Node name or uuid + :return: null. + """ + return ironic.node_set_boot_device( + request, + node_id, + request.DATA.get('boot_device'), + persistent=request.DATA.get('persistent')) + + +@urls.register +class SupportedBootDevices(generic.View): + + url_regex = r'ironic/nodes/(?P{})/boot_device/supported$' . \ + format(LOGICAL_NAME_PATTERN) + + @rest_utils.ajax() + def get(self, request, node_id): + """Get the list of supported boot devices for a specified node + + :param request: HTTP request. + :param node_id: Node name or uuid + :return: List of supported boot devices + """ + return ironic.node_get_supported_boot_devices(request, node_id) + @urls.register class Drivers(generic.View): diff --git a/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.js b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.js new file mode 100644 index 00000000..a130eded --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.js @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + /** + * @ngdoc controller + * @name horizon.dashboard.admin.ironic:BootDeviceController + * @ngController + * + * @description + * Controller used to prompt the user for information associated with + * setting the boot device of a node + */ + angular + .module('horizon.dashboard.admin.ironic') + .controller('BootDeviceController', BootDeviceController); + + BootDeviceController.$inject = [ + '$uibModalInstance', + 'horizon.app.core.openstack-service-api.ironic', + 'node' + ]; + + function BootDeviceController($uibModalInstance, ironic, node) { + var ctrl = this; + + ctrl.modalTitle = gettext("Set Boot Device"); + + ironic.getSupportedBootDevices(node.uuid).then( + function(bootDevices) { + ctrl.supportedBootDevices = bootDevices; + }); + + // Initialize form fields to current values + ctrl.bootDevice = null; + ctrl.persistent = 'False'; + ironic.getBootDevice(node.uuid).then(function(device) { + ctrl.bootDevice = device.boot_device; + ctrl.persistent = device.persistent ? 'True' : 'False'; + }); + + ctrl.cancel = function() { + $uibModalInstance.dismiss('cancel'); + }; + + ctrl.setSelectedBootDevice = function() { + $uibModalInstance.close({device: ctrl.bootDevice, + persistent: ctrl.persistent === 'True'}); + }; + } +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.spec.js b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.spec.js new file mode 100644 index 00000000..e52c7490 --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.controller.spec.js @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Cray Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + describe('horizon.dashboard.admin.ironic.BootDeviceController', function () { + var BOOT_DEVICE_CONTROLLER_PROPERTIES = [ + 'bootDevice', + 'cancel', + 'modalTitle', + 'persistent', + 'setSelectedBootDevice', + 'supportedBootDevices' + ]; + var uibModalInstance, ironicBackendMockService, node; + var ctrl = {}; + + beforeEach(module('horizon.dashboard.admin.ironic')); + + beforeEach(module('horizon.framework.util')); + + beforeEach(module(function($provide) { + $provide.value('$uibModal', {}); + })); + + beforeEach(module(function($provide) { + uibModalInstance = { + close: jasmine.createSpy(), + dismiss: jasmine.createSpy() + }; + $provide.value('$uibModalInstance', uibModalInstance); + })); + + beforeEach(module(function($provide) { + $provide.value('horizon.framework.widgets.toast.service', + {}); + })); + + beforeEach(module('horizon.app.core.openstack-service-api')); + + beforeEach(inject(function($injector) { + ironicBackendMockService = + $injector.get('horizon.dashboard.admin.ironic.backend-mock.service'); + ironicBackendMockService.init(); + + var ironicAPI = + $injector.get('horizon.app.core.openstack-service-api.ironic'); + + ironicAPI.createNode( + {driver: ironicBackendMockService.params.defaultDriver}) + .then(function(response) { + node = response.data; + var controller = $injector.get('$controller'); + ctrl = controller('BootDeviceController', {node: node}); + }); + ironicBackendMockService.flush(); + })); + + it('controller should be defined', function () { + expect(ctrl).toBeDefined(); + expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual( + BOOT_DEVICE_CONTROLLER_PROPERTIES.sort()); + expect(ctrl.supportedBootDevices).toEqual( + ironicBackendMockService.getNodeSupportedBootDevices(node.uuid)); + var bootDevice = ironicBackendMockService.getNodeBootDevice(node.uuid); + expect(ctrl.bootDevice).toEqual(bootDevice.boot_device); + expect(ctrl.persistent).toEqual(bootDevice.persistent ? 'True' : 'False'); + }); + + it('cancel', function () { + ctrl.cancel(); + expect(uibModalInstance.dismiss).toHaveBeenCalled(); + }); + + it('setSelectedBootDevice', function () { + ctrl.bootDevice = 'pxe'; + ctrl.persistent = 'False'; + ctrl.setSelectedBootDevice(); + expect(uibModalInstance.close).toHaveBeenCalledWith( + {device: ctrl.bootDevice, + persistent: ctrl.persistent === 'True'}); + }); + }); +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.html b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.html new file mode 100644 index 00000000..502623da --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.html @@ -0,0 +1,58 @@ + + + + diff --git a/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.js b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.js new file mode 100644 index 00000000..955fd83e --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.js @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + /* + * @ngdoc service + * @name horizon.dashboard.admin.ironic.bootdevice.service + * @description Service for setting the boot device of a node + */ + angular + .module('horizon.dashboard.admin.ironic') + .factory('horizon.dashboard.admin.ironic.bootdevice.service', + bootDeviceService); + + bootDeviceService.$inject = [ + '$uibModal', + 'horizon.dashboard.admin.ironic.basePath', + 'horizon.app.core.openstack-service-api.ironic' + ]; + + function bootDeviceService($uibModal, basePath, ironic) { + var service = { + setBootDevice: setBootDevice + }; + return service; + + /* + * @description Set the boot device of a specified node + * + * @param {object} node - node object + * @return {promise} + */ + function setBootDevice(node) { + var promise; + var options = { + controller: "BootDeviceController as ctrl", + backdrop: 'static', + resolve: { + node: function() { + return node; + } + }, + templateUrl: basePath + '/bootdevice/bootdevice.html' + }; + promise = $uibModal.open(options).result.then( + function(result) { + return ironic.nodeSetBootDevice(node.uuid, + result.device, + result.persistent); + }); + return promise; + } + } +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.spec.js b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.spec.js new file mode 100644 index 00000000..c5d0ed7c --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/bootdevice/bootdevice.service.spec.js @@ -0,0 +1,142 @@ +/** + * Copyright 2017 Cray Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + "use strict"; + + /** + * @description Unit tests for the Ironic-UI boot-device service + */ + + describe('horizon.dashboard.admin.ironic.bootdevice.service', + function() { + var $q, + $uibModal, + bootDeviceService, + ironicAPI, + ironicBackendMockService, + defaultDriver; + + beforeEach(module('horizon.dashboard.admin.ironic')); + + beforeEach(module('horizon.framework.util')); + + beforeEach(module(function($provide) { + $provide.value('$uibModal', { + open: function() { + return $q.when({device: 'pxe', + persistent: true}); + } + }); + })); + + beforeEach(module(function($provide) { + $provide.value('horizon.framework.widgets.toast.service', { + add: function() {} + }); + })); + + beforeEach(module('horizon.app.core.openstack-service-api')); + + beforeEach(inject(function($injector) { + ironicBackendMockService = + $injector.get('horizon.dashboard.admin.ironic.backend-mock.service'); + ironicBackendMockService.init(); + defaultDriver = ironicBackendMockService.params.defaultDriver; + })); + + beforeEach(inject(function($injector) { + $q = $injector.get('$q'); + + $uibModal = $injector.get('$uibModal'); + + ironicAPI = + $injector.get('horizon.app.core.openstack-service-api.ironic'); + + bootDeviceService = + $injector.get('horizon.dashboard.admin.ironic.bootdevice.service'); + })); + + it('defines the bootDeviceService', function() { + expect(bootDeviceService).toBeDefined(); + expect(bootDeviceService.setBootDevice).toBeDefined(); + }); + + afterEach(function() { + ironicBackendMockService.postTest(); + }); + + /** + * @description Utility function that creates a node and returns + * both it and its boot device + * + * @return {promise} Containing node and boot_device + */ + function createNode() { + return ironicAPI.createNode({driver: defaultDriver}) + .then(function(response) { + return response.data; + }) + .then(function(node) { + return ironicAPI.getBootDevice(node.uuid).then(function(device) { + return {node: node, boot_device: device}; + }); + }); + } + + it('setBootDevice', function() { + var targetBootDevice = { + device: "safe", + persistent: false + }; + + spyOn($uibModal, 'open').and.returnValue( + {result: $q.when(targetBootDevice)}); + + createNode().then(function(data) { + expect(data.node.boot_device).not.toEqual(targetBootDevice.device); + bootDeviceService.setBootDevice(data.node) + .then(function() { + ironicAPI.getBootDevice(data.node.uuid).then(function(device) { + expect(device).toEqual( + {boot_device: targetBootDevice.device, + persistent: targetBootDevice.persistent}); + }); + }) + .catch(fail); + }); + + ironicBackendMockService.flush(); + }); + + it('setBootDevice - cancel', function() { + spyOn($uibModal, 'open').and.returnValue( + {result: $q.reject('cancel')}); + + createNode().then(function(data) { + bootDeviceService.setBootDevice(data.node) + .then(fail) + .catch(function() { + ironicAPI.getBootDevice(data.node.uuid).then(function(device) { + expect(device).toEqual(data.boot_device); + }); + }); + }); + + ironicBackendMockService.flush(); + }); + }); +})(); 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 07612224..0444b72c 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 @@ -103,12 +103,11 @@ // Additional service parameters var params = { - // Currently, all nodes have the same boot device. - bootDevice: {boot_device: 'pxe', persistent: true}, // Console info consoleType: "shellinabox", consoleUrl: "http://localhost:", - defaultDriver: "agent_ipmitool" + defaultDriver: "agent_ipmitool", + supportedBootDevices: ["pxe", "bios", "safe"] }; // List of supported drivers @@ -123,6 +122,8 @@ flush: flush, postTest: postTest, getNode: getNode, + getNodeBootDevice: getNodeBootDevice, + getNodeSupportedBootDevices: getNodeSupportedBootDevices, nodeGetConsoleUrl: nodeGetConsoleUrl, getDrivers: getDrivers, getImages: getImages, @@ -183,7 +184,12 @@ base: node, consolePort: getNextAvailableSystemPort(), ports: {}, // Indexed by port-uuid - portgroups: {} // Indexed by portgroup-uuid + portgroups: {}, // Indexed by portgroup-uuid + supportedBootDevices: service.params.supportedBootDevices, + bootDevice: { + boot_device: service.params.supportedBootDevices[0], + persistent: true + } }; nodes[node.uuid] = backendNode; @@ -196,7 +202,7 @@ } /** - * description Get a specified node. + * @description Get a specified node. * * @param {string} nodeId - Uuid or name of the requested node. * @return {object|null} Base node object, or null if the node @@ -207,6 +213,29 @@ } /** + * @description Get the boot device of a specified node. + * + * @param {string} nodeId - Uuid or name of the requested node. + * @return {object} Boot device. + */ + function getNodeBootDevice(nodeId) { + return angular.isDefined(nodes[nodeId]) + ? nodes[nodeId].bootDevice : undefined; + } + + /** + * @description Get the list of supported boot devices of + * a specified node. + * + * @param {string} nodeId - Uuid or name of the requested node. + * @return {string []} List of supported boot devices. + */ + function getNodeSupportedBootDevices(nodeId) { + return angular.isDefined(nodes[nodeId]) + ? nodes[nodeId].supportedBootDevices : undefined; + } + + /* * @description Get the console-url for a specified node. * * @param {string} nodeId - Uuid or name of the node. @@ -493,7 +522,47 @@ $httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/boot_device$/, undefined, ['nodeId']) - .respond(responseCode.SUCCESS, service.params.bootDevice); + .respond(function(method, url, data, headers, params) { + if (angular.isDefined(nodes[params.nodeId])) { + return [200, nodes[params.nodeId].bootDevice]; + } else { + return [400, null]; + } + }); + + // Get supported boot devices + $httpBackend.whenGET( + /\/api\/ironic\/nodes\/([^\/]+)\/boot_device\/supported$/, + undefined, + ['nodeId']) + .respond(function(method, url, data, headers, params) { + if (angular.isDefined(nodes[params.nodeId])) { + return [200, nodes[params.nodeId].supportedBootDevices]; + } else { + return [400, null]; + } + }); + + // Set boot device + $httpBackend.whenPUT(/\/api\/ironic\/nodes\/(.+)\/boot_device/, + undefined, + undefined, + ['nodeId']) + .respond(function(method, url, data, headers, params) { + data = JSON.parse(data); + var status = 404; + if (angular.isDefined(nodes[params.nodeId])) { + var node = nodes[params.nodeId]; + if (node.supportedBootDevices.indexOf(data.boot_device) !== -1) { + node.bootDevice.boot_device = data.boot_device; + if (angular.isDefined(data.persistent)) { + node.bootDevice.persistent = data.persistent; + } + status = 200; + } + } + return [status, null]; + }); // Validate the interfaces associated with a specified node $httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/validate$/, diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js index 9a5e5cb4..dab58e64 100755 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js @@ -1,6 +1,7 @@ /* * © Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP * © Copyright 2016 Cray Inc. + * Copyright 2017 Intel Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,9 +51,11 @@ getNodes: getNodes, getPortsWithNode: getPortsWithNode, getBootDevice: getBootDevice, + getSupportedBootDevices: getSupportedBootDevices, nodeGetConsole: nodeGetConsole, nodeSetConsoleMode: nodeSetConsoleMode, nodeSetMaintenance: nodeSetMaintenance, + nodeSetBootDevice: nodeSetBootDevice, nodeSetPowerState: nodeSetPowerState, setNodeProvisionState: setNodeProvisionState, updateNode: updateNode, @@ -119,11 +122,11 @@ * @description Retrieve the boot device for a node * https://developer.openstack.org/api-ref/baremetal/#get-boot-device * - * @param {string} uuid – UUID or logical name of a node. + * @param {string} nodeId – UUID or logical name of a node. * @return {promise} Dictionary describing the current boot device */ - function getBootDevice(uuid) { - return apiService.get('/api/ironic/nodes/' + uuid + '/boot_device') + function getBootDevice(nodeId) { + return apiService.get('/api/ironic/nodes/' + nodeId + '/boot_device') .then(function(response) { return response.data; }) @@ -137,6 +140,30 @@ }); } + /** + * @description Retrieve the supported boot devices for a node + * https://developer.openstack.org/api-ref/baremetal/#get-supported-boot-devices + * + * @param {string} nodeId – UUID or logical name of a node. + * @return {promise} List of supported boot devices + */ + function getSupportedBootDevices(nodeId) { + return apiService.get('/api/ironic/nodes/' + nodeId + + '/boot_device/supported') + .then(function(response) { + return response.data; // List of supported boot devices + }) + .catch(function(response) { + var msg = interpolate( + gettext( + 'Unable to retrieve supported boot devices for Ironic node. %s'), + [response.data], + false); + toastService.add('error', msg); + return $q.reject(msg); + }); + } + /** * @description Retrieve a list of ports associated with a node. * @@ -200,6 +227,33 @@ }); } + /** + * @description Set the boot device of a node + * + * http://developer.openstack.org/api-ref/baremetal/#set-boot-device + * + * @param {string} nodeId – UUID or logical name of a node. + * @param {string} bootDevice - Selected boot device. + * @param {Boolean} persistent - True or False. + * @return {promise} Promise + */ + function nodeSetBootDevice(nodeId, bootDevice, persistent) { + return apiService.put('/api/ironic/nodes/' + nodeId + '/boot_device', + {boot_device: bootDevice, + persistent: persistent}) + .then(function() { + toastService.add('success', + gettext('Refresh page to see set boot device')); + }) + .catch(function(response) { + var msg = interpolate(gettext('Unable to set boot device: %s'), + [response.data], + false); + toastService.add('error', msg); + return $q.reject(msg); + }); + } + /** * @description Set the power state of the 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 f6d529e5..16d7f8ec 100644 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js @@ -31,7 +31,9 @@ 'getPortgroups', 'getPortsWithNode', 'getBootDevice', + 'getSupportedBootDevices', 'nodeGetConsole', + 'nodeSetBootDevice', 'nodeSetConsoleMode', 'nodeSetPowerState', 'nodeSetMaintenance', @@ -296,27 +298,90 @@ .then(function(node) { expect(node.console_enabled).toEqual(false); return node; - }); + }) + .catch(failTest); ironicBackendMockService.flush(); }); it('getBootDevice', function() { createNode({driver: defaultDriver}) - .then(function(node) { - expect(node.console_enabled).toEqual(false); - return node; - }) .then(function(node) { return ironicAPI.getBootDevice(node.uuid) .then(function(bootDevice) { - return bootDevice; + return {node: node, bootDevice: bootDevice}; }); }) - .then(function(bootDevice) { - expect(bootDevice).toEqual( - ironicBackendMockService.params.bootDevice); - }); + .then(function(data) { + expect(data.bootDevice).toEqual( + ironicBackendMockService.getNodeBootDevice(data.node.uuid)); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); + + it('getSupportedBootDevices', function() { + createNode({driver: defaultDriver}) + .then(function(node) { + return ironicAPI.getSupportedBootDevices(node.uuid); + }) + .then(function(bootDevices) { + expect(bootDevices).toEqual( + ironicBackendMockService.params.supportedBootDevices); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); + + it('nodeSetBootDevice', function() { + var bootDevice = { + boot_device: "bios", + persistent: false + }; + + createNode({driver: defaultDriver}) + .then(function(node) { + return ironicAPI.nodeSetBootDevice(node.uuid, + bootDevice.boot_device, + bootDevice.persistent) + .then(function() { + return node; + }); + }) + .then(function(node) { + ironicAPI.getBootDevice(node.uuid).then(function(device) { + expect(device).toEqual(bootDevice); + }); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); + + it('nodeSetBootDevice - bad device', function() { + createNode({driver: defaultDriver}) + .then(function(node) { + return ironicAPI.getBootDevice(node.uuid) + .then(function(device) { + return {node: node, currentBootDevice: device}; + }); + }) + .then(function(data) { + ironicAPI.nodeSetBootDevice(data.node.uuid, + "bad-device", + false) + .then(failTest) + .catch(function() { + // Ensure the boot device is unchanged + ironicAPI.getBootDevice(data.node.uuid) + .then(function(device) { + expect(device).toEqual(data.currentBootDevice); + }); + }); + }) + .catch(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 0e4479ca..337591fb 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 @@ -33,6 +33,7 @@ 'horizon.dashboard.admin.ironic.create-port.service', 'horizon.dashboard.admin.ironic.edit-port.service', 'horizon.dashboard.admin.ironic.maintenance.service', + 'horizon.dashboard.admin.ironic.bootdevice.service', 'horizon.dashboard.admin.ironic.node-state-transition.service', 'horizon.dashboard.admin.ironic.validUuidPattern' ]; @@ -47,6 +48,7 @@ createPortService, editPortService, maintenanceService, + bootDeviceService, nodeStateTransitionService, validUuidPattern) { var ctrl = this; @@ -57,6 +59,7 @@ ctrl.actions = actions; ctrl.maintenanceService = maintenanceService; + ctrl.bootDeviceService = bootDeviceService; ctrl.sections = [ { 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 c3121724..5b3c7686 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 @@ -37,6 +37,14 @@ "Maintenance off" : "Maintenance on" | translate $} +
  • + + {$ "Set boot device" | translate $} + +
  • - - -
    -

    Boot Device

    -
    -
    -
    Device
    -
    {$ ctrl.node.bootDevice.boot_device | noValue $}
    -
    Persistent
    -
    {$ ctrl.node.bootDevice.persistent | noValue $}
    -
    -
    @@ -407,4 +395,16 @@
    {$ propertyValue | noValue $}
    + + +
    +

    Boot Device

    +
    +
    +
    Device
    +
    {$ ctrl.node.bootDevice.boot_device | noValue $}
    +
    Persistent
    +
    {$ ctrl.node.bootDevice.persistent | noValue $}
    +
    +
    diff --git a/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.controller.js b/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.controller.js index 7f3a6e34..e7231a89 100755 --- a/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.controller.js @@ -27,6 +27,7 @@ 'horizon.app.core.openstack-service-api.ironic', 'horizon.dashboard.admin.ironic.actions', 'horizon.dashboard.admin.ironic.maintenance.service', + 'horizon.dashboard.admin.ironic.bootdevice.service', 'horizon.dashboard.admin.ironic.enroll-node.service', 'horizon.dashboard.admin.ironic.edit-node.service', 'horizon.dashboard.admin.ironic.create-port.service', @@ -38,6 +39,7 @@ ironic, actions, maintenanceService, + bootDeviceService, enrollNodeService, editNodeService, createPortService, @@ -48,6 +50,7 @@ ctrl.nodesSrc = []; ctrl.actions = actions; ctrl.maintenanceService = maintenanceService; + ctrl.bootDeviceService = bootDeviceService; ctrl.enrollNode = enrollNode; ctrl.editNode = editNode; diff --git a/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.html b/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.html index c0edced4..21128492 100644 --- a/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.html +++ b/ironic_ui/static/dashboard/admin/ironic/node-list/node-list.html @@ -163,6 +163,14 @@ "Maintenance off" : "Maintenance on" | translate $}
  • +
  • + + {$ "Set boot device" | translate $} + +