0

I would like to make my Apache Camel application to be more resilient and start even when the MQTT broker is not reachable. We are using Camel on IoT devices with a possibly unstable internet connection and I want our application to start even without internet access.

An exemplary route looks like the following:

from("timer:heartbeat?period=5000")
    .routeId("send heartbeat")
    .setBody(simple("Hello World!"))
    .to("paho:myhostnome/heartbeat?brokerUrl={{broker.url}}")

This works fine as long as the MQTT server is available. However when the server is not reachable, the context fails while warming up the PahoEndpoint.

Caused by: org.apache.camel.FailedToCreateRouteException: Failed to create route send heartbeat: Route(send heartbeat)[[From[timer:heartbeat?period={{heartbe... because of Unable to connect to server
at org.apache.camel.impl.RouteService.warmUp(RouteService.java:147) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.doWarmUpRoutes(DefaultCamelContext.java:3758) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.safelyStartRouteServices(DefaultCamelContext.java:3665) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.doStartOrResumeRoutes(DefaultCamelContext.java:3451) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.doStartCamel(DefaultCamelContext.java:3305) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.access$000(DefaultCamelContext.java:202) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:3089) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:3085) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.doWithDefinedClassLoader(DefaultCamelContext.java:3108) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.doStart(DefaultCamelContext.java:3085) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.impl.DefaultCamelContext.start(DefaultCamelContext.java:3022) ~[camel-core-2.19.3.jar:2.19.3]
at org.apache.camel.spring.boot.RoutesCollector.maybeStart(RoutesCollector.java:242) ~[camel-spring-boot-2.19.3.jar:2.19.3]
at org.apache.camel.spring.boot.RoutesCollector.onApplicationEvent(RoutesCollector.java:217) ~[camel-spring-boot-2.19.3.jar:2.19.3]
... 13 common frames omitted
Caused by: org.eclipse.paho.client.mqttv3.MqttException: Unable to connect to server
at org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule.start(TCPNetworkModule.java:79) ~[org.eclipse.paho.client.mqttv3-1.1.0.jar:na]
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:650) ~[org.eclipse.paho.client.mqttv3-1.1.0.jar:na]
at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_121]
Caused by: java.net.ConnectException: Connection refused (Connection refused)
at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0_121]
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_121]
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_121]
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_121]
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_121]
at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_121]
at org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule.start(TCPNetworkModule.java:70) ~[org.eclipse.paho.client.mqttv3-1.1.0.jar:na]

My first idea was to disable auto startup on all routes involving Paho and start them manually. This does not work as the PahoEndpoint is started even when the route itself is not started.

I'm now looking for an alternative approach to handle this problem.

Michi Gysel
  • 760
  • 6
  • 11

2 Answers2

3

Apache Camel 2.20 onwards comes with a new functionality to let the startup procedure run using a superviser controller that takes over starting up from CamelContext itself. This allows to setup more advanced configuration to let the controller handle errors, and attempt to retry starting the failed routes etc.

There is an example where its configured via Spring Boot, but you can configure it from Java API as well: https://github.com/apache/camel-spring-boot-examples/tree/main/supervising-route-controller

In the upcoming releases we will improve on this new functionality. For older versions of Camel then its often the component itself that may or may not offer any kind of retry mechanism, you need to configure, instead of failing fast.

simonalexander2005
  • 4,338
  • 4
  • 48
  • 92
Claus Ibsen
  • 56,060
  • 7
  • 50
  • 65
1

Camel has a try() catch() functionality.

Why not add a try() before calling the to() and catch the exception and then just log it? Something like:

from("timer:heartbeat?period=5000")
    .routeId("send heartbeat")
    .setBody(simple("Hello World!"))
    .doTry()
    .to("paho:myhostnome/heartbeat?brokerUrl={{broker.url}}")
     .doCatch( java.net.ConnectException.class).log("failed")
    .end()
Souciance Eqdam Rashti
  • 3,143
  • 3
  • 15
  • 31
  • This does not help with camel-paho as the connection to the server is opened when initializing the route (not when running it). – Michi Gysel Sep 18 '17 at 13:07
  • I guess one workaround is not to hard code the uri. Add it as a property and inject the value in the dynamic router toD() instead. For instance read the url from a property file or environment variable? That way, it should start and only when the property is injected will it fail. – Souciance Eqdam Rashti Sep 18 '17 at 13:44