This is an automated email from the ASF dual-hosted git repository.
bossenti pushed a commit to branch
2045-implement-migration-for-adapter-and-pipeline-element-configurations
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/2045-implement-migration-for-adapter-and-pipeline-element-configurations
by this push:
new 10375f3bb Improvements to extension migration (#2101)
10375f3bb is described below
commit 10375f3bb029e75091b4f034b5bebf647fcd48cb
Author: Dominik Riemer <[email protected]>
AuthorDate: Mon Oct 30 07:50:38 2023 +0100
Improvements to extension migration (#2101)
* Extract MigrationResource logic into smaller units
* Use single request for submitting migrations from extensions to core
* refactor: remove dead code
* style: change formatting
---------
Co-authored-by: bossenti <[email protected]>
---
.../apache/streampipes/client/api/IAdminApi.java | 4 +-
.../apache/streampipes/client/api/AdminApi.java | 18 +-
.../management/AdapterMigrationManager.java | 113 +++++
.../migration/AbstractMigrationManager.java | 155 +++++++
.../manager/migration/IMigrationHandler.java | 26 +-
.../manager/migration/MigrationUtils.java | 73 +++
.../migration/PipelineElementMigrationManager.java | 249 ++++++++++
.../manager/migration/MigrationUtilsTest.java | 12 +-
.../rest/impl/admin/MigrationResource.java | 510 ++-------------------
.../extensions/ExtensionsModelSubmitter.java | 26 +-
10 files changed, 645 insertions(+), 541 deletions(-)
diff --git
a/streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
b/streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
index 35047ca9e..ba776ce51 100644
---
a/streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
+++
b/streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
@@ -40,9 +40,7 @@ public interface IAdminApi {
void deregisterFunction(String functionId);
- void registerAdapterMigrations(List<ModelMigratorConfig> migrationConfigs,
String serviceId);
-
- void registerPipelineElementMigrations(List<ModelMigratorConfig>
migratorConfigs, String serviceId);
+ void registerMigrations(List<ModelMigratorConfig> migrationConfigs, String
serviceId);
MessagingSettings getMessagingSettings();
}
diff --git
a/streampipes-client/src/main/java/org/apache/streampipes/client/api/AdminApi.java
b/streampipes-client/src/main/java/org/apache/streampipes/client/api/AdminApi.java
index 0670d9448..970eb6224 100644
---
a/streampipes-client/src/main/java/org/apache/streampipes/client/api/AdminApi.java
+++
b/streampipes-client/src/main/java/org/apache/streampipes/client/api/AdminApi.java
@@ -72,12 +72,8 @@ public class AdminApi extends AbstractClientApi implements
IAdminApi {
* @param migrationConfigs list of migration configs to be registered
*/
@Override
- public void registerAdapterMigrations(List<ModelMigratorConfig>
migrationConfigs, String serviceId) {
- post(getAdapterMigrationPath().addToPath(serviceId), migrationConfigs);
- }
-
- public void registerPipelineElementMigrations(List<ModelMigratorConfig>
migratorConfigs, String serviceId) {
- post(getPipelineElementMigrationPath().addToPath(serviceId),
migratorConfigs);
+ public void registerMigrations(List<ModelMigratorConfig> migrationConfigs,
String serviceId) {
+ post(getMigrationPath().addToPath(serviceId), migrationConfigs);
}
@Override
@@ -113,15 +109,9 @@ public class AdminApi extends AbstractClientApi implements
IAdminApi {
return getFunctionsPath().addToPath(functionId);
}
- private StreamPipesApiPath getAdapterMigrationPath() {
- return StreamPipesApiPath
- .fromBaseApiPath()
- .addToPath("migrations/adapter");
- }
-
- private StreamPipesApiPath getPipelineElementMigrationPath() {
+ private StreamPipesApiPath getMigrationPath() {
return StreamPipesApiPath
.fromBaseApiPath()
- .addToPath("migrations/pipeline-element");
+ .addToPath("migrations");
}
}
diff --git
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMigrationManager.java
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMigrationManager.java
new file mode 100644
index 000000000..26f158a5b
--- /dev/null
+++
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMigrationManager.java
@@ -0,0 +1,113 @@
+/*
+ * 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.connect.management.management;
+
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
+import org.apache.streampipes.manager.migration.AbstractMigrationManager;
+import org.apache.streampipes.manager.migration.IMigrationHandler;
+import
org.apache.streampipes.model.extensions.svcdiscovery.SpServiceRegistration;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.storage.api.IAdapterStorage;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class AdapterMigrationManager extends AbstractMigrationManager
implements IMigrationHandler {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AdapterMigrationManager.class);
+
+ private final IAdapterStorage adapterStorage;
+
+ public AdapterMigrationManager(IAdapterStorage adapterStorage) {
+ this.adapterStorage = adapterStorage;
+ }
+
+ @Override
+ public void handleMigrations(SpServiceRegistration extensionsServiceConfig,
+ List<ModelMigratorConfig> migrationConfigs) {
+
+ LOG.info("Received {} migrations from extension service {}.",
+ migrationConfigs.size(),
+ extensionsServiceConfig.getServiceUrl());
+ LOG.info("Updating adapter descriptions by replacement...");
+ updateDescriptions(migrationConfigs,
extensionsServiceConfig.getServiceUrl());
+ LOG.info("Adapter descriptions are up to date.");
+
+ LOG.info("Checking migrations for existing adapters in StreamPipes Core
...");
+ for (var migrationConfig : migrationConfigs) {
+ LOG.info("Searching for assets of '{}'", migrationConfig.targetAppId());
+ LOG.debug("Searching for assets of '{}' with config {}",
migrationConfig.targetAppId(), migrationConfig);
+ var adapterDescriptions =
adapterStorage.getAdaptersByAppId(migrationConfig.targetAppId());
+ LOG.info("Found {} instances for appId '{}'",
adapterDescriptions.size(), migrationConfig.targetAppId());
+ for (var adapterDescription : adapterDescriptions) {
+
+ var adapterVersion = adapterDescription.getVersion();
+
+ if (adapterVersion == migrationConfig.fromVersion()) {
+ LOG.info("Migration is required for adapter '{}'. Migrating from
version '{}' to '{}' ...",
+ adapterDescription.getElementId(),
+ adapterVersion, migrationConfig.toVersion()
+ );
+
+ var migrationResult = performMigration(
+ adapterDescription,
+ migrationConfig,
+ String.format("%s/%s/adapter",
+ extensionsServiceConfig.getServiceUrl(),
+ MIGRATION_ENDPOINT
+ )
+ );
+
+ if (migrationResult.success()) {
+ LOG.info("Migration successfully performed by extensions service.
Updating adapter description ...");
+ LOG.debug(
+ "Migration was performed by extensions service '{}'",
+ extensionsServiceConfig.getServiceUrl());
+
+ adapterStorage.updateAdapter(migrationResult.element());
+ LOG.info("Adapter description is updated - Migration successfully
completed at Core.");
+ } else {
+ LOG.error("Migration failed with the following reason: {}",
migrationResult.message());
+ LOG.error(
+ "Migration for adapter '{}' failed - Stopping adapter ...",
+ migrationResult.element().getElementId()
+ );
+ try {
+
WorkerRestClient.stopStreamAdapter(extensionsServiceConfig.getServiceUrl(),
adapterDescription);
+ } catch (AdapterException e) {
+ LOG.error("Stopping adapter failed: {}",
StringUtils.join(e.getStackTrace(), "\n"));
+ }
+ LOG.info("Adapter successfully stopped.");
+ }
+ } else {
+ LOG.info(
+ "Migration is not applicable for adapter '{}' because of a
version mismatch - "
+ + "adapter version: '{}', migration starts at: '{}'",
+ adapterDescription.getElementId(),
+ adapterVersion,
+ migrationConfig.fromVersion()
+ );
+ }
+ }
+ }
+ }
+}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/AbstractMigrationManager.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/AbstractMigrationManager.java
new file mode 100644
index 000000000..3641f0b7c
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/AbstractMigrationManager.java
@@ -0,0 +1,155 @@
+/*
+ * 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.migration;
+
+import org.apache.streampipes.commons.exceptions.SepaParseException;
+import org.apache.streampipes.manager.endpoint.HttpJsonParser;
+import org.apache.streampipes.manager.execution.ExtensionServiceExecutions;
+import org.apache.streampipes.manager.operations.Operations;
+import org.apache.streampipes.model.base.VersionedNamedStreamPipesEntity;
+import org.apache.streampipes.model.extensions.migration.MigrationRequest;
+import org.apache.streampipes.model.message.Notification;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.serializers.json.JacksonSerializer;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static
org.apache.streampipes.manager.migration.MigrationUtils.getRequestUrl;
+
+public abstract class AbstractMigrationManager {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AbstractMigrationManager.class);
+
+ protected static final String MIGRATION_ENDPOINT = "api/v1/migrations";
+
+ /**
+ * Performs the actual migration of a pipeline element.
+ * This includes the communication with the extensions service which runs
the migration.
+ *
+ * @param pipelineElement pipeline element to be migrated
+ * @param migrationConfig config of the migration to be performed
+ * @param url url of the migration endpoint at the extensions
service
+ * where the migration should be performed
+ * @param <T> type of the processing element
+ * @return result of the migration
+ */
+ protected <T extends VersionedNamedStreamPipesEntity> MigrationResult<T>
performMigration(
+ T pipelineElement,
+ ModelMigratorConfig migrationConfig,
+ String url
+ ) {
+
+ try {
+
+ var migrationRequest = new MigrationRequest<>(pipelineElement,
migrationConfig);
+
+ String serializedRequest =
JacksonSerializer.getObjectMapper().writeValueAsString(migrationRequest);
+
+ var migrationResponse = ExtensionServiceExecutions.extServicePostRequest(
+ url,
+ serializedRequest
+ ).execute();
+
+ TypeReference<MigrationResult<T>> typeReference = new TypeReference<>() {
+ };
+
+ return JacksonSerializer
+ .getObjectMapper()
+ .readValue(migrationResponse.returnContent().asString(),
typeReference);
+ } catch (JsonProcessingException e) {
+ LOG.error(
+ "Migration of pipeline element failed before sending to the
extensions service, "
+ + "pipeline element is not migrated. Serialization of migration
request failed: {}",
+ StringUtils.join(e.getStackTrace(), "\n")
+ );
+ } catch (IOException e) {
+ LOG.error("Migration of pipeline element failed at the extensions
service, pipeline element is not migrated: {}.",
+ StringUtils.join(e.getStackTrace(), "\n")
+ );
+ }
+ return MigrationResult.failure(pipelineElement, "Internal error during
migration at StreamPipes Core");
+ }
+
+ /**
+ * Update all descriptions of entities in the Core that are affected by
migrations.
+ *
+ * @param migrationConfigs List of migrations to take in account
+ * @param serviceUrl Url of the extension service that provides the
migrations.
+ */
+ protected void updateDescriptions(List<ModelMigratorConfig>
migrationConfigs, String serviceUrl) {
+ migrationConfigs
+ .stream()
+ .collect(
+ // We only need to update the description once per appId,
+ // because this is directly done with the newest version of the
description and
+ // there is iterative migration required.
+ // To avoid unnecessary, multiple updates,
+ // we filter the migration configs such that every appId is unique.
+ // This ensures that every description is only updated once.
+ Collectors.toMap(
+ ModelMigratorConfig::targetAppId,
+ Function.identity(),
+ (existing, replacement) -> existing
+ )
+ )
+ .values()
+ .stream()
+ .peek(config -> {
+ var requestUrl = getRequestUrl(config.modelType(),
config.targetAppId(), serviceUrl);
+ performUpdate(requestUrl);
+ })
+ .toList();
+ }
+
+ /**
+ * Perform the update of the description based on the given requestUrl
+ *
+ * @param requestUrl URl that references the description to be updated at
the extensions service.
+ */
+ protected void performUpdate(String requestUrl) {
+
+ try {
+ var entityPayload =
HttpJsonParser.getContentFromUrl(URI.create(requestUrl));
+ var updateResult = Operations.verifyAndUpdateElement(entityPayload);
+ if (!updateResult.isSuccess()) {
+ LOG.error(
+ "Updating the pipeline element description failed: {}",
+ StringUtils.join(
+
updateResult.getNotifications().stream().map(Notification::toString).toList(),
+ "\n")
+ );
+ }
+ } catch (IOException | SepaParseException e) {
+ LOG.error("Updating the pipeline element description failed due to the
following exception:\n{}",
+ StringUtils.join(e.getStackTrace(), "\n")
+ );
+ }
+ }
+}
diff --git
a/streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/IMigrationHandler.java
similarity index 51%
copy from
streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
copy to
streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/IMigrationHandler.java
index 35047ca9e..7847e2432 100644
---
a/streampipes-client-api/src/main/java/org/apache/streampipes/client/api/IAdminApi.java
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/IMigrationHandler.java
@@ -16,33 +16,15 @@
*
*/
-package org.apache.streampipes.client.api;
+package org.apache.streampipes.manager.migration;
-import org.apache.streampipes.model.configuration.MessagingSettings;
-import
org.apache.streampipes.model.extensions.configuration.SpServiceConfiguration;
import
org.apache.streampipes.model.extensions.svcdiscovery.SpServiceRegistration;
-import org.apache.streampipes.model.function.FunctionDefinition;
import org.apache.streampipes.model.migration.ModelMigratorConfig;
import java.util.List;
-public interface IAdminApi {
+public interface IMigrationHandler {
- void registerService(SpServiceRegistration serviceRegistration);
-
- void deregisterService(String serviceId);
-
- void registerServiceConfiguration(SpServiceConfiguration
serviceConfiguration);
-
- SpServiceConfiguration getServiceConfiguration(String serviceGroup);
-
- void registerFunctions(List<FunctionDefinition> functions);
-
- void deregisterFunction(String functionId);
-
- void registerAdapterMigrations(List<ModelMigratorConfig> migrationConfigs,
String serviceId);
-
- void registerPipelineElementMigrations(List<ModelMigratorConfig>
migratorConfigs, String serviceId);
-
- MessagingSettings getMessagingSettings();
+ void handleMigrations(SpServiceRegistration serviceRegistration,
+ List<ModelMigratorConfig> migrationConfigs);
}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/MigrationUtils.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/MigrationUtils.java
new file mode 100644
index 000000000..8a96c3c1f
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/MigrationUtils.java
@@ -0,0 +1,73 @@
+/*
+ * 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.migration;
+
+import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
+
+import java.util.List;
+import java.util.Optional;
+
+public class MigrationUtils {
+
+ /**
+ * Filter the application migration for a pipeline definition.
+ * By definition, there is only one migration config that fulfills the
requirements.
+ * Otherwise, it should have been detected as duplicate by the extensions
service.
+ *
+ * @param pipelineElement pipeline element that should be migrated
+ * @param migrationConfigs available migration configs to pick the
applicable from
+ * @return config that is applicable for the given pipeline element
+ */
+ public static Optional<ModelMigratorConfig> getApplicableMigration(
+ InvocableStreamPipesEntity pipelineElement,
+ List<ModelMigratorConfig> migrationConfigs
+ ) {
+ return migrationConfigs
+ .stream()
+ .filter(
+ config ->
config.modelType().equals(pipelineElement.getServiceTagPrefix())
+ && config.targetAppId().equals(pipelineElement.getAppId())
+ && config.fromVersion() == pipelineElement.getVersion()
+ )
+ .findFirst();
+ }
+
+ /**
+ * Get the URL that provides the description for an entity.
+ *
+ * @param entityType Type of the entity to be updated.
+ * @param appId AppId of the entity to be updated
+ * @param serviceUrl URL of the extensions service to which the entity
belongs
+ * @return URL of the endpoint that provides the description for the given
entity
+ */
+ public static String getRequestUrl(SpServiceTagPrefix entityType, String
appId, String serviceUrl) {
+
+ SpServiceUrlProvider urlProvider;
+ switch (entityType) {
+ case ADAPTER -> urlProvider = SpServiceUrlProvider.ADAPTER;
+ case DATA_PROCESSOR -> urlProvider = SpServiceUrlProvider.DATA_PROCESSOR;
+ case DATA_SINK -> urlProvider = SpServiceUrlProvider.DATA_SINK;
+ default -> throw new RuntimeException("Unexpected instance type.");
+ }
+ return urlProvider.getInvocationUrl(serviceUrl, appId);
+ }
+}
diff --git
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/PipelineElementMigrationManager.java
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/PipelineElementMigrationManager.java
new file mode 100644
index 000000000..28338e1ac
--- /dev/null
+++
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/migration/PipelineElementMigrationManager.java
@@ -0,0 +1,249 @@
+/*
+ * 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.migration;
+
+import org.apache.streampipes.manager.execution.PipelineExecutor;
+import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
+import
org.apache.streampipes.model.extensions.svcdiscovery.SpServiceRegistration;
+import org.apache.streampipes.model.graph.DataProcessorInvocation;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.pipeline.PipelineHealthStatus;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.storage.api.IDataProcessorStorage;
+import org.apache.streampipes.storage.api.IDataSinkStorage;
+import org.apache.streampipes.storage.api.IPipelineStorage;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static
org.apache.streampipes.manager.migration.MigrationUtils.getApplicableMigration;
+
+public class PipelineElementMigrationManager extends AbstractMigrationManager
implements IMigrationHandler {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(PipelineElementMigrationManager.class);
+
+ private final IPipelineStorage pipelineStorage;
+ private final IDataProcessorStorage dataProcessorStorage;
+ private final IDataSinkStorage dataSinkStorage;
+
+ public PipelineElementMigrationManager(IPipelineStorage pipelineStorage,
+ IDataProcessorStorage
dataProcessorStorage,
+ IDataSinkStorage dataSinkStorage) {
+ this.pipelineStorage = pipelineStorage;
+ this.dataProcessorStorage = dataProcessorStorage;
+ this.dataSinkStorage = dataSinkStorage;
+ }
+
+ @Override
+ public void handleMigrations(SpServiceRegistration extensionsServiceConfig,
+ List<ModelMigratorConfig> migrationConfigs) {
+
+ LOG.info("Updating pipeline element descriptions by replacement...");
+ updateDescriptions(migrationConfigs,
extensionsServiceConfig.getServiceUrl());
+ LOG.info("Pipeline element descriptions are up to date.");
+
+ LOG.info("Received {} pipeline element migrations from extension service
{}.",
+ migrationConfigs.size(),
+ extensionsServiceConfig.getServiceUrl());
+ var availablePipelines = pipelineStorage.getAllPipelines();
+ if (!availablePipelines.isEmpty()) {
+ LOG.info("Found {} available pipelines. Checking pipelines for
applicable migrations...",
+ availablePipelines.size()
+ );
+ }
+
+ for (var pipeline : availablePipelines) {
+ List<MigrationResult<?>> failedMigrations = new ArrayList<>();
+
+ var migratedDataProcessors = pipeline.getSepas()
+ .stream()
+ .map(processor -> {
+ if (getApplicableMigration(processor,
migrationConfigs).isPresent()) {
+ return migratePipelineElement(
+ processor,
+ migrationConfigs,
+ String.format("%s/%s/processor",
+ extensionsServiceConfig.getServiceUrl(),
+ MIGRATION_ENDPOINT
+ ),
+ failedMigrations
+ );
+ } else {
+ LOG.info("No migration applicable for data processor '{}'.",
processor.getElementId());
+ return processor;
+ }
+ })
+ .toList();
+ pipeline.setSepas(migratedDataProcessors);
+
+ var migratedDataSinks = pipeline.getActions()
+ .stream()
+ .map(sink -> {
+ if (getApplicableMigration(sink, migrationConfigs).isPresent()) {
+ return migratePipelineElement(
+ sink,
+ migrationConfigs,
+ String.format("%s/%s/sink",
+ extensionsServiceConfig.getServiceUrl(),
+ MIGRATION_ENDPOINT
+ ),
+ failedMigrations
+ );
+ } else {
+ LOG.info("No migration applicable for data sink '{}'.",
sink.getElementId());
+ return sink;
+ }
+ })
+ .toList();
+ pipeline.setActions(migratedDataSinks);
+
+ pipelineStorage.updatePipeline(pipeline);
+
+ if (failedMigrations.isEmpty()) {
+ LOG.info("Migration for pipeline successfully completed.");
+ } else {
+ // pass most recent version of pipeline
+
handleFailedMigrations(pipelineStorage.getPipeline(pipeline.getPipelineId()),
failedMigrations);
+ }
+ }
+ }
+
+ /**
+ * Takes care about the failed migrations of pipeline elements.
+ * This includes the following steps:
+ * <ul>
+ * <li> logging of failed pipeline elements
+ * <li> setting migration results as pipeline notifications
+ * <li> updating pipeline health status
+ * <li> stopping the pipeline
+ * </ul>
+ *
+ * @param pipeline the pipeline affected by failed migrations
+ * @param failedMigrations the list of failed migrations
+ */
+ protected void handleFailedMigrations(Pipeline pipeline,
List<MigrationResult<?>> failedMigrations) {
+ LOG.error("Failures in migration detected - The following pipeline
elements could to be migrated:\n"
+ +
StringUtils.join(failedMigrations.stream().map(Record::toString).toList()),
"\n");
+
+ pipeline.setPipelineNotifications(failedMigrations.stream().map(
+ failedMigration -> "Failed migration of pipeline element:
%s".formatted(failedMigration.message())
+ ).toList());
+ pipeline.setHealthStatus(PipelineHealthStatus.REQUIRES_ATTENTION);
+
+ pipelineStorage.updatePipeline(pipeline);
+
+ // get updated version of pipeline after modification
+ pipeline = pipelineStorage.getPipeline(pipeline.getPipelineId());
+
+ stopPipeline(pipeline);
+ }
+
+
+ public void stopPipeline(Pipeline pipeline) {
+ var pipelineExecutor = new PipelineExecutor(pipeline, true);
+ var pipelineStopResult = pipelineExecutor.stopPipeline();
+
+ if (pipelineStopResult.isSuccess()) {
+ LOG.info("Pipeline successfully stopped.");
+ } else {
+ LOG.error("Pipeline stop failed.");
+ }
+ }
+
+ /**
+ * Handle the migration of a pipeline element with respect to the given
model migration configs.
+ * All applicable migrations found in the provided configs are executed for
the given pipeline element.
+ * In case a migration fails, the related pipeline element receives the
latest definition of its static properties,
+ * so that the pipeline element can be adapted by the user to resolve the
failed migration.
+ *
+ * @param pipelineElement pipeline element to be migrated
+ * @param modelMigrations list of model migrations that might be applicable
for this pipeline element
+ * @param url url of the extensions service endpoint that
handles the migration
+ * @param failedMigrations collection of failed migrations which is extended
by occurring migration failures
+ * @param <T> type of the pipeline element (e.g.,
DataProcessorInvocation)
+ * @return the migrated (or - in case of a failure - updated) pipeline
element
+ */
+ protected <T extends InvocableStreamPipesEntity> T migratePipelineElement(
+ T pipelineElement,
+ List<ModelMigratorConfig> modelMigrations,
+ String url,
+ List<MigrationResult<?>> failedMigrations
+ ) {
+
+ // loop until no migrations are available anymore
+ // this allows to apply multiple migrations for a pipeline element
sequentially
+ // For example, first migration from 0 to 1 and the second migration from
1 to 2
+ while (getApplicableMigration(pipelineElement,
modelMigrations).isPresent() && failedMigrations.isEmpty()) {
+
+ var migrationConfig = getApplicableMigration(pipelineElement,
modelMigrations).get();
+ LOG.info(
+ "Found applicable migration for pipeline element '{}': {}",
+ pipelineElement.getElementId(),
+ migrationConfig
+ );
+
+ var migrationResult = performMigration(
+ pipelineElement,
+ migrationConfig,
+ url
+ );
+
+ if (migrationResult.success()) {
+ LOG.info("Migration successfully performed by extensions service.
Updating pipeline element invocation ...");
+ LOG.debug("Migration was performed at extensions service endpoint
'{}'", url);
+ pipelineElement = migrationResult.element();
+ } else {
+ LOG.error("Migration failed with the following reason: {}",
migrationResult.message());
+ failedMigrations.add(migrationResult);
+ }
+ }
+ if (!failedMigrations.isEmpty()) {
+ updateFailedPipelineElement(pipelineElement);
+ LOG.info("Updated pipeline elements with new description where automatic
migration failed.");
+ }
+ return pipelineElement;
+ }
+
+ /**
+ * Update the static properties of the failed pipeline element with its
description.
+ * This allows to adapt the failed pipeline element in the UI to overcome
the failed migration.
+ *
+ * @param pipelineElement pipeline element with failed migration
+ */
+ protected void updateFailedPipelineElement(InvocableStreamPipesEntity
pipelineElement) {
+ List<StaticProperty> updatedStaticProperties = new ArrayList<>();
+ if (pipelineElement instanceof DataProcessorInvocation) {
+ updatedStaticProperties = dataProcessorStorage
+ .getFirstDataProcessorByAppId(pipelineElement.getAppId())
+ .getStaticProperties();
+ } else if (pipelineElement instanceof DataSinkInvocation) {
+ updatedStaticProperties = dataSinkStorage
+ .getFirstDataSinkByAppId(pipelineElement.getAppId())
+ .getStaticProperties();
+ }
+ pipelineElement.setStaticProperties(updatedStaticProperties);
+ }
+}
diff --git
a/streampipes-rest/src/test/java/org/apache/streampipes/rest/impl/admin/MigrationResourceTest.java
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/migration/MigrationUtilsTest.java
similarity index 83%
rename from
streampipes-rest/src/test/java/org/apache/streampipes/rest/impl/admin/MigrationResourceTest.java
rename to
streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/migration/MigrationUtilsTest.java
index 63f0b92a7..05545db19 100644
---
a/streampipes-rest/src/test/java/org/apache/streampipes/rest/impl/admin/MigrationResourceTest.java
+++
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/migration/MigrationUtilsTest.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.streampipes.rest.impl.admin;
+package org.apache.streampipes.manager.migration;
import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
import org.apache.streampipes.model.graph.DataProcessorInvocation;
@@ -30,7 +30,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-public class MigrationResourceTest {
+public class MigrationUtilsTest {
List<ModelMigratorConfig> migrationConfigs = List.of(
new ModelMigratorConfig("app-id",
SpServiceTagPrefix.DATA_PROCESSOR, 0, 1),
@@ -60,18 +60,18 @@ public class MigrationResourceTest {
assertEquals(
migrationConfigs.get(0),
- MigrationResource.getApplicableMigration(pipelineElement1,
migrationConfigs).get()
+ MigrationUtils.getApplicableMigration(pipelineElement1,
migrationConfigs).get()
);
assertEquals(
migrationConfigs.get(1),
- MigrationResource.getApplicableMigration(pipelineElement2,
migrationConfigs).get()
+ MigrationUtils.getApplicableMigration(pipelineElement2,
migrationConfigs).get()
);
assertEquals(
migrationConfigs.get(2),
- MigrationResource.getApplicableMigration(pipelineElement3,
migrationConfigs).get()
+ MigrationUtils.getApplicableMigration(pipelineElement3,
migrationConfigs).get()
);
assertTrue(
- MigrationResource.getApplicableMigration(pipelineElement4,
migrationConfigs).isEmpty()
+ MigrationUtils.getApplicableMigration(pipelineElement4,
migrationConfigs).isEmpty()
);
}
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/MigrationResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/MigrationResource.java
index 7342bcc21..13c87875f 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/MigrationResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/MigrationResource.java
@@ -18,45 +18,24 @@
package org.apache.streampipes.rest.impl.admin;
-import org.apache.streampipes.commons.exceptions.SepaParseException;
-import org.apache.streampipes.commons.exceptions.connect.AdapterException;
-import org.apache.streampipes.connect.management.management.WorkerRestClient;
-import org.apache.streampipes.manager.execution.ExtensionServiceExecutions;
-import org.apache.streampipes.manager.execution.PipelineExecutor;
-import org.apache.streampipes.manager.operations.Operations;
-import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
-import org.apache.streampipes.model.base.VersionedNamedStreamPipesEntity;
-import org.apache.streampipes.model.extensions.migration.MigrationRequest;
+import
org.apache.streampipes.connect.management.management.AdapterMigrationManager;
+import
org.apache.streampipes.manager.migration.PipelineElementMigrationManager;
import
org.apache.streampipes.model.extensions.svcdiscovery.SpServiceRegistration;
import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
-import org.apache.streampipes.model.graph.DataProcessorInvocation;
-import org.apache.streampipes.model.graph.DataSinkInvocation;
-import org.apache.streampipes.model.message.Notification;
-import org.apache.streampipes.model.migration.MigrationResult;
import org.apache.streampipes.model.migration.ModelMigratorConfig;
-import org.apache.streampipes.model.pipeline.Pipeline;
-import org.apache.streampipes.model.pipeline.PipelineHealthStatus;
-import org.apache.streampipes.model.staticproperty.StaticProperty;
import
org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
import org.apache.streampipes.rest.security.AuthConstants;
-import org.apache.streampipes.serializers.json.JacksonSerializer;
import org.apache.streampipes.storage.api.CRUDStorage;
import org.apache.streampipes.storage.api.IAdapterStorage;
import org.apache.streampipes.storage.api.IDataProcessorStorage;
import org.apache.streampipes.storage.api.IDataSinkStorage;
import org.apache.streampipes.storage.api.IPipelineStorage;
-import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@@ -67,24 +46,15 @@ import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Collectors;
@Path("v2/migrations")
@Component
@PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
public class MigrationResource extends AbstractAuthGuardedRestResource {
- private static final Logger LOG =
LoggerFactory.getLogger(MigrationResource.class);
- private static final String MIGRATION_ENDPOINT = "api/v1/migrations";
-
private final CRUDStorage<String, SpServiceRegistration>
extensionsServiceStorage =
- getNoSqlStorage().getExtensionsServiceStorage();
+ getNoSqlStorage().getExtensionsServiceStorage();
private final IAdapterStorage adapterStorage =
getNoSqlStorage().getAdapterInstanceStorage();
@@ -94,457 +64,51 @@ public class MigrationResource extends
AbstractAuthGuardedRestResource {
private final IPipelineStorage pipelineStorage =
getNoSqlStorage().getPipelineStorageAPI();
@POST
- @Path("adapter/{serviceId}")
+ @Path("{serviceId}")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(
- summary = "Migrate adapters based on migration configs", tags =
{"Core", "Migration"},
- responses = {
- @ApiResponse(
- responseCode = "" + HttpStatus.SC_OK,
- description = "All provided migrations are handled. If
an error appeared, "
- + "the corresponding actions are taken.")
- }
- )
- public Response registerAdapterMigrations(
- @Parameter(
- in = ParameterIn.PATH,
- description = "the id of the extensions service that
requests migrations",
- required = true
- )
- @PathParam("serviceId") String serviceId,
- @Parameter(
- description = "list of configs (ModelMigratorConfig) that
describe the requested migrations",
- required = true
- )
- List<ModelMigratorConfig> migrationConfigs) {
-
- var extensionsServiceConfig =
extensionsServiceStorage.getElementById(serviceId);
-
- LOG.info("Received {} migrations from extension service {}.",
- migrationConfigs.size(),
- extensionsServiceConfig.getServiceUrl());
- LOG.info("Updating adapter descriptions by replacement...");
- updateDescriptions(migrationConfigs,
extensionsServiceConfig.getServiceUrl());
- LOG.info("Adapter descriptions are up to date.");
-
- LOG.info("Checking migrations for existing adapters in StreamPipes Core
...");
- for (var migrationConfig : migrationConfigs) {
- LOG.info("Searching for assets of '{}'", migrationConfig.targetAppId());
- LOG.debug("Searching for assets of '{}' with config {}",
migrationConfig.targetAppId(), migrationConfig);
- var adapterDescriptions =
adapterStorage.getAdaptersByAppId(migrationConfig.targetAppId());
- LOG.info("Found {} instances for appId '{}'",
adapterDescriptions.size(), migrationConfig.targetAppId());
- for (var adapterDescription : adapterDescriptions) {
-
- var adapterVersion = adapterDescription.getVersion();
-
- if (adapterVersion == migrationConfig.fromVersion()) {
- LOG.info("Migration is required for adapter '{}'. Migrating from
version '{}' to '{}' ...",
- adapterDescription.getElementId(),
- adapterVersion, migrationConfig.toVersion()
- );
-
- var migrationResult = performMigration(
- adapterDescription,
- migrationConfig,
- String.format("%s/%s/adapter",
- extensionsServiceConfig.getServiceUrl(),
- MIGRATION_ENDPOINT
- )
- );
-
- if (migrationResult.success()) {
- LOG.info("Migration successfully performed by extensions service.
Updating adapter description ...");
- LOG.debug(
- "Migration was performed by extensions service '{}'",
- extensionsServiceConfig.getServiceUrl());
-
- adapterStorage.updateAdapter(migrationResult.element());
- LOG.info("Adapter description is updated - Migration successfully
completed at Core.");
- } else {
- LOG.error("Migration failed with the following reason: {}",
migrationResult.message());
- LOG.error(
- "Migration for adapter '{}' failed - Stopping adapter ...",
- migrationResult.element().getElementId()
- );
- try {
-
WorkerRestClient.stopStreamAdapter(extensionsServiceConfig.getServiceUrl(),
adapterDescription);
- } catch (AdapterException e) {
- LOG.error("Stopping adapter failed: {}",
StringUtils.join(e.getStackTrace(), "\n"));
- }
- LOG.info("Adapter successfully stopped.");
- }
- } else {
- LOG.info(
- "Migration is not applicable for adapter '{}' because of a
version mismatch - "
- + "adapter version: '{}', migration starts at:
'{}'",
- adapterDescription.getElementId(),
- adapterVersion,
- migrationConfig.fromVersion()
- );
- }
+ summary = "Migrate adapters and pipeline elements based on migration
configs", tags = {"Core", "Migration"},
+ responses = {
+ @ApiResponse(
+ responseCode = "" + HttpStatus.SC_OK,
+ description = "All provided migrations are handled. If an error
appeared, "
+ + "the corresponding actions are taken.")
}
- }
- return ok();
- }
-
- @POST
- @Path("pipeline-element/{serviceId}")
- @Consumes(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Migrate pipeline elements based on migration configs",
tags = {"Core", "Migration"},
- responses = {
- @ApiResponse(
- responseCode = "200" + HttpStatus.SC_OK,
- description = "All provided migrations are handled. "
- + "If an error appeared, the corresponding
actions are taken."
- )
- }
)
- public Response registerPipelineElementMigrations(
- @Parameter(
- in = ParameterIn.PATH,
- description = "the id of the extensions service that
requests migrations",
- required = true
- )
- @PathParam("serviceId") String serviceId,
- @Parameter(
- description = "list of config that describe the requested
migrations"
- )
- List<ModelMigratorConfig> migrationConfigs) {
+ public Response performMigrations(
+ @Parameter(
+ in = ParameterIn.PATH,
+ description = "the id of the extensions service that requests
migrations",
+ required = true
+ )
+ @PathParam("serviceId") String serviceId,
+ @Parameter(
+ description = "list of configs (ModelMigratorConfig) that describe
the requested migrations",
+ required = true
+ )
+ List<ModelMigratorConfig> migrationConfigs) {
var extensionsServiceConfig =
extensionsServiceStorage.getElementById(serviceId);
- LOG.info("Updating pipeline element descriptions by replacement...");
- updateDescriptions(migrationConfigs,
extensionsServiceConfig.getServiceUrl());
- LOG.info("Pipeline element descriptions are up to date.");
-
- LOG.info("Received {} pipeline element migrations from extension service
{}.",
- migrationConfigs.size(),
- extensionsServiceConfig.getServiceUrl());
- var availablePipelines = pipelineStorage.getAllPipelines();
- if (!availablePipelines.isEmpty()) {
- LOG.info("Found {} available pipelines. Checking pipelines for
applicable migrations...",
- availablePipelines.size()
- );
- }
-
- for (var pipeline : availablePipelines) {
- List<MigrationResult<?>> failedMigrations = new ArrayList<>();
-
- var migratedDataProcessors = pipeline.getSepas()
- .stream()
- .map(processor -> {
- if (getApplicableMigration(processor,
migrationConfigs).isPresent()) {
- return migratePipelineElement(
- processor,
- migrationConfigs,
- String.format("%s/%s/processor",
-
extensionsServiceConfig.getServiceUrl(),
- MIGRATION_ENDPOINT
- ),
- failedMigrations
- );
- } else {
- LOG.info("No migration applicable for data processor
'{}'.", processor.getElementId());
- return processor;
- }
- }
- ).toList();
- pipeline.setSepas(migratedDataProcessors);
-
- var migratedDataSinks = pipeline.getActions()
- .stream()
- .map(sink -> {
- if (getApplicableMigration(sink,
migrationConfigs).isPresent()) {
- return migratePipelineElement(
- sink,
- migrationConfigs,
- String.format("%s/%s/sink",
-
extensionsServiceConfig.getServiceUrl(),
- MIGRATION_ENDPOINT
- ),
- failedMigrations
- );
- } else {
- LOG.info("No migration applicable for data sink
'{}'.", sink.getElementId());
- return sink;
- }
- }
- ).toList();
- pipeline.setActions(migratedDataSinks);
-
- pipelineStorage.updatePipeline(pipeline);
-
- if (failedMigrations.isEmpty()) {
- LOG.info("Migration for pipeline successfully completed.");
- } else {
- // pass most recent version of pipeline
-
handleFailedMigrations(pipelineStorage.getPipeline(pipeline.getPipelineId()),
failedMigrations);
- }
- }
+ var adapterMigrations = filterConfigs(migrationConfigs,
List.of(SpServiceTagPrefix.ADAPTER));
+ var pipelineElementMigrations = filterConfigs(
+ migrationConfigs,
+ List.of(SpServiceTagPrefix.DATA_PROCESSOR,
SpServiceTagPrefix.DATA_SINK)
+ );
+
+ new
AdapterMigrationManager(adapterStorage).handleMigrations(extensionsServiceConfig,
adapterMigrations);
+ new PipelineElementMigrationManager(
+ pipelineStorage,
+ dataProcessorStorage,
+ dataSinkStorage)
+ .handleMigrations(extensionsServiceConfig, pipelineElementMigrations);
return ok();
}
- /**
- * Takes care about the failed migrations of pipeline elements.
- * This includes the following steps:
- * <ul>
- * <li> logging of failed pipeline elements
- * <li> setting migration results as pipeline notifications
- * <li> updating pipeline health status
- * <li> stopping the pipeline
- * </ul>
- *
- * @param pipeline the pipeline affected by failed migrations
- * @param failedMigrations the list of failed migrations
- */
- protected void handleFailedMigrations(Pipeline pipeline,
List<MigrationResult<?>> failedMigrations) {
- LOG.error("Failures in migration detected - The following pipeline
elements could to be migrated:\n"
- +
StringUtils.join(failedMigrations.stream().map(Record::toString).toList()),
"\n");
-
- pipeline.setPipelineNotifications(failedMigrations.stream().map(
- failedMigration -> "Failed migration of pipeline element:
%s".formatted(failedMigration.message())
- ).toList());
- pipeline.setHealthStatus(PipelineHealthStatus.REQUIRES_ATTENTION);
-
- pipelineStorage.updatePipeline(pipeline);
-
- // get updated version of pipeline after modification
- pipeline = pipelineStorage.getPipeline(pipeline.getPipelineId());
-
- stopPipeline(pipeline);
- }
-
-
- public void stopPipeline(Pipeline pipeline) {
- var pipelineExecutor = new PipelineExecutor(pipeline, true);
- var pipelineStopResult = pipelineExecutor.stopPipeline();
-
- if (pipelineStopResult.isSuccess()) {
- LOG.info("Pipeline successfully stopped.");
- } else {
- LOG.error("Pipeline stop failed.");
- }
- }
-
- /**
- * Filter the application migration for a pipeline definition.
- * By definition, there is only one migration config that fulfills the
requirements.
- * Otherwise, it should have been detected as duplicate by the extensions
service.
- *
- * @param pipelineElement pipeline element that should be migrated
- * @param migrationConfigs available migration configs to pick the
applicable from
- * @return config that is applicable for the given pipeline element
- */
- protected static Optional<ModelMigratorConfig> getApplicableMigration(
- InvocableStreamPipesEntity pipelineElement,
- List<ModelMigratorConfig> migrationConfigs
- ) {
+ private List<ModelMigratorConfig> filterConfigs(List<ModelMigratorConfig>
migrationConfigs,
+
List<SpServiceTagPrefix> modelTypes) {
return migrationConfigs
- .stream()
- .filter(
- config ->
config.modelType().equals(pipelineElement.getServiceTagPrefix())
- &&
config.targetAppId().equals(pipelineElement.getAppId())
- && config.fromVersion() ==
pipelineElement.getVersion()
- )
- .findFirst();
- }
-
- /**
- * Handle the migration of a pipeline element with respect to the given
model migration configs.
- * All applicable migrations found in the provided configs are executed for
the given pipeline element.
- * In case a migration fails, the related pipeline element receives the
latest definition of its static properties,
- * so that the pipeline element can be adapted by the user to resolve the
failed migration.
- *
- * @param pipelineElement pipeline element to be migrated
- * @param modelMigrations list of model migrations that might be applicable
for this pipeline element
- * @param url url of the extensions service endpoint that
handles the migration
- * @param failedMigrations collection of failed migrations which is extended
by occurring migration failures
- * @param <T> type of the pipeline element (e.g.,
DataProcessorInvocation)
- * @return the migrated (or - in case of a failure - updated) pipeline
element
- */
- protected <T extends InvocableStreamPipesEntity> T migratePipelineElement(
- T pipelineElement,
- List<ModelMigratorConfig> modelMigrations,
- String url,
- List<MigrationResult<?>> failedMigrations
- ) {
-
- // loop until no migrations are available anymore
- // this allows to apply multiple migrations for a pipeline element
sequentially
- // For example, first migration from 0 to 1 and the second migration from
1 to 2
- while (getApplicableMigration(pipelineElement,
modelMigrations).isPresent() && failedMigrations.isEmpty()) {
-
- var migrationConfig = getApplicableMigration(pipelineElement,
modelMigrations).get();
- LOG.info(
- "Found applicable migration for pipeline element '{}': {}",
- pipelineElement.getElementId(),
- migrationConfig
- );
-
- var migrationResult = performMigration(
- pipelineElement,
- migrationConfig,
- url
- );
-
- if (migrationResult.success()) {
- LOG.info("Migration successfully performed by extensions service.
Updating pipeline element invocation ...");
- LOG.debug("Migration was performed at extensions service endpoint
'{}'", url);
- pipelineElement = migrationResult.element();
- } else {
- LOG.error("Migration failed with the following reason: {}",
migrationResult.message());
- failedMigrations.add(migrationResult);
- }
- }
- if (!failedMigrations.isEmpty()) {
- updateFailedPipelineElement(pipelineElement);
- LOG.info("Updated pipeline elements with new description where automatic
migration failed.");
- }
- return pipelineElement;
- }
-
- /**
- * Performs the actual migration of a pipeline element.
- * This includes the communication with the extensions service which runs
the migration.
- *
- * @param pipelineElement pipeline element to be migrated
- * @param migrationConfig config of the migration to be performed
- * @param url url of the migration endpoint at the extensions
service
- * where the migration should be performed
- * @param <T> type of the processing element
- * @return result of the migration
- */
- protected <T extends VersionedNamedStreamPipesEntity> MigrationResult<T>
performMigration(
- T pipelineElement,
- ModelMigratorConfig migrationConfig,
- String url
- ) {
-
- try {
-
- var migrationRequest = new MigrationRequest<>(pipelineElement,
migrationConfig);
-
- String serializedRequest =
JacksonSerializer.getObjectMapper().writeValueAsString(migrationRequest);
-
- var migrationResponse = ExtensionServiceExecutions.extServicePostRequest(
- url,
- serializedRequest
- ).execute();
-
- TypeReference<MigrationResult<T>> typeReference = new TypeReference<>() {
- };
-
- return JacksonSerializer
- .getObjectMapper()
- .readValue(migrationResponse.returnContent().asString(),
typeReference);
- } catch (JsonProcessingException e) {
- LOG.error(
- "Migration of pipeline element failed before sending to the
extensions service, "
- + "pipeline element is not migrated. Serialization of
migration request failed: {}",
- StringUtils.join(e.getStackTrace(), "\n")
- );
- } catch (IOException e) {
- LOG.error("Migration of pipeline element failed at the extensions
service, pipeline element is not migrated: {}.",
- StringUtils.join(e.getStackTrace(), "\n")
- );
- }
- return MigrationResult.failure(pipelineElement, "Internal error during
migration at StreamPipes Core");
- }
-
- /**
- * Update all descriptions of entities in the Core that are affected by
migrations.
- *
- * @param migrationConfigs List of migrations to take in account
- * @param serviceUrl Url of the extension service that provides the
migrations.
- */
- protected void updateDescriptions(List<ModelMigratorConfig>
migrationConfigs, String serviceUrl) {
- migrationConfigs
- .stream()
- .collect(
- // We only need to update the description once per appId,
- // because this is directly done with the newest version of
the description and
- // there is iterative migration required.
- // To avoid unnecessary, multiple updates,
- // we filter the migration configs such that every appId is
unique.
- // This ensures that every description is only updated once.
- Collectors.toMap(
- ModelMigratorConfig::targetAppId,
- Function.identity(),
- (existing, replacement) -> existing
- )
- )
- .values()
.stream()
- .peek(config -> {
- var requestUrl = getRequestUrl(config.modelType(),
config.targetAppId(), serviceUrl);
- performUpdate(requestUrl);
- }
- )
+ .filter(config -> modelTypes.stream().anyMatch(modelType -> modelType
== config.modelType()))
.toList();
}
-
- /**
- * Get the URL that provides the description for an entity.
- *
- * @param entityType Type of the entity to be updated.
- * @param appId AppId of the entity to be updated
- * @param serviceUrl URL of the extensions service to which the entity
belongs
- * @return URL of the endpoint that provides the description for the given
entity
- */
- protected String getRequestUrl(SpServiceTagPrefix entityType, String appId,
String serviceUrl) {
-
- SpServiceUrlProvider urlProvider;
- switch (entityType) {
- case ADAPTER -> urlProvider = SpServiceUrlProvider.ADAPTER;
- case DATA_PROCESSOR -> urlProvider = SpServiceUrlProvider.DATA_PROCESSOR;
- case DATA_SINK -> urlProvider = SpServiceUrlProvider.DATA_SINK;
- default -> throw new RuntimeException("Unexpected instance type.");
- }
- return urlProvider.getInvocationUrl(serviceUrl, appId);
- }
-
- /**
- * Perform the update of the description based on the given requestUrl
- *
- * @param requestUrl URl that references the description to be updated at
the extensions service.
- */
- protected void performUpdate(String requestUrl) {
-
- try {
- var entityPayload = parseURIContent(requestUrl);
- var updateResult = Operations.verifyAndUpdateElement(entityPayload);
- if (!updateResult.isSuccess()) {
- LOG.error(
- "Updating the pipeline element description failed: {}",
- StringUtils.join(
-
updateResult.getNotifications().stream().map(Notification::toString).toList(),
- "\n")
- );
- }
- } catch (IOException | URISyntaxException | SepaParseException e) {
- LOG.error("Updating the pipeline element description failed due to the
following exception:\n{}",
- StringUtils.join(e.getStackTrace(), "\n")
- );
- }
- }
-
- /**
- * Update the static properties of the failed pipeline element with its
description.
- * This allows to adapt the failed pipeline element in the UI to overcome
the failed migration.
- *
- * @param pipelineElement pipeline element with failed migration
- */
- protected void updateFailedPipelineElement(InvocableStreamPipesEntity
pipelineElement) {
- List<StaticProperty> updatedStaticProperties = new ArrayList<>();
- if (pipelineElement instanceof DataProcessorInvocation) {
- updatedStaticProperties = dataProcessorStorage
- .getFirstDataProcessorByAppId(pipelineElement.getAppId())
- .getStaticProperties();
- } else if (pipelineElement instanceof DataSinkInvocation) {
- updatedStaticProperties = dataSinkStorage
- .getFirstDataSinkByAppId(pipelineElement.getAppId())
- .getStaticProperties();
- }
- pipelineElement.setStaticProperties(updatedStaticProperties);
- }
}
diff --git
a/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/ExtensionsModelSubmitter.java
b/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/ExtensionsModelSubmitter.java
index c562ae02f..d15efbc68 100644
---
a/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/ExtensionsModelSubmitter.java
+++
b/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/ExtensionsModelSubmitter.java
@@ -23,7 +23,6 @@ import
org.apache.streampipes.extensions.management.client.StreamPipesClientReso
import org.apache.streampipes.extensions.management.init.DeclarersSingleton;
import org.apache.streampipes.extensions.management.model.SpServiceDefinition;
import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTag;
-import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
import
org.apache.streampipes.service.extensions.function.StreamPipesFunctionHandler;
import org.apache.streampipes.service.extensions.security.WebSecurityConfig;
@@ -51,28 +50,9 @@ public abstract class ExtensionsModelSubmitter extends
StreamPipesExtensionsServ
public void afterServiceRegistered(SpServiceDefinition serviceDef) {
StreamPipesClient client = new
StreamPipesClientResolver().makeStreamPipesClientInstance();
- // register all adapter migrations at StreamPipes Core
- var adapterMigrations = serviceDef.getMigrators()
- .stream()
- .filter(modelMigrator -> modelMigrator.config().modelType() ==
SpServiceTagPrefix.ADAPTER)
- .toList();
- client.adminApi().registerAdapterMigrations(
- adapterMigrations.stream().map(ModelMigrator::config).toList(),
- serviceId()
- );
-
- // register all pipeline element migrations at StreamPipes Core
- var pipelineElementMigrations = serviceDef.getMigrators()
- .stream()
- .filter(modelMigrator ->
- modelMigrator.config().modelType() ==
SpServiceTagPrefix.DATA_PROCESSOR
- || modelMigrator.config().modelType() ==
SpServiceTagPrefix.DATA_SINK
- )
- .toList();
- client.adminApi().registerPipelineElementMigrations(
-
pipelineElementMigrations.stream().map(ModelMigrator::config).toList(),
- serviceId()
- );
+ // register all migrations at StreamPipes Core
+ var migrationConfigs =
serviceDef.getMigrators().stream().map(ModelMigrator::config).toList();
+ client.adminApi().registerMigrations(migrationConfigs, serviceId());
// initialize all function instances
StreamPipesFunctionHandler.INSTANCE.initializeFunctions(serviceDef.getServiceGroup());