use of org.eclipse.hono.client.command.CommandResponseSender 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());
}
use of org.eclipse.hono.client.command.CommandResponseSender in project hono by eclipse.
the class AbstractProtocolAdapterApplication method setCollaborators.
/**
* Sets collaborators required by all protocol adapters.
*
* @param adapter The adapter to set the collaborators on.
* @throws NullPointerException if adapter is {@code null}
* @throws IllegalStateException if no connection to the Command Router service has been configured.
*/
protected void setCollaborators(final AbstractProtocolAdapterBase<?> adapter) {
Objects.requireNonNull(adapter);
final DeviceRegistrationClient registrationClient = registrationClient();
final MessagingClientProvider<TelemetrySender> telemetrySenderProvider = new MessagingClientProvider<>();
final MessagingClientProvider<EventSender> eventSenderProvider = new MessagingClientProvider<>();
final MessagingClientProvider<CommandResponseSender> commandResponseSenderProvider = new MessagingClientProvider<>();
final KafkaClientMetricsSupport kafkaClientMetricsSupport = kafkaClientMetricsSupport(kafkaMetricsOptions);
final var tenantClient = tenantClient();
if (kafkaEventConfig.isConfigured()) {
LOG.info("Kafka client configuration present, adding Kafka messaging clients");
final KafkaProducerFactory<String, Buffer> factory = CachingKafkaProducerFactory.sharedFactory(vertx);
factory.setMetricsSupport(kafkaClientMetricsSupport);
telemetrySenderProvider.setClient(new KafkaBasedTelemetrySender(vertx, factory, kafkaTelemetryConfig, protocolAdapterProperties.isDefaultsEnabled(), tracer));
eventSenderProvider.setClient(new KafkaBasedEventSender(vertx, factory, kafkaEventConfig, protocolAdapterProperties.isDefaultsEnabled(), tracer));
commandResponseSenderProvider.setClient(new KafkaBasedCommandResponseSender(vertx, factory, kafkaCommandResponseConfig, tracer));
}
if (downstreamSenderConfig.isHostConfigured()) {
telemetrySenderProvider.setClient(downstreamSender());
eventSenderProvider.setClient(downstreamSender());
commandResponseSenderProvider.setClient(new ProtonBasedCommandResponseSender(HonoConnection.newConnection(vertx, commandResponseSenderConfig(), tracer), messageSamplerFactory, protocolAdapterProperties.isJmsVendorPropsEnabled()));
}
final MessagingClientProviders messagingClientProviders = new MessagingClientProviders(telemetrySenderProvider, eventSenderProvider, commandResponseSenderProvider);
if (commandRouterConfig.isHostConfigured()) {
final CommandRouterClient commandRouterClient = commandRouterClient();
adapter.setCommandRouterClient(commandRouterClient);
final CommandRouterCommandConsumerFactory commandConsumerFactory = commandConsumerFactory(commandRouterClient);
if (commandConsumerConfig.isHostConfigured()) {
commandConsumerFactory.registerInternalCommandConsumer((id, handlers) -> new ProtonBasedInternalCommandConsumer(commandConsumerConnection(), id, handlers));
}
final CommandResponseSender kafkaCommandResponseSender = messagingClientProviders.getCommandResponseSenderProvider().getClient(MessagingType.kafka);
if (kafkaCommandInternalConfig.isConfigured() && kafkaCommandConfig.isConfigured() && kafkaCommandResponseSender != null) {
commandConsumerFactory.registerInternalCommandConsumer((id, handlers) -> new KafkaBasedInternalCommandConsumer(vertx, kafkaCommandInternalConfig, kafkaCommandConfig, tenantClient, kafkaCommandResponseSender, id, handlers, tracer).setMetricsSupport(kafkaClientMetricsSupport));
}
adapter.setCommandConsumerFactory(commandConsumerFactory);
} else {
throw new IllegalStateException("No Command Router connection configured");
}
adapter.setMessagingClientProviders(messagingClientProviders);
Optional.ofNullable(connectionEventProducer()).ifPresent(adapter::setConnectionEventProducer);
adapter.setCredentialsClient(credentialsClient());
adapter.setHealthCheckServer(healthCheckServer);
adapter.setRegistrationClient(registrationClient);
adapter.setResourceLimitChecks(prometheusResourceLimitChecks(resourceLimitChecksConfig, tenantClient));
adapter.setTenantClient(tenantClient);
adapter.setTracer(tracer);
}
use of org.eclipse.hono.client.command.CommandResponseSender 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"));
}
});
}
}
use of org.eclipse.hono.client.command.CommandResponseSender in project hono by eclipse.
the class KafkaBasedInternalCommandConsumerTest method setUp.
/**
* Sets up fixture.
*/
@BeforeEach
public void setUp() {
final Admin kafkaAdminClient = mock(Admin.class);
@SuppressWarnings("unchecked") final KafkaConsumer<String, Buffer> kafkaConsumer = mock(KafkaConsumer.class);
final String adapterInstanceId = "adapterInstanceId";
final Span span = TracingMockSupport.mockSpan();
final Tracer tracer = TracingMockSupport.mockTracer(span);
context = VertxMockSupport.mockContext(mock(Vertx.class));
commandHandlers = new CommandHandlers();
tenantClient = mock(TenantClient.class);
doAnswer(invocation -> {
final String tenantId = invocation.getArgument(0);
return Future.succeededFuture(TenantObject.from(tenantId));
}).when(tenantClient).get(anyString(), any());
commandResponseSender = mock(CommandResponseSender.class);
when(commandResponseSender.sendCommandResponse(any(TenantObject.class), any(RegistrationAssertion.class), any(CommandResponse.class), any())).thenReturn(Future.succeededFuture());
internalCommandConsumer = new KafkaBasedInternalCommandConsumer(context, kafkaAdminClient, kafkaConsumer, "testClientId", tenantClient, commandResponseSender, adapterInstanceId, commandHandlers, tracer);
}
use of org.eclipse.hono.client.command.CommandResponseSender in project hono by eclipse.
the class CommandResponseResourceTest method testUploadCommandResponseFailsForDisabledTenant.
/**
* Verifies that the adapter fails the upload of a command response with a 4.03
* if the adapter is disabled for the device's tenant.
*
* @param ctx The vert.x test context.
*/
@Test
public void testUploadCommandResponseFailsForDisabledTenant(final VertxTestContext ctx) {
// GIVEN an adapter
givenAnAdapter(properties);
final var resource = givenAResource(adapter);
final Promise<Void> outcome = Promise.promise();
final CommandResponseSender sender = givenACommandResponseSenderForAnyTenant(outcome);
// that is not enabled for a device's tenant
when(adapter.isAdapterEnabled(any(TenantObject.class))).thenReturn(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN)));
// WHEN a device publishes an command response
final String reqId = Commands.encodeRequestIdParameters("correlation", "replyToId", "device", MessagingType.amqp);
final Buffer payload = Buffer.buffer("some payload");
final OptionSet options = new OptionSet();
options.addUriPath(CommandConstants.COMMAND_RESPONSE_ENDPOINT).addUriPath(reqId);
options.addUriQuery(String.format("%s=%d", Constants.HEADER_COMMAND_RESPONSE_STATUS, 200));
options.setContentFormat(MediaTypeRegistry.TEXT_PLAIN);
final CoapExchange coapExchange = newCoapExchange(payload, Type.CON, options);
final Device authenticatedDevice = new Device("tenant", "device");
final CoapContext context = CoapContext.fromRequest(coapExchange, authenticatedDevice, authenticatedDevice, "device", span);
resource.uploadCommandResponseMessage(context).onComplete(ctx.failing(t -> {
ctx.verify(() -> {
// THEN the command response has not been forwarded downstream
verify(sender, never()).sendCommandResponse(any(TenantObject.class), any(RegistrationAssertion.class), any(CommandResponse.class), any(SpanContext.class));
// and the device gets a 4.03 response
assertThat(t).isInstanceOf(ClientErrorException.class);
assertThat(((ClientErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_FORBIDDEN);
// and the response has not been reported as forwarded
verify(metrics, never()).reportCommand(eq(Direction.RESPONSE), eq("tenant"), any(), eq(ProcessingOutcome.FORWARDED), anyInt(), any());
});
ctx.completeNow();
}));
}
Aggregations