use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class AmqpClientUnitTestHelper method mockHonoConnection.
/**
* Creates a mocked Hono connection.
*
* @param <T> The type of result when executing code on the connection's vert.x Context.
* @param vertx The vert.x instance to use.
* @param props The client properties to use.
* @param tracer The tracer to use.
* @return The connection.
*/
public static <T> HonoConnection mockHonoConnection(final Vertx vertx, final ClientConfigProperties props, final Tracer tracer) {
final HonoConnection connection = mock(HonoConnection.class);
when(connection.getVertx()).thenReturn(vertx);
when(connection.getConfig()).thenReturn(props);
when(connection.getTracer()).thenReturn(tracer);
when(connection.executeOnContext(VertxMockSupport.anyHandler())).then(invocation -> {
final Promise<T> result = Promise.promise();
final Handler<Future<T>> handler = invocation.getArgument(0);
handler.handle(result.future());
return result.future();
});
when(connection.isConnected()).thenReturn(Future.succeededFuture());
when(connection.isConnected(anyLong())).thenReturn(Future.succeededFuture());
return connection;
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImplTest method testConnectFailsIfAnotherConnectAttemptIsScheduled.
/**
* Verifies that a connection attempt by the client is failed if it occurs while another connect invocation is
* waiting for the next reconnect attempt.
*
* @param ctx The test execution context.
*/
@Test
public void testConnectFailsIfAnotherConnectAttemptIsScheduled(final VertxTestContext ctx) {
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 only shall get invoked on demand (after a corresponding promise gets completed)
final AtomicBoolean reconnectTimerStarted = new AtomicBoolean();
final Promise<Void> reconnectTimerContinuePromise = Promise.promise();
when(vertx.setTimer(eq(reconnectMinDelay), any())).thenAnswer(invocation -> {
reconnectTimerStarted.set(true);
final Handler<Long> reconnectTimerHandler = invocation.getArgument(1);
reconnectTimerContinuePromise.future().onComplete(ar -> reconnectTimerHandler.handle(0L));
return 1L;
});
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 starting another connect attempt before the connection has been established
final Future<HonoConnection> connectFuture2 = honoConnection.connect();
// and letting the first attempt finish
reconnectTimerContinuePromise.complete();
// THEN the first connect invocation succeeds and the second is failed
connectFuture.onComplete(ctx.succeeding(cause -> {
ctx.verify(() -> {
assertThat(connectFuture2.failed()).isTrue();
assertThat(connectFuture2.cause()).isInstanceOf(ClientErrorException.class);
assertThat(((ClientErrorException) connectFuture2.cause()).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_CONFLICT);
});
ctx.completeNow();
}));
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImplTest method testOnRemoteCloseTriggeredReconnectChecksReconnectAttempts.
/**
* Verifies that when the client tries to re-connect to a server instance if the
* connection is closed by the peer, the configured number of reconnect attempts is taken
* into account, skipping the reconnect if that number is zero.
*
* @param ctx The test context.
*/
@Test
public void testOnRemoteCloseTriggeredReconnectChecksReconnectAttempts(final VertxTestContext ctx) {
// GIVEN a client that is connected to a server but should do no automatic reconnect
props.setReconnectAttempts(0);
honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
final Promise<HonoConnection> connected = Promise.promise();
@SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
honoConnection.addDisconnectListener(disconnectListener);
final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
honoConnection.connect(new ProtonClientOptions().setReconnectAttempts(0)).onComplete(connected);
connected.future().onComplete(ctx.succeeding(c -> {
// WHEN the peer closes the connection
connectionFactory.getCloseHandler().handle(Future.failedFuture("shutting down for maintenance"));
ctx.verify(() -> {
// THEN the client invokes the registered disconnect handler
verify(disconnectListener).onDisconnect(honoConnection);
// and the original connection has been closed locally
verify(con).close();
verify(con).disconnectHandler(null);
// and no further connect invocation has been done
assertThat(connectionFactory.getConnectInvocations()).isEqualTo(1);
assertThat(reconnectListenerInvocations.get()).isEqualTo(0);
});
ctx.completeNow();
}));
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImplTest method testRemoteSessionCloseTriggersReconnection.
/**
* Verifies that the client tries to reconnect to the peer if the peer
* closes the connection's session.
*
* @param ctx The test context.
*/
@Test
public void testRemoteSessionCloseTriggersReconnection(final VertxTestContext ctx) {
// GIVEN a client that is connected to a server
final Promise<HonoConnection> connected = Promise.promise();
@SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
honoConnection.addDisconnectListener(disconnectListener);
final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
props.setServerRole("service-provider");
honoConnection.connect(new ProtonClientOptions()).onComplete(connected);
connectionFactory.setExpectedSucceedingConnectionAttempts(1);
connected.future().onComplete(ctx.succeeding(c -> {
ctx.verify(() -> {
// WHEN the peer closes the session
final ArgumentCaptor<Handler<AsyncResult<ProtonSession>>> sessionCloseHandler = VertxMockSupport.argumentCaptorHandler();
verify(session).closeHandler(sessionCloseHandler.capture());
sessionCloseHandler.getValue().handle(Future.succeededFuture(session));
// THEN the client invokes the registered disconnect handler
verify(disconnectListener).onDisconnect(honoConnection);
// and the original connection has been closed locally
verify(con).close();
verify(con).disconnectHandler(null);
// and the connection is re-established
assertThat(connectionFactory.await()).isTrue();
assertThat(reconnectListenerInvocations.get()).isEqualTo(1);
});
ctx.completeNow();
}));
}
use of org.eclipse.hono.client.HonoConnection in project hono by eclipse.
the class HonoConnectionImpl method createReceiver.
@Override
public Future<ProtonReceiver> createReceiver(final String sourceAddress, final ProtonQoS qos, final ProtonMessageHandler messageHandler, final int preFetchSize, final boolean autoAccept, final Handler<String> remoteCloseHook) {
Objects.requireNonNull(sourceAddress);
Objects.requireNonNull(qos);
Objects.requireNonNull(messageHandler);
if (preFetchSize < 0) {
throw new IllegalArgumentException("pre-fetch size must be >= 0");
}
return executeOnContext(result -> {
checkConnected().compose(v -> {
final Promise<ProtonReceiver> receiverPromise = Promise.promise();
final ProtonReceiver receiver = session.createReceiver(sourceAddress);
if (clientConfigProperties.getMaxMessageSize() > ClientConfigProperties.MAX_MESSAGE_SIZE_UNLIMITED) {
receiver.setMaxMessageSize(new UnsignedLong(clientConfigProperties.getMaxMessageSize()));
}
receiver.setAutoAccept(autoAccept);
receiver.setQoS(qos);
receiver.setPrefetch(preFetchSize);
receiver.handler((delivery, message) -> {
HonoProtonHelper.onReceivedMessageDeliveryUpdatedFromRemote(delivery, d -> log.debug("got unexpected disposition update for received message [remote state: {}]", delivery.getRemoteState()));
try {
messageHandler.handle(delivery, message);
if (log.isTraceEnabled()) {
final int remainingCredits = receiver.getCredit() - receiver.getQueued();
log.trace("handling message [remotely settled: {}, queued messages: {}, remaining credit: {}]", delivery.remotelySettled(), receiver.getQueued(), remainingCredits);
}
} catch (final Exception ex) {
log.warn("error handling message", ex);
ProtonHelper.released(delivery, true);
}
});
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening receiver [{}] failed: got disconnected", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
receiver.openHandler(recvOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (receiverPromise.future().isComplete()) {
log.debug("ignoring server response for opening receiver [{}]: receiver creation already timed out", sourceAddress);
} else if (recvOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = receiver.getRemoteCondition();
if (error == null) {
log.debug("opening receiver [{}] failed", sourceAddress, recvOpen.cause());
receiverPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open receiver", recvOpen.cause()));
} else {
log.debug("opening receiver [{}] failed: {} - {}", sourceAddress, error.getCondition(), error.getDescription());
receiverPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(receiver)) {
log.debug("receiver open [source: {}]", sourceAddress);
receiverPromise.tryComplete(recvOpen.result());
} 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 source [{}] and will detach the link", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(receiver, remoteDetached -> onRemoteDetach(receiver, connection.getRemoteContainer(), false, remoteCloseHook));
HonoProtonHelper.setCloseHandler(receiver, remoteClosed -> onRemoteDetach(receiver, connection.getRemoteContainer(), true, remoteCloseHook));
receiver.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(receiver, clientConfigProperties, receiverPromise);
}
});
return receiverPromise.future();
}).onComplete(result);
});
}
Aggregations