Верблюд 3: Как перехватить маршрут из `onException` с помощью` interceptSendToEndpoint` - PullRequest
2 голосов
/ 20 февраля 2020

Проблема:

Во время перехода с верблюда 2 на 3 мои тесты маршрутизации ошибок не пройдены.

Шаблон, который я использую, состоит в том, чтобы вызвать исключение и утверждать, что блок onException() отправляет в мою метрику маршрут с соответствующими тегами.

Я использую сопоставление с шаблоном uri для индивидуальной проверки того, что каждый тег испускается ... это сильно влияет на тестовую комбинацию

Примечание: В обоих нижеприведенных примерах метод createRouteBuilder() идентичен

Пример Passing Camel 2 * Пример 10101 * 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) } } Failing 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 , используя шаблоны ? Ручное тестирование показывает, что ошибка маршрутизации ведет себя так, как и ожидалось в Prod, поэтому, похоже, это проблема конфигурации теста.

Прочие сведения:

  • Этот модульный тест из Верблюжий репо демонстрирует, что я пытаюсь сделать, но вручную перехватывая маршрут, а не используя mock: непосредственно в маршруте.
  • Когда мне не нужно сопоставление с шаблоном, тогда это альтернативный подход работает

    override fun isMockEndpointsAndSkip() = myUri
    
    // ... in test
    getMockEndpoint("mock:$myUri").expectedMessageCount(1)
    

Ответы [ 3 ]

2 голосов
/ 26 февраля 2020

Прежде всего, большое спасибо за очень хорошо сформулированный вопрос с правильными примерами кода! Руководство по фиктивному компоненту относится к введению функции 'Пересечение существующих конечных точек' , очень вероятно, это то, что вас заблокировало. Я не очень уверен, какая версия Camel представила эту функцию.

В любом случае, чтобы обойти текущее ограничение, вы можете использовать саму автоматическую функцию c mocking. Ваш тестовый метод может быть изменен, как показано ниже, чтобы заставить его работать.

 @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), если нет особых c причин использовать 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

Screenshot from 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);


    }


}

1 голос
/ 27 февраля 2020

Дополнительная информация для хорошего ответа @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;
    }

Пункт назначения «direct: errors» в 2.x был переписан найденной конечной точкой перехвата. Но теперь этот код был помечен как "старый крест" и удален @clausibsen. Я сомневаюсь, что это ошибка, потому что простой interceptSendToEndpoint все еще работает. Возможно, есть изменения в использовании advicewith + перехватчики.

0 голосов
/ 27 февраля 2020

Похоже, это напрямую связано с использованием onException(). Очевидно, что в Camel 3 вы больше не можете перехватывать напрямую из onException, поэтому перемещение бизнес-логики c из блока исключений на новый маршрут позволяет перехвату работать.

В моем случае это просто необходимо сохранение релевантной 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)
    }
}
...