This is an automated email from the ASF dual-hosted git repository.
gomm pushed a commit to branch edge-extensions
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
The following commit(s) were added to refs/heads/edge-extensions by this push:
new a4d0bad Initial setup for auto migration
a4d0bad is described below
commit a4d0bad2a417022c2e430007273c1bf04c8e5cd6
Author: Daniel Gomm <[email protected]>
AuthorDate: Fri Apr 16 13:14:56 2021 +0200
Initial setup for auto migration
---
.../management/pe/InvocableElementManager.java | 23 +++++++
.../management/rebalance/MigrationManager.java | 26 ++++++++
.../management/rebalance/triggers/Comparator.java | 6 ++
.../rebalance/triggers/ThresholdTriggerPolicy.java | 7 ++
.../rebalance/triggers/TrendTriggerPolicy.java | 77 ++++++++++++++++++++++
.../rebalance/triggers/TriggerPolicy.java | 7 ++
.../management/resource/ResourceManager.java | 16 ++++-
.../migration/MigrationPipelineGenerator.java | 61 +++++++++++++++++
.../manager/node/StreamPipesClusterManager.java | 8 +--
.../org/apache/streampipes/rest/impl/Node.java | 30 ++++++++-
10 files changed, 255 insertions(+), 6 deletions(-)
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/pe/InvocableElementManager.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/pe/InvocableElementManager.java
index abc4841..76f406b 100644
---
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/pe/InvocableElementManager.java
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/pe/InvocableElementManager.java
@@ -38,6 +38,7 @@ import
org.apache.streampipes.model.staticproperty.StaticProperty;
import org.apache.streampipes.node.controller.config.NodeControllerConfig;
import org.apache.streampipes.node.controller.management.node.NodeManager;
import
org.apache.streampipes.node.controller.management.pe.storage.RunningInvocableInstances;
+import
org.apache.streampipes.node.controller.management.resource.model.ResourceMetrics;
import org.apache.streampipes.serializers.json.JacksonSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -163,6 +164,19 @@ public class InvocableElementManager implements
IPipelineElementLifeCycle {
return response;
}
+ public void postMigrationRequest(InvocableStreamPipesEntity
instanceToMigrate){
+ try {
+ String url = generateBackendMigrationEndpoint();
+ String desc = toJson(instanceToMigrate);
+ org.apache.http.client.fluent.Response resp = Request.Post(url)
+ .bodyString(desc, ContentType.APPLICATION_JSON)
+ .execute();
+ resp.returnContent();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
private void adaptPipelineDescription(InvocableStreamPipesEntity graph,
PipelineElementReconfigurationEntity reconfigurationEntity){
List<StaticProperty> staticProperties = new ArrayList<>();
graph.getStaticProperties().forEach(sp -> {
@@ -292,6 +306,15 @@ public class InvocableElementManager implements
IPipelineElementLifeCycle {
+ NodeControllerConfig.INSTANCE.getNodeControllerId();
}
+ private String generateBackendMigrationEndpoint() {
+ return HTTP_PROTOCOL
+ + NodeControllerConfig.INSTANCE.backendLocation()
+ + COLON
+ + NodeControllerConfig.INSTANCE.backendPort()
+ + SLASH
+ +
"streampipes-backend/api/v2/users/[email protected]/nodes/rebalance";
+ }
+
private void registerAtConsul(InvocableRegistration registration) {
try {
String endpoint = consulURL().toString() + SLASH +
CONSUL_REGISTRATION_ROUTE;
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/MigrationManager.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/MigrationManager.java
new file mode 100644
index 0000000..28c98a6
--- /dev/null
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/MigrationManager.java
@@ -0,0 +1,26 @@
+package org.apache.streampipes.node.controller.management.rebalance;
+
+import org.apache.http.client.fluent.Request;
+import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
+import
org.apache.streampipes.node.controller.management.pe.InvocableElementManager;
+import
org.apache.streampipes.node.controller.management.pe.storage.RunningInvocableInstances;
+
+import java.util.List;
+import java.util.Random;
+
+public class MigrationManager {
+
+ public static boolean migrateRandomPE(){
+ List<InvocableStreamPipesEntity> instances =
RunningInvocableInstances.INSTANCE.getAll();
+ if(instances.size() == 0)
+ return false;
+ InvocableStreamPipesEntity randomlySelectedInstance =
instances.get(new Random().nextInt(instances.size()));
+
InvocableElementManager.getInstance().postMigrationRequest(randomlySelectedInstance);
+ return true;
+ }
+
+ public static void migrateHeaviestPE(){
+ //TODO: Migrate the PE with the highest load on CPU (or possibly other
resource)
+ }
+
+}
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/Comparator.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/Comparator.java
new file mode 100644
index 0000000..efceb8f
--- /dev/null
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/Comparator.java
@@ -0,0 +1,6 @@
+package org.apache.streampipes.node.controller.management.rebalance.triggers;
+
+public enum Comparator {
+ SMALLER,
+ GREATER
+}
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/ThresholdTriggerPolicy.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/ThresholdTriggerPolicy.java
new file mode 100644
index 0000000..e1a349c
--- /dev/null
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/ThresholdTriggerPolicy.java
@@ -0,0 +1,7 @@
+package org.apache.streampipes.node.controller.management.rebalance.triggers;
+
+public class ThresholdTriggerPolicy<T extends Number> extends
TrendTriggerPolicy<T>{
+ public ThresholdTriggerPolicy(Comparator comparator, T threshold) {
+ super(1, comparator, threshold, 1);
+ }
+}
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/TrendTriggerPolicy.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/TrendTriggerPolicy.java
new file mode 100644
index 0000000..b5329eb
--- /dev/null
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/TrendTriggerPolicy.java
@@ -0,0 +1,77 @@
+package org.apache.streampipes.node.controller.management.rebalance.triggers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+
+public class TrendTriggerPolicy<T extends Number> implements TriggerPolicy<T>{
+
+ private Queue<T> history;
+ private T threshold;
+ private Comparator comparator;
+ private int numberOfThresholdViolations;
+ private static final Logger LOG =
+
LoggerFactory.getLogger(TrendTriggerPolicy.class.getCanonicalName());
+
+ public TrendTriggerPolicy(int length, Comparator comparator,T threshold,
int numberOfThresholdViolations){
+ this.history = new ArrayBlockingQueue<>(length);
+ this.comparator = comparator;
+ this.threshold = threshold;
+ this.numberOfThresholdViolations = numberOfThresholdViolations;
+ }
+
+ public TrendTriggerPolicy(int length, Comparator comparator, T threshold){
+ this(length, comparator, threshold, length);
+ }
+
+ @Override
+ public void addValue(T value) {
+ if(!this.history.offer(value)) {
+ this.history.poll();
+ this.history.offer(value);
+ }
+ }
+
+ @Override
+ public boolean isTriggered() {
+ int numViolations = 0;
+ switch (this.comparator){
+ case GREATER:
+ for(T value : this.history){
+ //TODO: Replace comparison with sth more robust than
conversion into double values (either
+ // compare double or long values? Or compare BigDecimal
values instead?)
+ if(value.doubleValue() > this.threshold.doubleValue()){
+ numViolations++;
+ }
+ }
+ break;
+ case SMALLER:
+ for(T value : this.history){
+ if(value.doubleValue() < this.threshold.doubleValue()){
+ numViolations++;
+ }
+ }
+ break;
+ }
+ if(numViolations >= this.numberOfThresholdViolations){
+ LOG.info("Trend above threshold detected.");
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void reset() {
+ this.history = new ArrayBlockingQueue<>(this.history.size());
+ }
+
+ //TODO: Remove -- Only for debugging purposes
+ @Override
+ public String toString() {
+ return "TrendTriggerPolicy{" +
+ "history=" + history +
+ '}';
+ }
+}
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/TriggerPolicy.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/TriggerPolicy.java
new file mode 100644
index 0000000..61103ea
--- /dev/null
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/rebalance/triggers/TriggerPolicy.java
@@ -0,0 +1,7 @@
+package org.apache.streampipes.node.controller.management.rebalance.triggers;
+
+public interface TriggerPolicy<T> {
+ void addValue(T value);
+ boolean isTriggered();
+ void reset();
+}
diff --git
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/resource/ResourceManager.java
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/resource/ResourceManager.java
index d274fba..edc2b97 100644
---
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/resource/ResourceManager.java
+++
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/management/resource/ResourceManager.java
@@ -19,6 +19,11 @@ package
org.apache.streampipes.node.controller.management.resource;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.streampipes.node.controller.config.NodeControllerConfig;
+import
org.apache.streampipes.node.controller.management.pe.InvocableElementManager;
+import
org.apache.streampipes.node.controller.management.rebalance.MigrationManager;
+import
org.apache.streampipes.node.controller.management.rebalance.triggers.Comparator;
+import
org.apache.streampipes.node.controller.management.rebalance.triggers.TrendTriggerPolicy;
+import
org.apache.streampipes.node.controller.management.rebalance.triggers.TriggerPolicy;
import
org.apache.streampipes.node.controller.management.resource.model.ResourceMetrics;
import org.apache.streampipes.serializers.json.JacksonSerializer;
import org.slf4j.Logger;
@@ -40,7 +45,7 @@ public class ResourceManager {
private static final Logger LOG =
LoggerFactory.getLogger(ResourceManager.class.getCanonicalName());
- // OSHI to retreive system information
+ // OSHI to retrieve system information
private final SystemInfo si = new SystemInfo();
private final HardwareAbstractionLayer hal = si.getHardware();
private final OperatingSystem os = si.getOperatingSystem();
@@ -68,6 +73,8 @@ public class ResourceManager {
private final Runnable getCurrentResources = () -> {
+ TriggerPolicy<Float> tp = new TrendTriggerPolicy<>(5,
Comparator.GREATER, 99f, 5);
+
while(true) {
try {
// get current node resource metrics
@@ -96,6 +103,13 @@ public class ResourceManager {
resourceMetrics.setFreeDiskSpaceInBytes(k.getValue().get("usableDiskSpace"));
}
+ tp.addValue(resourceMetrics.getCpuLoadInPercent());
+ if(tp.isTriggered()){
+ LOG.info("Auto migration triggered.");
+ if(MigrationManager.migrateRandomPE())
+ tp.reset();
+ }
+
} catch (InterruptedException e) {
LOG.error("Thread interrupted. {}", e.toString());
}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/MigrationPipelineGenerator.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/MigrationPipelineGenerator.java
new file mode 100644
index 0000000..83bff42
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/MigrationPipelineGenerator.java
@@ -0,0 +1,61 @@
+package org.apache.streampipes.manager.migration;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.streampipes.manager.node.StreamPipesClusterManager;
+import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
+import org.apache.streampipes.model.graph.DataProcessorInvocation;
+import org.apache.streampipes.model.node.NodeInfoDescription;
+import org.apache.streampipes.model.pipeline.Pipeline;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+
+public class MigrationPipelineGenerator {
+
+ public static Pipeline
generateMigrationPipeline(InvocableStreamPipesEntity entityToMigrate, Pipeline
correspondingPipeline){
+
+ List<NodeInfoDescription> possibleTargetNodes = new ArrayList<>();
+ List<NodeInfoDescription> nodeInfo =
StreamPipesClusterManager.getAllActiveAndHealthyNodes();
+ nodeInfo.forEach(desc ->{
+ if(desc.getSupportedElements().stream().anyMatch(element ->
element.equals(entityToMigrate.getAppId()))
+ &&
!desc.getNodeControllerId().equals(entityToMigrate.getDeploymentTargetNodeId()))
+ possibleTargetNodes.add(desc);
+ });
+
+ if(possibleTargetNodes.isEmpty())
+ return null;
+
+ //Choose random node; should be adjusted to seek for a proper node to
migrate to (e.g. based on user e.g.
+ // selected labels, locality, free resources,...)
+ NodeInfoDescription targetNode = possibleTargetNodes.get(new
Random().nextInt(possibleTargetNodes.size()));
+
+
entityToMigrate.setDeploymentTargetNodeHostname(targetNode.getHostname());
+
entityToMigrate.setDeploymentTargetNodeId(targetNode.getNodeControllerId());
+ entityToMigrate.setDeploymentTargetNodePort(targetNode.getPort());
+ entityToMigrate.setElementEndpointHostname(targetNode.getHostname());
+ entityToMigrate.setElementEndpointPort(targetNode.getPort());
+
+ Optional<DataProcessorInvocation> originalInvocation =
+ correspondingPipeline.getSepas().stream().filter(dp ->
+
dp.getDeploymentRunningInstanceId().equals(entityToMigrate.getDeploymentRunningInstanceId()))
+ .findFirst();
+ int index =
correspondingPipeline.getSepas().indexOf(originalInvocation.get());
+
+ Pipeline targetPipeline;
+ try {
+ targetPipeline =
PipelineElementMigrationHandler.deepCopyPipeline(correspondingPipeline);
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ targetPipeline.getSepas().remove(index);
+ targetPipeline.getSepas().add(index, (DataProcessorInvocation)
entityToMigrate);
+
+ return targetPipeline;
+ }
+
+}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/StreamPipesClusterManager.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/StreamPipesClusterManager.java
index b3c29d3..2e49f8e 100644
---
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/StreamPipesClusterManager.java
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/StreamPipesClusterManager.java
@@ -38,14 +38,14 @@ import java.util.Optional;
public class StreamPipesClusterManager extends AbstractClusterManager {
private static final Logger LOG =
LoggerFactory.getLogger(StreamPipesClusterManager.class.getCanonicalName());
- public static List<NodeInfoDescription> getAvailableNodes() {
- List<NodeInfoDescription> availableAndHealthyNodes = new ArrayList<>();
+ public static List<NodeInfoDescription> getAllActiveAndHealthyNodes() {
+ List<NodeInfoDescription> activeAndHealthyNodes = new ArrayList<>();
getNodeStorageApi().getAllActiveNodes().forEach(node -> {
if (ClusterHealthCheck.check(node)) {
- availableAndHealthyNodes.add(node);
+ activeAndHealthyNodes.add(node);
}
});
- return availableAndHealthyNodes;
+ return activeAndHealthyNodes;
}
public static List<NodeInfoDescription> getAllNodes() {
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
index 31f138c..2a434e1 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
@@ -17,10 +17,15 @@
*/
package org.apache.streampipes.rest.impl;
+import org.apache.streampipes.manager.migration.MigrationPipelineGenerator;
import org.apache.streampipes.manager.node.StreamPipesClusterManager;
+import org.apache.streampipes.manager.operations.Operations;
+import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
import org.apache.streampipes.model.message.NotificationType;
import org.apache.streampipes.model.message.Notifications;
import org.apache.streampipes.model.node.NodeInfoDescription;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.pipeline.PipelineOperationStatus;
import org.apache.streampipes.rest.api.INode;
import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
@@ -103,7 +108,7 @@ public class Node extends AbstractRestResource implements
INode {
@Produces(MediaType.APPLICATION_JSON)
@Override
public Response getAvailableNodes() {
- return ok(StreamPipesClusterManager.getAvailableNodes());
+ return ok(StreamPipesClusterManager.getAllActiveAndHealthyNodes());
}
@GET
@@ -113,4 +118,27 @@ public class Node extends AbstractRestResource implements
INode {
public Response getNodes() {
return ok(StreamPipesClusterManager.getAllNodes());
}
+
+
+ @POST
+ @Path("/rebalance")
+ @Produces(MediaType.APPLICATION_JSON)
+ @JacksonSerialized
+ public Response migrateProcessorToOtherNode(InvocableStreamPipesEntity
elementToMigrate) {
+ Pipeline correspondingPipeline =
getPipelineStorage().getPipeline(elementToMigrate.getCorrespondingPipeline());
+ Pipeline desiredPipeline =
MigrationPipelineGenerator.generateMigrationPipeline(elementToMigrate,
+ correspondingPipeline);
+ //TODO: Handle this case properly
+ if(desiredPipeline == null)
+ return
statusMessage(Notifications.error(NotificationType.UNKNOWN_ERROR));
+
+ try {
+ PipelineOperationStatus status =
Operations.handlePipelineElementMigration(desiredPipeline,
+ true, true, true);
+ return ok(status);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return
statusMessage(Notifications.error(NotificationType.UNKNOWN_ERROR));
+ }
+ }
}