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