Search in sources :

Example 11 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class KafkaBasedMappingAndDelegatingCommandHandlerTest method testCommandDelegationOrderWithMappingFailedForFirstEntry.

/**
 * Verifies the behaviour of the
 * {@link KafkaBasedMappingAndDelegatingCommandHandler#mapAndDelegateIncomingCommandMessage(KafkaConsumerRecord)}
 * method in a scenario where the rather long-running processing of a command delays subsequent, already mapped
 * commands from getting delegated to the target adapter instance. After the processing of the first command finally
 * resulted in an error, the subsequent commands shall get delegated in the correct order.
 *
 * @param ctx The vert.x test context
 */
@Test
public void testCommandDelegationOrderWithMappingFailedForFirstEntry(final VertxTestContext ctx) {
    final String deviceId1 = "device1";
    final String deviceId2 = "device2";
    final String deviceId3 = "device3";
    final String deviceId4 = "device4";
    // GIVEN valid command records
    final KafkaConsumerRecord<String, Buffer> commandRecord1 = getCommandRecord(tenantId, deviceId1, "subject1", 0, 1);
    final KafkaConsumerRecord<String, Buffer> commandRecord2 = getCommandRecord(tenantId, deviceId2, "subject2", 0, 2);
    final KafkaConsumerRecord<String, Buffer> commandRecord3 = getCommandRecord(tenantId, deviceId3, "subject3", 0, 3);
    final KafkaConsumerRecord<String, Buffer> commandRecord4 = getCommandRecord(tenantId, deviceId4, "subject4", 0, 4);
    // WHEN getting the target adapter instances for the commands results in different delays for each command
    // so that the invocations are completed with the order: commandRecord3, commandRecord2, commandRecord1 (failed), commandRecord4
    // with command 1 getting failed
    final Promise<JsonObject> resultForCommand1 = Promise.promise();
    when(commandTargetMapper.getTargetGatewayAndAdapterInstance(eq(tenantId), eq(deviceId1), any())).thenReturn(resultForCommand1.future());
    final Promise<JsonObject> resultForCommand2 = Promise.promise();
    when(commandTargetMapper.getTargetGatewayAndAdapterInstance(eq(tenantId), eq(deviceId2), any())).thenReturn(resultForCommand2.future());
    final Promise<JsonObject> resultForCommand3 = Promise.promise();
    when(commandTargetMapper.getTargetGatewayAndAdapterInstance(eq(tenantId), eq(deviceId3), any())).thenReturn(resultForCommand3.future());
    doAnswer(invocation -> {
        resultForCommand3.complete(createTargetAdapterInstanceJson(deviceId3, adapterInstanceId));
        resultForCommand2.complete(createTargetAdapterInstanceJson(deviceId2, adapterInstanceId));
        resultForCommand1.fail("mapping of command 1 failed for some reason");
        return Future.succeededFuture(createTargetAdapterInstanceJson(deviceId4, adapterInstanceId));
    }).when(commandTargetMapper).getTargetGatewayAndAdapterInstance(eq(tenantId), eq(deviceId4), any());
    // WHEN mapping and delegating the commands
    final Future<Void> cmd1Future = cmdHandler.mapAndDelegateIncomingCommandMessage(commandRecord1);
    final Future<Void> cmd2Future = cmdHandler.mapAndDelegateIncomingCommandMessage(commandRecord2);
    final Future<Void> cmd3Future = cmdHandler.mapAndDelegateIncomingCommandMessage(commandRecord3);
    final Future<Void> cmd4Future = cmdHandler.mapAndDelegateIncomingCommandMessage(commandRecord4);
    // THEN the messages are delegated in the original order, with command 1 left out because it timed out
    CompositeFuture.all(cmd2Future, cmd3Future, cmd4Future).onComplete(ctx.succeeding(r -> {
        ctx.verify(() -> {
            assertThat(cmd1Future.failed()).isTrue();
            final ArgumentCaptor<CommandContext> commandContextCaptor = ArgumentCaptor.forClass(CommandContext.class);
            verify(internalCommandSender, times(3)).sendCommand(commandContextCaptor.capture(), anyString());
            final List<CommandContext> capturedCommandContexts = commandContextCaptor.getAllValues();
            assertThat(capturedCommandContexts.get(0).getCommand().getDeviceId()).isEqualTo(deviceId2);
            assertThat(capturedCommandContexts.get(1).getCommand().getDeviceId()).isEqualTo(deviceId3);
            assertThat(capturedCommandContexts.get(2).getCommand().getDeviceId()).isEqualTo(deviceId4);
        });
        ctx.completeNow();
    }));
}
Also used : Buffer(io.vertx.core.buffer.Buffer) HttpURLConnection(java.net.HttpURLConnection) BeforeEach(org.junit.jupiter.api.BeforeEach) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) KafkaBasedCommandResponseSender(org.eclipse.hono.client.command.kafka.KafkaBasedCommandResponseSender) Context(io.vertx.core.Context) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) KafkaBasedInternalCommandSender(org.eclipse.hono.client.command.kafka.KafkaBasedInternalCommandSender) Mockito.doAnswer(org.mockito.Mockito.doAnswer) TracingMockSupport(org.eclipse.hono.test.TracingMockSupport) JsonObject(io.vertx.core.json.JsonObject) DeviceConnectionConstants(org.eclipse.hono.util.DeviceConnectionConstants) CommandContext(org.eclipse.hono.client.command.CommandContext) CommandTargetMapper(org.eclipse.hono.commandrouter.CommandTargetMapper) UUID(java.util.UUID) TenantClient(org.eclipse.hono.client.registry.TenantClient) VertxExtension(io.vertx.junit5.VertxExtension) Future(io.vertx.core.Future) Test(org.junit.jupiter.api.Test) List(java.util.List) Buffer(io.vertx.core.buffer.Buffer) VertxMockSupport(org.eclipse.hono.test.VertxMockSupport) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Assertions.assertTrue(org.junit.jupiter.api.Assertions.assertTrue) Optional(java.util.Optional) Mockito.mock(org.mockito.Mockito.mock) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) VertxTestContext(io.vertx.junit5.VertxTestContext) Assertions.assertNotNull(org.junit.jupiter.api.Assertions.assertNotNull) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) ClientErrorException(org.eclipse.hono.client.ClientErrorException) AtomicReference(java.util.concurrent.atomic.AtomicReference) ArrayList(java.util.ArrayList) CompositeFuture(io.vertx.core.CompositeFuture) ArgumentCaptor(org.mockito.ArgumentCaptor) Timer(io.micrometer.core.instrument.Timer) Assertions.assertEquals(org.junit.jupiter.api.Assertions.assertEquals) Tracer(io.opentracing.Tracer) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) Mockito.times(org.mockito.Mockito.times) Mockito.when(org.mockito.Mockito.when) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) Truth.assertThat(com.google.common.truth.Truth.assertThat) KafkaBasedCommandContext(org.eclipse.hono.client.command.kafka.KafkaBasedCommandContext) Mockito.verify(org.mockito.Mockito.verify) TenantObject(org.eclipse.hono.util.TenantObject) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) Mockito.never(org.mockito.Mockito.never) CommandRouterMetrics(org.eclipse.hono.commandrouter.CommandRouterMetrics) Handler(io.vertx.core.Handler) KafkaHeader(io.vertx.kafka.client.producer.KafkaHeader) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) ArgumentCaptor(org.mockito.ArgumentCaptor) CommandContext(org.eclipse.hono.client.command.CommandContext) KafkaBasedCommandContext(org.eclipse.hono.client.command.kafka.KafkaBasedCommandContext) JsonObject(io.vertx.core.json.JsonObject) List(java.util.List) ArrayList(java.util.ArrayList) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) Test(org.junit.jupiter.api.Test)

