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()
    }

В исходный код было внесено два изменения.

  1. Мок-конечная точка была переименована с mock:test чтобы соответствовать автоматически созданным типам фиктивных конечных точек (mock:direct:errors)
  2. Звонок в 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 все еще работает. Возможно, есть изменения в использовании советников с + перехватчиками.

Другие вопросы по тегам