From 7dc721fd790b8b3c71f7afb4d4cb3372f58b7204 Mon Sep 17 00:00:00 2001 From: Peter Piela Date: Wed, 26 Jul 2017 11:14:53 -0400 Subject: [PATCH] Added support for creating portgroups The existing functionality for portgroups in the node-details/configuration tab has been augmented with the capability to create portgroups. Clicking on the menu button above the portgroup table will launch a modal dialog that guides the user in entering the parameters required to create a portgroup. Change-Id: Ieef7fff4f29318ae74e11559dc69e6317c3c25d6 --- .../base-portgroup.controller.js | 103 +++++++++++++ .../base-portgroup.controller.spec.js | 61 ++++++++ .../ironic/base-portgroup/base-portgroup.html | 37 +++++ .../create-portgroup.controller.js | 74 +++++++++ .../create-portgroup.controller.spec.js | 145 ++++++++++++++++++ .../create-portgroup.service.js | 56 +++++++ .../node-details/node-details.controller.js | 16 ++ .../node-details/sections/configuration.html | 2 +- .../dashboard/admin/ironic/test-data.spec.js | 10 ++ 9 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.spec.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.html create mode 100644 ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.spec.js create mode 100644 ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.service.js diff --git a/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.js b/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.js new file mode 100644 index 00000000..c0f0e293 --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.js @@ -0,0 +1,103 @@ +/* + * 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'; + + /** + * Controller used to support operations on an Ironic portgroup + */ + angular + .module('horizon.dashboard.admin.ironic') + .controller('BasePortgroupController', BasePortgroupController); + + BasePortgroupController.$inject = [ + '$uibModalInstance', + 'horizon.dashboard.admin.ironic.validMacAddressPattern', + 'horizon.dashboard.admin.ironic.validDatapathIdPattern', + 'horizon.dashboard.admin.ironic.form-field.service', + 'horizon.dashboard.admin.ironic.property-collection.service', + 'ctrl' + ]; + + function BasePortgroupController($uibModalInstance, + validMacAddressPattern, + validDatapathIdPattern, + formFieldService, + propertyCollectionService, + ctrl) { + + ctrl.address = new formFieldService.FormField({ + id: "macAddress", + title: gettext("MAC address"), + desc: gettext("MAC address for this portgroup."), + pattern: new RegExp(validMacAddressPattern), + value: null, + autoFocus: true + }); + + ctrl.name = new formFieldService.FormField({ + id: "portgroupName", + title: gettext("Name"), + desc: gettext("Name for the portgroup.") + }); + + ctrl.standalone_ports_supported = new formFieldService.FormField({ + type: "radio", + id: "standalonePorts", + title: gettext("Standalone Ports Supported"), + desc: gettext( + "Specifies whether ports in this portgroup can be used as standalone ports."), + options: ['True', 'False'], + value: 'True'}); + + ctrl.mode = new formFieldService.FormField({ + type: "radio", + id: "mode", + title: gettext("Mode"), + desc: gettext("Linux portgroup mode. For possible values refer to https://www.kernel.org/doc/Documentation/networking/bonding.txt"), // eslint-disable-line max-len + options: ['balance-rr', + 'active-backup', + 'balance-xor', + 'broadcast', + '802.3ad', + 'balance-tlb', + 'balance-alb'], + value: 'active-backup'}); + + ctrl.properties = new propertyCollectionService.PropertyCollection({ + id: 'properties', + title: gettext('Properties'), + addPropertyLabel: gettext('Add Property'), + placeholder: gettext('Property Name') + }); + + ctrl.extra = new propertyCollectionService.PropertyCollection({ + id: 'extra', + title: gettext('Extras'), + addPropertyLabel: gettext('Add Extra'), + placeholder: gettext('Property Name') + }); + + /** + * Cancel the modal + * + * @return {void} + */ + ctrl.cancel = function() { + $uibModalInstance.dismiss('cancel'); + }; + } +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.spec.js b/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.spec.js new file mode 100644 index 00000000..a99fea0a --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.controller.spec.js @@ -0,0 +1,61 @@ +/* + * 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.base-portgroup', function () { + var uibModalInstance; + var ctrl = {}; + + beforeEach(module('horizon.dashboard.admin.ironic')); + + beforeEach(module(function($provide) { + $provide.value('$uibModal', {}); + })); + + beforeEach(module(function($provide) { + uibModalInstance = { + dismiss: jasmine.createSpy() + }; + $provide.value('$uibModalInstance', uibModalInstance); + })); + + beforeEach(inject(function($injector) { + var controller = $injector.get('$controller'); + controller('BasePortgroupController', {ctrl: ctrl}); + })); + + it('controller should be defined', function () { + expect(ctrl).toBeDefined(); + }); + + it('base construction', function () { + expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual( + BASE_PORTGROUP_CONTROLLER_PROPERTIES.sort()); + + angular.forEach( + ['address', 'name', 'standalone_ports_supported', 'mode'], + function(propertyName) { + expect(Object.keys(ctrl[propertyName])).toContain('value'); + }); + }); + + it('cancel', function () { + ctrl.cancel(); + expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel'); + }); + }); +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.html b/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.html new file mode 100644 index 00000000..075167d7 --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/base-portgroup/base-portgroup.html @@ -0,0 +1,37 @@ + + + + diff --git a/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.js b/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.js new file mode 100644 index 00000000..f0cd3d5a --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.js @@ -0,0 +1,74 @@ +/* + * 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'; + + /** + * Controller used to create a portgroup on a specified node + */ + angular + .module('horizon.dashboard.admin.ironic') + .controller('CreatePortgroupController', CreatePortgroupController); + + CreatePortgroupController.$inject = [ + '$controller', + '$uibModalInstance', + 'horizon.app.core.openstack-service-api.ironic', + 'node' + ]; + + function CreatePortgroupController($controller, + $uibModalInstance, + ironic, + node) { + var ctrl = this; + + $controller('BasePortgroupController', + {ctrl: ctrl, + $uibModalInstance: $uibModalInstance}); + + ctrl.modalTitle = gettext("Create Portgroup"); + ctrl.submitButtonTitle = ctrl.modalTitle; + + /** + * Create the defined portgroup + * + * @return {void} + */ + ctrl.createPortgroup = function() { + var portgroup = { + extra: ctrl.extra.properties, + properties: ctrl.properties.properties + }; + + portgroup.node_uuid = node.uuid; + angular.forEach(['address', 'name', 'standalone_ports_supported', 'mode'], + function(propertyName) { + if (ctrl[propertyName].hasValue()) { + portgroup[propertyName] = ctrl[propertyName].value; + } + }); + ironic.createPortgroup(portgroup).then( + function(createdPortgroup) { + $uibModalInstance.close(createdPortgroup); + }); + }; + + ctrl.submit = function() { + ctrl.createPortgroup(); + }; + } +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.spec.js b/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.spec.js new file mode 100644 index 00000000..dfe39c57 --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.controller.spec.js @@ -0,0 +1,145 @@ +/* + * 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.create-portgroup', function () { + var ironicBackendMockService, uibModalInstance, ironicAPI, controller; + + beforeEach(module('horizon.dashboard.admin.ironic')); + + beforeEach(module('horizon.framework.util')); + + beforeEach(module(function($provide) { + $provide.value('$uibModal', {}); + })); + + beforeEach(module(function($provide) { + uibModalInstance = {}; + $provide.value('$uibModalInstance', uibModalInstance); + })); + + 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(); + + ironicAPI = + $injector.get('horizon.app.core.openstack-service-api.ironic'); + + controller = $injector.get('$controller'); + })); + + afterEach(function() { + ironicBackendMockService.postTest(); + }); + + function createController() { + return ironicAPI.createNode({ + driver: ironicBackendMockService.params.defaultDriver}) + .then(function(response) { + var node = response.data; + return {node: response.data, + ctrl: controller('CreatePortgroupController', + {node: node})}; + }); + } + + it('controller should be defined', function () { + createController() + .then(function(data) { + expect(data.ctrl).toBeDefined(); + }) + .catch(function() { + fail(); + }); + ironicBackendMockService.flush(); + }); + + it('base construction', function () { + createController() + .then(function(data) { + var ctrl = data.ctrl; + var properties = angular.copy(BASE_PORTGROUP_CONTROLLER_PROPERTIES); + properties.push('modalTitle'); + properties.push('submitButtonTitle'); + properties.push('createPortgroup'); + properties.push('submit'); + expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual( + properties.sort()); + }) + .catch(function() { + fail(); + }); + ironicBackendMockService.flush(); + }); + + it('submit - success', function () { + var portgroupParams = { + address: '00:00:00:00:00:00', + name: 'my-portgroup', + standalone_ports_supported: 'False', + mode: '802.3ad', + properties: { + prop_1: 'prop-1-value' + }, + extra: { + extra_1: 'extra-1-value' + } + }; + + spyOn(ironicAPI, 'createPortgroup').and.callThrough(); + + uibModalInstance.close = function(portgroup) { + expect(portgroup).toEqual( + ironicBackendMockService.getPortgroup(portgroupParams.name)); + }; + + createController() + .then(function(data) { + var ctrl = data.ctrl; + angular.forEach( + portgroupParams, + function(value, param) { + if (param === 'properties' || + param === 'extra') { + ctrl[param].properties = value; + } else { + ctrl[param].value = value; + } + }); + portgroupParams.node_uuid = data.node.uuid; + + ctrl.submit(); + + expect(ironicAPI.createPortgroup) + .toHaveBeenCalledWith(portgroupParams); + }) + .catch(function() { + fail(); + }); + ironicBackendMockService.flush(); + }); + }); +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.service.js b/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.service.js new file mode 100644 index 00000000..7c3d2494 --- /dev/null +++ b/ironic_ui/static/dashboard/admin/ironic/create-portgroup/create-portgroup.service.js @@ -0,0 +1,56 @@ +/* + * 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'; + + angular + .module('horizon.dashboard.admin.ironic') + .factory('horizon.dashboard.admin.ironic.create-portgroup.service', + createPortgroupService); + + createPortgroupService.$inject = [ + '$uibModal', + 'horizon.dashboard.admin.ironic.basePath' + ]; + + function createPortgroupService($uibModal, basePath) { + var service = { + createPortgroup: createPortgroup + }; + return service; + + /** + * @description Launch a modal dialog that will guide the user + * in creating a new portgroup + * + * @param {object} node - Node to which the portgroup will be associated + * @return {promise} Object describing the created portgroup + */ + function createPortgroup(node) { + var options = { + controller: 'CreatePortgroupController as ctrl', + backdrop: 'static', + resolve: { + node: function() { + return node; + } + }, + templateUrl: basePath + '/base-portgroup/base-portgroup.html' + }; + return $uibModal.open(options).result; + } + } +})(); 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 28ebf3b1..5f6fb016 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 @@ -31,6 +31,7 @@ 'horizon.dashboard.admin.ironic.basePath', 'horizon.dashboard.admin.ironic.edit-node.service', 'horizon.dashboard.admin.ironic.create-port.service', + 'horizon.dashboard.admin.ironic.create-portgroup.service', 'horizon.dashboard.admin.ironic.edit-port.service', 'horizon.dashboard.admin.ironic.maintenance.service', 'horizon.dashboard.admin.ironic.bootdevice.service', @@ -46,6 +47,7 @@ basePath, editNodeService, createPortService, + createPortgroupService, editPortService, maintenanceService, bootDeviceService, @@ -90,6 +92,7 @@ ctrl.getVifPortId = getVifPortId; ctrl.editNode = editNode; ctrl.createPort = createPort; + ctrl.createPortgroup = createPortgroup; ctrl.deletePort = deletePort; ctrl.editPort = editPort; ctrl.refresh = refresh; @@ -318,5 +321,18 @@ function toggleConsoleMode() { ironic.nodeSetConsoleMode(ctrl.node.uuid, !ctrl.node.console_enabled); } + + /** + * @name horizon.dashboard.admin.ironic.NodeDetailsController.createPortgroup + * @description Initiate creation of a portgroup for the current + * node + * + * @return {void} + */ + function createPortgroup() { + createPortgroupService.createPortgroup(ctrl.node).then(function() { + ctrl.refresh(); + }); + } } })(); 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 0950bc0e..90a89d0d 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 @@ -138,7 +138,7 @@ + callback="ctrl.createPortgroup"> {$ ::'Create portgroup' | translate $} 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 f2b90a7c..ca4c67f0 100644 --- a/ironic_ui/static/dashboard/admin/ironic/test-data.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/test-data.spec.js @@ -41,6 +41,16 @@ var BASE_NODE_CONTROLLER_PROPERTIES = [ 'submitButtonTitle', 'validHostNameRegex']; +/* exported BASE_PORTGROUP_CONTROLLER_PROPERTIES */ +var BASE_PORTGROUP_CONTROLLER_PROPERTIES = [ + 'address', + 'cancel', + 'extra', + 'mode', + 'name', + 'properties', + 'standalone_ports_supported']; + /* exported PROPERTY_COLLECTION_PROPERTIES */ var PROPERTY_COLLECTION_PROPERTIES = [