Search in sources :

Example 21 with CommandContext

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

the class KafkaBasedInternalCommandConsumer method handleCommandMessage.

void handleCommandMessage(final KafkaConsumerRecord<String, Buffer> record) {
    // get partition/offset of the command record - related to the tenant-based topic the command was originally received in
    final Integer commandPartition = KafkaRecordHelper.getOriginalPartitionHeader(record.headers()).orElse(null);
    final Long commandOffset = KafkaRecordHelper.getOriginalOffsetHeader(record.headers()).orElse(null);
    if (commandPartition == null || commandOffset == null) {
        LOG.warn("command record is invalid - missing required original partition/offset headers");
        return;
    }
    final KafkaBasedCommand command;
    try {
        command = KafkaBasedCommand.fromRoutedCommandRecord(record);
    } catch (final IllegalArgumentException e) {
        LOG.warn("command record is invalid [tenant-id: {}, device-id: {}]", KafkaRecordHelper.getTenantId(record.headers()).orElse(null), KafkaRecordHelper.getDeviceId(record.headers()).orElse(null), e);
        return;
    }
    // check whether command has already been received and handled;
    // partition index and offset here are related to the *tenant-based* topic the command was originally received in
    // therefore they are stored in a map with the tenant as key
    final Map<Integer, Long> lastHandledPartitionOffsets = lastHandledPartitionOffsetsPerTenant.computeIfAbsent(command.getTenant(), k -> new HashMap<>());
    final Long lastHandledOffset = lastHandledPartitionOffsets.get(commandPartition);
    if (lastHandledOffset != null && commandOffset <= lastHandledOffset) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("ignoring command - record partition offset {} <= last handled offset {} [{}]", commandOffset, lastHandledOffset, command);
        }
    } else {
        lastHandledPartitionOffsets.put(commandPartition, commandOffset);
        final CommandHandlerWrapper commandHandler = commandHandlers.getCommandHandler(command.getTenant(), command.getGatewayOrDeviceId());
        if (commandHandler != null && commandHandler.getGatewayId() != null) {
            // Gateway information set in command handler means a gateway has subscribed for commands for a specific device.
            // This information isn't getting set in the record (by the Command Router) and therefore has to be adopted manually here.
            command.setGatewayId(commandHandler.getGatewayId());
        }
        final SpanContext spanContext = KafkaTracingHelper.extractSpanContext(tracer, record);
        final SpanContext followsFromSpanContext = commandHandler != null ? commandHandler.getConsumerCreationSpanContext() : null;
        final Span currentSpan = CommandContext.createSpan(tracer, command, spanContext, followsFromSpanContext, getClass().getSimpleName());
        currentSpan.setTag(MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, adapterInstanceId);
        KafkaTracingHelper.TAG_OFFSET.set(currentSpan, record.offset());
        final var commandContext = new KafkaBasedCommandContext(command, commandResponseSender, currentSpan);
        tenantClient.get(command.getTenant(), spanContext).onFailure(t -> {
            if (ServiceInvocationException.extractStatusCode(t) == HttpURLConnection.HTTP_NOT_FOUND) {
                commandContext.reject(new TenantDisabledOrNotRegisteredException(command.getTenant(), HttpURLConnection.HTTP_NOT_FOUND));
            } else {
                commandContext.release(new ServerErrorException(command.getTenant(), HttpURLConnection.HTTP_UNAVAILABLE, "error retrieving tenant configuration", t));
            }
        }).onSuccess(tenantConfig -> {
            commandContext.put(CommandContext.KEY_TENANT_CONFIG, tenantConfig);
            if (commandHandler != null) {
                LOG.trace("using [{}] for received command [{}]", commandHandler, command);
                // command.isValid() check not done here - it is to be done in the command handler
                commandHandler.handleCommand(commandContext);
            } else {
                LOG.info("no command handler found for command [{}]", command);
                commandContext.release(new NoConsumerException("no command handler found for command"));
            }
        });
    }
}
Also used : HttpURLConnection(java.net.HttpURLConnection) MessagingKafkaConsumerConfigProperties(org.eclipse.hono.client.kafka.consumer.MessagingKafkaConsumerConfigProperties) LoggerFactory(org.slf4j.LoggerFactory) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) HashMap(java.util.HashMap) CommandHandlerWrapper(org.eclipse.hono.client.command.CommandHandlerWrapper) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) Supplier(java.util.function.Supplier) CommandResponseSender(org.eclipse.hono.client.command.CommandResponseSender) KafkaClientFactory(org.eclipse.hono.client.kafka.KafkaClientFactory) Context(io.vertx.core.Context) NoConsumerException(org.eclipse.hono.client.NoConsumerException) CompositeFuture(io.vertx.core.CompositeFuture) Status(io.vertx.ext.healthchecks.Status) HealthCheckHandler(io.vertx.ext.healthchecks.HealthCheckHandler) KafkaClientMetricsSupport(org.eclipse.hono.client.kafka.metrics.KafkaClientMetricsSupport) Map(java.util.Map) KafkaAdminClientConfigProperties(org.eclipse.hono.client.kafka.KafkaAdminClientConfigProperties) Admin(org.apache.kafka.clients.admin.Admin) CommandHandlers(org.eclipse.hono.client.command.CommandHandlers) KafkaTracingHelper(org.eclipse.hono.client.kafka.tracing.KafkaTracingHelper) CommonClientConfigs(org.apache.kafka.clients.CommonClientConfigs) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) Promise(io.vertx.core.Promise) NewTopic(org.apache.kafka.clients.admin.NewTopic) CommandContext(org.eclipse.hono.client.command.CommandContext) Vertx(io.vertx.core.Vertx) Set(java.util.Set) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ConsumerConfig(org.apache.kafka.clients.consumer.ConsumerConfig) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) TenantClient(org.eclipse.hono.client.registry.TenantClient) MessageHelper(org.eclipse.hono.util.MessageHelper) Future(io.vertx.core.Future) InternalCommandConsumer(org.eclipse.hono.client.command.InternalCommandConsumer) SpanContext(io.opentracing.SpanContext) TopicPartition(io.vertx.kafka.client.common.TopicPartition) Objects(java.util.Objects) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) List(java.util.List) TopicExistsException(org.apache.kafka.common.errors.TopicExistsException) Buffer(io.vertx.core.buffer.Buffer) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Optional(java.util.Optional) Span(io.opentracing.Span) KafkaConsumer(io.vertx.kafka.client.consumer.KafkaConsumer) SpanContext(io.opentracing.SpanContext) NoConsumerException(org.eclipse.hono.client.NoConsumerException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) Span(io.opentracing.Span) CommandHandlerWrapper(org.eclipse.hono.client.command.CommandHandlerWrapper) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 22 with CommandContext

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

