Camel 3: Как перехватить маршрут из ʻonException` с помощью ʻinterceptSendToEndpoint`
Проблема:
Во время перехода с Camel 2 на 3 мои тесты маршрутизации ошибок сломались.
Шаблон, которому я следую, состоит в том, чтобы вызвать исключение и утверждать, что onException()
block отправляет на мой маршрут метрик с соответствующими тегами.
Я использую сопоставление с шаблоном uri, чтобы индивидуально проверить, что каждый тег испускается... это сильно влияет на тестовый шаблон
Примечание. В обоих примерах нижеcreateRouteBuilder()
метод идентичен
Пример прохождения верблюда 2
import org.apache.camel.RoutesBuilder
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.test.junit4.CamelTestSupport
import org.junit.Test
import java.util.concurrent.TimeUnit
class Camel2Test : CamelTestSupport() {
val startUri = "direct:start"
val baseMetricsUri = "micrometer:counter:errors"
// Want to use pattern to test each individual tag here
val fullMetricsUri = "$baseMetricsUri?tags=a=1,b=2"
override fun isUseAdviceWith(): Boolean {
return true
}
override fun createRouteBuilder(): RoutesBuilder {
return object : RouteBuilder() {
override fun configure() {
onException(Exception::class.java)
.to(fullMetricsUri)
from(startUri)
.routeId(startUri)
.throwException(Exception())
}
}
}
@Test
fun `metric with tag B is emitted`() {
val exchange = createExchangeWithBody("")
val mockEndpoint = getMockEndpoint("mock:test")
context.getRouteDefinition(startUri)
.adviceWith(context, object : RouteBuilder() {
override fun configure() {
interceptSendToEndpoint("$baseMetricsUri.*b.*2.*") // <-- PATTERN
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
})
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied(2, TimeUnit.SECONDS)
}
}
Неудачный пример Camel 3
import org.apache.camel.RoutesBuilder
import org.apache.camel.builder.AdviceWithRouteBuilder
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.test.junit4.CamelTestSupport
import org.junit.Test
import java.util.concurrent.TimeUnit
class Camel3Test : CamelTestSupport() {
val startUri = "direct:start"
val baseMetricsUri = "micrometer:counter:errors"
// Want to use pattern to test each individual tag here
val fullMetricsUri = "$baseMetricsUri?tags=a=1,b=2"
override fun isUseAdviceWith(): Boolean {
return true
}
override fun createRouteBuilder(): RoutesBuilder {
return object : RouteBuilder() {
override fun configure() {
onException(Exception::class.java)
.to(fullMetricsUri)
from(startUri)
.routeId(startUri)
.throwException(Exception())
}
}
}
@Test
fun `metric with tag B is emitted`() {
val exchange = createExchangeWithBody("")
val mockEndpoint = getMockEndpoint("mock:test")
AdviceWithRouteBuilder.adviceWith(context, startUri) { routeBuilder ->
routeBuilder.interceptSendToEndpoint("$baseMetricsUri.*b.*2.*") // <-- PATTERN
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied(2, TimeUnit.SECONDS)
}
}
В mockEndpoint
не получает обмен, а вместо этого все еще идет к конечной точке метрик.
Вопрос:
Как в Camel 3 перехватить маршрут, как в Camel 2, используя шаблоны? Ручное тестирование показывает, что маршрутизация ошибок в продукте работает так, как ожидалось, так что это, похоже, проблема с тестовой конфигурацией.
Другие детали:
- Этот модульный тест из репозитория camel демонстрирует, что я пытаюсь сделать, но путем ручного перехвата маршрута, а не использования
mock:
прямо в маршруте. Когда мне не нужно сопоставление с образцом, работает этот альтернативный подход.
override fun isMockEndpointsAndSkip() = myUri // ... in test getMockEndpoint("mock:$myUri").expectedMessageCount(1)
3 ответа
Похоже, это напрямую связано с использованием onException()
. Видимо в Camel 3 больше нельзя перехватывать прямо изonException
, поэтому перемещение бизнес-логики из блока исключений в новый маршрут позволяет перехвату работать.
В моем случае это просто требовало сохранения соответствующих onException
информация в свойствах обмена, на которую затем можно ссылаться при отправке показателей.
import org.apache.camel.RoutesBuilder
import org.apache.camel.builder.AdviceWithRouteBuilder
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.test.junit4.CamelTestSupport
import org.junit.Test
import java.util.concurrent.TimeUnit
class Camel3ErrorInterceptWorking : CamelTestSupport() {
val startUri = "direct:start"
val errorUri = "direct:errors"
val baseMetricsUri = "micrometer:counter:errors"
val fullMetricsUri = "$baseMetricsUri?tags=a=1,b=2"
override fun isUseAdviceWith(): Boolean {
return true
}
override fun createRouteBuilder(): RoutesBuilder {
return object : RouteBuilder() {
override fun configure() {
onException(Exception::class.java)
.to(errorUri)
from(errorUri)
.to(fullMetricsUri) // Moved metrics here from `onException`
from(startUri)
.routeId(startUri)
.throwException(Exception())
}
}
}
@Test
fun `exception is routed to error logging route`() {
val exchange = createExchangeWithBody("")
val mockEndpoint = getMockEndpoint("mock:test")
AdviceWithRouteBuilder.adviceWith(context, startUri) { routeBuilder ->
routeBuilder.interceptSendToEndpoint("$baseMetricsUri.*b.*2.*") // <-- PATTERN
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied(2, TimeUnit.SECONDS)
}
}
Прежде всего, большое спасибо за очень хорошо сформулированный вопрос с соответствующими примерами кода! В руководстве по фиктивному компоненту упоминается введение функции "Мокинг существующих конечных точек", скорее всего, именно это вас и заблокировало. Я не очень уверен, в какой версии Camel появилась эта функция.
В любом случае, чтобы обойти текущее ограничение, вы можете использовать саму функцию автоматического имитации. Ваш метод тестирования можно изменить, как показано ниже, чтобы он заработал.
@Test
fun `exception is routed to error logging route`() {
val exchange = createExchangeWithBody("")
// Create new mock endpoint that will replace our error route
val mockEndpoint = getMockEndpoint("mock:$errorUri")
AdviceWithRouteBuilder.adviceWith(context, startUri) { routeBuilder ->
routeBuilder.mockEndpoints(errorUri)
routeBuilder.interceptSendToEndpoint(errorUri)
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
}
context.start()
mockEndpoint.expectedMessageCount(1)
template.send(startUri, exchange)
assertMockEndpointsSatisfied()
}
В исходный код было внесено два изменения.
- Мок-конечная точка была переименована с
mock:test
чтобы соответствовать автоматически созданным типам фиктивных конечных точек (mock:direct:errors
) - Звонок в
routeBuilder.mockEndpoints(errorUri)
чтобы верблюд мог автоматически вводить Mocks для шаблонов, как описаноerrorUri
В дополнение к нему возможна замена блока ниже
routeBuilder.mockEndpoints(errorUri)
routeBuilder.interceptSendToEndpoint(errorUri)
.skipSendToOriginalEndpoint()
.to(mockEndpoint)
с одним вкладышем routeBuilder.mockEndpointsAndSkip(errorUri)
, если нет особых причин использовать intercept
как вы упомянули в своем вопросе.
Дополнительные наблюдения:
Запуск вашего кода без изменений четко показывает RouteReifier
подключение к конечной точке Mock, mock://test
на месте direct:errors
. В дополнениеcontext
оказался надлежащим endpointStrategy
также.
Это могло быть ошибкой. Хотя есть простые альтернативы, рассмотрите возможность поднять это как проблему и в ASF Jira.
14:32:34.307 [main] INFO org.apache.camel.reifier.RouteReifier - Adviced route before/after as XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="direct:start">
<from uri="direct:start"/>
<onException>
<exception>java.lang.Exception</exception>
<to uri="direct:errors"/>
</onException>
<throwException/>
</route>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="direct:start">
<from uri="direct:start"/>
<onException>
<exception>java.lang.Exception</exception>
<to uri="direct:errors"/>
</onException>
<interceptSendToEndpoint skipSendToOriginalEndpoint="true" uri="direct:errors">
<to uri="mock://test"/>
</interceptSendToEndpoint>
<throwException/>
</route>
Прохождение теста в IDE
Реализация Java (если кому то нужно)
import org.apache.camel.Exchange;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Assert;
import org.junit.Test;
public class Camel3RouteTest extends CamelTestSupport {
private static final String startUri = "direct:start";
private static final String errorUri = "direct:errors";
private static final String mockErrorURI = "mock:"+ errorUri;
private static final String ERROR_MESSAGE = "ERROR MESSAGE!";
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
onException(Exception.class)
.to(errorUri);
from(errorUri)
.routeId(errorUri)
.log("error happened!");
from(startUri)
.routeId(startUri)
.throwException(new Exception(ERROR_MESSAGE));
}
};
}
@Test
public void testExecution() throws Exception {
AdviceWithRouteBuilder.adviceWith(context, startUri, adviceWithRouteBuilder -> {
//a.mockEndpointsAndSkip(errorUri);
adviceWithRouteBuilder.mockEndpoints(errorUri);
adviceWithRouteBuilder.interceptSendToEndpoint(errorUri).skipSendToOriginalEndpoint().to(mockErrorURI);
});
MockEndpoint mockEndpoint = getMockEndpoint(mockErrorURI);
mockEndpoint.setExpectedMessageCount(1);
context.start();
sendBody(startUri, "A Test message");
assertMockEndpointsSatisfied();
Assert.assertNotNull(mockEndpoint.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT));
Exception receivedException = (Exception) mockEndpoint.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT);
Assert.assertTrue(receivedException instanceof Exception);
Assert.assertEquals(receivedException.getMessage(), ERROR_MESSAGE);
}
}
Дополнительная информация для отличного ответа @ShellDragon. Во время отладки ваших примеров я обнаружил интересную вещь. Ваши примеры не работают на верблюде 3, потому что SendProcessor потерял часть кода (метод doStart):
// the destination could since have been intercepted by a interceptSendToEndpoint so we got to
// lookup this before we can use the destination
Endpoint lookup = camelContext.hasEndpoint(destination.getEndpointKey());
if (lookup instanceof InterceptSendToEndpoint) {
if (log.isDebugEnabled()) {
log.debug("Intercepted sending to {} -> {}",
URISupport.sanitizeUri(destination.getEndpointUri()), URISupport.sanitizeUri(lookup.getEndpointUri()));
}
destination = lookup;
}
Пункт назначения "прямой: ошибки" в 2.x был переписан найденной конечной точкой перехвата. Но теперь этот код был помечен как "старый мусор" и удален @clausibsen . Я сомневаюсь, что это ошибка, потому что простой interceptSendToEndpoint все еще работает. Возможно, есть изменения в использовании советников с + перехватчиками.