390 lines
10 KiB
JavaScript
390 lines
10 KiB
JavaScript
/**
|
|
* Copyright 2017 Red Hat 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.
|
|
*/
|
|
|
|
import { defineMessages } from 'react-intl';
|
|
import { normalize } from 'normalizr';
|
|
import when from 'when';
|
|
|
|
import { getNodesByIds } from '../selectors/nodes';
|
|
import { handleErrors } from './ErrorActions';
|
|
import IronicApiService from '../services/IronicApiService';
|
|
import IronicInspectorApiService from '../services/IronicInspectorApiService';
|
|
import NodesConstants from '../constants/NodesConstants';
|
|
import { notify } from './NotificationActions';
|
|
import {
|
|
nodeSchema,
|
|
portSchema,
|
|
introspectionStatusSchema
|
|
} from '../normalizrSchemas/nodes';
|
|
import MistralConstants from '../constants/MistralConstants';
|
|
import { setNodeCapability } from '../utils/nodes';
|
|
import { startWorkflow } from './WorkflowActions';
|
|
import { sanitizeMessage } from '../utils';
|
|
|
|
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'
|
|
},
|
|
nodeIntrospectionFailedNotificationTitle: {
|
|
id: 'NodesActions.nodeIntrospectionFailedNotificationTitle',
|
|
defaultMessage: 'Node Introspection Failed'
|
|
}
|
|
});
|
|
|
|
export const startOperation = nodeIds => ({
|
|
type: NodesConstants.START_NODES_OPERATION,
|
|
payload: nodeIds
|
|
});
|
|
|
|
export const finishOperation = nodeIds => ({
|
|
type: NodesConstants.FINISH_NODES_OPERATION,
|
|
payload: nodeIds
|
|
});
|
|
|
|
export const addNodes = nodes => ({
|
|
type: NodesConstants.ADD_NODES,
|
|
payload: nodes
|
|
});
|
|
|
|
export const requestNodes = () => ({
|
|
type: NodesConstants.REQUEST_NODES
|
|
});
|
|
|
|
export const receiveNodes = entities => ({
|
|
type: NodesConstants.RECEIVE_NODES,
|
|
payload: entities
|
|
});
|
|
|
|
export const fetchNodes = () => (dispatch, getState) => {
|
|
dispatch(requestNodes());
|
|
return when
|
|
.all([
|
|
dispatch(IronicApiService.getNodes()),
|
|
dispatch(IronicApiService.getPorts()),
|
|
dispatch(IronicInspectorApiService.getIntrospectionStatuses())
|
|
])
|
|
.then(response => {
|
|
const nodes = normalize(response[0].nodes, [nodeSchema]).entities.nodes;
|
|
const ports = normalize(response[1].ports, [portSchema]).entities.ports;
|
|
const introspectionStatuses = normalize(response[2].introspection, [
|
|
introspectionStatusSchema
|
|
]).entities.introspectionStatuses;
|
|
dispatch(receiveNodes({ nodes, ports, introspectionStatuses }));
|
|
})
|
|
.catch(error => {
|
|
dispatch(handleErrors(error, 'Nodes could not be loaded'));
|
|
dispatch(receiveNodes({}));
|
|
});
|
|
};
|
|
|
|
export const fetchNodeIntrospectionDataSuccess = (nodeId, data) => ({
|
|
type: NodesConstants.FETCH_NODE_INTROSPECTION_DATA_SUCCESS,
|
|
payload: { nodeId, data }
|
|
});
|
|
|
|
export const fetchNodeIntrospectionDataFailed = nodeId => ({
|
|
type: NodesConstants.FETCH_NODE_INTROSPECTION_DATA_FAILED,
|
|
payload: nodeId
|
|
});
|
|
|
|
export const fetchNodeIntrospectionData = nodeId => dispatch =>
|
|
dispatch(IronicInspectorApiService.getIntrospectionData(nodeId))
|
|
.then(response => {
|
|
dispatch(fetchNodeIntrospectionDataSuccess(nodeId, response));
|
|
})
|
|
.catch(error => {
|
|
dispatch(
|
|
handleErrors(error, 'Node introspection data could not be loaded')
|
|
);
|
|
dispatch(fetchNodeIntrospectionDataFailed(nodeId));
|
|
});
|
|
|
|
/*
|
|
* Poll fetchNodes until no node is in progress
|
|
*/
|
|
export const pollNodeslistDuringProgress = () => (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(fetchNodes());
|
|
}
|
|
setTimeout(() => {
|
|
dispatch(pollNodeslistDuringProgress());
|
|
}, 5000);
|
|
}
|
|
};
|
|
|
|
export const startNodesIntrospection = nodeIds => (dispatch, getState) => {
|
|
dispatch(startOperation(nodeIds));
|
|
dispatch(pollNodeslistDuringProgress());
|
|
// Nodes are introspected in batches of 20, each batch 15 minutes
|
|
const timeout = Math.ceil(nodeIds.length / 20) * 15 * 60 * 1000;
|
|
return dispatch(
|
|
startWorkflow(
|
|
MistralConstants.BAREMETAL_INTROSPECT,
|
|
{
|
|
node_uuids: nodeIds,
|
|
max_retry_attempts: 1
|
|
},
|
|
nodesIntrospectionFinished,
|
|
timeout
|
|
)
|
|
).catch(error => {
|
|
dispatch(handleErrors(error, 'Selected Nodes could not be introspected'));
|
|
dispatch(finishOperation(nodeIds));
|
|
});
|
|
};
|
|
|
|
export const nodesIntrospectionFinished = execution => (
|
|
dispatch,
|
|
getState,
|
|
{ getIntl }
|
|
) => {
|
|
const { formatMessage } = getIntl(getState());
|
|
const {
|
|
input: { node_uuids: nodeIds },
|
|
output: { message },
|
|
state
|
|
} = execution;
|
|
dispatch(finishOperation(nodeIds));
|
|
dispatch(fetchNodes());
|
|
|
|
switch (state) {
|
|
case 'SUCCESS': {
|
|
dispatch(
|
|
notify({
|
|
type: 'success',
|
|
title: formatMessage(messages.introspectionNotificationTitle),
|
|
message: formatMessage(messages.introspectionNotificationMessage)
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case 'ERROR': {
|
|
dispatch(
|
|
notify({
|
|
title: formatMessage(messages.introspectionFailedNotificationTitle),
|
|
message: sanitizeMessage(message)
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
export const nodeIntrospectionFinished = execution => (
|
|
dispatch,
|
|
getState,
|
|
{ getIntl }
|
|
) => {
|
|
const { formatMessage } = getIntl(getState());
|
|
const {
|
|
input: { node_uuid: nodeId },
|
|
output: { message },
|
|
state
|
|
} = execution;
|
|
dispatch(finishOperation([nodeId]));
|
|
|
|
if (state === 'ERROR') {
|
|
dispatch(
|
|
notify({
|
|
title: formatMessage(messages.nodeIntrospectionFailedNotificationTitle),
|
|
message: sanitizeMessage(message)
|
|
})
|
|
);
|
|
}
|
|
};
|
|
|
|
export const tagNodes = (nodeIds, tag) => (dispatch, getState) => {
|
|
const nodes = getNodesByIds(getState(), nodeIds);
|
|
nodes.map(node => {
|
|
dispatch(
|
|
updateNode({
|
|
uuid: node.get('uuid'),
|
|
patches: [
|
|
{
|
|
op: 'replace',
|
|
path: '/properties/capabilities',
|
|
value: setNodeCapability(
|
|
node.getIn(['properties', 'capabilities']),
|
|
'profile',
|
|
tag
|
|
)
|
|
}
|
|
]
|
|
})
|
|
);
|
|
});
|
|
};
|
|
|
|
export const startProvideNodes = nodeIds => (dispatch, getState) => {
|
|
dispatch(startOperation(nodeIds));
|
|
dispatch(pollNodeslistDuringProgress());
|
|
return dispatch(
|
|
startWorkflow(
|
|
MistralConstants.BAREMETAL_PROVIDE,
|
|
{ node_uuids: nodeIds },
|
|
provideNodesFinished
|
|
)
|
|
).catch(error => {
|
|
dispatch(handleErrors(error, 'Selected Nodes could not be provided'));
|
|
dispatch(finishOperation(nodeIds));
|
|
});
|
|
};
|
|
|
|
export const provideNodesFinished = execution => (dispatch, getState) => {
|
|
const { input, output, state } = execution;
|
|
const nodeIds = input.node_uuids;
|
|
dispatch(finishOperation(nodeIds));
|
|
dispatch(fetchNodes());
|
|
|
|
switch (state) {
|
|
case 'SUCCESS': {
|
|
dispatch(
|
|
notify({
|
|
type: 'success',
|
|
title: 'Nodes are available',
|
|
message: sanitizeMessage(output.message)
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case 'ERROR': {
|
|
dispatch(
|
|
notify({
|
|
title: 'Some Nodes could not be provided',
|
|
message: sanitizeMessage(output.message)
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
export const startManageNodes = nodeIds => (dispatch, getState) => {
|
|
dispatch(startOperation(nodeIds));
|
|
dispatch(pollNodeslistDuringProgress());
|
|
dispatch(
|
|
startWorkflow(
|
|
MistralConstants.BAREMETAL_MANAGE,
|
|
{ node_uuids: nodeIds },
|
|
manageNodesFinished
|
|
)
|
|
).catch(error => {
|
|
dispatch(handleErrors(error, 'Selected Nodes could not be managed'));
|
|
dispatch(finishOperation(nodeIds));
|
|
});
|
|
};
|
|
|
|
export const manageNodesFinished = execution => (dispatch, getState) => {
|
|
const {
|
|
input: { node_uuids: nodeIds },
|
|
output: { message },
|
|
state
|
|
} = execution;
|
|
dispatch(finishOperation(nodeIds));
|
|
dispatch(fetchNodes());
|
|
|
|
switch (state) {
|
|
case 'SUCCESS': {
|
|
dispatch(
|
|
notify({
|
|
type: 'success',
|
|
title: 'Nodes are manageable',
|
|
message: sanitizeMessage(message)
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case 'ERROR': {
|
|
dispatch(
|
|
notify({
|
|
title: 'Some Nodes could not be managed',
|
|
message: sanitizeMessage(message)
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
export const updateNode = nodePatch => (dispatch, getState) => {
|
|
dispatch(updateNodePending(nodePatch.uuid));
|
|
return dispatch(IronicApiService.patchNode(nodePatch))
|
|
.then(response => {
|
|
dispatch(updateNodeSuccess(response));
|
|
})
|
|
.catch(error => {
|
|
dispatch(handleErrors(error, 'Node could not be updated'));
|
|
dispatch(updateNodeFailed(nodePatch.uuid));
|
|
});
|
|
};
|
|
|
|
export const updateNodePending = nodeId => ({
|
|
type: NodesConstants.UPDATE_NODE_PENDING,
|
|
payload: nodeId
|
|
});
|
|
|
|
export const updateNodeFailed = nodeId => ({
|
|
type: NodesConstants.UPDATE_NODE_FAILED,
|
|
payload: nodeId
|
|
});
|
|
|
|
export const updateNodeSuccess = node => ({
|
|
type: NodesConstants.UPDATE_NODE_SUCCESS,
|
|
payload: node
|
|
});
|
|
|
|
export const deleteNodes = nodeIds => dispatch => {
|
|
dispatch(startOperation(nodeIds));
|
|
return Promise.all(
|
|
nodeIds.map(nodeId =>
|
|
dispatch(IronicApiService.deleteNode(nodeId))
|
|
.then(response => dispatch(deleteNodeSuccess(nodeId)))
|
|
.catch(error => {
|
|
dispatch(handleErrors(error, 'Node could not be deleted'));
|
|
dispatch(deleteNodeFailed(nodeId));
|
|
})
|
|
)
|
|
);
|
|
};
|
|
|
|
export const deleteNodeSuccess = nodeId => ({
|
|
type: NodesConstants.DELETE_NODE_SUCCESS,
|
|
payload: nodeId
|
|
});
|
|
|
|
export const deleteNodeFailed = nodeId => ({
|
|
type: NodesConstants.DELETE_NODE_FAILED,
|
|
payload: nodeId
|
|
});
|