Extend support for the Ironic state machine

The following changes have been made:
(1) Create a separate node-state-transition service that is used
to orchestrate changes in the node provision state
(2) Extend the node list and detail UIs (actions) to support
states other the manageable, available, and active
(3) It should be noted that state transition actions are rendered
as html list items as opposed to actions. The reason for this is
that the <action> tag is not compatible with the ng-repeat
directive.

Change-Id: Id6543667f944c47f8f9727c1c2f3085ae75a1625
This commit is contained in:
Peter Piela 2016-12-13 12:10:50 -05:00
parent 6517bf419c
commit 58df012f66
7 changed files with 164 additions and 87 deletions

View File

@ -18,41 +18,6 @@
(function () {
'use strict';
var provisionStateTransitionMatrix = {
enroll: {
manageable: 'manage'
},
manageable: {
active: 'adopt',
available: 'provide'
},
active: {
available: 'deleted'
},
available: {
active: 'active',
manageable: 'manage'
},
'adopt failed': {
manageable: 'manage',
active: 'adopt'
},
'inspect failed': {
manageable: 'manage'
},
'clean failed': {
manageable: 'manage'
},
'deploy failed': {
active: 'active',
manageable: 'deleted'
},
error: {
active: 'rebuild',
manageable: 'deleted'
}
};
angular
.module('horizon.app.core.openstack-service-api')
.factory('horizon.app.core.openstack-service-api.ironic', ironicAPI);
@ -82,7 +47,6 @@
getNode: getNode,
getNodes: getNodes,
getPortsWithNode: getPortsWithNode,
getProvisionStateTransitionVerb: getProvisionStateTransitionVerb,
powerOffNode: powerOffNode,
powerOnNode: powerOnNode,
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
@ -447,25 +411,6 @@
toastService.add('error', interpolate(msg, [reason], false));
});
}
/**
* @description Get the verb used to transition a node from a source
* provision-state to a target provision-state
*
* @param {string} sourceState source state
* @param {string} targetState target state
* @return {string} Verb used to transition from source to target state.
* null if the requested transition is not allowed.
*/
function getProvisionStateTransitionVerb(sourceState, targetState) {
var verb = null;
if (angular.isDefined(provisionStateTransitionMatrix[sourceState]) &&
angular.isDefined(
provisionStateTransitionMatrix[sourceState][targetState])) {
verb = provisionStateTransitionMatrix[sourceState][targetState];
}
return verb;
}
}
}());

View File

@ -75,7 +75,6 @@
deleteNodes: deleteNodes,
deletePort: deletePort,
deletePorts: deletePorts,
getProvisionStateTransitionVerb: getProvisionStateTransitionVerb,
powerOn: powerOn,
powerOff: powerOff,
powerOnAll: powerOnNodes,
@ -207,10 +206,6 @@
ironic.setNodeProvisionState(args.node.uuid, args.verb);
}
function getProvisionStateTransitionVerb(sourceState, targetState) {
return ironic.getProvisionStateTransitionVerb(sourceState, targetState);
}
function createPort(node) {
return createPortService.modal(node);
}

View File

