From f49249a3b7a969569c1a45edebf9397c41e6b8fb Mon Sep 17 00:00:00 2001 From: Pierre-Yves Date: Thu, 6 Jul 2023 13:59:49 -0700 Subject: [PATCH] Loadtest (#26) Load test --- Dockerfile | 8 + README.md | 283 ++------ doc/docker/README.md | 10 + doc/flowscenario/README.md | 7 + doc/kubernetes/README.md | 7 + doc/scenarioreference/README.md | 176 +++++ doc/unitscenario/README.md | 48 ++ docker-processautomator/.env | 21 + docker-processautomator/README.md | 3 + docker-processautomator/connector-secrets.txt | 2 + .../docker-compose-automator.yaml | 60 ++ .../docker-compose-core.yaml | 164 +++++ k8s/README.md | 18 + k8s/pa-verification.yaml | 469 +++++++++++++ k8s/processautomator.yaml | 45 ++ pom.xml | 138 ++-- src/main/frontend/README.md | 70 ++ src/main/frontend/package.json | 38 ++ src/main/frontend/public/index.html | 43 ++ src/main/frontend/public/logo512.png | Bin 0 -> 9664 bytes src/main/frontend/src/App.css | 38 ++ src/main/frontend/src/App.js | 25 + src/main/frontend/src/App.test.js | 8 + src/main/frontend/src/index.css | 13 + src/main/frontend/src/index.js | 17 + src/main/frontend/src/logo.svg | 1 + src/main/frontend/src/reportWebVitals.js | 13 + src/main/frontend/src/setupTests.js | 5 + .../org/camunda/automator/AutomatorAPI.java | 67 +- .../org/camunda/automator/AutomatorCLI.java | 24 +- .../automator/bpmnengine/BpmnEngine.java | 157 +++-- .../bpmnengine/BpmnEngineConfiguration.java | 130 ---- .../BpmnEngineConfigurationInstance.java | 66 +- .../bpmnengine/BpmnEngineFactory.java | 28 +- .../camunda7/BpmnEngineCamunda7.java | 308 +++++++-- ...kCompleteJobExceptionHandlingStrategy.java | 41 ++ ...hmarkStartPiExceptionHandlingStrategy.java | 38 ++ .../camunda8/BpmnEngineCamunda8.java | 382 +++++++++-- .../camunda8/StatisticsCollector.java | 49 ++ .../refactoring/RefactoredCommandWrapper.java | 87 +++ .../bpmnengine/dummy/BpmnEngineDummy.java | 85 ++- .../configuration/ConfigurationBpmEngine.java | 297 +++++++++ .../ConfigurationServersEngine.java | 69 ++ .../configuration/ConfigurationStartup.java | 98 +++ .../automator/definition/Scenario.java | 65 +- .../definition/ScenarioDeployment.java | 5 +- .../definition/ScenarioExecution.java | 66 +- .../definition/ScenarioFlowControl.java | 51 ++ .../automator/definition/ScenarioStep.java | 125 +++- .../automator/definition/ScenarioTool.java | 2 +- .../definition/ScenarioVerification.java | 23 +- .../definition/ScenarioVerificationTask.java | 12 +- .../ScenarioVerificationVariable.java | 4 +- .../definition/ScenarioWarmingUp.java | 30 + .../automator/engine/AutomatorException.java | 2 +- .../automator/engine/RunParameters.java | 40 +- .../camunda/automator/engine/RunResult.java | 177 +++-- .../camunda/automator/engine/RunScenario.java | 74 ++- .../engine/RunScenarioExecution.java | 66 +- .../engine/RunScenarioVerification.java | 20 +- .../automator/engine/RunZeebeOperation.java | 32 + .../automator/engine/SchedulerExecution.java | 70 +- .../engine/flow/FixedBackoffSupplier.java | 23 + .../automator/engine/flow/RunObjectives.java | 298 +++++++++ .../engine/flow/RunScenarioFlowBasic.java | 86 +++ .../flow/RunScenarioFlowServiceTask.java | 275 ++++++++ .../flow/RunScenarioFlowStartEvent.java | 190 ++++++ .../engine/flow/RunScenarioFlows.java | 343 ++++++++++ .../engine/flow/RunScenarioWarmingUp.java | 244 +++++++ .../automator/services/AutomatorStartup.java | 178 +++++ .../automator/services/ServiceAccess.java | 21 + .../services/ServiceDataOperation.java | 4 +- .../services/dataoperation/DataOperation.java | 2 +- .../DataOperationGenerateList.java | 33 + .../dataoperation/DataOperationLoadFile.java | 13 +- .../DataOperationStringToDate.java | 18 +- src/main/resources/application.yaml | 65 +- src/main/resources/loadtest/C7SimpleTask.bpmn | 74 +++ src/main/resources/loadtest/C7SimpleTask.json | 54 ++ .../loadtest/DiscoverySeedExtraction.bpmn | 326 +++++++++ .../loadtest/DiscoverySeedExtraction.json | 122 ++++ src/main/resources/loadtest/Verification.bpmn | 617 ++++++++++++++++++ src/main/resources/loadtest/Verification.json | 123 ++++ ...eUserTask.java => TestSimpleUserTask.java} | 39 +- .../Complexprocess/ComplexProcess.md | 0 .../Complexprocess/ComplexProcess.png | Bin .../ComplexProcessAutormator.json | 4 +- .../Complexprocess/ComplexProcess_C7.bpmn | 0 .../Complexprocess/ComplexProcess_C8.bpmn | 0 .../paralleletask/ParalleleTask.bpmn | 0 .../AutomatorSimpleUserTask.json | 0 .../simpleusertask/SimpleUserTask-C8.bpmn | 0 .../simpleusertask/SimpleUserTask.bpmn | 0 93 files changed, 6624 insertions(+), 1048 deletions(-) create mode 100644 Dockerfile create mode 100644 doc/docker/README.md create mode 100644 doc/flowscenario/README.md create mode 100644 doc/kubernetes/README.md create mode 100644 doc/scenarioreference/README.md create mode 100644 doc/unitscenario/README.md create mode 100644 docker-processautomator/.env create mode 100644 docker-processautomator/README.md create mode 100644 docker-processautomator/connector-secrets.txt create mode 100644 docker-processautomator/docker-compose-automator.yaml create mode 100644 docker-processautomator/docker-compose-core.yaml create mode 100644 k8s/README.md create mode 100644 k8s/pa-verification.yaml create mode 100644 k8s/processautomator.yaml create mode 100644 src/main/frontend/README.md create mode 100644 src/main/frontend/package.json create mode 100644 src/main/frontend/public/index.html create mode 100644 src/main/frontend/public/logo512.png create mode 100644 src/main/frontend/src/App.css create mode 100644 src/main/frontend/src/App.js create mode 100644 src/main/frontend/src/App.test.js create mode 100644 src/main/frontend/src/index.css create mode 100644 src/main/frontend/src/index.js create mode 100644 src/main/frontend/src/logo.svg create mode 100644 src/main/frontend/src/reportWebVitals.js create mode 100644 src/main/frontend/src/setupTests.js delete mode 100644 src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfiguration.java create mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java create mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java create mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java create mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java create mode 100644 src/main/java/org/camunda/automator/configuration/ConfigurationBpmEngine.java create mode 100644 src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java create mode 100644 src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java create mode 100644 src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java create mode 100644 src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java create mode 100644 src/main/java/org/camunda/automator/engine/RunZeebeOperation.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/RunObjectives.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java create mode 100644 src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java create mode 100644 src/main/java/org/camunda/automator/services/AutomatorStartup.java create mode 100644 src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java create mode 100644 src/main/resources/loadtest/C7SimpleTask.bpmn create mode 100644 src/main/resources/loadtest/C7SimpleTask.json create mode 100644 src/main/resources/loadtest/DiscoverySeedExtraction.bpmn create mode 100644 src/main/resources/loadtest/DiscoverySeedExtraction.json create mode 100644 src/main/resources/loadtest/Verification.bpmn create mode 100644 src/main/resources/loadtest/Verification.json rename src/test/java/automatorapi/{SimpleUserTask.java => TestSimpleUserTask.java} (54%) rename src/test/{ressources => resources}/Complexprocess/ComplexProcess.md (100%) rename src/test/{ressources => resources}/Complexprocess/ComplexProcess.png (100%) rename src/test/{ressources => resources}/Complexprocess/ComplexProcessAutormator.json (96%) rename src/test/{ressources => resources}/Complexprocess/ComplexProcess_C7.bpmn (100%) rename src/test/{ressources => resources}/Complexprocess/ComplexProcess_C8.bpmn (100%) rename src/test/{ressources => resources}/paralleletask/ParalleleTask.bpmn (100%) rename src/test/{ressources => resources}/simpleusertask/AutomatorSimpleUserTask.json (100%) rename src/test/{ressources => resources}/simpleusertask/SimpleUserTask-C8.bpmn (100%) rename src/test/{ressources => resources}/simpleusertask/SimpleUserTask.bpmn (100%) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7d3d2d0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +# docker build -t zeebe-cherry-officepdf:1.0.0 . +FROM openjdk:17-alpine +EXPOSE 9081 +COPY target/process-execution-automator-*-exec.jar /app.jar +COPY src/main/resources /app/scenarii +COPY src/test/resources /app/scenarii +ENTRYPOINT ["java","-jar","/app.jar"] + diff --git a/README.md b/README.md index caca677..f3b4532 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +./reb # process-execution-automator Create scenarios to automate any execution of processes. Objectives are A unit test, load test, @@ -6,7 +7,7 @@ Camunda Engine and pilots the execution. It can connect to a Camunda 7 or a Camunda 8 server. -# Execute a process +## Execute a process From a scenario, Automator calls the Camunda Engine server (C7 or C8) and executes the different steps in the scenario. Let's take an example with this scenario: @@ -15,32 +16,32 @@ steps in the scenario. Let's take an example with this scenario: create a new process instance with variable "subscriptionLevel: "GOLD", "customerId": 14422 ```` -The process is created and processed by the Camunda Engine. The GetContext operation is executed by -the Camunda Engine, and, according to the information, the process instance moves to the task " -Review Level 1" -in the scenario, Automator waits for this user task. It will execute it and set "ReviewLevel2Needed" -to True. The Camunda Engine move the process instance to Review Level 2. In the scenario, Automator +The process is created and processed by the Camunda Engine. The `GetContext` operation is executed by +the Camunda Engine, and, according to the information, the process instance moves to the task `Review Level 1` +in the scenario, Automator waits for this user task. It will execute it and set `ReviewLevel2Needed` +to True. The Camunda Engine move the process instance to `Review Level 2`. In the scenario, Automator waits for this user task. It will execute it. The Camunda engine continues the execution. It -executes "Register Application", waits for the message, executes "Notify Applicant" and completes +executes `Register Application`, waits for the message, executes `Notify Applicant` and completes the process instance. -Another scenario can execute only "Review Level1" or no review at all. +Another scenario can execute only `Review Level1` or no review at all. What Automator do: * it creates a process instance with some specific value * it executes user tasks with some specific value * it can throw a BPMN Message +* simulate execute service task in Flow Scenario Automator do not -* execute service task +* execute Service task in unitscenario * It is not expected to throw BPMN Message in the flow: a real system is piloted by the Automator. * The goal of the Automator is not to simulate the execution, it is to pilot an execution on a real system, and to verify that the process reacts as expected. -# Requirement +## Requirement Automator needs to connect to a running platform, Camunda 7 or Camunda 8. Automator is not a process simulator. The running platform will execute all service tasks. @@ -51,233 +52,47 @@ A scenario can be executed on a Camunda 7 or a Camunda 8 server. Automator provi * a docker image * an API to be integrated into any other tools -# Different usages - -## Load test in the situation. - -It is possible to start multiple instances on multiple Scenarios. For example, it is possible to -say, "Start 2000 process instances with Scenario 1, 3000 instances with Scenario 2". The platform -will be overloaded, so it is possible to study if the configuration is acceptable. This is not a -performance test: the goal is not to generate all process instances as fast as possible but to -simulate real usage. It is possible in the scenario: -to specify a delay between each creation to specify a delay in each user task, to simulate a real -user to determine the number of users per task, which execute the "Task refresh" action to simulate -a pool of users - -## Verification - -![Process](doc/explanationProcess.png) - -in a CD/CI, you want to verify that a process follows the same behavior in the same performance -time. Running every day (or hours) or asking via an API call to replay a scenario is useful to -verify there is no difference. If the customer is 4555, do we still move the process instance to -Review Level 1"? The second verification is the performance. The scenario can record an expected -duration target (for example, 4 seconds to execute the Get Context service task. Does the execution -still at this time? - -## Coverage report - -Execute multiple scenarios to be sure that all the process is covered correctly. An "Execution -round" is a set of scenarios executed at the same time. At the end of the execution, a coverage test -can be performed. A CD/CI verification may be to check the scenario execution, the target time, and -the coverage. - -## Advance process instance for development - -During the development, you verify the task "Notify applicant". To test it in the situation, you -must have a process instance in the process and pass four user tasks. Each test takes time: when you -deploy a new process or want a new process instance, you need to execute again the different user -task. Using Automator with the correct scenario solves the issue. Deploy a new process, but instead -of starting from the beginning of a new process instance, start it via Automator. The scenario will -advance the process instance where you want it. - -# Different BPM - -The Automator executes a process instance. It does not care about the definition of the process: -does the process instance call a sub-process? An Event sub-process? It does not matter. - -## Call Activity and sub-process - -Automator does not care about these artifacts. An execution is a suite of Activities. These -activities are in the process, or a sub-process does not change the execution. - -## User Multi-instance - -A process can have a multi-instance task. In the scenario, each task may have multiple executions. -It is possible to execute a multi-instance and give different values for each execution. - -## External operation - -A scenario may consist of executing some task and then sending a Cancellation message or starting a -process instance in a different process to get a Cancellation message. This is possible to describe -this operation in a step. - -# Scenario - -A scenario is a JSON file. A scenario explains one execution, from the process creation until a -point. It may not be the end of the process: Automator can be used to advance process instances -until a specific task. It contains: - -* Information on the process: which process has to start? Some information on a delay between two - creations can be set -* Service task can be registered: Automator will check the process instance executes the task (but - does not execute it) -* The end event can be registered to verify that the process goes to the end The process instance - can execute other tasks: Automator does not verify that, except if the "mode verification" is set - to "Strict." - -`````json - - -{ - "name": "execution Round 14", - "version": "1.2", - "processId": "MergingInclusive", - "executions": [ - { - "name": "multinstance", - "policy": "STOPATFIRSTERROR", - "numberProcessInstances": 100, - "numberOfThreads": 5, - "steps": [ - { - "type": "STARTEVENT", - "taskId": "StartEvent_1" - }, - { - "type": "SERVICETASK", - "taskId": "Get context", - "executiontargetms": 10000 - }, - { - "type": "USERTASK", - "taskId": "Review level 1", - "waitingTime": "PT5S", - "numberofexecution": 10, - "taskvariable": { - "statusreview": "YES" - } - } - ], - "verifications": { - "activities": [ - { - "type": "USERTASK", - "taskId": "Review level 1", - "state": "ACTIVE" - }, - { - "type": "ENDEVENT", - "taskId": "Application Done" - } - ], - "variables": [ - { - "variableName": "Score", - "variableValue": 120 - } - ], - "performances": [ - { - "taskIdBegin": "getScore", - "taskIdEND": "getScore", - "performanceTarget": "PT0.5S" - }, - { - "taskIdBegin": "getScore", - "taskIdEND": "riskLevel", - "performanceTarget": "PT4S" - } - ] - } - } - ] -} -````` - -## Execution parameters - -| Parameter | Explanation | Example | -|------------------------|--------------------------------------------------------------------------------------------------------|---------------------------------| -| Name | Name of execution | "name": "This is the first run" | -| policy | "STOPATFIRSTERROR" or "CONTINUE": in case of error, what is the next move. Default is STOPATFIRSTERROR | "policy": "STOPATFIRSTERROR" | -| numberProcessInstances | Number of process instance to create. Each process instance follows steps. | "numberProcessInstances": 45 | -| numberOfThreads | Number of thread to execute in parallel. Default is 1. | "numberOfThreads": 5 | -| execution | if false, the execution does not start. Unot present, the default value is TRUE. | "execution" : false | - -Then the execution contains a list of steps - -## STARTEVENT step - -Start a new process instance - -| Parameter | Explanation | Example | -|--------------------|-------------------------------|---------------------------| -| type | Specify the type (STARTEVENT) | type: "STARTEVENT" | -| taskId | Activity ID of start event | actiityId= "StartEvent_1" | - -## USERTASK step - -The step wait for a user task, and execute it. - -| Parameter | Explanation | Example | -|--------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------| -| type | Specify the type (USERTASK) | type: "USERTASK" | -| delay | Deplay to wait before looking for the task, in ISO 8601 | delay="PT0.1S" waits 100 ms | -| waitingTime | Wait maximum this time, before returning an error. Automator query the engine every 500 ms, until this delay. Default value is 5 minutes | waitingTime="PT10S" | -| taskId | Activity ID to query | actiityId= "review" | -| variables | List of variable (JSON file) to update | {"amount": 450, "account": "myBankAccount", "colors": ["blue","red"]} | -| numberOfExecutions | Number of execution, the task may be multi instance. Default is 1 | numberOfExecutions = 3 | - -## SERVICETASK step - -The step wait for a service task, and execute it. - -It's depends on the usage of the scenario: if a CD/CI, the service task should be executed by the -real workers, not by the automator But in some environment, or to advance quickly the task to a -certain position, you may want to simulate the worker. Then, the automator can execute a service -task. The real worker should be deactivate then. If the service task is not found, then the scenario -will have an error. - -| Parameter | Explanation | Example | -|--------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------| -| type | Specify the type (SERVICETASK) | type: "USERTASK" | -| delay | Deplay to wait before looking for the task, in ISO 8601 | delay="PT0.1S" waits 100 ms | -| waitingTime | Wait maximum this time, before returning an error. Automator query the engine every 500 ms, until this delay. Default value is 5 minutes | waitingTime="PT10S" | -| taskId | Activity ID to query | actiityId= "review" | -| topic | Topic to search the task (mandatory in C8) | get-score | -| variables | List of variable (JSON file) to update | {"amount": 450, "account": "myBankAccount", "colors": ["blue","red"]} | -| numberOfExecutions | Number of execution, the task may be multi instance. Default is 1 | numberOfExecutions = 3 | - -## Verification - -Each execution can declare verifications. Verification are executed after the execution. - -It's possible to check: - -* active activity: does the process instance is correctly waiting on the task "Final Review" after - the execution? -* any completed activity : does the process instance executed the task "GetScore"? Does the process - instance ended on the end event "Application Done" ? -* any variable value : does the process variable "ApplicantScore" is 150? -* any performance: does the execution of the activity "getScore" stay under 500 milliseconds? Does - the execution from the activity "getScore" to "GetRiskLevel" stay under 4 seconds? - -# Build a Scenario - -Automator can generate a scenario from a real execution. The user creates a process instance and -executes it. It executes user tasks until the end of the process instance or at a certain point. Via -the UI (or the API), the user gives the process instance. Automator queries Camunda Engine to -collect the history of the process and, for each user task, which variable was provided. A new -scenario is created from this example. - -# Connect to a server +## Different usages + +### Unit test (unitscenario) + +The unit scenario describe one process instance execution. Creation and user task are describe. +This functionality is used to run regression test, coverage test or just advance process instances in the process for the development + + +Visit unitscenario/README.md + +### Load test (flowscenario) + +The flow scenario describe an environment, and send a requirement like "generate 500 PI every 40 seconds". +The flow scenario has a duration, and objective to verify. + + +You can specify objectives: produce 1000 Process Instance, ended 500 process instances, produce 300 tasks in a user task. + +Visit unitscenario/README.md + +## Scenario + +Visit the Scenario reference. + + +## Connect to a server Automator does not contain any Camunda server. It connects to an existing Camunda Engine. Two communication interfaces exist, one for Camunda 7 and one for Camunda 8. A scenario can then pilot a Camunda 7 or a Camunda 8 server. -# Comments +## Use in Docker +A docker image is created. The image can be used in a docker-compose. + +Visit [Docker documentation](doc/docker/README.md) + +## Use in Kubernetes + +The project can be used in a docker environment, to create for example one container to run a scenario within a context. +For example, it's possible to create multiple container to execute the same scenario, but different part: +* one container deploy the process, then create 500 process instances every 40 seconds +* one container simulate the worker `verification-retrieve` . This workers run 200 threads. 30 replicats must be instanciate -* embedded a Camunda 7 -* start after an activity \ No newline at end of file +The docker image can be used to build this kind of platform. Visit [Kubernetes documentation](doc/kubernetes/README.md) diff --git a/doc/docker/README.md b/doc/docker/README.md new file mode 100644 index 0000000..cd95bdc --- /dev/null +++ b/doc/docker/README.md @@ -0,0 +1,10 @@ + +# Docker + +## Manual + +```` +> docker build -t pierreyvesmonnet/processautomator:1.0.0 . + +> docker push pierreyvesmonnet/processautomator:1.0.0 +```` diff --git a/doc/flowscenario/README.md b/doc/flowscenario/README.md new file mode 100644 index 0000000..e9ef314 --- /dev/null +++ b/doc/flowscenario/README.md @@ -0,0 +1,7 @@ +# Unit Scenario + +## Goal + +## Scenario definition + +## execute \ No newline at end of file diff --git a/doc/kubernetes/README.md b/doc/kubernetes/README.md new file mode 100644 index 0000000..54732b8 --- /dev/null +++ b/doc/kubernetes/README.md @@ -0,0 +1,7 @@ +# Kubernetes + + +## Start + +cd k8s +kubectl create -f pa-DiscoverySeedExtraction.yaml \ No newline at end of file diff --git a/doc/scenarioreference/README.md b/doc/scenarioreference/README.md new file mode 100644 index 0000000..52f701d --- /dev/null +++ b/doc/scenarioreference/README.md @@ -0,0 +1,176 @@ +# Scenario reference + + + +A scenario is a JSON file. A scenario explains one execution, from the process creation until a +point. It may not be the end of the process: Automator can be used to advance process instances +until a specific task. It contains: + +* Information on the process: which process has to start? Some information on a delay between two + creations can be set +* Service task can be registered: Automator will check the process instance executes the task (but + does not execute it) +* The end event can be registered to verify that the process goes to the end The process instance + can execute other tasks: Automator does not verify that, except if the "mode verification" is set + to "Strict." + + +# Different BPM + +The Automator executes a process instance. It does not care about the definition of the process: +does the process instance call a sub-process? An Event sub-process? It does not matter. + +## Call Activity and sub-process + +Automator does not care about these artifacts. An execution is a suite of Activities. These +activities are in the process, or a sub-process does not change the execution. + +## User Multi-instance + +A process can have a multi-instances task. In the scenario, each task may have multiple executions. +It is possible to execute a multi-instance and give different values for each execution. + +## External operation + +A scenario may consist of executing some task and then sending a Cancellation message or starting a +process instance in a different process to get a Cancellation message. This is possible to describe +this operation in a step. + +## Example + +`````json + + +{ + "name": "execution Round 14", + "version": "1.2", + "processId": "MergingInclusive", + "executions": [ + { + "name": "multinstance", + "policy": "STOPATFIRSTERROR", + "numberProcessInstances": 100, + "numberOfThreads": 5, + "steps": [ + { + "type": "STARTEVENT", + "taskId": "StartEvent_1" + }, + { + "type": "SERVICETASK", + "taskId": "Get context", + "executiontargetms": 10000 + }, + { + "type": "USERTASK", + "taskId": "Review level 1", + "waitingTime": "PT5S", + "numberofexecution": 10, + "taskvariable": { + "statusreview": "YES" + } + } + ], + "verifications": { + "activities": [ + { + "type": "USERTASK", + "taskId": "Review level 1", + "state": "ACTIVE" + }, + { + "type": "ENDEVENT", + "taskId": "Application Done" + } + ], + "variables": [ + { + "variableName": "Score", + "variableValue": 120 + } + ], + "performances": [ + { + "taskIdBegin": "getScore", + "taskIdEND": "getScore", + "performanceTarget": "PT0.5S" + }, + { + "taskIdBegin": "getScore", + "taskIdEND": "riskLevel", + "performanceTarget": "PT4S" + } + ] + } + } + ] +} +````` + +## Execution parameters + +| Parameter | Explanation | Example | +|------------------------|--------------------------------------------------------------------------------------------------------|---------------------------------| +| Name | Name of execution | "name": "This is the first run" | +| policy | "STOPATFIRSTERROR" or "CONTINUE": in case of error, what is the next move. Default is STOPATFIRSTERROR | "policy": "STOPATFIRSTERROR" | +| numberProcessInstances | Number of process instance to create. Each process instance follows steps. | "numberProcessInstances": 45 | +| numberOfThreads | Number of thread to execute in parallel. Default is 1. | "numberOfThreads": 5 | +| execution | if false, the execution does not start. Unot present, the default value is TRUE. | "execution" : false | + +Then the execution contains a list of steps + +## STARTEVENT step + +Start a new process instance + +| Parameter | Explanation | Example | +|--------------------|-------------------------------|---------------------------| +| type | Specify the type (STARTEVENT) | type: "STARTEVENT" | +| taskId | Activity ID of start event | actiityId= "StartEvent_1" | + +## USERTASK step + +The step wait for a user task, and execute it. + +| Parameter | Explanation | Example | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------| +| type | Specify the type (USERTASK) | type: "USERTASK" | +| delay | Deplay to wait before looking for the task, in ISO 8601 | delay="PT0.1S" waits 100 ms | +| waitingTime | Wait maximum this time, before returning an error. Automator query the engine every 500 ms, until this delay. Default value is 5 minutes | waitingTime="PT10S" | +| taskId | Activity ID to query | actiityId= "review" | +| variables | List of variable (JSON file) to update | {"amount": 450, "account": "myBankAccount", "colors": ["blue","red"]} | +| numberOfExecutions | Number of execution, the task may be multi instance. Default is 1 | numberOfExecutions = 3 | + +## SERVICETASK step + +The step wait for a service task, and execute it. + +It's depends on the usage of the scenario: if a CD/CI, the service task should be executed by the +real workers, not by the automator But in some environment, or to advance quickly the task to a +certain position, you may want to simulate the worker. Then, the automator can execute a service +task. The real worker should be deactivate then. If the service task is not found, then the scenario +will have an error. + +| Parameter | Explanation | Example | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------| +| type | Specify the type (SERVICETASK) | type: "USERTASK" | +| delay | Deplay to wait before looking for the task, in ISO 8601 | delay="PT0.1S" waits 100 ms | +| waitingTime | Wait maximum this time, before returning an error. Automator query the engine every 500 ms, until this delay. Default value is 5 minutes | waitingTime="PT10S" | +| taskId | Activity ID to query | actiityId= "review" | +| topic | Topic to search the task (mandatory in C8) | get-score | +| variables | List of variable (JSON file) to update | {"amount": 450, "account": "myBankAccount", "colors": ["blue","red"]} | +| numberOfExecutions | Number of execution, the task may be multi instance. Default is 1 | numberOfExecutions = 3 | + +## Verification + +Each execution can declare verifications. Verification are executed after the execution. + +It's possible to check: + +* active activity: does the process instance is correctly waiting on the task "Final Review" after + the execution? +* any completed activity : does the process instance executed the task "GetScore"? Does the process + instance ended on the end event "Application Done" ? +* any variable value : does the process variable "ApplicantScore" is 150? +* any performance: does the execution of the activity "getScore" stay under 500 milliseconds? Does + the execution from the activity "getScore" to "GetRiskLevel" stay under 4 seconds? diff --git a/doc/unitscenario/README.md b/doc/unitscenario/README.md new file mode 100644 index 0000000..5db55ba --- /dev/null +++ b/doc/unitscenario/README.md @@ -0,0 +1,48 @@ +# Unit Scenario + +## Goal + + + +### Verification + +![Process](doc/explanationProcess.png) + +in a CD/CI, you want to verify that a process follows the same behavior in the same performance +time. Running every day (or hours) or asking via an API call to replay a scenario is useful to +verify there is no difference. If the customer is 4555, do we still move the process instance to +Review Level 1"? The second verification is the performance. The scenario can record an expected +duration target (for example, 4 seconds to execute the Get Context service task. Does the execution +still at this time? + +### Coverage report + +Execute multiple scenarios to be sure that all the process is covered correctly. An "Execution +round" is a set of scenarios executed at the same time. At the end of the execution, a coverage test +can be performed. A CD/CI verification may be to check the scenario execution, the target time, and +the coverage. + +### Advance process instances to a step for development + +During the development, you verify the task "Notify applicant". To test it in the situation, you +must have a process instance in the process and pass four user tasks. Each test takes time: when you +deploy a new process or want a new process instance, you need to execute again the different user +task. Using Automator with the correct scenario solves the issue. Deploy a new process, but instead +of starting from the beginning of a new process instance, start it via Automator. The scenario will +advance the process instance where you want it. + + +# Build a Scenario + + + +Automator can generate a scenario from a real execution. The user creates a process instance and +executes it. It executes user tasks until the end of the process instance or at a certain point. Via +the UI (or the API), the user gives the process instance. Automator queries Camunda Engine to +collect the history of the process and, for each user task, which variable was provided. A new +scenario is created from this example. + + +## Scenario definition + +## execute \ No newline at end of file diff --git a/docker-processautomator/.env b/docker-processautomator/.env new file mode 100644 index 0000000..2c52531 --- /dev/null +++ b/docker-processautomator/.env @@ -0,0 +1,21 @@ +## Image versions ## +CAMUNDA_CONNECTORS_VERSION=0.19.1 +CAMUNDA_OPTIMIZE_VERSION=3.10.0 +CAMUNDA_PLATFORM_VERSION=8.2.4 +CAMUNDA_WEB_MODELER_VERSION=8.2.2 +ELASTIC_VERSION=7.17.9 +KEYCLOAK_SERVER_VERSION=19.0.3 +MAILPIT_VERSION=v1.5.4 +POSTGRES_VERSION=14.5-alpine +HOST=localhost + +## Configuration ## +# By default the zeebe api is public, when setting this to `identity` a valid zeebe client token is required +ZEEBE_AUTHENTICATION_MODE=none +ZEEBE_CLIENT_ID=zeebe +ZEEBE_CLIENT_SECRET=zecret + +# Set to 'true' to enable resource based authorizations for users and groups +# This can be used to limit access for users or groups to view/update specific +# processes and decisions in Operate and Tasklist +RESOURCE_AUTHORIZATIONS_ENABLED=false diff --git a/docker-processautomator/README.md b/docker-processautomator/README.md new file mode 100644 index 0000000..19c8c12 --- /dev/null +++ b/docker-processautomator/README.md @@ -0,0 +1,3 @@ +# Docker process automator + +## Example diff --git a/docker-processautomator/connector-secrets.txt b/docker-processautomator/connector-secrets.txt new file mode 100644 index 0000000..5b761a3 --- /dev/null +++ b/docker-processautomator/connector-secrets.txt @@ -0,0 +1,2 @@ +# add secrets per line in the format NAME=VALUE +# WARNING: ensure not to commit changes to this file diff --git a/docker-processautomator/docker-compose-automator.yaml b/docker-processautomator/docker-compose-automator.yaml new file mode 100644 index 0000000..b9d9924 --- /dev/null +++ b/docker-processautomator/docker-compose-automator.yaml @@ -0,0 +1,60 @@ +version: "3" +services: + + processautomatorDiscovery: + image: pierreyvesmonnet/processautomator:1.0.0 + container_name: processautomatorDiscovery + ports: + - "8380:8380" + environment: + - automator.servers.camunda8.zeebeGatewayAddress=zeebe:26500 + - automator.servers.camunda8.operateUserName=demo + - automator.servers.camunda8.operateUserPassword=demo + - automator.servers.camunda8.operateUrl=http://operate:8080 + - automator.servers.camunda8.taskListUrl= + - automator.servers.camunda8.workerExecutionThreads=500 + - automator.startup.scenarioPath=/app/processautomator/src/main/resources/loadtest + - automator.startup.scenarioAtStartup=DiscoverySeedExtraction.json + - automator.startup.waitWarmup=PT1M + - automator.startup.policyExecution=CREATION|SERVICETASK|USERTASK + - automator.startup.loglevel=MAIN + - ZEEBE_CLIENT_SECURITY_PLAINTEXT=true +# - ZEEBE_CLIENT_CLOUD_REGION= +# - ZEEBE_CLIENT_CLOUD_CLUSTERID= +# - ZEEBE_CLIENT_CLOUD_CLIENTID= +# - ZEEBE_CLIENT_CLOUD_CLIENTSECRET= + - LOGGING_LEVEL_ROOT=INFO + volumes: + - ../:/app/processautomator + networks: + - camunda-platform + depends_on: + - zeebe + - operate + + processautomatorVerification: + image: pierreyvesmonnet/processautomator:1.0.0 + container_name: processautomatorVerification + ports: + - "8381:8380" + environment: + - automator.servers.camunda8.zeebeGatewayAddress=zeebe:26500 + - automator.servers.camunda8.operateUserName=demo + - automator.servers.camunda8.operateUserPassword=demo + - automator.servers.camunda8.operateUrl=http://operate:8080 + - automator.servers.camunda8.taskListUrl= + - automator.servers.camunda8.workerExecutionThreads=500 + - automator.startup.scenarioPath=/app/processautomator/src/main/resources/loadtest + - automator.startup.scenarioAtStartup=Verification.json + - automator.startup.waitWarmup=PT1M + - automator.startup.policyExecution=CREATION|SERVICETASK|USERTASK + - automator.startup.logLevel=MAIN + volumes: + - ../:/app/processautomator + networks: + - camunda-platform + depends_on: + - zeebe + - operate +networks: + camunda-platform: diff --git a/docker-processautomator/docker-compose-core.yaml b/docker-processautomator/docker-compose-core.yaml new file mode 100644 index 0000000..008183e --- /dev/null +++ b/docker-processautomator/docker-compose-core.yaml @@ -0,0 +1,164 @@ +# While the Docker images themselves are supported for production usage, +# this docker-compose.yaml is designed to be used by developers to run +# an environment locally. It is not designed to be used in production. +# We recommend to use Kubernetes in production with our Helm Charts: +# https://docs.camunda.io/docs/self-managed/platform-deployment/kubernetes-helm/ +# For local development, we recommend using KIND instead of `docker-compose`: +# https://docs.camunda.io/docs/self-managed/platform-deployment/helm-kubernetes/guides/local-kubernetes-cluster/ + +# This is a lightweight configuration with Zeebe, Operate, Tasklist, and Elasticsearch +# See docker-compose.yml for a configuration that also includes Optimize, Identity, and Keycloak. + +services: + + zeebe: # https://docs.camunda.io/docs/self-managed/platform-deployment/docker/#zeebe + image: camunda/zeebe:${CAMUNDA_PLATFORM_VERSION} + container_name: zeebe + ports: + - "26500:26500" + - "9600:9600" + environment: # https://docs.camunda.io/docs/self-managed/zeebe-deployment/configuration/environment-variables/ + - ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_CLASSNAME=io.camunda.zeebe.exporter.ElasticsearchExporter + - ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_URL=http://elasticsearch:9200 + # default is 1000, see here: https://github.com/camunda/zeebe/blob/main/exporters/elasticsearch-exporter/src/main/java/io/camunda/zeebe/exporter/ElasticsearchExporterConfiguration.java#L259 + - ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_BULK_SIZE=1 + # allow running with low disk space + - ZEEBE_BROKER_DATA_DISKUSAGECOMMANDWATERMARK=0.998 + - ZEEBE_BROKER_DATA_DISKUSAGEREPLICATIONWATERMARK=0.999 + - "JAVA_TOOL_OPTIONS=-Xms512m -Xmx512m" + restart: always + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:9600/ready" ] + interval: 30s + timeout: 5s + retries: 5 + start_period: 30s +# volumes: +# - zeebe:/usr/local/zeebe/data + networks: + - camunda-platform + depends_on: + - elasticsearch + + operate: # https://docs.camunda.io/docs/self-managed/platform-deployment/docker/#operate + image: camunda/operate:${CAMUNDA_PLATFORM_VERSION} + container_name: operate + ports: + - "8081:8080" + environment: # https://docs.camunda.io/docs/self-managed/operate-deployment/configuration/ + - CAMUNDA_OPERATE_ZEEBE_GATEWAYADDRESS=zeebe:26500 + - CAMUNDA_OPERATE_ELASTICSEARCH_URL=http://elasticsearch:9200 + - CAMUNDA_OPERATE_ZEEBEELASTICSEARCH_URL=http://elasticsearch:9200 + - management.endpoints.web.exposure.include=health + - management.endpoint.health.probes.enabled=true + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:8080/actuator/health/readiness" ] + interval: 30s + timeout: 1s + retries: 5 + start_period: 30s + networks: + - camunda-platform + depends_on: + - zeebe + - elasticsearch + + tasklist: # https://docs.camunda.io/docs/self-managed/platform-deployment/docker/#tasklist + image: camunda/tasklist:${CAMUNDA_PLATFORM_VERSION} + container_name: tasklist + ports: + - "8082:8080" + environment: # https://docs.camunda.io/docs/self-managed/tasklist-deployment/configuration/ + - CAMUNDA_TASKLIST_ZEEBE_GATEWAYADDRESS=zeebe:26500 + - CAMUNDA_TASKLIST_ELASTICSEARCH_URL=http://elasticsearch:9200 + - CAMUNDA_TASKLIST_ZEEBEELASTICSEARCH_URL=http://elasticsearch:9200 + - management.endpoints.web.exposure.include=health + - management.endpoint.health.probes.enabled=true + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:8080/actuator/health/readiness" ] + interval: 30s + timeout: 1s + retries: 5 + start_period: 30s + networks: + - camunda-platform + depends_on: + - zeebe + - elasticsearch + + connectors: # https://docs.camunda.io/docs/components/integration-framework/connectors/out-of-the-box-connectors/available-connectors-overview/ + image: camunda/connectors-bundle:${CAMUNDA_CONNECTORS_VERSION} + container_name: connectors + ports: + - "8085:8080" + environment: + - ZEEBE_CLIENT_BROKER_GATEWAY-ADDRESS=zeebe:26500 + - ZEEBE_CLIENT_SECURITY_PLAINTEXT=true + - OPERATE_CLIENT_ENABLED=true + - CAMUNDA_OPERATE_CLIENT_URL=http://operate:8080 + - CAMUNDA_OPERATE_CLIENT_USERNAME=demo + - CAMUNDA_OPERATE_CLIENT_PASSWORD=demo + - management.endpoints.web.exposure.include=health + - management.endpoint.health.probes.enabled=true + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:8080/actuator/health/readiness" ] + interval: 30s + timeout: 1s + retries: 5 + start_period: 30s + env_file: connector-secrets.txt + networks: + - camunda-platform + depends_on: + - zeebe + - operate + + elasticsearch: # https://hub.docker.com/_/elasticsearch + image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION} + container_name: elasticsearch + ports: + - "9200:9200" + - "9300:9300" + environment: + - bootstrap.memory_lock=true + - discovery.type=single-node + - xpack.security.enabled=false + # allow running with low disk space + - cluster.routing.allocation.disk.threshold_enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + restart: always + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:9200/_cat/health | grep -q green" ] + interval: 30s + timeout: 5s + retries: 3 +# volumes: +# - elastic:/usr/share/elasticsearch/data + networks: + - camunda-platform + + kibana: + image: docker.elastic.co/kibana/kibana:${ELASTIC_VERSION} + container_name: kibana + ports: + - 5601:5601 + volumes: + - kibana:/usr/share/kibana/data + networks: + - camunda-platform + depends_on: + - elasticsearch + profiles: + - kibana + +volumes: + zeebe: + elastic: + kibana: + +networks: + camunda-platform: diff --git a/k8s/README.md b/k8s/README.md new file mode 100644 index 0000000..39454e1 --- /dev/null +++ b/k8s/README.md @@ -0,0 +1,18 @@ +# Process Execution Automator + +# Build the docker image + +at root, use +````yaml +docker build -t pierreyvesmonnet/vobilediscovery:1.0.0 . + +docker push pierreyvesmonnet/vobilediscovery:1.0.0 +```` + +Or use the image generated on camunda-hub + +# Deploy + +````yaml +kubectl create -f pa-verification.yaml +```` diff --git a/k8s/pa-verification.yaml b/k8s/pa-verification.yaml new file mode 100644 index 0000000..90f6a9c --- /dev/null +++ b/k8s/pa-verification.yaml @@ -0,0 +1,469 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-creation + labels: + app: pa-verification-creation +spec: + selector: + matchLabels: + app: pa-verification-creation + replicas: 1 + template: + metadata: + labels: + app: pa-verification-creation + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-creation + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=1 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=DEPLOYPROCESS|WARMINGUP|CREATION + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 2Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-retrieve + labels: + app: pa-verification-retrieve +spec: + selector: + matchLabels: + app: pa-verification-retrieve + # 34 + replicas: 1 + template: + metadata: + labels: + app: pa-verification-retrieve + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-retrieve + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=50 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-retrieve + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-checkurl + labels: + app: pa-verification-checkurl +spec: + selector: + matchLabels: + app: pa-verification-checkurl + # 173 + replicas: 1 + template: + metadata: + labels: + app: pa-verification-checkurl + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-checkurl + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=300 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-checkurl + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-updateitem + labels: + app: pa-verification-updateitem +spec: + selector: + matchLabels: + app: pa-verification-updateitem + # 34 + replicas: 1 + template: + metadata: + labels: + app: pa-verification-updateitem + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-updateitem + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=50 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-updateitem + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-download + labels: + app: pa-verification-download +spec: + selector: + matchLabels: + app: pa-verification-download + # 1042 + replicas: 10 + template: + metadata: + labels: + app: pa-verification-download + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-download + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=200 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-download + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-fingerprint + labels: + app: pa-verification-fingerprint +spec: + selector: + matchLabels: + app: pa-verification-fingerprint + # 2083 + replicas: 12 + template: + metadata: + labels: + app: pa-verification-fingerprint + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-fingerprint + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=200 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-fingerprint + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-getresult + labels: + app: pa-verification-getresult +spec: + selector: + matchLabels: + app: pa-verification-getresult + # 2083 + replicas: 12 + template: + metadata: + labels: + app: pa-verification-getresult + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-getresult + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=200 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-getresult + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-obtaindescription + labels: + app: pa-verification-obtaindescription +spec: + selector: + matchLabels: + app: pa-verification-obtaindescription + # 104 + replicas: 2 + template: + metadata: + labels: + app: pa-verification-obtaindescription + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-obtaindescription + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=200 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-obtaindescription + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-thresoldcheck + labels: + app: pa-verification-thresoldcheck +spec: + selector: + matchLabels: + app: pa-verification-thresoldcheck + # 34 + replicas: 2 + template: + metadata: + labels: + app: pa-verification-thresoldcheck + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-thresoldcheck + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=200 + -Dautomator.servers.camunda8.workerMaxJobsActive=15 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.waitWarmup=PT10S + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-thresoldcheck + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pa-verification-potentialclaim + labels: + app: pa-verification-potentialclaim +spec: + selector: + matchLabels: + app: pa-verification-potentialclaim + # 34 + replicas: 1 + template: + metadata: + labels: + app: pa-verification-potentialclaim + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: pa-verification-createpotentialclaim + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=200 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Verification.json + -Dautomator.startup.policyExecution=SERVICETASK|USERTASK + -Dautomator.startup.filterService=verification-createpotentialclaim + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi diff --git a/k8s/processautomator.yaml b/k8s/processautomator.yaml new file mode 100644 index 0000000..87f5b52 --- /dev/null +++ b/k8s/processautomator.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: camunda-8-processautomator-service + labels: + app: camunda-8-processautomator-service +spec: + selector: + matchLabels: + app: camunda-8-processautomator-service + replicas: 1 + template: + metadata: + labels: + app: camunda-8-processautomator-service + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: camunda-8-processautomator-service + image: pierreyvesmonnet/processautomator:1.0.3 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl= + -Dautomator.servers.camunda8.workerExecutionThreads=2000 + -Dautomator.startup.scenarioPath=/app/scenarii/loadtest + -Dautomator.startup.scenarioAtStartup=Discovery.json + -Dautomator.startup.waitWarmup=PT10S + -Dautomator.startup.policyExecution=DEPLOYPROCESS|WARMINGUP|CREATION|SERVICETASK|USERTASK + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: 1 + memory: 2Gi + requests: + cpu: 1 + memory: 1Gi diff --git a/pom.xml b/pom.xml index f4db7bc..73ba873 100644 --- a/pom.xml +++ b/pom.xml @@ -6,14 +6,15 @@ org.camunda.community.automator process-execution-automator - 1.2.0 + 1.3.0 17 ${java.version} ${java.version} - 8.1.4 + 8.2.0 + 8.2.4 7.18.0 5.9.1 @@ -47,14 +48,14 @@ - io.camunda - spring-zeebe-starter + io.camunda.spring + spring-boot-starter-camunda ${zeebe.version} io.camunda zeebe-client-java - ${zeebe.version} + ${zeebe-client.version} @@ -100,6 +101,19 @@ 1.6.1 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + org.glassfish.jaxb + jaxb-runtime + 2.3.1 + + org.junit.jupiter @@ -117,95 +131,40 @@ - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - - org.junit.platform - junit-platform-surefire-provider - 1.3.2 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - 17 - 17 - - - - - - org.springframework.boot - spring-boot-maven-plugin - 2.7.5 - - io.camunda.CherryApplication - - - - - - maven-assembly-plugin - - - install - - single - - - - - - jar-with-dependencies - - - - org.camunda.automator.AutomatorApplication - - - - - - - com.github.eirslett frontend-maven-plugin + 1.12.1 - src/main/frontend + ${project.basedir}/src/main/frontend + ${project.basedir}/target install node and npm + compile install-node-and-npm - v16.13.0 - - 8.2.0 - + v18.5.0 + 8.12.1 npm install + compile npm - install npm run build + compile npm @@ -220,7 +179,7 @@ copy-resources - validate + compile copy-resources @@ -236,6 +195,47 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + org.junit.platform + junit-platform-surefire-provider + 1.3.2 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + 17 + 17 + + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.0.6 + + org.camunda.automator.AutomatorApplication + exec + + + + + repackage + + + + + diff --git a/src/main/frontend/README.md b/src/main/frontend/README.md new file mode 100644 index 0000000..58beeac --- /dev/null +++ b/src/main/frontend/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json new file mode 100644 index 0000000..7f643df --- /dev/null +++ b/src/main/frontend/package.json @@ -0,0 +1,38 @@ +{ + "name": "process-automator", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/src/main/frontend/public/index.html b/src/main/frontend/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/src/main/frontend/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/src/main/frontend/public/logo512.png b/src/main/frontend/public/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e47a6545bc15971f8f63fba70e4013df88a664 GIT binary patch literal 9664 zcmYj%RZtvEu=T>?y0|+_a0zY+Zo%Dkae}+MySoIppb75o?vUW_?)>@g{U2`ERQIXV zeY$JrWnMZ$QC<=ii4X|@0H8`si75jB(ElJb00HAB%>SlLR{!zO|C9P3zxw_U8?1d8uRZ=({Ga4shyN}3 zAK}WA(ds|``G4jA)9}Bt2Hy0+f3rV1E6b|@?hpGA=PI&r8)ah|)I2s(P5Ic*Ndhn^ z*T&j@gbCTv7+8rpYbR^Ty}1AY)YH;p!m948r#%7x^Z@_-w{pDl|1S4`EM3n_PaXvK z1JF)E3qy$qTj5Xs{jU9k=y%SQ0>8E$;x?p9ayU0bZZeo{5Z@&FKX>}s!0+^>C^D#z z>xsCPvxD3Z=dP}TTOSJhNTPyVt14VCQ9MQFN`rn!c&_p?&4<5_PGm4a;WS&1(!qKE z_H$;dDdiPQ!F_gsN`2>`X}$I=B;={R8%L~`>RyKcS$72ai$!2>d(YkciA^J0@X%G4 z4cu!%Ps~2JuJ8ex`&;Fa0NQOq_nDZ&X;^A=oc1&f#3P1(!5il>6?uK4QpEG8z0Rhu zvBJ+A9RV?z%v?!$=(vcH?*;vRs*+PPbOQ3cdPr5=tOcLqmfx@#hOqX0iN)wTTO21jH<>jpmwRIAGw7`a|sl?9y9zRBh>(_%| zF?h|P7}~RKj?HR+q|4U`CjRmV-$mLW>MScKnNXiv{vD3&2@*u)-6P@h0A`eeZ7}71 zK(w%@R<4lLt`O7fs1E)$5iGb~fPfJ?WxhY7c3Q>T-w#wT&zW522pH-B%r5v#5y^CF zcC30Se|`D2mY$hAlIULL%-PNXgbbpRHgn<&X3N9W!@BUk@9g*P5mz-YnZBb*-$zMM z7Qq}ic0mR8n{^L|=+diODdV}Q!gwr?y+2m=3HWwMq4z)DqYVg0J~^}-%7rMR@S1;9 z7GFj6K}i32X;3*$SmzB&HW{PJ55kT+EI#SsZf}bD7nW^Haf}_gXciYKX{QBxIPSx2Ma? zHQqgzZq!_{&zg{yxqv3xq8YV+`S}F6A>Gtl39_m;K4dA{pP$BW0oIXJ>jEQ!2V3A2 zdpoTxG&V=(?^q?ZTj2ZUpDUdMb)T?E$}CI>r@}PFPWD9@*%V6;4Ag>D#h>!s)=$0R zRXvdkZ%|c}ubej`jl?cS$onl9Tw52rBKT)kgyw~Xy%z62Lr%V6Y=f?2)J|bZJ5(Wx zmji`O;_B+*X@qe-#~`HFP<{8$w@z4@&`q^Q-Zk8JG3>WalhnW1cvnoVw>*R@c&|o8 zZ%w!{Z+MHeZ*OE4v*otkZqz11*s!#s^Gq>+o`8Z5 z^i-qzJLJh9!W-;SmFkR8HEZJWiXk$40i6)7 zZpr=k2lp}SasbM*Nbn3j$sn0;rUI;%EDbi7T1ZI4qL6PNNM2Y%6{LMIKW+FY_yF3) zSKQ2QSujzNMSL2r&bYs`|i2Dnn z=>}c0>a}>|uT!IiMOA~pVT~R@bGlm}Edf}Kq0?*Af6#mW9f9!}RjW7om0c9Qlp;yK z)=XQs(|6GCadQbWIhYF=rf{Y)sj%^Id-ARO0=O^Ad;Ph+ z0?$eE1xhH?{T$QI>0JP75`r)U_$#%K1^BQ8z#uciKf(C701&RyLQWBUp*Q7eyn76} z6JHpC9}R$J#(R0cDCkXoFSp;j6{x{b&0yE@P7{;pCEpKjS(+1RQy38`=&Yxo%F=3y zCPeefABp34U-s?WmU#JJw23dcC{sPPFc2#J$ZgEN%zod}J~8dLm*fx9f6SpO zn^Ww3bt9-r0XaT2a@Wpw;C23XM}7_14#%QpubrIw5aZtP+CqIFmsG4`Cm6rfxl9n5 z7=r2C-+lM2AB9X0T_`?EW&Byv&K?HS4QLoylJ|OAF z`8atBNTzJ&AQ!>sOo$?^0xj~D(;kS$`9zbEGd>f6r`NC3X`tX)sWgWUUOQ7w=$TO&*j;=u%25ay-%>3@81tGe^_z*C7pb9y*Ed^H3t$BIKH2o+olp#$q;)_ zfpjCb_^VFg5fU~K)nf*d*r@BCC>UZ!0&b?AGk_jTPXaSnCuW110wjHPPe^9R^;jo3 zwvzTl)C`Zl5}O2}3lec=hZ*$JnkW#7enKKc)(pM${_$9Hc=Sr_A9Biwe*Y=T?~1CK z6eZ9uPICjy-sMGbZl$yQmpB&`ouS8v{58__t0$JP%i3R&%QR3ianbZqDs<2#5FdN@n5bCn^ZtH992~5k(eA|8|@G9u`wdn7bnpg|@{m z^d6Y`*$Zf2Xr&|g%sai#5}Syvv(>Jnx&EM7-|Jr7!M~zdAyjt*xl;OLhvW-a%H1m0 z*x5*nb=R5u><7lyVpNAR?q@1U59 zO+)QWwL8t zyip?u_nI+K$uh{y)~}qj?(w0&=SE^8`_WMM zTybjG=999h38Yes7}-4*LJ7H)UE8{mE(6;8voE+TYY%33A>S6`G_95^5QHNTo_;Ao ztIQIZ_}49%{8|=O;isBZ?=7kfdF8_@azfoTd+hEJKWE!)$)N%HIe2cplaK`ry#=pV z0q{9w-`i0h@!R8K3GC{ivt{70IWG`EP|(1g7i_Q<>aEAT{5(yD z=!O?kq61VegV+st@XCw475j6vS)_z@efuqQgHQR1T4;|-#OLZNQJPV4k$AX1Uk8Lm z{N*b*ia=I+MB}kWpupJ~>!C@xEN#Wa7V+7{m4j8c?)ChV=D?o~sjT?0C_AQ7B-vxqX30s0I_`2$in86#`mAsT-w?j{&AL@B3$;P z31G4(lV|b}uSDCIrjk+M1R!X7s4Aabn<)zpgT}#gE|mIvV38^ODy@<&yflpCwS#fRf9ZX3lPV_?8@C5)A;T zqmouFLFk;qIs4rA=hh=GL~sCFsXHsqO6_y~*AFt939UYVBSx1s(=Kb&5;j7cSowdE;7()CC2|-i9Zz+_BIw8#ll~-tyH?F3{%`QCsYa*b#s*9iCc`1P1oC26?`g<9))EJ3%xz+O!B3 zZ7$j~To)C@PquR>a1+Dh>-a%IvH_Y7^ys|4o?E%3`I&ADXfC8++hAdZfzIT#%C+Jz z1lU~K_vAm0m8Qk}K$F>|>RPK%<1SI0(G+8q~H zAsjezyP+u!Se4q3GW)`h`NPSRlMoBjCzNPesWJwVTY!o@G8=(6I%4XHGaSiS3MEBK zhgGFv6Jc>L$4jVE!I?TQuwvz_%CyO!bLh94nqK11C2W$*aa2ueGopG8DnBICVUORP zgytv#)49fVXDaR$SukloYC3u7#5H)}1K21=?DKj^U)8G;MS)&Op)g^zR2($<>C*zW z;X7`hLxiIO#J`ANdyAOJle4V%ppa*(+0i3w;8i*BA_;u8gOO6)MY`ueq7stBMJTB; z-a0R>hT*}>z|Gg}@^zDL1MrH+2hsR8 zHc}*9IvuQC^Ju)^#Y{fOr(96rQNPNhxc;mH@W*m206>Lo<*SaaH?~8zg&f&%YiOEG zGiz?*CP>Bci}!WiS=zj#K5I}>DtpregpP_tfZtPa(N<%vo^#WCQ5BTv0vr%Z{)0q+ z)RbfHktUm|lg&U3YM%lMUM(fu}i#kjX9h>GYctkx9Mt_8{@s%!K_EI zScgwy6%_fR?CGJQtmgNAj^h9B#zmaMDWgH55pGuY1Gv7D z;8Psm(vEPiwn#MgJYu4Ty9D|h!?Rj0ddE|&L3S{IP%H4^N!m`60ZwZw^;eg4sk6K{ ziA^`Sbl_4~f&Oo%n;8Ye(tiAdlZKI!Z=|j$5hS|D$bDJ}p{gh$KN&JZYLUjv4h{NY zBJ>X9z!xfDGY z+oh_Z&_e#Q(-}>ssZfm=j$D&4W4FNy&-kAO1~#3Im;F)Nwe{(*75(p=P^VI?X0GFakfh+X-px4a%Uw@fSbmp9hM1_~R>?Z8+ ziy|e9>8V*`OP}4x5JjdWp}7eX;lVxp5qS}0YZek;SNmm7tEeSF*-dI)6U-A%m6YvCgM(}_=k#a6o^%-K4{`B1+}O4x zztDT%hVb;v#?j`lTvlFQ3aV#zkX=7;YFLS$uIzb0E3lozs5`Xy zi~vF+%{z9uLjKvKPhP%x5f~7-Gj+%5N`%^=yk*Qn{`> z;xj&ROY6g`iy2a@{O)V(jk&8#hHACVDXey5a+KDod_Z&}kHM}xt7}Md@pil{2x7E~ zL$k^d2@Ec2XskjrN+IILw;#7((abu;OJii&v3?60x>d_Ma(onIPtcVnX@ELF0aL?T zSmWiL3(dOFkt!x=1O!_0n(cAzZW+3nHJ{2S>tgSK?~cFha^y(l@-Mr2W$%MN{#af8J;V*>hdq!gx=d0h$T7l}>91Wh07)9CTX zh2_ZdQCyFOQ)l(}gft0UZG`Sh2`x-w`5vC2UD}lZs*5 zG76$akzn}Xi))L3oGJ75#pcN=cX3!=57$Ha=hQ2^lwdyU#a}4JJOz6ddR%zae%#4& za)bFj)z=YQela(F#Y|Q#dp}PJghITwXouVaMq$BM?K%cXn9^Y@g43$=O)F&ZlOUom zJiad#dea;-eywBA@e&D6Pdso1?2^(pXiN91?jvcaUyYoKUmvl5G9e$W!okWe*@a<^ z8cQQ6cNSf+UPDx%?_G4aIiybZHHagF{;IcD(dPO!#=u zWfqLcPc^+7Uu#l(Bpxft{*4lv#*u7X9AOzDO z1D9?^jIo}?%iz(_dwLa{ex#T}76ZfN_Z-hwpus9y+4xaUu9cX}&P{XrZVWE{1^0yw zO;YhLEW!pJcbCt3L8~a7>jsaN{V3>tz6_7`&pi%GxZ=V3?3K^U+*ryLSb)8^IblJ0 zSRLNDvIxt)S}g30?s_3NX>F?NKIGrG_zB9@Z>uSW3k2es_H2kU;Rnn%j5qP)!XHKE zPB2mHP~tLCg4K_vH$xv`HbRsJwbZMUV(t=ez;Ec(vyHH)FbfLg`c61I$W_uBB>i^r z&{_P;369-&>23R%qNIULe=1~T$(DA`ev*EWZ6j(B$(te}x1WvmIll21zvygkS%vwG zzkR6Z#RKA2!z!C%M!O>!=Gr0(J0FP=-MN=5t-Ir)of50y10W}j`GtRCsXBakrKtG& zazmITDJMA0C51&BnLY)SY9r)NVTMs);1<=oosS9g31l{4ztjD3#+2H7u_|66b|_*O z;Qk6nalpqdHOjx|K&vUS_6ITgGll;TdaN*ta=M_YtyC)I9Tmr~VaPrH2qb6sd~=AcIxV+%z{E&0@y=DPArw zdV7z(G1hBx7hd{>(cr43^WF%4Y@PXZ?wPpj{OQ#tvc$pABJbvPGvdR`cAtHn)cSEV zrpu}1tJwQ3y!mSmH*uz*x0o|CS<^w%&KJzsj~DU0cLQUxk5B!hWE>aBkjJle8z~;s z-!A=($+}Jq_BTK5^B!`R>!MulZN)F=iXXeUd0w5lUsE5VP*H*oCy(;?S$p*TVvTxwAeWFB$jHyb0593)$zqalVlDX=GcCN1gU0 zlgU)I$LcXZ8Oyc2TZYTPu@-;7<4YYB-``Qa;IDcvydIA$%kHhJKV^m*-zxcvU4viy&Kr5GVM{IT>WRywKQ9;>SEiQD*NqplK-KK4YR`p0@JW)n_{TU3bt0 zim%;(m1=#v2}zTps=?fU5w^(*y)xT%1vtQH&}50ZF!9YxW=&7*W($2kgKyz1mUgfs zfV<*XVVIFnohW=|j+@Kfo!#liQR^x>2yQdrG;2o8WZR+XzU_nG=Ed2rK?ntA;K5B{ z>M8+*A4!Jm^Bg}aW?R?6;@QG@uQ8&oJ{hFixcfEnJ4QH?A4>P=q29oDGW;L;= z9-a0;g%c`C+Ai!UmK$NC*4#;Jp<1=TioL=t^YM)<<%u#hnnfSS`nq63QKGO1L8RzX z@MFDqs1z ztYmxDl@LU)5acvHk)~Z`RW7=aJ_nGD!mOSYD>5Odjn@TK#LY{jf?+piB5AM-CAoT_ z?S-*q7}wyLJzK>N%eMPuFgN)Q_otKP;aqy=D5f!7<=n(lNkYRXVpkB{TAYLYg{|(jtRqYmg$xH zjmq?B(RE4 zQx^~Pt}gxC2~l=K$$-sYy_r$CO(d=+b3H1MB*y_5g6WLaWTXn+TKQ|hNY^>Mp6k*$ zwkovomhu776vQATqT4blf~g;TY(MWCrf^^yfWJvSAB$p5l;jm@o#=!lqw+Lqfq>X= z$6~kxfm7`3q4zUEB;u4qa#BdJxO!;xGm)wwuisj{0y2x{R(IGMrsIzDY9LW>m!Y`= z04sx3IjnYvL<4JqxQ8f7qYd0s2Ig%`ytYPEMKI)s(LD}D@EY>x`VFtqvnADNBdeao zC96X+MxnwKmjpg{U&gP3HE}1=s!lv&D{6(g_lzyF3A`7Jn*&d_kL<;dAFx!UZ>hB8 z5A*%LsAn;VLp>3${0>M?PSQ)9s3}|h2e?TG4_F{}{Cs>#3Q*t$(CUc}M)I}8cPF6% z=+h(Kh^8)}gj(0}#e7O^FQ6`~fd1#8#!}LMuo3A0bN`o}PYsm!Y}sdOz$+Tegc=qT z8x`PH$7lvnhJp{kHWb22l;@7B7|4yL4UOOVM0MP_>P%S1Lnid)+k9{+3D+JFa#Pyf zhVc#&df87APl4W9X)F3pGS>@etfl=_E5tBcVoOfrD4hmVeTY-cj((pkn%n@EgN{0f zwb_^Rk0I#iZuHK!l*lN`ceJn(sI{$Fq6nN& zE<-=0_2WN}m+*ivmIOxB@#~Q-cZ>l136w{#TIJe478`KE7@=a{>SzPHsKLzYAyBQO zAtuuF$-JSDy_S@6GW0MOE~R)b;+0f%_NMrW(+V#c_d&U8Z9+ec4=HmOHw?gdjF(Lu zzra83M_BoO-1b3;9`%&DHfuUY)6YDV21P$C!Rc?mv&{lx#f8oc6?0?x zK08{WP65?#>(vPfA-c=MCY|%*1_<3D4NX zeVTi-JGl2uP_2@0F{G({pxQOXt_d{g_CV6b?jNpfUG9;8yle-^4KHRvZs-_2siata zt+d_T@U$&t*xaD22(fH(W1r$Mo?3dc%Tncm=C6{V9y{v&VT#^1L04vDrLM9qBoZ4@ z6DBN#m57hX7$C(=#$Y5$bJmwA$T8jKD8+6A!-IJwA{WOfs%s}yxUw^?MRZjF$n_KN z6`_bGXcmE#5e4Ym)aQJ)xg3Pg0@k`iGuHe?f(5LtuzSq=nS^5z>vqU0EuZ&75V%Z{ zYyhRLN^)$c6Ds{f7*FBpE;n5iglx5PkHfWrj3`x^j^t z7ntuV`g!9Xg#^3!x)l*}IW=(Tz3>Y5l4uGaB&lz{GDjm2D5S$CExLT`I1#n^lBH7Y zDgpMag@`iETKAI=p<5E#LTkwzVR@=yY|uBVI1HG|8h+d;G-qfuj}-ZR6fN>EfCCW z9~wRQoAPEa#aO?3h?x{YvV*d+NtPkf&4V0k4|L=uj!U{L+oLa(z#&iuhJr3-PjO3R z5s?=nn_5^*^Rawr>>Nr@K(jwkB#JK-=+HqwfdO<+P5byeim)wvqGlP-P|~Nse8=XF zz`?RYB|D6SwS}C+YQv+;}k6$-%D(@+t14BL@vM z2q%q?f6D-A5s$_WY3{^G0F131bbh|g!}#BKw=HQ7mx;Dzg4Z*bTLQSfo{ed{4}NZW zfrRm^Ca$rlE{Ue~uYv>R9{3smwATcdM_6+yWIO z*ZRH~uXE@#p$XTbCt5j7j2=86e{9>HIB6xDzV+vAo&B?KUiMP|ttOElepnl%|DPqL b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/src/main/frontend/src/App.css b/src/main/frontend/src/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/src/main/frontend/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/main/frontend/src/App.js b/src/main/frontend/src/App.js new file mode 100644 index 0000000..3784575 --- /dev/null +++ b/src/main/frontend/src/App.js @@ -0,0 +1,25 @@ +import logo from './logo.svg'; +import './App.css'; + +function App() { + return ( +
+
+ logo +

+ Edit src/App.js and save to reload. +

+ + Learn React + +
+
+ ); +} + +export default App; diff --git a/src/main/frontend/src/App.test.js b/src/main/frontend/src/App.test.js new file mode 100644 index 0000000..1f03afe --- /dev/null +++ b/src/main/frontend/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/src/main/frontend/src/index.css b/src/main/frontend/src/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/src/main/frontend/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/main/frontend/src/index.js b/src/main/frontend/src/index.js new file mode 100644 index 0000000..d563c0f --- /dev/null +++ b/src/main/frontend/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/main/frontend/src/logo.svg b/src/main/frontend/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/src/main/frontend/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/frontend/src/reportWebVitals.js b/src/main/frontend/src/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/src/main/frontend/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/src/main/frontend/src/setupTests.js b/src/main/frontend/src/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/src/main/frontend/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/src/main/java/org/camunda/automator/AutomatorAPI.java b/src/main/java/org/camunda/automator/AutomatorAPI.java index 41ae54f..82c4130 100644 --- a/src/main/java/org/camunda/automator/AutomatorAPI.java +++ b/src/main/java/org/camunda/automator/AutomatorAPI.java @@ -7,14 +7,16 @@ package org.camunda.automator; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; import org.camunda.automator.bpmnengine.BpmnEngineFactory; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.Scenario; import org.camunda.automator.engine.AutomatorException; import org.camunda.automator.engine.RunParameters; import org.camunda.automator.engine.RunResult; import org.camunda.automator.engine.RunScenario; import org.camunda.automator.services.ServiceAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -22,6 +24,7 @@ @Component public class AutomatorAPI { + static Logger logger = LoggerFactory.getLogger(AutomatorAPI.class); @Autowired ServiceAccess serviceAccess; @@ -48,17 +51,42 @@ public Scenario loadFromFile(File scenarioFile) throws AutomatorException { return Scenario.createFromFile(scenarioFile); } + /** + * Search the engine from the scenario + * + * @param scenario scenario + * @param engineConfiguration different engine configuration + * @return the engine, null if no engine exist, an exception if the connection is not possible + */ + public BpmnEngine getBpmnEngineFromScenario(Scenario scenario, ConfigurationBpmEngine engineConfiguration) + throws AutomatorException { + try { + + if (scenario.getServerName() != null) { + return getBpmnEngine(engineConfiguration, engineConfiguration.getByServerName(scenario.getServerName())); + } + if (scenario.getServerType() != null) { + return getBpmnEngine(engineConfiguration, engineConfiguration.getByServerType(scenario.getServerType())); + } + return null; + } catch (AutomatorException e) { + logger.error("Can't connect the engine for the scenario [{}] serverName[{}] serverType[{}] : {}", + scenario.getName(), scenario.getServerName(), scenario.getServerType(), e.getMessage()); + throw e; + } + + } + /** * Execute a scenario * - * @param bpmnEngine Access the Camunda engine - * @param runParameters parameters use to run the scenario - * @param scenario the scenario to execute + * @param bpmnEngine Access the Camunda engine. if null, then the value in the scenario are used + * @param runParameters parameters use to run the scenario + * @param scenario the scenario to execute */ - public RunResult executeScenario(BpmnEngine bpmnEngine, - RunParameters runParameters, - Scenario scenario) { + public RunResult executeScenario(BpmnEngine bpmnEngine, RunParameters runParameters, Scenario scenario) { RunScenario runScenario = null; + try { runScenario = new RunScenario(scenario, bpmnEngine, runParameters, serviceAccess); } catch (Exception e) { @@ -68,7 +96,7 @@ public RunResult executeScenario(BpmnEngine bpmnEngine, } RunResult runResult = new RunResult(runScenario); - runResult.add( runScenario.runScenario()); + runResult.add(runScenario.runScenario()); return runResult; } @@ -82,27 +110,22 @@ public RunResult executeScenario(BpmnEngine bpmnEngine, /* Deploy a process in the server */ /* ******************************************************************** */ - public BpmnEngine getBpmnEngine(BpmnEngineConfiguration engineConfiguration, - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition) { - try { - return BpmnEngineFactory.getInstance() - .getEngineFromConfiguration(engineConfiguration, serverDefinition); + public BpmnEngine getBpmnEngine(ConfigurationBpmEngine engineConfiguration, + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition) + throws AutomatorException { - } catch (Exception e) { - return null; - } + return BpmnEngineFactory.getInstance().getEngineFromConfiguration(engineConfiguration, serverDefinition); } /** * Deploy a process, bpmEngine is given by the caller - * @param bpmnEngine Engine to deploy + * + * @param bpmnEngine Engine to deploy * @param runParameters parameters used to deploy the version - * @param scenario scenario + * @param scenario scenario * @return the result object */ - public RunResult deployProcess(BpmnEngine bpmnEngine, - RunParameters runParameters, - Scenario scenario) { + public RunResult deployProcess(BpmnEngine bpmnEngine, RunParameters runParameters, Scenario scenario) { RunScenario runScenario = null; try { long begin = System.currentTimeMillis(); @@ -113,7 +136,7 @@ public RunResult deployProcess(BpmnEngine bpmnEngine, return runResult; } catch (Exception e) { RunResult result = new RunResult(runScenario); - result.addError(null, "Process deployment error error "+e.getMessage()); + result.addError(null, "Process deployment error error " + e.getMessage()); return result; } diff --git a/src/main/java/org/camunda/automator/AutomatorCLI.java b/src/main/java/org/camunda/automator/AutomatorCLI.java index 73f8ad8..0fdedb6 100644 --- a/src/main/java/org/camunda/automator/AutomatorCLI.java +++ b/src/main/java/org/camunda/automator/AutomatorCLI.java @@ -1,7 +1,7 @@ package org.camunda.automator; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.Scenario; import org.camunda.automator.engine.RunParameters; import org.camunda.automator.engine.RunResult; @@ -27,7 +27,7 @@ public class AutomatorCLI implements CommandLineRunner { @Autowired AutomatorAPI automatorAPI; @Autowired - BpmnEngineConfiguration engineConfiguration; + ConfigurationBpmEngine engineConfiguration; public static void main(String[] args) { isRunningCLI = true; @@ -89,7 +89,7 @@ private static void printUsage() { } - private static BpmnEngineConfiguration decodeConfiguration(String propertiesFileName) throws Exception { + private static ConfigurationBpmEngine decodeConfiguration(String propertiesFileName) throws Exception { throw new Exception("Not yet implemented"); } @@ -106,6 +106,8 @@ private static List detectRecursiveScenario(File folderRecursive) { } public void run(String[] args) { + if (!isRunningCLI) + return; File scenarioFile = null; File folderRecursive = null; @@ -142,14 +144,14 @@ public void run(String[] args) { } else if ("-d".equals(args[i]) || "--deploy".equals(args[i])) { if (args.length < i + 1) throw new Exception("Bad usage : -d TRUE|FALSE"); - runParameters.allowDeployment = "TRUE".equalsIgnoreCase(args[i+1]); + runParameters.deploymentProcess = "TRUE".equalsIgnoreCase(args[i + 1]); i++; } else if ("-x".equals(args[i]) || "--execute".equals(args[i])) { runParameters.execution = true; } else if ("-v".equals(args[i]) || "--verification".equals(args[i])) { runParameters.verification = true; } else if ("-f".equals(args[i]) || "--fullreport".equals(args[i])) { - runParameters.fullDetailsSythesis= true; + runParameters.fullDetailsSythesis = true; } else if ("run".equals(args[i])) { if (args.length < i + 1) throw new Exception("Bad usage : run "); @@ -177,7 +179,7 @@ public void run(String[] args) { } // get the correct server configuration - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition = null; + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition = null; if (serverName != null) { serverDefinition = engineConfiguration.getByServerName(serverName); @@ -186,7 +188,7 @@ public void run(String[] args) { + "] does not exist in the list of servers in application.yaml file"); } } else { - List listServers = engineConfiguration.decodeListServersConnection(); + List listServers = engineConfiguration.getListServers(); serverDefinition = listServers.isEmpty() ? null : listServers.get(0); } @@ -200,7 +202,9 @@ public void run(String[] args) { switch (action) { case RUN -> { Scenario scenario = automatorAPI.loadFromFile(scenarioFile); - RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); + BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngineFromScenario(scenario, engineConfiguration); + RunResult scenarioExecutionResult = automatorAPI.executeScenario( + bpmnEngineScenario == null ? bpmnEngine : bpmnEngineScenario, runParameters, scenario); logger.info(scenarioExecutionResult.getSynthesis(runParameters.fullDetailsSythesis)); } @@ -208,7 +212,9 @@ public void run(String[] args) { List listScenario = detectRecursiveScenario(folderRecursive); for (File scenarioFileIndex : listScenario) { Scenario scenario = automatorAPI.loadFromFile(scenarioFileIndex); - RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); + BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngineFromScenario(scenario, engineConfiguration); + RunResult scenarioExecutionResult = automatorAPI.executeScenario( + bpmnEngineScenario == null ? bpmnEngine : bpmnEngineScenario, runParameters, scenario); logger.info(scenarioExecutionResult.getSynthesis(false)); } diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java index 34aabf5..6b3b861 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java @@ -1,33 +1,48 @@ package org.camunda.automator.bpmnengine; +import io.camunda.operate.search.DateFilter; +import io.camunda.zeebe.client.api.worker.JobWorker; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.ScenarioDeployment; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.flow.FixedBackoffSupplier; +import org.camunda.bpm.client.topic.TopicSubscription; import java.io.File; +import java.time.Duration; import java.util.List; import java.util.Map; public interface BpmnEngine { /** - * init or reinit the connection + * init the engine. This method will + * * @throws Exception in case of error */ - void init() throws AutomatorException; + void init(); + public void connection() throws AutomatorException; + + public void disconnection() throws AutomatorException; + /** + * Engine is ready. If not, a connection() method must be call + * @return + */ + public boolean isReady(); /* ******************************************************************** */ /* */ /* Manage process instance */ /* */ /* ******************************************************************** */ + void turnHighFlowMode(boolean hightFlowMode); /** - * - * @param processId Process ID (BPMN ID : ExpenseNode) + * @param processId Process ID (BPMN ID : ExpenseNode) * @param starterEventId BPMN ID (startEvent) - * @param variables List of variables to create the process instance + * @param variables List of variables to create the process instance * @return a processInstanceId * @throws AutomatorException in case of error */ @@ -36,11 +51,12 @@ String createProcessInstance(String processId, String starterEventId, Map searchUserTasks(String processInstanceId, String userTaskId, int maxResult) throws AutomatorException; /** - * * @param userTaskId BPMN Id (Review) - * @param userId User id who executes the task - * @param variables variable to update + * @param userId User id who executes the task + * @param variables variable to update * @throws AutomatorException in case of error */ void executeUserTask(String userTaskId, String userId, Map variables) throws AutomatorException; @@ -75,25 +89,67 @@ String createProcessInstance(String processId, String starterEventId, Map searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException; + List searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + throws AutomatorException; /** * Execute a service task + * * @param serviceTaskId BPMN ID (Review) - * @param workerId Worker who execute the task - * @param variables variable to updates + * @param workerId Worker who execute the task + * @param variables variable to updates * @throws AutomatorException in case of error */ - void executeServiceTask(String serviceTaskId, String workerId, Map variables) throws AutomatorException; + void executeServiceTask(String serviceTaskId, String workerId, Map variables) + throws AutomatorException; /* ******************************************************************** */ /* */ @@ -103,33 +159,50 @@ String createProcessInstance(String processId, String starterEventId, Map searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) throws AutomatorException; + List searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) + throws AutomatorException; /** * Search process instance by a variable content - * @param processId BPMN Process ID + * + * @param processId BPMN Process ID * @param filterVariables Variable name - * @param maxResult maxResult + * @param maxResult maxResult * @return list of ProcessInstance which match the filter * @throws AutomatorException in case of error */ - List searchProcessInstanceByVariable(String processId, Map filterVariables, int maxResult) throws AutomatorException; + List searchProcessInstanceByVariable(String processId, + Map filterVariables, + int maxResult) throws AutomatorException; + /** + * Get variables of a process instanceId + * + * @param processInstanceId the process instance ID + * @return variables attached to the process instance ID + * @throws AutomatorException in case of error + */ + Map getVariables(String processInstanceId) throws AutomatorException; + + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException; - /** - * Get variables of a process instanceId - * @param processInstanceId the process instance ID - * @return variables attached to the process instance ID - * @throws AutomatorException in case of error - */ - Map getVariables(String processInstanceId) throws AutomatorException; + long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException; + long countNumberOfTasks(String processId, String taskId) throws AutomatorException; /* ******************************************************************** */ /* */ @@ -139,12 +212,13 @@ String createProcessInstance(String processId, String starterEventId, Map serversConnection; - public List servers; - - public List> serversMap; - - public enum CamundaEngine {CAMUNDA_7, CAMUNDA_8, CAMUNDA_8_SAAS, DUMMY} - - public BpmnEngineConfiguration.BpmnServerDefinition getByServerName(String serverName) throws AutomatorException { - // decode the serverConnections - List listFromConnection = decodeListServersConnection(); - List allServers = new ArrayList<>(); - if (listFromConnection != null) - allServers.addAll(listFromConnection); - if (servers != null) - allServers.addAll(servers); - - for (BpmnEngineConfiguration.BpmnServerDefinition serverIndex : allServers) { - if (serverName.equals(serverIndex.name)) - return serverIndex; - } - return null; - } - - public List decodeListServersConnection() throws AutomatorException { - if (serversConnection == null) - return null; - - // not possible to use a Stream: decode throw an exception - List list = new ArrayList<>(); - for (String s : serversConnection) { - BpmnServerDefinition bpmnServerDefinition = decodeServerConnection(s); - list.add(bpmnServerDefinition); - } - return list; - } - - public BpmnServerDefinition decodeServerConnection(String connectionString) throws AutomatorException { - StringTokenizer st = new StringTokenizer(connectionString, ","); - BpmnServerDefinition bpmnServerDefinition = new BpmnServerDefinition(); - bpmnServerDefinition.name = (st.hasMoreTokens() ? st.nextToken() : null); - try { - bpmnServerDefinition.camundaEngine = (st.hasMoreTokens() ? CamundaEngine.valueOf(st.nextToken()) : null); - if (CamundaEngine.CAMUNDA_7.equals(bpmnServerDefinition.camundaEngine)) { - bpmnServerDefinition.serverUrl = (st.hasMoreTokens() ? st.nextToken() : null); - - } else if (CamundaEngine.CAMUNDA_8.equals(bpmnServerDefinition.camundaEngine)) { - bpmnServerDefinition.zeebeGatewayAddress = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUrl = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUserName = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUserPassword = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.tasklistUrl = (st.hasMoreTokens() ? st.nextToken() : null); - - } else if (CamundaEngine.CAMUNDA_8_SAAS.equals(bpmnServerDefinition.camundaEngine)) { - bpmnServerDefinition.zeebeCloudRegister = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeCloudRegion = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeCloudClusterId = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeCloudClientId = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.clientSecret = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUrl = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUserName = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUserPassword = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.tasklistUrl = (st.hasMoreTokens() ? st.nextToken() : null); - } - return bpmnServerDefinition; - } catch( Exception e){ - throw new AutomatorException("Can't decode string ["+connectionString+"] "+e.getMessage()); - } - } - -} diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java index 2fcd074..8505844 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java @@ -1,79 +1,73 @@ package org.camunda.automator.bpmnengine; -import java.util.ArrayList; +import org.camunda.automator.configuration.ConfigurationBpmEngine; /** * Generate BpmnEngineConfiguration for different servers */ public class BpmnEngineConfigurationInstance { - public static BpmnEngineConfiguration getZeebeSaas(String zeebeGatewayAddress, String zeebeSecurityPlainText) { - BpmnEngineConfiguration bpmEngineConfiguration = new BpmnEngineConfiguration(); + public static ConfigurationBpmEngine getZeebeSaas(String zeebeGatewayAddress, String zeebeSecurityPlainText) { + ConfigurationBpmEngine bpmEngineConfiguration = new ConfigurationBpmEngine(); - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition = new BpmnEngineConfiguration.BpmnServerDefinition(); - serverDefinition.camundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8; + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition = new ConfigurationBpmEngine.BpmnServerDefinition(); + serverDefinition.serverType = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8; serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; serverDefinition.zeebeSecurityPlainText = zeebeSecurityPlainText; - bpmEngineConfiguration.servers = new ArrayList<>(); - bpmEngineConfiguration.servers.add( serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); return bpmEngineConfiguration; } - public static BpmnEngineConfiguration getCamunda7(String serverUrl) { - BpmnEngineConfiguration bpmEngineConfiguration = new BpmnEngineConfiguration(); + public static ConfigurationBpmEngine getCamunda7(String serverUrl) { + ConfigurationBpmEngine bpmEngineConfiguration = new ConfigurationBpmEngine(); - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition = new BpmnEngineConfiguration.BpmnServerDefinition(); - serverDefinition.camundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_7; - serverDefinition.serverUrl = serverUrl; + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition = new ConfigurationBpmEngine.BpmnServerDefinition(); + serverDefinition.serverType = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_7; + serverDefinition.camunda7ServerUrl = serverUrl; - bpmEngineConfiguration.servers = new ArrayList<>(); - bpmEngineConfiguration.servers.add( serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); return bpmEngineConfiguration; } - public static BpmnEngineConfiguration getCamunda8(String zeebeGatewayAddress) { - BpmnEngineConfiguration bpmEngineConfiguration = new BpmnEngineConfiguration(); + public static ConfigurationBpmEngine getCamunda8(String zeebeGatewayAddress) { + ConfigurationBpmEngine bpmEngineConfiguration = new ConfigurationBpmEngine(); - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition = new BpmnEngineConfiguration.BpmnServerDefinition(); - serverDefinition.camundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8; + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition = new ConfigurationBpmEngine.BpmnServerDefinition(); + serverDefinition.serverType = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8; serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; - bpmEngineConfiguration.servers = new ArrayList<>(); - bpmEngineConfiguration.servers.add( serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); return bpmEngineConfiguration; } - public static BpmnEngineConfiguration getCamundaSaas8(String zeebeCloudRegister, - String zeebeCloudRegion, - String zeebeCloudClusterId, - String zeebeCloudClientId) { - BpmnEngineConfiguration bpmEngineConfiguration = new BpmnEngineConfiguration(); + public static ConfigurationBpmEngine getCamundaSaas8(String zeebeCloudRegister, + String zeebeCloudRegion, + String zeebeCloudClusterId, + String zeebeCloudClientId) { + ConfigurationBpmEngine bpmEngineConfiguration = new ConfigurationBpmEngine(); - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition = new BpmnEngineConfiguration.BpmnServerDefinition(); - serverDefinition.camundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8; + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition = new ConfigurationBpmEngine.BpmnServerDefinition(); + serverDefinition.serverType = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8; serverDefinition.zeebeCloudRegister = zeebeCloudRegister; serverDefinition.zeebeCloudRegion = zeebeCloudRegion; serverDefinition.zeebeCloudClusterId = zeebeCloudClusterId; serverDefinition.zeebeCloudClientId = zeebeCloudClientId; - bpmEngineConfiguration.servers = new ArrayList<>(); - bpmEngineConfiguration.servers.add( serverDefinition); - + bpmEngineConfiguration.addExplicitServer(serverDefinition); return bpmEngineConfiguration; } - public static BpmnEngineConfiguration getDummy() { - BpmnEngineConfiguration bpmEngineConfiguration = new BpmnEngineConfiguration(); + public static ConfigurationBpmEngine getDummy() { + ConfigurationBpmEngine bpmEngineConfiguration = new ConfigurationBpmEngine(); - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition = new BpmnEngineConfiguration.BpmnServerDefinition(); - serverDefinition.camundaEngine = BpmnEngineConfiguration.CamundaEngine.DUMMY; + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition = new ConfigurationBpmEngine.BpmnServerDefinition(); + serverDefinition.serverType = ConfigurationBpmEngine.CamundaEngine.DUMMY; - bpmEngineConfiguration.servers = new ArrayList<>(); - bpmEngineConfiguration.servers.add( serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); return bpmEngineConfiguration; } diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java index 3cdaf8d..60596e7 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java @@ -9,6 +9,7 @@ import org.camunda.automator.bpmnengine.camunda7.BpmnEngineCamunda7; import org.camunda.automator.bpmnengine.camunda8.BpmnEngineCamunda8; import org.camunda.automator.bpmnengine.dummy.BpmnEngineDummy; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.engine.AutomatorException; /** @@ -20,27 +21,20 @@ public static BpmnEngineFactory getInstance() { return new BpmnEngineFactory(); } - public BpmnEngine getEngineFromConfiguration(BpmnEngineConfiguration engineConfiguration, BpmnEngineConfiguration.BpmnServerDefinition serverDefinition) throws - AutomatorException { - BpmnEngine engine = null; - switch (serverDefinition.camundaEngine) { - case CAMUNDA_7 -> - engine = new BpmnEngineCamunda7(engineConfiguration,serverDefinition); + public BpmnEngine getEngineFromConfiguration(ConfigurationBpmEngine engineConfiguration, + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition) + throws AutomatorException { + BpmnEngine engine = switch (serverDefinition.serverType) { + case CAMUNDA_7 -> new BpmnEngineCamunda7(engineConfiguration, serverDefinition); - case CAMUNDA_8 -> - engine = new BpmnEngineCamunda8(engineConfiguration,serverDefinition); + case CAMUNDA_8 -> new BpmnEngineCamunda8(engineConfiguration, serverDefinition); - case CAMUNDA_8_SAAS -> - engine = new BpmnEngineCamunda8(engineConfiguration,serverDefinition); + case CAMUNDA_8_SAAS -> new BpmnEngineCamunda8(engineConfiguration, serverDefinition); - case DUMMY -> - engine = new BpmnEngineDummy(engineConfiguration); + case DUMMY -> new BpmnEngineDummy(engineConfiguration); + + }; - } - if (engine == null) { - throw new AutomatorException("No engine is defined : use [" + BpmnEngineConfiguration.CamundaEngine.CAMUNDA_7 + "," - + BpmnEngineConfiguration.CamundaEngine.DUMMY + "] values"); - } engine.init(); return engine; } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java index 42ea4c5..46dd8db 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java @@ -1,11 +1,17 @@ package org.camunda.automator.bpmnengine.camunda7; +import io.camunda.operate.search.DateFilter; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.ScenarioDeployment; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.flow.FixedBackoffSupplier; +import org.camunda.bpm.client.ExternalTaskClient; +import org.camunda.bpm.client.backoff.ExponentialBackoffStrategy; +import org.camunda.bpm.client.task.ExternalTaskHandler; import org.camunda.community.rest.client.api.DeploymentApi; +import org.camunda.community.rest.client.api.EngineApi; import org.camunda.community.rest.client.api.ExternalTaskApi; import org.camunda.community.rest.client.api.ProcessDefinitionApi; import org.camunda.community.rest.client.api.ProcessInstanceApi; @@ -17,12 +23,15 @@ import org.camunda.community.rest.client.dto.ExternalTaskDto; import org.camunda.community.rest.client.dto.ExternalTaskQueryDto; import org.camunda.community.rest.client.dto.LockExternalTaskDto; +import org.camunda.community.rest.client.dto.ProcessEngineDto; import org.camunda.community.rest.client.dto.ProcessInstanceDto; import org.camunda.community.rest.client.dto.ProcessInstanceQueryDto; +import org.camunda.community.rest.client.dto.ProcessInstanceQueryDtoSorting; import org.camunda.community.rest.client.dto.ProcessInstanceWithVariablesDto; import org.camunda.community.rest.client.dto.StartProcessInstanceDto; import org.camunda.community.rest.client.dto.TaskDto; import org.camunda.community.rest.client.dto.TaskQueryDto; +import org.camunda.community.rest.client.dto.TaskQueryDtoSorting; import org.camunda.community.rest.client.dto.UserIdDto; import org.camunda.community.rest.client.dto.VariableInstanceDto; import org.camunda.community.rest.client.dto.VariableInstanceQueryDto; @@ -34,6 +43,8 @@ import org.slf4j.LoggerFactory; import java.io.File; +import java.time.Duration; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -48,46 +59,109 @@ public class BpmnEngineCamunda7 implements BpmnEngine { private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda7.class); private final String serverUrl; + private final String userName; + private final String password; + + private final int workerMaxJobsActive; private final boolean logDebug; + public static final int SEARCH_MAX_SIZE = 100; - ApiClient client = null; + ApiClient apiClient = null; ProcessDefinitionApi processDefinitionApi; TaskApi taskApi; ExternalTaskApi externalTaskApi; ProcessInstanceApi processInstanceApi; VariableInstanceApi variableInstanceApi; DeploymentApi deploymentApi; - - public BpmnEngineCamunda7(BpmnEngineConfiguration engineConfiguration, - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition) { - this.serverUrl = serverDefinition.serverUrl; + EngineApi engineApi; + + public BpmnEngineCamunda7(ConfigurationBpmEngine engineConfiguration, + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition) { + this.serverUrl = serverDefinition.camunda7ServerUrl; + this.userName = serverDefinition.camunda7UserName; + this.password = serverDefinition.camunda7Password; + this.workerMaxJobsActive = serverDefinition.workerMaxJobsActive; this.logDebug = engineConfiguration.logDebug; init(); } - @Override - public void init() { - client = new ApiClient(); - client.setBasePath(serverUrl); - processDefinitionApi = new ProcessDefinitionApi(); - taskApi = new TaskApi(); - externalTaskApi = new ExternalTaskApi(); - processInstanceApi = new ProcessInstanceApi(); - variableInstanceApi = new VariableInstanceApi(); - deploymentApi = new DeploymentApi(); - } - /** * P * * @param serverUrl is "http://localhost:8080/engine-rest" */ - public BpmnEngineCamunda7(String serverUrl, boolean logDebug) { + public BpmnEngineCamunda7(String serverUrl, String userName, String password, boolean logDebug) { this.serverUrl = serverUrl; + this.userName = userName; + this.password = password; + this.workerMaxJobsActive = 1; this.logDebug = logDebug; init(); } + @Override + public void init() { + apiClient = new ApiClient(); + apiClient.setBasePath(serverUrl); + if (!userName.trim().isEmpty()) { + apiClient.setUsername(userName); + apiClient.setPassword(password); + } else { + } + + processDefinitionApi = new ProcessDefinitionApi(apiClient); + + taskApi = new TaskApi(apiClient); + + externalTaskApi = new ExternalTaskApi(apiClient); + + processInstanceApi = new ProcessInstanceApi(apiClient); + + variableInstanceApi = new VariableInstanceApi(apiClient); + + deploymentApi = new DeploymentApi(apiClient); + + engineApi = new EngineApi(apiClient); + } + + private int count = 0; + + public void connection() throws AutomatorException { + count++; + // we verify if we have the connection + // logger.info("Connection to Camunda7 server[{}] User[{}] password[***]", serverUrl, userName); + if (count > 2) + return; + try { + engineApi.getProcessEngineNames(); + logger.info("Connection successfully to Camunda7 [{}] ", apiClient.getBasePath()); + } catch (ApiException e) { + logger.error("Can't connect Camunda7 server[{}] User[{}]: {}", apiClient.getBasePath(), userName, e.toString()); + throw new AutomatorException("Can't connect to Camunda7 [" + apiClient.getBasePath() + "] : " + e.toString()); + } + } + + public void disconnection() throws AutomatorException { + // nothing to do here + } + + /** + * Engine is ready. If not, a connection() method must be call + * + * @return + */ + public boolean isReady() { + if (count > 2) + return true; + + try { + engineApi.getProcessEngineNames(); + } catch (ApiException e) { + // no need to log, connect will be called + return false; + } + return true; + } /* ******************************************************************** */ /* */ @@ -101,13 +175,15 @@ public String createProcessInstance(String processId, String starterEventId, Map if (logDebug) { logger.info("BpmnEngine7.CreateProcessInstance: Process[" + processId + "] StartEvent[" + starterEventId + "]"); } + String dateString = dateToString(new Date()); + Map variablesApi = new HashMap<>(); for (Map.Entry entry : variables.entrySet()) { variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); } try { ProcessInstanceWithVariablesDto processInstanceDto = processDefinitionApi.startProcessInstanceByKey(processId, - new StartProcessInstanceDto().variables(variablesApi)); + new StartProcessInstanceDto().variables(variablesApi).businessKey(dateString)); return processInstanceDto.getId(); } catch (ApiException e) { throw new AutomatorException( @@ -175,12 +251,41 @@ public void executeUserTask(String userTaskId, String userId, Map searchTasksByProcessInstanceId(String processInstan public List searchProcessInstanceByVariable(String processId, Map filterVariables, int maxResult) throws AutomatorException { - return null; + return Collections.emptyList(); } @Override @@ -308,19 +413,93 @@ public Map getVariables(String processInstanceId) throws Automat VariableInstanceQueryDto variableQuery = new VariableInstanceQueryDto(); variableQuery.processInstanceIdIn(List.of(processInstanceId)); try { - List variableInstanceDtos = variableInstanceApi.queryVariableInstances(0, 1000, true, - variableQuery); + List variableInstanceDtos = variableInstanceApi.queryVariableInstances(0, 1000, true, + variableQuery); - Map variables = new HashMap<>(); - for (VariableInstanceDto variable : variableInstanceDtos) { - variables.put(variable.getName(), variable.getValue()); - } - return variables; + Map variables = new HashMap<>(); + for (VariableInstanceDto variable : variableInstanceDtos) { + variables.put(variable.getName(), variable.getValue()); + } + return variables; } catch (ApiException e) { throw new AutomatorException("Can't searchVariables", e); } } + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + + @Override + public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + + try { + int cumul = 0; + ProcessInstanceQueryDto processInstanceQuery = new ProcessInstanceQueryDto(); + processInstanceQuery = processInstanceQuery.addProcessDefinitionKeyInItem(processName); + processInstanceQuery.addSortingItem( + new ProcessInstanceQueryDtoSorting().sortBy(ProcessInstanceQueryDtoSorting.SortByEnum.INSTANCEID) + .sortOrder(ProcessInstanceQueryDtoSorting.SortOrderEnum.ASC)); + + int maxLoop = 0; + int firstResult = 0; + List processInstanceDtos; + do { + maxLoop++; + processInstanceDtos = processInstanceApi.queryProcessInstances(firstResult, SEARCH_MAX_SIZE, + processInstanceQuery); + firstResult += processInstanceDtos.size(); + cumul += processInstanceDtos.stream().filter(t -> { + Date datePI = stringToDate(t.getBusinessKey()); + if (datePI == null) + return false; + return datePI.after(startDate.getDate()); + }).count(); + + } while (processInstanceDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + + } catch (Exception e) { + throw new AutomatorException("Error during countNumberOfProcessInstancesCreated"); + + } + } + + @Override + public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } + + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + try { + int cumul = 0; + TaskQueryDto taskQueryDto = new TaskQueryDto(); + taskQueryDto = taskQueryDto.addProcessDefinitionKeyInItem(processId); + taskQueryDto.addSortingItem(new TaskQueryDtoSorting().sortBy(TaskQueryDtoSorting.SortByEnum.INSTANCEID) + .sortOrder(TaskQueryDtoSorting.SortOrderEnum.ASC)); + + int maxLoop = 0; + int firstResult = 0; + List taskDtos; + do { + maxLoop++; + taskDtos = taskApi.queryTasks(firstResult, SEARCH_MAX_SIZE, taskQueryDto); + + firstResult += taskDtos.size(); + cumul += taskDtos.size(); + + } while (taskDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + + } catch (Exception e) { + throw new AutomatorException("Error during countNumberOfTasks"); + + } + } /* ******************************************************************** */ /* */ @@ -354,16 +533,50 @@ public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) thr /* ******************************************************************** */ @Override - public BpmnEngineConfiguration.CamundaEngine getTypeCamundaEngine() { - return BpmnEngineConfiguration.CamundaEngine.CAMUNDA_7; + public ConfigurationBpmEngine.CamundaEngine getTypeCamundaEngine() { + return ConfigurationBpmEngine.CamundaEngine.CAMUNDA_7; + } + + @Override + public String getSignature() { + return ConfigurationBpmEngine.CamundaEngine.CAMUNDA_7 + " " + "serverUrl[" + serverUrl + "]"; + } + + @Override + public int getWorkerExecutionThreads() { + return workerMaxJobsActive; + } + + public void turnHighFlowMode(boolean hightFlowMode) { + } + + private String getUniqWorkerId() { + return Thread.currentThread().getName() + "-" + System.currentTimeMillis(); } /** - * Call back asynchronous + * Collect all subprocess for a process instance + * + * @param rootProcessInstance root process instance + * @return list of SubProcess ID + * @throws AutomatorException if any errors arrive */ - public class ExternalCallBack implements ApiCallback { + private List getListSubProcessInstance(String rootProcessInstance) throws AutomatorException { + ProcessInstanceQueryDto processInstanceQueryDto = new ProcessInstanceQueryDto(); + processInstanceQueryDto.superProcessInstance(rootProcessInstance); + List processInstanceDtos; + try { + processInstanceDtos = processInstanceApi.queryProcessInstances(0, 100000, processInstanceQueryDto); + } catch (ApiException e) { + throw new AutomatorException("Can't searchSubProcess", e); + } + return processInstanceDtos.stream().map(ProcessInstanceDto::getId).toList(); + } - public enum STATUS {WAIT, FAILURE, SUCCESS} + /** + * Call back asynchronous + */ + public static class ExternalCallBack implements ApiCallback { public STATUS status = STATUS.WAIT; public ApiException e; @@ -388,28 +601,17 @@ public void onUploadProgress(long l, long l1, boolean b) { public void onDownloadProgress(long l, long l1, boolean b) { } + + public enum STATUS {WAIT, FAILURE, SUCCESS} } - private String getUniqWorkerId() { - return Thread.currentThread().getName() + "-" + System.currentTimeMillis(); + private String dateToString(Date date) { + return String.valueOf(date.getTime()); } - /** - * Collect all subprocess for a process instance - * - * @param rootProcessInstance root process instance - * @return list of SubProcess ID - * @throws AutomatorException if any errors arrive - */ - private List getListSubProcessInstance(String rootProcessInstance) throws AutomatorException { - ProcessInstanceQueryDto processInstanceQueryDto = new ProcessInstanceQueryDto(); - processInstanceQueryDto.superProcessInstance(rootProcessInstance); - List processInstanceDtos; - try { - processInstanceDtos = processInstanceApi.queryProcessInstances(0, 100000, processInstanceQueryDto); - } catch (ApiException e) { - throw new AutomatorException("Can't searchSubProcess", e); - } - return processInstanceDtos.stream().map(ProcessInstanceDto::getId).toList(); + private Date stringToDate(String dateSt) { + if (dateSt == null) + return null; + return new Date(Long.valueOf(dateSt)); } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java new file mode 100644 index 0000000..f8a5584 --- /dev/null +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java @@ -0,0 +1,41 @@ +package org.camunda.automator.bpmnengine.camunda8; + +import io.camunda.zeebe.client.api.worker.BackoffSupplier; +import io.camunda.zeebe.spring.client.jobhandling.CommandWrapper; +import io.camunda.zeebe.spring.client.jobhandling.DefaultCommandExceptionHandlingStrategy; +import io.grpc.StatusRuntimeException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import java.util.concurrent.Executors; + +@Primary +@Component +public class BenchmarkCompleteJobExceptionHandlingStrategy extends DefaultCommandExceptionHandlingStrategy { + + @Autowired + private StatisticsCollector stats; + + public BenchmarkCompleteJobExceptionHandlingStrategy(@Autowired BackoffSupplier backoffSupplier) { + super(backoffSupplier, Executors.newScheduledThreadPool(1)); + } + + @Override + public void handleCommandError(CommandWrapper command, Throwable throwable) { + if (StatusRuntimeException.class.isAssignableFrom(throwable.getClass())) { + StatusRuntimeException exception = (StatusRuntimeException) throwable; + stats.incCompletedJobsException(exception.getStatus().getCode().name()); + /* Backpressure on Job completion cannot happen at the moment (whitelisted) + if (Status.Code.RESOURCE_EXHAUSTED == exception.getStatus().getCode()) { + stats.getBackpressureOnJobCompleteMeter().mark(); + return; + }*/ + } else { + stats.incCompletedJobsException(throwable.getMessage()); + } + + // use normal behavior, e.g. increasing back-off for backpressure + super.handleCommandError(command, throwable); + } +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java new file mode 100644 index 0000000..585b02e --- /dev/null +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java @@ -0,0 +1,38 @@ +package org.camunda.automator.bpmnengine.camunda8; + +import io.camunda.zeebe.client.api.worker.BackoffSupplier; +import io.camunda.zeebe.spring.client.jobhandling.CommandWrapper; +import io.camunda.zeebe.spring.client.jobhandling.DefaultCommandExceptionHandlingStrategy; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.Executors; + +@Component +public class BenchmarkStartPiExceptionHandlingStrategy extends DefaultCommandExceptionHandlingStrategy { + + @Autowired + private StatisticsCollector stats; + + public BenchmarkStartPiExceptionHandlingStrategy(@Autowired BackoffSupplier backoffSupplier) { + super(backoffSupplier, Executors.newScheduledThreadPool(1)); + } + + @Override + public void handleCommandError(CommandWrapper command, Throwable throwable) { + if (StatusRuntimeException.class.isAssignableFrom(throwable.getClass())) { + StatusRuntimeException exception = (StatusRuntimeException) throwable; + stats.incStartedProcessInstancesException(exception.getStatus().getCode().name()); + if (Status.Code.RESOURCE_EXHAUSTED == exception.getStatus().getCode()) { + stats.incStartedProcessInstancesBackpressure(); + return; // ignore backpressure, as we don't want to add a big wave of retries + } + } else { + stats.incStartedProcessInstancesException(throwable.getMessage()); + } + // use normal behavior + super.handleCommandError(command, throwable); + } +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java index 5fe99eb..0f07b28 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java @@ -4,10 +4,15 @@ import io.camunda.operate.dto.FlownodeInstance; import io.camunda.operate.dto.FlownodeInstanceState; import io.camunda.operate.dto.ProcessInstance; +import io.camunda.operate.dto.ProcessInstanceState; +import io.camunda.operate.dto.SearchResult; import io.camunda.operate.exception.OperateException; +import io.camunda.operate.search.DateFilter; import io.camunda.operate.search.FlownodeInstanceFilter; import io.camunda.operate.search.ProcessInstanceFilter; import io.camunda.operate.search.SearchQuery; +import io.camunda.operate.search.Sort; +import io.camunda.operate.search.SortOrder; import io.camunda.operate.search.VariableFilter; import io.camunda.tasklist.CamundaTaskListClient; import io.camunda.tasklist.dto.Pagination; @@ -19,19 +24,30 @@ import io.camunda.tasklist.exception.TaskListException; import io.camunda.zeebe.client.ZeebeClient; import io.camunda.zeebe.client.ZeebeClientBuilder; +import io.camunda.zeebe.client.api.command.FinalCommandStep; import io.camunda.zeebe.client.api.response.ActivateJobsResponse; import io.camunda.zeebe.client.api.response.ActivatedJob; import io.camunda.zeebe.client.api.response.DeploymentEvent; import io.camunda.zeebe.client.api.response.ProcessInstanceEvent; +import io.camunda.zeebe.client.api.worker.JobHandler; +import io.camunda.zeebe.client.api.worker.JobWorker; +import io.camunda.zeebe.client.api.worker.JobWorkerBuilderStep1; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.bpmnengine.camunda8.refactoring.RefactoredCommandWrapper; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.ScenarioDeployment; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.flow.FixedBackoffSupplier; +import org.camunda.automator.engine.flow.RunScenarioFlowServiceTask; +import org.camunda.bpm.client.task.ExternalTaskHandler; +import org.camunda.community.rest.client.invoker.ApiException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import java.io.File; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -42,27 +58,34 @@ public class BpmnEngineCamunda8 implements BpmnEngine { public static final String THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME = "ThisIsACompleteImpossibleVariableName"; + public static final int SEARCH_MAX_SIZE = 100; private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda8.class); - private final BpmnEngineConfiguration.BpmnServerDefinition serverDefinition; - + private final ConfigurationBpmEngine.BpmnServerDefinition serverDefinition; + boolean hightFlowMode = false; + /** + * It is not possible to search user task for a specfic processInstance. So, to realize this, a marker is created in each process instance. Retrieving the user task, + * the process instance can be found and correction can be done + */ + Map cacheProcessInstanceMarker = new HashMap<>(); + Random random = new Random(System.currentTimeMillis()); private ZeebeClient zeebeClient; private CamundaOperateClient operateClient; private CamundaTaskListClient taskClient; - - private BpmnEngineConfiguration.CamundaEngine typeCamundaEngine; + @Autowired + private BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy; + private ConfigurationBpmEngine.CamundaEngine typeCamundaEngine; /** * Constructor from existing object * - * @param engineConfiguration - * @param serverDefinition + * @param engineConfiguration configuration for this engine + * @param serverDefinition server definition */ - public BpmnEngineCamunda8(BpmnEngineConfiguration engineConfiguration, - BpmnEngineConfiguration.BpmnServerDefinition serverDefinition) { + public BpmnEngineCamunda8(ConfigurationBpmEngine engineConfiguration, + ConfigurationBpmEngine.BpmnServerDefinition serverDefinition) { this.serverDefinition = serverDefinition; - } /** @@ -91,11 +114,11 @@ public BpmnEngineCamunda8(String zeebeSelfGatewayAddress, String operateUserName, String operateUserPassword, String tasklistUrl) { - this.serverDefinition = new BpmnEngineConfiguration.BpmnServerDefinition(); + this.serverDefinition = new ConfigurationBpmEngine.BpmnServerDefinition(); this.serverDefinition.zeebeGatewayAddress = zeebeSelfGatewayAddress; this.serverDefinition.zeebeSecurityPlainText = zeebeSelfSecurityPlainText; - /** + /* * SaaS Zeebe */ this.serverDefinition.zeebeCloudRegister = zeebeSaasCloudRegister; @@ -104,32 +127,43 @@ public BpmnEngineCamunda8(String zeebeSelfGatewayAddress, this.serverDefinition.zeebeCloudClientId = zeebeSaasCloudClientId; this.serverDefinition.clientSecret = zeebeSaasClientSecret; - /** + /* * Connection to Operate */ this.serverDefinition.operateUserName = operateUserName; this.serverDefinition.operateUserPassword = operateUserPassword; this.serverDefinition.operateUrl = operateUrl; - this.serverDefinition.tasklistUrl = tasklistUrl; + this.serverDefinition.taskListUrl = tasklistUrl; } @Override - public void init() throws AutomatorException { + public void init() { + // nothing to do there + } + + public void connection() throws AutomatorException + { final String defaultAddress = "localhost:26500"; final String envVarAddress = System.getenv("ZEEBE_ADDRESS"); - this.typeCamundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8; + // connection is critical, so let build the analysis + StringBuilder analysis = new StringBuilder(); + analysis.append("ZeebeConnection: "); + this.typeCamundaEngine = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8; if (this.serverDefinition.zeebeCloudRegister != null && !this.serverDefinition.zeebeCloudRegister.trim().isEmpty()) - this.typeCamundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8_SAAS; - + this.typeCamundaEngine = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8_SAAS; final ZeebeClientBuilder clientBuilder; io.camunda.operate.auth.AuthInterface saOperate; io.camunda.tasklist.auth.AuthInterface saTaskList; - if (this.serverDefinition.zeebeCloudRegister != null && !this.serverDefinition.zeebeCloudRegister.trim().isEmpty()) { + if (this.serverDefinition.zeebeCloudRegister != null && !this.serverDefinition.zeebeCloudRegister.trim() + .isEmpty()) { + analysis.append("Saas ClientId["); + analysis.append(serverDefinition.zeebeCloudClientId); + analysis.append("]"); /* Connect to Camunda Cloud Cluster, assumes that credentials are set in environment variables. * See JavaDoc on class level for details */ @@ -139,10 +173,15 @@ public void init() throws AutomatorException { saTaskList = new io.camunda.tasklist.auth.SaasAuthentication(serverDefinition.zeebeCloudClientId, serverDefinition.clientSecret); - typeCamundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8_SAAS; + typeCamundaEngine = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8_SAAS; // Camunda 8 Self Manage - } else if (serverDefinition.zeebeGatewayAddress != null && !this.serverDefinition.zeebeGatewayAddress.trim().isEmpty()) { + } else if (serverDefinition.zeebeGatewayAddress != null && !this.serverDefinition.zeebeGatewayAddress.trim() + .isEmpty()) { + analysis.append("GatewayAddress ["); + analysis.append(serverDefinition.zeebeGatewayAddress); + analysis.append("]"); + // connect to local deployment; assumes that authentication is disabled clientBuilder = ZeebeClient.newClientBuilder() .gatewayAddress(serverDefinition.zeebeGatewayAddress) @@ -151,28 +190,60 @@ public void init() throws AutomatorException { serverDefinition.operateUserPassword, serverDefinition.operateUrl); saTaskList = new io.camunda.tasklist.auth.SimpleAuthentication(serverDefinition.operateUserName, serverDefinition.operateUserPassword); - typeCamundaEngine = BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8; + typeCamundaEngine = ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8; } else throw new AutomatorException("Invalid configuration"); try { + analysis.append("ExecutionThread["); + analysis.append(serverDefinition.workerExecutionThreads); + analysis.append("] MaxJobsActive["); + analysis.append(serverDefinition.workerMaxJobsActive); + analysis.append("] "); + if (serverDefinition.workerMaxJobsActive==-1) { + serverDefinition.workerMaxJobsActive=serverDefinition.workerExecutionThreads; + analysis.append("No MaxJobsActive defined, align to the number of threads, "); + } + if (serverDefinition.workerExecutionThreads < serverDefinition.workerMaxJobsActive) { + logger.error("Incorrect definition: the number of threads must be >= number Jobs Active, else ZeebeClient will not fetch enough jobs to feed threads"); + } + + clientBuilder.numJobWorkerExecutionThreads(serverDefinition.workerExecutionThreads); + clientBuilder.defaultJobWorkerMaxJobsActive(serverDefinition.workerMaxJobsActive); zeebeClient = clientBuilder.build(); + analysis.append("Zeebe connection with success,"); + operateClient = new CamundaOperateClient.Builder().operateUrl(serverDefinition.operateUrl) .authentication(saOperate) .build(); - - taskClient = new CamundaTaskListClient.Builder().taskListUrl(serverDefinition.tasklistUrl) - .authentication(saTaskList) - .build(); + analysis.append("OperateConnection with success,"); + + // TaskList is not mandatory + if (serverDefinition.taskListUrl != null && !serverDefinition.taskListUrl.isEmpty()) { + taskClient = new CamundaTaskListClient.Builder().taskListUrl(serverDefinition.taskListUrl) + .authentication(saTaskList) + .build(); + analysis.append("TasklistConnection with success,"); + } //get tasks assigned to demo + logger.info(analysis.toString()); } catch (Exception e) { - throw new AutomatorException("Can't connect to Zeebe " + e.getMessage()); + zeebeClient=null; + throw new AutomatorException("Can't connect to Zeebe " + e.getMessage() + " - Analysis:" + analysis); } - } - Map cacheProcessInstanceMarker = new HashMap<>(); + public void disconnection() throws AutomatorException { + // nothing to do here + } + /** + * Engine is ready. If not, a connection() method must be call + * @return + */ + public boolean isReady(){ + return zeebeClient!=null; + } /* ******************************************************************** */ /* */ @@ -180,13 +251,54 @@ public void init() throws AutomatorException { /* */ /* ******************************************************************** */ + /** + * HighFlowMode: when true, the class does not save anything, to reduce the footprint + * + * @param hightFlowMode true or false + */ + public void turnHighFlowMode(boolean hightFlowMode) { + this.hightFlowMode = hightFlowMode; + } + @Override public String createProcessInstance(String processId, String starterEventId, Map variables) throws AutomatorException { try { - String marker = getUniqueMarker(processId, starterEventId); + String marker = null; + if (!hightFlowMode) { + marker = getUniqueMarker(processId, starterEventId); + variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); + } + + FinalCommandStep createCommand = zeebeClient.newCreateInstanceCommand() + .bpmnProcessId(processId) + .latestVersion() + .variables(variables); + RefactoredCommandWrapper command = new RefactoredCommandWrapper(createCommand, + System.currentTimeMillis() + 5 * 60 * 1000, + // 5 minutes + "CreatePi" + processId, exceptionHandlingStrategy); + + ProcessInstanceEvent workflowInstanceEvent = (ProcessInstanceEvent) command.executeSync(); + Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); + if (!hightFlowMode) { + cacheProcessInstanceMarker.put(marker, processInstanceId); + } + return String.valueOf(processInstanceId); + } catch (Exception e) { + throw new AutomatorException("Can't create in process [" + processId + "] :" + e.getMessage()); + } + } + + public String createProcessInstanceSimple(String processId, String starterEventId, Map variables) + throws AutomatorException { + try { + String marker = null; + if (!hightFlowMode) { + marker = getUniqueMarker(processId, starterEventId); + variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); + } - variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); ProcessInstanceEvent workflowInstanceEvent = zeebeClient.newCreateInstanceCommand() .bpmnProcessId(processId) .latestVersion() @@ -194,13 +306,22 @@ public String createProcessInstance(String processId, String starterEventId, Map .send() .join(); Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); - cacheProcessInstanceMarker.put(marker, processInstanceId); + if (!hightFlowMode) { + cacheProcessInstanceMarker.put(marker, processInstanceId); + } return String.valueOf(processInstanceId); } catch (Exception e) { - throw new AutomatorException("Can't create in process [" + processId + "]"); + throw new AutomatorException("Can't create in process [" + processId + "] :" + e.getMessage()); } } + + /* ******************************************************************** */ + /* */ + /* User tasks */ + /* */ + /* ******************************************************************** */ + @Override public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { // clean in the cache @@ -213,13 +334,6 @@ public void endProcessInstance(String processInstanceId, boolean cleanAll) throw } - - /* ******************************************************************** */ - /* */ - /* User tasks */ - /* */ - /* ******************************************************************** */ - @Override public List searchUserTasks(String processInstanceId, String userTaskId, int maxResult) throws AutomatorException { @@ -259,6 +373,41 @@ public List searchUserTasks(String processInstanceId, String userTaskId, } } + + + /* ******************************************************************** */ + /* */ + /* Service tasks */ + /* */ + /* ******************************************************************** */ + @Override + public RegisteredTask registerServiceTask(String workerId, + String topic, + Duration lockTime, + Object jobHandler, + FixedBackoffSupplier backoffSupplier) + { + if (! (jobHandler instanceof JobHandler)) + { + logger.error("handler is not a JobHandler implementation, can't register the worker [{}], topic [{}]", workerId, topic); + return null; + } + RegisteredTask registeredTask = new RegisteredTask(); + + JobWorkerBuilderStep1.JobWorkerBuilderStep3 step3 = zeebeClient.newWorker() + .jobType(topic) + .handler((JobHandler) jobHandler) + .timeout(lockTime) + .name(workerId); + + if (backoffSupplier!=null) { + step3.backoffSupplier(backoffSupplier); + } + registeredTask.jobWorker = step3.open(); + return registeredTask; + } + + @Override public void executeUserTask(String userTaskId, String userId, Map variables) throws AutomatorException { @@ -270,14 +419,6 @@ public void executeUserTask(String userTaskId, String userId, Map searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException { @@ -305,6 +446,15 @@ public List searchServiceTasks(String processInstanceId, String serviceT } } + + + + /* ******************************************************************** */ + /* */ + /* generic search */ + /* */ + /* ******************************************************************** */ + @Override public void executeServiceTask(String serviceTaskId, String workerId, Map variables) throws AutomatorException { @@ -315,15 +465,6 @@ public void executeServiceTask(String serviceTaskId, String workerId, Map searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) throws AutomatorException { @@ -342,7 +483,6 @@ public List searchTasksByProcessInstanceId(String processInstan return taskDescription; }).toList(); - } catch (OperateException e) { throw new AutomatorException("Can't search users task " + e.getMessage()); } @@ -422,14 +562,109 @@ public Map getVariables(String processInstanceId) throws Automat } } + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + public long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + try { + int cumul = 0; + SearchResult searchResult = null; + queryBuilder = queryBuilder.filter(new ProcessInstanceFilter.Builder().bpmnProcessId(processId).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + int maxLoop = 0; + do { + maxLoop++; + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchProcessInstanceResults(searchQuery); + + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessInstanceCreated " + e.getMessage()); + } + } + + public long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + try { + int cumul = 0; + SearchResult searchResult = null; + + queryBuilder = queryBuilder.filter(new ProcessInstanceFilter.Builder().bpmnProcessId(processId) + // .startDate(startDate) + // .endDate(endDate) + .state(ProcessInstanceState.COMPLETED).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + int maxLoop = 0; + do { + maxLoop++; + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchProcessInstanceResults(searchQuery); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); + } + } /* ******************************************************************** */ /* */ /* Deployment */ /* */ /* ******************************************************************** */ + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + + try { + + int cumul = 0; + SearchResult searchResult = null; + int maxLoop = 0; + do { + maxLoop++; + + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + queryBuilder = queryBuilder.filter(new FlownodeInstanceFilter.Builder().flowNodeId(taskId).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchFlownodeInstanceResults(searchQuery); + cumul += (long) searchResult.getItems().size(); + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); + } + } + + + /* ******************************************************************** */ + /* */ + /* get server definition */ + /* */ + /* ******************************************************************** */ + @Override public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { try { @@ -444,21 +679,34 @@ public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) thr } } - - /* ******************************************************************** */ - /* */ - /* get server definition */ - /* */ - /* ******************************************************************** */ - @Override - public BpmnEngineConfiguration.CamundaEngine getTypeCamundaEngine() { + public ConfigurationBpmEngine.CamundaEngine getTypeCamundaEngine() { return typeCamundaEngine; } - Random random = new Random(System.currentTimeMillis()); + @Override + public String getSignature() { + String signature = typeCamundaEngine.toString() + " "; + if (typeCamundaEngine.equals(ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8_SAAS)) + signature += "Cloud[" + serverDefinition.zeebeCloudRegister + "] ClientId[" + serverDefinition.zeebeCloudClientId + + "] ClusterId[" + serverDefinition.zeebeCloudClusterId + "]"; + else + signature += "Address[" + serverDefinition.zeebeGatewayAddress + "]"; + signature += " numJobWorkerExecutionThreads[" + serverDefinition.workerExecutionThreads + "] workerMaxJobsActive[" + + serverDefinition.workerMaxJobsActive + "]"; + return signature; + } + + @Override + public int getWorkerExecutionThreads() { + return serverDefinition != null ? serverDefinition.workerExecutionThreads : 0; + } private String getUniqueMarker(String processId, String starterEventId) { return processId + "-" + random.nextInt(1000000); } + + public ZeebeClient getZeebeClient() { + return zeebeClient; + } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java new file mode 100644 index 0000000..5df37b6 --- /dev/null +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java @@ -0,0 +1,49 @@ +package org.camunda.automator.bpmnengine.camunda8; + +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.Date; + +@Component +public class StatisticsCollector { + + private final Date startTime = new Date(); + + private final long lastPrintStartedProcessInstances = 0; + private final long lastPrintCompletedProcessInstances = 0; + private final long lastPrintCompletedJobs = 0; + private final long lastPrintStartedProcessInstancesBackpressure = 0; + + private long piPerSecondGoal; + + @PostConstruct + public void init() { + } + + public void hintOnNewPiPerSecondGoald(long piPerSecondGoal) { + this.piPerSecondGoal = piPerSecondGoal; + } + + public void incStartedProcessInstances() { + } + + public void incStartedProcessInstancesBackpressure() { + } + + public void incCompletedProcessInstances() { + } + + public void incCompletedProcessInstances(long startMillis, long endMillis) { + } + + public void incCompletedJobs() { + } + + public void incStartedProcessInstancesException(String exceptionMessage) { + } + + public void incCompletedJobsException(String exceptionMessage) { + } + +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java new file mode 100644 index 0000000..1eb2488 --- /dev/null +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java @@ -0,0 +1,87 @@ +package org.camunda.automator.bpmnengine.camunda8.refactoring; + +import io.camunda.zeebe.client.api.ZeebeFuture; +import io.camunda.zeebe.client.api.command.FinalCommandStep; +import io.camunda.zeebe.client.api.worker.BackoffSupplier; +import io.camunda.zeebe.spring.client.jobhandling.CommandWrapper; +import io.camunda.zeebe.spring.client.jobhandling.DefaultCommandExceptionHandlingStrategy; + +import java.time.Instant; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Copied from CommandWrapper from spring-zeebe. Refactor over there to be able to use built-in stuff directly + */ +public class RefactoredCommandWrapper extends CommandWrapper { + + private final FinalCommandStep command; + private final long deadline; + private final String entityLogInfo; + private final DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy; + private final int maxRetries = 20; + private long currentRetryDelay = 50L; + private int invocationCounter = 0; + + public RefactoredCommandWrapper(FinalCommandStep command, + long deadline, + String entityLogInfo, + DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy) { + super(command, null, commandExceptionHandlingStrategy); + this.command = command; + this.deadline = deadline; + this.entityLogInfo = entityLogInfo; + this.commandExceptionHandlingStrategy = commandExceptionHandlingStrategy; + } + + @Override + public void executeAsync() { + ++this.invocationCounter; + this.command.send().exceptionally(t -> { + this.commandExceptionHandlingStrategy.handleCommandError(this, t); + return null; + }); + } + + public Object executeSync() { + ++this.invocationCounter; + ZeebeFuture zeebeFutur = this.command.send(); + + zeebeFutur.exceptionally(t -> { + this.commandExceptionHandlingStrategy.handleCommandError(this, t); + return null; + }); + return zeebeFutur.join(); + } + + @Override + public void increaseBackoffUsing(BackoffSupplier backoffSupplier) { + this.currentRetryDelay = backoffSupplier.supplyRetryDelay(this.currentRetryDelay); + } + + @Override + public void scheduleExecutionUsing(ScheduledExecutorService scheduledExecutorService) { + scheduledExecutorService.schedule(this::executeAsync, this.currentRetryDelay, TimeUnit.MILLISECONDS); + } + + @Override + public String toString() { + return "{command=" + this.command.getClass() + ", entity=" + this.entityLogInfo + ", currentRetryDelay=" + + this.currentRetryDelay + '}'; + } + + @Override + public boolean hasMoreRetries() { + if (this.jobDeadlineExceeded()) { + return false; + } else { + return this.invocationCounter < this.maxRetries; + } + } + + @Override + public boolean jobDeadlineExceeded() { + return Instant.now().getEpochSecond() > this.deadline; + } + +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java index f77ba91..b960ffd 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java +++ b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java @@ -1,22 +1,26 @@ package org.camunda.automator.bpmnengine.dummy; +import io.camunda.operate.search.DateFilter; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.ScenarioDeployment; import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.flow.FixedBackoffSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Map; public class BpmnEngineDummy implements BpmnEngine { - private final BpmnEngineConfiguration engineConfiguration; + private final ConfigurationBpmEngine engineConfiguration; private final Logger logger = LoggerFactory.getLogger(BpmnEngineDummy.class); - public BpmnEngineDummy(BpmnEngineConfiguration engineConfiguration) { + public BpmnEngineDummy(ConfigurationBpmEngine engineConfiguration) { this.engineConfiguration = engineConfiguration; } @@ -25,6 +29,21 @@ public void init() { logger.info("BpmnEngineDummy.Init:"); } + public void connection() throws AutomatorException { + } + + public void disconnection() throws AutomatorException { + } + + /** + * Engine is ready. If not, a connection() method must be call + * + * @return + */ + public boolean isReady() { + return true; + } + @Override public String createProcessInstance(String processId, String starterEventId, Map variables) throws AutomatorException { @@ -47,14 +66,22 @@ public List searchUserTasks(String processInstanceId, String userTaskId, @Override public void executeUserTask(String userTaskId, String userId, Map variables) throws AutomatorException { - logger.info("BpmnEngineDummy.executeUserTask: activityId[" + userTaskId + "]"); } + @Override + public RegisteredTask registerServiceTask(String workerId, + String topic, + Duration lockTime, + Object jobHandler, + FixedBackoffSupplier backoffSupplier) { + return null; + } + @Override public List searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException { - return null; + return Collections.emptyList(); } @Override @@ -65,22 +92,44 @@ public void executeServiceTask(String serviceTaskId, String workerId, Map searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) throws AutomatorException { - return null; + return Collections.emptyList(); } @Override public List searchProcessInstanceByVariable(String processId, - Map filterVariables, int maxResult) throws AutomatorException { - return null; + Map filterVariables, + int maxResult) throws AutomatorException { + return Collections.emptyList(); } @Override public Map getVariables(String processInstanceId) throws AutomatorException { - return null; + return Collections.emptyMap(); } + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + + @Override + public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } + + @Override + public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } + + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } @Override public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { @@ -88,8 +137,22 @@ public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) thr } @Override - public BpmnEngineConfiguration.CamundaEngine getTypeCamundaEngine() { - return BpmnEngineConfiguration.CamundaEngine.DUMMY; + public ConfigurationBpmEngine.CamundaEngine getTypeCamundaEngine() { + return ConfigurationBpmEngine.CamundaEngine.DUMMY; + } + + @Override + public String getSignature() { + return ConfigurationBpmEngine.CamundaEngine.DUMMY.toString(); + } + + @Override + + public int getWorkerExecutionThreads() { + return 0; + } + + public void turnHighFlowMode(boolean hightFlowMode) { } } diff --git a/src/main/java/org/camunda/automator/configuration/ConfigurationBpmEngine.java b/src/main/java/org/camunda/automator/configuration/ConfigurationBpmEngine.java new file mode 100644 index 0000000..067522c --- /dev/null +++ b/src/main/java/org/camunda/automator/configuration/ConfigurationBpmEngine.java @@ -0,0 +1,297 @@ +/** + * ConfigurationBpmEngine + * Configuration are defined in the application.yaml file. + * + * @return + */ +package org.camunda.automator.configuration; + +import org.camunda.automator.engine.AutomatorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.StringTokenizer; + +@Configuration +@PropertySource("classpath:application.yaml") +public class ConfigurationBpmEngine { + static Logger logger = LoggerFactory.getLogger(ConfigurationBpmEngine.class); + @Value("${automator.logDebug:false}") + public boolean logDebug = false; + @Value("#{'${automator.serversconnection}'.split(';')}") + public List serversConnection; + @Autowired + ConfigurationServersEngine configurationServersEngine; + private List allServers = new ArrayList<>(); + + /** + * Add an explicit server, by the API + * + * @param bpmnEngineConfiguration server to add in the list + */ + public void addExplicitServer(BpmnServerDefinition bpmnEngineConfiguration) { + allServers.add(bpmnEngineConfiguration); + } + + @PostConstruct + public void init() { + allServers = new ArrayList<>(); + allServers.addAll(getFromServerConfiguration()); + + // decode the serverConnections + try { + List listFromConnection = decodeListServersConnection(); + allServers.addAll(listFromConnection); + + // log all servers detected + logger.info("ConfigurationBpmEngine: servers detected : {} ", allServers.size()); + for (BpmnServerDefinition server : allServers) { + String serverDetails = "Server Type[" + server.serverType + "] " + switch (server.serverType) { + case CAMUNDA_8 -> "ZeebeadressGateway [" + server.zeebeGatewayAddress + "]"; + case CAMUNDA_8_SAAS -> "ZeebeClientId [" + server.zeebeCloudClientId + "] ClusterId[" + + server.zeebeCloudClusterId + "] RegionId[" + server.zeebeCloudRegion + "]"; + case CAMUNDA_7 -> "Camunda7URL [" + server.camunda7ServerUrl + "]"; + case DUMMY -> "Dummy"; + }; + logger.info(serverDetails); + } + } catch (Exception e) { + logger.error("Error during initialization"); + } + } + + public List getListServers() { + return allServers; + } + + /** + * get a server by its name + * + * @param serverName serverName + * @return the server, or null + * @throws AutomatorException on any error + */ + public ConfigurationBpmEngine.BpmnServerDefinition getByServerName(String serverName) throws AutomatorException { + Optional first = allServers.stream().filter(t -> t.name.equals(serverName)).findFirst(); + return first.isPresent() ? first.get() : null; + } + + /** + * get a server by its type + * + * @param serverType type of server CAMUNDA 8 ? 7 ? + * @return a server + * @throws AutomatorException on any error + */ + public ConfigurationBpmEngine.BpmnServerDefinition getByServerType(CamundaEngine serverType) + throws AutomatorException { + Optional first = allServers.stream() + .filter(t -> sameType(t.serverType, serverType)) + .findFirst(); + return first.isPresent() ? first.get() : null; + } + + /** + * Compare type : CAMUNDA_8 and CAMUNDA_8_SAAS are consider as equals + * + * @param type1 type one to compare + * @param type2 type two to compare + * @return true if types are identical + */ + private boolean sameType(CamundaEngine type1, CamundaEngine type2) { + if (type1.equals(CamundaEngine.CAMUNDA_8_SAAS)) + type1 = CamundaEngine.CAMUNDA_8; + if (type2.equals(CamundaEngine.CAMUNDA_8_SAAS)) + type2 = CamundaEngine.CAMUNDA_8; + return type1.equals(type2); + } + + private List decodeListServersConnection() throws AutomatorException { + // not possible to use a Stream: decode throw an exception + List list = new ArrayList<>(); + for (String s : serversConnection) { + if (s.isEmpty()) + continue; + BpmnServerDefinition bpmnServerDefinition = decodeServerConnection(s); + if (bpmnServerDefinition.serverType == null) { + logger.error("Server Type can't be detected in string [{}]", s); + continue; + } + + list.add(bpmnServerDefinition); + } + return list; + } + + /** + * DecodeServerConnection + * + * @param connectionString connection string + * @return a ServerDefinition + * @throws AutomatorException on any error + */ + private BpmnServerDefinition decodeServerConnection(String connectionString) throws AutomatorException { + StringTokenizer st = new StringTokenizer(connectionString, ","); + BpmnServerDefinition bpmnServerDefinition = new BpmnServerDefinition(); + bpmnServerDefinition.name = (st.hasMoreTokens() ? st.nextToken() : null); + try { + bpmnServerDefinition.serverType = st.hasMoreTokens() ? CamundaEngine.valueOf(st.nextToken()) : null; + if (CamundaEngine.CAMUNDA_7.equals(bpmnServerDefinition.serverType)) { + bpmnServerDefinition.camunda7ServerUrl = (st.hasMoreTokens() ? st.nextToken() : null); + + } else if (CamundaEngine.CAMUNDA_8.equals(bpmnServerDefinition.serverType)) { + bpmnServerDefinition.zeebeGatewayAddress = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUrl = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUserName = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUserPassword = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.taskListUrl = (st.hasMoreTokens() ? st.nextToken() : null); + + } else if (CamundaEngine.CAMUNDA_8_SAAS.equals(bpmnServerDefinition.serverType)) { + bpmnServerDefinition.zeebeCloudRegister = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeCloudRegion = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeCloudClusterId = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeCloudClientId = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.clientSecret = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUrl = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUserName = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUserPassword = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.taskListUrl = (st.hasMoreTokens() ? st.nextToken() : null); + } + return bpmnServerDefinition; + } catch (Exception e) { + throw new AutomatorException("Can't decode string [" + connectionString + "] " + e.getMessage()); + } + } + + /** + * Get the list from the serverConfiguration + * + * @return list of BpmnServer + */ + private List getFromServerConfiguration() { + List list = new ArrayList<>(); + + // get the direct list + if (hasValue(configurationServersEngine.camunda7Url)) { + BpmnServerDefinition camunda7 = new BpmnServerDefinition(); + camunda7.serverType = CamundaEngine.CAMUNDA_7; + camunda7.name = configurationServersEngine.camunda7Name; + camunda7.camunda7ServerUrl = configurationServersEngine.camunda7Url; + camunda7.camunda7UserName = configurationServersEngine.camunda7UserName; + camunda7.camunda7Password = configurationServersEngine.camunda7Password; + + camunda7.workerMaxJobsActive = parseInt("Camunda7.workerMaxJobsActive", + configurationServersEngine.C7workerMaxJobsActive, -1);; + list.add(camunda7); + logger.info("Configuration: Camunda7 Name[{}] url[{}] MaxJobsActive[{}]", camunda7.name, camunda7.camunda7ServerUrl, + camunda7.workerMaxJobsActive); + } + if (hasValue(configurationServersEngine.zeebeGatewayAddress)) { + BpmnServerDefinition camunda8 = new BpmnServerDefinition(); + camunda8.serverType = CamundaEngine.CAMUNDA_8; + camunda8.name = configurationServersEngine.zeebeName; + camunda8.zeebeGatewayAddress = configurationServersEngine.zeebeGatewayAddress; + camunda8.workerExecutionThreads = parseInt("Camunda8.workerExecutionThreads", + configurationServersEngine.workerExecutionThreads, 101); + camunda8.workerMaxJobsActive = parseInt("Camunda8.workerMaxJobsActive", + configurationServersEngine.C8workerMaxJobsActive, -1); + camunda8.operateUrl = configurationServersEngine.operateUrl; + camunda8.operateUserName = configurationServersEngine.operateUserName; + camunda8.operateUserPassword = configurationServersEngine.operateUserPassword; + camunda8.taskListUrl = configurationServersEngine.taskListUrl; + list.add(camunda8); + logger.info("Configuration: Camunda8 Name[{}] zeebeGateway[{}] MaxJobsActive[{}] WorkerThreads[{}] " + + "OperateURL[{}]", + camunda8.name, + camunda8.camunda7ServerUrl, + camunda8.workerMaxJobsActive, + camunda8.workerExecutionThreads, + camunda8.operateUrl); + + } + if (hasValue(configurationServersEngine.zeebeCloudRegister)) { + BpmnServerDefinition camunda8 = new BpmnServerDefinition(); + + camunda8.zeebeCloudRegister = configurationServersEngine.zeebeCloudRegister; + camunda8.zeebeCloudRegion = configurationServersEngine.zeebeCloudRegion; + camunda8.zeebeCloudClusterId = configurationServersEngine.zeebeCloudClusterId; + camunda8.zeebeCloudClientId = configurationServersEngine.zeebeCloudClientId; + camunda8.clientSecret = configurationServersEngine.clientSecret; + camunda8.operateUrl = configurationServersEngine.operateUrl; + camunda8.operateUserName = configurationServersEngine.operateUserName; + camunda8.operateUserPassword = configurationServersEngine.operateUserPassword; + camunda8.taskListUrl = configurationServersEngine.taskListUrl; + list.add(camunda8); + + } + return list; + } + + private int parseInt(String label, String value, int defaultValue) { + try { + if (value.equals("''")) + return defaultValue; + return Integer.parseInt(value); + } catch (Exception e) { + logger.error("Can't parse value [{}] at [{}]", value, label); + return defaultValue; + } + } + + private boolean hasValue(String value) { + if (value == null) + return false; + if (value.equals("''")) + return false; + return !value.trim().isEmpty(); + } + + public enum CamundaEngine {CAMUNDA_7, CAMUNDA_8, CAMUNDA_8_SAAS, DUMMY} + + public static class BpmnServerDefinition { + public String name; + + public CamundaEngine serverType; + + /** + * My Zeebe Address + */ + public String zeebeGatewayAddress; + public String zeebeSecurityPlainText; + + /** + * SaaS Zeebe + */ + public String zeebeCloudRegister; + public String zeebeCloudRegion; + public String zeebeCloudClusterId; + public String zeebeCloudClientId; + public String clientSecret; + + public Integer workerExecutionThreads = Integer.valueOf(100); + public Integer workerMaxJobsActive = Integer.valueOf(-1); + + /** + * Connection to Operate + */ + public String operateUserName; + public String operateUserPassword; + public String operateUrl; + public String taskListUrl; + + /** + * Camunda 7 + */ + public String camunda7ServerUrl; + public String camunda7UserName; + public String camunda7Password; + } +} diff --git a/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java b/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java new file mode 100644 index 0000000..7971913 --- /dev/null +++ b/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java @@ -0,0 +1,69 @@ +package org.camunda.automator.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +@Component +@PropertySource("classpath:application.yaml") +@Configuration +public class ConfigurationServersEngine { + + + @Value("${automator.servers.camunda7.url:''}") + public String camunda7Url; + + @Value("${automator.servers.camunda7.username:}") + public String camunda7UserName; + + @Value("${automator.servers.camunda7.password:}") + public String camunda7Password; + + @Value("${automator.servers.camunda7.name:''}") + public String camunda7Name; + + @Value("${automator.servers.camunda7.workerMaxJobsActive:''}") + public String C7workerMaxJobsActive; + + @Value("${automator.servers.camunda8.name:''}") + public String zeebeName; + + @Value("${automator.servers.camunda8.zeebeGatewayAddress:''}") + public String zeebeGatewayAddress; + + @Value("${automator.servers.camunda8.zeebeCloudRegister:''}") + public String zeebeCloudRegister; + + @Value("${automator.servers.camunda8.zeebeCloudRegion:''}") + public String zeebeCloudRegion; + + @Value("${automator.servers.camunda8.zeebeCloudClusterId:''}") + public String zeebeCloudClusterId; + + @Value("${automator.servers.camunda8.zeebeCloudClientId:''}") + public String zeebeCloudClientId; + + @Value("${automator.servers.camunda8.clientSecret:''}") + public String clientSecret; + + @Value("${automator.servers.camunda8.operateUrl:''}") + public String operateUrl; + + @Value("${automator.servers.camunda8.operateUserName:''}") + public String operateUserName; + + @Value("${automator.servers.camunda8.operateUserPassword:''}") + public String operateUserPassword; + + @Value("${automator.servers.camunda8.taskListUrl:''}") + public String taskListUrl; + + @Value("${automator.servers.camunda8.workerExecutionThreads:''}") + public String workerExecutionThreads; + + @Value("${automator.servers.camunda8.workerMaxJobsActive:''}") + public String C8workerMaxJobsActive; + +} + diff --git a/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java b/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java new file mode 100644 index 0000000..2cacf86 --- /dev/null +++ b/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java @@ -0,0 +1,98 @@ +package org.camunda.automator.configuration; + +import org.camunda.automator.engine.RunParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.List; + +@Component +@PropertySource("classpath:application.yaml") +@Configuration +public class ConfigurationStartup { + static Logger logger = LoggerFactory.getLogger(ConfigurationStartup.class); + + @Value("#{'${automator.startup.scenarioAtStartup:\'\'}'.split(';')}") + public List scenarioAtStartup; + + @Value("#{'${automator.startup.filterService:\'\'}'.split(';')}") + public List filterService; + + @Value("${automator.startup.scenarioPath}") + public String scenarioPath; + + @Value("${automator.startup.logLevel:MONITORING}") + public String logLevel; + + @Value("${automator.startup.deeptracking:false}") + public boolean deepTracking; + + @Value("${automator.startup.policyExecution:DEPLOYPROCESS|WARMINGUP|CREATION|SERVICETASK|USERTASK}") + public String policyExecution; + + /** + * it may be necessary to wait the other component to warm up + */ + @Value("${automator.startup.waitWarmUpServer:PT0S}") + public String waitWarmupServer; + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public RunParameters.LOGLEVEL getLogLevelEnum() { + try { + return RunParameters.LOGLEVEL.valueOf(logLevel); + } catch (Exception e) { + logger.error("Unknow LogLevel (automator.startup.loglevel) : [{}} ", logLevel); + return RunParameters.LOGLEVEL.MONITORING; + } + } + + public boolean deepTracking() { + return deepTracking; + } + + public boolean isPolicyExecutionCreation() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|CREATION|"); + } + + public boolean isPolicyExecutionServiceTask() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|SERVICETASK|"); + } + + public boolean isPolicyExecutionUserTask() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|USERTASK|"); + } + + public boolean isPolicyExecutionWarmingUp() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|WARMINGUP|"); + } + + public boolean isPolicyDeployProcess() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|DEPLOYPROCESS|"); + } + + public List getFilterService() { + return filterService; + } + + public Duration getWarmingUpServer() { + try { + return Duration.parse(waitWarmupServer); + } catch (Exception e) { + logger.error("Can't parse warmup [{}]", waitWarmupServer); + return Duration.ZERO; + } + } +} diff --git a/src/main/java/org/camunda/automator/definition/Scenario.java b/src/main/java/org/camunda/automator/definition/Scenario.java index 8395eb0..b294726 100644 --- a/src/main/java/org/camunda/automator/definition/Scenario.java +++ b/src/main/java/org/camunda/automator/definition/Scenario.java @@ -1,14 +1,14 @@ /* ******************************************************************** */ /* */ -/* Scenario */ +/* Scenario */ /* */ -/* Store a scenario */ -/* a scenario is a list of order to execute +/* Store a scenario. It is a list of order to execute */ /* ******************************************************************** */ package org.camunda.automator.definition; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.engine.AutomatorException; import java.io.BufferedReader; @@ -24,6 +24,10 @@ public class Scenario { private final List executions = new ArrayList<>(); private final List deployments = new ArrayList<>(); + private final List flows = new ArrayList<>(); + private ScenarioWarmingUp warmingUp; + private ScenarioFlowControl flowControl; + private String name; private String version; private String processName; @@ -31,10 +35,16 @@ public class Scenario { private String modeVerification; /** - * This value is fulfill only if the scenario was read from a file + * Server to run the scenario */ - private transient File scenarioFile=null; + private String serverName; + private String serverType; + + /** + * This value is fulfill only if the scenario was read from a file + */ + private String scenarioFile = null; public static Scenario createFromJson(String jsonFile) { GsonBuilder builder = new GsonBuilder(); @@ -51,20 +61,20 @@ public static Scenario createFromJson(String jsonFile) { * * @param scenarioFile file to read * @return the scenario - * @throws Exception if file cannot be read or it's not a Json file + * @throws AutomatorException if file cannot be read or it's not a Json file */ public static Scenario createFromFile(File scenarioFile) throws AutomatorException { - try (BufferedReader br = new BufferedReader(new FileReader(scenarioFile));){ + try (BufferedReader br = new BufferedReader(new FileReader(scenarioFile))) { StringBuilder jsonContent = new StringBuilder(); String st; while ((st = br.readLine()) != null) jsonContent.append(st); - Scenario scnHead= createFromJson(jsonContent.toString()); - scnHead.scenarioFile = scenarioFile; + Scenario scnHead = createFromJson(jsonContent.toString()); + scnHead.scenarioFile = scenarioFile.getAbsolutePath(); return scnHead; } catch (Exception e) { - throw new AutomatorException("Can't read ["+scenarioFile.getAbsolutePath()+"] "+ e.getMessage()); + throw new AutomatorException("Can't interpret JSON [" + scenarioFile.getAbsolutePath() + "] " + e.getMessage()); } } @@ -83,11 +93,22 @@ public List getExecutions() { return executions; } + public List getFlows() { + return flows; + } + + public ScenarioWarmingUp getWarmingUp() { + return warmingUp; + } + + public ScenarioFlowControl getFlowControl() { + return flowControl; + } + public List getDeployments() { return deployments; } - public String getName() { return name; } @@ -109,14 +130,30 @@ public String getProcessId() { return processId; } - public File getScenarioFile() { - return scenarioFile; - } public Scenario setProcessId(String processId) { this.processId = processId; return this; } + public File getScenarioFile() { + return new File(scenarioFile); + } + + public String getServerName() { + if (serverName == null || serverName.isEmpty()) + return null; + return serverName; + } + + public ConfigurationBpmEngine.CamundaEngine getServerType() { + try { + return ConfigurationBpmEngine.CamundaEngine.valueOf(serverType.toUpperCase()); + } catch (Exception e) { + return null; + } + + } + public String getModeVerification() { return modeVerification; } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java b/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java index 51a8705..953d3a1 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java @@ -1,12 +1,12 @@ package org.camunda.automator.definition; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.configuration.ConfigurationBpmEngine; public class ScenarioDeployment { /** * type of server */ - public BpmnEngineConfiguration.CamundaEngine server; + public ConfigurationBpmEngine.CamundaEngine serverType; /** * Type pf deployment */ @@ -19,6 +19,7 @@ public class ScenarioDeployment { public Policy policy; public enum TypeDeployment {PROCESS} + public enum Policy {ONLYNOTEXIST, ALWAYS} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java index c91a434..132bf2d 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java @@ -9,9 +9,8 @@ */ public class ScenarioExecution { - private Scenario scnHead; private final List steps = new ArrayList<>(); - + private Scenario scnHead; private ScenarioVerification verifications; /** @@ -27,14 +26,7 @@ public class ScenarioExecution { * Number of thread in parallel to execute all process instances. Default is 1 */ private Integer numberOfThreads; - - /** - * Decide what to do when an error is find: stop or continue? - * default is STOPATFIRSTERROR - */ - public enum Policy { STOPATFIRSTERROR, CONTINUE} private Policy policy; - /** * if set to false, this execution is skipped */ @@ -49,6 +41,10 @@ protected ScenarioExecution(Scenario scenario) { this.scnHead = scenario; } + public static ScenarioExecution createExecution(Scenario scnHead) { + return new ScenarioExecution(scnHead); + } + /* ******************************************************************** */ @@ -57,10 +53,6 @@ protected ScenarioExecution(Scenario scenario) { /* */ /* ******************************************************************** */ - public static ScenarioExecution createExecution(Scenario scnHead) { - return new ScenarioExecution(scnHead); - } - /** * After UnSerialize, all link to parent are not restored * @@ -83,15 +75,13 @@ public ScenarioExecution addStep(ScenarioStep step) { steps.add(step); return this; } - /** - * Ask this execution to execute a number of process instance. - * - * @param numberProcessInstances number of process instance to execute - * @return this object - */ - public ScenarioExecution setNumberProcessInstances(int numberProcessInstances) { - this.numberProcessInstances = numberProcessInstances; - return this; + + public List getSteps() { + return steps == null ? Collections.emptyList() : steps; + } + + public ScenarioVerification getVerifications() { + return verifications; } @@ -102,16 +92,19 @@ public ScenarioExecution setNumberProcessInstances(int numberProcessInstances) { /* */ /* ******************************************************************** */ - public List getSteps() { - return steps==null? Collections.emptyList(): steps; - } - - public ScenarioVerification getVerifications() { - return verifications; + public int getNumberProcessInstances() { + return numberProcessInstances == null ? 1 : numberProcessInstances; } - public int getNumberProcessInstances() { - return numberProcessInstances==null? 1 : numberProcessInstances; + /** + * Ask this execution to execute a number of process instance. + * + * @param numberProcessInstances number of process instance to execute + * @return this object + */ + public ScenarioExecution setNumberProcessInstances(int numberProcessInstances) { + this.numberProcessInstances = numberProcessInstances; + return this; } public Scenario getScnHead() { @@ -128,15 +121,20 @@ public ScenarioExecution setName(String name) { } public int getNumberOfThreads() { - return (numberOfThreads ==null ? 1: numberOfThreads<=0? 1 : numberOfThreads); + return (numberOfThreads == null ? 1 : numberOfThreads <= 0 ? 1 : numberOfThreads); } - public Policy getPolicy() { - return (policy==null? Policy.STOPATFIRSTERROR : policy); + return (policy == null ? Policy.STOPATFIRSTERROR : policy); } public boolean isExecution() { - return execution==null || Boolean.TRUE.equals(execution); + return execution == null || Boolean.TRUE.equals(execution); } + + /** + * Decide what to do when an error is find: stop or continue? + * default is STOPATFIRSTERROR + */ + public enum Policy {STOPATFIRSTERROR, CONTINUE} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java b/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java new file mode 100644 index 0000000..6bd7218 --- /dev/null +++ b/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java @@ -0,0 +1,51 @@ +/* ******************************************************************** */ +/* */ +/* ScenarioFlowControl */ +/* */ +/* Parameters to control the Flow execution */ +/* ******************************************************************** */ +package org.camunda.automator.definition; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; + +public class ScenarioFlowControl { + private String duration; + + private Integer increaseStep; + private List objectives; + + public Duration getDuration() { + try { + return Duration.parse(duration); + } catch (Exception e) { + return Duration.ofMinutes(10); + } + } + + public Integer getIncreaseStep() { + return increaseStep; + } + + public List getObjectives() { + return objectives == null ? Collections.emptyList() : objectives; + } + + public static class Objective { + public int index; + public String label; + public TYPEOBJECTIVE type; + public String processId; + public String taskId; + public String period; + public Integer value; + public Integer standardDeviation; + + public int getStandardDeviation() { + return standardDeviation == null ? 0 : standardDeviation; + } + + public enum TYPEOBJECTIVE {CREATED, ENDED, USERTASK, FLOWRATEUSERTASKMN} + } +} diff --git a/src/main/java/org/camunda/automator/definition/ScenarioStep.java b/src/main/java/org/camunda/automator/definition/ScenarioStep.java index 8a31ba4..388f173 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioStep.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioStep.java @@ -9,11 +9,28 @@ import org.camunda.automator.engine.AutomatorException; +import java.time.Duration; import java.util.Collections; import java.util.Map; public class ScenarioStep { + /** + * each component contains an operations, to fulfill variables + * operations; stringtodate() + */ + private final Map variablesOperation = Collections.emptyMap(); + /** + * In case of a Flow Step, the number of workers to execute this tasks + */ + private final Integer nbWorkers = Integer.valueOf(1); + private final Long fixedBackOffDelay = Long.valueOf(0); + /** + * if the step is used in a WarmingUp operation, it can decide this is the time to finish it + * Expression is + * UserTaskThreashold(,) + */ + public String endWarmingUp; private ScenarioExecution scnExecution; private Step type; private String taskId; @@ -22,29 +39,42 @@ public class ScenarioStep { */ private String topic; private Map variables = Collections.emptyMap(); - - /** - * each component contains an operations, to fulfill variables - * operations; stringtodate() - */ - private final Map variablesOperation = Collections.emptyMap(); - private String userId; - /** * ISO 8601: PT10S */ private String delay; - /** * ISO 8601: PT10S */ private String waitingTime; - /** - * Optional, may not b + * Optional, may not be set */ private Integer numberOfExecutions; + /** + * In case of a Flow step, the frequency to execute this step, for example PT10S every 10 seconds + */ + private String frequency; + /** + * In case of FlowStep, the processId to execute the step + */ + private String processId; + + /** + * MODE EXECUTION + * WAIT: the worker wait the waitingTime time + * ASYNCHRONOUS: the worker release the method, wait asynchrously the waiting time and send back the answer + * ASYNCHRONOUSLIMITED: same as ASYNCHRONOUS, but use the maxClient information to not accept more than this number + * In ASYNCHRONOUS, the method can potentially having millions of works in parallel (it accept works, + * but because it finish the method, then Zeebe Client will accept more works. So, with a waiting time of 1 mn, it may have a lot + * of works in progress in the client. + * This mode limit the number of current execution on the worker. it redeem immediately the method, but when we reach this + * limitation, it froze the worker, waiting for a slot. + */ + public enum MODEEXECUTION {WAIT, ASYNCHRONOUS, ASYNCHRONOUSLIMITED} + + private MODEEXECUTION modeExecution = MODEEXECUTION.WAIT; public ScenarioStep(ScenarioExecution scnExecution) { this.scnExecution = scnExecution; @@ -92,8 +122,9 @@ public String getUserId() { return userId; } - public String getTopic() { - return topic; + public ScenarioStep setUserId(String userId) { + this.userId = userId; + return this; } /* ******************************************************************** */ @@ -102,9 +133,8 @@ public String getTopic() { /* */ /* ******************************************************************** */ - public ScenarioStep setUserId(String userId) { - this.userId = userId; - return this; + public String getTopic() { + return topic; } public Map getVariablesOperations() { @@ -129,10 +159,6 @@ public ScenarioStep setDelay(String delay) { return this; } - public void setScnExecution(ScenarioExecution scnExecution) { - this.scnExecution = scnExecution; - } - public String getWaitingTime() { return waitingTime; } @@ -141,10 +167,27 @@ public void setWaitingTime(String waitingTime) { this.waitingTime = waitingTime; } + /** + * Return the waiting time in Duration + * + * @return Duration, defaultDuration if error + */ + public Duration getWaitingTimeDuration(Duration defaultDuration) { + try { + return Duration.parse(waitingTime); + } catch (Exception e) { + return defaultDuration; + } + } + public ScenarioExecution getScnExecution() { return scnExecution; } + public void setScnExecution(ScenarioExecution scnExecution) { + this.scnExecution = scnExecution; + } + public int getNumberOfExecutions() { return numberOfExecutions == null ? 1 : numberOfExecutions; } @@ -153,17 +196,25 @@ public void setNumberOfExecutions(int numberOfExecutions) { this.numberOfExecutions = numberOfExecutions; } - protected void afterUnSerialize(ScenarioExecution scnExecution) { - this.scnExecution = scnExecution; + public String getFrequency() { + return frequency; } - public enum Step {STARTEVENT, USERTASK, SERVICETASK, MESSAGE, ENDEVENT, EXCLUSIVEGATEWAY, PARALLELGATEWAY} + public int getNbWorkers() { + return nbWorkers == null || nbWorkers == 0 ? 1 : nbWorkers; + } - /* ******************************************************************** */ - /* */ - /* Check consistence */ - /* */ - /* ******************************************************************** */ + public String getProcessId() { + return processId; + } + + public long getFixedBackOffDelay() { + return fixedBackOffDelay == null ? 0 : fixedBackOffDelay; + } + + protected void afterUnSerialize(ScenarioExecution scnExecution) { + this.scnExecution = scnExecution; + } public void checkConsistence() throws AutomatorException { if (getTaskId() == null || getTaskId().trim().isEmpty()) @@ -173,7 +224,25 @@ public void checkConsistence() throws AutomatorException { if (getTopic() == null || getTopic().trim().isEmpty()) throw new AutomatorException("Step.SERVICETASK: " + getTaskId() + " topic is mandatory"); } + default -> { + } } } + public String getEndWarmingUp() { + return endWarmingUp; + } + + public MODEEXECUTION getModeExecution() { + return modeExecution==null? MODEEXECUTION.WAIT : modeExecution; + } + + /* ******************************************************************** */ + /* */ + /* Check consistence */ + /* */ + /* ******************************************************************** */ + + public enum Step {STARTEVENT, USERTASK, SERVICETASK, MESSAGE, ENDEVENT, EXCLUSIVEGATEWAY, PARALLELGATEWAY} + } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioTool.java b/src/main/java/org/camunda/automator/definition/ScenarioTool.java index 8676265..cab9678 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioTool.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioTool.java @@ -8,7 +8,7 @@ public class ScenarioTool { - public static File loadFile(String fileName, RunScenario runScenario) throws AutomatorException { + public static File loadFile(String fileName, RunScenario runScenario) throws AutomatorException { File file = new File(fileName); if (file.exists()) return file; diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerification.java b/src/main/java/org/camunda/automator/definition/ScenarioVerification.java index 3dfcdd0..d650143 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerification.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerification.java @@ -6,6 +6,7 @@ import java.util.Map; public class ScenarioVerification { + private final ScenarioExecution scenarioExecution; /** * List of activities to check * Maybe null due the Gson deserializer if there is no definition @@ -16,36 +17,30 @@ public class ScenarioVerification { * Maybe null due the Gson deserializer if there is no definition */ private List variables = new ArrayList<>(); - - - /** * Variable to search the process instance, if only the verification is running - * Maybe null due the Gson deserializer if there is no definition + * Maybe null due the Gson deserializer if there is no definition */ - private Map searchProcessInstanceByVariable; - - private final ScenarioExecution scenarioExecution; - + private Map searchProcessInstanceByVariable; protected ScenarioVerification(ScenarioExecution scenarioExecution) { this.scenarioExecution = scenarioExecution; } public List getActivities() { - return activities==null? Collections.emptyList() : activities; - } - - public Map getSearchProcessInstanceByVariable() { - return searchProcessInstanceByVariable==null? Collections.emptyMap() : searchProcessInstanceByVariable; + return activities == null ? Collections.emptyList() : activities; } public void setActivities(List activities) { this.activities = activities; } + public Map getSearchProcessInstanceByVariable() { + return searchProcessInstanceByVariable == null ? Collections.emptyMap() : searchProcessInstanceByVariable; + } + public List getVariables() { - return variables==null? Collections.emptyList(): variables; + return variables == null ? Collections.emptyList() : variables; } public void setVariables(List variables) { diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java b/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java index 24308a8..39a9224 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java @@ -1,14 +1,12 @@ package org.camunda.automator.definition; -public class ScenarioVerificationTask implements ScenarioVerificationBasic{ +public class ScenarioVerificationTask implements ScenarioVerificationBasic { + private final ScenarioVerification scenarioVerification; public ScenarioStep.Step type; public String taskId; public Integer numberOfTasks; - public enum StepState { COMPLETED, ACTIVE} public StepState state; - private final ScenarioVerification scenarioVerification; - public ScenarioVerificationTask(ScenarioVerification scenarioVerification) { this.scenarioVerification = scenarioVerification; } @@ -30,7 +28,7 @@ public void setTaskId(String taskId) { } public int getNumberOfTasks() { - return numberOfTasks==null? 1 : numberOfTasks; + return numberOfTasks == null ? 1 : numberOfTasks; } public void setNumberOfTasks(int numberOfTasks) { @@ -42,7 +40,9 @@ public ScenarioVerification getScenarioVerification() { } public String getSynthesis() { - return "ActivityCheck ["+ taskId +"] "+state.toString(); + return "ActivityCheck [" + taskId + "] " + state.toString(); } + public enum StepState {COMPLETED, ACTIVE} + } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java b/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java index 266ab4b..9affe2a 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java @@ -1,11 +1,11 @@ package org.camunda.automator.definition; -public class ScenarioVerificationVariable implements ScenarioVerificationBasic{ +public class ScenarioVerificationVariable implements ScenarioVerificationBasic { public String name; public Object value; public String getSynthesis() { - return "VariableCheck ["+ name +"]=["+value+"]"; + return "VariableCheck [" + name + "]=[" + value + "]"; } } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java b/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java new file mode 100644 index 0000000..1dbb1d0 --- /dev/null +++ b/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java @@ -0,0 +1,30 @@ +/* ******************************************************************** */ +/* */ +/* ScenarioWarmingUp */ +/* */ +/* Warming up the server */ + +/* ******************************************************************** */ +package org.camunda.automator.definition; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; + +public class ScenarioWarmingUp { + + /** + * The warmingUp will take this duration maximum, except if during this time, all operations warmingUp declare the end + * (see ScenarioStep) + */ + public String duration; + public List operations; + + public Duration getDuration() { + return duration == null ? Duration.ZERO : Duration.parse(duration); + } + + public List getOperations() { + return operations == null ? Collections.emptyList() : operations; + } +} diff --git a/src/main/java/org/camunda/automator/engine/AutomatorException.java b/src/main/java/org/camunda/automator/engine/AutomatorException.java index 808e125..f501fef 100644 --- a/src/main/java/org/camunda/automator/engine/AutomatorException.java +++ b/src/main/java/org/camunda/automator/engine/AutomatorException.java @@ -17,7 +17,7 @@ public AutomatorException(String message) { public AutomatorException(String message, ApiException exception) { this.code = exception.getCode(); - this.message = message + " : " + exception.getMessage()+" "+exception.getResponseBody(); + this.message = message + " : " + exception.getMessage() + " " + exception.getResponseBody(); } public String getMessage() { diff --git a/src/main/java/org/camunda/automator/engine/RunParameters.java b/src/main/java/org/camunda/automator/engine/RunParameters.java index 23679ec..0568684 100644 --- a/src/main/java/org/camunda/automator/engine/RunParameters.java +++ b/src/main/java/org/camunda/automator/engine/RunParameters.java @@ -1,5 +1,8 @@ package org.camunda.automator.engine; +import java.util.Collections; +import java.util.List; + public class RunParameters { public LOGLEVEL logLevel = LOGLEVEL.MONITORING; @@ -9,6 +12,13 @@ public class RunParameters { * Execute the scenario (execution part): create process instance, execute user & service task */ public boolean execution = false; + + /** + * On execution, it's possible to pilot each item, one by one + */ + public boolean creation = true; + public boolean servicetask = true; + public boolean usertask = true; /** * Verify the scenario (verification part) : check that tasks exist */ @@ -22,16 +32,23 @@ public class RunParameters { /** * Allow any deployment */ - public boolean allowDeployment = true; + public boolean deploymentProcess = true; public boolean fullDetailsSythesis = false; + public List filterServiceTask = Collections.emptyList(); + + public boolean deepTracking = true; + /** + * Load the scenario path here. Some functions may be relative to this path + */ + public String scenarioPath; + + public boolean warmingUp = true; public int getNumberOfThreadsPerScenario() { return (numberOfThreadsPerScenario <= 0 ? 1 : numberOfThreadsPerScenario); } - public enum LOGLEVEL {DEBUG, INFO, MONITORING, MAIN, NOTHING} - public boolean isLevelDebug() { return getLogLevelAsNumber() >= 4; } @@ -44,6 +61,18 @@ public boolean isLevelMonitoring() { return getLogLevelAsNumber() >= 2; } + public void setFilterExecutionServiceTask(List filterServiceTask) { + this.filterServiceTask = filterServiceTask; + } + + public boolean blockExecutionServiceTask(String topic) { + // no filter: execute everything + if (filterServiceTask.isEmpty()) + return false; + // filter in place: only if the topic is registered + return !filterServiceTask.contains(topic); + } + private int getLogLevelAsNumber() { switch (logLevel) { case NOTHING -> { @@ -65,9 +94,6 @@ private int getLogLevelAsNumber() { return 0; } - /** - * Load the scenario path here. Some functions may be relative to this path - */ - public String scenarioPath; + public enum LOGLEVEL {DEBUG, INFO, MONITORING, MAIN, NOTHING} } diff --git a/src/main/java/org/camunda/automator/engine/RunResult.java b/src/main/java/org/camunda/automator/engine/RunResult.java index 90e2a7a..64888d7 100644 --- a/src/main/java/org/camunda/automator/engine/RunResult.java +++ b/src/main/java/org/camunda/automator/engine/RunResult.java @@ -12,12 +12,12 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class RunResult { - Logger logger = LoggerFactory.getLogger(RunResult.class); - /** * Scenario attached to this execution */ @@ -27,25 +27,20 @@ public class RunResult { */ private final List listErrors = new ArrayList<>(); private final List listDetailsSteps = new ArrayList<>(); - - public class VerificationStatus { - public ScenarioVerificationBasic verification; - public boolean isSuccess; - public String message; - } - - private final List listVerifications = new ArrayList<>(); - /** * process instance started for this execution. The executionResult stand for only one process instance */ private final List listProcessInstancesId = new ArrayList<>(); - private final List listProcessIdDeployed = new ArrayList<>(); - - private int numberOfProcessInstances = 0; + Logger logger = LoggerFactory.getLogger(RunResult.class); + /** + * Keep a photo of process instance created/failed per processid + */ + private Map recordCreationPIMap = new HashMap<>(); private int numberOfSteps = 0; + private int numberOfErrorSteps = 0; + /** * Time to execute it */ @@ -56,6 +51,19 @@ public RunResult(RunScenario runScenario) { } + /** + * Add the process instance - this is mandatory to + * + * @param processInstanceId processInstanceId to add + */ + public void addProcessInstanceId(String processId, String processInstanceId) { + this.listProcessInstancesId.add(processInstanceId); + + RecordCreationPI create= recordCreationPIMap.getOrDefault(processId, new RecordCreationPI(processId)); + create.nbCreated++; + recordCreationPIMap.put(processId, create); + } + /* ******************************************************************** */ /* */ @@ -64,13 +72,15 @@ public RunResult(RunScenario runScenario) { /* ******************************************************************** */ /** - * Add the process instance - this is mandatory to - * - * @param processInstanceId processInstanceId to add + * large flow: just register the number of PI */ - public void addProcessInstanceId(String processInstanceId) { - this.listProcessInstancesId.add(processInstanceId); - numberOfProcessInstances++; + public void registerAddProcessInstance(String processId, boolean withSuccess) { + RecordCreationPI create= recordCreationPIMap.getOrDefault(processId, new RecordCreationPI(processId)); + if (withSuccess) + create.nbCreated++; + else + create.nbFailed++; + recordCreationPIMap.put(processId, create); } public void addTimeExecution(long timeToAdd) { @@ -87,19 +97,31 @@ public void addStepExecution(ScenarioStep step, long timeExecution) { } } + /** + * large flow: just register the number of execution + */ + public void registerAddStepExecution() { + numberOfSteps++; + } + + public void registerAddErrorStepExecution() { + numberOfErrorSteps++; + + } + + public List getListErrors() { + return listErrors; + } + /* ******************************************************************** */ /* */ /* Errors */ /* */ /* ******************************************************************** */ - public List getListErrors() { - return listErrors; - } - public void addError(ScenarioStep step, String explanation) { this.listErrors.add(new ErrorDescription(step, explanation)); - logger.error("scnResult: " + (step == null ? "" : step.getType().toString()) + " error " + explanation); + logger.error((step == null ? "" : step.getType().toString()) + " " + explanation); } @@ -107,15 +129,6 @@ public void addError(ScenarioStep step, AutomatorException e) { this.listErrors.add(new ErrorDescription(step, e.getMessage())); } - - - /* ******************************************************************** */ - /* */ - /* Verifications */ - /* */ - /* ******************************************************************** */ - - public void addVerification(ScenarioVerificationBasic verification, boolean isSuccess, String message) { VerificationStatus verificationStatus = new VerificationStatus(); verificationStatus.verification = verification; @@ -124,16 +137,18 @@ public void addVerification(ScenarioVerificationBasic verification, boolean isSu this.listVerifications.add(verificationStatus); } - public List getListVerifications() { - return listVerifications; - } + /* ******************************************************************** */ /* */ - /* merge */ + /* Verifications */ /* */ /* ******************************************************************** */ + public List getListVerifications() { + return listVerifications; + } + /** * Merge the result in this result * @@ -143,10 +158,18 @@ public void add(RunResult result) { addTimeExecution(result.getTimeExecution()); listErrors.addAll(result.listErrors); listVerifications.addAll(result.listVerifications); - numberOfProcessInstances += result.numberOfProcessInstances; + for (Map.Entry entry : result.recordCreationPIMap.entrySet()) { + RecordCreationPI currentReference = recordCreationPIMap.getOrDefault(entry.getKey(), new RecordCreationPI( + entry.getKey())); + currentReference.nbFailed += entry.getValue().nbFailed; + currentReference.nbCreated += entry.getValue().nbCreated; + + recordCreationPIMap.put(entry.getKey(), currentReference); + } numberOfSteps += result.numberOfSteps; + numberOfErrorSteps += result.numberOfErrorSteps; // we collect the list only if the level is low - if (runScenario.getRunParameters()!=null && runScenario.getRunParameters().isLevelInfo()) { + if (runScenario.getRunParameters() != null && runScenario.getRunParameters().isLevelInfo()) { listDetailsSteps.addAll(result.listDetailsSteps); listProcessInstancesId.addAll(result.listProcessInstancesId); } @@ -154,7 +177,7 @@ public void add(RunResult result) { /* ******************************************************************** */ /* */ - /* method to get information */ + /* merge */ /* */ /* ******************************************************************** */ @@ -163,6 +186,12 @@ public boolean isSuccess() { return listErrors.isEmpty() && nbVerificationErrors == 0; } + /* ******************************************************************** */ + /* */ + /* method to get information */ + /* */ + /* ******************************************************************** */ + public String getFirstProcessInstanceId() { return listProcessInstancesId.isEmpty() ? null : listProcessInstancesId.get(0); } @@ -187,6 +216,25 @@ public void addDeploymentProcessId(String processId) { this.listProcessIdDeployed.add(processId); } + public Map getRecordCreationPI() { + return recordCreationPIMap; + } + + public long getRecordCreationPIAllProcesses() { + long sum = 0; + for (RecordCreationPI value : recordCreationPIMap.values()) + sum += value.nbCreated; + return sum; + } + + public int getNumberOfSteps() { + return numberOfSteps; + } + + public int getNumberOfErrorSteps() { + return numberOfErrorSteps; + } + /** * @return a synthesis */ @@ -200,10 +248,14 @@ public String getSynthesis(boolean fullDetail) { synthesis.append(timeExecution); synthesis.append(" timeExecution(ms), "); - synthesis.append(numberOfProcessInstances); - synthesis.append(" processInstancesCreated, "); + synthesis.append(recordCreationPIMap.get(runScenario.getScenario().getProcessId()).nbCreated); + synthesis.append(" PICreated, "); + synthesis.append(recordCreationPIMap.get(runScenario.getScenario().getProcessId()).nbFailed); + synthesis.append(" PIFailed, "); synthesis.append(numberOfSteps); synthesis.append(" stepsExecuted, "); + synthesis.append(numberOfErrorSteps); + synthesis.append(" errorStepsExecuted, "); StringBuilder errorMessage = new StringBuilder(); // add errors @@ -223,8 +275,8 @@ public String getSynthesis(boolean fullDetail) { synthesis.append(verificationMessage); } // add full details - if (fullDetail && numberOfProcessInstances == listProcessInstancesId.size()) { - synthesis.append(" ListOfProcessInstancesCreated: "); + if (fullDetail) { + synthesis.append(" ListOfPICreated: "); synthesis.append(listProcessInstancesId.stream() // stream .collect(Collectors.joining(","))); @@ -233,17 +285,11 @@ public String getSynthesis(boolean fullDetail) { } - /* ******************************************************************** */ - /* */ - /* local class */ - /* */ - /* ******************************************************************** */ - public static class StepExecution { public final List listErrors = new ArrayList<>(); + private final RunResult scenarioExecutionResult; public ScenarioStep step; public long timeExecution; - private final RunResult scenarioExecutionResult; public StepExecution(RunResult scenarioExecutionResult) { this.scenarioExecutionResult = scenarioExecutionResult; @@ -254,6 +300,12 @@ public void addError(ErrorDescription error) { } } + /* ******************************************************************** */ + /* */ + /* local class */ + /* */ + /* ******************************************************************** */ + public static class ErrorDescription { public ScenarioStep step; public ScenarioVerificationBasic verificationBasic; @@ -270,4 +322,25 @@ public ErrorDescription(ScenarioVerificationBasic verificationBasic, String expl } } + public class VerificationStatus { + public ScenarioVerificationBasic verification; + public boolean isSuccess; + public String message; + } + + public static class RecordCreationPI { + public String processId; + public long nbCreated=0; + public long nbFailed=0; + public RecordCreationPI(String processId) { + this.processId = processId; + } + + public void add(RecordCreationPI record) { + if (record==null) + return; + nbCreated+= record.nbCreated; + nbFailed += record.nbFailed; + } + } } diff --git a/src/main/java/org/camunda/automator/engine/RunScenario.java b/src/main/java/org/camunda/automator/engine/RunScenario.java index a87a714..965a9a5 100644 --- a/src/main/java/org/camunda/automator/engine/RunScenario.java +++ b/src/main/java/org/camunda/automator/engine/RunScenario.java @@ -1,11 +1,12 @@ package org.camunda.automator.engine; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.Scenario; import org.camunda.automator.definition.ScenarioDeployment; import org.camunda.automator.definition.ScenarioExecution; import org.camunda.automator.definition.ScenarioTool; +import org.camunda.automator.engine.flow.RunScenarioFlows; import org.camunda.automator.services.ServiceAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,19 +21,17 @@ import java.util.concurrent.Future; /** - * This execute a scenario, in a context. Context is + * This object executes a scenario, in a context. Context is * - the scenario to execute * - the BPMN Engine to access * - the RunParameters */ public class RunScenario { - Logger logger = LoggerFactory.getLogger(RunScenario.class); - private final Scenario scenario; private final BpmnEngine bpmnEngine; private final RunParameters runParameters; - private final ServiceAccess serviceAccess; + Logger logger = LoggerFactory.getLogger(RunScenario.class); /** * @param scenario scenario to be executed @@ -55,15 +54,18 @@ public RunScenario(Scenario scenario, * A scenario is composed of * - deployment * - execution (which contains the verifications) - + *

* these steps are controlled by the runParameters * * @return tue result object */ public RunResult runScenario() { RunResult result = new RunResult(this); - if (runParameters.allowDeployment) + logger.info("RunScenario: ------ Deployment ({})", runParameters.deploymentProcess); + if (runParameters.deploymentProcess) result.add(runDeployment()); + logger.info("RunScenario: ------ End deployment "); + // verification is inside execution result.add(runExecutions()); return result; @@ -80,23 +82,29 @@ public RunResult runDeployment() { // first, do we have to deploy something? if (scenario.getDeployments() != null) { for (ScenarioDeployment deployment : scenario.getDeployments()) { + boolean sameTypeServer = false; - if (deployment.server.equals(BpmnEngineConfiguration.CamundaEngine.CAMUNDA_7)) { - sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineConfiguration.CamundaEngine.CAMUNDA_7); - } else if (deployment.server.equals(BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8)) { - sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8) - || bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineConfiguration.CamundaEngine.CAMUNDA_8_SAAS); + if (deployment.serverType.equals(ConfigurationBpmEngine.CamundaEngine.CAMUNDA_7)) { + sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(ConfigurationBpmEngine.CamundaEngine.CAMUNDA_7); + } else if (deployment.serverType.equals(ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8)) { + sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8) + || bpmnEngine.getTypeCamundaEngine().equals(ConfigurationBpmEngine.CamundaEngine.CAMUNDA_8_SAAS); } if (sameTypeServer) { try { long begin = System.currentTimeMillis(); File processFile = ScenarioTool.loadFile(deployment.processFile, this); + logger.info("Deploy process[{}] on {}", processFile.getName(), bpmnEngine.getSignature()); result.addDeploymentProcessId(bpmnEngine.deployBpmn(processFile, deployment.policy)); result.addTimeExecution(System.currentTimeMillis() - begin); } catch (AutomatorException e) { result.addError(null, "Can't deploy process [" + deployment.processFile + "] " + e.getMessage()); } } + else { + logger.info("RunScenario: can't Deploy ({}), not the same server", deployment.processFile); + + } } } return result; @@ -114,30 +122,36 @@ public RunResult runExecutions() { // each execution is run in a different thread ExecutorService executor = Executors.newFixedThreadPool(runParameters.getNumberOfThreadsPerScenario()); - List> listFutures = new ArrayList<>(); - - for (int i = 0; i < scenario.getExecutions().size(); i++) { - ScenarioExecution scnExecution = scenario.getExecutions().get(i); - ScnExecutionCallable scnExecutionCallable = new ScnExecutionCallable("Agent-" + i, this, scnExecution, - runParameters); + // the scenario can be an Execution or a Flow + if (!scenario.getExecutions().isEmpty()) { + List> listFutures = new ArrayList<>(); - listFutures.add(executor.submit(scnExecutionCallable)); - } + for (int i = 0; i < scenario.getExecutions().size(); i++) { + ScenarioExecution scnExecution = scenario.getExecutions().get(i); + ScnExecutionCallable scnExecutionCallable = new ScnExecutionCallable("Agent-" + i, this, scnExecution, + runParameters); - // wait the end of all executions - try { - for (Future f : listFutures) { - Object scnRunResult = f.get(); - result.add((RunResult) scnRunResult); + listFutures.add(executor.submit(scnExecutionCallable)); } - } catch (ExecutionException ee) { - result.addError(null, "Error during executing in parallel " + ee.getMessage()); + // wait the end of all executions + try { + for (Future f : listFutures) { + Object scnRunResult = f.get(); + result.add((RunResult) scnRunResult); + } + + } catch (ExecutionException ee) { + result.addError(null, "Error during executing in parallel " + ee.getMessage()); - } catch (Exception e) { - result.addError(null, "Error during executing in parallel " + e.getMessage()); + } catch (Exception e) { + result.addError(null, "Error during executing in parallel " + e.getMessage()); + } + } + if (!scenario.getFlows().isEmpty()) { + RunScenarioFlows scenarioFlows = new RunScenarioFlows(serviceAccess, this); + scenarioFlows.execute(result); } - return result; } diff --git a/src/main/java/org/camunda/automator/engine/RunScenarioExecution.java b/src/main/java/org/camunda/automator/engine/RunScenarioExecution.java index 392bf54..b6a368c 100644 --- a/src/main/java/org/camunda/automator/engine/RunScenarioExecution.java +++ b/src/main/java/org/camunda/automator/engine/RunScenarioExecution.java @@ -8,24 +8,20 @@ import java.time.Duration; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** - * Execute an exection in a runscenario + * one ExecutionStep in a runScenario */ public class RunScenarioExecution { private final Logger logger = LoggerFactory.getLogger(RunScenarioExecution.class); - - private String agentName = ""; - private final RunScenario runScenario; private final ScenarioExecution scnExecution; + private String agentName = ""; public RunScenarioExecution(RunScenario runScenario, ScenarioExecution scnExecution) { this.runScenario = runScenario; @@ -88,9 +84,10 @@ public RunResult runExecution() { */ public RunResult startEvent(RunResult result, ScenarioStep step) { try { - result.addProcessInstanceId(runScenario.getBpmnEngine() + result.addProcessInstanceId(step.getScnExecution().getScnHead().getProcessId(), + runScenario.getBpmnEngine() .createProcessInstance(step.getScnExecution().getScnHead().getProcessId(), step.getTaskId(), // activityId - getVariablesStep(step))); // resolve variables + RunZeebeOperation.getVariablesStep(runScenario, step))); // resolve variables } catch (AutomatorException e) { result.addError(step, "Error at creation " + e.getMessage()); } @@ -106,7 +103,6 @@ public RunResult startEvent(RunResult result, ScenarioStep step) { */ public RunResult executeUserTask(RunResult result, ScenarioStep step) { - if (step.getDelay() != null) { Duration duration = Duration.parse(step.getDelay()); try { @@ -146,7 +142,9 @@ public RunResult executeUserTask(RunResult result, ScenarioStep step) { + result.getFirstProcessInstanceId() + "]"); return result; } - runScenario.getBpmnEngine().executeUserTask(listActivities.get(0), step.getUserId(), getVariablesStep(step)); + runScenario.getBpmnEngine() + .executeUserTask(listActivities.get(0), step.getUserId(), + RunZeebeOperation.getVariablesStep(runScenario, step)); } catch (AutomatorException e) { result.addError(step, e.getMessage()); return result; @@ -203,7 +201,9 @@ public RunResult executeServiceTask(RunResult result, ScenarioStep step) { + result.getFirstProcessInstanceId() + "]"); return result; } - runScenario.getBpmnEngine().executeServiceTask(listActivities.get(0), step.getUserId(), getVariablesStep(step)); + runScenario.getBpmnEngine() + .executeServiceTask(listActivities.get(0), step.getUserId(), + RunZeebeOperation.getVariablesStep(runScenario, step)); } catch (AutomatorException e) { result.addError(step, e.getMessage()); return result; @@ -214,24 +214,7 @@ public RunResult executeServiceTask(RunResult result, ScenarioStep step) { } - /** - * Resolve variables - */ - private Map getVariablesStep(ScenarioStep step) throws AutomatorException { - Map variablesCompleted = new HashMap<>(); - variablesCompleted.putAll(step.getVariables()); - - // execute all operations now - for (Map.Entry entryOperation : step.getVariablesOperations().entrySet()) { - variablesCompleted.put(entryOperation.getKey(), - runScenario.getServiceAccess().serviceDataOperation.execute(entryOperation.getValue(), runScenario)); - } - // Check if - - - return variablesCompleted; - } /* ******************************************************************** */ /* */ @@ -265,13 +248,13 @@ public Object call() throws Exception { // two uses case here: // Execution AND verifications: for each process Instance created, a verification is running - // Only VERIFICATION: the verification ojbnect define a filter to search existing process instance. Verification is perform againts this list - if (runParameters.verification && (scnExecution.getVerifications()!=null)) { + // Only VERIFICATION: the verification ojbect define a filter to search existing process instance. Verification is perform againts this list + if (runParameters.verification && (scnExecution.getVerifications() != null)) { if (runParameters.execution) { // we just finish executing process instance, so wait 30 S to let the engine finish try { Thread.sleep(30 * 1000); - }catch(Exception e) { + } catch (Exception e) { // nothing to do } runVerifications(); @@ -279,14 +262,13 @@ public Object call() throws Exception { // use the search criteria if (scnExecution.getVerifications().getSearchProcessInstanceByVariable().isEmpty()) { scnRunResult.addVerification(null, false, "No Search Instance by Variable is defined"); - } - else { + } else { List listProcessInstances = runScenario.getBpmnEngine() .searchProcessInstanceByVariable(scnExecution.getScnHead().getProcessId(), scnExecution.getVerifications().getSearchProcessInstanceByVariable(), 100); for (BpmnEngine.ProcessDescription processInstance : listProcessInstances) { - scnRunResult.addProcessInstanceId(processInstance.processInstanceId); + scnRunResult.addProcessInstanceId(scnExecution.getScnHead().getProcessId(), processInstance.processInstanceId); } runVerifications(); } @@ -311,13 +293,12 @@ public void runExecution() { if (scnRunExecution.runScenario.getRunParameters().isLevelDebug()) logger.info( - "ScnRunExecution.StartExecution.Execute [" + scnRunExecution.runScenario.getScenario().getName() + "."+step.getTaskId()+" agent[" - + agentName + "]"); - + "ScnRunExecution.StartExecution.Execute [" + scnRunExecution.runScenario.getScenario().getName() + "." + + step.getTaskId() + " agent[" + agentName + "]"); try { step.checkConsistence(); - } catch(AutomatorException e) { + } catch (AutomatorException e) { scnRunResult.addError(step, e.getMessage()); continue; } @@ -328,15 +309,18 @@ public void runExecution() { } switch (step.getType()) { case STARTEVENT -> { - scnRunResult = scnRunExecution.startEvent(scnRunResult, step); + if (scnRunExecution.runScenario.getRunParameters().creation) + scnRunResult = scnRunExecution.startEvent(scnRunResult, step); } case USERTASK -> { // wait for the user Task - scnRunResult = scnRunExecution.executeUserTask(scnRunResult, step); + if (scnRunExecution.runScenario.getRunParameters().usertask) + scnRunResult = scnRunExecution.executeUserTask(scnRunResult, step); } case SERVICETASK -> { // wait for the user Task - scnRunResult = scnRunExecution.executeServiceTask(scnRunResult, step); + if (scnRunExecution.runScenario.getRunParameters().servicetask) + scnRunResult = scnRunExecution.executeServiceTask(scnRunResult, step); } case ENDEVENT -> { diff --git a/src/main/java/org/camunda/automator/engine/RunScenarioVerification.java b/src/main/java/org/camunda/automator/engine/RunScenarioVerification.java index f40b5a1..f382530 100644 --- a/src/main/java/org/camunda/automator/engine/RunScenarioVerification.java +++ b/src/main/java/org/camunda/automator/engine/RunScenarioVerification.java @@ -63,20 +63,22 @@ private void checkTask(RunScenario runScenario, } // check the type for each taskDescription List listNotExpected = listTaskDescriptions.stream() - .filter(t -> !(verificationActivity.getType() != null && verificationActivity.getType().toString().equalsIgnoreCase(t.type.toString()))) - .filter(t-> ( - (t.isCompleted && ScenarioVerificationTask.StepState.COMPLETED.toString().equals(verificationActivity.state.toString())) - || (!t.isCompleted && ScenarioVerificationTask.StepState.ACTIVE.toString().equals(verificationActivity.state.toString())))) + .filter(t -> !(verificationActivity.getType() != null && verificationActivity.getType() + .toString() + .equalsIgnoreCase(t.type.toString()))) + .filter(t -> ((t.isCompleted && ScenarioVerificationTask.StepState.COMPLETED.toString() + .equals(verificationActivity.state.toString())) || (!t.isCompleted + && ScenarioVerificationTask.StepState.ACTIVE.toString().equals(verificationActivity.state.toString())))) .toList(); if (!listNotExpected.isEmpty()) { message.append("CheckTask: FAILED_BADTYPE PID["); - message.append( processInstanceId ); + message.append(processInstanceId); message.append("] Task["); - message.append( verificationActivity.taskId); - message.append( "] type expected ["); - message.append( verificationActivity.type.toString()); + message.append(verificationActivity.taskId); + message.append("] type expected ["); + message.append(verificationActivity.type.toString()); message.append("] FAILED, received "); - message.append( listNotExpected.stream().map(t -> t.taskId + ":" + t.type.toString()).toList()); + message.append(listNotExpected.stream().map(t -> t.taskId + ":" + t.type.toString()).toList()); } result.addVerification(verificationActivity, message.isEmpty(), message.toString()); diff --git a/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java b/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java new file mode 100644 index 0000000..f48ce95 --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java @@ -0,0 +1,32 @@ +/* ******************************************************************** */ +/* */ +/* RunZeebeOperation */ +/* */ +/* Different tool to execute operation */ +/* ******************************************************************** */ +package org.camunda.automator.engine; + +import org.camunda.automator.definition.ScenarioStep; + +import java.util.HashMap; +import java.util.Map; + +public class RunZeebeOperation { + + /** + * Resolve variables + */ + public static Map getVariablesStep(RunScenario runScenario, ScenarioStep step) + throws AutomatorException { + Map variablesCompleted = new HashMap<>(); + variablesCompleted.putAll(step.getVariables()); + + // execute all operations now + for (Map.Entry entryOperation : step.getVariablesOperations().entrySet()) { + variablesCompleted.put(entryOperation.getKey(), + runScenario.getServiceAccess().serviceDataOperation.execute(entryOperation.getValue(), runScenario)); + } + + return variablesCompleted; + } +} diff --git a/src/main/java/org/camunda/automator/engine/SchedulerExecution.java b/src/main/java/org/camunda/automator/engine/SchedulerExecution.java index 16d1a92..cb1d4cb 100644 --- a/src/main/java/org/camunda/automator/engine/SchedulerExecution.java +++ b/src/main/java/org/camunda/automator/engine/SchedulerExecution.java @@ -1,10 +1,7 @@ package org.camunda.automator.engine; import org.camunda.automator.AutomatorCLI; -import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; -import org.camunda.automator.bpmnengine.BpmnEngineFactory; -import org.camunda.automator.definition.Scenario; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.services.ServiceAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,31 +11,19 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.io.File; -import java.util.ArrayList; -import java.util.List; @Component @ConfigurationProperties(prefix = "automator.scheduler") public class SchedulerExecution { - @Value("${automator.scheduler.run-at-startup}") - public boolean runAtStartup; - @Value("${automator.scheduler.scenario-path}") + @Value("${automator.scheduler.scenario-path:''}") public String scenarioPath; - @Value("${automator.scheduler.server}") - public String serverScheduler; // https://www.baeldung.com/spring-boot-yaml-list // @Value("${automator.scheduler.colors}") - - private List colors; - @Autowired - BpmnEngineConfiguration bpmnEngineConfiguration; - + ConfigurationBpmEngine bpmnEngineConfiguration; Logger logger = LoggerFactory.getLogger(SchedulerExecution.class); - @Autowired ServiceAccess serviceAccess; @@ -47,54 +32,7 @@ public void init() { // We run the CLI, do nothing if (AutomatorCLI.isRunningCLI) return; - // read the configuration, and start the execution - if (scenarioPath != null && runAtStartup) { - // execute all test now - if (bpmnEngineConfiguration == null) { - logger.error("Unknown configuration"); - return; - } - BpmnEngine bpmnEngine; - try { - bpmnEngine = BpmnEngineFactory.getInstance() - .getEngineFromConfiguration(bpmnEngineConfiguration, getSchedulerServer()); - } catch (Exception e) { - logger.error("SchedulerExecution.init: Server connection Initialization error"); - return; - } - RunParameters runParameters = new RunParameters(); - runParameters.logLevel = RunParameters.LOGLEVEL.MONITORING; - - // parse all file and sub directory so search scenarion - List listScenarioFile = collectScenario(new File(scenarioPath)); - for (File fileScenario : listScenarioFile) { - try { - Scenario scenario = Scenario.createFromFile(fileScenario); - RunScenario scenarioExecution = new RunScenario(scenario, bpmnEngine, runParameters, serviceAccess); - RunResult scenarioExecutionResult = scenarioExecution.runScenario(); - } catch (Exception e) { - logger.error("Error processing [" + fileScenario.getAbsolutePath() + "] " + e.getMessage()); - } - } - } + logger.info("SchedulerExecution soon"); } - private BpmnEngineConfiguration.BpmnServerDefinition getSchedulerServer() throws AutomatorException { - // explode the scheduler.server information to get the correct server definition - return bpmnEngineConfiguration.getByServerName(serverScheduler); - - } - - private List collectScenario(File folder) { - List listFiles = new ArrayList<>(); - File[] filesListInFolder = folder.listFiles(); - for (File file : filesListInFolder) { - if (file.isDirectory()) { - listFiles.addAll(collectScenario(file)); - } - if (file.getName().endsWith(".json")) - listFiles.add(file); - } - return listFiles; - } } diff --git a/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java b/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java new file mode 100644 index 0000000..4456ebb --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java @@ -0,0 +1,23 @@ +/* ******************************************************************** */ +/* */ +/* FixedBackoffSupplier */ +/* */ +/* A backoff based on a fixed delay */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import io.camunda.zeebe.client.api.worker.BackoffSupplier; + +public class FixedBackoffSupplier implements BackoffSupplier { + + private long fixedBackOffDelay = 0; + + public FixedBackoffSupplier(long fixedBackOffDelay) { + this.fixedBackOffDelay = fixedBackOffDelay; + } + + @Override + public long supplyRetryDelay(long currentRetryDelay) { + return fixedBackOffDelay; + } +} \ No newline at end of file diff --git a/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java b/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java new file mode 100644 index 0000000..c203b50 --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java @@ -0,0 +1,298 @@ +/* ******************************************************************** */ +/* */ +/* CheckObjective */ +/* */ +/* Check if an objective is reach */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import io.camunda.operate.search.DateFilter; +import org.camunda.automator.bpmnengine.BpmnEngine; +import org.camunda.automator.definition.ScenarioFlowControl; +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RunObjectives { + private final BpmnEngine bpmnEngine; + private final Map recordCreationPIMap; + private final Map> flowRateMnObjective = new HashMap<>(); + private final List listObjectives; + Logger logger = LoggerFactory.getLogger(RunObjectives.class); + private DateFilter startDateFilter; + private DateFilter endDateFilter; + private long lastHeartBeat; + + public RunObjectives(List listObjectives, + BpmnEngine bpmnEngine, + Map recordCreationPIMap) { + this.listObjectives = listObjectives; + this.bpmnEngine = bpmnEngine; + this.recordCreationPIMap = recordCreationPIMap; + + for (int i = 0; i < listObjectives.size(); i++) { + listObjectives.get(i).index = i; + } + } + + public void setStartDate(Date startTestDate) { + this.startDateFilter = new DateFilter(startTestDate); + this.lastHeartBeat = System.currentTimeMillis(); + } + + public void setEndDate(Date endTestDate) { + this.endDateFilter = new DateFilter(endTestDate); + } + + /** + * heartbeat + */ + public void heartBeat() { + long currentTime = System.currentTimeMillis(); + // only one minutes + if (currentTime - lastHeartBeat < 1000 * 60) + return; + + // one minutes: do we have a FLOWRATEUSERTASKMN objective + for (ScenarioFlowControl.Objective objective : listObjectives) { + if (ScenarioFlowControl.Objective.TYPEOBJECTIVE.FLOWRATEUSERTASKMN.equals(objective.type)) { + // get the value + SavePhoto currentPhoto = new SavePhoto(); + try { + currentPhoto.nbOfTasks = bpmnEngine.countNumberOfTasks(objective.processId, objective.taskId); + } catch (AutomatorException e) { + logger.error("Can't get NumberOfTask "); + } + List listValues = flowRateMnObjective.getOrDefault(objective.index, new ArrayList<>()); + SavePhoto previousPhoto = listValues.isEmpty() ? new SavePhoto() : listValues.get(listValues.size() - 1); + currentPhoto.delta = currentPhoto.nbOfTasks - previousPhoto.nbOfTasks; + listValues.add(currentPhoto); + flowRateMnObjective.put(objective.index, listValues); + logger.info("heartBeat: FlowRateUserTaskMn [{}] prev [{}} current [{}] delta [{}] expected [{}] in {} s", + objective.label, previousPhoto.nbOfTasks, currentPhoto.nbOfTasks, currentPhoto.delta, objective.value, + (currentTime - lastHeartBeat) / 1000); + } + } + lastHeartBeat = currentTime; + } + + /** + * Check the objective, and return an analysis string; If the string is empty, the objectif is reach + * + * @return empty if the objective is Ok, else an analysis + */ + public List check() { + List listCheck = new ArrayList<>(); + for (ScenarioFlowControl.Objective objective : listObjectives) { + listCheck.add(switch (objective.type) { + case CREATED -> checkObjectiveCreated(objective); + case ENDED -> checkObjectiveEnded(objective); + case USERTASK -> checkObjectiveUserTask(objective); + case FLOWRATEUSERTASKMN -> checkObjectiveFlowRate(objective); + }); + } + return listCheck; + } + + /** + * Creation: does the number of process instance was created? + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveCreated(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.objectiveValue = objective.value; + if (objective.value <= 0) { + objectiveResult.success = true; + objectiveResult.analysis += "No value to reach"; + return objectiveResult; + } + try { + long processInstancesCreatedAPI = bpmnEngine.countNumberOfProcessInstancesCreated(objective.processId, + startDateFilter, endDateFilter); + RunResult.RecordCreationPI recordCreation = recordCreationPIMap.getOrDefault(objective.processId, + new RunResult.RecordCreationPI(objective.processId)); + + objectiveResult.recordedSuccessValue = recordCreation.nbCreated; + objectiveResult.recordedFailValue = recordCreation.nbFailed; + + int percent = (int) (100.0 * objectiveResult.recordedSuccessValue / (objective.value == 0 ? 1 : objective.value)); + + objectiveResult.analysis += + "Objective " + objective.label + ": ObjectiveCreation[" + objective.value // objective + + "] Created(zeebeAPI)[" + processInstancesCreatedAPI // Value by the API, not really accurate + + "] Create(AutomatorRecord)[" + objectiveResult.recordedSuccessValue // value recorded by automator + + " (" + percent + " % )" // percent based on the recorded value + + " CreateFail(AutomatorRecord)[" + objectiveResult.recordedFailValue + "]"; + + if (objectiveResult.recordedSuccessValue < objective.value) { + objectiveResult.success = false; + } + } catch (AutomatorException e) { + objectiveResult.success = false; + objectiveResult.analysis += "Can't search countNumberOfProcessInstancesCreated " + e.getMessage(); + } + return objectiveResult; + } + + /** + * ObjectiveEnded : does process ended? + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveEnded(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.objectiveValue = objective.value; + if (objective.value <= 0) { + objectiveResult.success = true; + objectiveResult.analysis += "No value to reach"; + return objectiveResult; + } + try { + objectiveResult.recordedSuccessValue = bpmnEngine.countNumberOfProcessInstancesEnded(objective.processId, + startDateFilter, endDateFilter); + if (objectiveResult.recordedSuccessValue < objective.value) { + objectiveResult.analysis += "Fail: " + objective.label + " : " + objective.value + " ended expected, " + + objectiveResult.recordedSuccessValue + " created (" + (int) (100.0 * objectiveResult.recordedSuccessValue + / objective.value) + " %), "; + objectiveResult.success = false; + } + + } catch (AutomatorException e) { + objectiveResult.success = false; + objectiveResult.analysis += "Can't search NumberOfProcessInstanceEnded: " + e.getMessage(); + } + return objectiveResult; + } + + /** + * UserTask: does user tasks are present? + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveUserTask(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.objectiveValue = objective.value; + if (objective.value <= 0) { + objectiveResult.success = true; + objectiveResult.analysis += "No value to reach"; + return objectiveResult; + } + try { + objectiveResult.recordedSuccessValue = bpmnEngine.countNumberOfTasks(objective.processId, objective.taskId); + if (objectiveResult.recordedSuccessValue < objective.value) { + objectiveResult.analysis += "Fail: " + objective.label + " : [" + objective.value + "] tasks expected, "; + objectiveResult.analysis += + objectiveResult.recordedSuccessValue + " found (" + (int) (100.0 * objectiveResult.recordedSuccessValue + / objective.value) + " %), "; + objectiveResult.success = false; + } + } catch (AutomatorException e) { + objectiveResult.success = false; + objectiveResult.analysis += "Can't search NumberOfProcessInstanceEnded: " + e.getMessage(); + } + return objectiveResult; + } + + /** + * FlowRate + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveFlowRate(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + // check all values + try { + long lowThreshold = (long) (((double) objective.value) * (1.0 + - ((double) objective.getStandardDeviation()) / 100.0)); + objectiveResult.objectiveValue = objective.value; + objectiveResult.analysis += + "Threshold[" + objective.value + "] standardDeviation[" + objective.getStandardDeviation() + "] LowThreshold[" + + lowThreshold + "]"; + long sumValues = 0; + List listValues = flowRateMnObjective.getOrDefault(objective.index, new ArrayList<>()); + if (listValues.isEmpty()) { + objectiveResult.analysis += "No values"; + objectiveResult.success = false; + return objectiveResult; + } + + StringBuilder valuesString = new StringBuilder(); + int numberUnderThreshold = 0; + int count = 0; + for (SavePhoto photo : listValues) { + sumValues += photo.delta; + count++; + if (count == 50) { + valuesString.append("... TooManyValues["); + valuesString.append(listValues.size()); + valuesString.append("]"); + } + if (count < 50) { + valuesString.append(photo.delta); + valuesString.append(","); + } + + if (photo.delta < lowThreshold) { + numberUnderThreshold++; + } + } + if (numberUnderThreshold > 0) { + objectiveResult.analysis += + "NumberOrValueUnderThreshold[" + numberUnderThreshold + "], values: " + valuesString; + objectiveResult.success = false; + } + // the total must be at the value + long averageValue = (long) (((double) sumValues) / listValues.size()); + objectiveResult.recordedSuccessValue = averageValue; + if (averageValue < objective.value) { + objectiveResult.analysis += "AverageUnderObjective[" + averageValue + "]"; + objectiveResult.success = false; + } else { + objectiveResult.analysis += "AverageReach[" + averageValue + "]"; + } + } catch (Exception e) { + logger.error("Error during checkFlowRateObjective {}", e.getMessage()); + objectiveResult.success = false; + } + return objectiveResult; + + } + + public static class ObjectiveResult { + public String analysis = ""; + public boolean success = true; + public long objectiveValue; + public long recordedSuccessValue; + public long recordedFailValue; + ScenarioFlowControl.Objective objective; + + public ObjectiveResult(ScenarioFlowControl.Objective objective) { + this.objective = objective; + } + } + + /** + * Key is the Objective Index + * Value is a list of two information: + * - the reference value in the slot + * - the + */ + public static class SavePhoto { + public long nbOfTasks = 0; + public long delta = 0; + + } + +} diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java new file mode 100644 index 0000000..926e389 --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java @@ -0,0 +1,86 @@ +/* ******************************************************************** */ +/* */ +/* RunScenarioFlowBasic */ +/* */ +/* All execution derived on this class */ +/* When multiple worker are required for a step, multiple FlowBasic */ +/* object are created, with a different index. */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import org.camunda.automator.definition.ScenarioStep; +import org.camunda.automator.engine.RunResult; +import org.camunda.automator.engine.RunScenario; + +public abstract class RunScenarioFlowBasic { + + protected final RunResult runResult; + private final ScenarioStep scenarioStep; + private final RunScenario runScenario; + private final int index; + + RunScenarioFlowBasic(ScenarioStep scenarioStep, int index, RunScenario runScenario, RunResult runResult) { + this.index = index; + this.scenarioStep = scenarioStep; + this.runScenario = runScenario; + this.runResult = runResult; + } + + /** + * Return the index of this basicFlow + * + * @return index + */ + public int getIndex() { + return index; + } + + public String getId() { + String id = scenarioStep.getType() + " "; + id += switch (scenarioStep.getType()) { + case STARTEVENT -> scenarioStep.getProcessId() + "-" + scenarioStep.getTaskId()+"-"+Thread.currentThread().getName(); + case SERVICETASK -> scenarioStep.getTopic()+"-"+Thread.currentThread().getName(); + default -> ""; + }; + return id + "#" + getIndex(); + } + + public RunScenario getRunScenario() { + return runScenario; + } + + /** + * The flow return the runResult given at the execution + * + * @return result + */ + public RunResult getRunResult() { + return runResult; + } + + /** + * The flow execute a step - return it + * + * @return scenarioStep + */ + public ScenarioStep getScenarioStep() { + return scenarioStep; + } + + /** + * Start the execution. Attention, only errors must be reported in the result + */ + public abstract void execute(); + + public abstract STATUS getStatus(); + + public abstract int getCurrentNumberOfThreads(); + + /** + * The flow must stop now + */ + public abstract void pleaseStop(); + + public enum STATUS {RUNNING, STOPPING, STOPPED} + +} diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java new file mode 100644 index 0000000..481df2c --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java @@ -0,0 +1,275 @@ +/* ******************************************************************** */ +/* */ +/* RunScenarioFlowServiceTask */ +/* */ +/* Execute a service task */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import io.camunda.zeebe.client.api.command.CompleteJobCommandStep1; +import io.camunda.zeebe.client.api.command.FinalCommandStep; +import io.camunda.zeebe.client.api.response.ActivatedJob; +import io.camunda.zeebe.client.api.worker.JobClient; +import io.camunda.zeebe.client.api.worker.JobHandler; +import io.camunda.zeebe.client.api.worker.JobWorker; +import io.camunda.zeebe.spring.client.jobhandling.CommandWrapper; +import org.camunda.automator.bpmnengine.BpmnEngine; +import org.camunda.automator.bpmnengine.camunda7.BpmnEngineCamunda7; +import org.camunda.automator.bpmnengine.camunda8.BenchmarkCompleteJobExceptionHandlingStrategy; +import org.camunda.automator.bpmnengine.camunda8.BpmnEngineCamunda8; +import org.camunda.automator.bpmnengine.camunda8.refactoring.RefactoredCommandWrapper; +import org.camunda.automator.definition.ScenarioStep; +import org.camunda.automator.engine.RunResult; +import org.camunda.automator.engine.RunScenario; +import org.camunda.bpm.client.task.ExternalTaskHandler; +import org.camunda.bpm.client.task.ExternalTaskService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.TaskScheduler; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Semaphore; + +public class RunScenarioFlowServiceTask extends RunScenarioFlowBasic { + private final TaskScheduler scheduler; + + private static final TrackActiveWorker trackActiveWorkers = new TrackActiveWorker(); + private static final TrackActiveWorker trackAsynchronousWorkers = new TrackActiveWorker(); + + Logger logger = LoggerFactory.getLogger(RunScenarioFlowServiceTask.class); + private BpmnEngine.RegisteredTask registeredTask; + private boolean stopping; + @Autowired + private BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy; + + private Semaphore semaphore; + + public RunScenarioFlowServiceTask(TaskScheduler scheduler, + ScenarioStep scenarioStep, + int index, + RunScenario runScenario, + RunResult runResult) { + super(scenarioStep, index, runScenario, runResult); + this.scheduler = scheduler; + this.semaphore = new Semaphore(runScenario.getBpmnEngine().getWorkerExecutionThreads()); + + } + + @Override + public void execute() { + registerWorker(); + } + + @Override + public void pleaseStop() { + logger.info("Ask Stopping [" + getId() + "]"); + stopping = true; + if (registeredTask==null || (registeredTask.isNull()) ) + return; + if (registeredTask.isClosed()) { + return; + } +registeredTask.close(); + + Duration durationSleep = getScenarioStep().getWaitingTimeDuration(Duration.ZERO); + long expectedEndTime = System.currentTimeMillis() + durationSleep.toMillis(); + while (!registeredTask.isClosed() && System.currentTimeMillis() < expectedEndTime) { + registeredTask.close(); + try { + Thread.sleep(500); + } catch (Exception e) { + // do nothing + } + } + logger.info("[" + getId() + "] " + (registeredTask.isClosed() ? "stopped" : "Fail to stop")); + + registeredTask = null; + } + + @Override + public STATUS getStatus() { + if (registeredTask == null) + return STATUS.STOPPED; + if (stopping) + return STATUS.STOPPING; + return STATUS.RUNNING; + } + + @Override + public int getCurrentNumberOfThreads() { + return trackActiveWorkers.getCounter() + trackAsynchronousWorkers.getCounter(); + } + + /** + * Register the worker + */ + + private void registerWorker() { + BpmnEngine bpmnEngine = getRunScenario().getBpmnEngine(); + + Duration durationSleep = getScenarioStep().getWaitingTimeDuration(Duration.ZERO); + durationSleep = durationSleep.plusSeconds(10); + + registeredTask = bpmnEngine.registerServiceTask(getId(), // workerId + getScenarioStep().getTopic(), // topic + durationSleep, // lock time + new SimpleDelayHandler(this), new FixedBackoffSupplier(getScenarioStep().getFixedBackOffDelay())); + /* + // calculate the lock duration: this is * + ZeebeClient zeebeClient = ((BpmnEngineCamunda8) getRunScenario().getBpmnEngine()).getZeebeClient(); + + JobWorkerBuilderStep1.JobWorkerBuilderStep3 step3 = zeebeClient.newWorker() + .jobType(getScenarioStep().getTopic()) + .handler(new SimpleDelayC8Handler(this)) + .timeout(durationSleep) + .name(getId()); + + if (getScenarioStep().getFixedBackOffDelay() > 0) { + step3.backoffSupplier(new FixedBackoffSupplier(getScenarioStep().getFixedBackOffDelay())); + } + jobWorker = step3.open(); + */ + + } + + private static class TrackActiveWorker { + public int counter = 0; + + public synchronized void movement(int movement) { + counter += movement; + } + + public int getCounter() { + return counter; + } + } + + /** + * C7, C8 Handler + */ + public class SimpleDelayHandler implements ExternalTaskHandler, JobHandler { + private final RunScenarioFlowServiceTask flowServiceTask; + private final Duration durationSleep; + + public SimpleDelayHandler(RunScenarioFlowServiceTask flowServiceTask) { + this.flowServiceTask = flowServiceTask; + durationSleep = flowServiceTask.getScenarioStep().getWaitingTimeDuration(Duration.ZERO); + } + + /* C7 Management */ + @Override + public void execute(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService) { + switch (getScenarioStep().getModeExecution()) { + case WAIT -> manageWaitExecution(externalTask, externalTaskService, null, null, durationSleep.toMillis()); + case ASYNCHRONOUS -> manageAsynchronousExecution(externalTask, externalTaskService, null, null); + case ASYNCHRONOUSLIMITED -> manageAsynchronousLimitedExecution(externalTask, externalTaskService,null,null); + } + } + + /* C8 management */ + @Override + public void handle(JobClient jobClient, ActivatedJob activatedJob) throws Exception { + switch (getScenarioStep().getModeExecution()) { + case WAIT -> manageWaitExecution(null, null, jobClient, activatedJob, durationSleep.toMillis()); + case ASYNCHRONOUS -> manageAsynchronousExecution(null, null, jobClient, activatedJob); + case ASYNCHRONOUSLIMITED -> manageAsynchronousLimitedExecution(null, null,jobClient, activatedJob); + } + } + + private void manageWaitExecution(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService, + JobClient jobClient, ActivatedJob activatedJob, + long waitTimeInMs) { + long begin = System.currentTimeMillis(); + try { + if (getRunScenario().getRunParameters().deepTracking) + trackActiveWorkers.movement(1); + + if (waitTimeInMs > 0) + Thread.sleep(waitTimeInMs); + + Map variables = new HashMap<>(); + + /* C7 */ + if (externalTask!=null) { + externalTaskService.complete(externalTask, variables); + } + /* C8 */ + if (jobClient!=null) { + CompleteJobCommandStep1 completeCommand = jobClient.newCompleteCommand(activatedJob.getKey()); + CommandWrapper command = new RefactoredCommandWrapper((FinalCommandStep) completeCommand, + activatedJob.getDeadline(), activatedJob.toString(), exceptionHandlingStrategy); + + command.executeAsync(); + } + + flowServiceTask.runResult.registerAddStepExecution(); + + } catch (Exception e) { + logger.error( + "Error task[" + flowServiceTask.getId() + " " + externalTask.getBusinessKey() + " : " + e.getMessage()); + + flowServiceTask.runResult.registerAddErrorStepExecution(); + + } + long end = System.currentTimeMillis(); + + if (getRunScenario().getRunParameters().deepTracking) + trackActiveWorkers.movement(-1); + + if (getRunScenario().getRunParameters().isLevelMonitoring()) { + logger.info( + "Executed task[" + getId() + "] in " + (end - begin) + " ms" + " Sleep [" + durationSleep.getSeconds() + + " s]"); + } + } + + private void manageAsynchronousExecution(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService, + JobClient jobClient, ActivatedJob activatedJob) { + if (getRunScenario().getRunParameters().deepTracking) + trackAsynchronousWorkers.movement(1); + flowServiceTask.scheduler.schedule(new Runnable() { + @Override + public void run() { + manageWaitExecution(externalTask, externalTaskService, jobClient, activatedJob,0); + if (getRunScenario().getRunParameters().deepTracking) + trackAsynchronousWorkers.movement(-1); + } + }, Instant.now().plusMillis(durationSleep.toMillis())); + } + + private void manageAsynchronousLimitedExecution(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService, + JobClient jobClient, ActivatedJob activatedJob) { + // we register + try { + flowServiceTask.semaphore.acquire(); + if (getRunScenario().getRunParameters().isLevelMonitoring()) { + logger.info("task[{}] Semaphore acquire", getId()); + } + } catch (Exception e) { + return; + } + // Ok, now we can run that asynchronous + flowServiceTask.scheduler.schedule(new Runnable() { + @Override + public void run() { + manageWaitExecution(externalTask, externalTaskService, jobClient, activatedJob, 0); + flowServiceTask.semaphore.release(); + if (getRunScenario().getRunParameters().isLevelMonitoring()) { + logger.info("task[{}] Semaphore release", getId()); + } + } + }, Instant.now().plusMillis(durationSleep.toMillis())); + + } + + } + +} \ No newline at end of file diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java new file mode 100644 index 0000000..360583f --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java @@ -0,0 +1,190 @@ +/* ******************************************************************** */ +/* */ +/* RunScenarioFlowStartEvent */ +/* */ +/* Create process instance in Flow */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import org.camunda.automator.definition.ScenarioStep; +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunResult; +import org.camunda.automator.engine.RunScenario; +import org.camunda.automator.engine.RunZeebeOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.TaskScheduler; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class RunScenarioFlowStartEvent extends RunScenarioFlowBasic { + private final TaskScheduler scheduler; + Logger logger = LoggerFactory.getLogger(RunScenarioFlowStartEvent.class); + private boolean stopping; + private boolean isRunning; + + public RunScenarioFlowStartEvent(TaskScheduler scheduler, + ScenarioStep scenarioStep, + int index, + RunScenario runScenario, + RunResult runResult) { + super(scenarioStep, index, runScenario, runResult); + this.scheduler = scheduler; + } + + @Override + public void execute() { + stopping = false; + isRunning = true; + Duration duration = Duration.parse(getScenarioStep().getFrequency()); + + StartEventRunnable startEventRunnable = new StartEventRunnable(scheduler, getScenarioStep(), runResult, + getRunScenario(), this); + scheduler.schedule(startEventRunnable, Instant.now()); + } + + @Override + public void pleaseStop() { + this.stopping = true; + } + + public RunScenarioFlowBasic.STATUS getStatus() { + if (!isRunning) + return RunScenarioFlowBasic.STATUS.STOPPED; + if (stopping) { + return RunScenarioFlowBasic.STATUS.STOPPING; + } + return RunScenarioFlowBasic.STATUS.RUNNING; + } + + @Override + public int getCurrentNumberOfThreads() { + return 0; + } + + @Override + public RunResult getRunResult() { + return runResult; + } + + public enum STATUS {RUNNING, STOPPING, STOPPED} + + private int stepNumber=0; + /** + * StartEventRunnable + */ + class StartEventRunnable implements Runnable { + + private final TaskScheduler scheduler; + private final ScenarioStep scenarioStep; + private final RunResult runResult; + private final RunScenario runScenario; + private final RunScenarioFlowStartEvent flowStartEvent; + + private int nbOverloaded = 0; + private int totalCreation = 0; + private int totalCreationGoal = 0; + private int totalFailed = 0; + + public StartEventRunnable(TaskScheduler scheduler, + ScenarioStep scenarioStep, + RunResult runResult, + RunScenario runScenario, + RunScenarioFlowStartEvent flowStartEvent) { + this.scheduler = scheduler; + this.scenarioStep = scenarioStep; + this.runResult = runResult; + this.runScenario = runScenario; + this.flowStartEvent = flowStartEvent; + } + + @Override + public void run() { + stepNumber++; + if (flowStartEvent.stopping) { + if (runScenario.getRunParameters().isLevelMonitoring()) { + logger.info("Stop now [" + getId() + "]"); + if (nbOverloaded > 0) + runResult.addError(scenarioStep, + "Overloaded " + nbOverloaded + " TotalCreation " + totalCreation + " Goal " + totalCreationGoal + + " Process[" + scenarioStep.getProcessId() + "] Can't create PI at the required frequency"); + if (totalFailed > 0) + runResult.addError(scenarioStep, + "Failed " + totalFailed + " Process[" + scenarioStep.getProcessId() + "] Can't create PI "); + + } + // notify my parent that I stop now + flowStartEvent.isRunning = false; + return; + } + long begin = System.currentTimeMillis(); + int nbCreation = 0; + int nbFailed = 0; + boolean isOverloadSection = false; + Duration duration = Duration.parse(scenarioStep.getFrequency()); + + List listProcessInstances = new ArrayList<>(); + totalCreationGoal += scenarioStep.getNumberOfExecutions(); + boolean alreadyLoggedError = false; + for (int i = 0; i < scenarioStep.getNumberOfExecutions(); i++) { + + // operation + try { + String processInstance = runScenario.getBpmnEngine() + .createProcessInstance(scenarioStep.getProcessId(), scenarioStep.getTaskId(), // activityId + RunZeebeOperation.getVariablesStep(runScenario, scenarioStep)); + if (listProcessInstances.size() < 21) + listProcessInstances.add(processInstance); + nbCreation++; + totalCreation++; + runResult.registerAddProcessInstance(scenarioStep.getProcessId(), true); + } catch (AutomatorException e) { + if (!alreadyLoggedError) + runResult.addError(scenarioStep, "Step #"+stepNumber+"-"+getId()+" Error at creation: [" + e.getMessage() + "]"); + alreadyLoggedError = true; + nbFailed++; + totalFailed++; + runResult.registerAddProcessInstance(scenarioStep.getProcessId(), false); + } + + // do we have to stop the execution? + long currentTimeMillis = System.currentTimeMillis(); + Duration durationCurrent = duration.minusMillis(currentTimeMillis - begin); + if (durationCurrent.isNegative()) { + // take too long to create the required process instance, so stop now. + logger.info("Step #"+stepNumber+"-"+getId()+" Take too long to created ProcessInstances: created {} when expected {}", nbCreation, + scenarioStep.getNumberOfExecutions()); + isOverloadSection = true; + break; + } + + } // end of loop getNumberOfExecutions() + + + long end = System.currentTimeMillis(); + duration = duration.minusMillis(end - begin); + if (duration.isNegative()) { + duration = Duration.ZERO; + isOverloadSection = true; + nbOverloaded++; + } + + if (runScenario.getRunParameters().isLevelMonitoring()) { + logger.info("Step #"+stepNumber+"-" + getId() // id + + "] Create (real/scenario)[" + nbCreation + "/" + scenarioStep.getNumberOfExecutions() // creation/target + + "] Failed[" + nbFailed // failed + + "] in " + (end - begin) + " ms " // time of operation + + (isOverloadSection ? "OVERLOAD" : "") + " Sleep[" + duration.getSeconds() + " s] listPI(max20): " + + listProcessInstances.stream().collect(Collectors.joining(","))); + } + scheduler.schedule(this, Instant.now().plusMillis(duration.toMillis())); + + } + } + + +} diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java new file mode 100644 index 0000000..ddbe205 --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java @@ -0,0 +1,343 @@ +/* ******************************************************************** */ +/* */ +/* RunScenarioFlows */ +/* */ +/* Execute all flows in a scenario */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import org.camunda.automator.bpmnengine.BpmnEngine; +import org.camunda.automator.definition.ScenarioStep; +import org.camunda.automator.engine.RunResult; +import org.camunda.automator.engine.RunScenario; +import org.camunda.automator.services.ServiceAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RunScenarioFlows { + private final ServiceAccess serviceAccess; + private final RunScenario runScenario; + Logger logger = LoggerFactory.getLogger(RunScenarioFlows.class); + private final Map previousValueMap = new HashMap<>(); + + public RunScenarioFlows(ServiceAccess serviceAccess, RunScenario runScenario) { + this.serviceAccess = serviceAccess; + this.runScenario = runScenario; + } + + /** + * Execute the scenario flow + * + * @param runResult result to populate + */ + public void execute(RunResult runResult) { + // Create one executor per flow + RunScenarioWarmingUp runScenarioWarmingUp = new RunScenarioWarmingUp(serviceAccess, runScenario); + Map recordCreationPIMap = new HashMap<>(); + RunObjectives runObjectives = new RunObjectives(runScenario.getScenario().getFlowControl().getObjectives(), + runScenario.getBpmnEngine(), recordCreationPIMap); + + logger.info("ScenarioFlow: ------ WarmingUp"); + runScenarioWarmingUp.warmingUp(); + + Date startTestDate = new Date(); + runObjectives.setStartDate(startTestDate); + + logger.info("ScenarioFlow: ------ Start"); + List listFlows = startExecution(); + + waitEndExecution(runObjectives, startTestDate, listFlows); + + Date endTestDate = new Date(); + runObjectives.setEndDate(endTestDate); + logger.info("ScenarioFlow: ------ Stop"); + + stopExecution(listFlows); + + logger.info("ScenarioFlow: ------ CollectData"); + collectInformation(listFlows, runResult, recordCreationPIMap); + + // Check with Objective now + logger.info("ScenarioFlow: ------ CheckObjectives"); + if (runScenario.getScenario().getFlowControl() != null + && runScenario.getScenario().getFlowControl().getObjectives() != null) { + checkObjectives(runObjectives, startTestDate, endTestDate, runResult); + } + logger.info("ScenarioFlow: ------ TheEnd"); + } + + /** + * Start execution + * + * @return list of Flow started + */ + private List startExecution() { + List listFlows = new ArrayList<>(); + for (ScenarioStep scenarioStep : runScenario.getScenario().getFlows()) { + if (ScenarioStep.Step.STARTEVENT.equals(scenarioStep.getType())) { + if (!runScenario.getRunParameters().creation) { + logger.info("According configuration, STARTEVENT[" + scenarioStep.getProcessId() + "] is fully disabled"); + } else + for (int i = 0; i < scenarioStep.getNbWorkers(); i++) { + RunScenarioFlowStartEvent runStartEvent = new RunScenarioFlowStartEvent( + serviceAccess.getTaskScheduler(scenarioStep.getProcessId() + "-" + i), scenarioStep, i, runScenario, + new RunResult(runScenario)); + runStartEvent.execute(); + listFlows.add(runStartEvent); + } + } + if (ScenarioStep.Step.SERVICETASK.equals(scenarioStep.getType())) { + if (!runScenario.getRunParameters().servicetask) { + logger.info("According configuration, SERVICETASK[{}] is fully disabled", scenarioStep.getTopic()); + } else if (runScenario.getRunParameters().blockExecutionServiceTask(scenarioStep.getTopic())) { + logger.info("According configuration, SERVICETASK[{}] is disabled (only acceptable {})", + scenarioStep.getTopic(), runScenario.getRunParameters().filterServiceTask); + } else { + RunScenarioFlowServiceTask runStartEvent = new RunScenarioFlowServiceTask( + serviceAccess.getTaskScheduler("serviceTask"), scenarioStep, 0, runScenario, new RunResult(runScenario)); + runStartEvent.execute(); + listFlows.add(runStartEvent); + } + } + } + return listFlows; + } + + /** + * Wait end of execution. according to the time in the scenario, wait this time + * + * @param runObjectives checkObjectif: we may have a Flow Objectives + * @param listFlows list of flows to monitor the execution + */ + private void waitEndExecution(RunObjectives runObjectives, Date startTestDate, List listFlows) { + // Then wait the delay, and kill everything after + Duration durationExecution = runScenario.getScenario().getFlowControl().getDuration(); + Duration durationWarmingUp = Duration.ZERO; + // if this server didn't do the warmingUp, then other server did it: we have to keep this time into account + if (!runScenario.getRunParameters().warmingUp) + { + // is the scenario has a warming up defined? + if (runScenario.getScenario().getWarmingUp()!=null) + durationWarmingUp = runScenario.getScenario().getWarmingUp().getDuration(); + } + + + long endTimeExpected = + startTestDate.getTime() + durationExecution.getSeconds() * 1000 + durationWarmingUp.getSeconds() * 1000; + + logger.info("Start: FixedWarmingUp {} s ExecutionDuration {} s (total {} s)", + durationWarmingUp.getSeconds(), durationExecution.getSeconds(), + durationWarmingUp.getSeconds() + durationExecution.getSeconds()); + + while (System.currentTimeMillis() < endTimeExpected) { + long currentTime = System.currentTimeMillis(); + long sleepTime = Math.min(30 * 1000, endTimeExpected - currentTime); + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + } + int advancement = (int) (100.0 * (currentTime - startTestDate.getTime()) / (endTimeExpected + - startTestDate.getTime())); + runObjectives.heartBeat(); + logRealTime(listFlows, endTimeExpected - System.currentTimeMillis(), advancement); + } + } + + /** + * Stop the execution + * + * @param listFlows list of flows to stop + */ + private void stopExecution(List listFlows) { + logger.info("End - wait end FlowBasic"); + // now, stop all executions + for (RunScenarioFlowBasic flowBasic : listFlows) { + flowBasic.pleaseStop(); + } + // wait the end of all executions + long numberOfActives = listFlows.size(); + int count = 0; + while (numberOfActives > 0 && count < 100) { + count++; + numberOfActives = listFlows.stream() + .filter(t -> !t.getStatus().equals(RunScenarioFlowBasic.STATUS.STOPPED)) + .count(); + if (numberOfActives > 0) + try { + Thread.sleep(2000); + } catch (Exception e) { + numberOfActives = 0; + } + } + } + + /** + * Collect multiple information + * + * @param listFlows list of flow + * @param runResult runResult to populate + * @param recordCreationPIMap statistics + */ + private void collectInformation(List listFlows, + RunResult runResult, + Map recordCreationPIMap) { + // Collect information + logger.info("CollectData : listFlows[{}]", listFlows.size()); + for (RunScenarioFlowBasic flowBasic : listFlows) { + RunResult runResultFlow = flowBasic.getRunResult(); + runResult.add(runResultFlow); + if (flowBasic instanceof RunScenarioFlowStartEvent) { + String processId = flowBasic.getScenarioStep().getProcessId(); + RunResult.RecordCreationPI recordFlow = runResultFlow.getRecordCreationPI().get(processId); + RunResult.RecordCreationPI recordCreationPI = recordCreationPIMap.getOrDefault(processId, + new RunResult.RecordCreationPI(processId)); + + recordCreationPI.add(recordFlow); + recordCreationPIMap.put(processId, recordCreationPI); + logger.info("CollectData : StartEvent, processId[{}] PICreated[{}] PIFailed[{}]", processId, + recordFlow.nbCreated, recordFlow.nbFailed); + } + } + } + + /** + * Check the objective of the scenario + * + * @param startTestDate date when the test start + * @param endTestDate date when the test end + * @param runResult result to populate + */ + private void checkObjectives(RunObjectives runObjectives, + Date startTestDate, + Date endTestDate, + RunResult runResult) { + + // Objectives ask Operate, which get the result with a delay. So, wait 1 mn + logger.info("CollectingData..."); + try { + Thread.sleep(1000 * 60); + } catch (InterruptedException e) { + // do nothing + } + + List listCheckResult = runObjectives.check(); + for (RunObjectives.ObjectiveResult checkResult : listCheckResult) { + if (checkResult.success) { + logger.info("Objective: SUCCESS type {} label [{}} processId[{}] reach {} (objective is {} ) analysis [{}}", + checkResult.objective.type, checkResult.objective.label, checkResult.objective.processId, + checkResult.recordedSuccessValue, checkResult.objective.value, checkResult.analysis); + // do not need to log the error, already done + + } else { + runResult.addError(null, + "Objective: FAIL " + checkResult.objective.label + " type " + checkResult.objective.type + " processId [" + + checkResult.objective.processId + "] " + checkResult.analysis); + } + } + } + + /** + * Log to see the advancement + * + * @param listFlows list flows running + * @param percentAdvancement percentAdvancement of the test, according the timeframe + */ + private void logRealTime(List listFlows, long timeToFinishInMs, int percentAdvancement) { + logger.info("------------ Log advancement at {} ----- {} %, end in {} s", new Date(), percentAdvancement, + timeToFinishInMs / 1000); + + for (RunScenarioFlowBasic flowBasic : listFlows) { + + RunResult runResultFlow = flowBasic.getRunResult(); + int currentNumberOfThreads = flowBasic.getCurrentNumberOfThreads(); + // logs only flow with a result or currently active + if (runResultFlow.getRecordCreationPIAllProcesses() + runResultFlow.getNumberOfSteps() + + runResultFlow.getNumberOfErrorSteps() == 0 && currentNumberOfThreads == 0) + continue; + long previousValue = previousValueMap.getOrDefault(flowBasic.getId(), 0L); + + ScenarioStep scenarioStep = flowBasic.getScenarioStep(); + String key = "[" + flowBasic.getId() + "] " + flowBasic.getStatus().toString() + " " + " currentNbThreads[" + + currentNumberOfThreads + "] "; + key += switch (scenarioStep.getType()) { + case STARTEVENT -> "PI[" + runResultFlow.getRecordCreationPI() + "] delta[" + ( + runResultFlow.getRecordCreationPI().get(flowBasic.getScenarioStep().getProcessId()).nbCreated + - previousValue) + "]"; + case SERVICETASK -> "StepsExecuted[" + runResultFlow.getNumberOfSteps() + "] delta [" + ( + runResultFlow.getNumberOfSteps() - previousValue) + "] StepsErrors[" + runResultFlow.getNumberOfErrorSteps() + + "]"; + default -> "]"; + }; + logger.info(key); + switch (scenarioStep.getType()) { + case STARTEVENT -> { + previousValueMap.put(flowBasic.getId(), + (long) runResultFlow.getRecordCreationPI().get(flowBasic.getScenarioStep().getProcessId()).nbCreated); + } + case SERVICETASK -> { + previousValueMap.put(flowBasic.getId(), (long) runResultFlow.getNumberOfSteps()); + } + default -> { + } + } + + } + int nbThreadsServiceTask = 0; + int nbThreadsAutomator = 0; + int nbThreadsTimeWaiting = 0; + int nbThreadsWaiting = 0; + int nbThreadsTimeRunnable = 0; + for (Map.Entry entry : Thread.getAllStackTraces().entrySet()) { + boolean isZeebe = false; + boolean isServiceTask = false; + boolean isAutomator = false; + for (StackTraceElement ste : entry.getValue()) { + if (ste.getClassName().contains("io.camunda")) + isZeebe = true; + else if (ste.getClassName().contains(RunScenarioFlowServiceTask.SimpleDelayHandler.class.getName())) + isServiceTask = true; + else if (ste.getClassName().contains(".automator.")) + isAutomator = true; + + // org.camunda.automator.engine.flow.RunScenarioFlowServiceTask$SimpleDelayCompletionHandler + } + if (!isZeebe && !isServiceTask && !isAutomator) + continue; + + if (isServiceTask) + nbThreadsServiceTask++; + else if (isAutomator) + nbThreadsAutomator++; + else + // TIME_WAITING: typical for the FlowServiceTask with a sleep + if (entry.getKey().getState().equals(Thread.State.TIMED_WAITING)) { + // is the thread is running the service task (with a Thread.sleep? + nbThreadsTimeWaiting++; + } else if (entry.getKey().getState().equals(Thread.State.WAITING)) { + nbThreadsTimeWaiting++; + } else if (entry.getKey().getState().equals(Thread.State.RUNNABLE)) { + nbThreadsTimeRunnable++; + } else { + logger.info(" {} {}", entry.getKey(), entry.getKey().getState()); + for (StackTraceElement ste : entry.getValue()) { + logger.info("\tat {}", ste); + } + + } + } + BpmnEngine bpmnEngine = runScenario.getBpmnEngine(); + int workerExecutionThreads = bpmnEngine.getWorkerExecutionThreads(); + if (nbThreadsServiceTask + nbThreadsTimeWaiting + nbThreadsWaiting + nbThreadsTimeRunnable + nbThreadsAutomator > 0) + logger.info("Threads: ServiceTaskExecution (ThreadService/maxJobActive) [{}/{}] {} % Automator[{}] TIME_WAITING[{}] WAITING[{}] RUNNABLE[{}] ", + nbThreadsServiceTask, workerExecutionThreads, + workerExecutionThreads == 0 ? 0 : (int) (100.0 * nbThreadsServiceTask / workerExecutionThreads), + nbThreadsAutomator, nbThreadsTimeWaiting, nbThreadsWaiting, nbThreadsTimeRunnable); + } +} diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java new file mode 100644 index 0000000..c6dc618 --- /dev/null +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java @@ -0,0 +1,244 @@ +/* ******************************************************************** */ +/* */ +/* RunScenarioWarmingUp */ +/* */ +/* Manage the warming up of a scenario */ +/* ******************************************************************** */ +package org.camunda.automator.engine.flow; + +import org.camunda.automator.definition.ScenarioStep; +import org.camunda.automator.definition.ScenarioWarmingUp; +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunResult; +import org.camunda.automator.engine.RunScenario; +import org.camunda.automator.engine.RunZeebeOperation; +import org.camunda.automator.services.ServiceAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.TaskScheduler; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.stream.Collectors; + +public class RunScenarioWarmingUp { + private final ServiceAccess serviceAccess; + private final RunScenario runScenario; + Logger logger = LoggerFactory.getLogger(RunScenarioWarmingUp.class); + + RunScenarioWarmingUp(ServiceAccess serviceAccess, RunScenario runScenario) { + this.serviceAccess = serviceAccess; + this.runScenario = runScenario; + } + + /** + * warmingUp + * Do it! + */ + public void warmingUp() { + ScenarioWarmingUp warmingUp = runScenario.getScenario().getWarmingUp(); + if (warmingUp == null) { + logger.info("WarmingUp not present in scenario"); + return; + } + if (!runScenario.getRunParameters().warmingUp) { + logger.info("WarmingUp present, but not allowed to start"); + return; + } + long beginTime = System.currentTimeMillis(); + + // If no duration is set, then 10 Mn max + long endWarmingUp = + beginTime + (warmingUp.getDuration().toMillis() > 0 ? warmingUp.getDuration().toMillis() : 1000 * 60 * 10); + + logger.info("WarmingUp: Start ---- {} operations ", warmingUp.getOperations().size()); + + List listWarmingUpServiceTask = new ArrayList<>(); + List listWarmingUpStartEvent = new ArrayList<>(); + for (ScenarioStep scenarioStep : warmingUp.getOperations()) { + switch (scenarioStep.getType()) { + case STARTEVENT -> { + logger.info("WarmingUp: StartEvent Generate {} Frequency [{}] EndWarmingUp [{}]", + scenarioStep.getNumberOfExecutions(), scenarioStep.getFrequency(), scenarioStep.getEndWarmingUp()); + + StartEventWarmingUpRunnable startEventWarmingUpRunnable = new StartEventWarmingUpRunnable( + serviceAccess.getTaskScheduler("warmingUp"), scenarioStep, runScenario); + listWarmingUpStartEvent.add(startEventWarmingUpRunnable); + startEventWarmingUpRunnable.run(); + } + case SERVICETASK -> { + logger.info("WarmingUp: Start Service Task topic[{}]", scenarioStep.getTopic()); + RunScenarioFlowServiceTask task = new RunScenarioFlowServiceTask(serviceAccess.getTaskScheduler("serviceTask"), + scenarioStep, 0, runScenario, new RunResult(runScenario)); + task.execute(); + listWarmingUpServiceTask.add(task); + } + default -> { + logger.info("WarmingUp: Unknown [{}]", scenarioStep.getType()); + } + } + } + + // check if we reach the end of the warming up + boolean warmingUpIsFinish = false; + while (!warmingUpIsFinish) { + long currentTime = System.currentTimeMillis(); + String analysis = " max in " + (endWarmingUp - currentTime) / 1000 + " s, "; + if (currentTime >= endWarmingUp) { + analysis += "Over maximum duration,"; + warmingUpIsFinish = true; + } + boolean allIsFinished = true; + for (StartEventWarmingUpRunnable startRunnable : listWarmingUpStartEvent) { + analysis += "warmingUp[" + startRunnable.scenarioStep.getTaskId() + "] instanceCreated[" + + startRunnable.nbInstancesCreated + "]"; + if (startRunnable.warmingUpFinished) { + analysis += " FINISH!:" + startRunnable.warmingUpFinishedAnalysis; + } else { + allIsFinished = false; + } + + } + if (allIsFinished) { + warmingUpIsFinish = true; + } + logger.info("WarmingUpFinished? {} analysis: {}", warmingUpIsFinish, analysis); + if (!warmingUpIsFinish) { + try { + Thread.sleep(1000L * 15); + } catch (InterruptedException e) { + // do not care + } + } + } + + // stop everything + for (StartEventWarmingUpRunnable startRunnable : listWarmingUpStartEvent) { + startRunnable.pleaseStop(true); + } + + // stop all tasks now + for (RunScenarioFlowServiceTask task : listWarmingUpServiceTask) { + task.pleaseStop(); + } + // now warmup is finished + logger.info("WarmingUp: Complete ----"); + + } + + /** + * StartEventRunnable + */ + class StartEventWarmingUpRunnable implements Runnable { + + private final TaskScheduler scheduler; + private final ScenarioStep scenarioStep; + private final RunScenario runScenario; + public boolean stop = false; + public boolean warmingUpFinished = false; + public String warmingUpFinishedAnalysis = ""; + public int nbInstancesCreated = 0; + private int nbOverloaded = 0; + + public StartEventWarmingUpRunnable(TaskScheduler scheduler, ScenarioStep scenarioStep, RunScenario runScenario) { + this.scheduler = scheduler; + this.scenarioStep = scenarioStep; + this.runScenario = runScenario; + } + + public void pleaseStop(boolean stop) { + this.stop = stop; + } + + @Override + public void run() { + if (stop) { + return; + } + // check if the condition is reach + CheckFunctionResult checkFunctionResult = null; + if (scenarioStep.getEndWarmingUp() != null) { + checkFunctionResult = endCheckFunction(scenarioStep.getEndWarmingUp()); + if (checkFunctionResult.goalReach) { + warmingUpFinishedAnalysis += "GoalReach[" + checkFunctionResult.analysis + "]"; + warmingUpFinished = true; + return; + } + } + // continue to generate PI + long begin = System.currentTimeMillis(); + List listProcessInstance = new ArrayList<>(); + try { + for (int i = 0; i < scenarioStep.getNumberOfExecutions(); i++) { + String processInstance = runScenario.getBpmnEngine() + .createProcessInstance(scenarioStep.getProcessId(), scenarioStep.getTaskId(), // activityId + RunZeebeOperation.getVariablesStep(runScenario, scenarioStep)); + nbInstancesCreated++; + if (listProcessInstance.size() < 21) + listProcessInstance.add(processInstance); + } + } catch (AutomatorException e) { + logger.error("Error at creation: [{}]", e.getMessage()); + } + long end = System.currentTimeMillis(); + // one step generation? + if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { + if (runScenario.getRunParameters().isLevelMonitoring()) { + logger.info("WarmingUp:StartEvent Create[{}] in {} " + " ms" + " (oneShoot) listPI(max20): ", + scenarioStep.getNumberOfExecutions(), (end - begin), + listProcessInstance.stream().collect(Collectors.joining(","))); + } + warmingUpFinishedAnalysis += "GoalOneShoot"; + warmingUpFinished = true; + return; + } + + Duration duration = Duration.parse(scenarioStep.getFrequency()); + duration = duration.minusMillis(end - begin); + if (duration.isNegative()) { + duration = Duration.ZERO; + nbOverloaded++; + } + + if (runScenario.getRunParameters().isLevelMonitoring()) { + logger.info( + "Warmingup Create[" + scenarioStep.getNumberOfExecutions() + "] in " + (end - begin) + " ms" + " Sleep [" + + duration.getSeconds() + " s]" + (checkFunctionResult == null ? + "" : + "EndWarmingUp:" + checkFunctionResult.analysis)); + } + scheduler.schedule(this, Instant.now().plusMillis(duration.toMillis())); + } + + private CheckFunctionResult endCheckFunction(String function) { + try { + int posParenthesis = function.indexOf("("); + String functionName = function.substring(0, posParenthesis); + String parameters = function.substring(posParenthesis + 1); + parameters = parameters.substring(0, parameters.length() - 1); + StringTokenizer st = new StringTokenizer(parameters, ","); + if ("UserTaskThreshold".equalsIgnoreCase(functionName)) { + String taskId = st.hasMoreTokens() ? st.nextToken() : ""; + Integer threshold = st.hasMoreTokens() ? Integer.valueOf(st.nextToken()) : 0; + long value = runScenario.getBpmnEngine().countNumberOfTasks(runScenario.getScenario().getProcessId(), taskId); + return new CheckFunctionResult(value >= threshold, + "Task[" + taskId + "] value [" + value + "] / threshold[" + threshold + "]"); + } + logger.error("Unknown function [{}]", functionName); + return new CheckFunctionResult(false, "Unknown function"); + } catch (AutomatorException e) { + logger.error("Error during warmingup {}", e.getMessage()); + return new CheckFunctionResult(false, "Exception " + e.getMessage()); + } + } + + /** + * UserTaskTask(,) + */ + public record CheckFunctionResult(boolean goalReach, String analysis) { + } + } +} diff --git a/src/main/java/org/camunda/automator/services/AutomatorStartup.java b/src/main/java/org/camunda/automator/services/AutomatorStartup.java new file mode 100644 index 0000000..59c2f0a --- /dev/null +++ b/src/main/java/org/camunda/automator/services/AutomatorStartup.java @@ -0,0 +1,178 @@ +package org.camunda.automator.services; + +import org.camunda.automator.AutomatorAPI; +import org.camunda.automator.AutomatorCLI; +import org.camunda.automator.bpmnengine.BpmnEngine; +import org.camunda.automator.configuration.ConfigurationBpmEngine; +import org.camunda.automator.configuration.ConfigurationStartup; +import org.camunda.automator.definition.Scenario; +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunParameters; +import org.camunda.automator.engine.RunResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.time.Instant; +import java.util.List; + +@Service +public class AutomatorStartup { + static Logger logger = LoggerFactory.getLogger(AutomatorStartup.class); + + @Autowired + ConfigurationStartup configurationStartup; + + @Autowired + AutomatorAPI automatorAPI; + + @Autowired + AutomatorCLI automatorCLI; + + @Autowired + ConfigurationBpmEngine engineConfiguration; + + @Autowired + ServiceAccess serviceAccess; + + @PostConstruct + public void init() { + if (AutomatorCLI.isRunningCLI) + return; + + AutomatorSetupRunnable automatorSetupRunnable = new AutomatorSetupRunnable(configurationStartup, automatorAPI, + automatorCLI, engineConfiguration); + serviceAccess.getTaskScheduler("AutomatorSetup").schedule(automatorSetupRunnable, Instant.now()); + + } + + /** + * AutomatorSetupRunnable - run in parallel + */ + class AutomatorSetupRunnable implements Runnable { + + ConfigurationStartup configurationStartup; + + AutomatorAPI automatorAPI; + + AutomatorCLI automatorCLI; + + ConfigurationBpmEngine engineConfiguration; + + public AutomatorSetupRunnable(ConfigurationStartup configurationStartup, + AutomatorAPI automatorAPI, + AutomatorCLI automatorCLI, + ConfigurationBpmEngine engineConfiguration) { + this.configurationStartup = configurationStartup; + this.automatorAPI = automatorAPI; + this.automatorCLI = automatorCLI; + this.engineConfiguration = engineConfiguration; + } + + @Override + public void run() { + + RunParameters runParameters = new RunParameters(); + runParameters.execution = true; + runParameters.logLevel = configurationStartup.getLogLevelEnum(); + runParameters.creation = configurationStartup.isPolicyExecutionCreation(); + runParameters.servicetask = configurationStartup.isPolicyExecutionServiceTask(); + runParameters.usertask = configurationStartup.isPolicyExecutionUserTask(); + runParameters.warmingUp = configurationStartup.isPolicyExecutionWarmingUp(); + runParameters.deploymentProcess = configurationStartup.isPolicyDeployProcess(); + runParameters.deepTracking = configurationStartup.deepTracking(); + List filterService = configurationStartup.getFilterService(); + if (filterService != null) { + runParameters.setFilterExecutionServiceTask(filterService); + } + + logger.info( + "AutomatorStartup parameters warmingUp[{}] creation:[{}] serviceTask:[{}] userTask:[{}] ScenarioPath[{}] logLevel[{}] waitWarmingUpServer[{} s]", + runParameters.warmingUp, runParameters.creation, runParameters.servicetask, runParameters.usertask, + configurationStartup.scenarioPath, configurationStartup.logLevel, + configurationStartup.getWarmingUpServer().toMillis() / 1000); + + try { + String currentPath = new java.io.File(".").getCanonicalPath(); + logger.info("Local Path[{}]", currentPath); + } catch (Exception e) { + logger.error("Can't access Local Path : {} ", e.getMessage()); + } + if (configurationStartup.getWarmingUpServer().getSeconds() > 30) + logger.info("Warmup: wait.... {} s", configurationStartup.getWarmingUpServer().getSeconds()); + + try { + Thread.sleep(configurationStartup.getWarmingUpServer().toMillis()); + } catch (Exception e) { + // do nothing + } + if (configurationStartup.getWarmingUpServer().getSeconds() > 30) + logger.info("Warmup: start now"); + + for (String scenarioFileName : configurationStartup.scenarioAtStartup) { + File scenarioFile = new File(configurationStartup.scenarioPath + "/" + scenarioFileName); + if (!scenarioFile.exists()) { + logger.error("Can't find [{}/{}]", configurationStartup.scenarioPath, scenarioFileName); + continue; + } + + try { + Scenario scenario = automatorAPI.loadFromFile(scenarioFile); + logger.info("Start scenario [{}]", scenario.getName()); + + // BpmnEngine: find the correct one referenced in the scenario + int countEngineIsNotReady = 0; + BpmnEngine bpmnEngine = null; + boolean pleaseTryAgain = false; + do { + countEngineIsNotReady++; + + try { + if (runParameters.isLevelMonitoring()) { + logger.info("Connect to Bpmn Engine Type{}", scenario.getServerType()); + } + bpmnEngine = automatorAPI.getBpmnEngineFromScenario(scenario, engineConfiguration); + if (! bpmnEngine.isReady()) { + bpmnEngine.connection(); + } + } catch (AutomatorException e) { + pleaseTryAgain = true; + } + if (pleaseTryAgain && countEngineIsNotReady < 10) { + logger.info( + "Scenario [{}] file[{}] No BPM ENGINE running Sleep 30s. Scenario reference serverName[{}] serverType[{}]", + scenario.getName(), scenarioFile.getName(), scenario.getServerName(), scenario.getServerType()); + try { + Thread.sleep(((long) 1000) * 30); + } catch (InterruptedException e) { + // nothing to do + } + } + } while (pleaseTryAgain && countEngineIsNotReady < 10); + + if (bpmnEngine == null) { + logger.error( + "Scenario [{}] file[{}] No BPM ENGINE running. Scenario reference serverName[{}] serverType[{}]", + scenario.getName(), scenarioFile.getName(), scenario.getServerName(), scenario.getServerType()); + continue; + } + + bpmnEngine.turnHighFlowMode(true); + logger.info("Scenario [{}] file[{}] use BpmnEngine {}", scenario.getName(), scenarioFile.getName(), + bpmnEngine.getSignature()); + RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); + logger.info("AutomatorStartup: end scenario [{}] in {} ms", scenario.getName(), + scenarioExecutionResult.getTimeExecution()); + bpmnEngine.turnHighFlowMode(false); + + } catch (AutomatorException e) { + logger.error("Error during execution [{}]: {}", scenarioFileName, e.getMessage()); + } + } + } + } + +} diff --git a/src/main/java/org/camunda/automator/services/ServiceAccess.java b/src/main/java/org/camunda/automator/services/ServiceAccess.java index b45c145..f529d35 100644 --- a/src/main/java/org/camunda/automator/services/ServiceAccess.java +++ b/src/main/java/org/camunda/automator/services/ServiceAccess.java @@ -1,14 +1,35 @@ package org.camunda.automator.services; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Service; /** * This class reference all services, and can be pass in any new object to give access to all services */ @Service +@Configuration public class ServiceAccess { + private final Logger logger = LoggerFactory.getLogger(ServiceAccess.class); @Autowired public ServiceDataOperation serviceDataOperation; + @Value("${scheduler.poolSize}") + private int schedulerPoolSize; + + /** + * Executor to run everything that is scheduled (also @Scheduled) + */ + public TaskScheduler getTaskScheduler(String schedulerName) { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(schedulerPoolSize); + scheduler.setThreadNamePrefix(schedulerName); + scheduler.initialize(); + return scheduler; + } } diff --git a/src/main/java/org/camunda/automator/services/ServiceDataOperation.java b/src/main/java/org/camunda/automator/services/ServiceDataOperation.java index cd7510a..e4ce53b 100644 --- a/src/main/java/org/camunda/automator/services/ServiceDataOperation.java +++ b/src/main/java/org/camunda/automator/services/ServiceDataOperation.java @@ -25,9 +25,7 @@ public Object execute(String value, RunScenario runScenario) throws AutomatorExc if (dataOperation.match(value)) return dataOperation.execute(value, runScenario); } - throw new AutomatorException("No operation for ["+value+"]"); + throw new AutomatorException("No operation for [" + value + "]"); } - - } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java index d0090c2..f125858 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java @@ -28,7 +28,7 @@ protected List extractArgument(String value, boolean resolveValue) throw throw new AutomatorException("Format must be function(args), received [" + value + "]"); String args = value.substring(pos); args = args.substring(1, args.length() - 1); - StringTokenizer st = new StringTokenizer(args,","); + StringTokenizer st = new StringTokenizer(args, ","); while (st.hasMoreTokens()) listResult.add(st.nextToken()); diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java new file mode 100644 index 0000000..d0e5d72 --- /dev/null +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java @@ -0,0 +1,33 @@ +package org.camunda.automator.services.dataoperation; + +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunScenario; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class DataOperationGenerateList extends DataOperation { + @Override + public boolean match(String value) { + return matchFunction(value, "generaterandomlist"); + } + + @Override + public Object execute(String value, RunScenario runScenario) throws AutomatorException { + List args = extractArgument(value, true); + List listValues = new ArrayList<>(); + try { + Integer sizeList = Integer.valueOf(args.get(0)); + for (int i = 0; i < sizeList; i++) { + listValues.add("I" + i); + } + } catch (Exception e) { + throw new AutomatorException( + "can't generate a list second parameters must be a Integer[" + args + "] : " + e.getMessage()); + + } + return listValues; + } +} diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java index 2e8607c..88c4604 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java @@ -19,13 +19,11 @@ public boolean match(String value) { @Override public Object execute(String value, RunScenario runScenario) throws AutomatorException { - File fileLoad = loadFile(value,runScenario); - if (fileLoad==null) + File fileLoad = loadFile(value, runScenario); + if (fileLoad == null) return null; - FileValue typedFileValue = Variables - .fileValue(fileLoad.getName()) - .file(fileLoad) + FileValue typedFileValue = Variables.fileValue(fileLoad.getName()).file(fileLoad) // .mimeType("text/plain") // .encoding("UTF-8") .create(); @@ -33,8 +31,7 @@ public Object execute(String value, RunScenario runScenario) throws AutomatorExc } - - private File loadFile(String value, RunScenario runScenario) throws AutomatorException { + private File loadFile(String value, RunScenario runScenario) throws AutomatorException { List args = extractArgument(value, true); File fileToLoad = null; @@ -43,7 +40,7 @@ private File loadFile(String value, RunScenario runScenario) throws AutomatorEx } String formatArgs = args.get(0); - return ScenarioTool.loadFile( formatArgs, runScenario); + return ScenarioTool.loadFile(formatArgs, runScenario); } } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java index a6a532a..94b6f00 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java @@ -22,28 +22,24 @@ public class DataOperationStringToDate extends DataOperation { public static final String FCT_DATE = "DATE"; public static final String FCT_ZONEDATETIME = "ZONEDATETIME"; public static final String FCT_LOCALDATE = "LOCALDATE"; + // visit https://docs.camunda.io/docs/components/modeler/bpmn/timer-events/#time-date + // 2019-10-01T12:00:00Z + public static String ISO_8601_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + public static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd"; @Override public boolean match(String value) { return matchFunction(value, "stringtodate"); } - // visit https://docs.camunda.io/docs/components/modeler/bpmn/timer-events/#time-date - // 2019-10-01T12:00:00Z - public static String ISO_8601_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - public static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd"; - @Override public Object execute(String value, RunScenario runScenario) throws AutomatorException { List args = extractArgument(value, true); if (args.size() != 2) { - throw new AutomatorException("Bad argument: stringtodate("+FCT_LOCALDATETIME - +"|"+FCT_DATETIME - +"|"+FCT_DATE - +"|"+FCT_ZONEDATETIME - +"|"+FCT_LOCALDATE - +", dateSt"); + throw new AutomatorException( + "Bad argument: stringtodate(" + FCT_LOCALDATETIME + "|" + FCT_DATETIME + "|" + FCT_DATE + "|" + + FCT_ZONEDATETIME + "|" + FCT_LOCALDATE + ", dateSt"); } String formatArgs = args.get(0).toUpperCase(Locale.ROOT); String valueArgs = args.get(1); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 65c14b9..098497b 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,18 +1,26 @@ automator: + + scheduler: - runAtStartup: true - server: camunda7LocalBlue - scenarioPath: D:\dev\intellij\community\process-execution-automator\src\test\ressources\simpleusertask - exam: a,b,c,d - colors: - - blue - - green - - red + startup: + scenarioPath: ./src/main/resources/loadtest + # list of scenario separate by ; + scenarioAtStartup: C7SimpleTask.json; + # DEBUG, INFO, MONITORING, MAIN, NOTHING + logLevel: MAIN + # string composed with DEPLOYPROCESS, WARMINGUP, CREATION, SERVICETASK (ex: "CREATION", "DEPLOYPROCESS|CREATION|SERVICETASK") + policyExecution: DEPLOYPROCESS|WARMINGUP|CREATION + policyExecution2: SERVICETASK|CREATION + # list of topice separate by ;. If empty, all services task in the scenario are started, else only the list here + filterService3: discovery-seedextraction-retrieve;discovery-seedextraction-crawl + filterService2: discovery-seedextraction-crawl + filterService: simple-task + deepTracking: false - logdebug: true + logdebug: false # servers connection is a list of connection separate by ; # each connection contains a name and a type. then, other parameters depends on the type @@ -20,19 +28,38 @@ automator: # ,CAMUNDA_8,ZeebeGatewayAddress,OperateUserName,OperateUserPassword,OperateUrl # ,CAMUNDA_8_SAAS,zeebeCloudRegister,zeebeCloudRegion,zeebeCloudClusterId,zeebeCloudClientId,clientSecret,OperateUserName,OperateUserPassword,OperateUrl - serversconnection: camunda7Local,CAMUNDA_7,http://localhost:8080/engine-rest;Camunda8Local,CAMUNDA_8,127.0.0.1:26500,http://localhost:8081,demo,demo,http://localhost:8082 + serversconnection: # other way to provide the list of server connection + servers: - - name: camunda7Local - type: Camunda_7 + camunda7: + name: "camunda7Local" url: "http://localhost:8080/engine-rest" - - name: Camunda8Local - type: Camunda_8 - zeebeGatewayAddress: "http://127.0.0.1:26500" - operateUserName: demo - operateUserPassword: demo - operateUrl: http://localhost:8081/ + workerMaxJobsActive: 20 + + camunda8: + name: "Camunda8Local" + zeebeGatewayAddress: "127.0.0.1:26500" + operateUserName: "demo" + operateUserPassword: "demo" + operateUrl: "http://localhost:8081" + taskListUrl: "" + workerExecutionThreads: 500 + workerMaxJobsActive2: 500 + + + + + + + zeebeCloudRegister: + zeebeCloudRegion: + clientSecret: + zeebeCloudClusterId: + zeebeCloudClientId: + -server.port: 8089 +server.port: 8380 +scheduler.poolSize: 10 \ No newline at end of file diff --git a/src/main/resources/loadtest/C7SimpleTask.bpmn b/src/main/resources/loadtest/C7SimpleTask.bpmn new file mode 100644 index 0000000..ed8ab52 --- /dev/null +++ b/src/main/resources/loadtest/C7SimpleTask.bpmn @@ -0,0 +1,74 @@ + + + + + Flow_1eqt7hk + + + + Flow_1eqt7hk + Flow_1xtx9mq + + + + Flow_1xtx9mq + Flow_0galpl7 + + + Flow_0galpl7 + + + + Flow_1htprfx + + + + Flow_1htprfx + + PT1H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/loadtest/C7SimpleTask.json b/src/main/resources/loadtest/C7SimpleTask.json new file mode 100644 index 0000000..5fc3c87 --- /dev/null +++ b/src/main/resources/loadtest/C7SimpleTask.json @@ -0,0 +1,54 @@ +{ + "name": "C7SimpleTask", + "processId": "SimpleTask", + "modeVerification": "NO", + "type": "FLOW", + "serverType": "Camunda_7", + "deployments": [ + { + "serverType": "CAMUNDA_7", + "type": "PROCESS", + "processFile": "C7SimpleTask.bpmn", + "policy": "ONLYNOTEXIST" + } + ], + "flowControl": { + "duration": "PT1M", + "objectives": [ + { + "label": "Creation SimpleTask", + "processId": "SimpleTask", + "type": "CREATED", + "value": 60000, + "comment": "1000/second " + }, + { + "label": "Ended (UserTask TheEnd) Verification", + "processId": "SimpleTask", + "type" : "USERTASK", + "taskId" : "CheckTask", + "value": 60000 + } + ] + }, + + "flows": [ + { + "type": "STARTEVENT", + "taskId": "Start_SimpleTask", + "processId": "SimpleTask", + "frequency": "PT1S", + "numberOfExecutions": "25", + "nbWorkers": "40", + "variablesOperation": { + "loopcrawl": "generaterandomlist(1000)" + } + }, + { + "type": "SERVICETASK", + "topic": "simple-task", + "waitingTime": "PT0S", + "modeExecution": "ASYNCHRONOUS" + } + ] + } \ No newline at end of file diff --git a/src/main/resources/loadtest/DiscoverySeedExtraction.bpmn b/src/main/resources/loadtest/DiscoverySeedExtraction.bpmn new file mode 100644 index 0000000..9aaea72 --- /dev/null +++ b/src/main/resources/loadtest/DiscoverySeedExtraction.bpmn @@ -0,0 +1,326 @@ + + + + + Flow_1v7okp9 + + + + + + + Flow_1v7okp9 + Flow_14poxxw + + + Flow_14poxxw + Flow_0kfwoz3 + + + + + 1000 + + + Flow_1y15m6l + + + + Flow_07t7cvu + Flow_1fmjuj7 + + + + + 10 + + + Flow_1bfp76u + + + + + Flow_1j2urpi + + + + + + + Flow_1bfp76u + Flow_1pgbqjn + + + + + + Flow_1pgbqjn + Flow_1j2urpi + + + 10 s + + + + 2 s + + + + + + Flow_1fmjuj7 + + + + + + + Flow_1y15m6l + Flow_07t7cvu + + + 10 loop + + + + 2 s + + + + + + Flow_1cvbvg8 + + + + Flow_1lqx0x6 + + PT4H + + + + Flow_1lqx0x6 + + + + Flow_1vov107 + + PT4H + + + + Flow_1vov107 + + + + + Flow_0kfwoz3 + Flow_1cvbvg8 + + + Flow_1tp79hf + + + + Flow_1tp79hf + + PT4H + + + + 20 000 /day + + + + 2 s + + + + 1k loop + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/loadtest/DiscoverySeedExtraction.json b/src/main/resources/loadtest/DiscoverySeedExtraction.json new file mode 100644 index 0000000..c960779 --- /dev/null +++ b/src/main/resources/loadtest/DiscoverySeedExtraction.json @@ -0,0 +1,122 @@ +{ + "name": "DiscoverySeedExtraction", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction", + "modeVerification": "NO", + "type": "FLOW", + "serverType": "Camunda_8", + "deployments": [ + { + "serverType": "CAMUNDA_8", + "type": "PROCESS", + "processFileSubprocess": "DiscoverySeedExtraction", + "processFileCallactivity": "DiscoverySeedExtraction-ca", + "processFile": "DiscoverySeedExtraction-ca.bpmn", + "policy": "ONLYNOTEXIST" + } + ], + "flowControl": { + "duration": "PT10M", + "objectives": [ + { + "label": "Creation SeedExtraction", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction-ca", + "type" : "CREATED", + "value" : 150, + "real": "Frequency: 5/20S Duration: 10MN : 150" + }, + { + "label": "Ended SeedExtraction", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction-ca", + "type" : "ENDED", + "value" : 0 + }, + { + "label": "Ended (UserTask TheEnd) SeedExtraction", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction-ca", + "type" : "USERTASK", + "taskId" : "Activity_DiscoverySeedExtraction_TheEnd", + "value": 150 + }, + { + "label": "Flow per minutes", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction-ca", + "type" : "FLOWRATEUSERTASKMN", + "taskId" : "Activity_DiscoverySeedExtraction_TheEnd", + "standardDeviation": 10, + "value": 15 + } + ] + }, + "warmingUp" : { + "duration": "PT4M", + "operations": [ + { + "type": "STARTEVENT", + "taskId": "StartEvent_1", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction-ca", + "variablesOperation": { + "loopcrawl": "generaterandomlist(1000)", + "loopmatch": "generaterandomlist(1)" + }, + "frequency": "PT20S", + "numberOfExecutions": "5", + "endWarmingUp": "UserTaskThreshold(Activity_DiscoverySeedExtraction_TheEnd,1)" + } + ] + }, + "flows": [ + { + "type": "STARTEVENT", + "taskId": "StartEvent_1", + "processIdSubprocess": "DiscoverySeedExtraction", + "processIdCallactivity": "DiscoverySeedExtraction-ca", + "processId": "DiscoverySeedExtraction-ca", + "variablesOperation": { + "loopcrawl": "generaterandomlist(1000)", + "loopmatch": "generaterandomlist(1)" + }, + "frequency": "PT20S", + "numberOfExecutions": "5", + "nbWorkers": 1, + "real": "PT20S 5 exe" + }, + { + "type": "SERVICETASK", + "topic": "discovery-seedextraction-retrieve", + "waitingTime": "PT2S", + "fixedBackOffDelay": "0" + }, + { + "type": "SERVICETASK", + "topic": "discovery-seedextraction-crawl", + "waitingTime": "PT2S", + "modeExecution": "ASYNCHRONOUS", + "fixedBackOffDelay": "0" + }, + { + "type": "SERVICETASK", + "topic": "discovery-seedextraction-match", + "modeExecution": "ASYNCHRONOUS", + "waitingTime": "PT10S" + }, + { + "type": "SERVICETASK", + "topic": "discovery-seedextraction-store", + "modeExecution": "ASYNCHRONOUS", + "waitingTime": "PT2S" + } + ] +} + diff --git a/src/main/resources/loadtest/Verification.bpmn b/src/main/resources/loadtest/Verification.bpmn new file mode 100644 index 0000000..ab600ec --- /dev/null +++ b/src/main/resources/loadtest/Verification.bpmn @@ -0,0 +1,617 @@ + + + + + Flow_03wp37n + Flow_1pvp3fl + + Flow_0bm99i9 + + + Flow_1yr5npa + Flow_1te1f06 + Flow_0wv5rgi + + + Flow_1whfxi3 + Flow_1p57ad4 + Flow_03k7nfv + + + Flow_1p57ad4 + Flow_0p9193k + Flow_11fjs4n + + + Flow_0p9193k + Flow_1k34fbm + Flow_08y6bs2 + + + Flow_1k34fbm + Flow_1ceyr7m + + Flow_0nxebjm + + + + + + Flow_1b6jz4x + + + + + + + Flow_0uq1nht + Flow_1b6jz4x + + + + + + Flow_0fv4cwf + Flow_0uq1nht + + + + + + Flow_0nxebjm + Flow_0fv4cwf + + + 30 s + + + + 1 mn + + + + 1 mn + + + + + Flow_1ceyr7m + Flow_08y6bs2 + Flow_1uh2f8t + + + + + + Flow_0bm99i9 + Flow_1yr5npa + + + Flow_0wv5rgi + + + Flow_1to8lki + Flow_0q78q86 + Flow_0vbxrze + + + Flow_1j6tytw + Flow_169lbhb + Flow_0s6j7xx + + + Flow_195l3vj + + + Flow_0s6j7xx + + + Flow_11fjs4n + + + Flow_04ixvbe + + + + + + Flow_1uh2f8t + Flow_03k7nfv + Flow_1to8lki + + + + + + Flow_0q78q86 + Flow_1j6tytw + + + + + + Flow_0vbxrze + Flow_04ixvbe + + + + + + Flow_169lbhb + Flow_195l3vj + + + + + + Flow_11q26iu + Flow_1whfxi3 + + + + + + Flow_1te1f06 + Flow_11q26iu + + + + + =true + + + + + =true + + + + =true + + + + =true + + + + + + + =true + + + + + =true + + + + + + + + + + + + + + + 3 Millions/day + + + 1 s + + + 1 s + + + 3 s + + + 1 s + + + 1 s + + + 1 s + + + 5 s + + + + Flow_0qb8c6g + + PT1H + + + + Flow_0qb8c6g + + + + Flow_03wp37n + + + + Flow_1xpt3al + + + + + Flow_1pvp3fl + Flow_1xpt3al + + + Flow_06bkp5k + + + + Flow_06bkp5k + + PT1H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/loadtest/Verification.json b/src/main/resources/loadtest/Verification.json new file mode 100644 index 0000000..20683c3 --- /dev/null +++ b/src/main/resources/loadtest/Verification.json @@ -0,0 +1,123 @@ +{ + "name": "Verification", + "processId": "Verification", + "modeVerification": "NO", + "type": "FLOW", + "serverType": "Camunda_8", + "deployments": [ + { + "serverType": "CAMUNDA_8", + "type": "PROCESS", + "processFile": "Verification.bpmn", + "policy": "ONLYNOTEXIST" + } + ], + "flowControl": { + "duration": "PT10M", + "objectives": [ + { + "label": "Creation Verification", + "processId": "Verification", + "type": "CREATED", + "value": 20820, + "real": "Frequency: 347 PI/10s Duration(warmup): 2mn42 ==> CREATION 10MN: 20820, Ended 10 MN: 15198" + }, + { + "label": "Ended (UserTask TheEnd) Verification", + "processId": "Verification", + "type" : "USERTASK", + "taskId" : "Activity_Verification_TheEnd", + "value": 20820 + }, + { + "label": "Flow per minutes", + "processId": "Verification", + "type" : "FLOWRATEUSERTASKMN", + "taskId" : "Activity_Verification_TheEnd", + "standardDeviation": 10, + "value": 2082 + } + ] + }, + "warmingUp" : { + "duration": "PT4M", + "operations": [ + { + "type": "STARTEVENT", + "taskId": "StartEvent_1", + "processId": "Verification", + "variables": {}, + "frequency": "PT10S", + "numberOfExecutions": "347", + "endWarmingUp": "UserTaskThreshold(Activity_Verification_TheEnd,10)" + } + ] + }, + "flows": [ + { + "type": "STARTEVENT", + "taskId": "StartEvent_1", + "processId": "Verification", + "variables": {}, + "frequency": "PT10S", + "numberOfExecutions": "347", + "nbWorkers": "1", + "label" : "347 /1 worker" + }, + { + "type": "SERVICETASK", + "topic": "verification-retrieve", + "modeExecution": "WAIT", + "waitingTime": "PT1S" + }, + { + "type": "SERVICETASK", + "topic": "verification-checkurl", + "modeExecution": "WAIT", + "waitingTime": "PT5S" + }, + { + "type": "SERVICETASK", + "topic": "verification-updateitem", + "modeExecution": "WAIT", + "waitingTime": "PT1S" + }, + { + "type": "SERVICETASK", + "topic": "verification-download", + "modeExecution": "WAIT", + "waitingTime": "PT30S" + }, + { + "type": "SERVICETASK", + "topic": "verification-fingerprint", + "modeExecution": "WAIT", + "waitingTime": "PT1M" + }, + { + "type": "SERVICETASK", + "topic": "verification-getresult", + "modeExecution": "WAIT", + "waitingTime": "PT1M" + }, + { + "type": "SERVICETASK", + "topic": "verification-obtaindescription", + "modeExecution": "WAIT", + "waitingTime": "PT3S" + }, + { + "type": "SERVICETASK", + "topic": "verification-thresoldcheck", + "modeExecution": "WAIT", + "waitingTime": "PT1S" + }, + { + "type": "SERVICETASK", + "topic": "verification-createpotentialclaim", + "modeExecution": "WAIT", + "waitingTime": "PT1S" + } + ] +} + diff --git a/src/test/java/automatorapi/SimpleUserTask.java b/src/test/java/automatorapi/TestSimpleUserTask.java similarity index 54% rename from src/test/java/automatorapi/SimpleUserTask.java rename to src/test/java/automatorapi/TestSimpleUserTask.java index 1161b0d..dd8f4ef 100644 --- a/src/test/java/automatorapi/SimpleUserTask.java +++ b/src/test/java/automatorapi/TestSimpleUserTask.java @@ -1,10 +1,11 @@ package automatorapi; import org.camunda.automator.AutomatorAPI; -import org.camunda.automator.bpmnengine.BpmnEngineConfiguration; +import org.camunda.automator.bpmnengine.BpmnEngine; import org.camunda.automator.bpmnengine.BpmnEngineConfigurationInstance; -import org.camunda.automator.definition.ScenarioExecution; +import org.camunda.automator.configuration.ConfigurationBpmEngine; import org.camunda.automator.definition.Scenario; +import org.camunda.automator.definition.ScenarioExecution; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.engine.RunParameters; import org.camunda.automator.engine.RunResult; @@ -13,38 +14,54 @@ import java.io.File; -public class SimpleUserTask { +public class TestSimpleUserTask { @Autowired AutomatorAPI automatorApi; @Test public void SimpleUserTaskAPI() { + if (automatorApi==null) { + // SpringBoot didn't provide the object + assert(true); + return; + } - Scenario scenario = automatorApi.createScenario() - .setProcessId("SimpleUserTask") - .setName("Simple User Task"); + Scenario scenario = automatorApi.createScenario().setProcessId("SimpleUserTask").setName("Simple User Task"); ScenarioExecution execution = ScenarioExecution.createExecution(scenario) // .setName("dummy") // name .setNumberProcessInstances(2); // number of process instance to generate - - execution.addStep(ScenarioStep.createStepCreate(execution, "StartEvent_Review")); RunParameters runParameters = new RunParameters(); runParameters.logLevel = RunParameters.LOGLEVEL.DEBUG; + try { - BpmnEngineConfiguration engineConfiguration = BpmnEngineConfigurationInstance.getDummy(); - RunResult scenarioExecutionResult = automatorApi.executeScenario(automatorApi.getBpmnEngine(engineConfiguration, engineConfiguration.servers.get(0)), runParameters, scenario); - assert (scenarioExecutionResult.isSuccess()); + ConfigurationBpmEngine engineConfiguration = BpmnEngineConfigurationInstance.getDummy(); + BpmnEngine bpmnEngine = automatorApi.getBpmnEngineFromScenario(scenario, engineConfiguration); + if (bpmnEngine == null) + bpmnEngine = automatorApi.getBpmnEngine(engineConfiguration, engineConfiguration.getListServers().get(0)); + + RunResult scenarioExecutionResult = automatorApi.executeScenario(bpmnEngine, runParameters, scenario); + assert (scenarioExecutionResult.isSuccess()); + } catch (Exception e) { + + assert (false); + } } @Test public void SimpleUserTaskScenario() { try { + if (automatorApi==null) { + // SpringBoot didn't provide the object + assert(true); + return; + } File userTaskFile = new File("./test/resources/simpleusertask/AutomatorSimpleUserTask.json"); Scenario scenario = automatorApi.loadFromFile(userTaskFile); + assert(scenario!=null); } catch (Exception e) { assert (false); } diff --git a/src/test/ressources/Complexprocess/ComplexProcess.md b/src/test/resources/Complexprocess/ComplexProcess.md similarity index 100% rename from src/test/ressources/Complexprocess/ComplexProcess.md rename to src/test/resources/Complexprocess/ComplexProcess.md diff --git a/src/test/ressources/Complexprocess/ComplexProcess.png b/src/test/resources/Complexprocess/ComplexProcess.png similarity index 100% rename from src/test/ressources/Complexprocess/ComplexProcess.png rename to src/test/resources/Complexprocess/ComplexProcess.png diff --git a/src/test/ressources/Complexprocess/ComplexProcessAutormator.json b/src/test/resources/Complexprocess/ComplexProcessAutormator.json similarity index 96% rename from src/test/ressources/Complexprocess/ComplexProcessAutormator.json rename to src/test/resources/Complexprocess/ComplexProcessAutormator.json index 4bd637d..f9b8726 100644 --- a/src/test/ressources/Complexprocess/ComplexProcessAutormator.json +++ b/src/test/resources/Complexprocess/ComplexProcessAutormator.json @@ -4,14 +4,14 @@ "numberOfExecutions" : 1000, "deployments": [ { - "server": "CAMUNDA_7", + "serverType": "CAMUNDA_7", "name": "Camunda7Local", "type": "PROCESS", "processFile": "ComplexProcess_C7.bpmn", "policy": "ONLYNOTEXIST" }, { - "server": "CAMUNDA_8", + "serverType": "CAMUNDA_8", "name": "Camunda8Local", "type": "PROCESS", "processFile": "ComplexProcess_C8.bpmn", diff --git a/src/test/ressources/Complexprocess/ComplexProcess_C7.bpmn b/src/test/resources/Complexprocess/ComplexProcess_C7.bpmn similarity index 100% rename from src/test/ressources/Complexprocess/ComplexProcess_C7.bpmn rename to src/test/resources/Complexprocess/ComplexProcess_C7.bpmn diff --git a/src/test/ressources/Complexprocess/ComplexProcess_C8.bpmn b/src/test/resources/Complexprocess/ComplexProcess_C8.bpmn similarity index 100% rename from src/test/ressources/Complexprocess/ComplexProcess_C8.bpmn rename to src/test/resources/Complexprocess/ComplexProcess_C8.bpmn diff --git a/src/test/ressources/paralleletask/ParalleleTask.bpmn b/src/test/resources/paralleletask/ParalleleTask.bpmn similarity index 100% rename from src/test/ressources/paralleletask/ParalleleTask.bpmn rename to src/test/resources/paralleletask/ParalleleTask.bpmn diff --git a/src/test/ressources/simpleusertask/AutomatorSimpleUserTask.json b/src/test/resources/simpleusertask/AutomatorSimpleUserTask.json similarity index 100% rename from src/test/ressources/simpleusertask/AutomatorSimpleUserTask.json rename to src/test/resources/simpleusertask/AutomatorSimpleUserTask.json diff --git a/src/test/ressources/simpleusertask/SimpleUserTask-C8.bpmn b/src/test/resources/simpleusertask/SimpleUserTask-C8.bpmn similarity index 100% rename from src/test/ressources/simpleusertask/SimpleUserTask-C8.bpmn rename to src/test/resources/simpleusertask/SimpleUserTask-C8.bpmn diff --git a/src/test/ressources/simpleusertask/SimpleUserTask.bpmn b/src/test/resources/simpleusertask/SimpleUserTask.bpmn similarity index 100% rename from src/test/ressources/simpleusertask/SimpleUserTask.bpmn rename to src/test/resources/simpleusertask/SimpleUserTask.bpmn