Example 12 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class AbstractMappingAndDelegatingCommandHandler method mapAndDelegateIncomingCommand.

/**
 * Delegates an incoming command to the protocol adapter instance that the target
 * device is connected to.
 * <p>
 * Determines the target gateway (if applicable) and protocol adapter instance for an incoming command
 * and delegates the command to the resulting protocol adapter instance.
 *
 * @param commandContext The context of the command to send.
 * @param timer The timer indicating the amount of time used for processing the command message.
 * @return A future indicating the outcome of the operation.
 * @throws NullPointerException if any of the parameters are {@code null}.
 */
protected final Future<Void> mapAndDelegateIncomingCommand(final CommandContext commandContext, final Timer.Sample timer) {
    Objects.requireNonNull(commandContext);
    Objects.requireNonNull(timer);
    final Command command = commandContext.getCommand();
    // determine last used gateway device id
    if (log.isTraceEnabled()) {
        log.trace("determine command target gateway/adapter for [{}]", command);
    }
    final Future<TenantObject> tenantObjectFuture = tenantClient.get(command.getTenant(), commandContext.getTracingContext());
    return tenantObjectFuture.compose(tenantObject -> {
        TenantTraceSamplingHelper.applyTraceSamplingPriority(tenantObject, null, commandContext.getTracingSpan());
        commandContext.put(CommandContext.KEY_TENANT_CONFIG, tenantObject);
        // check whether the handler messaging type is equal to the messaging type of the tenant (if set)
        final MessagingType tenantMessagingType = Optional.ofNullable(tenantObject.getProperty(TenantConstants.FIELD_EXT, JsonObject.class)).map(ext -> ext.getString(TenantConstants.FIELD_EXT_MESSAGING_TYPE)).map(MessagingType::valueOf).orElse(null);
        if (tenantMessagingType != null && getMessagingType() != tenantMessagingType) {
            log.info("command received via {} but tenant is configured to use {} [{}]", getMessagingType(), tenantMessagingType, commandContext.getCommand());
            commandContext.getTracingSpan().log(String.format("command received via %s but tenant is configured to use %s", getMessagingType(), tenantMessagingType));
        }
        return commandTargetMapper.getTargetGatewayAndAdapterInstance(command.getTenant(), command.getDeviceId(), commandContext.getTracingContext());
    }).recover(cause -> {
        final Throwable error;
        if (tenantObjectFuture.failed() && ServiceInvocationException.extractStatusCode(cause) == HttpURLConnection.HTTP_NOT_FOUND) {
            error = new TenantDisabledOrNotRegisteredException(command.getTenant(), HttpURLConnection.HTTP_NOT_FOUND);
        } else if (cause instanceof DeviceDisabledOrNotRegisteredException) {
            error = cause;
        } else if (ServiceInvocationException.extractStatusCode(cause) == HttpURLConnection.HTTP_NOT_FOUND) {
            log.debug("no target adapter instance found for command with device id " + command.getDeviceId(), cause);
            error = new NoConsumerException("no target adapter instance found");
        } else {
            log.debug("error getting target gateway and adapter instance for command with device id " + command.getDeviceId(), cause);
            error = new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "error getting target gateway and adapter instance", cause);
        }
        if (error instanceof ClientErrorException) {
            commandContext.reject(error);
        } else {
            commandContext.release(error);
        }
        reportCommandProcessingError(command, tenantObjectFuture.result(), error, timer);
        return Future.failedFuture(cause);
    }).compose(result -> {
        final String targetAdapterInstanceId = result.getString(DeviceConnectionConstants.FIELD_ADAPTER_INSTANCE_ID);
        final String targetDeviceId = result.getString(DeviceConnectionConstants.FIELD_PAYLOAD_DEVICE_ID);
        final String targetGatewayId = targetDeviceId.equals(command.getDeviceId()) ? null : targetDeviceId;
        if (Objects.isNull(targetGatewayId)) {
            log.trace("determined target adapter instance [{}] for [{}] (command not mapped to gateway)", targetAdapterInstanceId, command);
        } else {
            command.setGatewayId(targetGatewayId);
            log.trace("determined target gateway [{}] and adapter instance [{}] for [{}]", targetGatewayId, targetAdapterInstanceId, command);
            commandContext.getTracingSpan().log("determined target gateway [" + targetGatewayId + "]");
        }
        return sendCommand(commandContext, targetAdapterInstanceId, tenantObjectFuture.result(), timer);
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) InternalCommandSender(org.eclipse.hono.client.command.InternalCommandSender) Command(org.eclipse.hono.client.command.Command) TenantConstants(org.eclipse.hono.util.TenantConstants) Lifecycle(org.eclipse.hono.util.Lifecycle) LoggerFactory(org.slf4j.LoggerFactory) ClientErrorException(org.eclipse.hono.client.ClientErrorException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) Tags(io.opentracing.tag.Tags) NoConsumerException(org.eclipse.hono.client.NoConsumerException) DeviceDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.DeviceDisabledOrNotRegisteredException) MessagingType(org.eclipse.hono.util.MessagingType) Timer(io.micrometer.core.instrument.Timer) JsonObject(io.vertx.core.json.JsonObject) TracingHelper(org.eclipse.hono.tracing.TracingHelper) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) DeviceConnectionConstants(org.eclipse.hono.util.DeviceConnectionConstants) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) ServerErrorException(org.eclipse.hono.client.ServerErrorException) CommandTargetMapper(org.eclipse.hono.commandrouter.CommandTargetMapper) TenantClient(org.eclipse.hono.client.registry.TenantClient) Future(io.vertx.core.Future) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) Objects(java.util.Objects) CommandRouterMetrics(org.eclipse.hono.commandrouter.CommandRouterMetrics) TenantTraceSamplingHelper(org.eclipse.hono.tracing.TenantTraceSamplingHelper) Optional(java.util.Optional) Span(io.opentracing.Span) TenantObject(org.eclipse.hono.util.TenantObject) DeviceDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.DeviceDisabledOrNotRegisteredException) Command(org.eclipse.hono.client.command.Command) NoConsumerException(org.eclipse.hono.client.NoConsumerException) MessagingType(org.eclipse.hono.util.MessagingType) ClientErrorException(org.eclipse.hono.client.ClientErrorException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 13 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class KafkaBasedInternalCommandConsumerTest method testHandleCommandMessageWithHandlerForGatewayAndSpecificDevice.

/**
 * Verifies that the consumer handles a valid message, for which the matching command handler is associated
 * with a gateway, by invoking the handler and adopting the gateway identifier in the command object.
 */
@Test
void testHandleCommandMessageWithHandlerForGatewayAndSpecificDevice() {
    final String tenantId = "myTenant";
    final String deviceId = "4711";
    final String gatewayId = "gw-1";
    final String subject = "subject";
    final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, getHeaders(tenantId, deviceId, subject, 0L));
    final Handler<CommandContext> commandHandler = VertxMockSupport.mockHandler();
    commandHandlers.putCommandHandler(tenantId, deviceId, gatewayId, commandHandler, context);
    internalCommandConsumer.handleCommandMessage(commandRecord);
    final ArgumentCaptor<CommandContext> commandContextCaptor = ArgumentCaptor.forClass(CommandContext.class);
    verify(commandHandler).handle(commandContextCaptor.capture());
    assertThat(commandContextCaptor.getValue()).isNotNull();
    assertThat(commandContextCaptor.getValue().getCommand().isValid()).isTrue();
    // assert that command is directed at the gateway
    assertThat(commandContextCaptor.getValue().getCommand().getGatewayId()).isEqualTo(gatewayId);
    assertThat(commandContextCaptor.getValue().getCommand().getDeviceId()).isEqualTo(deviceId);
}
Also used : Buffer(io.vertx.core.buffer.Buffer) CommandContext(org.eclipse.hono.client.command.CommandContext) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 14 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class KafkaBasedInternalCommandConsumerTest method testHandleCommandMessageSendErrorResponse.

