From 04916aa9d6fb96dea1ea9dfbf47e7b9151a286c6 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 22 Feb 2021 17:41:28 +0100 Subject: [PATCH] WIP: rework AMQP commands --- src/ic/ic-action.js | 180 ++++++++++++++++++++++++++++++++++++- src/ic/ic-store.js | 31 ++++++- src/ic/ics-data-manager.js | 69 ++++++++++++-- src/ic/ics.js | 28 +++--- src/scenario/scenario.js | 74 +++------------ 5 files changed, 291 insertions(+), 91 deletions(-) diff --git a/src/ic/ic-action.js b/src/ic/ic-action.js index b0333b8..b187c5e 100644 --- a/src/ic/ic-action.js +++ b/src/ic/ic-action.js @@ -17,6 +17,7 @@ import React from 'react'; import { Button, DropdownButton, Dropdown, InputGroup, FormControl } from 'react-bootstrap'; +import AppDispatcher from "../common/app-dispatcher"; class ICAction extends React.Component { constructor(props) { @@ -47,9 +48,178 @@ class ICAction extends React.Component { }; } } + return null } + runAction(action, when) { + + console.log("configs", this.props.configs) + console.log("selectedConfigs", this.props.selectedConfigs) + console.log("ics", this.props.ics) + console.log("selectedICs", this.props.selectedICs) + console.log("action", action) + console.log("when", when) + + if (action.data.action === 'none') { + console.warn("No command selected. Nothing was sent."); + return; + } + + if (!this.props.hasConfigs){ + let newAction = {}; + newAction["action"] = action.data.action + newAction["when"] = when + + for (let index of this.props.selectedICs) { + let ic = this.props.ics[index]; + let icID = ic.id; + + /* VILLAScontroller protocol + see: https://villas.fein-aachen.org/doc/controller-protocol.html + + RESET SHUTDOWN + { + "action": "reset/shutdown/stop/pause/resume" + "when": "1234567" + } + + DELETE + { + "action": "delete" + "parameters":{ + "uuid": "uuid-of-the-manager-for-this-IC" + } + "when": "1234567" + } + + CREATE is not possible within ICAction (see add IC) + */ + + if (newAction.action === "delete"){ + // prepare parameters for delete incl. correct IC id + newAction["parameters"] = {}; + newAction.parameters["uuid"] = ic.id; + icID = ic.manager; // send delete action to manager of IC + } + + AppDispatcher.dispatch({ + type: 'ics/start-action', + icid: ic.id, + action: newAction, + result: null, + token: this.props.token + }); + + } // end for loop over selected ICs + } else { + + /*VILLAScontoller protocol + see: https://villas.fein-aachen.org/doc/controller-protocol.html + * + * STOP PAUSE RESUME + { + "action": "reset/shutdown/stop/pause/resume" + "when": "1234567" + } + * + * START + { + "action": "start" + "when": 1234567 + "parameters": { + Start parameters for this IC as configured in the component config + } + "model": { + "type": "url" + "url": "https://villas.k8s.eonerc.rwth-aachen.de/api/v2/files/{fileID}" where fileID is the model file configured in the component config + "token": "asessiontoken" + } + "results":{ + "type": "url" + "url" : "https://villas.k8s.eonerc.rwth-aachen.de/api/v2/results/{resultID}/file" where resultID is the ID of the result created for this run + "token": "asessiontoken" + } + } + * + * + * */ + + + let newActions = []; + for (let config of this.props.selectedConfigs) { + let newAction = {} + newAction["action"] = action.data.action + newAction["when"] = when + + // get IC for component config + let ic = null; + for (let component of this.props.ics) { + if (component.id === config.icID) { + ic = component; + } + } + + if (ic == null) { + continue; + } + + // the following is not required by the protocol; it is an internal help + newAction["icid"] = ic.id + + if (newAction.action === 'start') { + newAction["parameters"] = config.startParameters; + newAction["model"] = {} + + if (config.fileIDs.length > 0){ + newAction.model["type"] = "url" + newAction.model["token"] = this.props.token + // TODO do not default to the first file of the config + newAction.model["url"] = "/files/" + config.fileIDs[0].toString() + } + + newAction["results"] = {} + newAction.results["type"] = "url" + newAction.results["token"] = this.props.token + newAction.results["url"] = "/results/RESULTID/file" // RESULTID serves as placeholder and is replaced later + + } + + // add the new action + newActions.push(newAction); + console.log("New actions in loop", newAction, newActions) + + } // end for loop over selected configs + + + let newResult = {} + newResult["result"] = {} + if (action.data.action === 'start') { + + let configSnapshots = []; + // create config snapshots in case action is start + for (let config of this.props.selectedConfigs) { + let index = this.props.configs.indexOf(config) + configSnapshots.push(this.props.snapshotConfig(index)); + } + + // create new result for new run + newResult.result["description"] = "Placeholder for description" + newResult.result["scenarioID"] = this.props.selectedConfigs[0].scenarioID + newResult.result["configSnapshots"] = configSnapshots + } + + + console.log("Dispatching actions for configs", newActions, newResult) + AppDispatcher.dispatch({ + type: 'ics/start-action', + action: newActions, + result: newResult, + token: this.props.token + }); + } + } + setAction = id => { // search action for (let action of this.props.actions) { @@ -65,7 +235,13 @@ class ICAction extends React.Component { render() { - let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1" + let sendCommandDisabled = false; + if (!this.props.hasConfigs && this.props.selectedICs.length === 0 || this.state.selectedAction == null || this.state.selectedAction.id === "-1"){ + sendCommandDisabled = true; + } + if (this.props.hasConfigs && this.props.selectedConfigs.length === 0|| this.state.selectedAction == null || this.state.selectedAction.id === "-1"){ + sendCommandDisabled = true; + } let time = this.state.time.getFullYear().pad(4) + '-' + this.state.time.getMonth().pad(2) + '-' + @@ -98,7 +274,7 @@ class ICAction extends React.Component { + onClick={() => this.runAction(this.state.selectedAction, this.state.time)}>Run Select time for synced command execution ; diff --git a/src/ic/ic-store.js b/src/ic/ic-store.js index 7cf314a..c6d0810 100644 --- a/src/ic/ic-store.js +++ b/src/ic/ic-store.js @@ -66,16 +66,41 @@ class InfrastructureComponentStore extends ArrayStore { return state; case 'ics/start-action': - if (!Array.isArray(action.data)) - action.data = [ action.data ] + if (!Array.isArray(action.action)) + action.action = [ action.action ] - ICsDataManager.doActions(action.ic, action.data, action.token); + ICsDataManager.doActions(action.icid, action.action, action.token, action.result); return state; case 'ics/action-error': console.log(action.error); return state; + case 'ics/action-result-added': + + for (let a of action.actions){ + let icid = Object.assign({}, a.icid) + + if (a.results !== undefined && a.results != null){ + // adapt URL for newly created result ID + a.results.url = a.results.url.replace("RESULTID", action.data.result.id); + a.results.url = ICsDataManager.makeURL(a.results.url); + a.results.url = window.location.host + a.results.url; + } + if (a.model !== undefined && a.model != null) { + // adapt URL for model file + a.model.url = ICsDataManager.makeURL(a.model.url); + a.model.url = window.location.host + a.model.url; + } + delete a.icid + ICsDataManager.doActions(icid, [a], action.token) + } + return state; + + case 'ics/action-result-add-error': + console.log(action.error); + return state + case 'ics/get-status': ICsDataManager.getStatus(action.url, action.token, action.ic); return super.reduce(state, action); diff --git a/src/ic/ics-data-manager.js b/src/ic/ics-data-manager.js index 02d480f..a6a625d 100644 --- a/src/ic/ics-data-manager.js +++ b/src/ic/ics-data-manager.js @@ -24,24 +24,79 @@ class IcsDataManager extends RestDataManager { super('ic', '/ic'); } - doActions(ic, actions, token = null) { + doActions(icid, actions, token = null, result=null) { for (let action of actions) { if (action.when) // Send timestamp as Unix Timestamp action.when = Math.round(action.when.getTime() / 1000); } - RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), actions, token).then(response => { + if (icid !== undefined && icid != null) { + + console.log("doActions, icid:", icid) + // sending action to a specific IC via IC list + + RestAPI.post(this.makeURL(this.url + '/' + icid + '/action'), actions, token).then(response => { AppDispatcher.dispatch({ - type: 'ics/action-started', - data: response + type: 'ics/action-started', + data: response }); - }).catch(error => { + }).catch(error => { AppDispatcher.dispatch({ - type: 'ics/action-error', + type: 'ics/action-error', + error + }); + }); + } else { + // sending the same action to multiple ICs via scenario controls + + // distinguish between "start" action and any other + + if (actions[0].action !== "start"){ + for (let a of actions){ + console.log("doActions, a.icid:", a.icid) + icid = JSON.parse(JSON.stringify(a.icid)) + delete a.icid + console.log("doActions, icid:", icid) + // sending action to a specific IC via IC list + + RestAPI.post(this.makeURL(this.url + '/' + icid + '/action'), [a], token).then(response => { + AppDispatcher.dispatch({ + type: 'ics/action-started', + data: response + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ics/action-error', + error + }); + }); + + } + } else{ + // for start actions procedure is different + // first a result needs to be created, then the start actions can be sent + + RestAPI.post(this.makeURL( '/results'), result, token).then(response => { + AppDispatcher.dispatch({ + type: 'ics/action-result-added', + data: response, + actions: actions, + token: token, + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ics/action-result-add-error', error + }); }); - }); + + + } + + + + } } getStatus(url,token,ic){ diff --git a/src/ic/ics.js b/src/ic/ics.js index de10aaa..9c1f2db 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -240,18 +240,7 @@ class InfrastructureComponents extends Component { this.setState({ selectedICs: selectedICs }); } - runAction(action, when) { - for (let index of this.state.selectedICs) { - action.when = when; - AppDispatcher.dispatch({ - type: 'ics/start-action', - ic: this.state.ics[index], - data: action.data, - token: this.state.sessionToken, - }); - } - } static isICOutdated(component) { if (!component.stateUpdateAt) @@ -420,19 +409,19 @@ class InfrastructureComponents extends Component { modifier={(stateUpdateAt, component) => this.stateUpdateModifier(stateUpdateAt, component)} /> - {this.state.currentUser.role === "Admin" && editable ? + {this.state.currentUser.role === "Admin" ? !this.isExternalIC(index)} exportButton - deleteButton + deleteButton = {(index) => !this.isExternalIC(index)} onEdit={index => this.setState({editModal: true, modalIC: ics[index], modalIndex: index})} onExport={index => this.exportIC(index)} onDelete={index => this.setState({deleteModal: true, modalIC: ics[index], modalIndex: index})} /> : this.exportIC(index)} /> @@ -496,12 +485,15 @@ class InfrastructureComponents extends Component { {this.state.currentUser.role === "Admin" && this.state.numberOfExternalICs > 0 ?
this.runAction(action, when)} + hasConfigs = {false} + ics={this.state.ics} + selectedICs={this.state.selectedICs} + token={this.state.sessionToken} actions={[ {id: '-1', title: 'Action', data: {action: 'none'}}, {id: '0', title: 'Reset', data: {action: 'reset'}}, {id: '1', title: 'Shutdown', data: {action: 'shutdown'}}, + {id: '2', title: 'Delete', data: {action: 'delete'}} ]} />
diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 791e79f..cacb2db 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -302,6 +302,7 @@ class Scenario extends React.Component { } copyConfig(index) { + console.log("index", index, "copyConfig: ", this.state.configs[index]) let config = JSON.parse(JSON.stringify(this.state.configs[index])); let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10)))); @@ -394,60 +395,6 @@ class Scenario extends React.Component { } - runAction(action, when) { - if (action.data.action === 'none') { - console.warn("No command selected. Nothing was sent."); - return; - } - - let configs = []; - for (let index of this.state.selectedConfigs) { - // get IC for component config - let ic = null; - for (let component of this.state.ics) { - if (component.id === this.state.configs[index].icID) { - ic = component; - } - } - - if (ic == null) { - continue; - } - - if (action.data.action === 'start') { - configs.push(this.copyConfig(index)); - action.data.parameters = this.state.configs[index].startParameters; - } - - action.data.when = when; - - console.log("Sending action: ", action.data) - - AppDispatcher.dispatch({ - type: 'ics/start-action', - ic: ic, - data: action.data, - token: this.state.sessionToken - }); - } - - if (configs.length !== 0) { //create result (only if command was 'start') - let componentConfigs = {}; - componentConfigs["configs"] = configs; - let data = {}; - data["Description"] = "Run " + this.state.scenario.name; // default description, to be change by user later - data["ResultFileIDs"] = []; - data["scenarioID"] = this.state.scenario.id; - data["ConfigSnapshots"] = JSON.stringify(componentConfigs, null, 2); - AppDispatcher.dispatch({ - type: 'results/start-add', - data, - token: this.state.sessionToken, - }) - } - - }; - getICName(icID) { for (let ic of this.state.ics) { if (ic.id === icID) { @@ -710,7 +657,7 @@ class Scenario extends React.Component { } openResultConfigSnaphots(result) { - if (!result.configSnapshots || result.configSnapshots == "") { + if (result.configSnapshots === null || result.configSnapshots === undefined) { this.setState({ modalResultConfigs: {"configs": []}, modalResultConfigsIndex: result.id, @@ -881,7 +828,8 @@ class Scenario extends React.Component { this.usesExternalIC(index)} + // checkboxDisabled={(index) => this.usesExternalIC(index)} + checkboxDistabled={false} onChecked={(index, event) => this.onConfigChecked(index, event)} width='30' /> @@ -925,11 +873,15 @@ class Scenario extends React.Component { />
- {this.state.ExternalICInUse ? ( + {/*{this.state.ExternalICInUse ? (*/}
this.runAction(action, when)} + hasConfigs={true} + ics={this.state.ics} + configs={this.state.configs} + selectedConfigs = {this.state.selectedConfigs} + snapshotConfig = {(index) => this.copyConfig(index)} + token = {this.state.sessionToken} actions={[ { id: '-1', title: 'Action', data: { action: 'none' } }, { id: '0', title: 'Start', data: { action: 'start' } }, @@ -938,8 +890,8 @@ class Scenario extends React.Component { { id: '3', title: 'Resume', data: { action: 'resume' } } ]} />
- ) : (
) - } + {/*) : (
)*/} + {/*}*/} < div style={{ clear: 'both' }} />