This is an automated email from the ASF dual-hosted git repository.
SvenO3 pushed a commit to branch
4430-chart-settings-not-correctly-migrated-after-event-schema-changes
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/4430-chart-settings-not-correctly-migrated-after-event-schema-changes
by this push:
new f1ed665e78 Implement measurement update management for pipeline changes
f1ed665e78 is described below
commit f1ed665e7822025b7935c3820fa53357a433d984
Author: Sven Oehler <[email protected]>
AuthorDate: Mon May 18 15:52:50 2026 +0200
Implement measurement update management for pipeline changes
---
.../datalake}/CriticalMeasurementFieldChange.java | 5 +-
.../model/datalake/MeasurementUpdateInfo.java | 63 ++++++++++
.../v2/pipeline/MeasurementChangeDetector.java | 1 +
.../pipeline/MeasurementChangeValidationStep.java | 21 +---
.../update/ChartSchemaUpdateCoordinator.java | 45 ++------
.../update/MeasurementUpdateManagement.java | 111 ++++++++++++++++++
.../pipeline/update/MeasurementUpdateUtils.java | 78 +++++++++++++
.../update/MeasurementUpdateManagementTest.java | 127 +++++++++++++++++++++
.../streampipes/rest/impl/PipelineResource.java | 16 +++
9 files changed, 410 insertions(+), 57 deletions(-)
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/CriticalMeasurementFieldChange.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/CriticalMeasurementFieldChange.java
similarity index 89%
rename from
streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/CriticalMeasurementFieldChange.java
rename to
streampipes-model/src/main/java/org/apache/streampipes/model/datalake/CriticalMeasurementFieldChange.java
index 250b55ab52..b461e38926 100644
---
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/CriticalMeasurementFieldChange.java
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/CriticalMeasurementFieldChange.java
@@ -16,8 +16,11 @@
*
*/
-package org.apache.streampipes.manager.matching.v2.pipeline;
+package org.apache.streampipes.model.datalake;
+import org.apache.streampipes.model.shared.annotation.TsModel;
+
+@TsModel
public record CriticalMeasurementFieldChange(String runtimeName,
String existingType,
String updatedType) {
diff --git
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/MeasurementUpdateInfo.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/MeasurementUpdateInfo.java
new file mode 100644
index 0000000000..77eb9edb78
--- /dev/null
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/MeasurementUpdateInfo.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.model.datalake;
+
+import org.apache.streampipes.model.connect.adapter.ChartSchemaUpdateInfo;
+import org.apache.streampipes.model.shared.annotation.TsModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@TsModel
+public class MeasurementUpdateInfo {
+
+ private String measurementName;
+ private List<ChartSchemaUpdateInfo> chartSchemaUpdateInfos;
+ private List<CriticalMeasurementFieldChange> criticalMeasurementFieldChanges;
+
+ public MeasurementUpdateInfo() {
+ this.chartSchemaUpdateInfos = new ArrayList<>();
+ this.criticalMeasurementFieldChanges = new ArrayList<>();
+ }
+
+ public String getMeasurementName() {
+ return measurementName;
+ }
+
+ public void setMeasurementName(String measurementName) {
+ this.measurementName = measurementName;
+ }
+
+ public List<ChartSchemaUpdateInfo> getChartSchemaUpdateInfos() {
+ return chartSchemaUpdateInfos;
+ }
+
+ public void setChartSchemaUpdateInfos(List<ChartSchemaUpdateInfo>
chartSchemaUpdateInfos) {
+ this.chartSchemaUpdateInfos = chartSchemaUpdateInfos;
+ }
+
+ public List<CriticalMeasurementFieldChange>
getCriticalMeasurementFieldChanges() {
+ return criticalMeasurementFieldChanges;
+ }
+
+ public void setCriticalMeasurementFieldChanges(
+ List<CriticalMeasurementFieldChange> criticalMeasurementFieldChanges) {
+ this.criticalMeasurementFieldChanges = criticalMeasurementFieldChanges;
+ }
+}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeDetector.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeDetector.java
index a7026fb7a6..0c0c51e993 100644
---
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeDetector.java
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeDetector.java
@@ -18,6 +18,7 @@
package org.apache.streampipes.manager.matching.v2.pipeline;
+import org.apache.streampipes.model.datalake.CriticalMeasurementFieldChange;
import org.apache.streampipes.model.schema.EventProperty;
import org.apache.streampipes.model.schema.EventPropertyPrimitive;
import org.apache.streampipes.model.schema.EventSchema;
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeValidationStep.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeValidationStep.java
index 4d53c0d49c..325b821faa 100644
---
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeValidationStep.java
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/v2/pipeline/MeasurementChangeValidationStep.java
@@ -18,10 +18,11 @@
package org.apache.streampipes.manager.matching.v2.pipeline;
-import org.apache.streampipes.model.DataSinkType;
+import org.apache.streampipes.manager.pipeline.update.MeasurementUpdateUtils;
import org.apache.streampipes.model.SpDataStream;
import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
import org.apache.streampipes.model.base.NamedStreamPipesEntity;
+import org.apache.streampipes.model.datalake.CriticalMeasurementFieldChange;
import org.apache.streampipes.model.graph.DataProcessorInvocation;
import org.apache.streampipes.model.graph.DataSinkInvocation;
import org.apache.streampipes.model.pipeline.PipelineElementValidationInfo;
@@ -31,21 +32,18 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
public class MeasurementChangeValidationStep extends
AbstractPipelineValidationStep {
public static final String MEASUREMENT_UPDATE_REQUIRED =
"Measurement field storage type changed. Manual measurement update
handling required";
- private static final String DATA_LAKE_SINK_APP_ID =
"org.apache.streampipes.sinks.internal.jvm.datalake";
@Override
public void apply(NamedStreamPipesEntity source,
InvocableStreamPipesEntity target,
Set<InvocableStreamPipesEntity> allTargets,
List<PipelineElementValidationInfo> validationInfos) {
- if (target instanceof DataSinkInvocation dataSink &&
isDatabaseSink(dataSink)) {
+ if (target instanceof DataSinkInvocation dataSink &&
MeasurementUpdateUtils.isDataLakeSink(dataSink)) {
var criticalFieldChanges = findCriticalMeasurementFieldChanges(source,
dataSink);
if (!criticalFieldChanges.isEmpty()) {
validationInfos.add(PipelineElementValidationInfo.error(makeValidationMessage(criticalFieldChanges)));
@@ -57,11 +55,6 @@ public class MeasurementChangeValidationStep extends
AbstractPipelineValidationS
}
}
- private boolean isDatabaseSink(DataSinkInvocation dataSink) {
- return DATA_LAKE_SINK_APP_ID.equals(dataSink.getAppId())
- ||
streamOf(dataSink.getCategory()).anyMatch(DataSinkType.DATABASE.name()::equals);
- }
-
private List<CriticalMeasurementFieldChange>
findCriticalMeasurementFieldChanges(
NamedStreamPipesEntity source,
DataSinkInvocation dataSink) {
@@ -108,12 +101,4 @@ public class MeasurementChangeValidationStep extends
AbstractPipelineValidationS
return Optional.empty();
}
}
-
- private <T> Stream<T> streamOf(Iterable<T> iterable) {
- if (iterable == null) {
- return Stream.empty();
- }
-
- return StreamSupport.stream(iterable.spliterator(), false);
- }
}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/ChartSchemaUpdateCoordinator.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/ChartSchemaUpdateCoordinator.java
index 38015777f3..b7d2272f50 100644
---
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/ChartSchemaUpdateCoordinator.java
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/ChartSchemaUpdateCoordinator.java
@@ -22,11 +22,9 @@ import
org.apache.streampipes.model.connect.adapter.ChartSchemaUpdateInfo;
import org.apache.streampipes.model.datalake.DataExplorerWidgetHealthStatus;
import org.apache.streampipes.model.datalake.DataExplorerWidgetModel;
import org.apache.streampipes.model.datalake.DataLakeMeasure;
-import org.apache.streampipes.model.graph.DataSinkInvocation;
import org.apache.streampipes.model.pipeline.Pipeline;
import org.apache.streampipes.model.schema.EventProperty;
import org.apache.streampipes.model.schema.EventSchema;
-import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
import org.apache.streampipes.serializers.json.JacksonSerializer;
import org.apache.streampipes.storage.api.explorer.IDataExplorerWidgetStorage;
import org.apache.streampipes.storage.management.StorageDispatcher;
@@ -44,8 +42,6 @@ import java.util.stream.Collectors;
public class ChartSchemaUpdateCoordinator {
- private static final String DATA_LAKE_SINK_APP_ID =
"org.apache.streampipes.sinks.internal.jvm.datalake";
- private static final String DATA_LAKE_MEASUREMENT_FIELD = "db_measurement";
private static final String SOURCE_CONFIGS = "sourceConfigs";
private static final String MEASURE_NAME = "measureName";
private static final String MEASURE = "measure";
@@ -69,9 +65,12 @@ public class ChartSchemaUpdateCoordinator {
this.objectMapper = JacksonSerializer.getObjectMapper();
}
- public List<ChartSchemaUpdateInfo> checkChartMigrations(Pipeline pipeline,
- EventSchema
updatedSchema) {
- var measureNames = extractMeasureNames(pipeline);
+ public List<ChartSchemaUpdateInfo> checkChartMigrations(Pipeline pipeline,
EventSchema updatedSchema) {
+ var measureNames = MeasurementUpdateUtils.extractMeasureNames(pipeline);
+ return checkChartMigrations(measureNames, updatedSchema);
+ }
+
+ public List<ChartSchemaUpdateInfo> checkChartMigrations(Set<String>
measureNames, EventSchema updatedSchema) {
return widgetStorage
.findAll()
.stream()
@@ -82,7 +81,7 @@ public class ChartSchemaUpdateCoordinator {
public void updateCharts(Pipeline pipeline,
EventSchema updatedSchema) {
- var measureNames = extractMeasureNames(pipeline);
+ var measureNames = MeasurementUpdateUtils.extractMeasureNames(pipeline);
updateCharts(measureNames, updatedSchema);
}
@@ -248,33 +247,6 @@ public class ChartSchemaUpdateCoordinator {
}
}
- private Set<String> extractMeasureNames(Pipeline pipeline) {
- if (pipeline.getActions() == null) {
- return Set.of();
- }
-
- return pipeline
- .getActions()
- .stream()
- .filter(ChartSchemaUpdateCoordinator::isDataLakeSink)
- .map(this::extractMeasureName)
- .flatMap(Optional::stream)
- .collect(Collectors.toSet());
- }
-
- private Optional<String> extractMeasureName(DataSinkInvocation sink) {
- return Optional
- .ofNullable(sink.getStaticProperties())
- .stream()
- .flatMap(List::stream)
- .filter(property ->
DATA_LAKE_MEASUREMENT_FIELD.equals(property.getInternalName()))
- .filter(FreeTextStaticProperty.class::isInstance)
- .map(FreeTextStaticProperty.class::cast)
- .map(FreeTextStaticProperty::getValue)
- .filter(Objects::nonNull)
- .findFirst();
- }
-
private String getChartTitle(DataExplorerWidgetModel widget) {
var baseAppearanceConfig = widget.getBaseAppearanceConfig();
if (baseAppearanceConfig != null && baseAppearanceConfig.get(WIDGET_TITLE)
instanceof String widgetTitle) {
@@ -286,7 +258,4 @@ public class ChartSchemaUpdateCoordinator {
}
}
- private static boolean isDataLakeSink(DataSinkInvocation dataSink) {
- return DATA_LAKE_SINK_APP_ID.equals(dataSink.getAppId());
- }
}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateManagement.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateManagement.java
new file mode 100644
index 0000000000..5d253a6738
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateManagement.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.manager.pipeline.update;
+
+import
org.apache.streampipes.manager.matching.v2.pipeline.MeasurementChangeDetector;
+import org.apache.streampipes.model.SpDataStream;
+import org.apache.streampipes.model.connect.adapter.ChartSchemaUpdateInfo;
+import org.apache.streampipes.model.datalake.CriticalMeasurementFieldChange;
+import org.apache.streampipes.model.datalake.MeasurementUpdateInfo;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.schema.EventSchema;
+import org.apache.streampipes.storage.management.StorageDispatcher;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class MeasurementUpdateManagement {
+
+ private final ChartSchemaUpdateCoordinator chartSchemaUpdateCoordinator;
+
+ public MeasurementUpdateManagement() {
+ this(new ChartSchemaUpdateCoordinator());
+ }
+
+ MeasurementUpdateManagement(ChartSchemaUpdateCoordinator
chartSchemaUpdateCoordinator) {
+ this.chartSchemaUpdateCoordinator = chartSchemaUpdateCoordinator;
+ }
+
+ public List<MeasurementUpdateInfo> checkPipelineMigrations(String pipelineId,
+ Pipeline
updatedPipeline) {
+ var storedPipeline = StorageDispatcher.INSTANCE.getNoSqlStore()
+ .getPipelineStorageAPI()
+ .getElementById(pipelineId);
+
+ return checkPipelineMigrations(storedPipeline, updatedPipeline);
+ }
+
+ List<MeasurementUpdateInfo> checkPipelineMigrations(Pipeline storedPipeline,
+ Pipeline
updatedPipeline) {
+ return MeasurementUpdateUtils.getDataLakeSinks(updatedPipeline)
+ .stream()
+ .map(updatedSink -> makeUpdateInfo(storedPipeline, updatedSink))
+ .flatMap(Optional::stream)
+ .toList();
+ }
+
+ private Optional<MeasurementUpdateInfo> makeUpdateInfo(Pipeline
storedPipeline,
+ DataSinkInvocation
updatedSink) {
+ var criticalFieldChanges =
findCriticalMeasurementFieldChanges(storedPipeline, updatedSink);
+ var chartSchemaUpdateInfos = findChartSchemaUpdateInfos(updatedSink);
+
+ if (criticalFieldChanges.isEmpty() && chartSchemaUpdateInfos.isEmpty()) {
+ return Optional.empty();
+ } else {
+ var measurementUpdateInfo = new MeasurementUpdateInfo();
+
measurementUpdateInfo.setMeasurementName(MeasurementUpdateUtils.extractMeasureName(updatedSink).orElse(null));
+
measurementUpdateInfo.setCriticalMeasurementFieldChanges(criticalFieldChanges);
+ measurementUpdateInfo.setChartSchemaUpdateInfos(chartSchemaUpdateInfos);
+ return Optional.of(measurementUpdateInfo);
+ }
+ }
+
+ private List<CriticalMeasurementFieldChange>
findCriticalMeasurementFieldChanges(Pipeline storedPipeline,
+
DataSinkInvocation updatedSink) {
+ return MeasurementUpdateUtils.getDataLakeSinkById(storedPipeline,
updatedSink.getElementId())
+ .flatMap(storedSink -> getFirstInputStreamSchema(storedSink)
+ .flatMap(existingSchema -> getFirstInputStreamSchema(updatedSink)
+ .map(updatedSchema ->
MeasurementChangeDetector.findCriticalMeasurementFieldChanges(
+ existingSchema,
+ updatedSchema
+ ))))
+ .orElse(List.of());
+ }
+
+ private List<ChartSchemaUpdateInfo>
findChartSchemaUpdateInfos(DataSinkInvocation updatedSink) {
+ return getFirstInputStreamSchema(updatedSink)
+ .map(updatedSchema ->
chartSchemaUpdateCoordinator.checkChartMigrations(
+
MeasurementUpdateUtils.extractMeasureName(updatedSink).stream().collect(Collectors.toSet()),
+ updatedSchema
+ ))
+ .orElse(List.of());
+ }
+
+ private Optional<EventSchema> getFirstInputStreamSchema(DataSinkInvocation
sink) {
+ return Optional
+ .ofNullable(sink.getInputStreams())
+ .stream()
+ .flatMap(List::stream)
+ .findFirst()
+ .map(SpDataStream::getEventSchema);
+ }
+
+}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateUtils.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateUtils.java
new file mode 100644
index 0000000000..5145ae8099
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.manager.pipeline.update;
+
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class MeasurementUpdateUtils {
+
+ public static final String DATA_LAKE_SINK_APP_ID =
"org.apache.streampipes.sinks.internal.jvm.datalake";
+ public static final String DATA_LAKE_MEASUREMENT_FIELD = "db_measurement";
+
+ private MeasurementUpdateUtils() {
+ }
+
+ public static List<DataSinkInvocation> getDataLakeSinks(Pipeline pipeline) {
+ return pipeline.getActions()
+ .stream()
+ .filter(MeasurementUpdateUtils::isDataLakeSink)
+ .toList();
+ }
+
+ public static Optional<DataSinkInvocation> getDataLakeSinkById(Pipeline
pipeline, String id) {
+ return pipeline.getActions()
+ .stream()
+ .filter(MeasurementUpdateUtils::isDataLakeSink)
+ .filter(storedSink -> Objects.equals(storedSink.getElementId(), id))
+ .findFirst();
+ }
+
+ public static Set<String> extractMeasureNames(Pipeline pipeline) {
+ return getDataLakeSinks(pipeline)
+ .stream()
+ .map(MeasurementUpdateUtils::extractMeasureName)
+ .flatMap(Optional::stream)
+ .collect(Collectors.toSet());
+ }
+
+ public static Optional<String> extractMeasureName(DataSinkInvocation sink) {
+ return Optional
+ .ofNullable(sink.getStaticProperties())
+ .stream()
+ .flatMap(List::stream)
+ .filter(property ->
DATA_LAKE_MEASUREMENT_FIELD.equals(property.getInternalName()))
+ .filter(FreeTextStaticProperty.class::isInstance)
+ .map(FreeTextStaticProperty.class::cast)
+ .map(FreeTextStaticProperty::getValue)
+ .filter(Objects::nonNull)
+ .findFirst();
+ }
+
+ public static boolean isDataLakeSink(DataSinkInvocation dataSink) {
+ return DATA_LAKE_SINK_APP_ID.equals(dataSink.getAppId());
+ }
+}
diff --git
a/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateManagementTest.java
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateManagementTest.java
new file mode 100644
index 0000000000..e6b1ae5494
--- /dev/null
+++
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/MeasurementUpdateManagementTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.manager.pipeline.update;
+
+import org.apache.streampipes.model.SpDataStream;
+import org.apache.streampipes.model.connect.adapter.ChartSchemaUpdateInfo;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.schema.EventPropertyPrimitive;
+import org.apache.streampipes.model.schema.EventSchema;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import org.apache.streampipes.vocabulary.XSD;
+
+import org.junit.jupiter.api.Test;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MeasurementUpdateManagementTest {
+
+ private static final String DATA_LAKE_SINK_APP_ID =
"org.apache.streampipes.sinks.internal.jvm.datalake";
+ private static final String DATA_LAKE_MEASUREMENT_FIELD = "db_measurement";
+
+ @Test
+ void checkPipelineMigrations_ShouldReturnEmptyListWhenNoWarningsExist() {
+ var chartSchemaUpdateCoordinator =
mock(ChartSchemaUpdateCoordinator.class);
+ var management = new
MeasurementUpdateManagement(chartSchemaUpdateCoordinator);
+ var storedPipeline =
makePipeline(makeDataLakeSink(makeSchema(property("temperature",
XSD.INTEGER))));
+ var updatedPipeline =
makePipeline(makeDataLakeSink(makeSchema(property("temperature", XSD.LONG))));
+
when(chartSchemaUpdateCoordinator.checkChartMigrations(Collections.singleton(any()),
any())).thenReturn(List.of());
+
+ var result = management.checkPipelineMigrations(storedPipeline,
updatedPipeline);
+
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ void
checkPipelineMigrations_ShouldReturnWarningForCriticalMeasurementFieldChange() {
+ var chartSchemaUpdateCoordinator =
mock(ChartSchemaUpdateCoordinator.class);
+ var management = new
MeasurementUpdateManagement(chartSchemaUpdateCoordinator);
+ var storedPipeline =
makePipeline(makeDataLakeSink(makeSchema(property("temperature",
XSD.INTEGER))));
+ var updatedPipeline =
makePipeline(makeDataLakeSink(makeSchema(property("temperature", XSD.STRING))));
+
when(chartSchemaUpdateCoordinator.checkChartMigrations(Collections.singleton(any()),
any())).thenReturn(List.of());
+
+ var result = management.checkPipelineMigrations(storedPipeline,
updatedPipeline);
+
+ assertEquals(1, result.size());
+ assertEquals("measure", result.get(0).getMeasurementName());
+
assertTrue(result.get(0).getCriticalMeasurementFieldChanges().get(0).runtimeName().contains("temperature"));
+ }
+
+ @Test
+ void checkPipelineMigrations_ShouldReturnWarningForAffectedCharts() {
+ var chartSchemaUpdateCoordinator =
mock(ChartSchemaUpdateCoordinator.class);
+ var management = new
MeasurementUpdateManagement(chartSchemaUpdateCoordinator);
+ var storedPipeline =
makePipeline(makeDataLakeSink(makeSchema(property("temperature",
XSD.INTEGER))));
+ var updatedPipeline = makePipeline(makeDataLakeSink(makeSchema()));
+ var chartSchemaUpdateInfo = new ChartSchemaUpdateInfo();
+ chartSchemaUpdateInfo.setChartId("chart-1");
+ chartSchemaUpdateInfo.setChartTitle("Chart");
+ chartSchemaUpdateInfo.setAffectedFields(List.of("temperature"));
+
when(chartSchemaUpdateCoordinator.checkChartMigrations(Collections.singleton(any()),
any())).thenReturn(List.of(chartSchemaUpdateInfo));
+
+ var result = management.checkPipelineMigrations(storedPipeline,
updatedPipeline);
+
+ assertEquals(1, result.size());
+ assertEquals(List.of(chartSchemaUpdateInfo),
result.get(0).getChartSchemaUpdateInfos());
+ }
+
+ private Pipeline makePipeline(DataSinkInvocation dataSink) {
+ var pipeline = new Pipeline();
+ pipeline.setPipelineId("pipeline-1");
+ pipeline.setName("Pipeline");
+ pipeline.setActions(List.of(dataSink));
+ pipeline.setStreams(dataSink.getInputStreams());
+ return pipeline;
+ }
+
+ private DataSinkInvocation makeDataLakeSink(EventSchema eventSchema) {
+ var stream = new SpDataStream();
+ stream.setElementId("stream-1");
+ stream.setEventSchema(eventSchema);
+
+ var sink = new DataSinkInvocation();
+ sink.setElementId("sink-1");
+ sink.setName("Data Lake");
+ sink.setAppId(DATA_LAKE_SINK_APP_ID);
+ sink.setInputStreams(List.of(stream));
+
sink.setStaticProperties(List.of(FreeTextStaticProperty.of(DATA_LAKE_MEASUREMENT_FIELD,
"measure")));
+ return sink;
+ }
+
+ private EventSchema makeSchema(EventPropertyPrimitive... eventProperties) {
+ return new EventSchema(List.of(eventProperties));
+ }
+
+ private EventPropertyPrimitive property(String runtimeName,
+ URI runtimeType) {
+ var property = new EventPropertyPrimitive();
+ property.setRuntimeName(runtimeName);
+ property.setRuntimeType(runtimeType.toString());
+ return property;
+ }
+}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
index 551cf3ec1b..077f0e7a2c 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
@@ -24,9 +24,11 @@ import
org.apache.streampipes.manager.execution.status.PipelineStatusManager;
import org.apache.streampipes.manager.matching.PipelineVerificationHandlerV2;
import org.apache.streampipes.manager.pipeline.PipelineManager;
import
org.apache.streampipes.manager.pipeline.compact.CompactPipelineManagement;
+import
org.apache.streampipes.manager.pipeline.update.MeasurementUpdateManagement;
import org.apache.streampipes.manager.recommender.ElementRecommender;
import org.apache.streampipes.manager.storage.PipelineStorageService;
import org.apache.streampipes.model.client.user.DefaultPrivilege;
+import org.apache.streampipes.model.datalake.MeasurementUpdateInfo;
import org.apache.streampipes.model.message.ErrorMessage;
import org.apache.streampipes.model.message.Message;
import org.apache.streampipes.model.message.Notification;
@@ -83,6 +85,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
private final CompactPipelineManagement compactPipelineManagement;
private final ExtensionServiceRequestManager requestManager;
+ private final MeasurementUpdateManagement measurementUpdateManagement;
public PipelineResource(ExtensionServiceRequestManager requestManager) {
this.compactPipelineManagement = new CompactPipelineManagement(
@@ -90,6 +93,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
requestManager
);
this.requestManager = requestManager;
+ this.measurementUpdateManagement = new MeasurementUpdateManagement();
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@@ -189,6 +193,18 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
return ok(compactPipelineManagement.convertPipeline(pipeline));
}
+ @PutMapping(
+ path = "/{pipelineId}/pipeline-migration-preflight",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @Operation(summary = "Check migration warnings for an existing pipeline
update", tags = {"Pipeline"})
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission(#pipelineId,
'WRITE')")
+ public ResponseEntity<List<MeasurementUpdateInfo>>
performPipelineMigrationPreflight(
+ @PathVariable("pipelineId") String pipelineId,
+ @RequestBody Pipeline pipeline) {
+ return ok(measurementUpdateManagement.checkPipelineMigrations(pipelineId,
pipeline));
+ }
+
@PostMapping(
path = "/recommend/{recId}",
consumes = MediaType.APPLICATION_JSON_VALUE,