/**
 * Verifies that an error response is sent to the application if the tenant of the target device
 * is unknown or cannot be retrieved.
 */
@ParameterizedTest
@ValueSource(ints = { HttpURLConnection.HTTP_NOT_FOUND, HttpURLConnection.HTTP_UNAVAILABLE })
void testHandleCommandMessageSendErrorResponse(final int tenantServiceErrorCode) {
    final String tenantId = "myTenant";
    final String deviceId = "4711";
    final String subject = "subject";
    final Handler<CommandContext> commandHandler = VertxMockSupport.mockHandler();
    commandHandlers.putCommandHandler(tenantId, deviceId, null, commandHandler, context);
    when(tenantClient.get(eq("myTenant"), any())).thenReturn(Future.failedFuture(ServiceInvocationException.create(tenantServiceErrorCode)));
    final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, getHeaders(tenantId, deviceId, subject, 0L));
    internalCommandConsumer.handleCommandMessage(commandRecord);
    verify(commandHandler, never()).handle(any(KafkaBasedCommandContext.class));
    verify(commandResponseSender).sendCommandResponse(argThat(t -> t.getTenantId().equals("myTenant")), argThat(r -> r.getDeviceId().equals("4711")), argThat(cr -> cr.getStatus() == tenantServiceErrorCode), any());
}
Also used : Buffer(io.vertx.core.buffer.Buffer) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) HttpURLConnection(java.net.HttpURLConnection) BeforeEach(org.junit.jupiter.api.BeforeEach) ArgumentMatchers.argThat(org.mockito.ArgumentMatchers.argThat) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) CommandResponseSender(org.eclipse.hono.client.command.CommandResponseSender) Context(io.vertx.core.Context) ArrayList(java.util.ArrayList) ArgumentCaptor(org.mockito.ArgumentCaptor) Mockito.doAnswer(org.mockito.Mockito.doAnswer) Admin(org.apache.kafka.clients.admin.Admin) TracingMockSupport(org.eclipse.hono.test.TracingMockSupport) CommandHandlers(org.eclipse.hono.client.command.CommandHandlers) KafkaClientUnitTestHelper(org.eclipse.hono.kafka.test.KafkaClientUnitTestHelper) ValueSource(org.junit.jupiter.params.provider.ValueSource) InOrder(org.mockito.InOrder) Tracer(io.opentracing.Tracer) CommandContext(org.eclipse.hono.client.command.CommandContext) Vertx(io.vertx.core.Vertx) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) Mockito.when(org.mockito.Mockito.when) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) TenantClient(org.eclipse.hono.client.registry.TenantClient) Truth.assertThat(com.google.common.truth.Truth.assertThat) Future(io.vertx.core.Future) Mockito.verify(org.mockito.Mockito.verify) CommandResponse(org.eclipse.hono.client.command.CommandResponse) TenantObject(org.eclipse.hono.util.TenantObject) Test(org.junit.jupiter.api.Test) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) Mockito.never(org.mockito.Mockito.never) List(java.util.List) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) Buffer(io.vertx.core.buffer.Buffer) VertxMockSupport(org.eclipse.hono.test.VertxMockSupport) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Mockito.inOrder(org.mockito.Mockito.inOrder) Span(io.opentracing.Span) KafkaConsumer(io.vertx.kafka.client.consumer.KafkaConsumer) Handler(io.vertx.core.Handler) KafkaHeader(io.vertx.kafka.client.producer.KafkaHeader) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) Mockito.mock(org.mockito.Mockito.mock) CommandContext(org.eclipse.hono.client.command.CommandContext) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) ValueSource(org.junit.jupiter.params.provider.ValueSource) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 15 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class KafkaBasedInternalCommandConsumerTest method testHandleCommandMessageWithHandlerForDevice.

