Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migrated example #313

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions c8-1-instance-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Instance Migration Workaround for Camunda 8

Currently, Camunda 8 does not support instance migration.
This prototype shows how we can use the Zeebe and Operate API to move process instances from one model version to another:
1. The process instances must have entered a wait state, such as a user task or a service task for which the external task worker has been deactivated.
2. For each instance of the old version, an instance in the new version is created.
* Variables are copied
* The instance is modified to start at a predefined point (i.e., the wait state)
3. The old instance is canceled.

This prototype considers the two latest versions of a model. Older versions are ignored.
This prototype is heavily inspired by the [Camunda 7 to 8 migration tooling](https://github.com/camunda-community-hub/camunda-7-to-8-migration).
## Configuration

Everything is preconfigured for local testing (i.e., localhost addresses without encryption).
You can configure the connection to Zeebe as described [here](https://github.com/camunda-community-hub/spring-zeebe).
You can furthermore use the following properties to configure the connection to Operate:
```
operate:
url: https://bru-2.operate.camunda.io/757dbc30-5127-4bed-XXXX-XXXXXXXXXXXX
# for self-managed setup configure
keycloak:
realm: camunda-platform
url: https://mykeycloak.example.com
```

## Version Information

This prototype has been build for Camunda 8.1.8.
It is compatible with both self-managed and SaaS deployments.

Camunda 8.2 provides additional API endpoints, which can be used to improve this prototype:
We can get the element ID of currently enabled flow nodes.
With this information, activity "wait until all processes reached waiting state" becomes obsolete.
34 changes: 34 additions & 0 deletions c8-1-instance-migration/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.camunda.consulting</groupId>
<artifactId>c8instancemigration</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.camunda</groupId>
<artifactId>spring-zeebe-starter</artifactId>
<version>8.1.13</version>
</dependency>
<dependency>
<groupId>io.camunda</groupId>
<artifactId>camunda-operate-client-java</artifactId>
<version>8.1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.7.7</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.camunda.consulting;

import io.camunda.operate.CamundaOperateClient;
import io.camunda.operate.dto.ProcessDefinition;
import io.camunda.operate.dto.ProcessInstance;
import io.camunda.operate.dto.ProcessInstanceState;
import io.camunda.operate.exception.OperateException;
import io.camunda.operate.search.ProcessDefinitionFilter;
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.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.response.ProcessInstanceEvent;
import io.camunda.zeebe.spring.client.EnableZeebeClient;
import io.camunda.zeebe.spring.client.annotation.JobWorker;
import io.camunda.zeebe.spring.client.annotation.Variable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootApplication
@EnableZeebeClient
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}

private static Logger LOG = LoggerFactory.getLogger(Main.class);

@Autowired
ZeebeClient client;
@Autowired
CamundaOperateClient operate;

@JobWorker
public Map<String, Object> fetchInstances(@Variable String bpmnProcessId) throws OperateException {
List<Long> processInstanceIds = new ArrayList<>();
ProcessDefinitionFilter filter = new ProcessDefinitionFilter.Builder().bpmnProcessId(bpmnProcessId).build();
Sort sort = new Sort("version", SortOrder.DESC);
SearchQuery query = new SearchQuery.Builder().filter(filter).sort(sort).build();
List<ProcessDefinition> definitions = operate.searchProcessDefinitions(query);
LOG.info("Found {} versions of process {}", definitions.size(), bpmnProcessId);
if (definitions.size() >= 2) {
ProcessInstanceFilter instanceFilter = new ProcessInstanceFilter.Builder().bpmnProcessId(bpmnProcessId).processVersion(definitions.get(1).getVersion()).state(
ProcessInstanceState.ACTIVE).build();
processInstanceIds = operate.searchProcessInstances(new SearchQuery.Builder().filter(instanceFilter).build()).stream()
.map(ProcessInstance::getKey).toList();
}
LOG.info("{} process instances will be migrated to latest version", processInstanceIds.size());
return Map.of("processInstanceKeys", processInstanceIds);
}

@JobWorker
public Map<String, Object> startInstance(@Variable Long processInstanceKey, @Variable String bpmnProcessId, @Variable String startBeforeElement)
throws OperateException {
Map<String, Object> variables = new HashMap<>();
VariableFilter filter = new VariableFilter.Builder().processInstanceKey(processInstanceKey).build();
List<io.camunda.operate.dto.Variable> vars = operate.searchVariables(new SearchQuery.Builder().filter(filter).build());
vars.forEach(var -> {
if (var.getTruncated()) {
try {
io.camunda.operate.dto.Variable fullVar = operate.getVariable(var.getKey());
variables.put(fullVar.getName(), fullVar.getValue());
} catch (OperateException e) {
throw new RuntimeException(e);
}
} else {
variables.put(var.getName(), var.getValue());
}
});
variables.put("oldInstanceKey", processInstanceKey);
ProcessInstanceEvent newInstance = client.newCreateInstanceCommand()
.bpmnProcessId(bpmnProcessId)
.latestVersion()
.startBeforeElement(startBeforeElement)
.variables(variables)
.send()
.join();
LOG.info("Migration of instance {} completed. New instance ID is {}.", processInstanceKey, newInstance.getProcessInstanceKey());
return Map.of("newInstanceKey", newInstance.getProcessInstanceKey());
}

@JobWorker
public void deleteOldInstance(@Variable Long processInstanceKey) {
client.newCancelInstanceCommand(processInstanceKey).send().join();
LOG.info("Instance {} has been cancelled, because it has been migrated to a new process model version.", processInstanceKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.camunda.consulting;

import io.camunda.operate.CamundaOperateClient;
import io.camunda.operate.auth.AuthInterface;
import io.camunda.operate.auth.SaasAuthentication;
import io.camunda.operate.auth.SelfManagedAuthentication;
import io.camunda.operate.auth.SimpleAuthentication;
import io.camunda.operate.exception.OperateException;
import io.camunda.zeebe.spring.client.annotation.Deployment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Objects;

@Configuration
@Deployment(resources = {"classpath:c8-to-c8-instance_migration.bpmn"})
public class OperateClientConfiguration {
@Value("${operate.keycloak.url:#{null}}")
String keycloakUrl;
@Value("${zeebe.client.cloud.client-id:#{null}}")
String clientId;

@Value("${zeebe.client.cloud.client-secret:#{null}}")
String clientSecret;

@Value("${operate.baseUrl:operate.camunda.io}")
String baseUrl;

@Value("${operate.authUrl:https://login.cloud.camunda.io/oauth/token}")
String authUrl;

@Value("${operate.url:http://localhost:8081}")
String operateUrl;

@Value("${operate.user:demo}")
String operateUsr;

@Value("${operate.password:demo}")
String password;

@Value("${operate.keycloak.realm:camunda-platform}")
String keycloakRealm;

@Bean
public CamundaOperateClient operate() {
AuthInterface auth = null;
if (Objects.nonNull(clientSecret) && Objects.nonNull(clientId)) {
if (Objects.nonNull(keycloakUrl)) {
auth = new SelfManagedAuthentication()
.clientId(clientId)
.clientSecret(clientSecret)
.keycloakRealm(keycloakRealm)
.keycloakUrl(keycloakUrl);
} else {
auth = new SaasAuthentication(authUrl, baseUrl, clientId, clientSecret);
}
} else {
auth = new SimpleAuthentication(operateUsr, password, operateUrl);
}
CamundaOperateClient client = null;
try {
client = new CamundaOperateClient.Builder().operateUrl(operateUrl)
.authentication(auth)
.build();
} catch (OperateException e) {
throw new RuntimeException(e);
}
return client;
}
}
3 changes: 3 additions & 0 deletions c8-1-instance-migration/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
zeebe.client:
broker.gateway-address: 127.0.0.1:26500
security.plaintext: true
Loading