You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/09/01 13:30:25 UTC
[camel] 01/02: CAMEL-18267: ContextReloadStrategy to reload all routes after external changes such as properties/vault updated.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch reload
in repository https://gitbox.apache.org/repos/asf/camel.git
commit a8d8788bdda4f2a0e48d7d613a0ea20a709f975d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Sep 1 15:00:00 2022 +0200
CAMEL-18267: ContextReloadStrategy to reload all routes after external changes such as properties/vault updated.
---
.../apache/camel/spi/ContextReloadStrategy.java | 57 ++++++++++
.../org/apache/camel/spi/PropertiesComponent.java | 5 +
.../java/org/apache/camel/spi/RouteController.java | 16 +++
.../camel/impl/engine/AbstractCamelContext.java | 14 +++
.../impl/engine/DefaultContextReloadStrategy.java | 118 +++++++++++++++++++++
.../camel/impl/engine/DefaultRouteController.java | 10 ++
.../camel/impl/engine/InternalRouteController.java | 21 ++++
.../AbstractLocationPropertiesSource.java | 5 +
.../properties/DefaultPropertiesLookup.java | 2 +-
.../component/properties/PropertiesComponent.java | 3 +-
.../org/apache/camel/impl/DefaultCamelContext.java | 24 +++--
.../impl/lw/LightweightRuntimeCamelContext.java | 10 ++
.../camel/impl/CamelContextReloadStrategyTest.java | 107 +++++++++++++++++++
13 files changed, 384 insertions(+), 8 deletions(-)
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ContextReloadStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/ContextReloadStrategy.java
new file mode 100644
index 00000000000..b240f26dc04
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ContextReloadStrategy.java
@@ -0,0 +1,57 @@
+/*
+ * 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.camel.spi;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.StaticService;
+
+/**
+ * SPI strategy for reloading {@link CamelContext}.
+ *
+ * The reloading is limited to:
+ * - all routes (reload only changes from routes that has been loaded as a {@link Resource}.
+ * - all {@link LoadablePropertiesSource} properties.
+ *
+ * General services in the {@link CamelContext} is not reloaded.
+ *
+ * @see ResourceReloadStrategy
+ */
+public interface ContextReloadStrategy extends StaticService, CamelContextAware {
+
+ /**
+ * Trigger reload of the {@link CamelContext}.
+ *
+ * @param source source that triggers the reloading.
+ */
+ void onReload(Object source);
+
+ /**
+ * Number of reloads succeeded.
+ */
+ int getReloadCounter();
+
+ /**
+ * Number of reloads failed.
+ */
+ int getFailedCounter();
+
+ /**
+ * Reset the counters.
+ */
+ void resetCounters();
+}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java
index 8b12fe48243..f225f255977 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java
@@ -176,6 +176,11 @@ public interface PropertiesComponent extends StaticService {
*/
PropertiesSource getPropertiesSource(String name);
+ /**
+ * Gets the properties sources
+ */
+ List<PropertiesSource> getPropertiesSources();
+
/**
* Registers the {@link PropertiesFunction} as a function to this component.
*/
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java b/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java
index 3f1c4f6b524..5b7eefcff34 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RouteController.java
@@ -99,6 +99,22 @@ public interface RouteController extends CamelContextAware, StaticService {
*/
boolean isStartingRoutes();
+ /**
+ * Reloads all the routes
+ *
+ * @throws Exception is thrown if a route could not be reloaded for whatever reason
+ */
+ void reloadAllRoutes() throws Exception;
+
+ /**
+ * Indicates whether current thread is reloading route(s).
+ * <p/>
+ * This can be useful to know by {@link LifecycleStrategy} or the likes, in case they need to react differently.
+ *
+ * @return <tt>true</tt> if current thread is reloading route(s), or <tt>false</tt> if not.
+ */
+ boolean isReloadingRoutes();
+
/**
* Returns the current status of the given route
*
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index e685def233e..86960f4d2be 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -223,6 +223,7 @@ public abstract class AbstractCamelContext extends BaseService
private final List<LifecycleStrategy> lifecycleStrategies = new CopyOnWriteArrayList<>();
private final ThreadLocal<Boolean> isStartingRoutes = new ThreadLocal<>();
private final ThreadLocal<Boolean> isSetupRoutes = new ThreadLocal<>();
+ private final ThreadLocal<Boolean> isLockModel = new ThreadLocal<>();
private final Map<String, FactoryFinder> factories = new ConcurrentHashMap<>();
private final Map<String, FactoryFinder> bootstrapFactories = new ConcurrentHashMap<>();
private volatile FactoryFinder bootstrapFactoryFinder;
@@ -1228,6 +1229,19 @@ public abstract class AbstractCamelContext extends BaseService
}
}
+ public boolean isLockModel() {
+ Boolean answer = isLockModel.get();
+ return answer != null && answer;
+ }
+
+ public void setLockModel(boolean lockModel) {
+ if (lockModel) {
+ isLockModel.set(true);
+ } else {
+ isLockModel.remove();
+ }
+ }
+
@Override
public boolean isSetupRoutes() {
Boolean answer = isSetupRoutes.get();
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextReloadStrategy.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextReloadStrategy.java
new file mode 100644
index 00000000000..5d3700dae71
--- /dev/null
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextReloadStrategy.java
@@ -0,0 +1,118 @@
+/*
+ * 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.camel.impl.engine;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ExtendedCamelContext;
+import org.apache.camel.api.management.ManagedAttribute;
+import org.apache.camel.api.management.ManagedOperation;
+import org.apache.camel.spi.ContextReloadStrategy;
+import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.spi.PropertiesSource;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultContextReloadStrategy extends ServiceSupport implements ContextReloadStrategy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultContextReloadStrategy.class);
+
+ private CamelContext camelContext;
+ private int succeeded;
+ private int failed;
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public void onReload(Object source) {
+ LOG.info("Reloading CamelContext ({}) triggered by: {}", camelContext.getName(), source);
+
+ try {
+ reloadProperties(source);
+ reloadRoutes(source);
+ incSucceededCounter();
+ } catch (Exception e) {
+ incFailedCounter();
+ LOG.warn("Error reloading CamelContext (" + camelContext.getName() + ") due to: " + e.getMessage(), e);
+ }
+ }
+
+ protected void reloadRoutes(Object source) throws Exception {
+ getCamelContext().getRouteController().reloadAllRoutes();
+ }
+
+ protected void reloadProperties(Object source) throws Exception {
+ PropertiesComponent pc = getCamelContext().getPropertiesComponent();
+ for (PropertiesSource ps : pc.getPropertiesSources()) {
+ // reload by restarting
+ ServiceHelper.stopAndShutdownService(ps);
+ ServiceHelper.startService(ps);
+ }
+ }
+
+ @ManagedAttribute(description = "Number of reloads succeeded")
+ public int getReloadCounter() {
+ return succeeded;
+ }
+
+ @ManagedAttribute(description = "Number of reloads failed")
+ public int getFailedCounter() {
+ return failed;
+ }
+
+ public void setSucceeded(int succeeded) {
+ this.succeeded = succeeded;
+ }
+
+ public void setFailed(int failed) {
+ this.failed = failed;
+ }
+
+ @ManagedOperation(description = "Reset counters")
+ public void resetCounters() {
+ succeeded = 0;
+ failed = 0;
+ }
+
+ protected void incSucceededCounter() {
+ succeeded++;
+ }
+
+ protected void incFailedCounter() {
+ failed++;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ // noop
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ // noop
+ }
+
+}
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
index 0fc55b76019..3b249751e12 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRouteController.java
@@ -109,6 +109,16 @@ public class DefaultRouteController extends ServiceSupport implements RouteContr
return getInternalRouteController().isStartingRoutes();
}
+ @Override
+ public void reloadAllRoutes() throws Exception {
+ getInternalRouteController().reloadAllRoutes();
+ }
+
+ @Override
+ public boolean isReloadingRoutes() {
+ return getInternalRouteController().isReloadingRoutes();
+ }
+
@Override
public ServiceStatus getRouteStatus(String routeId) {
return getInternalRouteController().getRouteStatus(routeId);
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
index 97130b57a76..78e3ba51ede 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteController.java
@@ -83,6 +83,27 @@ class InternalRouteController implements RouteController {
abstractCamelContext.removeAllRoutes();
}
+ @Override
+ public void reloadAllRoutes() throws Exception {
+ // lock model as we need to preserve the model definitions
+ // during reloading because we need to create new processors from the models
+ abstractCamelContext.setLockModel(true);
+ try {
+ abstractCamelContext.removeAllRoutes();
+ // remove left-over route templates and endpoints, so we can start on a fresh
+ abstractCamelContext.getEndpointRegistry().clear();
+ // start all routes again
+ abstractCamelContext.startRouteDefinitions();
+ } finally {
+ abstractCamelContext.setLockModel(false);
+ }
+ }
+
+ @Override
+ public boolean isReloadingRoutes() {
+ return abstractCamelContext.isLockModel();
+ }
+
@Override
public boolean isStartingRoutes() {
return abstractCamelContext.isStartingRoutes();
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java
index b4bd4084e85..a11044d76dc 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java
@@ -109,6 +109,11 @@ public abstract class AbstractLocationPropertiesSource extends ServiceSupport
}
}
+ @Override
+ protected void doShutdown() throws Exception {
+ properties.clear();
+ }
+
/**
* Strategy to prepare loaded properties before being used by Camel.
* <p/>
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java
index f912b62021c..1fc42e2c653 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesLookup.java
@@ -73,7 +73,7 @@ public class DefaultPropertiesLookup implements PropertiesLookup {
}
if (answer == null) {
// try till first found source
- for (PropertiesSource ps : component.getSources()) {
+ for (PropertiesSource ps : component.getPropertiesSources()) {
answer = ps.getProperty(name);
if (answer != null) {
String source = ps.getName();
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
index 47723392a64..852fc155be8 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
@@ -666,7 +666,8 @@ public class PropertiesComponent extends ServiceSupport
return null;
}
- public List<PropertiesSource> getSources() {
+ @Override
+ public List<PropertiesSource> getPropertiesSources() {
return sources;
}
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
index 1a02fe0c44b..35d9a52bc72 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
@@ -364,7 +364,9 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
if (model == null && isLightweight()) {
throw new IllegalStateException("Access to model not supported in lightweight mode");
}
- model.removeRouteDefinitions(routeDefinitions);
+ if (!isLockModel()) {
+ model.removeRouteDefinitions(routeDefinitions);
+ }
}
@Override
@@ -372,7 +374,9 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
if (model == null && isLightweight()) {
throw new IllegalStateException("Access to model not supported in lightweight mode");
}
- model.removeRouteDefinition(routeDefinition);
+ if (!isLockModel()) {
+ model.removeRouteDefinition(routeDefinition);
+ }
}
@Override
@@ -412,7 +416,9 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
if (model == null && isLightweight()) {
throw new IllegalStateException("Access to model not supported in lightweight mode");
}
- model.removeRouteTemplateDefinitions(routeTemplateDefinitions);
+ if (!isLockModel()) {
+ model.removeRouteTemplateDefinitions(routeTemplateDefinitions);
+ }
}
@Override
@@ -420,7 +426,9 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
if (model == null && isLightweight()) {
throw new IllegalStateException("Access to model not supported in lightweight mode");
}
- model.removeRouteTemplateDefinition(routeTemplateDefinition);
+ if (!isLockModel()) {
+ model.removeRouteTemplateDefinition(routeTemplateDefinition);
+ }
}
@Override
@@ -428,7 +436,9 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
if (model == null && isLightweight()) {
throw new IllegalStateException("Access to model not supported in lightweight mode");
}
- model.removeRouteTemplateDefinitions(pattern);
+ if (!isLockModel()) {
+ model.removeRouteTemplateDefinitions(pattern);
+ }
}
@Override
@@ -471,7 +481,9 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
if (model == null && isLightweight()) {
throw new IllegalStateException("Access to model not supported in lightweight mode");
}
- model.removeRouteTemplateDefinitions(pattern);
+ if (!isLockModel()) {
+ model.removeRouteTemplateDefinitions(pattern);
+ }
}
@Override
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
index d653af68007..38fa50f34d8 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
@@ -2177,6 +2177,16 @@ public class LightweightRuntimeCamelContext implements ExtendedCamelContext, Cat
return false;
}
+ @Override
+ public void reloadAllRoutes() throws Exception {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isReloadingRoutes() {
+ return false;
+ }
+
@Override
public ServiceStatus getRouteStatus(String routeId) {
return ServiceStatus.Started;
diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/CamelContextReloadStrategyTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/CamelContextReloadStrategyTest.java
new file mode 100644
index 00000000000..56d3abf00ba
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/CamelContextReloadStrategyTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.camel.impl;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.impl.engine.DefaultContextReloadStrategy;
+import org.apache.camel.spi.ContextReloadStrategy;
+import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.spi.PropertiesSource;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class CamelContextReloadStrategyTest extends ContextTestSupport {
+
+ @Test
+ public void testContextReload() throws Exception {
+ Assertions.assertEquals("Hello 1", context.resolvePropertyPlaceholders("{{hello}}"));
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedBodiesReceived("Hello 1");
+ template.sendBody("direct:start", "Camel");
+ mock.assertIsSatisfied();
+
+ ContextReloadStrategy crs = context.hasService(ContextReloadStrategy.class);
+ Assertions.assertNotNull(crs);
+ crs.onReload("CamelContextReloadStrategyTest");
+
+ Assertions.assertEquals("Hello 2", context.resolvePropertyPlaceholders("{{hello}}"));
+
+ // need to re-get endpoint after reload
+ mock = getMockEndpoint("mock:result");
+
+ mock.reset();
+ mock.expectedBodiesReceived("Hello 2");
+ template.sendBody("direct:start", "World");
+ mock.assertIsSatisfied();
+ }
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+
+ PropertiesComponent pc = context.getPropertiesComponent();
+ MySource my = new MySource();
+ ServiceHelper.startService(my);
+ pc.addPropertiesSource(my);
+
+ ContextReloadStrategy crs = new DefaultContextReloadStrategy();
+ context.addService(crs);
+
+ return context;
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start")
+ .setBody(constant("{{hello}}"))
+ .to("mock:result");
+ }
+ };
+ }
+
+ private class MySource extends ServiceSupport implements PropertiesSource {
+
+ private int counter;
+
+ @Override
+ public String getName() {
+ return "my";
+ }
+
+ @Override
+ public String getProperty(String name) {
+ if ("hello".equals(name)) {
+ return "Hello " + counter;
+ }
+ return null;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ counter++;
+ }
+ }
+}