From a33528e164091c9cbe2a68ca41d39217bb16f38a Mon Sep 17 00:00:00 2001 From: Mark Cornaia Date: Thu, 20 Feb 2020 20:18:17 +0000 Subject: [PATCH] [0.9.0] Fix Load in progress message in performance dashboard (#2281) * Add LoadRun socket status Remove await from runload fetch Signed-off-by: markcor11 * return 409 if run in progress Signed-off-by: markcor11 * Return error message in correct format Signed-off-by: markcor11 * Return optional err Signed-off-by: markcor11 * Clear status flag on error Signed-off-by: markcor11 --- .../src/components/actions/ActionRunLoad.jsx | 58 +++++++++++++------ src/pfe/portal/modules/LoadRunner.js | 1 + src/pfe/portal/modules/User.js | 11 +--- .../portal/routes/projects/loadtest.route.js | 21 ++++--- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/performance/dashboard/src/components/actions/ActionRunLoad.jsx b/src/performance/dashboard/src/components/actions/ActionRunLoad.jsx index 639b0931d..84c558d7d 100644 --- a/src/performance/dashboard/src/components/actions/ActionRunLoad.jsx +++ b/src/performance/dashboard/src/components/actions/ActionRunLoad.jsx @@ -9,10 +9,11 @@ * IBM Corporation - initial API and implementation ******************************************************************************/ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import IconRun from '@carbon/icons-react/es/play--filled/16' +import queryString from 'query-string'; +import IconRun from '@carbon/icons-react/es/play--filled/16'; import IconStop from '@carbon/icons-react/lib/close--outline/16'; import { Button, InlineLoading } from 'carbon-components-react'; import { SocketEvents } from '../../utils/sockets/SocketEvents'; @@ -51,6 +52,11 @@ class ActionRunLoad extends React.Component { componentDidMount() { this.props.socket.on(SocketEvents.RUNLOAD_STATUS_CHANGED, data => { if (data.projectID === this.props.projectID) { + + if (queryString.parse(location.search).debugsocket) { + console.log("SocketIO RX: ", data); + } + switch (data.status) { case 'preparing': { this.setState({ showModalRunTest: false, loadRunStatus: data, inlineTextLabel: 'Preparing...' }); @@ -62,17 +68,32 @@ class ActionRunLoad extends React.Component { } case 'started': { this.setState({ showModalRunTest: false, loadRunStatus: data, inlineTextLabel: 'Running...' }); + this.startCountdown(); break; } case 'completed': { // after receiving a loadrun completion message, wait a bit, then reset the button back to ready + this.setState({ showModalRunTest: false, loadRunStatus: data, inlineTextLabel: 'Completed...' }); + let nextData = data; + nextData.status = 'idle'; + setTimeout(() => this.setState({ loadRunStatus: nextData }), 3000); + break; + } + case 'cancelling': { + this.setState({ showModalRunTest: false, loadRunStatus: data, inlineTextLabel: 'Cancelling...' }); + break; + } + case 'cancelled': { + this.setState({ showModalRunTest: false, loadRunStatus: data, inlineTextLabel: 'Cancelled...' }); let nextData = data; nextData.status = 'idle'; - setTimeout(() => this.setState({ loadRunStatus: nextData }), 2000); + setTimeout(() => this.setState({ loadRunStatus: nextData }), 2000); break; } default: { - this.setState({ loadRunStatus: data }); + if (queryString.parse(location.search).debugsocket) { + console.log("Ignoring UISocket RX: ",data); + } } } } @@ -83,13 +104,13 @@ class ActionRunLoad extends React.Component { * Ask API to start a new test */ handleRunTestDlgStart(descriptionText) { + this.setState({ showModalRunTest: false, loadRunStatus: { status: 'requesting' }, inlineTextLabel: 'Requesting...' }); let instance = this; this.requestRunLoad(descriptionText).then(function (result) { - instance.setState({ showModalRunTest: false, inlineTextLabel: 'Running...' }); switch (result.status) { case 202: { // success - request to start load accepted; - instance.startCountdown(); + instance.setState({ loadRunStatus: { status: 'requested' }, inlineTextLabel: 'Requested...' }); break; } case 503: { @@ -112,7 +133,7 @@ class ActionRunLoad extends React.Component { /** * Send a post to the metric/runload api to start a new load test. * An optional description parameter can be provided. - * @param {string} desc + * @param {string} desc */ // eslint-disable-next-line class-methods-use-this async requestRunLoad(desc) { @@ -128,7 +149,6 @@ class ActionRunLoad extends React.Component { } async handleCancelLoad() { - this.setState({ inlineTextLabel: "Cancelling..." }); try { const response = await fetch(`${AppConstants.API_SERVER}/api/v1/projects/${this.props.projectID}/loadtest/cancel`, { @@ -136,9 +156,9 @@ class ActionRunLoad extends React.Component { headers: { "Content-Type": "application/json" } }); const reply = await response; - this.setState({ inlineTextLabel: "Cancelled" }); + console.error("Cancel accepted") } catch (err) { - this.setState({ inlineTextLabel: "Cancel failed" }); + console.error("Cancel failed:",err); } } @@ -152,7 +172,7 @@ class ActionRunLoad extends React.Component { showStartTestNotificationFail(err) { this.setState( - { showNotificationRunTestFail: true, notificationError: err }, + { showNotificationRunTestFail: true, notificationError: err, loadRunStatus: { status: '' } }, () => setTimeout(() => this.setState({ showNotificationRunTestFail: false }), 5000) ); } @@ -177,10 +197,10 @@ class ActionRunLoad extends React.Component { render() { const { showNotificationRunTestFail, loadRunStatus, inlineTextLabel, timeRemaining } = this.state; - let loadRunPreparing = loadRunStatus.status === 'preparing'; - let loadRunStarting = loadRunStatus.status === 'starting'; - let loadRunActive = loadRunStatus.status === 'started'; - let loadRunSuccess = loadRunStatus.status === 'completed'; + + let loadRunCompleted = loadRunStatus.status === 'completed'; + const options = ['preparing', 'starting', 'started', 'completed', 'requesting', 'requested', 'cancelling', 'cancelled'] + const showBusy = options.includes(loadRunStatus.status) let inlineTextLabelFormatted = (timeRemaining !== 0) ? `${inlineTextLabel}${timeRemaining}` : `${inlineTextLabel}` @@ -189,10 +209,10 @@ class ActionRunLoad extends React.Component {
{ - (loadRunActive || loadRunSuccess || loadRunPreparing || loadRunStarting) ? ( + (showBusy) ? (
- +
@@ -228,7 +248,7 @@ const ActionRunLoadWithSocket = props => ( ActionRunLoad.propTypes = { projectID: PropTypes.string.isRequired, small: PropTypes.bool, // show small button - kind: PropTypes.string // button kind eg: 'ghost' + kind: PropTypes.string // button kind eg: 'ghost' } diff --git a/src/pfe/portal/modules/LoadRunner.js b/src/pfe/portal/modules/LoadRunner.js index 3efbf2b7c..7e5885734 100644 --- a/src/pfe/portal/modules/LoadRunner.js +++ b/src/pfe/portal/modules/LoadRunner.js @@ -425,6 +425,7 @@ module.exports = class LoadRunner { */ this.socket.on('disconnect', () => { log.info('Loadrunner has disconnected') + this.project.loadInProgress = false; // If this.up is false we're already trying to reconnect if (this.up) { // socket.io-client will automatically reconnect and trigger the connect event. diff --git a/src/pfe/portal/modules/User.js b/src/pfe/portal/modules/User.js index f7bce2256..7eae5de2e 100644 --- a/src/pfe/portal/modules/User.js +++ b/src/pfe/portal/modules/User.js @@ -154,10 +154,6 @@ module.exports = class User { */ async runLoad(project, description) { log.debug("runLoad: project " + project.projectID + " loadInProgress=" + project.loadInProgress); - // If load in progress, throw an error - if (project.loadInProgress) { - throw new LoadRunError("RUN_IN_PROGRESS", `For project ${project.projectID}`); - } project.loadInProgress = true; try { let config = await project.getLoadTestConfig(); @@ -176,12 +172,11 @@ module.exports = class User { let url = projectProtocol + projectHost + ":" + projectPort + config.path; config.url = url; project.loadConfig = config; - log.info(`Running load for project: ${project.projectID} config: ${JSON.stringify(config)}`); + log.info(`Requesting load on project: ${project.projectID} config: ${JSON.stringify(config)}`); const runLoadResp = await this.loadRunner.runLoad(config, project, description); return runLoadResp; } catch (err) { // Reset run load flag and config in the project, and re-throw the error - project.loadInProgress = false; project.loadConfig = null; throw err; } @@ -192,15 +187,15 @@ module.exports = class User { */ async cancelLoad(project) { log.debug("cancelLoad: project " + project.projectID + " loadInProgress=" + project.loadInProgress); - + this.uiSocket.emit('runloadStatusChanged', { projectID: project.projectID, status: 'cancelling' }); if (project.loadInProgress) { project.loadInProgress = false; log.debug("Cancelling load for config: " + JSON.stringify(project.loadConfig)); - this.uiSocket.emit('runloadStatusChanged', { projectID: project.projectID, status: 'cancelling' }); let cancelLoadResp = await this.loadRunner.cancelRunLoad(project.loadConfig); this.uiSocket.emit('runloadStatusChanged', { projectID: project.projectID, status: 'cancelled' }); return cancelLoadResp; } + this.uiSocket.emit('runloadStatusChanged', { projectID: project.projectID, status: 'cancelled' }); throw new LoadRunError("NO_RUN_IN_PROGRESS", `For project ${project.projectID}`); } diff --git a/src/pfe/portal/routes/projects/loadtest.route.js b/src/pfe/portal/routes/projects/loadtest.route.js index 40cf76fbb..30fbd69dd 100644 --- a/src/pfe/portal/routes/projects/loadtest.route.js +++ b/src/pfe/portal/routes/projects/loadtest.route.js @@ -27,7 +27,7 @@ const log = new Logger(__filename); * 202 on success, 404 if project id does not exist, 400 if options are invalid or a run * is already in progress, 500 if error */ -router.post('/api/v1/projects/:id/loadtest', validateReq, async function(req,res){ +router.post('/api/v1/projects/:id/loadtest', validateReq, function(req,res){ const user = req.cw_user; const projectID = req.sanitizeParams('id'); let project = user.projectList.retrieveProject(projectID); @@ -46,20 +46,23 @@ router.post('/api/v1/projects/:id/loadtest', validateReq, async function(req,res } try { - let runLoadResp = await user.runLoad(project, description); - // Response logic completed in ..docker/loadrunner/server.js - res.status(runLoadResp.statusCode).send(runLoadResp.body); - } catch(err) { - log.error(err); - if (err.code == LoadRunError.RUN_IN_PROGRESS) { - res.status(409).send(err.info); + log.info(`LoadTest route: loadInProgres = ${project.loadInProgress}`); + if (project.loadInProgress == undefined || !project.loadInProgress) { + user.runLoad(project, description); + res.status(202).send(""); } else { - res.status(500).send(err.info || err); + const err = new LoadRunError("RUN_IN_PROGRESS", `For project ${project.projectID}`); + res.status(409).send(err.info || err); } + return; + } catch(err) { + log.error(err); + res.status(500).send(err.info || err); } } }); + /** * API function to cancel load against a given project * @param req, the request from the UI containing project id