Add support for manual cleaning of nodes
- The action list associated with a node in manageable state will have a "Clean" item. - When the clean action is initiated the user is prompted with a modal dialog in which he or she enters or copies a set of cleaning steps in JSON format. - Basic validation is performed on the JSON. The user is able to submit the cleaning request only when validation is successful. - Cleaning is not currently available as a batch action. Change-Id: I2af9385e2d9532a9ec46993d65f8c510e419b6c9 Closes-Bug: #1648559
This commit is contained in:
parent
d92d573f54
commit
736c2dab54
@ -100,17 +100,21 @@ def node_set_power_state(request, node_id, state):
|
|||||||
return ironicclient(request).node.set_power_state(node_id, state)
|
return ironicclient(request).node.set_power_state(node_id, state)
|
||||||
|
|
||||||
|
|
||||||
def node_set_provision_state(request, node_uuid, state):
|
def node_set_provision_state(request, node_uuid, state, cleansteps=None):
|
||||||
"""Set the target provision state for a given node.
|
"""Set the target provision state for a given node.
|
||||||
|
|
||||||
:param request: HTTP request.
|
:param request: HTTP request.
|
||||||
:param node_uuid: The UUID of the node.
|
:param node_uuid: The UUID of the node.
|
||||||
:param state: the target provision state to set.
|
:param state: the target provision state to set.
|
||||||
|
:param cleansteps: Optional list of cleaning steps
|
||||||
:return: node.
|
:return: node.
|
||||||
|
|
||||||
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_provision_state
|
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_provision_state
|
||||||
"""
|
"""
|
||||||
return ironicclient(request).node.set_provision_state(node_uuid, state)
|
node_manager = ironicclient(request).node
|
||||||
|
return node_manager.set_provision_state(node_uuid,
|
||||||
|
state,
|
||||||
|
cleansteps=cleansteps)
|
||||||
|
|
||||||
|
|
||||||
def node_set_maintenance(request, node_id, state, maint_reason=None):
|
def node_set_maintenance(request, node_id, state, maint_reason=None):
|
||||||
|
@ -170,7 +170,11 @@ class StatesProvision(generic.View):
|
|||||||
:return: Return code
|
:return: Return code
|
||||||
"""
|
"""
|
||||||
verb = request.DATA.get('verb')
|
verb = request.DATA.get('verb')
|
||||||
return ironic.node_set_provision_state(request, node_uuid, verb)
|
clean_steps = request.DATA.get('clean_steps')
|
||||||
|
return ironic.node_set_provision_state(request,
|
||||||
|
node_uuid,
|
||||||
|
verb,
|
||||||
|
clean_steps)
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
@urls.register
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc controller
|
||||||
|
* @name horizon.dashboard.admin.ironic:CleanNodeController
|
||||||
|
* @ngController
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Controller used to prompt the user for a list of clean-steps
|
||||||
|
* in JSON format that will be applied a node
|
||||||
|
*/
|
||||||
|
angular
|
||||||
|
.module('horizon.dashboard.admin.ironic')
|
||||||
|
.controller('CleanNodeController', CleanNodeController);
|
||||||
|
|
||||||
|
CleanNodeController.$inject = [
|
||||||
|
'$uibModalInstance'
|
||||||
|
];
|
||||||
|
|
||||||
|
function CleanNodeController($uibModalInstance) {
|
||||||
|
var ctrl = this;
|
||||||
|
|
||||||
|
ctrl.errMsg = '';
|
||||||
|
|
||||||
|
ctrl.cancel = function() {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl.clean = function(cleanSteps) {
|
||||||
|
try {
|
||||||
|
var steps = JSON.parse(cleanSteps);
|
||||||
|
if (angular.isArray(steps) && steps.length > 0) {
|
||||||
|
var valid = true;
|
||||||
|
angular.forEach(steps, function(step) {
|
||||||
|
if (angular.isUndefined(step.interface) ||
|
||||||
|
angular.isUndefined(step.step)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (valid) {
|
||||||
|
$uibModalInstance.close(steps);
|
||||||
|
} else {
|
||||||
|
ctrl.errMsg = gettext('Each cleaning step must be an object that contains "interface" and "step" properties'); // eslint-disable-line max-len
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctrl.errMsg = gettext('Clean steps should be an non-empty array');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ctrl.errMsg = gettext('Unable to validate the JSON input');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
@ -0,0 +1,41 @@
|
|||||||
|
<div class="modal-header" modal-draggable>
|
||||||
|
<h3 class="modal-title" translate>Clean Node</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body clearfix">
|
||||||
|
<div class="content">
|
||||||
|
<div translate class="subtitle">Provide a list of cleaning steps in JSON format</div>
|
||||||
|
<div class="form-group"
|
||||||
|
ng-init="cleanSteps=''">
|
||||||
|
<div class="form-field">
|
||||||
|
<textarea type="text"
|
||||||
|
class="form-control input-sm"
|
||||||
|
ng-model="cleanSteps"
|
||||||
|
ng-change="ctrl.errMsg=''"
|
||||||
|
auto-focus
|
||||||
|
rows="8"
|
||||||
|
required
|
||||||
|
placeholder=""/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div uib-alert
|
||||||
|
ng-hide="ctrl.errMsg === ''"
|
||||||
|
ng-class="'alert-danger'">
|
||||||
|
{$ ctrl.errMsg $}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-default secondary"
|
||||||
|
type="button"
|
||||||
|
ng-click="ctrl.cancel()"
|
||||||
|
translate>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
ng-disabled="cleanSteps === '' || ctrl.errMsg !== ''"
|
||||||
|
ng-click="ctrl.clean(cleanSteps)"
|
||||||
|
translate>
|
||||||
|
Clean node
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Cray Inc.
|
||||||
|
* Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
|
||||||
|
*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @ngdoc service
|
||||||
|
* @name horizon.dashboard.admin.ironic.maintenance.service
|
||||||
|
* @description Service for putting nodes in, and removing them from
|
||||||
|
* maintenance mode
|
||||||
|
*/
|
||||||
|
angular
|
||||||
|
.module('horizon.dashboard.admin.ironic')
|
||||||
|
.factory('horizon.dashboard.admin.ironic.clean-node.service',
|
||||||
|
cleanNodeService);
|
||||||
|
|
||||||
|
cleanNodeService.$inject = [
|
||||||
|
'$uibModal',
|
||||||
|
'horizon.dashboard.admin.ironic.basePath',
|
||||||
|
'horizon.app.core.openstack-service-api.ironic'
|
||||||
|
];
|
||||||
|
|
||||||
|
function cleanNodeService($uibModal, basePath, ironic) {
|
||||||
|
var service = {
|
||||||
|
clean: clean
|
||||||
|
};
|
||||||
|
return service;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @description Initiate manual cleaning of an Ironic node.
|
||||||
|
* The user is prompted for a list of steps that are then
|
||||||
|
* used to clean the node.
|
||||||
|
*
|
||||||
|
* @param {object} node - Node to be cleaned
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function clean(node) {
|
||||||
|
var options = {
|
||||||
|
controller: 'CleanNodeController as ctrl',
|
||||||
|
backdrop: 'static',
|
||||||
|
templateUrl: basePath + '/clean-node/clean-node.html'
|
||||||
|
};
|
||||||
|
$uibModal.open(options).result.then(function(cleanSteps) {
|
||||||
|
return ironic.setNodeProvisionState(node.uuid,
|
||||||
|
'clean',
|
||||||
|
cleanSteps);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -244,14 +244,14 @@
|
|||||||
* @param {string} uuid – UUID of a node.
|
* @param {string} uuid – UUID of a node.
|
||||||
* @param {string} verb – Provisioning verb used to move node to desired
|
* @param {string} verb – Provisioning verb used to move node to desired
|
||||||
* target state
|
* target state
|
||||||
|
* @param {object []} cleanSteps - List of cleaning steps. Only used
|
||||||
|
* when the value of verb is 'clean'
|
||||||
* @return {promise} Promise
|
* @return {promise} Promise
|
||||||
*/
|
*/
|
||||||
function setNodeProvisionState(uuid, verb) {
|
function setNodeProvisionState(uuid, verb, cleanSteps) {
|
||||||
var data = {
|
|
||||||
verb: verb
|
|
||||||
};
|
|
||||||
return apiService.put('/api/ironic/nodes/' + uuid + '/states/provision',
|
return apiService.put('/api/ironic/nodes/' + uuid + '/states/provision',
|
||||||
data)
|
{verb: verb,
|
||||||
|
clean_steps: cleanSteps})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
var msg = gettext(
|
var msg = gettext(
|
||||||
'A request has been made to change the provisioning state of node %s');
|
'A request has been made to change the provisioning state of node %s');
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
'horizon.dashboard.admin.ironic.events',
|
'horizon.dashboard.admin.ironic.events',
|
||||||
'horizon.framework.widgets.modal.deleteModalService',
|
'horizon.framework.widgets.modal.deleteModalService',
|
||||||
'horizon.dashboard.admin.ironic.create-port.service',
|
'horizon.dashboard.admin.ironic.create-port.service',
|
||||||
|
'horizon.dashboard.admin.ironic.clean-node.service',
|
||||||
'$q',
|
'$q',
|
||||||
'$rootScope'
|
'$rootScope'
|
||||||
];
|
];
|
||||||
@ -39,6 +40,7 @@
|
|||||||
ironicEvents,
|
ironicEvents,
|
||||||
deleteModalService,
|
deleteModalService,
|
||||||
createPortService,
|
createPortService,
|
||||||
|
cleanNodeService,
|
||||||
$q,
|
$q,
|
||||||
$rootScope) {
|
$rootScope) {
|
||||||
var service = {
|
var service = {
|
||||||
@ -169,7 +171,11 @@
|
|||||||
* the node to the desired target state for the node.
|
* the node to the desired target state for the node.
|
||||||
*/
|
*/
|
||||||
function setProvisionState(args) {
|
function setProvisionState(args) {
|
||||||
ironic.setNodeProvisionState(args.node.uuid, args.verb);
|
if (args.verb === 'clean') {
|
||||||
|
cleanNodeService.clean(args.node);
|
||||||
|
} else {
|
||||||
|
ironic.setNodeProvisionState(args.node.uuid, args.verb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPort(node) {
|
function createPort(node) {
|
||||||
|
@ -56,6 +56,9 @@
|
|||||||
states.manageable.addTransition('manageable',
|
states.manageable.addTransition('manageable',
|
||||||
'inspect',
|
'inspect',
|
||||||
gettext('Inspect'));
|
gettext('Inspect'));
|
||||||
|
states.manageable.addTransition('manageable',
|
||||||
|
'clean',
|
||||||
|
gettext('Clean'));
|
||||||
|
|
||||||
states.active.addTransition('available', 'deleted');
|
states.active.addTransition('available', 'deleted');
|
||||||
|
|
||||||
@ -99,7 +102,7 @@
|
|||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
this.addTransition = function(target, verb, label) {
|
this.addTransition = function(target, verb, label) {
|
||||||
this.transitions[target] =
|
this.transitions[verb] =
|
||||||
{source: this.name,
|
{source: this.name,
|
||||||
target: target,
|
target: target,
|
||||||
verb: verb,
|
verb: verb,
|
||||||
|
Loading…
Reference in New Issue
Block a user