Allow selecting interfaces while enrolling nodes
Adds support for selecting from the enabled interfaces for the underlying driver while creating nodes. A new tab is added in the enroll node modal. Also enhanced "package.json" and "karma.conf.js" to widen the range of accepted versions and jasmine capabilities. Change-Id: Ie1b24cbf147b849a1d57fcdcbd735429ea7c9e34 Partial-Bug: #1672729
This commit is contained in:
parent
dfbe630ab2
commit
c73492c877
@ -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
|
||||
|
||||
|
@ -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<driver_name>[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)
|
||||
|
@ -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'
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -26,6 +26,15 @@
|
||||
data-target="#driverDetails"
|
||||
data-toggle="tab"
|
||||
translate>Driver Details</a></li>
|
||||
<li ng-if="ctrl.driverType === 'classic' || !ctrl.driverProperties"
|
||||
class="disabled">
|
||||
<a data-target="#driverInterfaces"
|
||||
translation>Driver Interfaces</a></li>
|
||||
<li ng-if="ctrl.driverType === 'dynamic' && ctrl.driverProperties">
|
||||
<a href=""
|
||||
data-target="#driverInterfaces"
|
||||
data-toggle="tab"
|
||||
translate>Driver Interfaces</a><li>
|
||||
</ul>
|
||||
|
||||
<!--base node form-->
|
||||
@ -69,7 +78,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!--network interface-->
|
||||
<div class="form-group">
|
||||
<div ng-if="ctrl.driverType === 'classic'" class="form-group">
|
||||
<label for="network_interface"
|
||||
class="control-label"
|
||||
translate>
|
||||
@ -91,6 +100,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--storage interface-->
|
||||
<div ng-if="ctrl.driverType === 'classic'" class="form-group">
|
||||
<label for="storage_interface"
|
||||
class="control-label"
|
||||
translate>
|
||||
Storage Interface
|
||||
</label>
|
||||
<span class="help-icon"
|
||||
data-container="body"
|
||||
title=""
|
||||
data-toggle="tooltip"
|
||||
data-original-title="{$ ::'Interface used for attaching and detaching volumes on this node.' | translate $}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
<div>
|
||||
<div class="btn-group">
|
||||
<label class="btn btn-default"
|
||||
ng-repeat="opt in ['noop', 'cinder']"
|
||||
ng-model="ctrl.node.storage_interface"
|
||||
uib-btn-radio="opt">{$ opt $}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--node driver-->
|
||||
<div class="form-group required">
|
||||
<label for="driver"
|
||||
@ -102,7 +134,7 @@
|
||||
class="form-control"
|
||||
ng-options="driver as driver.name for driver in ctrl.drivers"
|
||||
ng-model="ctrl.selectedDriver"
|
||||
ng-change="ctrl.loadDriverProperties(ctrl.selectedDriver.name)">
|
||||
ng-change="ctrl.loadDriver(ctrl.selectedDriver.name)">
|
||||
<option value="" disabled selected translate>Select a Driver</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -253,6 +285,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<!--end driver details tab-->
|
||||
|
||||
<!-- start of driver interfaces tab -->
|
||||
<div class="tab-pane", id="driverInterfaces">
|
||||
<p class="text-center"
|
||||
ng-if="ctrl.loadingDriverProperties">
|
||||
<small><em><i class="fa fa-spin fa-refresh"></i></em></small>
|
||||
</p>
|
||||
<form id="DriverInterfacesForm", name="DriverInterfacesForm">
|
||||
<div ng-repeat="field in ctrl.driverInterfaceFields"
|
||||
class="form-group">
|
||||
<form-field field="field" form="DriverInterfacesForm"></form-field>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!--end tabbed content-->
|
||||
</form>
|
||||
|
@ -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));
|
||||
|
||||
|
@ -78,7 +78,11 @@
|
||||
});
|
||||
expect(ctrl.node.name).toEqual(editNode.name);
|
||||
expect(ctrl.node.resource_class).toEqual(editNode.resource_class);
|
||||
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);
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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']);
|
||||
}
|
||||
})();
|
||||
|
@ -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/?
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -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;
|
||||
|
@ -338,7 +338,7 @@
|
||||
<span ng-switch-default class="fa fa-minus"></span>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<span ng-if="item.result">
|
||||
<span>
|
||||
{$ item.hw_interface $}</span>
|
||||
</td>
|
||||
<td class="rsp-p2">
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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)) {
|
||||
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 {
|
||||
|
20
package.json
20
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"
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user