the class KafkaBasedInternalCommandSender method sendCommand.

@Override
public Future<Void> sendCommand(final CommandContext commandContext, final String adapterInstanceId) {
    Objects.requireNonNull(commandContext);
    Objects.requireNonNull(adapterInstanceId);
    final Command command = commandContext.getCommand();
    if (!(command instanceof KafkaBasedCommand)) {
        commandContext.release();
        log.error("command is not an instance of KafkaBasedCommand");
        throw new IllegalArgumentException("command is not an instance of KafkaBasedCommand");
    }
    final String topicName = getInternalCommandTopic(adapterInstanceId);
    final Span currentSpan = startChildSpan("delegate Command request", topicName, command.getTenant(), command.getDeviceId(), commandContext.getTracingContext());
    return sendAndWaitForOutcome(topicName, command.getTenant(), command.getDeviceId(), command.getPayload(), getHeaders((KafkaBasedCommand) command), currentSpan).onSuccess(v -> commandContext.accept()).onFailure(thr -> commandContext.release(new ServerErrorException(command.getTenant(), HttpURLConnection.HTTP_UNAVAILABLE, "failed to publish command message on internal command topic", thr))).onComplete(ar -> currentSpan.finish());
}
Also used : HttpURLConnection(java.net.HttpURLConnection) InternalCommandSender(org.eclipse.hono.client.command.InternalCommandSender) MessagingKafkaProducerConfigProperties(org.eclipse.hono.client.kafka.producer.MessagingKafkaProducerConfigProperties) Tracer(io.opentracing.Tracer) Command(org.eclipse.hono.client.command.Command) CommandContext(org.eclipse.hono.client.command.CommandContext) ServerErrorException(org.eclipse.hono.client.ServerErrorException) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) Future(io.vertx.core.Future) ArrayList(java.util.ArrayList) Objects(java.util.Objects) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) List(java.util.List) KafkaProducerFactory(org.eclipse.hono.client.kafka.producer.KafkaProducerFactory) Buffer(io.vertx.core.buffer.Buffer) Optional(java.util.Optional) Span(io.opentracing.Span) KafkaHeader(io.vertx.kafka.client.producer.KafkaHeader) AbstractKafkaBasedMessageSender(org.eclipse.hono.client.kafka.producer.AbstractKafkaBasedMessageSender) Command(org.eclipse.hono.client.command.Command) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Span(io.opentracing.Span)

