341 lines
10 KiB
JavaScript
341 lines
10 KiB
JavaScript
import { defineMessages } from 'react-intl';
|
|
import { normalize, arrayOf } from 'normalizr';
|
|
import when from 'when';
|
|
|
|
import { getNodesByIds } from '../selectors/nodes';
|
|
import IronicApiErrorHandler from '../services/IronicApiErrorHandler';
|
|
import IronicApiService from '../services/IronicApiService';
|
|
import MistralApiService from '../services/MistralApiService';
|
|
import MistralApiErrorHandler from '../services/MistralApiErrorHandler';
|
|
import NodesConstants from '../constants/NodesConstants';
|
|
import NotificationActions from './NotificationActions';
|
|
import { nodeSchema } from '../normalizrSchemas/nodes';
|
|
import MistralConstants from '../constants/MistralConstants';
|
|
import logger from '../services/logger';
|
|
import { setNodeCapability } from '../utils/nodes';
|
|
|
|
const messages = defineMessages({
|
|
introspectionNotificationMessage: {
|
|
id: 'NodesActions.introspectionNotificationMessage',
|
|
defaultMessage: 'Selected nodes were successfully introspected.'
|
|
},
|
|
introspectionNotificationTitle: {
|
|
id: 'NodesActions.introspectionNotificationTitle',
|
|
defaultMessage: 'Nodes Introspection Complete'
|
|
},
|
|
introspectionFailedNotificationTitle: {
|
|
id: 'NodesActions.introspectionFailedNotificationTitle',
|
|
defaultMessage: 'Nodes Introspection Failed'
|
|
}
|
|
});
|
|
|
|
export default {
|
|
startOperation(nodeIds) {
|
|
return {
|
|
type: NodesConstants.START_NODES_OPERATION,
|
|
payload: nodeIds
|
|
};
|
|
},
|
|
|
|
finishOperation(nodeIds) {
|
|
return {
|
|
type: NodesConstants.FINISH_NODES_OPERATION,
|
|
payload: nodeIds
|
|
};
|
|
},
|
|
|
|
addNodes(nodes) {
|
|
return {
|
|
type: NodesConstants.ADD_NODES,
|
|
payload: nodes
|
|
};
|
|
},
|
|
|
|
requestNodes() {
|
|
return {
|
|
type: NodesConstants.REQUEST_NODES
|
|
};
|
|
},
|
|
|
|
receiveNodes(entities) {
|
|
return {
|
|
type: NodesConstants.RECEIVE_NODES,
|
|
payload: entities
|
|
};
|
|
},
|
|
|
|
fetchNodes() {
|
|
return (dispatch, getState) => {
|
|
dispatch(this.requestNodes());
|
|
IronicApiService.getNodes().then(response => {
|
|
return when.map(response.nodes, node => {
|
|
return IronicApiService.getNodePorts(node.uuid).then(response => {
|
|
node.portsDetail = response.ports;
|
|
return node;
|
|
});
|
|
}).then(nodes => {
|
|
const normalizedNodes = normalize(nodes, arrayOf(nodeSchema)).entities;
|
|
dispatch(this.receiveNodes(normalizedNodes));
|
|
});
|
|
}).catch((error) => {
|
|
dispatch(this.receiveNodes({}));
|
|
logger.error('Error in NodesActions.fetchNodes', error.stack || error);
|
|
let errorHandler = new IronicApiErrorHandler(error);
|
|
errorHandler.errors.forEach((error) => {
|
|
dispatch(NotificationActions.notify(error));
|
|
});
|
|
});
|
|
};
|
|
},
|
|
|
|
/*
|
|
* Poll fetchNodes until no node is in progress
|
|
*/
|
|
pollNodeslistDuringProgress() {
|
|
return (dispatch, getState) => {
|
|
const nodesState = getState().nodes;
|
|
if(nodesState.get('nodesInProgress').size > 0) {
|
|
// Only fetch nodes if there's currently no unfinished request.
|
|
if(!nodesState.get('isFetching')) {
|
|
dispatch(this.fetchNodes());
|
|
}
|
|
setTimeout(() => {
|
|
dispatch(this.pollNodeslistDuringProgress());
|
|
}, 5000);
|
|
}
|
|
};
|
|
},
|
|
|
|
startNodesIntrospection(nodeIds) {
|
|
return (dispatch, getState) => {
|
|
dispatch(this.startOperation(nodeIds));
|
|
dispatch(this.pollNodeslistDuringProgress());
|
|
MistralApiService.runWorkflow(MistralConstants.BAREMETAL_INTROSPECT,
|
|
{ node_uuids: nodeIds })
|
|
.then((response) => {
|
|
if(response.state === 'ERROR') {
|
|
dispatch(NotificationActions.notify({ title: 'Error', message: response.state_info }));
|
|
dispatch(this.finishOperation(nodeIds));
|
|
}
|
|
}).catch((error) => {
|
|
logger.error('Error in NodesActions.startNodesIntrospection', error.stack || error);
|
|
let errorHandler = new MistralApiErrorHandler(error);
|
|
errorHandler.errors.forEach((error) => {
|
|
dispatch(NotificationActions.notify(error));
|
|
});
|
|
dispatch(this.finishOperation(nodeIds));
|
|
});
|
|
};
|
|
},
|
|
|
|
nodesIntrospectionFinished(messagePayload) {
|
|
return (dispatch, getState, { getIntl }) => {
|
|
const { formatMessage } = getIntl(getState());
|
|
const nodeIds = messagePayload.execution.input.node_uuids;
|
|
dispatch(this.finishOperation(nodeIds));
|
|
dispatch(this.fetchNodes());
|
|
|
|
switch(messagePayload.status) {
|
|
case 'SUCCESS': {
|
|
dispatch(NotificationActions.notify({
|
|
type: 'success',
|
|
title: formatMessage(messages.introspectionNotificationTitle),
|
|
message: formatMessage(messages.introspectionNotificationMessage)
|
|
}));
|
|
break;
|
|
}
|
|
case 'FAILED': {
|
|
dispatch(NotificationActions.notify({
|
|
type: 'error',
|
|
title: formatMessage(messages.introspectionFailedNotificationTitle),
|
|
message: messagePayload.message.join(', ')
|
|
}));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
},
|
|
|
|
tagNodes(nodeIds, tag) {
|
|
return (dispatch, getState) => {
|
|
const nodes = getNodesByIds(getState(), nodeIds);
|
|
nodes.map(node => {
|
|
dispatch(this.updateNode({
|
|
uuid: node.get('uuid'),
|
|
patches: [{
|
|
op: 'replace',
|
|
path: '/properties/capabilities',
|
|
value: setNodeCapability(node.getIn(['properties', 'capabilities']), 'profile', tag)
|
|
}]
|
|
}));
|
|
});
|
|
};
|
|
},
|
|
|
|
startProvideNodes(nodeIds) {
|
|
return (dispatch, getState) => {
|
|
dispatch(this.startOperation(nodeIds));
|
|
dispatch(this.pollNodeslistDuringProgress());
|
|
MistralApiService.runWorkflow(MistralConstants.BAREMETAL_PROVIDE,
|
|
{ node_uuids: nodeIds })
|
|
.then((response) => {
|
|
if(response.state === 'ERROR') {
|
|
dispatch(NotificationActions.notify({ title: 'Error', message: response.state_info }));
|
|
dispatch(this.finishOperation(nodeIds));
|
|
}
|
|
}).catch((error) => {
|
|
logger.error('Error in NodesActions.startProvideNodes', error.stack || error);
|
|
let errorHandler = new MistralApiErrorHandler(error);
|
|
errorHandler.errors.forEach((error) => {
|
|
dispatch(NotificationActions.notify(error));
|
|
});
|
|
dispatch(this.finishOperation(nodeIds));
|
|
});
|
|
};
|
|
},
|
|
|
|
provideNodesFinished(messagePayload) {
|
|
return (dispatch, getState) => {
|
|
const nodeIds = messagePayload.execution.input.node_uuids;
|
|
dispatch(this.finishOperation(nodeIds));
|
|
dispatch(this.fetchNodes());
|
|
|
|
switch(messagePayload.status) {
|
|
case 'SUCCESS': {
|
|
dispatch(NotificationActions.notify({
|
|
type: 'success',
|
|
title: 'Nodes are available',
|
|
message: messagePayload.message
|
|
}));
|
|
break;
|
|
}
|
|
case 'FAILED': {
|
|
dispatch(NotificationActions.notify({
|
|
type: 'error',
|
|
title: 'Error',
|
|
message: messagePayload.message
|
|
}));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
},
|
|
|
|
updateNode(nodePatch) {
|
|
return (dispatch, getState) => {
|
|
dispatch(this.updateNodePending(nodePatch.uuid));
|
|
IronicApiService.patchNode(nodePatch).then(response => {
|
|
dispatch(this.updateNodeSuccess(response));
|
|
}).catch(error => {
|
|
dispatch(this.updateNodeFailed(nodePatch.uuid));
|
|
logger.error('Error in NodesActions.UpdateNode', error.stack || error);
|
|
let errorHandler = new IronicApiErrorHandler(error);
|
|
errorHandler.errors.forEach((error) => {
|
|
dispatch(NotificationActions.notify(error));
|
|
});
|
|
});
|
|
};
|
|
},
|
|
|
|
updateNodePending(nodeId) {
|
|
return {
|
|
type: NodesConstants.UPDATE_NODE_PENDING,
|
|
payload: nodeId
|
|
};
|
|
},
|
|
|
|
updateNodeFailed(nodeId) {
|
|
return {
|
|
type: NodesConstants.UPDATE_NODE_FAILED,
|
|
payload: nodeId
|
|
};
|
|
},
|
|
|
|
updateNodeSuccess(node) {
|
|
return {
|
|
type: NodesConstants.UPDATE_NODE_SUCCESS,
|
|
payload: node
|
|
};
|
|
},
|
|
|
|
deleteNodes(nodeIds) {
|
|
return (dispatch, getState) => {
|
|
dispatch(this.startOperation(nodeIds));
|
|
nodeIds.map((nodeId) => {
|
|
IronicApiService.deleteNode(nodeId).then(response => {
|
|
dispatch(this.deleteNodeSuccess(nodeId));
|
|
}).catch(error => {
|
|
dispatch(this.deleteNodeFailed(nodeId));
|
|
logger.error('Error in NodesActions.DeleteNodes', error.stack || error);
|
|
let errorHandler = new IronicApiErrorHandler(error);
|
|
errorHandler.errors.forEach((error) => {
|
|
dispatch(NotificationActions.notify(error));
|
|
});
|
|
});
|
|
});
|
|
};
|
|
},
|
|
|
|
deleteNodeSuccess(nodeId) {
|
|
return {
|
|
type: NodesConstants.DELETE_NODE_SUCCESS,
|
|
payload: nodeId
|
|
};
|
|
},
|
|
|
|
deleteNodeFailed(nodeId) {
|
|
return {
|
|
type: NodesConstants.DELETE_NODE_FAILED,
|
|
payload: nodeId
|
|
};
|
|
},
|
|
|
|
startNodesAssignment(tagNodeIds, untagNodeIds, role, planName) {
|
|
const flatNodes = tagNodeIds.concat(untagNodeIds);
|
|
return (dispatch, getState) => {
|
|
dispatch(this.startOperation(flatNodes));
|
|
dispatch(this.pollNodeslistDuringProgress());
|
|
MistralApiService.runWorkflow(MistralConstants.TAG_NODES,
|
|
{ tag_node_uuids: tagNodeIds, untag_node_uuids: untagNodeIds, role: role, plan: planName })
|
|
.then((response) => {
|
|
if(response.state === 'ERROR') {
|
|
dispatch(NotificationActions.notify({ title: 'Error', message: response.state_info }));
|
|
dispatch(this.finishOperation(flatNodes));
|
|
}
|
|
}).catch((error) => {
|
|
let errorHandler = new MistralApiErrorHandler(error);
|
|
errorHandler.errors.forEach((error) => {
|
|
dispatch(NotificationActions.notify(error));
|
|
});
|
|
dispatch(this.finishOperation(flatNodes));
|
|
});
|
|
};
|
|
},
|
|
|
|
nodesAssignmentFinished(messagePayload) {
|
|
return (dispatch, getState) => {
|
|
const nodeIds = messagePayload.execution.input.tag_node_uuids.concat(
|
|
messagePayload.execution.input.untag_node_uuids);
|
|
dispatch(this.finishOperation(nodeIds));
|
|
dispatch(this.fetchNodes());
|
|
|
|
switch(messagePayload.status) {
|
|
case 'FAILED': {
|
|
dispatch(NotificationActions.notify({
|
|
type: 'error',
|
|
title: 'Error',
|
|
message: messagePayload.message
|
|
}));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
};
|