Skip to content
Snippets Groups Projects
Commit c25572a5 authored by Sandro Weber's avatar Sandro Weber Committed by Antoine Detailleur
Browse files

stop experiment calls ok

parent 525004b2
No related branches found
No related tags found
No related merge requests found
......@@ -19,6 +19,7 @@ export default class ExperimentListElement extends React.Component {
super(props);
this.state = {};
//TODO: put in service?
this.canLaunchExperiment = (this.props.experiment.private && this.props.experiment.owned) ||
!this.props.experiment.private;
this.launchButtonTitle = '';
......
.simulations-details-wrapper {}
.simulations-details-wrapper {
width: 640px;
}
.table-header {
font-weight: bold;
padding-bottom: 5px;
border-bottom: 2px solid lightgray;
}
.table-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
gap: 10px;
grid-template-columns: repeat(5, 120px);
padding-top: 10px;
}
.table-column-last {
......
......@@ -2,110 +2,74 @@ import React from 'react';
import timeDDHHMMSS from '../../utility/time-filter.js';
import { EXPERIMENT_STATE } from '../../services/experiments/experiment-constants.js';
import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service.js';
import './simulation-details.css';
/*<div className="table-wrapper"
uib-collapse="!config.canLaunchExperiments ||
(pageState.selected != exp.id)||(!pageState.showJoin && !running) || !exp.joinableServers.length">
<table className="table">
<thead>
<tr>
<th>Server</th>
<th>Creator</th>
<th>Uptime</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{this.props.simulations.map(simulation => {
return (
<tr>
<td>{simulation.server}</td>
<td>{simulation.owner}</td>
<td className="monospace-text">{timeDDHHMMSS(simulation.uptime)}</td>
<td>{simulation.runningSimulation.state}</td>
<td>
{// Join button enabled provided simulation state is consistent}
<button analytics-on analytics-event="Join" analytics-category="Experiment"
ng-click="(simulation.runningSimulation.state === STATE.CREATED) ||
simulation.stopping || joinExperiment(simulation, exp);"
type="button" className="btn btn-default"
ng-disabled="(simulation.runningSimulation.state === STATE.CREATED) ||
simulation.stopping">
Join »</button>
{// Stop button enabled provided simulation state is consistent}
<button analytics-on analytics-event="Stop" analytics-category="Experiment"
ng-click="stopSimulation(simulation, exp);"
type="button" className="btn btn-default"
ng-if="canStopSimulation(simulation)"
ng-disabled="simulation.stopping">
<i className="fa fa-spinner fa-spin" ng-if="simulation.stopping"></i> Stop
</button>
{//No edit rights: stop button disabled }
<button type="button" className="btn btn-default disabled enable-tooltip"
title="Sorry, you don't have sufficient rights to stop the simulation."
ng-if="!canStopSimulation(simulation)"> Stop
</button>
</td>
</tr>
);
}
)}
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td>
{// Stop all button enabled provided simulation state is consistent}
<button analytics-on analytics-event="Stop All" analytics-category="Experiment"
ng-click="stopAllthis.props.simulationsations(exp)"
type="button" className="btn btn-default" ng-if="canStopAllSimulations(exp)"
ng-disabled="!canStopAllSimulations(exp)">
<i className="fa fa-spinner fa-spin" ng-if=""></i> Stop All
</button>
{// No edit rights: stop all button disabled }
<button type="button" className="btn btn-default disabled enable-tooltip"
title="Sorry, you don't have sufficient rights to stop the this.props.simulationsations."
ng-if="!canStopAllSimulations(exp)"> Stop All
</button>
</td>
</tr>
</tbody>
</table>
</div>
*/
export default class SimulationDetails extends React.Component {
constructor() {
super();
this.state = {
simUptimes: [],
titleButtonStop: ''
};
}
componentDidMount() {
this.updateSimUptimes();
this.intervalUpdateSimUptimes = setInterval(() => {
this.updateSimUptimes();
}, 1000);
}
componentWillUnmount() {
this.intervalUpdateSimUptimes && clearInterval(this.intervalUpdateSimUptimes);
}
isJoinDisabled(simulation) {
return simulation.runningSimulation.state === EXPERIMENT_STATE.CREATED ||
simulation.stopping;
}
isStopDisabled() {
isStopDisabled(simulation) {
let disabled = simulation.stopping;
if (disabled) {
this.titleButtonStop = 'Sorry, you don\'t have sufficient rights to stop the simulation.';
}
else {
this.titleButtonStop = '';
}
return disabled;
}
updateSimUptimes() {
this.setState({
simUptimes: this.props.simulations.map(sim => {
return (Date.now() - Date.parse(sim.runningSimulation.creationDate)) / 1000;
})
});
}
render() {
console.info(this.props.simulations);
//console.info(this.props.simulations);
return (
<div className='simulations-details-wrapper'>
<div className='table-row'>
<div className='table-header'>Server</div>
<div className='table-header'>Creator</div>
<div className='table-header'>Uptime</div>
<div className='table-header'>Status</div>
<div className='table-header'>Actions</div>
<div className='table-row table-header'>
<div>Server</div>
<div>Creator</div>
<div>Uptime</div>
<div>Status</div>
<div>Actions</div>
</div>
{this.props.simulations.map(simulation => {
{this.props.simulations.map((simulation, index) => {
return (
<div key={simulation.runningSimulation.simulationID} className='table-row'>
<div>{simulation.server}</div>
<div>{simulation.runningSimulation.owner}</div>
<div>{timeDDHHMMSS(simulation.uptime)}</div>
<div>{timeDDHHMMSS(this.state.simUptimes[index])}</div>
<div>{simulation.runningSimulation.state}</div>
<div>
{/* Join button enabled provided simulation state is consistent */}
......@@ -118,17 +82,13 @@ export default class SimulationDetails extends React.Component {
</button>
{/* Stop button enabled provided simulation state is consistent */}
<button analytics-on analytics-event="Stop" analytics-category="Experiment"
ng-click="stopSimulation(simulation, exp);"
onClick={() => ExperimentExecutionService.instance.stopExperiment(simulation)}
type="button" className="btn btn-default"
ng-if="canStopSimulation(simulation)"
ng-disabled="simulation.stopping">
disabled={this.isStopDisabled(simulation)}
title={this.state.titleButtonStop}>
<i className="fa fa-spinner fa-spin" ng-if="simulation.stopping"></i> Stop
</button>
{/* No edit rights: stop button disabled */}
<button type="button" className="btn btn-default disabled enable-tooltip"
title="Sorry, you don't have sufficient rights to stop the simulation."
ng-if="!canStopSimulation(simulation)"> Stop
</button>
</div>
</div>
);
......
......@@ -20,7 +20,12 @@ class ErrorHandlerService {
}
displayServerHTTPError(error) {
//TODO: needs proper implementation
//TODO: needs proper UI implementation
console.error(error);
}
onErrorSimulationUpdate(error) {
//TODO: needs proper UI implementation
console.error(error);
}
......
......@@ -3,6 +3,7 @@ import _ from 'lodash';
import NrpAnalyticsService from '../../nrp-analytics-service.js';
import ExperimentServerService from './experiment-server-service.js';
import { HttpService } from '../../http-service.js';
import { EXPERIMENT_STATE } from '../experiment-constants.js';
let _instance = null;
const SINGLETON_ENFORCER = Symbol();
......@@ -16,6 +17,8 @@ class ExperimentExecutionService extends HttpService {
if (enforcer !== SINGLETON_ENFORCER) {
throw new Error('Use ' + this.constructor.name + '.instance');
}
this.stoppingExperiments = [];
}
static get instance() {
......@@ -47,10 +50,10 @@ class ExperimentExecutionService extends HttpService {
ExperimentExecutionService.instance.emit(ExperimentExecutionService.EVENTS.START_EXPERIMENT, experiment);
let fatalErrorOccurred = false,
serversToTry = experiment.devServer
? [experiment.devServer]
: ExperimentServerService.instance.getServerAvailability(true).map(s => s.id);
let fatalErrorOccurred = false;
let serversToTry = experiment.devServer
? [experiment.devServer]
: ExperimentServerService.instance.getServerAvailability(true).map(s => s.id);
let brainProcesses = launchSingleMode ? 1 : experiment.configuration.brainProcesses;
......@@ -59,7 +62,7 @@ class ExperimentExecutionService extends HttpService {
console.info(msg);
};
let launchInNextServer = async () => {
let launchOnNextServer = async () => {
let nextServer = serversToTry.splice(0, 1);
if (fatalErrorOccurred || !nextServer.length) {
//no more servers to retry, we have failed to start experiment
......@@ -85,11 +88,11 @@ class ExperimentExecutionService extends HttpService {
}
fatalErrorOccurred = fatalErrorOccurred || failure.isFatal;
return launchInNextServer();
return launchOnNextServer();
});
};
return launchInNextServer();
return launchOnNextServer();
};
/**
......@@ -155,30 +158,76 @@ class ExperimentExecutionService extends HttpService {
ExperimentServerService.instance.initConfigFiles(serverURL, simulation.simulationID)
.then(() => {
ExperimentExecutionService.instance.emit(ExperimentExecutionService.EVENTS.START_EXPERIMENT, undefined);
resolve(
'esv-private/experiment-view/' +
server +
'/' +
experimentID +
'/' +
privateExperiment +
'/' +
simulation.simulationID
);
let simulationURL = 'esv-private/experiment-view/' + server + '/' + experimentID + '/' +
privateExperiment + '/' + simulation.simulationID;
resolve(simulationURL);
})
.catch((err) => {
reject(err);
.catch(reject);
})
.catch(reject);
});
};
stopExperiment(simulation) {
return new Promise((resolve, reject) => {
simulation.stopping = true;
if (!this.stoppingExperiments[simulation.server]) {
this.stoppingExperiments[simulation.server] = {};
}
this.stoppingExperiments[simulation.server][
simulation.runningSimulation.simulationID
] = true;
//TODO: re-implement
/*this.storageServer.logActivity('simulation_stop', {
simulationID: simulation.runningSimulation.experimentID
});*/
ExperimentServerService.instance
.getServerConfig(simulation.server)
.then((serverConfig) => {
let serverURL = serverConfig.gzweb['nrp-services'];
let simulationID = simulation.runningSimulation.simulationID;
function updateSimulationState(state) {
/*eslint-disable camelcase*/
return ExperimentServerService.instance.updateSimulationState(
serverURL,
simulationID,
{ state: state }
);
}
return ExperimentServerService.instance
.getSimulationState(serverURL, simulationID)
.then((data) => {
if (!data || !data.state) {
return Promise.reject();
}
switch (data.state) {
case EXPERIMENT_STATE.CREATED: //CREATED --(initialize)--> PAUSED --(stop)--> STOPPED
return updateSimulationState(EXPERIMENT_STATE.INITIALIZED).then(
_.partial(updateSimulationState, EXPERIMENT_STATE.STOPPED)
);
case EXPERIMENT_STATE.STARTED: //STARTED --(stop)--> STOPPED
case EXPERIMENT_STATE.PAUSED: //PAUSED --(stop)--> STOPPED
case EXPERIMENT_STATE.HALTED: //HALTED --(stop)--> FAILED
return updateSimulationState(EXPERIMENT_STATE.STOPPED);
default:
return Promise.reject();
}
});
/*eslint-enable camelcase*/
})
.catch((err) => {
reject(err);
});
.then(resolve)
.catch(reject);
});
};
}
ExperimentExecutionService.EVENTS = Object.freeze({
START_EXPERIMENT: 'START_EXPERIMENT'
START_EXPERIMENT: 'START_EXPERIMENT',
STOP_EXPERIMENT: 'STOP_EXPERIMENT'
});
export default ExperimentExecutionService;
import _ from 'lodash';
import {Subject, timer}from 'rxjs';
import { Subject, timer } from 'rxjs';
import { switchMap, filter, map, multicast } from 'rxjs/operators';
import ErrorHandlerService from '../../error-handler-service.js';
......@@ -19,6 +19,7 @@ const SINGLETON_ENFORCER = Symbol();
let rosConnections = new Map();
const SLURM_MONITOR_POLL_INTERVAL = 5000;
const POLL_INTERVAL_SERVER_AVAILABILITY = 3000;
const CHECK_SIMULATION_READY_INTERVAL = 1000;
let clusterAvailability = { free: 'N/A', total: 'N/A' };
/**
......@@ -77,7 +78,7 @@ class ExperimentServerService extends HttpService {
);
this.getServerAvailability(true);
this.timerPollServerAvailability = setInterval(
this.intervalGetServerAvailability = setInterval(
() => {
this.getServerAvailability(true);
},
......@@ -90,7 +91,7 @@ class ExperimentServerService extends HttpService {
*/
stopUpdates() {
this.clusterAvailabilitySubscription && this.clusterAvailabilitySubscription.unsubscribe();
this.timerPollServerAvailability && clearInterval(this.timerPollServerAvailability);
this.intervalGetServerAvailability && clearInterval(this.intervalGetServerAvailability);
}
/**
......@@ -101,6 +102,11 @@ class ExperimentServerService extends HttpService {
return clusterAvailability;
}
/**
* Return a list of available servers for starting simulations.
* @param {boolean} forceUpdate force an update
* @returns {Array} A list of available servers.
*/
getServerAvailability(forceUpdate = false) {
if (!this.availableServers || forceUpdate) {
let update = async () => {
......@@ -184,7 +190,7 @@ class ExperimentServerService extends HttpService {
verifySimulation();
}
}).catch(reject);
}, 1000);
}, CHECK_SIMULATION_READY_INTERVAL);
};
verifySimulation();
......@@ -214,7 +220,7 @@ class ExperimentServerService extends HttpService {
rosConnection,
config['ros-topics'].status
);
rosConnections.set(rosbridgeWebsocket, {rosConnection, statusListener});
rosConnections.set(rosbridgeWebsocket, { rosConnection, statusListener });
statusListener.subscribe((data) => {
let message = JSON.parse(data.data);
......@@ -232,6 +238,28 @@ class ExperimentServerService extends HttpService {
}
});
};
async getSimulationState(serverURL, simulationID) {
let url = serverURL + '/simulation/' + simulationID + '/state';
try {
let response = await (await this.httpRequestGET(url)).json();
return response;
}
catch (error) {
ErrorHandlerService.instance.displayServerHTTPError(error);
}
}
async updateSimulationState(serverURL, simulationID, state) {
let url = serverURL + '/simulation/' + simulationID + '/state';
try {
let response = await this.httpRequestPUT(url, JSON.stringify(state));
return response;
}
catch (error) {
ErrorHandlerService.instance.onErrorSimulationUpdate(error);
}
}
}
ExperimentServerService.EVENTS = Object.freeze({
......
......@@ -89,12 +89,12 @@ export class HttpService extends EventEmitter {
* Perform a PUT http request to a url
* @param url - the url to perform the request
*/
httpRequestPUT = async (url) => {
httpRequestPUT = async (url, data) => {
// copy to avoid messing up the options object in case we need to reuse it
const { ...putOptions } = this.options;
putOptions.method = 'PUT';
return this.performRequest(url, putOptions);
return this.performRequest(url, putOptions, data);
};
/**
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment