This is an automated email from the ASF dual-hosted git repository.

JHoelli pushed a commit to branch marking_pipeline_falsely_as_rewuire_attention
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to 
refs/heads/marking_pipeline_falsely_as_rewuire_attention by this push:
     new 073605ee2f minor
073605ee2f is described below

commit 073605ee2fcbd68792de2aea91f8508d1afb0221
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Mon Nov 2 10:33:18 2026 +0100

    minor
---
 .../CustomTransformOutputSchemaGenerator.java      | 33 ++++++--
 .../pipeline/update/PipelineUpdateCoordinator.java | 41 ++++++++++
 .../CustomTransformOutputSchemaGeneratorTest.java  | 88 +++++++++++++++++++++-
 .../update/PipelineUpdateCoordinatorTest.java      |  8 +-
 4 files changed, 158 insertions(+), 12 deletions(-)

diff --git 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGenerator.java
 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGenerator.java
index 8f8a3da94d..193f4ab98a 100644
--- 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGenerator.java
+++ 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGenerator.java
@@ -17,6 +17,7 @@
  */
 package org.apache.streampipes.manager.matching.output;
 
+import 
org.apache.streampipes.commons.exceptions.NoServiceEndpointsAvailableException;
 import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceOperationResult;
 import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceRequestManager;
 import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceRequestTargets;
@@ -93,8 +94,12 @@ public class CustomTransformOutputSchemaGenerator extends 
OutputSchemaGenerator<
       );
       var response = 
requestManager.request(ExtensionServiceRequests.outputSchema(requestTarget, 
httpRequestBody));
       return handleResponse(response);
-    } catch (Exception e) {
+    } catch (OutputSchemaUnavailableException e) {
+      throw e;
+    } catch (NoServiceEndpointsAvailableException | IOException e) {
       throw new 
OutputSchemaUnavailableException(dataProcessorInvocation.getAppId(), e);
+    } catch (Exception e) {
+      return new EventSchema();
     }
   }
 
@@ -104,15 +109,29 @@ public class CustomTransformOutputSchemaGenerator extends 
OutputSchemaGenerator<
     return 
JacksonSerializer.getObjectMapper().writeValueAsString(requestInvocation);
   }
 
