diff --git a/releasenotes/notes/config-download-0600de77081b1094.yaml b/releasenotes/notes/config-download-0600de77081b1094.yaml new file mode 100644 index 00000000..37d004d6 --- /dev/null +++ b/releasenotes/notes/config-download-0600de77081b1094.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added integration of config-download deployment as a default deployment way + using TripleO-UI. Config Download feature splits deployment into 2 parts, at + first, Heat creates all the deployment data necessary via SoftwareDeployment + resources to perform the Overcloud installation and configuration. In second + part, using downloaded data from Heat, Ansible playbooks and tasks are + generated and are then used by the Undercloud to complete the configuration + of the Overcloud. \ No newline at end of file diff --git a/src/js/actions/DeploymentActions.js b/src/js/actions/DeploymentActions.js index 8b117af2..f58f147a 100644 --- a/src/js/actions/DeploymentActions.js +++ b/src/js/actions/DeploymentActions.js @@ -25,7 +25,12 @@ import { START_DEPLOYMENT_PENDING, START_DEPLOYMENT_SUCCESS, DEPLOYMENT_FAILED, - DEPLOYMENT_SUCCESS + DEPLOYMENT_SUCCESS, + START_UNDEPLOY_FAILED, + START_UNDEPLOY_PENDING, + START_UNDEPLOY_SUCCESS, + UNDEPLOY_FAILED, + UNDEPLOY_SUCCESS } from '../constants/DeploymentConstants'; import { handleErrors } from './ErrorActions'; import MistralConstants from '../constants/MistralConstants'; @@ -91,9 +96,9 @@ export const startDeploymentSuccess = planName => ({ payload: planName }); -export const startDeploymentFailed = (planName, message) => ({ +export const startDeploymentFailed = planName => ({ type: START_DEPLOYMENT_FAILED, - payload: { planName, message } + payload: planName }); export const startDeployment = planName => dispatch => { @@ -143,3 +148,67 @@ export const deploymentFinished = execution => ( dispatch(deploymentSuccess(planName, message)); } }; + +export const startUndeployPending = planName => ({ + type: START_UNDEPLOY_PENDING, + payload: planName +}); + +export const startUndeploySuccess = planName => ({ + type: START_UNDEPLOY_SUCCESS, + payload: planName +}); + +export const startUndeployFailed = planName => ({ + type: START_UNDEPLOY_FAILED, + payload: planName +}); + +export const startUndeploy = planName => dispatch => { + dispatch(startUndeployPending(planName)); + dispatch( + startWorkflow( + MistralConstants.UNDEPLOY_PLAN, + { + container: planName, + timeout: 240 + }, + execution => dispatch(undeployFinished(execution)), + 10 * 60 * 1000 + ) + ) + .then(execution => dispatch(startUndeploySuccess(planName))) + .catch(error => { + dispatch( + handleErrors(error, `Plan ${planName} deployment could not be deleted`) + ); + dispatch(startUndeployFailed(planName)); + }); +}; + +export const undeploySuccess = (planName, message) => ({ + type: UNDEPLOY_SUCCESS, + payload: { planName, message } +}); + +export const undeployFailed = (planName, message) => ({ + type: UNDEPLOY_FAILED, + payload: { planName, message } +}); + +export const undeployFinished = execution => ( + dispatch, + getState, + { getIntl } +) => { + const { + input: { container: planName }, + output: { message }, + state + } = execution; + if (state === 'ERROR') { + dispatch(undeployFailed(planName, message)); + } else { + dispatch(undeploySuccess(planName, message)); + } +}; diff --git a/src/js/actions/StacksActions.js b/src/js/actions/StacksActions.js index 3d3c42fe..8ec3b39f 100644 --- a/src/js/actions/StacksActions.js +++ b/src/js/actions/StacksActions.js @@ -174,41 +174,5 @@ export default { dispatch(this.fetchEnvironmentFailed(stack)); }); }; - }, - - deleteStackSuccess(stackName) { - return { - type: StacksConstants.DELETE_STACK_SUCCESS, - payload: stackName - }; - }, - - deleteStackFailed() { - return { - type: StacksConstants.DELETE_STACK_FAILED - }; - }, - - deleteStackPending() { - return { - type: StacksConstants.DELETE_STACK_PENDING - }; - }, - - /** - * Starts a delete request for a stack. - */ - deleteStack(stack) { - return dispatch => { - dispatch(this.deleteStackPending()); - dispatch(HeatApiService.deleteStack(stack.stack_name, stack.id)) - .then(response => { - dispatch(this.deleteStackSuccess(stack.stack_name)); - }) - .catch(error => { - dispatch(handleErrors(error, 'Stack could not be deleted')); - dispatch(this.deleteStackFailed()); - }); - }; } }; diff --git a/src/js/actions/ZaqarActions.js b/src/js/actions/ZaqarActions.js index 4b263e42..5d5ef8a3 100644 --- a/src/js/actions/ZaqarActions.js +++ b/src/js/actions/ZaqarActions.js @@ -14,20 +14,25 @@ * under the License. */ -import { deploymentStates } from '../constants/DeploymentConstants'; import { get } from 'lodash'; +import { normalize } from 'normalizr'; + +import { deploymentStates } from '../constants/DeploymentConstants'; +import { getCurrentStack } from '../selectors/stacks'; import LoggerActions from './LoggerActions'; import NodesActions from './NodesActions'; import PlansActions from './PlansActions'; import RegisterNodesActions from './RegisterNodesActions'; import RolesActions from './RolesActions'; import StacksActions from './StacksActions'; +import { stackSchema } from '../normalizrSchemas/stacks'; import MistralConstants from '../constants/MistralConstants'; import ZaqarWebSocketService from '../services/ZaqarWebSocketService'; import { handleWorkflowMessage } from './WorkflowActions'; import { getDeploymentStatusSuccess, deploymentFinished, + undeployFinished, configDownloadMessage } from './DeploymentActions'; import NetworksActions from './NetworksActions'; @@ -142,12 +147,19 @@ export default { break; } - case MistralConstants.HEAT_STACKS_GET: { - const { stack, stack: { stack_name, id } } = payload; - dispatch(StacksActions.fetchStackSuccess(stack)); - !getState().stacks.isFetchingResources && + case MistralConstants.HEAT_STACKS_LIST: { + const stacks = + normalize(payload.stacks, [stackSchema]).entities.stacks || {}; + dispatch(StacksActions.fetchStacksSuccess(stacks)); + + // TODO(jtomasek): It would be nicer if we could identify that + // stack has changed in the component and fetch resources there + const { isFetchingResources } = getState().stacks; + const currentStack = getCurrentStack(getState()); + if (!isFetchingResources && currentStack) { + const { stack_name, id } = currentStack; dispatch(StacksActions.fetchResources(stack_name, id)); - break; + } } case MistralConstants.CONFIG_DOWNLOAD_DEPLOY: { @@ -170,6 +182,25 @@ export default { break; } + case MistralConstants.UNDEPLOY_PLAN: { + if (payload.deployment_status === deploymentStates.UNDEPLOYING) { + const { message, plan_name, deployment_status } = payload; + dispatch( + getDeploymentStatusSuccess(plan_name, { + status: deployment_status, + message + }) + ); + } else { + dispatch( + handleWorkflowMessage(payload.execution_id, execution => + dispatch(undeployFinished(execution)) + ) + ); + } + break; + } + case MistralConstants.PLAN_EXPORT: { dispatch( handleWorkflowMessage(payload.execution.id, execution => diff --git a/src/js/components/deployment/DeploymentConfirmation.js b/src/js/components/deployment/DeploymentConfirmation.js index 87fd01ac..05cbb06c 100644 --- a/src/js/components/deployment/DeploymentConfirmation.js +++ b/src/js/components/deployment/DeploymentConfirmation.js @@ -27,7 +27,6 @@ import { CloseModalXButton, RoutedModalPanel } from '../ui/Modals'; -import { deploymentStates } from '../../constants/DeploymentConstants'; import { getCurrentPlanName } from '../../selectors/plans'; import { getCurrentPlanDeploymentStatus, @@ -91,14 +90,11 @@ class DeploymentConfirmation extends React.Component { const { allValidationsSuccessful, currentPlanName, - deploymentStatus, startDeployment, - environmentSummary + environmentSummary, + isPendingDeploymentRequest } = this.props; - const buttonDisabled = - deploymentStatus.status === deploymentStates.STARTING_DEPLOYMENT; - return ( @@ -132,7 +128,7 @@ class DeploymentConfirmation extends React.Component {

@@ -153,6 +149,7 @@ DeploymentConfirmation.propTypes = { deploymentStatus: PropTypes.object.isRequired, environmentSummary: PropTypes.string.isRequired, intl: PropTypes.object, + isPendingDeploymentRequest: PropTypes.bool.isRequired, runPreDeploymentValidations: PropTypes.func.isRequired, startDeployment: PropTypes.func.isRequired }; @@ -163,7 +160,9 @@ const mapStateToProps = (state, props) => ({ deploymentStatusLoaded: getCurrentPlanDeploymentStatusUI(state).isLoaded, deploymentStatus: getCurrentPlanDeploymentStatus(state), deploymentStatusUIError: getCurrentPlanDeploymentStatusUI(state).error, - environmentSummary: getEnvironmentConfigurationSummary(state) + environmentSummary: getEnvironmentConfigurationSummary(state), + isPendingDeploymentRequest: getCurrentPlanDeploymentStatusUI(state) + .isPendingRequest }); const mapDispatchToProps = dispatch => ({ diff --git a/src/js/components/deployment/DeploymentDetail.js b/src/js/components/deployment/DeploymentDetail.js index 41586dab..0104014b 100644 --- a/src/js/components/deployment/DeploymentDetail.js +++ b/src/js/components/deployment/DeploymentDetail.js @@ -21,6 +21,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import DeploymentProgress from './DeploymentProgress'; +import UndeployProgress from './UndeployProgress'; import DeploymentFailure from './DeploymentFailure'; import { deploymentStates } from '../../constants/DeploymentConstants'; import { getCurrentPlanName } from '../../selectors/plans'; @@ -56,21 +57,12 @@ class DeploymentDetail extends React.Component { switch (deploymentStatus.status) { case deploymentStates.DEPLOYING: - case deploymentStates.UNDEPLOYING: return ; - case deploymentStates.DEPLOY_SUCCESS: - return ( -
- {deploymentStatus.status} - {deploymentStatus.message} -
- ); + case deploymentStates.UNDEPLOYING: + return ; + case deploymentStates.UNDEPLOY_FAILED: case deploymentStates.DEPLOY_FAILED: return ; - case deploymentStates.UNDEPLOY_FAILED: - // TODO(jtomasek): handle undeploy failure - return 'undeploy failed'; - case deploymentStates.UNKNOWN: default: return null; } diff --git a/src/js/components/deployment/DeploymentFailure.js b/src/js/components/deployment/DeploymentFailure.js index fc451d4b..26ec7ccc 100644 --- a/src/js/components/deployment/DeploymentFailure.js +++ b/src/js/components/deployment/DeploymentFailure.js @@ -24,10 +24,14 @@ import React from 'react'; import DeleteStackButton from '../deployment_plan/DeleteStackButton'; import { deploymentStatusMessages } from '../../constants/DeploymentConstants'; import { getCurrentStack } from '../../selectors/stacks'; -import { getCurrentPlanDeploymentStatus } from '../../selectors/deployment'; +import { + getCurrentPlanDeploymentStatus, + getCurrentPlanDeploymentStatusUI +} from '../../selectors/deployment'; import InlineNotification from '../ui/InlineNotification'; import { sanitizeMessage } from '../../utils'; import StacksActions from '../../actions/StacksActions'; +import { startUndeploy } from '../../actions/DeploymentActions'; class DeploymentFailure extends React.Component { componentDidMount() { @@ -38,9 +42,9 @@ class DeploymentFailure extends React.Component { render() { const { deploymentStatus: { status, message }, - deleteStack, + undeployPlan, intl: { formatMessage }, - isRequestingStackDelete, + isPendingRequest, planName, stack } = this.props; @@ -53,36 +57,37 @@ class DeploymentFailure extends React.Component { >

{sanitizeMessage(message)}

- + {stack && ( + + )} ); } } DeploymentFailure.propTypes = { - deleteStack: PropTypes.func.isRequired, deploymentStatus: PropTypes.object.isRequired, fetchStacks: PropTypes.func.isRequired, intl: PropTypes.object, isFetchingStacks: PropTypes.bool.isRequired, - isRequestingStackDelete: PropTypes.bool.isRequired, + isPendingRequest: PropTypes.bool.isRequired, planName: PropTypes.string.isRequired, - stack: ImmutablePropTypes.record + stack: ImmutablePropTypes.record, + undeployPlan: PropTypes.func.isRequired }; const mapStateToProps = (state, props) => ({ deploymentStatus: getCurrentPlanDeploymentStatus(state), isFetchingStacks: state.stacks.isFetching, - isRequestingStackDelete: state.stacks.isRequestingStackDelete, + isPendingRequest: getCurrentPlanDeploymentStatusUI(state).isPendingRequest, stack: getCurrentStack(state) }); const mapDispatchToProps = dispatch => ({ - deleteStack: stack => dispatch(StacksActions.deleteStack(stack)), + undeployPlan: planName => dispatch(startUndeploy(planName)), fetchStacks: () => dispatch(StacksActions.fetchStacks()) }); diff --git a/src/js/components/deployment/DeploymentProgress.js b/src/js/components/deployment/DeploymentProgress.js index 6307bb9a..d251fd0a 100644 --- a/src/js/components/deployment/DeploymentProgress.js +++ b/src/js/components/deployment/DeploymentProgress.js @@ -28,7 +28,7 @@ import { getCreateCompleteResources } from '../../selectors/stacks'; import { getCurrentPlanDeploymentStatus } from '../../selectors/deployment'; -import { InlineLoader } from '../ui/Loader'; +import { InlineLoader, Loader } from '../ui/Loader'; import StackResourcesTable from './StackResourcesTable'; import { stackStates } from '../../constants/StacksConstants'; @@ -37,14 +37,6 @@ const messages = defineMessages({ id: 'DeploymentSuccess.resources', defaultMessage: 'Resources' }, - cancelDeployment: { - id: 'DeploymentProgress.cancelDeployment', - defaultMessage: 'Cancel Deployment' - }, - requestingDeletion: { - id: 'DeploymentProgress.requestingDeletion', - defaultMessage: 'Requesting Deletion of Deployment' - }, initializingDeployment: { id: 'DeploymentProgress.initializingDeployment', defaultMessage: 'Initializing {planName} plan deployment' @@ -70,77 +62,83 @@ class DeploymentProgress extends React.Component { resources, resourcesLoaded, stackDeploymentProgress, - stack + stack, + stacksLoaded } = this.props; return ( - {!stack && ( - -
- - {planName} }} - /> -
- 0%} - className="progress-label-top-right" - /> -
- )} - {stack && - stack.stack_status !== stackStates.CREATE_COMPLETE && ( + + {!stack && (
{planName}, - resourcesCount, - completeResourcesCount - }} - /> -
- {stackDeploymentProgress + '%'}} - className="progress-label-top-right" - /> - {message &&
{message}
} -

- -

-
-
- -
-
-
- )} - {stack && - stack.stack_status === stackStates.CREATE_COMPLETE && ( - -
- - {planName} }} />
- - {message &&
{message}
} - 0%} + className="progress-label-top-right" />
)} + {stack && + stack.stack_status !== stackStates.CREATE_COMPLETE && ( + +
+ + {planName}, + resourcesCount, + completeResourcesCount + }} + /> +
+ {stackDeploymentProgress + '%'}} + className="progress-label-top-right" + /> + {message &&
{message}
} +