Example 23 with CommandContext

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

the class KafkaBasedInternalCommandConsumerTest method testHandleCommandMessageWithInvalidRecord.

/**
 * Verifies that the consumer handles a command record with missing subject by invoking the matching handler.
 */
@Test
void testHandleCommandMessageWithInvalidRecord() {
    // command record with missing subject header
    final String tenantId = "myTenant";
    final String deviceId = "4711";
    final List<KafkaHeader> headers = List.of(KafkaRecordHelper.createTenantIdHeader(tenantId), KafkaRecordHelper.createDeviceIdHeader(deviceId), KafkaRecordHelper.createOriginalPartitionHeader(0), KafkaRecordHelper.createOriginalOffsetHeader(0L));
    final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, headers);
    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();
    // assert that command is not valid
    assertThat(commandContextCaptor.getValue().getCommand().isValid()).isFalse();
    assertThat(commandContextCaptor.getValue().getCommand().getInvalidCommandReason()).contains("subject");
}
Also used : Buffer(io.vertx.core.buffer.Buffer) CommandContext(org.eclipse.hono.client.command.CommandContext) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) KafkaHeader(io.vertx.kafka.client.producer.KafkaHeader) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 24 with CommandContext

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

the class KafkaBasedInternalCommandConsumerTest method testHandleCommandMessageWithHandlerForGateway.

/**
 * Verifies that the consumer handles a valid message, targeted at a gateway, by invoking the matching command
 * handler.
 */
@Test
void testHandleCommandMessageWithHandlerForGateway() {
    final String tenantId = "myTenant";
    final String deviceId = "4711";
    final String gatewayId = "gw-1";
    final String subject = "subject";
    final List<KafkaHeader> headers = new ArrayList<>(getHeaders(tenantId, deviceId, subject, 0L));
    headers.add(KafkaRecordHelper.createViaHeader(gatewayId));
    final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, headers);
    final Handler<CommandContext> commandHandler = VertxMockSupport.mockHandler();
    commandHandlers.putCommandHandler(tenantId, gatewayId, 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();
    // 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) ArrayList(java.util.ArrayList) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) KafkaHeader(io.vertx.kafka.client.producer.KafkaHeader) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 25 with CommandContext

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

the class KafkaBasedInternalCommandConsumerTest method testHandleDuplicateCommandMessage.

/**
 * Verifies that the consumer doesn't invoke a matching command handler if the command record
 * has a partition offset smaller or equal to one of a command that was already handled.
 */
@Test
void testHandleDuplicateCommandMessage() {
    final String tenantId = "myTenant";
    final String deviceId = "4711";
    final String subject = "subject";
    final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, getHeaders(tenantId, deviceId, subject, 10L));
    // 2nd command - with smaller offset, indicating it is a duplicate
    final KafkaConsumerRecord<String, Buffer> commandRecord2 = getCommandRecord(deviceId, getHeaders(tenantId, deviceId, subject, 5L));
    final Handler<CommandContext> commandHandler = VertxMockSupport.mockHandler();
    commandHandlers.putCommandHandler(tenantId, deviceId, null, commandHandler, context);
    internalCommandConsumer.handleCommandMessage(commandRecord);
    internalCommandConsumer.handleCommandMessage(commandRecord2);
    final InOrder inOrder = inOrder(commandHandler);
    final ArgumentCaptor<CommandContext> commandContextCaptor = ArgumentCaptor.forClass(CommandContext.class);
    // first invocation
    inOrder.verify(commandHandler).handle(commandContextCaptor.capture());
    assertThat(commandContextCaptor.getValue()).isNotNull();
    assertThat(commandContextCaptor.getValue().getCommand().isValid()).isTrue();
    // verify there was no second invocation
    inOrder.verify(commandHandler, never()).handle(any());
}
Also used : Buffer(io.vertx.core.buffer.Buffer) InOrder(org.mockito.InOrder) 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