-  private EventSchema handleResponse(ExtensionServiceOperationResult response) 
throws JsonSyntaxException, IOException {
+  private EventSchema handleResponse(ExtensionServiceOperationResult response) 
{
+    if (response == null) {
+      return new EventSchema();
+    }
+
     if (!response.isSuccess()) {
-      throw new IOException("Could not compute output schema, status code: " + 
response.statusCode());
+      var cause = new IOException("Could not compute output schema, status 
code: " + response.statusCode());
+      if (isUnavailableStatus(response.statusCode())) {
+        throw new 
OutputSchemaUnavailableException(dataProcessorInvocation.getAppId(), cause);
+      }
+      return new EventSchema();
     }
 
-    String resp = response.responseBody();
+    try {
+      return JacksonSerializer
+          .getObjectMapper()
+          .readValue(response.responseBody(), EventSchema.class);
+    } catch (IOException | JsonSyntaxException e) {
+      return new EventSchema();
+    }
+  }
 
-    return JacksonSerializer
-        .getObjectMapper()
-        .readValue(resp, EventSchema.class);
+  private boolean isUnavailableStatus(int statusCode) {
+    return statusCode == 502 || statusCode == 503 || statusCode == 504;
   }
 }
diff --git 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
index 469fcbec46..6dadd74ab4 100644
--- 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
+++ 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinator.java
@@ -46,12 +46,14 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Stream;
 
 public class PipelineUpdateCoordinator {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(PipelineUpdateCoordinator.class);
   private static final PipelinesStats PIPELINES_STATS = new PipelinesStats();
+  private static final String DEFERRED_VALIDATION_NOTIFICATION_PREFIX = 
"Validation deferred: ";
 
   private final ExtensionServiceRequestManager requestManager;
   private final ChartSchemaUpdateCoordinator chartSchemaUpdateCoordinator;
@@ -93,6 +95,7 @@ public class PipelineUpdateCoordinator {
 
     affectedPipelines.forEach(pipeline -> {
       var shouldRestartPipeline = pipeline.isRunning();
+      var pipelineStopped = new AtomicBoolean();
       var storedPipeline = 
pipelineManager.getPipeline(pipeline.getPipelineId());
       var updatedPipeline = updatePipeline(
           copyPipeline(storedPipeline), affectedElementId, updatedStreamName, 
updatedEventSchema);
@@ -104,6 +107,8 @@ public class PipelineUpdateCoordinator {
         var modifiedPipeline = 
verificationHandler.makeModifiedPipeline(modificationMessage).pipeline();
         var canAutoMigrate = canAutoMigrate(modificationMessage);
 
+        removeDeferredValidationNotifications(modifiedPipeline);
+
         if (!canAutoMigrate) {
           modifiedPipeline.setHealthStatus(
               requiresMeasurementUpdate(modificationMessage)
@@ -121,6 +126,7 @@ public class PipelineUpdateCoordinator {
 
         if (shouldRestartPipeline) {
           new PipelineExecutor(pipeline, requestManager, 
resourceManager).stopPipeline(true);
+          pipelineStopped.set(true);
           modifiedPipeline.setRunning(false);
           
modifiedPipeline.setRev(pipelineManager.getPipeline(pipeline.getPipelineId()).getRev());
         }
@@ -133,9 +139,17 @@ public class PipelineUpdateCoordinator {
           ).startPipeline();
         }
       } catch (OutputSchemaUnavailableException e) {
+        try {
+          deferPipelineUpdate(storedPipeline, notificationType, e);
+        } catch (Exception notificationException) {
+          LOG.warn("Could not record the deferred pipeline update for {}", 
storedPipeline.getName(), notificationException);
+        }
         LOG.warn("Skipped pipeline update for {} because the output schema for 
{} is unavailable",
             storedPipeline.getName(), e.getAppId(), e);
       } catch (Exception e) {
+        if (shouldRestartPipeline && !pipelineStopped.get()) {
+          new PipelineExecutor(pipeline, requestManager, 
resourceManager).stopPipeline(true);
+        }
         LOG.error("Could not update pipeline {}", updatedPipeline.getName(), 
e);
       }
     });
@@ -205,6 +219,33 @@ public class PipelineUpdateCoordinator {
     return updateInfo;
   }
 
+  private void deferPipelineUpdate(Pipeline pipeline,
+                                   String notificationType,
+                                   OutputSchemaUnavailableException exception) 
{
+    var notification = deferredValidationNotification(notificationType, 
exception.getAppId());
+    var notifications = new ArrayList<>(pipeline.getPipelineNotifications());
+    if (notifications.contains(notification)) {
+      return;
+    }
+
+    notifications.add(notification);
+    pipeline.setPipelineNotifications(notifications);
+    pipelineStorage.updateElement(pipeline);
+  }
+
+  private void removeDeferredValidationNotifications(Pipeline pipeline) {
+    
pipeline.setPipelineNotifications(pipeline.getPipelineNotifications().stream()
+        .filter(notification -> 
!notification.startsWith(DEFERRED_VALIDATION_NOTIFICATION_PREFIX))
+        .toList());
+  }
+
+  private String deferredValidationNotification(String notificationType,
+                                                String appId) {
+    return DEFERRED_VALIDATION_NOTIFICATION_PREFIX
+        + "%s modification could not be validated because pipeline element %s 
is unavailable".formatted(
+            notificationType, appId);
+  }
+
   private Pipeline updatePipeline(Pipeline pipeline,
                                   String affectedElementId,
                                   String updatedStreamName,
diff --git 
a/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGeneratorTest.java
 
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGeneratorTest.java
index 2e35fbef98..bf54707813 100644
--- 
a/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGeneratorTest.java
+++ 
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/matching/output/CustomTransformOutputSchemaGeneratorTest.java
@@ -18,7 +18,9 @@
 
 package org.apache.streampipes.manager.matching.output;
 
+import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceOperationResult;
 import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceRequestManager;
+import 
org.apache.streampipes.model.extensions.svcdiscovery.SpServiceRegistration;
 import org.apache.streampipes.model.graph.DataProcessorInvocation;
 import org.apache.streampipes.model.output.CustomTransformOutputStrategy;
 import org.apache.streampipes.model.staticproperty.SecretStaticProperty;
@@ -29,13 +31,20 @@ import 
org.apache.streampipes.user.management.encryption.SecretEncryptionManager
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 class CustomTransformOutputSchemaGeneratorTest {
 
@@ -68,8 +77,7 @@ class CustomTransformOutputSchemaGeneratorTest {
 
   @Test
   void throwsWhenTheExtensionCannotComputeTheOutputSchema() {
-    var invocation = new DataProcessorInvocation();
-    invocation.setAppId("org.apache.streampipes.test.processor");
+    var invocation = invocation();
     var generator = new CustomTransformOutputSchemaGenerator(
         mock(ISpServiceDiscovery.class),
         new CustomTransformOutputStrategy(),
@@ -83,6 +91,82 @@ class CustomTransformOutputSchemaGeneratorTest {
     assertEquals(invocation.getAppId(), exception.getAppId());
   }
 
+  @Test
+  void returnsAnEmptySchemaWhenTheExtensionReturnsAnErrorResponse() throws 
IOException {
+    var invocation = invocation();
+    var serviceDiscovery = mock(ISpServiceDiscovery.class);
+    var requestManager = mock(ExtensionServiceRequestManager.class);
+    when(serviceDiscovery.getService(anyString(), eq(true), 
anyList())).thenReturn(List.of(service()));
+    when(requestManager.request(any())).thenReturn(new 
ExtensionServiceOperationResult(500, null));
+    var generator = new CustomTransformOutputSchemaGenerator(
+        serviceDiscovery,
+        new CustomTransformOutputStrategy(),
+        invocation,
+        requestManager
+    );
+
+    var result = generator.buildFromOneStream(null);
+
+    assertNotNull(result.k);
+    assertTrue(result.k.getEventProperties().isEmpty());
+  }
+
+  @Test
+  void treatsGatewayUnavailableResponsesAsOutputSchemaUnavailable() throws 
IOException {
+    var invocation = invocation();
+    var serviceDiscovery = mock(ISpServiceDiscovery.class);
+    var requestManager = mock(ExtensionServiceRequestManager.class);
+    when(serviceDiscovery.getService(anyString(), eq(true), 
anyList())).thenReturn(List.of(service()));
+    when(requestManager.request(any())).thenReturn(new 
ExtensionServiceOperationResult(503, null));
+    var generator = new CustomTransformOutputSchemaGenerator(
+        serviceDiscovery,
+        new CustomTransformOutputStrategy(),
+        invocation,
+        requestManager
+    );
+
+    var exception = assertThrows(OutputSchemaUnavailableException.class,
+        () -> generator.buildFromOneStream(null));
+
+    assertEquals(invocation.getAppId(), exception.getAppId());
+  }
+
+  @Test
+  void returnsAnEmptySchemaWhenTheExtensionReturnsAnInvalidSchema() throws 
IOException {
+    var invocation = invocation();
+    var serviceDiscovery = mock(ISpServiceDiscovery.class);
+    var requestManager = mock(ExtensionServiceRequestManager.class);
+    when(serviceDiscovery.getService(anyString(), eq(true), 
anyList())).thenReturn(List.of(service()));
+    when(requestManager.request(any())).thenReturn(new 
ExtensionServiceOperationResult(
+        200, "not-an-event-schema".getBytes(StandardCharsets.UTF_8)));
+    var generator = new CustomTransformOutputSchemaGenerator(
+        serviceDiscovery,
+        new CustomTransformOutputStrategy(),
+        invocation,
+        requestManager
+    );
+
+    var result = generator.buildFromOneStream(null);
+
+    assertNotNull(result.k);
+    assertTrue(result.k.getEventProperties().isEmpty());
+  }
+
+  private DataProcessorInvocation invocation() {
+    var invocation = new DataProcessorInvocation();
+    invocation.setAppId("org.apache.streampipes.test.processor");
+    invocation.setStaticProperties(List.of());
+    return invocation;
+  }
+
+  private SpServiceRegistration service() {
+    var service = new SpServiceRegistration();
+    service.setHost("localhost");
+    service.setPort(8080);
+    service.setSvcId("test-service");
+    return service;
+  }
+
   private SecretStaticProperty makeSecretProperty(String value,
                                                   boolean encrypted) {
     var secretProperty = new SecretStaticProperty("secret", "Secret", "");
diff --git 
a/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinatorTest.java
 
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinatorTest.java
index 1a610a1917..69753f5aff 100644
--- 
a/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinatorTest.java
+++ 
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/pipeline/update/PipelineUpdateCoordinatorTest.java
@@ -58,7 +58,6 @@ import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockConstruction;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -193,7 +192,7 @@ class PipelineUpdateCoordinatorTest {
   }
 
   @Test
-  void updatePipelines_ShouldPreservePipelineWhenOutputSchemaIsUnavailable() {
+  void 
updatePipelines_ShouldDeferPipelineUpdateWhenOutputSchemaIsUnavailable() {
     var requestManager = mock(ExtensionServiceRequestManager.class);
     var resourceManager = mock(SpResourceManager.class);
     var pipelineStorage = mock(IPipelineStorage.class);
@@ -214,10 +213,13 @@ class PipelineUpdateCoordinatorTest {
 
       assertEquals(1, verificationHandlerConstruction.constructed().size());
       assertEquals(0, executorConstruction.constructed().size());
-      verify(pipelineStorage, never()).updateElement(storedPipeline);
+      verify(pipelineStorage).updateElement(storedPipeline);
       assertTrue(storedPipeline.isRunning());
       assertTrue(storedPipeline.isValid());
       assertEquals("Old stream", 
storedPipeline.getStreams().getFirst().getName());
+      assertEquals(List.of(
+          "Validation deferred: Data stream modification could not be 
validated because pipeline element math-expression is unavailable"
+      ), storedPipeline.getPipelineNotifications());
     }
   }
 

Reply via email to