Skip to content

Commit

Permalink
Merge pull request #25 from SeQuenC-Consortium/feature/4-enable-on-de…
Browse files Browse the repository at this point in the history
…mand-deployment-transformation

Feature/4 enable on demand deployment transformation
  • Loading branch information
wiomoc authored Sep 11, 2023
2 parents 12ee2ac + abfb5bf commit 892bada
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 25 deletions.
57 changes: 51 additions & 6 deletions components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import React from 'react';
import React, {Fragment, useState} from 'react';
import NotificationHandler from './notifications/NotificationHandler';
import {deployWorkflowToCamunda} from '../util/IoUtilities';
import {getCamundaEndpoint} from '../config/EditorConfigManager';
import {getRootProcess} from '../util/ModellingUtilities';
import {getServiceTasksToDeploy} from '../../extensions/opentosca/deployment/DeploymentUtils';
import {getModeler} from '../ModelerHandler';
import OnDemandDeploymentModal from './OnDemandDeploymentModal';
import {startOnDemandReplacementProcess} from "../../extensions/opentosca/replacement/OnDemandTransformator";

const defaultState = {
windowOpenOnDemandDeployment: false,
};

/**
* React button for starting the deployment of the workflow.
Expand All @@ -12,13 +20,37 @@ import {getRootProcess} from '../util/ModellingUtilities';
* @constructor
*/
export default function DeploymentButton(props) {
const [windowOpenOnDemandDeployment, setWindowOpenOnDemandDeployment] = useState(false);

const {modeler} = props;


/**
* Handle the result of a close operation on the tramfpr,atopm
*
* @param result the result from the close operation
*/
async function handleOnDemandDeployment(result) {
console.log(result);
if (result && result.hasOwnProperty('onDemand')) {
// get XML of the current workflow
let xml = (await modeler.saveXML({format: true})).xml;
console.log("XML", xml)
if (result.onDemand === true) {
xml = await startOnDemandReplacementProcess(xml);
}
// deploy in any case
deploy(xml);
}
// handle cancellation (don't deploy)
setWindowOpenOnDemandDeployment(false);

}

/**
* Deploy the current workflow to the Camunda engine
*/
async function deploy() {
async function deploy(xml) {

NotificationHandler.getInstance().displayNotification({
title: 'Deployment started',
Expand All @@ -27,7 +59,6 @@ export default function DeploymentButton(props) {

// get XML of the current workflow
const rootElement = getRootProcess(modeler.getDefinitions());
const xml = (await modeler.saveXML({format: true})).xml;

// check if there are views defined for the modeler and include them in the deployment
let viewsDict = {};
Expand Down Expand Up @@ -56,12 +87,26 @@ export default function DeploymentButton(props) {
}
}

async function onClick() {
let csarsToDeploy = getServiceTasksToDeploy(getRootProcess(getModeler().getDefinitions()));
if (csarsToDeploy.length > 0) {
setWindowOpenOnDemandDeployment(true);
} else {
deploy((await modeler.saveXML({format: true})).xml);
}
}

return (
<>
<Fragment>
<button type="button" className="qwm-toolbar-btn" title="Deploy the current workflow to a workflow engine"
onClick={() => deploy()}>
onClick={() => onClick()}>
<span className="qwm-workflow-deployment-btn"><span className="qwm-indent">Deploy Workflow</span></span>
</button>
</>
{windowOpenOnDemandDeployment && (
<OnDemandDeploymentModal
onClose={(e) => handleOnDemandDeployment(e)}
/>
)}
</Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2023 Institute of Architecture of Application Systems -
* University of Stuttgart
*
* This program and the accompanying materials are made available under the
* terms the Apache Software License 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: Apache-2.0
*/

/* eslint-disable no-unused-vars */
import React from 'react';

// polyfill upcoming structural components
import Modal from './modal/Modal';

const Title = Modal.Title || (({children}) => <h2>{children}</h2>);
const Footer = Modal.Footer || (({children}) => <div>{children}</div>);

export default function OnDemandDeploymentModal({onClose}) {

const onOnDemand = (value) => onClose({
onDemand: value,
});

return <Modal onClose={onClose}>

<Title>
Enable On Demand Service Deployment?
</Title>
<Footer>
<div id="deploymentButtons">
<button type="button" className="qwm-btn qwm-btn-primary" onClick={() => onOnDemand(true)}>Yes</button>
<button type="button" className="qwm-btn qwm-btn-secondary" onClick={() => onOnDemand(false)}>No</button>
</div>
</Footer>
</Modal>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as config from "../framework-config/config-manager";

const QUANTME_NAMESPACE_PULL_ENCODED = encodeURIComponent(encodeURIComponent('http://quantil.org/quantme/pull'));
const QUANTME_NAMESPACE_PUSH_ENCODED = encodeURIComponent(encodeURIComponent('http://quantil.org/quantme/push'));
Expand Down Expand Up @@ -48,9 +49,9 @@ export function getBindingType(serviceTask) {
* @param modeling the modeling element to adapt properties of the workflow elements
* @return {{success: boolean}} true if binding is successful, false otherwise
*/
export function bindUsingPull(topicName, serviceTaskId, elementRegistry, modeling) {
export function bindUsingPull(csar, serviceTaskId, elementRegistry, modeling) {

if (topicName === undefined || serviceTaskId === undefined || elementRegistry === undefined || modeling === undefined) {
if (csar.topicName === undefined || serviceTaskId === undefined || elementRegistry === undefined || modeling === undefined) {
console.error('Topic name, service task id, element registry, and modeling required for binding using pull!');
return { success: false };
}
Expand All @@ -62,9 +63,20 @@ export function bindUsingPull(topicName, serviceTaskId, elementRegistry, modelin
return { success: false };
}

let deploymentModelUrl = serviceTask.businessObject.get('opentosca:deploymentModelUrl');
if (deploymentModelUrl.startsWith('{{ wineryEndpoint }}')) {
deploymentModelUrl = deploymentModelUrl.replace('{{ wineryEndpoint }}', config.getWineryEndpoint());
}

// remove deployment model URL and set topic
modeling.updateProperties(serviceTask, { 'deploymentModelUrl': undefined, type: 'external', topic: topicName });
return { success: true };

modeling.updateProperties(serviceTask, {
'opentosca:deploymentModelUrl': deploymentModelUrl,
'opentosca:deploymentBuildPlanInstanceUrl': csar.buildPlanUrl,
type: 'external',
topic: csar.topicName
});
return {success: true};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ function getCSARName(serviceTask) {
* @param element the element to check
* @return {*|boolean} true if the element is a ServiceTask and has an assigned deployment model, false otherwise
*/
function isDeployableServiceTask(element) {
export function isDeployableServiceTask(element) {
return element.$type && element.$type === 'bpmn:ServiceTask' && element.deploymentModelUrl && getBindingType(element) !== undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export async function createServiceInstance(csar, camundaEngineEndpoint) {
}


function makeId(length) {
export function makeId(length) {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* Copyright (c) 2023 Institute of Architecture of Application Systems -
* University of Stuttgart
*
* This program and the accompanying materials are made available under the
* terms the Apache Software License 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: Apache-2.0
*/

import {createTempModelerFromXml} from '../../../editor/ModelerHandler';
import {getXml} from '../../../editor/util/IoUtilities';
import {isDeployableServiceTask} from "../deployment/DeploymentUtils";
import * as config from "../framework-config/config-manager";
import {makeId} from "../deployment/OpenTOSCAUtils";
import {getCamundaEndpoint} from "../../../editor/config/EditorConfigManager";


function createDeploymentScript(params) {
return `
var params = ${JSON.stringify(params)};
params.csarName = "ondemand_" + (Math.random().toString().substring(3));
function fetch(method, url, body) {
var resourceURL = new java.net.URL(url);
var urlConnection = resourceURL.openConnection();
urlConnection.setRequestMethod(method);
if (body) {
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/json");
var outputStream = urlConnection.getOutputStream()
var outputStreamWriter = new java.io.OutputStreamWriter(outputStream)
outputStreamWriter.write(body);
outputStreamWriter.flush();
outputStreamWriter.close();
outputStream.close();
}
var inputStream = new java.io.InputStreamReader(urlConnection
.getInputStream());
var bufferedReader = new java.io.BufferedReader(inputStream);
var inputLine = ""
var text = "";
var i = 5;
while ((inputLine = bufferedReader.readLine()) != null) {
text += inputLine
}
bufferedReader.close();
return text;
}
var createCsarResponse = fetch('POST', params.opentoscaEndpoint, JSON.stringify({
enrich: 'false',
name: params.csarName,
url: params.deploymentModelUrl
}))
var serviceTemplates = JSON.parse(fetch('GET', params.opentoscaEndpoint + "/" + params.csarName + ".csar/servicetemplates"))
var buildPlansUrl = serviceTemplates.service_templates[0]._links.self.href + '/buildplans'
var buildPlans = JSON.parse(fetch('GET', buildPlansUrl))
var buildPlanUrl = buildPlans.plans[0]._links.self.href
var inputParameters = JSON.parse(fetch('GET', buildPlanUrl)).input_parameters
for(var i = 0; i < inputParameters.length; i++) {
if(inputParameters[i].name === "camundaEndpoint") {
inputParameters[i].value = params.opentoscaEndpoint
} else if(inputParameters[i].name === "camundaTopic") {
inputParameters[i].value = params.camundaTopic
} else {
inputParameters[i].value = "null"
}
}
var createInstanceResponse = fetch('POST', buildPlanUrl + "/instances", JSON.stringify(inputParameters))
execution.setVariable(params.subprocessId + "_deploymentBuildPlanInstanceUrl", buildPlanUrl + "/instances/" + createInstanceResponse);`;
}

/**
* Initiate the replacement process for the QuantME tasks that are contained in the current process model
*
* @param xml the BPMN diagram in XML format
* @param currentQRMs the set of currently in the framework available QRMs
* @param endpointConfig endpoints of the services required for the dynamic hardware selection
*/
export async function startOnDemandReplacementProcess(xml) {
const modeler = await createTempModelerFromXml(xml);
const modeling = modeler.get('modeling');
const elementRegistry = modeler.get('elementRegistry');
const bpmnReplace = modeler.get('bpmnReplace');
const bpmnAutoResizeProvider = modeler.get('bpmnAutoResizeProvider');
bpmnAutoResizeProvider.canResize = () => false;

const serviceTasks = elementRegistry.filter(({businessObject}) => isDeployableServiceTask(businessObject));

for (const serviceTask of serviceTasks) {
let deploymentModelUrl = serviceTask.businessObject.get('opentosca:deploymentModelUrl');
if (deploymentModelUrl.startsWith('{{ wineryEndpoint }}')) {
deploymentModelUrl = deploymentModelUrl.replace('{{ wineryEndpoint }}', config.getWineryEndpoint());
}
let subProcess = bpmnReplace.replaceElement(serviceTask, {type: 'bpmn:SubProcess'});

subProcess.businessObject.set("opentosca:onDemandDeployment", true);
subProcess.businessObject.set("opentosca:deploymentModelUrl", deploymentModelUrl);

const startEvent = modeling.createShape({
type: 'bpmn:StartEvent'
}, {x: 200, y: 200}, subProcess);

let topicName = makeId(12);
const serviceTask1 = modeling.appendShape(startEvent, {
type: 'bpmn:ScriptTask',
}, {x: 400, y: 200});
serviceTask1.businessObject.set("scriptFormat", "javascript");
serviceTask1.businessObject.set("script", createDeploymentScript(
{
opentoscaEndpoint: config.getOpenTOSCAEndpoint(),
deploymentModelUrl: deploymentModelUrl,
subprocessId: subProcess.id,
camundaTopic: topicName,
camundaEndpoint: getCamundaEndpoint()
}
));


const serviceTask2 = modeling.appendShape(serviceTask1, {
type: 'bpmn:ServiceTask'
}, {x: 600, y: 200}, subProcess);

serviceTask2.businessObject.set("camunda:type", "external");
serviceTask2.businessObject.set("camunda:topic", topicName);
}

// layout diagram after successful transformation
let updatedXml = await getXml(modeler);
console.log(updatedXml);
return updatedXml;
}
Loading

0 comments on commit 892bada

Please sign in to comment.