use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class ProtonBasedNotificationSenderTest method setUp.
/**
* Sets up the fixture.
*/
@BeforeEach
void setUp() {
final var vertx = mock(Vertx.class);
when(vertx.eventBus()).thenReturn(mock(EventBus.class));
final HonoConnection connection = AmqpClientUnitTestHelper.mockHonoConnection(vertx);
final ProtonReceiver receiver = AmqpClientUnitTestHelper.mockProtonReceiver();
when(connection.createReceiver(anyString(), any(ProtonQoS.class), any(ProtonMessageHandler.class), anyInt(), anyBoolean(), VertxMockSupport.anyHandler())).thenReturn(Future.succeededFuture(receiver));
protonDelivery = mock(ProtonDelivery.class);
when(protonDelivery.remotelySettled()).thenReturn(true);
when(protonDelivery.getRemoteState()).thenReturn(new Accepted());
sender = AmqpClientUnitTestHelper.mockProtonSender();
when(sender.send(any(Message.class), VertxMockSupport.anyHandler())).thenReturn(protonDelivery);
when(connection.createSender(anyString(), any(), any())).thenReturn(Future.succeededFuture(sender));
notificationSender = new ProtonBasedNotificationSender(connection);
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImpl method createSender.
/**
* Creates a sender link.
*
* @param targetAddress The target address of the link. If the address is {@code null}, the
* sender link will be established to the 'anonymous relay' and each
* message must specify its destination address.
* @param qos The quality of service to use for the link.
* @param closeHook The handler to invoke when the link is closed by the peer (may be {@code null}).
* @return A future for the created link. The future will be completed once the link is open.
* The future will fail with a {@link ServiceInvocationException} if the link cannot be opened.
* @throws NullPointerException if qos is {@code null}.
*/
@Override
public final Future<ProtonSender> createSender(final String targetAddress, final ProtonQoS qos, final Handler<String> closeHook) {
Objects.requireNonNull(qos);
return executeOnContext(result -> {
checkConnected().compose(v -> {
if (targetAddress == null && !supportsCapability(Constants.CAP_ANONYMOUS_RELAY)) {
// before a client can use anonymous terminus
return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_NOT_IMPLEMENTED, "server does not support anonymous terminus"));
}
final Promise<ProtonSender> senderPromise = Promise.promise();
final ProtonSender sender = session.createSender(targetAddress);
sender.setQoS(qos);
sender.setAutoSettle(true);
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening sender [{}] failed: got disconnected", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
sender.openHandler(senderOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (senderPromise.future().isComplete()) {
log.debug("ignoring server response for opening sender [{}]: sender creation already timed out", targetAddress);
} else if (senderOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = sender.getRemoteCondition();
if (error == null) {
log.debug("opening sender [{}] failed", targetAddress, senderOpen.cause());
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open sender", senderOpen.cause()));
} else {
log.debug("opening sender [{}] failed: {} - {}", targetAddress, error.getCondition(), error.getDescription());
senderPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(sender)) {
log.debug("sender open [target: {}, sendQueueFull: {}, remote max-message-size: {}]", targetAddress, sender.sendQueueFull(), sender.getRemoteMaxMessageSize());
final long remoteMaxMessageSize = Optional.ofNullable(sender.getRemoteMaxMessageSize()).map(UnsignedLong::longValue).orElse(0L);
if (remoteMaxMessageSize > 0 && remoteMaxMessageSize < clientConfigProperties.getMinMaxMessageSize()) {
// peer won't accept our (biggest) messages
sender.close();
final String msg = String.format("peer does not support minimum max-message-size [required: %d, supported: %d", clientConfigProperties.getMinMaxMessageSize(), remoteMaxMessageSize);
log.debug(msg);
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_PRECON_FAILED, msg));
} else if (sender.getCredit() <= 0) {
// wait on credits a little time, if not already given
final long waitOnCreditsTimerId = vertx.setTimer(clientConfigProperties.getFlowLatency(), timerID -> {
log.debug("sender [target: {}] has {} credits after grace period of {}ms", targetAddress, sender.getCredit(), clientConfigProperties.getFlowLatency());
sender.sendQueueDrainHandler(null);
senderPromise.tryComplete(sender);
});
sender.sendQueueDrainHandler(replenishedSender -> {
log.debug("sender [target: {}] has received {} initial credits", targetAddress, replenishedSender.getCredit());
if (vertx.cancelTimer(waitOnCreditsTimerId)) {
result.tryComplete(replenishedSender);
replenishedSender.sendQueueDrainHandler(null);
}
// otherwise the timer has already completed the future and cleaned up
// sendQueueDrainHandler
});
} else {
senderPromise.tryComplete(sender);
}
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for target [{}] and will detach the link", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(sender, remoteDetached -> onRemoteDetach(sender, connection.getRemoteContainer(), false, closeHook));
HonoProtonHelper.setCloseHandler(sender, remoteClosed -> onRemoteDetach(sender, connection.getRemoteContainer(), true, closeHook));
sender.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(sender, clientConfigProperties, senderPromise);
}
});
return senderPromise.future();
}).onComplete(result);
});
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImplTest method testIsConnectedWithTimeoutFailsAfterConcurrentReconnectFailed.
/**
* Verifies that {@link HonoConnectionImpl#isConnected(long)} only completes once a concurrent
* connection attempt (which eventually fails here) is finished.
*
* @param ctx The vert.x test client.
*/
@Test
public void testIsConnectedWithTimeoutFailsAfterConcurrentReconnectFailed(final VertxTestContext ctx) {
final long isConnectedTimeout = 44444L;
// let the vertx timer for the isConnectedTimeout do nothing
when(vertx.setTimer(eq(isConnectedTimeout), VertxMockSupport.anyHandler())).thenAnswer(invocation -> 0L);
final AtomicBoolean isConnectedInvocationsDone = new AtomicBoolean(false);
final AtomicReference<Future<Void>> isConnected1FutureRef = new AtomicReference<>();
final AtomicReference<Future<Void>> isConnected2FutureRef = new AtomicReference<>();
// GIVEN a client that is configured to connect to a peer
// to which the connection can be established on the third attempt only
connectionFactory = new DisconnectHandlerProvidingConnectionFactory(con) {
@Override
public void connect(final ProtonClientOptions options, final String username, final String password, final String containerId, final Handler<AsyncResult<ProtonConnection>> closeHandler, final Handler<ProtonConnection> disconnectHandler, final Handler<AsyncResult<ProtonConnection>> connectionResultHandler) {
// and GIVEN "isConnected" invocations done while the "connect" invocation is still in progress
if (isConnectedInvocationsDone.compareAndSet(false, true)) {
isConnected1FutureRef.set(honoConnection.isConnected(isConnectedTimeout));
isConnected2FutureRef.set(honoConnection.isConnected(isConnectedTimeout));
// assert "isConnected" invocations have not completed yet
ctx.verify(() -> {
assertThat(isConnected1FutureRef.get().isComplete()).isFalse();
assertThat(isConnected2FutureRef.get().isComplete()).isFalse();
});
}
super.connect(options, username, password, containerId, closeHandler, disconnectHandler, connectionResultHandler);
}
};
connectionFactory.setExpectedFailingConnectionAttempts(3);
props.setReconnectAttempts(2);
props.setConnectTimeout(10);
honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
// WHEN the client tries to connect
honoConnection.connect().onComplete(ctx.failing(t -> {
ctx.verify(() -> {
// THEN the connection attempt fails and the "isConnected" futures fail as well
assertThat(((ServerErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
assertThat(isConnected1FutureRef.get().failed()).isTrue();
assertThat(((ServerErrorException) isConnected1FutureRef.get().cause()).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
assertThat(isConnected2FutureRef.get().failed()).isTrue();
assertThat(((ServerErrorException) isConnected2FutureRef.get().cause()).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
});
}));
// and the client has indeed tried three times in total before giving up
ctx.verify(() -> assertThat(connectionFactory.awaitFailure()).isTrue());
ctx.completeNow();
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImplTest method testIsConnectedWithTimeoutSucceedsAfterConcurrentReconnectSucceeded.
/**
* Verifies that {@link HonoConnectionImpl#isConnected(long)} only completes once a concurrent
* connection attempt (which eventually succeeds here) is finished.
*
* @param ctx The test execution context.
*/
@Test
public void testIsConnectedWithTimeoutSucceedsAfterConcurrentReconnectSucceeded(final VertxTestContext ctx) {
final long isConnectedTimeout = 44444L;
// let the vertx timer for the isConnectedTimeout do nothing
when(vertx.setTimer(eq(isConnectedTimeout), VertxMockSupport.anyHandler())).thenAnswer(invocation -> 0L);
final AtomicBoolean isConnectedInvocationsDone = new AtomicBoolean(false);
final AtomicReference<Future<Void>> isConnected1FutureRef = new AtomicReference<>();
final AtomicReference<Future<Void>> isConnectedTimeoutForcedFutureRef = new AtomicReference<>();
final AtomicReference<Future<Void>> isConnected2FutureRef = new AtomicReference<>();
// GIVEN a client that is configured to connect to a peer
// to which the connection can be established on the third attempt only
connectionFactory = new DisconnectHandlerProvidingConnectionFactory(con) {
@Override
public void connect(final ProtonClientOptions options, final String username, final String password, final String containerId, final Handler<AsyncResult<ProtonConnection>> closeHandler, final Handler<ProtonConnection> disconnectHandler, final Handler<AsyncResult<ProtonConnection>> connectionResultHandler) {
// and GIVEN "isConnected" invocations done while the "connect" invocation is still in progress
if (isConnectedInvocationsDone.compareAndSet(false, true)) {
isConnected1FutureRef.set(honoConnection.isConnected(isConnectedTimeout));
isConnectedTimeoutForcedFutureRef.set(honoConnection.isConnected(1L));
isConnected2FutureRef.set(honoConnection.isConnected(isConnectedTimeout));
// assert "isConnected" invocations have not completed yet, apart from the one with the forced timeout
ctx.verify(() -> {
assertThat(isConnected1FutureRef.get().isComplete()).isFalse();
assertThat(isConnectedTimeoutForcedFutureRef.get().failed()).isTrue();
assertThat(isConnected2FutureRef.get().isComplete()).isFalse();
});
}
super.connect(options, username, password, containerId, closeHandler, disconnectHandler, connectionResultHandler);
}
};
connectionFactory.setExpectedFailingConnectionAttempts(2);
props.setReconnectAttempts(2);
props.setConnectTimeout(10);
honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
// WHEN trying to connect
honoConnection.connect().compose(v -> CompositeFuture.all(isConnected1FutureRef.get(), isConnected2FutureRef.get())).onFailure(ctx::failNow);
ctx.verify(() -> {
// and the client fails twice to connect
assertThat(connectionFactory.awaitFailure()).isTrue();
// and succeeds to connect on the third attempt
assertThat(connectionFactory.await()).isTrue();
});
ctx.completeNow();
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImplTest method testDisconnectAbortsConnectAttemptWaitingForReconnect.
/**
* Verifies that if a client disconnects from the server, then an ongoing attempt to connect, currently waiting
* for the next reconnect attempt, is cancelled.
*
* @param ctx The test execution context.
*/
@Test
public void testDisconnectAbortsConnectAttemptWaitingForReconnect(final VertxTestContext ctx) {
final long reconnectTimerId = 32L;
final long reconnectMinDelay = 20L;
// GIVEN a client that is configured to connect to a peer
// to which the connection is only getting established on the 2nd attempt, after some delay.
// the reconnect timer handler will not get invoked, timer should get cancelled
final AtomicBoolean reconnectTimerStarted = new AtomicBoolean();
when(vertx.setTimer(eq(reconnectMinDelay), any())).thenAnswer(invocation -> {
reconnectTimerStarted.set(true);
return reconnectTimerId;
});
when(vertx.cancelTimer(eq(reconnectTimerId))).thenReturn(true);
connectionFactory = new DisconnectHandlerProvidingConnectionFactory(con) {
@Override
public void connect(final ProtonClientOptions options, final String username, final String password, final String containerId, final Handler<AsyncResult<ProtonConnection>> closeHandler, final Handler<ProtonConnection> disconnectHandler, final Handler<AsyncResult<ProtonConnection>> connectionResultHandler) {
super.connect(options, username, password, containerId, closeHandler, disconnectHandler, connectionResultHandler);
}
};
connectionFactory.setExpectedFailingConnectionAttempts(1);
props.setReconnectMinDelay(reconnectMinDelay);
honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
// WHEN trying to connect
final Future<HonoConnection> connectFuture = honoConnection.connect();
assertThat(reconnectTimerStarted.get()).isTrue();
// and disconnecting before the connection has been established
honoConnection.disconnect();
// THEN the reconnectTimer has been cancelled
verify(vertx).cancelTimer(eq(reconnectTimerId));
// AND the connect attempt is failed
connectFuture.onComplete(ctx.failing(cause -> {
ctx.verify(() -> assertThat(((ClientErrorException) cause).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_CONFLICT));
ctx.completeNow();
}));
}
Aggregations