use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.
the class AbstractMappingAndDelegatingCommandHandler method reportInvalidCommand.
/**
* Reports an invalid command.
*
* @param commandContext The context of the command to report.
* @param timer The timer indicating the amount of time used for processing the command message.
*/
protected void reportInvalidCommand(final CommandContext commandContext, final Timer.Sample timer) {
final Command command = commandContext.getCommand();
final Future<TenantObject> tenantObjectFuture = tenantClient.get(command.getTenant(), commandContext.getTracingContext());
tenantObjectFuture.recover(// ignore error here
thr -> Future.succeededFuture(null)).onSuccess(tenantObjectOrNull -> {
metrics.reportCommand(command.isOneWay() ? MetricsTags.Direction.ONE_WAY : MetricsTags.Direction.REQUEST, command.getTenant(), tenantObjectOrNull, MetricsTags.ProcessingOutcome.UNPROCESSABLE, command.getPayloadSize(), timer);
});
}
use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.
the class KafkaBasedMappingAndDelegatingCommandHandler method mapAndDelegateIncomingCommandMessage.
/**
* 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 consumerRecord The consumer record corresponding to the command.
* @return A future indicating the outcome of the operation.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
public Future<Void> mapAndDelegateIncomingCommandMessage(final KafkaConsumerRecord<String, Buffer> consumerRecord) {
Objects.requireNonNull(consumerRecord);
final Timer.Sample timer = getMetrics().startTimer();
final KafkaBasedCommand command;
try {
command = KafkaBasedCommand.from(consumerRecord);
} catch (final IllegalArgumentException exception) {
log.debug("command record is invalid", exception);
return Future.failedFuture("command record is invalid");
}
final SpanContext spanContext = KafkaTracingHelper.extractSpanContext(tracer, consumerRecord);
final Span currentSpan = createSpan(command.getTenant(), command.getDeviceId(), spanContext);
KafkaTracingHelper.setRecordTags(currentSpan, consumerRecord);
final KafkaBasedCommandContext commandContext = new KafkaBasedCommandContext(command, kafkaBasedCommandResponseSender, currentSpan);
command.logToSpan(currentSpan);
if (!command.isValid()) {
log.debug("received invalid command record [{}]", command);
return tenantClient.get(command.getTenant(), currentSpan.context()).compose(tenantConfig -> {
commandContext.put(CommandContext.KEY_TENANT_CONFIG, tenantConfig);
return Future.failedFuture("command is invalid");
}).onComplete(ar -> {
commandContext.reject("malformed command message");
reportInvalidCommand(commandContext, timer);
}).mapEmpty();
}
log.trace("received valid command record [{}]", command);
commandQueue.add(commandContext);
final Promise<Void> resultPromise = Promise.promise();
final long timerId = vertx.setTimer(PROCESSING_TIMEOUT.toMillis(), tid -> {
if (commandQueue.remove(commandContext) || !commandContext.isCompleted()) {
log.info("command processing timed out after {}s [{}]", PROCESSING_TIMEOUT.toSeconds(), commandContext.getCommand());
TracingHelper.logError(commandContext.getTracingSpan(), String.format("command processing timed out after %ds", PROCESSING_TIMEOUT.toSeconds()));
final ServerErrorException error = new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "command processing timed out");
commandContext.release(error);
resultPromise.tryFail(error);
}
});
mapAndDelegateIncomingCommand(commandContext, timer).onComplete(ar -> {
vertx.cancelTimer(timerId);
if (ar.failed()) {
commandQueue.remove(commandContext);
}
Futures.tryHandleResult(resultPromise, ar);
});
return resultPromise.future();
}
use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.
the class KafkaBasedMappingAndDelegatingCommandHandlerTest method testCommandDelegationTimesOutWhileCommandGetsSent.
/**
* Verifies the behaviour of the
* {@link KafkaBasedMappingAndDelegatingCommandHandler#mapAndDelegateIncomingCommandMessage(KafkaConsumerRecord)}
* method in a scenario where mapping/delegation of a valid command record times out while the command gets sent.
*/
@Test
public void testCommandDelegationTimesOutWhileCommandGetsSent() {
final AtomicReference<Handler<Long>> timerHandlerRef = new AtomicReference<>();
when(vertx.setTimer(anyLong(), VertxMockSupport.anyHandler())).thenAnswer(invocation -> {
final Handler<Long> handler = invocation.getArgument(1);
timerHandlerRef.set(handler);
return 1L;
});
// GIVEN a valid command record
final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(tenantId, deviceId, "cmd-subject", 0, 0);
final Promise<Void> sendCommandPromise = Promise.promise();
when(internalCommandSender.sendCommand(any(CommandContext.class), anyString())).thenReturn(sendCommandPromise.future());
// WHEN mapping and delegating the command (with no timeout triggered yet)
final Future<Void> resultFuture = cmdHandler.mapAndDelegateIncomingCommandMessage(commandRecord);
final ArgumentCaptor<KafkaBasedCommandContext> commandContextArgumentCaptor = ArgumentCaptor.forClass(KafkaBasedCommandContext.class);
// THEN the message is properly delegated
verify(internalCommandSender, times(1)).sendCommand(commandContextArgumentCaptor.capture(), eq(adapterInstanceId));
final KafkaBasedCommandContext commandContext = commandContextArgumentCaptor.getValue();
assertNotNull(commandContext);
assertTrue(commandContext.getCommand().isValid());
assertEquals(tenantId, commandContext.getCommand().getTenant());
assertEquals(deviceId, commandContext.getCommand().getDeviceId());
assertEquals("cmd-subject", commandContext.getCommand().getName());
assertThat(resultFuture.isComplete()).isFalse();
// WHEN the timeout is triggered and then sending the command succeeds
timerHandlerRef.get().handle(1L);
sendCommandPromise.complete();
// THEN the result future is failed because the timeout came first
assertThat(resultFuture.failed()).isTrue();
}
use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.
the class KafkaBasedMappingAndDelegatingCommandHandlerTest method testIncomingCommandOrderIsPreservedWhenDelegating.
/**
* Verifies the behaviour of the
* {@link KafkaBasedMappingAndDelegatingCommandHandler#mapAndDelegateIncomingCommandMessage(KafkaConsumerRecord)}
* method in a scenario where the mapping operation for one command completes earlier than for a previously received
* command. The order in which commands are then delegated to the target adapter instance has to be the same
* as the order in which commands were received.
*
* @param ctx The vert.x test context
*/
@Test
public void testIncomingCommandOrderIsPreservedWhenDelegating(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, commandRecord4
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.complete(createTargetAdapterInstanceJson(deviceId1, adapterInstanceId));
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
CompositeFuture.all(cmd1Future, cmd2Future, cmd3Future, cmd4Future).onComplete(ctx.succeeding(r -> {
ctx.verify(() -> {
final ArgumentCaptor<CommandContext> commandContextCaptor = ArgumentCaptor.forClass(CommandContext.class);
verify(internalCommandSender, times(4)).sendCommand(commandContextCaptor.capture(), anyString());
final List<CommandContext> capturedCommandContexts = commandContextCaptor.getAllValues();
assertThat(capturedCommandContexts.get(0).getCommand().getDeviceId()).isEqualTo(deviceId1);
assertThat(capturedCommandContexts.get(1).getCommand().getDeviceId()).isEqualTo(deviceId2);
assertThat(capturedCommandContexts.get(2).getCommand().getDeviceId()).isEqualTo(deviceId3);
assertThat(capturedCommandContexts.get(3).getCommand().getDeviceId()).isEqualTo(deviceId4);
});
ctx.completeNow();
}));
}
Aggregations