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 $}