diff --git a/ironic_ui/api/ironic.py b/ironic_ui/api/ironic.py index d9df1cbf..c93a0e71 100755 --- a/ironic_ui/api/ironic.py +++ b/ironic_ui/api/ironic.py @@ -25,7 +25,7 @@ from horizon.utils.memoized import memoized # noqa from openstack_dashboard.api import base -DEFAULT_IRONIC_API_VERSION = '1.31' +DEFAULT_IRONIC_API_VERSION = '1.34' DEFAULT_INSECURE = False DEFAULT_CACERT = None IRONIC_CLIENT_CLASS_NAME = 'baremetal' @@ -282,6 +282,19 @@ def driver_properties(request, driver_name): return ironicclient(request).driver.properties(driver_name) +def driver_details(request, driver_name): + """Retrieve the details of a specified driver + + :param request: HTTP request + :param driver_name: Name of the driver + :return: dictionary of driver details + + https://docs.openstack.org/python-ironicclient/latest/cli/osc/v1/index.html#baremetal-driver-show + """ + details = ironicclient(request).driver.get(driver_name) + return details.to_dict() + + def port_create(request, params): """Create network port diff --git a/ironic_ui/api/ironic_rest_api.py b/ironic_ui/api/ironic_rest_api.py index 7a962666..a75325e7 100755 --- a/ironic_ui/api/ironic_rest_api.py +++ b/ironic_ui/api/ironic_rest_api.py @@ -450,3 +450,20 @@ class RaidConfig(generic.View): request, node_id, request.DATA.get('target_raid_config')) + + +@urls.register +class DriverDetails(generic.View): + + url_regex = r'ironic/drivers/(?P[0-9a-zA-Z_-]+)$'. \ + format(LOGICAL_NAME_PATTERN) + + @rest_utils.ajax() + def get(self, request, driver_name): + """Get the details of a specified driver + + :param request: HTTP request + :param driver_name: Driver name + :return: Dictionary of details + """ + return ironic.driver_details(request, driver_name) diff --git a/ironic_ui/karma.conf.js b/ironic_ui/karma.conf.js index 2e7dc4af..67037090 100644 --- a/ironic_ui/karma.conf.js +++ b/ironic_ui/karma.conf.js @@ -44,6 +44,7 @@ * Django or via jasmine template. */ '../test-shim.js', + '../node_modules/string.prototype.endswith/*.js', // from jasmine.html toxPath + 'xstatic/pkg/jquery/data/jquery.js', @@ -113,9 +114,9 @@ autoWatch: true, - frameworks: ['jasmine'], + frameworks: ['jasmine', 'jasmine-matchers'], - browsers: ['Chrome'], + browsers: ['Chrome', 'PhantomJS'], browserNoActivityTimeout: 60000, @@ -129,7 +130,9 @@ plugins: [ 'karma-chrome-launcher', + 'karma-phantomjs-launcher', 'karma-jasmine', + 'karma-jasmine-matchers', 'karma-ng-html2js-preprocessor', 'karma-coverage', 'karma-threshold-reporter' diff --git a/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.js b/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.js index e45d58e8..772bf0e7 100644 --- a/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.js @@ -27,31 +27,41 @@ '$uibModalInstance', 'horizon.app.core.openstack-service-api.ironic', 'horizon.app.core.openstack-service-api.glance', + 'horizon.dashboard.admin.ironic.form-field.service', 'horizon.dashboard.admin.ironic.base-node.service', 'horizon.dashboard.admin.ironic.driver-property.service', 'horizon.dashboard.admin.ironic.graph.service', 'horizon.dashboard.admin.ironic.validHostNamePattern', + 'horizon.dashboard.admin.ironic.driverInterfaces', '$log', + '$q', 'ctrl' ]; function BaseNodeController($uibModalInstance, ironic, glance, + formFieldService, baseNodeService, driverPropertyService, graphService, validHostNamePattern, + driverInterfaces, $log, + $q, ctrl) { ctrl.validHostNameRegex = new RegExp(validHostNamePattern); ctrl.drivers = null; ctrl.images = null; ctrl.loadingDriverProperties = false; + ctrl.driverType = null; // Object containing the set of properties associated with the currently // selected driver ctrl.driverProperties = null; ctrl.driverPropertyGroups = null; + // Dictionary of form-fields for supported interfaces indexed by interface + // name for the currently selected driver + ctrl.driverInterfaceFields = {}; ctrl.modalTitle = gettext("Node"); ctrl.submitButtonTitle = gettext("Submit"); @@ -91,10 +101,14 @@ name: null, driver: null, driver_info: {}, - network_interface: null, resource_class: null }; + // Initialize hardware interfaces + angular.forEach(driverInterfaces, function(interfaceName) { + ctrl.node[interfaceName + '_interface'] = null; + }); + angular.forEach(ctrl.propertyCollections, function(collection) { ctrl.node[collection.id] = {}; }); @@ -189,8 +203,8 @@ * @param {string} driverName - Name of driver * @return {void} */ - ctrl.loadDriverProperties = function(driverName) { - ctrl.node.driver = driverName; + ctrl._loadDriverProperties = function(driverName) { + ctrl.node.driver = null; ctrl.node.driver_info = {}; ctrl.loadingDriverProperties = true; @@ -198,6 +212,7 @@ ctrl.driverPropertyGroups = null; return ironic.getDriverProperties(driverName).then(function(properties) { + ctrl.node.driver = driverName; ctrl.driverProperties = {}; angular.forEach(properties, function(desc, property) { ctrl.driverProperties[property] = @@ -275,5 +290,58 @@ } return ready; }; + + /** + * @description Load details for a specified driver. + * Includes driver type and supported interfaces. + * + * @param {string} driverName - driver name + * @return {void} + */ + ctrl._loadDriverDetails = function(driverName) { + // Re-initialize driver related properties + ctrl.driverType = null; + angular.forEach(driverInterfaces, function(interfaceName) { + ctrl.node[interfaceName + '_interface'] = null; + }); + + ctrl.driverInterfaceFields = {}; + return ironic.getDriverDetails(driverName).then(function(details) { + ctrl.driverType = details.type; + + // Extract interface information for dynamic drivers + angular.forEach(driverInterfaces, function(interfaceName) { + var enabled = 'enabled_' + interfaceName + '_interfaces'; + if (angular.isDefined(details[enabled]) && details[enabled] !== null) { + var options = []; + angular.forEach(details[enabled], function(value) { + options.push({label: value, value: value}); + }); + + ctrl.driverInterfaceFields[interfaceName] = + new formFieldService.FormField( + {type: 'radio', + id: interfaceName, + title: interfaceName, + options: options, + value: details['default_' + interfaceName + '_interface']}); + } + }); + }); + }; + + /** + * @description Load a specified driver. + * + * @param {string} driverName - driver name + * @return {promise} Promise that completes + * when both properties and details are loaded. + */ + ctrl.loadDriver = function(driverName) { + var promises = []; + promises.push(ctrl._loadDriverProperties(driverName)); + promises.push(ctrl._loadDriverDetails(driverName)); + return $q.all(promises); + }; } })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.spec.js b/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.spec.js index 0635cbee..436dce09 100644 --- a/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.controller.spec.js @@ -17,7 +17,7 @@ 'use strict'; describe('horizon.dashboard.admin.ironic.base-node', function () { - var ironicBackendMockService, uibModalInstance; + var ironicBackendMockService, uibModalInstance, driverInterfaces; var ctrl = {}; beforeEach(module('horizon.dashboard.admin.ironic')); @@ -51,6 +51,11 @@ controller('BaseNodeController', {ctrl: ctrl}); })); + beforeEach(inject(function($injector) { + driverInterfaces = + $injector.get('horizon.dashboard.admin.ironic.driverInterfaces'); + })); + afterEach(function() { ironicBackendMockService.postTest(); }); @@ -64,6 +69,7 @@ expect(ctrl.images).toBeNull(); expect(ctrl.loadingDriverProperties).toBe(false); expect(ctrl.driverProperties).toBeNull(); + expect(ctrl.driverInterfaceFields).toEqual({}); expect(ctrl.driverPropertyGroups).toBeNull(); expect(ctrl.modalTitle).toBeDefined(); angular.forEach(ctrl.propertyCollections, function(collection) { @@ -74,14 +80,17 @@ .toContain(jasmine.objectContaining({id: "properties"})); expect(ctrl.propertyCollections) .toContain(jasmine.objectContaining({id: "extra"})); - expect(ctrl.node).toEqual({ + var node = { name: null, driver: null, driver_info: {}, properties: {}, extra: {}, - network_interface: null, - resource_class: null}); + resource_class: null}; + angular.forEach(driverInterfaces, function(interfaceName) { + node[interfaceName + '_interface'] = null; + }); + expect(ctrl.node).toEqual(node); expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual( BASE_NODE_CONTROLLER_PROPERTIES.sort()); }); @@ -89,7 +98,7 @@ it('_loadDrivers', function () { ctrl._loadDrivers(); ironicBackendMockService.flush(); - expect(ctrl.drivers).toEqual(ironicBackendMockService.getDrivers()); + expect(ctrl.drivers).toEqual(ironicBackendMockService.getBaseDrivers()); }); it('_getImages', function () { @@ -103,5 +112,24 @@ expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel'); }); + it('_loadDriverProperties', function() { + var driverName = "ipmi"; + ctrl._loadDriverProperties(driverName); + var drivers = ironicBackendMockService.getDrivers(); + ironicBackendMockService.flush(); + + expect(ctrl.node.driver).toEqual(driverName); + expect(ctrl.node.driver).toEqual(drivers[driverName].details.name); + expect(ctrl.node.driver_info).toEqual({}); + expect(ctrl.driverPropertyGroups).toBeNonEmptyArray(); + }); + + it('_loadDriverDetails', function() { + var driverName = "ipmi"; + ctrl._loadDriverDetails(driverName); + ironicBackendMockService.flush(); + var drivers = ironicBackendMockService.getDrivers(); + expect(ctrl.driverType).toEqual(drivers[driverName].details.type); + }); }); })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.html b/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.html index e3550fc1..c016a37a 100644 --- a/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.html +++ b/ironic_ui/static/dashboard/admin/ironic/base-node/base-node.html @@ -26,6 +26,15 @@ data-target="#driverDetails" data-toggle="tab" translate>Driver Details +
  • + Driver Interfaces
  • +
  • + Driver Interfaces
  • @@ -69,7 +78,7 @@ -
    +
    + +
    + + + + +
    +
    + +
    +
    +
    @@ -253,7 +285,21 @@ - + + +
    +

    + +

    +
    +
    + +
    +
    +
    + diff --git a/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.js b/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.js index f720d402..c4941bb9 100644 --- a/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.js @@ -30,6 +30,7 @@ 'horizon.framework.widgets.toast.service', 'horizon.app.core.openstack-service-api.ironic', 'horizon.dashboard.admin.ironic.events', + 'horizon.dashboard.admin.ironic.driverInterfaces', 'horizon.dashboard.admin.ironic.edit-node.service', 'horizon.dashboard.admin.ironic.update-patch.service', '$log', @@ -42,6 +43,7 @@ toastService, ironic, ironicEvents, + driverInterfaces, editNodeService, updatePatchService, $log, @@ -67,8 +69,6 @@ ctrl.node[instanceInfoId] = {}; - ctrl.node[instanceInfoId] = {}; - init(node); function init(node) { @@ -80,11 +80,9 @@ function _loadNodeData(nodeId) { ironic.getNode(nodeId).then(function(node) { - ctrl.baseNode = node; - + ctrl.baseNode = angular.copy(node); ctrl.node.name = node.name; ctrl.node.resource_class = node.resource_class; - ctrl.node.network_interface = node.network_interface; for (var i = 0; i < ctrl.drivers.length; i++) { if (ctrl.drivers[i].name === node.driver) { ctrl.selectedDriver = ctrl.drivers[i]; @@ -92,12 +90,23 @@ } } - ctrl.loadDriverProperties(node.driver).then(function() { + ctrl.loadDriver(node.driver).then(function() { angular.forEach(node.driver_info, function(value, property) { if (angular.isDefined(ctrl.driverProperties[property])) { ctrl.driverProperties[property].inputValue = value; } }); + + if (ctrl.driverType === 'classic') { + ctrl.node.network_interface = node.network_interface; + ctrl.node.storage_interface = node.storage_interface; + } else { + angular.forEach( + ctrl.driverInterfaceFields, + function(field, interfaceName) { + field.value = ctrl.baseNode[interfaceName + '_interface']; + }); + } }); ctrl.node.properties = angular.copy(node.properties); @@ -121,9 +130,9 @@ this.id = id; this.path = path; }; + angular.forEach([new PatchItem("name", "/name"), new PatchItem("resource_class", "/resource_class"), - new PatchItem("network_interface", "/network_interface"), new PatchItem("driver", "/driver"), new PatchItem("properties", "/properties"), new PatchItem("extra", "/extra"), @@ -135,6 +144,15 @@ item.path); }); + angular.forEach(driverInterfaces, function(interfaceName) { + var propName = interfaceName + '_interface'; + if (angular.isDefined(sourceNode[propName])) { + patcher.buildPatch(sourceNode[propName], + targetNode[propName], + '/' + propName); + } + }); + return patcher.getPatch(); }; @@ -154,6 +172,10 @@ } }); + angular.forEach(ctrl.driverInterfaceFields, function(field, interfaceName) { + ctrl.node[interfaceName + '_interface'] = field.value; + }); + $log.info("Updating node " + JSON.stringify(ctrl.baseNode)); $log.info("to " + JSON.stringify(ctrl.node)); diff --git a/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.spec.js b/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.spec.js index c3a35718..a0ac60a6 100644 --- a/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/edit-node/edit-node.controller.spec.js @@ -78,7 +78,11 @@ }); expect(ctrl.node.name).toEqual(editNode.name); expect(ctrl.node.resource_class).toEqual(editNode.resource_class); - expect(ctrl.node.network_interface).toEqual(editNode.network_interface); + if (ctrl.driverType === 'classic') { + expect(ctrl.node.network_interface).toEqual(editNode.network_interface); + } else { + expect(ctrl.node.network_interface).toBeNull(); + } expect(ctrl.node.properties).toEqual(editNode.properties); expect(ctrl.node.extra).toEqual(editNode.extra); expect(ctrl.node.instance_info).toEqual(editNode.instance_info); diff --git a/ironic_ui/static/dashboard/admin/ironic/enroll-node/enroll-node.controller.js b/ironic_ui/static/dashboard/admin/ironic/enroll-node/enroll-node.controller.js index e3622e35..b3cab412 100644 --- a/ironic_ui/static/dashboard/admin/ironic/enroll-node/enroll-node.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/enroll-node/enroll-node.controller.js @@ -71,6 +71,10 @@ } }); + angular.forEach(ctrl.driverInterfaceFields, function(field, interfaceName) { + ctrl.node[interfaceName + '_interface'] = field.value; + }); + ironic.createNode(ctrl.node).then( function(response) { $log.info("create node response = " + JSON.stringify(response)); 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 e69f2eae..9e1b16cc 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 @@ -30,12 +30,14 @@ ironicBackendMockService.$inject = [ '$httpBackend', 'horizon.framework.util.uuid.service', - 'horizon.dashboard.admin.ironic.validMacAddressPattern' + 'horizon.dashboard.admin.ironic.validMacAddressPattern', + 'horizon.dashboard.admin.ironic.driverInterfaces' ]; function ironicBackendMockService($httpBackend, uuidService, - validMacAddressPattern) { + validMacAddressPattern, + driverInterfaces) { // Default node object. var defaultNode = { chassis_uuid: null, @@ -106,6 +108,38 @@ uuid: undefined }; + var drivers = { + ipmi: { + details: { + default_boot_interface: "pxe", + default_console_interface: "no-console", + default_deploy_interface: "iscsi", + default_inspect_interface: "no-inspect", + default_management_interface: "ipmitool", + default_network_interface: "flat", + default_power_interface: "ipmitool", + default_raid_interface: "no-raid", + default_vendor_interface: "ipmitool", + enabled_boot_interfaces: ["pxe"], + enabled_console_interfaces: ["no-console"], + enabled_deploy_interfaces: ["iscsi", "direct"], + enabled_inspect_interfaces: ["no-inspect"], + enabled_management_interfaces: ["ipmitool"], + enabled_network_interfaces: ["flat", "noop"], + enabled_power_interfaces: ["ipmitool"], + enabled_raid_interfaces: ["no-raid", "agent"], + enabled_vendor_interfaces: ["ipmitool", "no-vendor"], + hosts: ["testhost"], + name: "ipmi", + type: "dynamic" + }, + properties: { + deploy_kernel: "UUID (from Glance)", + deploy_ramdisk: "UUID (from Glance)" + } + } + }; + // Value of the next available system port var nextAvailableSystemPort = 1024; @@ -114,13 +148,10 @@ // Console info consoleType: "shellinabox", consoleUrl: "http://localhost:", - defaultDriver: "agent_ipmitool", + defaultDriver: "ipmi", supportedBootDevices: ["pxe", "bios", "safe"] }; - // List of supported drivers - var drivers = [{name: params.defaultDriver}]; - // List of images var images = []; @@ -142,6 +173,7 @@ getNodeBootDevice: getNodeBootDevice, getNodeSupportedBootDevices: getNodeSupportedBootDevices, nodeGetConsoleUrl: nodeGetConsoleUrl, + getBaseDrivers: getBaseDrivers, getDrivers: getDrivers, getImages: getImages, getPort: getPort, @@ -189,8 +221,22 @@ function createNode(params) { var node = null; - if (angular.isDefined(params.driver)) { + if (angular.isDefined(params.driver) && + angular.isDefined(drivers[params.driver])) { node = angular.copy(defaultNode); + + // For dynamic drivers, initialize interfaces based on + // default values + var details = drivers[params.driver].details; + if (details.type === 'dynamic') { + angular.forEach(driverInterfaces, function(interfaceName) { + var defaultInterface = 'default_' + interfaceName + '_interface'; + if (angular.isDefined(details[defaultInterface])) { + node[interfaceName + '_interface'] = details[defaultInterface]; + } + }); + } + angular.forEach(params, function(value, key) { node[key] = value; }); @@ -624,13 +670,28 @@ // Get the currently available drivers $httpBackend.whenGET(/\/api\/ironic\/drivers\/$/) - .respond(responseCode.SUCCESS, {drivers: drivers}); + .respond(function() { + return [responseCode.SUCCESS, + {drivers: service.getBaseDrivers()}]; + }); // Get driver properties $httpBackend.whenGET(/\/api\/ironic\/drivers\/([^\/]+)\/properties$/, undefined, ['driverName']) - .respond(responseCode.SUCCESS, []); + .respond(function(method, url, data, headers, params) { + return [responseCode.SUCCESS, + drivers[params.driverName].properties]; + }); + + // Get driver details + $httpBackend.whenGET(/\/api\/ironic\/drivers\/([^\/]+)$/, + undefined, + ['driverName']) + .respond(function(method, url, data, headers, params) { + return [responseCode.SUCCESS, + drivers[params.driverName].details]; + }); // Get glance images $httpBackend.whenGET(/\/api\/glance\/images/) @@ -768,14 +829,29 @@ } // init() /** - * @description Get the list of supported drivers + * @description Get the map of supported drivers * - * @return {[]} Array of driver objects + * @return {Object} Dictionary of driver objects */ function getDrivers() { return drivers; } + /** + * @description Get list of available drivers + * + * @return {[]} List of drivers. Each driver contains name + * and type properties. + */ + function getBaseDrivers() { + var driverList = []; + angular.forEach(drivers, function(driver) { + driverList.push({name: driver.details.name, + type: driver.details.type}); + }); + return driverList; + } + /** * @description Get the list of images * diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.module.js b/ironic_ui/static/dashboard/admin/ironic/ironic.module.js index 493e1cda..504d483e 100755 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.module.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.module.js @@ -56,5 +56,17 @@ EDIT_PORT_SUCCESS:'horizon.dashboard.admin.ironic.EDIT_PORT_SUCCESS' }; $provide.constant('horizon.dashboard.admin.ironic.events', events); + + $provide.constant('horizon.dashboard.admin.ironic.driverInterfaces', + ['boot', + 'console', + 'deploy', + 'inspect', + 'management', + 'network', + 'power', + 'raid', + 'storage', + 'vendor']); } })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js index 88d01749..87bdbc1a 100755 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js @@ -46,6 +46,7 @@ deleteNode: deleteNode, deletePort: deletePort, getDrivers: getDrivers, + getDriverDetails: getDriverDetails, getDriverProperties: getDriverProperties, getNode: getNode, getNodes: getNodes, @@ -71,6 +72,19 @@ return service; + /** + * @description Get details of a specified driver + * + * @param {string} driverName - Name of the driver. + * @return {promise} Promise containing driver details. + */ + function getDriverDetails(driverName) { + return apiService.get('/api/ironic/drivers/' + driverName) + .then(function(response) { + return response.data; + }); + } + /** * @description Retrieve a list of nodes * http://developer.openstack.org/api-ref/baremetal/? 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 1880109b..ce833f14 100644 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js @@ -25,6 +25,7 @@ 'deletePort', 'deletePortgroup', 'getDrivers', + 'getDriverDetails', 'getDriverProperties', 'getNode', 'getNodes', @@ -120,10 +121,31 @@ it('getDrivers', function() { ironicAPI.getDrivers() .then(function(drivers) { - expect(drivers.length).toBeGreaterThan(0); - angular.forEach(drivers, function(driver) { - expect(driver.name).toBeDefined(); - }); + expect(drivers).toEqual(ironicBackendMockService.getBaseDrivers()); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); + + it('getDriverDetails', function() { + var driver = ironicBackendMockService.params.defaultDriver; + ironicAPI.getDriverDetails(driver) + .then(function(details) { + var drivers = ironicBackendMockService.getDrivers(); + expect(details).toEqual(drivers[driver].details); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); + + it('getDriverProperties', function() { + var driver = ironicBackendMockService.params.defaultDriver; + ironicAPI.getDriverProperties(driver) + .then(function(properties) { + var drivers = ironicBackendMockService.getDrivers(); + expect(properties).toEqual(drivers[driver].properties); }) .catch(failTest); @@ -639,6 +661,7 @@ 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 737ab686..25e59b37 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 @@ -83,8 +83,8 @@ ctrl.portgroupDetailsTemplateUrl = path + "portgroup-details.html"; ctrl.node = null; - ctrl.nodeValidation = []; - ctrl.nodeValidationMap = {}; // Indexed by interface + ctrl.nodeValidation = []; // List of validation results + ctrl.nodeValidationMap = {}; // Validation results indexed by interface ctrl.nodeStateTransitions = []; ctrl.nodePowerTransitions = []; ctrl.ports = []; @@ -155,19 +155,6 @@ }); } - /** - * @name horizon.dashboard.admin.ironic.NodeDetailsController.nodeGetInterface - * @description Retrieve the current underlying interface for specified interface - * type. - * - * @param {string} interfacename - Name of interface, e.g. power, boot, etc. - * @return {string} current name of interface for the requested interface type. - */ - function nodeGetInterface(interfacename) { - return ctrl.node[interfacename + '_interface'] === null ? 'None' - : ctrl.node[interfacename + '_interface']; - } - /** * @name horizon.dashboard.admin.ironic.NodeDetailsController.retrievePorts * @description Retrieve the ports associated with the current node, @@ -227,7 +214,7 @@ angular.forEach(response.data, function(interfaceStatus) { interfaceStatus.id = interfaceStatus.interface; ctrl.nodeValidationMap[interfaceStatus.interface] = interfaceStatus; - interfaceStatus.hw_interface = nodeGetInterface(interfaceStatus.interface); + interfaceStatus.hw_interface = ctrl.node[interfaceStatus.interface + '_interface']; nodeValidation.push(interfaceStatus); }); ctrl.nodeValidation = nodeValidation; diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html b/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html index 0b150753..0561cf36 100644 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html @@ -338,7 +338,7 @@ - + {$ item.hw_interface $} diff --git a/ironic_ui/static/dashboard/admin/ironic/test-data.spec.js b/ironic_ui/static/dashboard/admin/ironic/test-data.spec.js index bece3eab..63732883 100644 --- a/ironic_ui/static/dashboard/admin/ironic/test-data.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/test-data.spec.js @@ -27,19 +27,23 @@ var BASE_NODE_CONTROLLER_PROPERTIES = [ 'cancel', 'collectionCheckPropertyUnique', 'collectionDeleteProperty', + 'driverInterfaceFields', 'driverProperties', 'driverPropertyGroups', 'drivers', 'images', 'isDriverPropertyActive', - 'loadDriverProperties', + 'loadDriver', + '_loadDriverDetails', + '_loadDriverProperties', 'loadingDriverProperties', 'modalTitle', 'node', 'propertyCollections', 'readyToSubmit', 'submitButtonTitle', - 'validHostNameRegex']; + 'validHostNameRegex', + 'driverType']; /* exported BASE_PORT_CONTROLLER_PROPERTIES */ diff --git a/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js b/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js index d60a14a7..3ea05a9b 100644 --- a/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js @@ -82,9 +82,9 @@ /** * @description Add instructions to the patch for processing a - * specified item + * specified property or collection * - * @param {object} item - item to be added + * @param {object} item - value of the item to be added * @param {string} path - Path to the item being added * @param {string} op - add or remove * @return {void} @@ -92,9 +92,14 @@ UpdatePatch.prototype._processItem = function(item, path, op) { $log.info("UpdatePatch._processItem: " + path + " " + op); if (isProperty(item)) { - this.patch.push({op: op, path: path, value: item}); + if (op === 'remove') { + this.patch.push({op: op, path: path}); + } else { + this.patch.push({op: op, path: path, value: item}); + } } else if (isCollection(item)) { - angular.forEach(item, function(partName, part) { + $log.info("Processing collection " + path); + angular.forEach(item, function(part, partName) { this._processItem(part, path + "/" + partName, op); }); } else { diff --git a/package.json b/package.json index e540a638..a6571970 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,16 @@ "eslint": "1.10.3", "eslint-config-openstack": "1.2.4", "eslint-plugin-angular": "1.0.1", - "jasmine-core": "2.4.1", - "karma": "1.1.2", - "karma-chrome-launcher": "1.0.1", + "jasmine-core": "^2.4.1", + "karma": "^1.1.2", + "karma-chrome-launcher": "^1.0.1", "karma-cli": "1.0.1", - "karma-coverage": "1.1.1", - "karma-jasmine": "1.0.2", - "karma-ng-html2js-preprocessor": "1.0.0", - "karma-threshold-reporter": "0.1.15" + "karma-coverage": "^1.1.1", + "karma-jasmine": "^1.0.2", + "karma-jasmine-matchers": "^3.7.0", + "karma-ng-html2js-preprocessor": "^1.0.0", + "karma-phantomjs-launcher": "^1.0.4", + "karma-threshold-reporter": "^0.1.15" }, "scripts": { "postinstall": "if [ ! -d .tox ] || [ ! -d .tox/py27 ]; then tox -epy27 --notest; fi", @@ -27,5 +29,7 @@ "lint": "eslint --no-color ironic_ui/static", "lintq": "eslint --quiet ironic_ui/static" }, - "dependencies": {} + "dependencies": { + "string.prototype.endswith": "^0.2.0" + } } diff --git a/releasenotes/notes/adding-driver-interfaces-while-node-enrollment-7cb82d6753074629.yaml b/releasenotes/notes/adding-driver-interfaces-while-node-enrollment-7cb82d6753074629.yaml new file mode 100644 index 00000000..858e3fdf --- /dev/null +++ b/releasenotes/notes/adding-driver-interfaces-while-node-enrollment-7cb82d6753074629.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds support for selecting driver interfaces for dynamic drivers + while creating nodes. The support for driver interfaces is not compatible + with classic drivers. This feature is supported with Pike and further + versions of ironic.