use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.
the class LoraProtocolAdapterTest method handleCommandForLNS.
/**
* Verifies that an uplink message triggers a command subscription.
*/
@SuppressWarnings("unchecked")
@Test
public void handleCommandForLNS() {
givenATelemetrySenderForAnyTenant();
final LoraProvider providerMock = getLoraProviderMock();
final HttpServerRequest request = mock(HttpServerRequest.class);
final HttpContext httpContext = newHttpContext();
when(httpContext.request()).thenReturn(request);
final CommandEndpoint commandEndpoint = new CommandEndpoint();
commandEndpoint.setHeaders(Map.of("my-header", "my-header-value"));
commandEndpoint.setUri("https://my-server.com/commands/{{deviceId}}/send");
setGatewayDeviceCommandEndpoint(commandEndpoint);
final CommandConsumer commandConsumer = mock(CommandConsumer.class);
when(commandConsumer.close(any())).thenReturn(Future.succeededFuture());
when(commandConsumerFactory.createCommandConsumer(any(), any(), any(), any(), any())).thenReturn(Future.succeededFuture(commandConsumer));
adapter.handleProviderRoute(httpContext, providerMock);
final ArgumentCaptor<Handler<CommandContext>> handlerArgumentCaptor = VertxMockSupport.argumentCaptorHandler();
verify(commandConsumerFactory).createCommandConsumer(eq(TEST_TENANT_ID), eq(TEST_GATEWAY_ID), handlerArgumentCaptor.capture(), isNull(), any());
final Handler<CommandContext> commandHandler = handlerArgumentCaptor.getValue();
final Command command = mock(Command.class);
when(command.getTenant()).thenReturn(TEST_TENANT_ID);
when(command.getDeviceId()).thenReturn(TEST_DEVICE_ID);
when(command.getGatewayId()).thenReturn(TEST_GATEWAY_ID);
when(command.getPayload()).thenReturn(Buffer.buffer("bumlux"));
when(command.isValid()).thenReturn(true);
final CommandContext commandContext = mock(CommandContext.class);
when(commandContext.getCommand()).thenReturn(command);
when(commandContext.getTracingSpan()).thenReturn(processMessageSpan);
final JsonObject json = new JsonObject().put("my-payload", "bumlux");
final LoraCommand loraCommand = new LoraCommand(json, "https://my-server.com/commands/deviceId/send");
when(providerMock.getCommand(any(), any(), any(), any())).thenReturn(loraCommand);
when(providerMock.getDefaultHeaders()).thenReturn(Map.of("my-provider-header", "my-provider-header-value"));
final HttpRequest<Buffer> httpClientRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_NO_CONTENT);
when(httpClientRequest.sendJson(any(JsonObject.class))).thenReturn(Future.succeededFuture(httpResponse));
when(webClient.postAbs(anyString())).thenReturn(httpClientRequest);
commandHandler.handle(commandContext);
verify(webClient, times(1)).postAbs("https://my-server.com/commands/deviceId/send");
verify(httpClientRequest, times(1)).putHeader("my-header", "my-header-value");
verify(httpClientRequest, times(1)).putHeader("my-provider-header", "my-provider-header-value");
verify(httpClientRequest, times(1)).sendJson(json);
}
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();
}
use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method onCommandReceived.
/**
* Invoked for every valid command that has been received from
* an application.
* <p>
* This implementation simply forwards the command to the device
* via the given link.
*
* @param tenantObject The tenant configuration object.
* @param sender The link for sending the command to the device.
* @param commandContext The context in which the adapter receives the command message.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
protected void onCommandReceived(final TenantObject tenantObject, final ProtonSender sender, final CommandContext commandContext) {
Objects.requireNonNull(tenantObject);
Objects.requireNonNull(sender);
Objects.requireNonNull(commandContext);
final Command command = commandContext.getCommand();
final AtomicBoolean isCommandSettled = new AtomicBoolean(false);
if (sender.sendQueueFull()) {
log.debug("cannot send command to device: no credit available [{}]", command);
commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "no credit available for sending command to device"));
reportSentCommand(tenantObject, commandContext, ProcessingOutcome.UNDELIVERABLE);
} else {
final Message msg = ProtonHelper.message();
msg.setAddress(String.format("%s/%s/%s", CommandConstants.COMMAND_ENDPOINT, command.getTenant(), command.getDeviceId()));
msg.setCorrelationId(command.getCorrelationId());
msg.setSubject(command.getName());
MessageHelper.setPayload(msg, command.getContentType(), command.getPayload());
if (command.isTargetedAtGateway()) {
MessageHelper.addDeviceId(msg, command.getDeviceId());
}
if (!command.isOneWay()) {
msg.setReplyTo(String.format("%s/%s/%s", CommandConstants.COMMAND_RESPONSE_ENDPOINT, command.getTenant(), Commands.getDeviceFacingReplyToId(command.getReplyToId(), command.getDeviceId(), command.getMessagingType())));
}
final Long timerId;
if (getConfig().getSendMessageToDeviceTimeout() < 1) {
timerId = null;
} else {
timerId = vertx.setTimer(getConfig().getSendMessageToDeviceTimeout(), tid -> {
if (log.isDebugEnabled()) {
final String linkOrConnectionClosedInfo = HonoProtonHelper.isLinkOpenAndConnected(sender) ? "" : " (link or connection already closed)";
log.debug("waiting for delivery update timed out after {}ms{} [{}]", getConfig().getSendMessageToDeviceTimeout(), linkOrConnectionClosedInfo, command);
}
if (isCommandSettled.compareAndSet(false, true)) {
// timeout reached -> release command
commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "timeout waiting for delivery update from device"));
reportSentCommand(tenantObject, commandContext, ProcessingOutcome.UNDELIVERABLE);
} else if (log.isTraceEnabled()) {
log.trace("command is already settled and downstream application was already notified [{}]", command);
}
});
}
sender.send(msg, delivery -> {
if (timerId != null) {
// disposition received -> cancel timer
vertx.cancelTimer(timerId);
}
if (!isCommandSettled.compareAndSet(false, true)) {
log.trace("command is already settled and downstream application was already notified [{}]", command);
} else {
// release the command message when the device either
// rejects or does not settle the command request message.
final DeliveryState remoteState = delivery.getRemoteState();
ProcessingOutcome outcome = null;
if (delivery.remotelySettled()) {
if (Accepted.class.isInstance(remoteState)) {
outcome = ProcessingOutcome.FORWARDED;
commandContext.accept();
} else if (Rejected.class.isInstance(remoteState)) {
outcome = ProcessingOutcome.UNPROCESSABLE;
final String cause = Optional.ofNullable(((Rejected) remoteState).getError()).map(ErrorCondition::getDescription).orElse(null);
commandContext.reject(cause);
} else if (Released.class.isInstance(remoteState)) {
outcome = ProcessingOutcome.UNDELIVERABLE;
commandContext.release();
} else if (Modified.class.isInstance(remoteState)) {
final Modified modified = (Modified) remoteState;
outcome = modified.getUndeliverableHere() ? ProcessingOutcome.UNPROCESSABLE : ProcessingOutcome.UNDELIVERABLE;
commandContext.modify(modified.getDeliveryFailed(), modified.getUndeliverableHere());
}
} else {
log.debug("device did not settle command message [remote state: {}, {}]", remoteState, command);
final Map<String, Object> logItems = new HashMap<>(2);
logItems.put(Fields.EVENT, "device did not settle command");
logItems.put("remote state", remoteState);
commandContext.getTracingSpan().log(logItems);
commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "device did not settle command"));
outcome = ProcessingOutcome.UNDELIVERABLE;
}
reportSentCommand(tenantObject, commandContext, outcome);
}
});
final Map<String, Object> items = new HashMap<>(4);
items.put(Fields.EVENT, "command sent to device");
if (sender.getRemoteTarget() != null) {
items.put(Tags.MESSAGE_BUS_DESTINATION.getKey(), sender.getRemoteTarget().getAddress());
}
items.put(TracingHelper.TAG_QOS.getKey(), sender.getQoS().name());
items.put(TracingHelper.TAG_CREDIT.getKey(), sender.getCredit());
commandContext.getTracingSpan().log(items);
}
}
Aggregations