+ +

+
+
+ +
+
+
+ )} + {stack && + stack.stack_status === stackStates.CREATE_COMPLETE && ( + +
+ + {planName} }} + /> +
+ + {message &&
{message}
} + +
+ )} +
); } @@ -158,22 +156,23 @@ DeploymentProgress.propTypes = { resourcesCount: PropTypes.number, resourcesLoaded: PropTypes.bool.isRequired, stack: ImmutablePropTypes.record, - stackDeploymentProgress: PropTypes.number.isRequired + stackDeploymentProgress: PropTypes.number.isRequired, + stacksLoaded: PropTypes.bool.isRequired }; const mapStateToProps = (state, props) => ({ completeResourcesCount: getCreateCompleteResources(state).size, - stack: getCurrentStack(state), deploymentStatus: getCurrentPlanDeploymentStatus(state), isFetchingStacks: state.stacks.isFetching, - stackDeploymentProgress: getCurrentStackDeploymentProgress(state), resourcesCount: state.stacks.resources.size, resources: state.stacks.resources, - resourcesLoaded: state.stacks.resourcesLoaded + resourcesLoaded: state.stacks.resourcesLoaded, + stack: getCurrentStack(state), + stackDeploymentProgress: getCurrentStackDeploymentProgress(state), + stacksLoaded: state.stacks.isLoaded }); const mapDispatchToProps = (dispatch, { planName }) => ({ - deleteStack: () => dispatch(StacksActions.deleteStack(planName, '')), fetchStacks: () => dispatch(StacksActions.fetchStacks()), fetchResources: (stackName, stackId) => dispatch(StacksActions.fetchResources(stackName, stackId)) diff --git a/src/js/components/deployment/UndeployProgress.js b/src/js/components/deployment/UndeployProgress.js new file mode 100644 index 00000000..049a8baa --- /dev/null +++ b/src/js/components/deployment/UndeployProgress.js @@ -0,0 +1,167 @@ +/** + * Copyright 2018 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 { connect } from 'react-redux'; +import { defineMessages, FormattedMessage } from 'react-intl'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { ModalBody, ProgressBar } from 'react-bootstrap'; +import PropTypes from 'prop-types'; +import React, { Fragment } from 'react'; + +import { + getCurrentStack, + getCurrentStackDeletionProgress +} from '../../selectors/stacks'; +import { getCurrentPlanDeploymentStatus } from '../../selectors/deployment'; +import { InlineLoader, Loader } from '../ui/Loader'; +import StackResourcesTable from './StackResourcesTable'; +import { stackStates } from '../../constants/StacksConstants'; + +const messages = defineMessages({ + resources: { + id: 'UndeployProgress.resources', + defaultMessage: 'Resources' + }, + initializingUndeploy: { + id: 'UndeployProgress.initializingDeployment', + defaultMessage: 'Initializing {planName} plan deployment deletion' + }, + undeployingPlan: { + id: 'UndeployProgress.undeployingPlan', + defaultMessage: 'Deleting {planName} plan deployment' + } +}); + +class UndeployProgress extends React.Component { + render() { + const { + deploymentStatus: { message }, + planName, + resources, + resourcesLoaded, + stackDeletionProgress, + stack, + stacksLoaded + } = this.props; + + return ( + + + {stack && + stack.stack_status !== stackStates.DELETE_IN_PROGRESS && ( + +
+ + {planName} }} + /> +
+ 0%} + className="progress-label-top-right" + /> +
+ )} + {stack && + stack.stack_status === stackStates.DELETE_IN_PROGRESS && ( + +
+ + {planName} }} + /> +
+ {stackDeletionProgress + '%'}} + className="progress-label-top-right" + /> + {message &&
{message}
} +

+ +

+
+
+ +
+
+
+ )} + {!stack && ( + +
+ + {planName} }} + /> +
+ {100 + '%'}} + className="progress-label-top-right" + /> + {message &&
{message}
} +
+ )} +
+
+ ); + } +} + +UndeployProgress.propTypes = { + deploymentStatus: PropTypes.object.isRequired, + fetchResources: PropTypes.func.isRequired, + fetchStacks: PropTypes.func.isRequired, + intl: PropTypes.object, + isFetchingStacks: PropTypes.bool.isRequired, + planName: PropTypes.string.isRequired, + resources: ImmutablePropTypes.list, + resourcesLoaded: PropTypes.bool.isRequired, + stack: ImmutablePropTypes.record, + stackDeletionProgress: PropTypes.number.isRequired, + stacksLoaded: PropTypes.bool.isRequired +}; + +const mapStateToProps = (state, props) => ({ + deploymentStatus: getCurrentPlanDeploymentStatus(state), + isFetchingStacks: state.stacks.isFetching, + resources: state.stacks.resources, + resourcesLoaded: state.stacks.resourcesLoaded, + stack: getCurrentStack(state), + stackDeletionProgress: getCurrentStackDeletionProgress(state), + stacksLoaded: state.stacks.isLoaded +}); + +const mapDispatchToProps = (dispatch, { planName }) => ({ + fetchStacks: () => dispatch(StacksActions.fetchStacks()), + fetchResources: (stackName, stackId) => + dispatch(StacksActions.fetchResources(stackName, stackId)) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(UndeployProgress); diff --git a/src/js/components/deployment_plan/DeleteStackButton.js b/src/js/components/deployment_plan/DeleteStackButton.js index 4df48917..6d8176ed 100644 --- a/src/js/components/deployment_plan/DeleteStackButton.js +++ b/src/js/components/deployment_plan/DeleteStackButton.js @@ -62,7 +62,7 @@ class DeleteStackButton extends React.Component { className="link btn btn-danger" > @@ -85,8 +85,7 @@ class DeleteStackButton extends React.Component { DeleteStackButton.propTypes = { deleteStack: PropTypes.func.isRequired, disabled: PropTypes.bool.isRequired, - intl: PropTypes.object, - loaded: PropTypes.bool.isRequired + intl: PropTypes.object }; export default injectIntl(DeleteStackButton); diff --git a/src/js/components/deployment_plan/DeployStep.js b/src/js/components/deployment_plan/DeployStep.js index 7ab0432f..224ab2ac 100644 --- a/src/js/components/deployment_plan/DeployStep.js +++ b/src/js/components/deployment_plan/DeployStep.js @@ -24,6 +24,7 @@ import React from 'react'; import DeploymentSuccess from './DeploymentSuccess'; import DeploymentFailure from './DeploymentFailure'; import DeploymentProgress from './DeploymentProgress'; +import UndeployProgress from './UndeployProgress'; import { deploymentStates, deploymentStatusMessages @@ -49,20 +50,20 @@ const messages = defineMessages({ export const DeployStep = ({ currentPlan, deploymentStatus, + deploymentStatusUIError, intl: { formatMessage }, - deploymentStatusUIError + isPendingDeploymentRequest }) => { switch (deploymentStatus.status) { case deploymentStates.DEPLOYING: - case deploymentStates.UNDEPLOYING: return ; + case deploymentStates.UNDEPLOYING: + return ; case deploymentStates.DEPLOY_SUCCESS: return ; case deploymentStates.DEPLOY_FAILED: - return ; case deploymentStates.UNDEPLOY_FAILED: - // TODO(jtomasek): handle undeploy failure - return 'undeploy failed'; + return ; case deploymentStates.UNKNOWN: return ( ); default: - const disabled = - deploymentStatus.status === deploymentStates.STARTING_DEPLOYMENT; return ( @@ -97,12 +96,15 @@ DeployStep.propTypes = { currentPlan: ImmutablePropTypes.record.isRequired, deploymentStatus: PropTypes.object.isRequired, deploymentStatusUIError: PropTypes.string, - intl: PropTypes.object + intl: PropTypes.object, + isPendingDeploymentRequest: PropTypes.bool.isRequired }; const mapStateToProps = (state, props) => ({ deploymentStatus: getCurrentPlanDeploymentStatus(state), - deploymentStatusUIError: getCurrentPlanDeploymentStatusUI(state).error + deploymentStatusUIError: getCurrentPlanDeploymentStatusUI(state).error, + isPendingDeploymentRequest: getCurrentPlanDeploymentStatusUI(state) + .isPendingRequest }); export default injectIntl(connect(mapStateToProps)(DeployStep)); diff --git a/src/js/components/deployment_plan/DeploymentConfirmationRoute.js b/src/js/components/deployment_plan/DeploymentConfirmationRoute.js index a5ce3163..739a6fa6 100644 --- a/src/js/components/deployment_plan/DeploymentConfirmationRoute.js +++ b/src/js/components/deployment_plan/DeploymentConfirmationRoute.js @@ -22,9 +22,7 @@ import DeploymentConfirmation from '../deployment/DeploymentConfirmation'; import { deploymentStates as ds } from '../../constants/DeploymentConstants'; const DeploymentConfirmationRoute = ({ currentPlanName, deploymentStatus }) => - [ds.UNDEPLOYED, ds.UNKNOWN, ds.STARTING_DEPLOYMENT].includes( - deploymentStatus - ) ? ( + [ds.UNDEPLOYED, ds.UNKNOWN].includes(deploymentStatus) ? ( ) : ( diff --git a/src/js/components/deployment_plan/DeploymentDetailRoute.js b/src/js/components/deployment_plan/DeploymentDetailRoute.js index f7511a31..d54766f8 100644 --- a/src/js/components/deployment_plan/DeploymentDetailRoute.js +++ b/src/js/components/deployment_plan/DeploymentDetailRoute.js @@ -22,7 +22,7 @@ import DeploymentDetail from '../deployment/DeploymentDetail'; import { deploymentStates as ds } from '../../constants/DeploymentConstants'; const DeploymentDetailRoute = ({ currentPlanName, deploymentStatus }) => - [ds.DEPLOYING, ds.UNDEPLOYING, ds.DEPLOY_FAILED].includes( + [ds.DEPLOYING, ds.UNDEPLOYING, ds.DEPLOY_FAILED, ds.UNDEPLOY_FAILED].includes( deploymentStatus ) ? ( diff --git a/src/js/components/deployment_plan/DeploymentProgress.js b/src/js/components/deployment_plan/DeploymentProgress.js index c89f8d2b..78496551 100644 --- a/src/js/components/deployment_plan/DeploymentProgress.js +++ b/src/js/components/deployment_plan/DeploymentProgress.js @@ -30,18 +30,10 @@ import { getCurrentStackDeploymentProgress, getCreateCompleteResources } from '../../selectors/stacks'; -import { InlineLoader } from '../ui/Loader'; +import { InlineLoader, Loader } from '../ui/Loader'; import StacksActions from '../../actions/StacksActions'; const messages = defineMessages({ - cancelDeployment: { - id: 'DeploymentProgress.cancelDeployment', - defaultMessage: 'Cancel Deployment' - }, - requestingDeletion: { - id: 'DeploymentProgress.requestingDeletion', - defaultMessage: 'Requesting Deletion of Deployment' - }, initializingDeployment: { id: 'DeploymentProgress.initializingDeployment', defaultMessage: 'Initializing {planName} plan deployment' @@ -66,10 +58,10 @@ class DeploymentProgress extends React.Component { this.fetchStacks(); } - componentWillReceiveProps(newProps) { - if (!this.props.stack && newProps.stack) { - const { stack: { stack_name, id } } = newProps; - this.props.fetchResources(stack_name, id); + componentDidUpdate(prevProps) { + if (!prevProps.stack && this.props.stack) { + const { stack: { stack_name, id }, fetchResources } = this.props; + fetchResources(stack_name, id); } } @@ -85,7 +77,8 @@ class DeploymentProgress extends React.Component { resourcesCount, completeResourcesCount, stackDeploymentProgress, - stack + stack, + stacksLoaded } = this.props; return ( @@ -101,58 +94,60 @@ class DeploymentProgress extends React.Component {

- {!stack && ( - -
- - {planName} }} - /> -
- 0%} - className="progress-label-top-right" - /> -
- )} - {stack && - stack.stack_status !== stackStates.CREATE_COMPLETE && ( + + {!stack && (
{planName}, - resourcesCount, - completeResourcesCount - }} - /> -
- {stackDeploymentProgress + '%'}} - className="progress-label-top-right" - /> - {message &&
{message}
} -
- )} - {stack && - stack.stack_status === stackStates.CREATE_COMPLETE && ( - -
- - {planName} }} />
- - {message &&
{message}
} + 0%} + className="progress-label-top-right" + />
)} + {stack && + stack.stack_status !== stackStates.CREATE_COMPLETE && ( + +
+ + {planName}, + resourcesCount, + completeResourcesCount + }} + /> +
+ {stackDeploymentProgress + '%'}} + className="progress-label-top-right" + /> + {message &&
{message}
} +
+ )} + {stack && + stack.stack_status === stackStates.CREATE_COMPLETE && ( + +
+ + {planName} }} + /> +
+ + {message &&
{message}
} +
+ )} +
); } @@ -168,7 +163,8 @@ DeploymentProgress.propTypes = { planName: PropTypes.string.isRequired, resourcesCount: PropTypes.number, stack: ImmutablePropTypes.record, - stackDeploymentProgress: PropTypes.number.isRequired + stackDeploymentProgress: PropTypes.number.isRequired, + stacksLoaded: PropTypes.bool.isRequired }; const mapStateToProps = (state, props) => ({ @@ -176,12 +172,12 @@ const mapStateToProps = (state, props) => ({ stack: getCurrentStack(state), deploymentStatus: getCurrentPlanDeploymentStatus(state), isFetchingStacks: state.stacks.isFetching, + stacksLoaded: state.stacks.isLoaded, stackDeploymentProgress: getCurrentStackDeploymentProgress(state), resourcesCount: state.stacks.resources.size }); const mapDispatchToProps = (dispatch, { planName }) => ({ - deleteStack: () => dispatch(StacksActions.deleteStack(planName, '')), fetchStacks: () => dispatch(StacksActions.fetchStacks()), fetchResources: (stackName, stackId) => dispatch(StacksActions.fetchResources(stackName, stackId)) diff --git a/src/js/components/deployment_plan/DeploymentSuccess.js b/src/js/components/deployment_plan/DeploymentSuccess.js index 9797ae17..f5e4e38d 100644 --- a/src/js/components/deployment_plan/DeploymentSuccess.js +++ b/src/js/components/deployment_plan/DeploymentSuccess.js @@ -22,11 +22,16 @@ import React, { Fragment } from 'react'; import DeleteStackButton from './DeleteStackButton'; import { deploymentStatusMessages } from '../../constants/DeploymentConstants'; -import { getCurrentPlanDeploymentStatus } from '../../selectors/deployment'; +import { + getCurrentPlanDeploymentStatus, + getCurrentPlanDeploymentStatusUI +} from '../../selectors/deployment'; import { getCurrentStack, getOvercloudInfo } from '../../selectors/stacks'; +import { getCurrentPlanName } from '../../selectors/plans'; import InlineNotification from '../ui/InlineNotification'; import OvercloudInfo from '../deployment/OvercloudInfo'; import { Loader } from '../ui/Loader'; +import { startUndeploy } from '../../actions/DeploymentActions'; import StacksActions from '../../actions/StacksActions'; class DeploymentSuccess extends React.Component { @@ -43,12 +48,13 @@ class DeploymentSuccess extends React.Component { render() { const { intl: { formatMessage }, + isPendingRequest, stack, stacksLoaded, overcloudInfo, - deleteStack, - deploymentStatus: { status, message }, - isRequestingStackDelete + planName, + undeployPlan, + deploymentStatus: { status, message } } = this.props; return ( @@ -67,9 +73,8 @@ class DeploymentSuccess extends React.Component { fetchOvercloudInfo={this.fetchOvercloudInfo.bind(this)} /> )} @@ -79,28 +84,30 @@ class DeploymentSuccess extends React.Component { } DeploymentSuccess.propTypes = { - deleteStack: PropTypes.func.isRequired, deploymentStatus: ImmutablePropTypes.record.isRequired, fetchStackEnvironment: PropTypes.func.isRequired, fetchStackResource: PropTypes.func.isRequired, fetchStacks: PropTypes.func.isRequired, intl: PropTypes.object, - isRequestingStackDelete: PropTypes.bool, + isPendingRequest: PropTypes.bool.isRequired, overcloudInfo: ImmutablePropTypes.map.isRequired, + planName: PropTypes.string.isRequired, stack: ImmutablePropTypes.record, - stacksLoaded: PropTypes.bool.isRequired + stacksLoaded: PropTypes.bool.isRequired, + undeployPlan: PropTypes.func.isRequired }; const mapStateToProps = state => ({ deploymentStatus: getCurrentPlanDeploymentStatus(state), - isRequestingStackDelete: state.stacks.isRequestingStackDelete, + planName: getCurrentPlanName(state), overcloudInfo: getOvercloudInfo(state), stack: getCurrentStack(state), - stacksLoaded: state.stacks.isLoaded + stacksLoaded: state.stacks.isLoaded, + isPendingRequest: getCurrentPlanDeploymentStatusUI(state).isPendingRequest }); const mapDispatchToProps = dispatch => ({ - deleteStack: planName => dispatch(StacksActions.deleteStack(planName, '')), + undeployPlan: planName => dispatch(startUndeploy(planName)), fetchStacks: () => dispatch(StacksActions.fetchStacks()), fetchStackEnvironment: stack => dispatch(StacksActions.fetchEnvironment(stack)), diff --git a/src/js/components/deployment_plan/UndeployProgress.js b/src/js/components/deployment_plan/UndeployProgress.js new file mode 100644 index 00000000..838a204d --- /dev/null +++ b/src/js/components/deployment_plan/UndeployProgress.js @@ -0,0 +1,183 @@ +/** + * Copyright 2018 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 { connect } from 'react-redux'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { Link } from 'react-router-dom'; +import { ProgressBar } from 'react-bootstrap'; +import PropTypes from 'prop-types'; +import React, { Fragment } from 'react'; + +import { deploymentStatusMessages } from '../../constants/DeploymentConstants'; +import { stackStates } from '../../constants/StacksConstants'; +import { getCurrentPlanDeploymentStatus } from '../../selectors/deployment'; +import { + getCurrentStack, + getCurrentStackDeletionProgress, + getDeleteCompleteResources +} from '../../selectors/stacks'; +import { InlineLoader, Loader } from '../ui/Loader'; +import StacksActions from '../../actions/StacksActions'; + +const messages = defineMessages({ + initializingUndeploy: { + id: 'UndeployProgress.initializingUndeploy', + defaultMessage: 'Initializing {planName} plan deployment deletion' + }, + viewInformation: { + id: 'UndeployProgress.viewInformation', + defaultMessage: 'View detailed information' + }, + undeployingPlan: { + id: 'UndeployProgress.undeployingPlan', + defaultMessage: 'Deleting {planName} plan deployment' + } +}); + +class UndeployProgress extends React.Component { + componentDidMount() { + this.fetchStacks(); + } + + componentDidUpdate(prevProps) { + if (!prevProps.stack && this.props.stack) { + const { stack: { stack_name, id }, fetchResources } = this.props; + fetchResources(stack_name, id); + } + } + + fetchStacks() { + const { fetchStacks, isFetchingStacks } = this.props; + !isFetchingStacks && fetchStacks(); + } + + render() { + const { + deploymentStatus: { status, message }, + planName, + stackDeletionProgress, + stack, + stacksLoaded + } = this.props; + + return ( +
+

+ + {planName} }} + /> + {' '} + + + +

+ + {stack && + stack.stack_status !== stackStates.DELETE_IN_PROGRESS && ( + +
+ + {planName} }} + /> +
+ 0%} + className="progress-label-top-right" + /> +
+ )} + {stack && + stack.stack_status === stackStates.DELETE_IN_PROGRESS && ( + +
+ + {planName} }} + /> +
+ {stackDeletionProgress + '%'}} + className="progress-label-top-right" + /> + {message &&
{message}
} +
+ )} + {!stack && ( + +
+ + {planName} }} + /> +
+ {100 + '%'}} + className="progress-label-top-right" + /> + {message &&
{message}
} +
+ )} +
+
+ ); + } +} + +UndeployProgress.propTypes = { + deletedResourcesCount: PropTypes.number, + deploymentStatus: PropTypes.object.isRequired, + fetchResources: PropTypes.func.isRequired, + fetchStacks: PropTypes.func.isRequired, + intl: PropTypes.object, + isFetchingStacks: PropTypes.bool.isRequired, + planName: PropTypes.string.isRequired, + resourcesCount: PropTypes.number, + stack: ImmutablePropTypes.record, + stackDeletionProgress: PropTypes.number.isRequired, + stacksLoaded: PropTypes.bool.isRequired +}; + +const mapStateToProps = (state, props) => ({ + deletedResourcesCount: getDeleteCompleteResources(state).size, + deploymentStatus: getCurrentPlanDeploymentStatus(state), + isFetchingStacks: state.stacks.isFetching, + stack: getCurrentStack(state), + stacksLoaded: state.stacks.isLoaded, + stackDeletionProgress: getCurrentStackDeletionProgress(state), + resourcesCount: state.stacks.resources.size +}); + +const mapDispatchToProps = (dispatch, { planName }) => ({ + fetchStacks: () => dispatch(StacksActions.fetchStacks()), + fetchResources: (stackName, stackId) => + dispatch(StacksActions.fetchResources(stackName, stackId)) +}); + +export default injectIntl( + connect(mapStateToProps, mapDispatchToProps)(UndeployProgress) +); diff --git a/src/js/constants/DeploymentConstants.js b/src/js/constants/DeploymentConstants.js index 8e5498b3..14bae47d 100644 --- a/src/js/constants/DeploymentConstants.js +++ b/src/js/constants/DeploymentConstants.js @@ -26,11 +26,15 @@ export const START_DEPLOYMENT_SUCCESS = 'START_DEPLOYMENT_SUCCESS'; export const START_DEPLOYMENT_PENDING = 'START_DEPLOYMENT_PENDING'; export const DEPLOYMENT_FAILED = 'DEPLOYMENT_FAILED'; export const DEPLOYMENT_SUCCESS = 'DEPLOYMENT_SUCCESS'; +export const START_UNDEPLOY_FAILED = 'START_UNDEPLOY_FAILED'; +export const START_UNDEPLOY_SUCCESS = 'START_UNDEPLOY_SUCCESS'; +export const START_UNDEPLOY_PENDING = 'START_UNDEPLOY_PENDING'; +export const UNDEPLOY_FAILED = 'UNDEPLOY_FAILED'; +export const UNDEPLOY_SUCCESS = 'UNDEPLOY_SUCCESS'; export const deploymentStates = keyMirror({ UNDEPLOYED: null, DEPLOY_SUCCESS: null, - STARTING_DEPLOYMENT: null, DEPLOYING: null, UNDEPLOYING: null, DEPLOY_FAILED: null, @@ -53,7 +57,8 @@ export const deploymentStatusMessages = defineMessages({ }, UNDEPLOYING: { id: 'DeploymentStatus.undeploying', - defaultMessage: 'Undeploy in progress' + defaultMessage: + 'Deletion of {planName} plan deployment is currently in progress' }, DEPLOY_FAILED: { id: 'DeploymentStatus.deploymentFailed', diff --git a/src/js/constants/MistralConstants.js b/src/js/constants/MistralConstants.js index 35784f7d..22cad74e 100644 --- a/src/js/constants/MistralConstants.js +++ b/src/js/constants/MistralConstants.js @@ -30,7 +30,7 @@ export default { DEPLOYMENT_DEPLOY_PLAN: 'tripleo.deployment.v1.deploy_plan', UNDEPLOY_PLAN: 'tripleo.deployment.v1.undeploy_plan', DOWNLOAD_LOGS: 'tripleo.plan_management.v1.download_logs', - HEAT_STACKS_GET: 'tripleo.stack.v1._heat_stacks_get', + HEAT_STACKS_LIST: 'tripleo.stack.v1._heat_stacks_list', NETWORK_LIST: 'tripleo.plan_management.v1.list_networks', PARAMETERS_GET: 'tripleo.parameters.get_flatten', PARAMETERS_UPDATE: 'tripleo.parameters.update', diff --git a/src/js/constants/StacksConstants.js b/src/js/constants/StacksConstants.js index 211d7527..9b572acc 100644 --- a/src/js/constants/StacksConstants.js +++ b/src/js/constants/StacksConstants.js @@ -18,9 +18,6 @@ import keyMirror from 'keymirror'; import { defineMessages } from 'react-intl'; export default keyMirror({ - DELETE_STACK_PENDING: null, - DELETE_STACK_FAILED: null, - DELETE_STACK_SUCCESS: null, FETCH_STACK_ENVIRONMENT_SUCCESS: null, FETCH_STACK_ENVIRONMENT_PENDING: null, FETCH_STACK_ENVIRONMENT_FAILED: null, diff --git a/src/js/immutableRecords/deploymentStatus.js b/src/js/immutableRecords/deploymentStatus.js index 3192650d..0dd609d1 100644 --- a/src/js/immutableRecords/deploymentStatus.js +++ b/src/js/immutableRecords/deploymentStatus.js @@ -28,5 +28,6 @@ export const DeploymentStatus = Record({ export const DeploymentStatusUI = Record({ error: undefined, isLoaded: false, - isFetching: false + isFetching: false, + isPendingRequest: false }); diff --git a/src/js/immutableRecords/stacks.js b/src/js/immutableRecords/stacks.js index f853591f..ff98a516 100644 --- a/src/js/immutableRecords/stacks.js +++ b/src/js/immutableRecords/stacks.js @@ -18,7 +18,6 @@ import { List, Map, Record } from 'immutable'; export const StacksState = Record({ currentStackEnvironment: Map(), - isRequestingStackDelete: false, isLoaded: false, isFetching: false, isFetchingResources: false, diff --git a/src/js/reducers/deploymentStatus.js b/src/js/reducers/deploymentStatus.js index abc49419..0babc947 100644 --- a/src/js/reducers/deploymentStatus.js +++ b/src/js/reducers/deploymentStatus.js @@ -27,6 +27,11 @@ import { START_DEPLOYMENT_FAILED, START_DEPLOYMENT_PENDING, START_DEPLOYMENT_SUCCESS, + START_UNDEPLOY_FAILED, + START_UNDEPLOY_PENDING, + START_UNDEPLOY_SUCCESS, + UNDEPLOY_FAILED, + UNDEPLOY_SUCCESS, deploymentStates } from '../constants/DeploymentConstants'; import { @@ -51,24 +56,11 @@ export const deploymentStatusByPlan = (state = Map(), { type, payload }) => { messages.push(payload.message) ) ); - case START_DEPLOYMENT_PENDING: - return state.set( - payload, - new DeploymentStatus({ status: deploymentStates.STARTING_DEPLOYMENT }) - ); case START_DEPLOYMENT_SUCCESS: return state.set( payload, new DeploymentStatus({ status: deploymentStates.DEPLOYING }) ); - case START_DEPLOYMENT_FAILED: - return state.set( - payload.planName, - new DeploymentStatus({ - status: deploymentStates.UNDEPLOYED, - message: payload.message - }) - ); case DEPLOYMENT_SUCCESS: return state.set( payload.planName, @@ -85,6 +77,27 @@ export const deploymentStatusByPlan = (state = Map(), { type, payload }) => { message: payload.message }) ); + case START_UNDEPLOY_SUCCESS: + return state.set( + payload, + new DeploymentStatus({ status: deploymentStates.UNDEPLOYING }) + ); + case UNDEPLOY_SUCCESS: + return state.set( + payload.planName, + new DeploymentStatus({ + status: deploymentStates.UNDEPLOYED, + message: payload.message + }) + ); + case UNDEPLOY_FAILED: + return state.set( + payload.planName, + new DeploymentStatus({ + status: deploymentStates.UNDEPLOY_FAILED, + message: payload.message + }) + ); default: return state; } @@ -112,6 +125,14 @@ export const deploymentStatusUI = (state = Map(), { type, payload }) => { error: undefined }) ); + case START_DEPLOYMENT_PENDING: + case START_UNDEPLOY_PENDING: + return state.setIn([payload, 'isPendingRequest'], true); + case START_DEPLOYMENT_SUCCESS: + case START_DEPLOYMENT_FAILED: + case START_UNDEPLOY_SUCCESS: + case START_UNDEPLOY_FAILED: + return state.setIn([payload, 'isPendingRequest'], false); default: return state; } diff --git a/src/js/reducers/stacksReducer.js b/src/js/reducers/stacksReducer.js index bc0ad6d3..628c0848 100644 --- a/src/js/reducers/stacksReducer.js +++ b/src/js/reducers/stacksReducer.js @@ -17,7 +17,7 @@ import { fromJS, Map } from 'immutable'; import { Stack, StackResource, StacksState } from '../immutableRecords/stacks'; -import StacksConstants, { stackStates } from '../constants/StacksConstants'; +import StacksConstants from '../constants/StacksConstants'; import PlansConstants from '../constants/PlansConstants'; const initialState = new StacksState(); @@ -90,20 +90,6 @@ export default function stacksReducer(state = initialState, action) { case PlansConstants.PLAN_CHOSEN: return initialState; - case StacksConstants.DELETE_STACK_SUCCESS: - return state - .set('isRequestingStackDelete', false) - .setIn( - ['stacks', action.payload, 'stack_status'], - stackStates.DELETE_IN_PROGRESS - ); - - case StacksConstants.DELETE_STACK_FAILED: - return state.set('isRequestingStackDelete', false); - - case StacksConstants.DELETE_STACK_PENDING: - return state.set('isRequestingStackDelete', true); - default: return state; } diff --git a/src/js/selectors/stacks.js b/src/js/selectors/stacks.js index be911a2a..2f6d8086 100644 --- a/src/js/selectors/stacks.js +++ b/src/js/selectors/stacks.js @@ -57,6 +57,11 @@ export const getCreateCompleteResources = createSelector( resources => resources.filter(r => r.resource_status === 'CREATE_COMPLETE') ); +export const getDeleteCompleteResources = createSelector( + [stackResourcesSelector], + resources => resources.filter(r => r.resource_status === 'DELETE_COMPLETE') +); + /** * Returns calculated percentage of deployment progress */ @@ -71,6 +76,20 @@ export const getCurrentStackDeploymentProgress = createSelector( } ); +/** + * Returns calculated percentage of deletion progress + */ +export const getCurrentStackDeletionProgress = createSelector( + [stackResourcesSelector, getDeleteCompleteResources], + (resources, completeResources) => { + let allResources = resources.size; + if (allResources > 0) { + return Math.ceil(completeResources.size / allResources * 100); + } + return 0; + } +); + /** * Returns a Map containing the overcloud information. */