Properly calculate taggedCounts in nodesAssignment

* Calculate tagged nodes counts based on profile of flavor assigned
  to the role
* Fetch flavors on Roles component mount so flavors are available
  to calculate tagged node counts

Closes-Bug: 1750821
Change-Id: If7bd4a49200f05f9ccf73eb6b3ecb7912402428f
(cherry picked from commit 6a06eb6d03)
This commit is contained in:
Jiri Tomasek 2018-02-21 13:44:22 +01:00
parent 77f9ba2483
commit 30c8c6830c
5 changed files with 235 additions and 10 deletions

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fixes `bug 1750821 <https://launchpad.net/bugs/1750821>`__
Available node counts in Role cards are properly calculated based on
node - flavor - role tagging

View File

@ -17,6 +17,7 @@
import { fromJS, Map } from 'immutable';
import * as selectors from '../../js/selectors/nodesAssignment';
import { Flavor } from '../../js/immutableRecords/flavors';
import { Port } from '../../js/immutableRecords/nodes';
import { Role, RolesState } from '../../js/immutableRecords/roles';
import {
@ -161,6 +162,145 @@ describe('Nodes Assignment selectors', () => {
expect(selectors.getUntaggedAvailableNodes(state).size).toEqual(2);
});
describe('getFlavorParametersByRole', () => {
const roles = Map({
Controller: new Role({
name: 'Controller'
}),
Compute: new Role({
name: 'Compute'
}),
BlockStorage: new Role({
name: 'BlockStorage'
})
});
const parameters = Map({
OvercloudControllerFlavor: new Parameter({
name: 'OvercloudControllerFlavor',
default: 'control'
}),
OvercloudComputeFlavor: new Parameter({
name: 'OvercloudComputeFlavor',
default: 'compute'
}),
OvercloudBlockStorageFlavor: new Parameter({
name: 'OvercloudBlockStorageFlavor',
default: 'block-storage'
}),
AnotherParameter1: new Parameter({
name: 'AnotherParameter1',
default: 'value 1'
}),
AnotherParameter2: new Parameter({
name: 'AnotherParameter2',
default: 'value 2'
})
});
it('provides flavor parameters by role', () => {
const result = selectors.getFlavorParametersByRole.resultFunc(
roles,
parameters
);
expect(result.get('Controller')).toEqual(
new Parameter({
name: 'OvercloudControllerFlavor',
default: 'control'
})
);
expect(result.get('Compute')).toEqual(
new Parameter({
name: 'OvercloudComputeFlavor',
default: 'compute'
})
);
expect(result.get('BlockStorage')).toEqual(
new Parameter({
name: 'OvercloudBlockStorageFlavor',
default: 'block-storage'
})
);
});
});
describe('getFlavorProfilesByRole', () => {
const flavors = Map({
control: new Flavor({
id: 'aaaa',
name: 'control',
extra_specs: Map({
'capabilities:profile': 'control'
})
}),
compute: new Flavor({
id: 'bbbb',
name: 'compute',
extra_specs: Map({
'capabilities:profile': 'compute'
})
}),
'block-storage': new Flavor({
id: 'cccc',
name: 'block-storage',
extra_specs: Map({
'capabilities:profile': 'block-storage'
})
})
});
const flavorParametersByRole = Map({
Controller: new Parameter({
name: 'OvercloudControllerFlavor',
default: 'control'
}),
Compute: new Parameter({
name: 'OvercloudComputeFlavor',
default: 'compute'
}),
BlockStorage: new Parameter({
name: 'OvercloudBlockStorageFlavor',
default: 'block-storage'
})
});
it('provides flavor profiles by role', () => {
const result = selectors.getFlavorProfilesByRole.resultFunc(
flavorParametersByRole,
flavors
);
expect(result.get('Controller')).toEqual('control');
expect(result.get('Compute')).toEqual('compute');
expect(result.get('BlockStorage')).toEqual('block-storage');
});
});
describe('getTaggedNodesCountByRole', () => {
const availableNodes = fromJS({
node1: {
uuid: 'node1',
properties: { capabilities: 'boot_option:local' }
},
node2: {
uuid: 'node2',
properties: { capabilities: 'boot_option:local,profile:control' }
}
});
const flavorProfilesByRole = Map({
Controller: 'control',
Compute: 'compute',
BlockStorage: 'block-storage'
});
it('calculates tagged node counts by role', () => {
const result = selectors.getTaggedNodesCountByRole.resultFunc(
availableNodes,
flavorProfilesByRole
);
expect(result.get('Controller')).toEqual(1);
expect(result.get('Compute')).toEqual(0);
expect(result.get('BlockStorage')).toEqual(0);
});
});
describe('provides getTotalUntaggedAssignedNodesCount selector', () => {
const nodes = fromJS({
node1: {
@ -186,6 +326,11 @@ describe('Nodes Assignment selectors', () => {
identifier: 'block-storage'
})
});
const taggedNodesCountByRole = Map({
Controller: 1,
Compute: 0,
BlockStorage: 0
});
const parametersByRole = Map({
Controler: new Parameter({
name: 'ControllerCount',
@ -201,6 +346,7 @@ describe('Nodes Assignment selectors', () => {
const result = selectors.getTotalUntaggedAssignedNodesCount.resultFunc(
nodes,
roles,
taggedNodesCountByRole,
parametersByRole
);
expect(result).toEqual(1);
@ -224,6 +370,7 @@ describe('Nodes Assignment selectors', () => {
const result = selectors.getTotalUntaggedAssignedNodesCount.resultFunc(
nodes,
roles,
taggedNodesCountByRole,
parametersByRole
);
expect(result).toEqual(1);
@ -264,6 +411,11 @@ describe('Nodes Assignment selectors', () => {
identifier: 'block-storage'
})
});
const taggedNodesCountByRole = Map({
Controller: 1,
Compute: 0,
BlockStorage: 0
});
const nodeCountParametersByRole = Map({
Controller: new Parameter({
name: 'ControllerCount',
@ -285,6 +437,7 @@ describe('Nodes Assignment selectors', () => {
availableNodes,
untaggedAvailableNodes,
roles,
taggedNodesCountByRole,
nodeCountParametersByRole,
totalUntaggedAssignedNodesCount
);
@ -313,6 +466,7 @@ describe('Nodes Assignment selectors', () => {
availableNodes,
untaggedAvailableNodes,
roles,
taggedNodesCountByRole,
nodeCountParametersByRole,
totalUntaggedAssignedNodesCount
);

View File

@ -20,6 +20,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import { withRouter } from 'react-router-dom';
import FlavorsActions from '../../actions/FlavorsActions';
import { getCurrentPlanName } from '../../selectors/plans';
import { getNodes } from '../../selectors/nodes';
import {
@ -54,8 +55,10 @@ const RolesStep = ({
allNodesCount,
availableNodesCount,
currentPlanName,
fetchFlavors,
fetchRoles,
fetchNodes,
flavorsLoaded,
intl,
isFetchingNodes,
isFetchingParameters,
@ -92,9 +95,10 @@ const RolesStep = ({
</InlineLoader>
</p>
<Roles
fetchFlavors={fetchFlavors}
fetchRoles={fetchRoles.bind(this, currentPlanName)}
fetchNodes={fetchNodes}
loaded={rolesLoaded && nodesLoaded}
loaded={rolesLoaded && nodesLoaded && flavorsLoaded}
/>
</div>
);
@ -103,8 +107,10 @@ RolesStep.propTypes = {
allNodesCount: PropTypes.number.isRequired,
availableNodesCount: PropTypes.number.isRequired,
currentPlanName: PropTypes.string,
fetchFlavors: PropTypes.func.isRequired,
fetchNodes: PropTypes.func.isRequired,
fetchRoles: PropTypes.func.isRequired,
flavorsLoaded: PropTypes.bool.isRequired,
intl: PropTypes.object,
isFetchingNodes: PropTypes.bool.isRequired,
isFetchingParameters: PropTypes.bool.isRequired,
@ -117,6 +123,7 @@ const mapStateToProps = state => ({
allNodesCount: getNodes(state).size,
availableNodesCount: getAvailableNodes(state).size,
currentPlanName: getCurrentPlanName(state),
flavorsLoaded: state.flavors.isLoaded,
isFetchingNodes: state.nodes.get('isFetching'),
isFetchingParameters: state.parameters.isFetching,
rolesLoaded: state.roles.loaded,
@ -124,6 +131,7 @@ const mapStateToProps = state => ({
totalAssignedNodesCount: getTotalAssignedNodesCount(state)
});
const mapDispatchToProps = dispatch => ({
fetchFlavors: () => dispatch(FlavorsActions.fetchFlavors()),
fetchRoles: planName => dispatch(RolesActions.fetchRoles(planName)),
fetchNodes: () => dispatch(NodesActions.fetchNodes())
});

View File

@ -34,6 +34,7 @@ class Roles extends React.Component {
componentDidMount() {
this.props.fetchRoles();
this.props.fetchNodes();
this.props.fetchFlavors();
}
render() {
@ -56,6 +57,7 @@ class Roles extends React.Component {
}
}
Roles.propTypes = {
fetchFlavors: PropTypes.func.isRequired,
fetchNodes: PropTypes.func.isRequired,
fetchRoles: PropTypes.func.isRequired,
intl: PropTypes.object,

View File

@ -17,9 +17,12 @@
import { createSelector } from 'reselect';
import { getFormValues } from 'redux-form';
import { Flavor } from '../immutableRecords/flavors';
import { getNodes, getNodeCapabilities } from './nodes';
import { getParameters } from './parameters';
import { getRoles } from './roles';
import { getFlavors } from './flavors';
import { Parameter } from '../immutableRecords/parameters';
/**
* Return Nodes which are either available or deployed (active) with current Plan
@ -65,16 +68,62 @@ export const getNodeCountParametersByRoleFromFormValues = createSelector(
)
);
/**
* Returns Overcloud<RoleName>Flavor parameters for each Role
*/
export const getFlavorParametersByRole = createSelector(
[getRoles, getParameters],
(roles, parameters) =>
roles.map(role =>
parameters.get(`Overcloud${role.name}Flavor`, new Parameter())
)
);
/**
* Returns flavor profile each Role.
*/
export const getFlavorProfilesByRole = createSelector(
[getFlavorParametersByRole, getFlavors],
(flavorParametersByRole, flavors) =>
flavorParametersByRole.map(roleFlavorParameter =>
flavors
.find(
flavor => flavor.name === roleFlavorParameter.default,
null,
new Flavor()
)
.getIn(['extra_specs', 'capabilities:profile'])
)
);
/**
* Returns number of tagged nodes for each Role
*/
export const getTaggedNodesCountByRole = createSelector(
[getAvailableNodes, getFlavorProfilesByRole],
(nodes, flavorProfilesByRole) =>
flavorProfilesByRole.map(
(roleFlavorProfile, roleName) =>
nodes.filter(node => {
const nodeProfile = getNodeCapabilities(node).profile;
return nodeProfile && nodeProfile === roleFlavorProfile;
}).size
)
);
/**
* Returns sum of untagged assigned Nodes counts across all Roles
*/
export const getTotalUntaggedAssignedNodesCount = createSelector(
[getAvailableNodes, getRoles, getNodeCountParametersByRoleFromFormValues],
(nodes, roles, parametersByRole) =>
[
getAvailableNodes,
getRoles,
getTaggedNodesCountByRole,
getNodeCountParametersByRoleFromFormValues
],
(nodes, roles, taggedNodesCountByRole, parametersByRole) =>
roles.reduce((total, role) => {
const taggedCount = nodes.filter(
node => getNodeCapabilities(node).profile === role.identifier
).size;
const taggedCount = taggedNodesCountByRole.get(role.name);
const assignedCount = parametersByRole.getIn([role.name, 'default'], 0);
const remainder = Math.max(0, assignedCount - taggedCount);
return total + remainder;
@ -89,14 +138,20 @@ export const getAvailableNodesCountsByRole = createSelector(
getAvailableNodes,
getUntaggedAvailableNodes,
getRoles,
getTaggedNodesCountByRole,
getNodeCountParametersByRoleFromFormValues,
getTotalUntaggedAssignedNodesCount
],
(nodes, untaggedNodes, roles, parametersByRole, untaggedAssignedCount) =>
(
nodes,
untaggedNodes,
roles,
taggedNodesCountByRole,
parametersByRole,
untaggedAssignedCount
) =>
roles.map(role => {
const taggedCount = nodes.filter(
node => getNodeCapabilities(node).profile === role.identifier
).size;
const taggedCount = taggedNodesCountByRole.get(role.name);
const assignedCount = parametersByRole.getIn([role.name, 'default'], 0);
const untaggedCount = untaggedNodes.size;
return (