use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.
the class CommandAndControlAmqpIT method connectAndSubscribe.
private void connectAndSubscribe(final VertxTestContext ctx, final String commandTargetDeviceId, final AmqpCommandEndpointConfiguration endpointConfig, final BiFunction<ProtonReceiver, ProtonSender, ProtonMessageHandler> commandConsumerFactory, final int expectedNoOfCommands) throws InterruptedException {
final VertxTestContext setup = new VertxTestContext();
final Checkpoint setupDone = setup.checkpoint();
final Checkpoint notificationReceived = setup.checkpoint();
connectToAdapter(tenantId, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
// expect empty notification with TTD -1
ctx.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
log.debug("received notification [{}]", notification);
ctx.verify(() -> assertThat(notification).isNotNull());
if (notification.getTtd() == -1) {
notificationReceived.flag();
}
})).compose(con -> createProducer(null, ProtonQoS.AT_LEAST_ONCE)).compose(sender -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId).map(recv -> {
recv.handler(commandConsumerFactory.apply(recv, sender));
// make sure that there are always enough credits, even if commands are sent faster than answered
recv.flow(expectedNoOfCommands);
return null;
})).onComplete(setup.succeeding(v -> setupDone.flag()));
assertWithMessage("connect and subscribe finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
}
}
use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandViaKafkaFailsForMalformedMessage.
/**
* Verifies that the adapter rejects malformed command messages sent by applications.
* <p>
* This test is applicable only if the messaging network type is Kafka.
*
* @param endpointConfig The endpoints to use for sending/receiving commands.
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("allCombinations")
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
@AssumeMessagingSystem(type = MessagingType.kafka)
public void testSendCommandViaKafkaFailsForMalformedMessage(final AmqpCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final AtomicReference<GenericKafkaSender> kafkaSenderRef = new AtomicReference<>();
final CountDownLatch expectedCommandResponses = new CountDownLatch(1);
final VertxTestContext setup = new VertxTestContext();
final Checkpoint setupDone = setup.checkpoint();
final Checkpoint ttdReceivedPrecondition = setup.checkpoint();
final Future<MessageConsumer> kafkaAsyncErrorResponseConsumer = helper.createDeliveryFailureCommandResponseConsumer(ctx, tenantId, HttpURLConnection.HTTP_BAD_REQUEST, response -> {
ctx.verify(() -> {
DownstreamMessageAssertions.assertMessageContainsTimeToLive(response, TTL_COMMAND_RESPONSE);
});
expectedCommandResponses.countDown();
}, null);
connectToAdapter(tenantId, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
// expect empty notification with TTD -1
setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
log.debug("received notification [{}]", notification);
setup.verify(() -> assertThat(notification).isNotNull());
if (notification.getTtd() == -1) {
ttdReceivedPrecondition.flag();
}
})).compose(con -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId).onSuccess(recv -> recv.handler((delivery, msg) -> ctx.failNow(new IllegalStateException("should not have received command"))))).compose(ok -> helper.createGenericKafkaSender().onSuccess(kafkaSenderRef::set).mapEmpty()).compose(v -> kafkaAsyncErrorResponseConsumer).onComplete(setup.succeeding(v -> setupDone.flag()));
assertWithMessage("setup of adapter finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
final String commandTopic = new HonoTopic(HonoTopic.Type.COMMAND, tenantId).toString();
log.debug("sending command message lacking subject and correlation ID - no failure response expected here");
final Map<String, Object> properties1 = Map.of(MessageHelper.APP_PROPERTY_DEVICE_ID, deviceId, MessageHelper.SYS_PROPERTY_CONTENT_TYPE, MessageHelper.CONTENT_TYPE_OCTET_STREAM, KafkaRecordHelper.HEADER_RESPONSE_REQUIRED, true);
kafkaSenderRef.get().sendAndWaitForOutcome(commandTopic, tenantId, deviceId, Buffer.buffer(), properties1).onComplete(ctx.succeeding(ok -> {
}));
log.debug("sending command message lacking subject");
final String correlationId = "1";
final Map<String, Object> properties2 = Map.of(MessageHelper.SYS_PROPERTY_CORRELATION_ID, correlationId, MessageHelper.APP_PROPERTY_DEVICE_ID, deviceId, MessageHelper.SYS_PROPERTY_CONTENT_TYPE, MessageHelper.CONTENT_TYPE_OCTET_STREAM, KafkaRecordHelper.HEADER_RESPONSE_REQUIRED, true);
kafkaSenderRef.get().sendAndWaitForOutcome(commandTopic, tenantId, deviceId, Buffer.buffer(), properties2).onComplete(ctx.succeeding(ok -> {
}));
final long timeToWait = 2500;
if (!expectedCommandResponses.await(timeToWait, TimeUnit.MILLISECONDS)) {
log.info("Timeout of {} milliseconds reached, stop waiting for command response", timeToWait);
}
kafkaAsyncErrorResponseConsumer.result().close().onComplete(ar -> {
if (expectedCommandResponses.getCount() == 0) {
ctx.completeNow();
} else {
ctx.failNow(new IllegalStateException("did not receive command response"));
}
});
}
use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandViaAmqpFailsForMalformedMessage.
/**
* Verifies that the adapter rejects malformed command messages sent by applications.
* <p>
* This test is applicable only if the messaging network type is AMQP.
*
* @param endpointConfig The endpoints to use for sending/receiving commands.
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("allCombinations")
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
@AssumeMessagingSystem(type = MessagingType.amqp)
public void testSendCommandViaAmqpFailsForMalformedMessage(final AmqpCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final AtomicReference<GenericSenderLink> amqpCmdSenderRef = new AtomicReference<>();
final VertxTestContext setup = new VertxTestContext();
final Checkpoint setupDone = setup.checkpoint();
final Checkpoint preconditions = setup.checkpoint(2);
connectToAdapter(tenantId, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
// expect empty notification with TTD -1
setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
log.debug("received notification [{}]", notification);
setup.verify(() -> assertThat(notification).isNotNull());
if (notification.getTtd() == -1) {
preconditions.flag();
}
})).compose(con -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId).map(recv -> {
recv.handler((delivery, msg) -> ctx.failNow(new IllegalStateException("should not have received command")));
return null;
})).compose(ok -> helper.createGenericAmqpMessageSender(endpointConfig.getNorthboundEndpoint(), tenantId)).map(s -> {
log.debug("created generic sender for sending commands [target address: {}]", endpointConfig.getSenderLinkTargetAddress(tenantId));
amqpCmdSenderRef.set(s);
preconditions.flag();
return s;
}).onComplete(setup.succeeding(v -> setupDone.flag()));
assertWithMessage("setup of adapter finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
final Checkpoint expectedFailures = ctx.checkpoint(2);
log.debug("sending command message lacking subject");
final Message messageWithoutSubject = ProtonHelper.message("input data");
messageWithoutSubject.setAddress(endpointConfig.getCommandMessageAddress(tenantId, commandTargetDeviceId));
messageWithoutSubject.setMessageId("message-id");
messageWithoutSubject.setReplyTo("reply/to/address");
amqpCmdSenderRef.get().sendAndWaitForOutcome(messageWithoutSubject, NoopSpan.INSTANCE).onComplete(ctx.failing(t -> {
ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
expectedFailures.flag();
}));
log.debug("sending command message lacking message ID and correlation ID");
final Message messageWithoutId = ProtonHelper.message("input data");
messageWithoutId.setAddress(endpointConfig.getCommandMessageAddress(tenantId, commandTargetDeviceId));
messageWithoutId.setSubject("setValue");
messageWithoutId.setReplyTo("reply/to/address");
amqpCmdSenderRef.get().sendAndWaitForOutcome(messageWithoutId, NoopSpan.INSTANCE).onComplete(ctx.failing(t -> {
ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
expectedFailures.flag();
}));
}
use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.
the class HonoExampleApplicationBase method sendCommandToAdapter.
/**
* Send a command to the device for which a {@link TimeUntilDisconnectNotification} was received.
* <p>
* If the contained <em>ttd</em> is set to a value @gt; 0, the commandClient will be closed after a response
* was received.
* If the contained <em>ttd</em> is set to -1, the commandClient will remain open for further commands to be sent.
* @param ttdNotification The ttd notification that was received for the device.
*/
private void sendCommandToAdapter(final String tenantId, final String deviceId, final TimeUntilDisconnectNotification ttdNotification) {
final Buffer commandBuffer = buildCommandPayload();
final String command = "setBrightness";
if (LOG.isDebugEnabled()) {
LOG.debug("Sending command [{}] to [{}].", command, ttdNotification.getTenantAndDeviceId());
}
client.sendCommand(tenantId, deviceId, command, "application/json", commandBuffer, buildCommandProperties()).onSuccess(result -> {
if (LOG.isDebugEnabled()) {
LOG.debug("Successfully sent command payload: [{}].", commandBuffer.toString());
LOG.debug("And received response: [{}].", Optional.ofNullable(result.getPayload()).orElseGet(Buffer::buffer).toString());
}
}).onFailure(t -> {
if (t instanceof ServiceInvocationException) {
final int errorCode = ((ServiceInvocationException) t).getErrorCode();
LOG.debug("Command was replied with error code [{}].", errorCode);
} else {
LOG.debug("Could not send command : {}.", t.getMessage());
}
});
}
use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.
the class HonoExampleApplicationBase method sendOneWayCommandToAdapter.
/**
* Send a one way command to the device for which a {@link TimeUntilDisconnectNotification} was received.
* <p>
* If the contained <em>ttd</em> is set to a value @gt; 0, the commandClient will be closed after a response
* was received.
* If the contained <em>ttd</em> is set to -1, the commandClient will remain open for further commands to be sent.
* @param ttdNotification The ttd notification that was received for the device.
*/
private void sendOneWayCommandToAdapter(final String tenantId, final String deviceId, final TimeUntilDisconnectNotification ttdNotification) {
final Buffer commandBuffer = buildOneWayCommandPayload();
if (LOG.isDebugEnabled()) {
LOG.debug("Sending one-way command [{}] to [{}].", COMMAND_SEND_LIFECYCLE_INFO, ttdNotification.getTenantAndDeviceId());
}
client.sendOneWayCommand(tenantId, deviceId, COMMAND_SEND_LIFECYCLE_INFO, commandBuffer).onSuccess(statusResult -> {
if (LOG.isDebugEnabled()) {
LOG.debug("Successfully sent one-way command payload: [{}] and received status [{}].", commandBuffer.toString(), statusResult);
}
}).onFailure(t -> {
if (t instanceof ServiceInvocationException) {
final int errorCode = ((ServiceInvocationException) t).getErrorCode();
LOG.debug("One-way command was replied with error code [{}].", errorCode);
} else {
LOG.debug("Could not send one-way command : {}.", t.getMessage());
}
});
}
Aggregations