@ -33,6 +33,7 @@
'horizon.dashboard.admin.ironic.basePath',
'horizon.dashboard.admin.ironic.edit-node.service',
'horizon.dashboard.admin.ironic.maintenance.service',
'horizon.dashboard.admin.ironic.node-state-transition.service',
'horizon.dashboard.admin.ironic.validUuidPattern'
];
@ -46,6 +47,7 @@
basePath,
editNodeService,
maintenanceService,
nodeStateTransitionService,
validUuidPattern) {
var ctrl = this;
var path = basePath + '/node-details/sections/';
@ -67,6 +69,7 @@
ctrl.node = null;
ctrl.nodeValidation = {};
ctrl.nodeStateTransitions = [];
ctrl.ports = [];
ctrl.portsSrc = [];
ctrl.basePath = basePath;
@ -121,6 +124,8 @@
var uuid = $location.absUrl().match(pattern)[2];
retrieveNode(uuid).then(function () {
ctrl.nodeStateTransitions =
nodeStateTransitionService.getTransitions(ctrl.node.provision_state);
retrievePorts(uuid);
ironic.validateNode(uuid).then(function(response) {
ctrl.nodeValidation = response.data;

View File

@ -32,18 +32,17 @@
disabled="!ctrl.node.maintenance">
{$ 'Maintenance off' | translate $}
</action>
<action ng-repeat="targetState in ['manageable', 'available', 'active']"
button-type="menu-item"
callback="ctrl.actions.setProvisionState"
item="{node: ctrl.node,
verb: ctrl.actions.getProvisionStateTransitionVerb(
ctrl.node.provision_state,
targetState)}"
disabled="ctrl.actions.getProvisionStateTransitionVerb(
ctrl.node.provision_state,
targetState) === null">
{$ ('Move to ' | translate) + targetState $}
</action>
<li role="presentation"
ng-repeat="transition in ctrl.nodeStateTransitions">
<a role="menuitem"
ng-click="ctrl.actions.setProvisionState({
node: ctrl.node,
verb: transition.verb});
$event.stopPropagation();
$event.preventDefault()">
<span>{$ transition.label $}</span>
</a>
</li>
<action button-type="menu-item"
callback="ctrl.editNode">
{$ 'Edit' | translate $}

View File

@ -31,7 +31,8 @@
'horizon.dashboard.admin.ironic.actions',
'horizon.dashboard.admin.ironic.maintenance.service',
'horizon.dashboard.admin.ironic.enroll-node.service',
'horizon.dashboard.admin.ironic.edit-node.service'
'horizon.dashboard.admin.ironic.edit-node.service',
'horizon.dashboard.admin.ironic.node-state-transition.service'
];
function IronicNodeListController($scope,
@ -43,7 +44,8 @@
actions,
maintenanceService,
enrollNodeService,
editNodeService) {
editNodeService,
nodeStateTransitionService) {
var ctrl = this;
ctrl.nodes = [];
@ -57,6 +59,7 @@
ctrl.enrollNode = enrollNode;
ctrl.editNode = editNode;
ctrl.refresh = refresh;
ctrl.getNodeStateTransitions = getNodeStateTransitions;
/**
* Filtering - client-side MagicSearch
@ -188,6 +191,10 @@
function refresh() {
init();
}
function getNodeStateTransitions(node) {
return nodeStateTransitionService.getTransitions(node.provision_state);
}
}
})();

View File

@ -172,19 +172,17 @@
item="node">
{$ 'Create port' | translate $}
</action>
<action ng-repeat="targetState in
['manageable', 'available', 'active']"
button-type="menu-item"
callback="table.actions.setProvisionState"
item="{node: node,
verb: table.actions.getProvisionStateTransitionVerb(
node.provision_state,
targetState)}"
disabled="table.actions.getProvisionStateTransitionVerb(
node.provision_state,
targetState) === null">
{$ ('Move to ' | translate) + targetState $}
</action>
<li role="presentation"
ng-repeat="transition in table.getNodeStateTransitions(node)">
<a role="menuitem"
ng-click="table.actions.setProvisionState({
node: node,
verb: transition.verb});
$event.stopPropagation();
$event.preventDefault()">
<span>{$ transition.label $}</span>
</a>
</li>
<action button-type="menu-item"
callback="table.editNode"
item="node">

View File

@ -0,0 +1,128 @@
/*
* © 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';
angular
.module('horizon.dashboard.admin.ironic')
.service('horizon.dashboard.admin.ironic.node-state-transition.service',
nodeStateTransitionService);
nodeStateTransitionService.$inject = [];
/**
* @description Utility service for working with the node state machine
* @return {void}
*/
function nodeStateTransitionService() {
// Dictionary of NodeState objects indexed by state name
var states = {};
// Create node state objects
angular.forEach(['enroll',
'manageable',
'active',
'available',
'adopt failed',
'clean failed',
'inspect failed',
'clean wait',
'deploy failed',
'error'],
function(state) {
states[state] = new NodeState(state);
});
// Add state transitions
states.enroll.addTransition('manageable', 'manage');
states.manageable.addTransition('active', 'adopt');
states.manageable.addTransition('available', 'provide');
states.active.addTransition('available', 'deleted');
states.available.addTransition('active', 'active');
states.available.addTransition('manageable', 'manage');
states['adopt failed'].addTransition('manageable', 'manage');
states['adopt failed'].addTransition('active', 'adopt');
states['inspect failed'].addTransition('manageable', 'manage');
states['clean failed'].addTransition('manageable', 'manage');
states['deploy failed'].addTransition('active', 'active');
states['deploy failed'].addTransition('manageable', 'deleted');
states.error.addTransition('active', 'rebuild');
states.error.addTransition('manageable', 'deleted');
/**
* @description Class constructor for NodeState object.
* A NodeState maintains a set of transitions to other states.
*
* @param {name} name Name of state
* @return {void}
*/
function NodeState(name) {
this.name = name;
this.transitions = {};
/**
* @description Add a transition to a specified target state.
*
* @param {string} target Name of target state
* @param {string} verb Verb used to accomplish transition
* @param {string} label Description of the transition. Optional.
* @return {void}
*/
this.addTransition = function(target, verb, label) {
this.transitions[target] =
{source: this.name,
target: target,
verb: verb,
label: angular.isDefined(label)
? label : gettext("Move to") + " " + target};
};
/**
* @description Get the transition object associated with a
* specified target state.
*
* @param {string} targetState Name of target state
* @return {object} Transition object. A value of null
* is returned if a transition does not exist.
*/
this.getTransition = function(targetState) {
return this.transitions.hasOwnProperty(targetState)
? this.transitions[targetState] : null;
};
}
this.getTransitions = function(sourceState) {
var transitions = [];
if (states.hasOwnProperty(sourceState)) {
angular.forEach(states[sourceState].transitions,
function(transition) {
transitions.push(transition);
});
}
return transitions;
};
}
}());