/**
 * Verifies that the consumer handles a valid message by invoking the matching command handler.
 */
@Test
void testHandleCommandMessageWithHandlerForDevice() {
    final String tenantId = "myTenant";
    final String deviceId = "4711";
    final String subject = "subject";
    final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, getHeaders(tenantId, deviceId, subject, 0L));
    final Handler<CommandContext> commandHandler = VertxMockSupport.mockHandler();
    commandHandlers.putCommandHandler(tenantId, deviceId, null, commandHandler, context);
    internalCommandConsumer.handleCommandMessage(commandRecord);
    final ArgumentCaptor<CommandContext> commandContextCaptor = ArgumentCaptor.forClass(CommandContext.class);
    verify(commandHandler).handle(commandContextCaptor.capture());
    assertThat(commandContextCaptor.getValue()).isNotNull();
    assertThat(commandContextCaptor.getValue().getCommand().isValid()).isTrue();
}
Also used : Buffer(io.vertx.core.buffer.Buffer) CommandContext(org.eclipse.hono.client.command.CommandContext) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Aggregations

CommandContext (org.eclipse.hono.client.command.CommandContext)34 Buffer (io.vertx.core.buffer.Buffer)22 Future (io.vertx.core.Future)19 HttpURLConnection (java.net.HttpURLConnection)18 TenantObject (org.eclipse.hono.util.TenantObject)17 Test (org.junit.jupiter.api.Test)17 Span (io.opentracing.Span)15 Handler (io.vertx.core.Handler)15 ServerErrorException (org.eclipse.hono.client.ServerErrorException)15 Promise (io.vertx.core.Promise)14 Optional (java.util.Optional)14 ClientErrorException (org.eclipse.hono.client.ClientErrorException)14 Command (org.eclipse.hono.client.command.Command)14 List (java.util.List)13 Objects (java.util.Objects)13 SpanContext (io.opentracing.SpanContext)12 ArgumentMatchers.anyString (org.mockito.ArgumentMatchers.anyString)12 TracingHelper (org.eclipse.hono.tracing.TracingHelper)11 Tracer (io.opentracing.Tracer)10 CompositeFuture (io.vertx.core.CompositeFuture)10