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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
:param request: HTTP request.
|
||||
:param node_uuid: The UUID of the node.
|
||||
:param state: the target provision state to set.
|
||||
:param cleansteps: Optional list of cleaning steps
|
||||
:return: node.
|
||||
|
||||
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):
|
||||
|
@ -170,7 +170,11 @@ class StatesProvision(generic.View):
|
||||
:return: Return code
|
||||
"""
|
||||
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
|
||||
|
@ -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} verb – Provisioning verb used to move node to desired
|
||||
* target state
|
||||
* @param {object []} cleanSteps - List of cleaning steps. Only used
|
||||
* when the value of verb is 'clean'
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function setNodeProvisionState(uuid, verb) {
|
||||
var data = {
|
||||
verb: verb
|
||||
};
|
||||
function setNodeProvisionState(uuid, verb, cleanSteps) {
|
||||
return apiService.put('/api/ironic/nodes/' + uuid + '/states/provision',
|
||||
data)
|
||||
{verb: verb,
|
||||
clean_steps: cleanSteps})
|
||||
.then(function() {
|
||||
var msg = gettext(
|
||||
'A request has been made to change the provisioning state of node %s');
|
||||
|
@ -30,6 +30,7 @@
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'horizon.framework.widgets.modal.deleteModalService',
|
||||
'horizon.dashboard.admin.ironic.create-port.service',
|
||||
'horizon.dashboard.admin.ironic.clean-node.service',
|
||||
'$q',
|
||||
'$rootScope'
|
||||
];
|
||||
@ -39,6 +40,7 @@
|
||||
ironicEvents,
|
||||
deleteModalService,
|
||||
createPortService,
|
||||
cleanNodeService,
|
||||
$q,
|
||||
$rootScope) {
|
||||
var service = {
|
||||
@ -169,7 +171,11 @@
|
||||
* the node to the desired target state for the node.
|
||||
*/
|
||||
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) {
|
||||
|
@ -56,6 +56,9 @@
|
||||
states.manageable.addTransition('manageable',
|
||||
'inspect',
|
||||
gettext('Inspect'));
|
||||
states.manageable.addTransition('manageable',
|
||||
'clean',
|
||||
gettext('Clean'));
|
||||
|
||||
states.active.addTransition('available', 'deleted');
|
||||
|
||||
@ -99,7 +102,7 @@
|
||||
* @return {void}
|
||||
*/
|
||||
this.addTransition = function(target, verb, label) {
|
||||
this.transitions[target] =
|
||||
this.transitions[verb] =
|
||||
{source: this.name,
|
||||
target: target,
|
||||
verb: verb,
|
||||
|
Loading…
Reference in New Issue
Block a user