Add support for editing Ironic network ports
The port table in node-detais/configuration tab has been updated to include an "Edit port" action for each port. Closes-Bug: #1648563 Change-Id: I04ec8904dc67f98ff9f0d94a7fa46618cfba956c
This commit is contained in:
parent
9e977e1328
commit
0c4c948324
@ -17,6 +17,7 @@
|
||||
from django.conf import settings
|
||||
|
||||
from ironicclient import client
|
||||
from ironicclient.v1 import resource_fields as res_fields
|
||||
|
||||
from horizon.utils.memoized import memoized # noqa
|
||||
|
||||
@ -227,3 +228,18 @@ def port_delete(request, port_uuid):
|
||||
:return: Port
|
||||
"""
|
||||
return ironicclient(request).port.delete(port_uuid)
|
||||
|
||||
|
||||
def port_update(request, port_id, patch):
|
||||
"""Update a specified port.
|
||||
|
||||
:param request: HTTP request.
|
||||
:param node_id: The uuid of the port.
|
||||
:param patch: Sequence of update operations
|
||||
:return: port.
|
||||
|
||||
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.port.html#ironicclient.v1.port.PortManager.update
|
||||
"""
|
||||
port = ironicclient(request).port.update(port_id, patch)
|
||||
return dict([(f, getattr(port, f, ''))
|
||||
for f in res_fields.PORT_DETAILED_RESOURCE.fields])
|
||||
|
@ -123,6 +123,22 @@ class Ports(generic.View):
|
||||
return ironic.port_delete(request, params)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Port(generic.View):
|
||||
|
||||
url_regex = r'ironic/ports/(?P<port_id>[0-9a-f-]+)$'
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def patch(self, request, port_id):
|
||||
"""Update an Ironic port
|
||||
|
||||
:param request: HTTP request
|
||||
:param port_id: Port id.
|
||||
"""
|
||||
patch = request.DATA.get('patch')
|
||||
return ironic.port_update(request, port_id, patch)
|
||||
|
||||
|
||||
@urls.register
|
||||
class StatesPower(generic.View):
|
||||
|
||||
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2016 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';
|
||||
|
||||
var UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG = gettext("This field is disabled because a port cannot have any connectivity attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless its associated node is in an enroll, inspecting, mangeable state; or in maintenance mode."); // eslint-disable-line max-len
|
||||
|
||||
/**
|
||||
* Controller used to edit a specified node port
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.controller('EditPortController', EditPortController);
|
||||
|
||||
EditPortController.$inject = [
|
||||
'$rootScope',
|
||||
'$controller',
|
||||
'$uibModalInstance',
|
||||
'$log',
|
||||
'$q',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'horizon.dashboard.admin.ironic.update-patch.service',
|
||||
'port',
|
||||
'node'
|
||||
];
|
||||
|
||||
function EditPortController($rootScope,
|
||||
$controller,
|
||||
$uibModalInstance,
|
||||
$log,
|
||||
$q,
|
||||
ironic,
|
||||
ironicEvents,
|
||||
updatePatchService,
|
||||
port,
|
||||
node) {
|
||||
var ctrl = this;
|
||||
$controller('BasePortController',
|
||||
{ctrl: ctrl,
|
||||
$uibModalInstance: $uibModalInstance});
|
||||
|
||||
ctrl.modalTitle = gettext("Edit Port");
|
||||
ctrl.submitButtonTitle = gettext("Update Port");
|
||||
|
||||
var cannotEditConnectivityAttr =
|
||||
!(node.maintenance || (node.provision_state === "enroll" ||
|
||||
node.provision_state === "inspecting" ||
|
||||
node.provision_state === "manageable"));
|
||||
|
||||
// Initialize form fields
|
||||
ctrl.port.address = port.address;
|
||||
|
||||
ctrl.pxeEnabled.value = port.pxe_enabled ? 'True' : 'False';
|
||||
if (cannotEditConnectivityAttr) {
|
||||
ctrl.pxeEnabled.disabled = true;
|
||||
ctrl.pxeEnabled.info = UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG;
|
||||
}
|
||||
|
||||
angular.forEach(
|
||||
['port_id', 'switch_id', 'switch_info'],
|
||||
function(prop) {
|
||||
if (angular.isDefined(port.local_link_connection[prop])) {
|
||||
ctrl.localLinkConnection[prop].value =
|
||||
port.local_link_connection[prop];
|
||||
}
|
||||
});
|
||||
|
||||
if (cannotEditConnectivityAttr) {
|
||||
ctrl.localLinkConnection.$setDisabled(
|
||||
true,
|
||||
UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG);
|
||||
}
|
||||
|
||||
ctrl.port.extra = angular.copy(port.extra);
|
||||
|
||||
/**
|
||||
* Apply updates to the port being edited
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.updatePort = function() {
|
||||
var patcher = new updatePatchService.UpdatePatch();
|
||||
|
||||
$log.info("Updating port " + JSON.stringify(port));
|
||||
|
||||
patcher.buildPatch(port.address, ctrl.port.address, "/address");
|
||||
patcher.buildPatch(port.pxe_enabled ? 'True' : 'False',
|
||||
ctrl.pxeEnabled.value,
|
||||
"/pxe_enabled");
|
||||
patcher.buildPatch(port.local_link_connection,
|
||||
ctrl.localLinkConnection.$toPortAttr(),
|
||||
"/local_link_connection");
|
||||
patcher.buildPatch(port.extra, ctrl.port.extra, "/extra");
|
||||
|
||||
var patch = patcher.getPatch();
|
||||
$log.info("patch = " + JSON.stringify(patch.patch));
|
||||
if (patch.status === updatePatchService.UpdatePatch.status.OK) {
|
||||
ironic.updatePort(port.uuid, patch.patch).then(function(port) {
|
||||
$rootScope.$emit(ironicEvents.EDIT_PORT_SUCCESS);
|
||||
$uibModalInstance.close(port);
|
||||
});
|
||||
} else {
|
||||
toastService.add('error',
|
||||
gettext('Unable to create port update patch.'));
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.submit = function() {
|
||||
ctrl.updatePort();
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.edit-port.service',
|
||||
editPortService);
|
||||
|
||||
editPortService.$inject = [
|
||||
'$uibModal',
|
||||
'horizon.dashboard.admin.ironic.basePath'
|
||||
];
|
||||
|
||||
function editPortService($uibModal, basePath) {
|
||||
var service = {
|
||||
modal: modal
|
||||
};
|
||||
|
||||
function modal(port, node) {
|
||||
var options = {
|
||||
controller: 'EditPortController as ctrl',
|
||||
backdrop: 'static',
|
||||
resolve: {
|
||||
port: function() {
|
||||
return port;
|
||||
},
|
||||
node: function() {
|
||||
return node;
|
||||
}
|
||||
},
|
||||
templateUrl: basePath + '/base-port/base-port.html'
|
||||
};
|
||||
return $uibModal.open(options).result;
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
})();
|
@ -52,7 +52,8 @@
|
||||
DELETE_NODE_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_NODE_SUCCESS',
|
||||
EDIT_NODE_SUCCESS:'horizon.dashboard.admin.ironic.EDIT_NODE_SUCCESS',
|
||||
CREATE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.CREATE_PORT_SUCCESS',
|
||||
DELETE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_PORT_SUCCESS'
|
||||
DELETE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_PORT_SUCCESS',
|
||||
EDIT_PORT_SUCCESS:'horizon.dashboard.admin.ironic.EDIT_PORT_SUCCESS'
|
||||
};
|
||||
$provide.constant('horizon.dashboard.admin.ironic.events', events);
|
||||
}
|
||||
|
@ -55,6 +55,7 @@
|
||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode,
|
||||
setNodeProvisionState: setNodeProvisionState,
|
||||
updateNode: updateNode,
|
||||
updatePort: updatePort,
|
||||
validateNode: validateNode
|
||||
};
|
||||
|
||||
@ -456,6 +457,32 @@
|
||||
return $q.reject(msg);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Update the definition of a specified port.
|
||||
*
|
||||
* http://developer.openstack.org/api-ref/baremetal/#update-a-port
|
||||
*
|
||||
* @param {string} portUuid – UUID of a port.
|
||||
* @param {object[]} patch – Sequence of update operations
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function updatePort(portUuid, patch) {
|
||||
return apiService.patch('/api/ironic/ports/' + portUuid,
|
||||
{patch: patch})
|
||||
.then(function(response) {
|
||||
var msg = gettext('Successfully updated port %s');
|
||||
toastService.add('success', interpolate(msg, [portUuid], false));
|
||||
return response.data; // The updated port
|
||||
})
|
||||
.catch(function(response) {
|
||||
var msg = interpolate(gettext('Unable to update port %s: %s'),
|
||||
[portUuid, response.data],
|
||||
false);
|
||||
toastService.add('error', msg);
|
||||
return $q.reject(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}());
|
||||
|
@ -32,6 +32,7 @@
|
||||
'horizon.dashboard.admin.ironic.actions',
|
||||
'horizon.dashboard.admin.ironic.basePath',
|
||||
'horizon.dashboard.admin.ironic.edit-node.service',
|
||||
'horizon.dashboard.admin.ironic.edit-port.service',
|
||||
'horizon.dashboard.admin.ironic.maintenance.service',
|
||||
'horizon.dashboard.admin.ironic.node-state-transition.service',
|
||||
'horizon.dashboard.admin.ironic.validUuidPattern'
|
||||
@ -46,6 +47,7 @@
|
||||
actions,
|
||||
basePath,
|
||||
editNodeService,
|
||||
editPortService,
|
||||
maintenanceService,
|
||||
nodeStateTransitionService,
|
||||
validUuidPattern) {
|
||||
@ -81,6 +83,7 @@
|
||||
ctrl.editNode = editNode;
|
||||
ctrl.createPort = createPort;
|
||||
ctrl.deletePort = deletePort;
|
||||
ctrl.editPort = editPort;
|
||||
ctrl.refresh = refresh;
|
||||
|
||||
$scope.emptyObject = function(obj) {
|
||||
@ -223,6 +226,18 @@
|
||||
ctrl.actions.createPort(ctrl.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Edit a specified port
|
||||
*
|
||||
* @param {port} port - Port to be edited
|
||||
* @return {void}
|
||||
*/
|
||||
function editPort(port) {
|
||||
editPortService.modal(port, ctrl.node).then(function() {
|
||||
ctrl.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.ironic.NodeDetailsController.deletePort
|
||||
* @description Delete a list of ports
|
||||
|
@ -18,7 +18,7 @@
|
||||
'use strict';
|
||||
|
||||
describe('horizon.dashboard.admin.ironic.node-details', function () {
|
||||
var ctrl, $q;
|
||||
var ctrl, $q, nodeStateTransitionService;
|
||||
var nodeUuid = "0123abcd-0123-4567-abcd-0123456789ab";
|
||||
var nodeName = "herp";
|
||||
var numPorts = 2;
|
||||
@ -27,9 +27,17 @@
|
||||
return '' + index + index + nodeUuid.substring(2);
|
||||
}
|
||||
|
||||
function portMacAddr(index) {
|
||||
var mac = '' + index + index;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
mac += ':' + index + index;
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
function createPort(nodeUuid, index, extra) {
|
||||
var uuid = portUuid(nodeUuid, index);
|
||||
var port = {uuid: uuid, id: uuid};
|
||||
var port = {uuid: portUuid(nodeUuid, index),
|
||||
address: portMacAddr(index)};
|
||||
if (angular.isDefined(extra)) {
|
||||
port.extra = extra;
|
||||
}
|
||||
@ -37,7 +45,9 @@
|
||||
}
|
||||
|
||||
function createNode(name, uuid) {
|
||||
return {name: name, uuid: uuid, id: uuid};
|
||||
return {name: name,
|
||||
uuid: uuid,
|
||||
provision_state: 'enroll'};
|
||||
}
|
||||
|
||||
var ironicAPI = {
|
||||
@ -88,16 +98,20 @@
|
||||
var $location = _$location_;
|
||||
$location.path('/admin/ironic/' + nodeUuid + '/');
|
||||
|
||||
nodeStateTransitionService = $injector.get(
|
||||
'horizon.dashboard.admin.ironic.node-state-transition.service');
|
||||
|
||||
ctrl = controller(
|
||||
'horizon.dashboard.admin.ironic.NodeDetailsController',
|
||||
{$scope: scope,
|
||||
$location: $location,
|
||||
'horizon.dashboard.admin.ironic.edit-port.service': {},
|
||||
'horizon.dashboard.admin.ironic.actions': {}});
|
||||
|
||||
scope.$apply();
|
||||
}));
|
||||
|
||||
it('should be defined', function () {
|
||||
it('controller should be defined', function () {
|
||||
expect(ctrl).toBeDefined();
|
||||
});
|
||||
|
||||
@ -107,7 +121,9 @@
|
||||
|
||||
it('should have a node', function () {
|
||||
expect(ctrl.node).toBeDefined();
|
||||
expect(ctrl.node).toEqual(createNode(nodeName, nodeUuid));
|
||||
var node = createNode(nodeName, nodeUuid);
|
||||
node.id = node.uuid;
|
||||
expect(ctrl.node).toEqual(node);
|
||||
});
|
||||
|
||||
it('should have ports', function () {
|
||||
@ -116,7 +132,10 @@
|
||||
|
||||
var ports = [];
|
||||
for (var i = 0; i < numPorts; i++) {
|
||||
ports.push(createPort(ctrl.node.uuid, i));
|
||||
var port = createPort(ctrl.node.uuid, i);
|
||||
port.id = port.uuid;
|
||||
port.name = port.address;
|
||||
ports.push(port);
|
||||
}
|
||||
expect(ctrl.portsSrc).toEqual(ports);
|
||||
});
|
||||
@ -138,5 +157,16 @@
|
||||
expect(ctrl.getVifPortId(createPort(ctrl.node.uuid, 1, extra))).
|
||||
toEqual("port_uuid");
|
||||
});
|
||||
|
||||
it('should have node-state-transitions', function () {
|
||||
expect(ctrl.nodeStateTransitions).toBeDefined();
|
||||
expect(ctrl.nodeStateTransitions).toEqual(
|
||||
nodeStateTransitionService.getTransitions(ctrl.node.provision_state));
|
||||
});
|
||||
|
||||
it('should have node-validation', function () {
|
||||
expect(ctrl.nodeValidation).toBeDefined();
|
||||
expect(ctrl.nodeValidation).toEqual([]);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -90,12 +90,21 @@
|
||||
</ul>
|
||||
</td>
|
||||
<td class="actions_column">
|
||||
<action-list>
|
||||
<action action-classes="'btn btn-default btn-sm'"
|
||||
callback="ctrl.deletePort"
|
||||
item="[port]">
|
||||
<span class="fa fa-trash"></span>
|
||||
<action-list uib-dropdown class="pull-right">
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
callback="ctrl.editPort"
|
||||
item="port">
|
||||
{$ ::'Edit port' | translate $}
|
||||
</action>
|
||||
<menu>
|
||||
<action button-type="menu-item"
|
||||
callback="ctrl.deletePort"
|
||||
item="[port]">
|
||||
<span class="fa fa-trash"></span>
|
||||
{$ ::'Delete port' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
|
Loading…
Reference in New Issue
Block a user