Search in sources :

Example 11 with CommandConsumer

use of org.eclipse.hono.client.command.CommandConsumer in project hono by eclipse.

the class LoraProtocolAdapter method handleTenantTimeout.

private void handleTenantTimeout(final Message<String> msg) {
    final String tenantId = msg.body();
    log.debug("check command subscriptions on timeout of tenant [{}]", tenantId);
    final Span span = TracingHelper.buildSpan(tracer, null, "check command subscriptions on tenant timeout", getClass().getSimpleName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).start();
    TracingHelper.setDeviceTags(span, tenantId, null);
    // check if tenant still exists
    getTenantConfiguration(tenantId, span.context()).recover(thr -> {
        if (thr instanceof TenantDisabledOrNotRegisteredException) {
            log.debug("tenant [{}] disabled or removed, removing corresponding command consumers", tenantId);
            span.log("tenant disabled or removed, corresponding command consumers will be closed");
            @SuppressWarnings("rawtypes") final List<Future> consumerCloseFutures = new LinkedList<>();
            for (final var iter = commandSubscriptions.entrySet().iterator(); iter.hasNext(); ) {
                final var entry = iter.next();
                if (entry.getKey().getTenant().equals(tenantId)) {
                    final CommandConsumer commandConsumer = entry.getValue().one();
                    consumerCloseFutures.add(commandConsumer.close(span.context()));
                    iter.remove();
                }
            }
            return CompositeFuture.join(consumerCloseFutures).mapEmpty();
        } else {
            return Future.failedFuture(thr);
        }
    }).onFailure(thr -> TracingHelper.logError(span, thr)).onComplete(ar -> span.finish());
}
Also used : HttpURLConnection(java.net.HttpURLConnection) LoraProviderMalformedPayloadException(org.eclipse.hono.adapter.lora.providers.LoraProviderMalformedPayloadException) LoggerFactory(org.slf4j.LoggerFactory) Router(io.vertx.ext.web.Router) Tag(io.opentracing.tag.Tag) RoutingContext(io.vertx.ext.web.RoutingContext) Tags(io.opentracing.tag.Tags) Map(java.util.Map) Pair(org.eclipse.hono.util.Pair) Fields(io.opentracing.log.Fields) JsonObject(io.vertx.core.json.JsonObject) TracingHelper(org.eclipse.hono.tracing.TracingHelper) TenantServiceBasedX509Authentication(org.eclipse.hono.adapter.auth.device.TenantServiceBasedX509Authentication) ChainAuthHandler(io.vertx.ext.web.handler.ChainAuthHandler) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) Message(io.vertx.core.eventbus.Message) EventConstants(org.eclipse.hono.util.EventConstants) StringTag(io.opentracing.tag.StringTag) Future(io.vertx.core.Future) HonoBasicAuthHandler(org.eclipse.hono.adapter.http.HonoBasicAuthHandler) Device(org.eclipse.hono.auth.Device) Objects(java.util.Objects) List(java.util.List) Buffer(io.vertx.core.buffer.Buffer) X509AuthHandler(org.eclipse.hono.adapter.http.X509AuthHandler) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Optional(java.util.Optional) Span(io.opentracing.Span) UsernamePasswordCredentials(org.eclipse.hono.adapter.auth.device.UsernamePasswordCredentials) CommandEndpoint(org.eclipse.hono.util.CommandEndpoint) HttpContext(org.eclipse.hono.service.http.HttpContext) Json(io.vertx.core.json.Json) LoraProvider(org.eclipse.hono.adapter.lora.providers.LoraProvider) WebClient(io.vertx.ext.web.client.WebClient) Command(org.eclipse.hono.client.command.Command) ClientErrorException(org.eclipse.hono.client.ClientErrorException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) Constants(org.eclipse.hono.util.Constants) ArrayList(java.util.ArrayList) TracingHandler(org.eclipse.hono.service.http.TracingHandler) CompositeFuture(io.vertx.core.CompositeFuture) StatusCodeMapper(org.eclipse.hono.client.StatusCodeMapper) HttpUtils(org.eclipse.hono.service.http.HttpUtils) LinkedList(java.util.LinkedList) UsernamePasswordAuthProvider(org.eclipse.hono.adapter.auth.device.UsernamePasswordAuthProvider) Logger(org.slf4j.Logger) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) AbstractVertxBasedHttpProtocolAdapter(org.eclipse.hono.adapter.http.AbstractVertxBasedHttpProtocolAdapter) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Sample(io.micrometer.core.instrument.Timer.Sample) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) HttpRequest(io.vertx.ext.web.client.HttpRequest) DeviceCredentialsAuthProvider(org.eclipse.hono.adapter.auth.device.DeviceCredentialsAuthProvider) X509AuthProvider(org.eclipse.hono.adapter.auth.device.X509AuthProvider) HttpMethod(io.vertx.core.http.HttpMethod) HttpProtocolAdapterProperties(org.eclipse.hono.adapter.http.HttpProtocolAdapterProperties) SubjectDnCredentials(org.eclipse.hono.adapter.auth.device.SubjectDnCredentials) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Future(io.vertx.core.Future) CompositeFuture(io.vertx.core.CompositeFuture) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) Span(io.opentracing.Span) LinkedList(java.util.LinkedList)

Example 12 with CommandConsumer

use of org.eclipse.hono.client.command.CommandConsumer 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);
}
Also used : Buffer(io.vertx.core.buffer.Buffer) LoraProvider(org.eclipse.hono.adapter.lora.providers.LoraProvider) CommandContext(org.eclipse.hono.client.command.CommandContext) HttpServerRequest(io.vertx.core.http.HttpServerRequest) HttpContext(org.eclipse.hono.service.http.HttpContext) TracingHandler(org.eclipse.hono.service.http.TracingHandler) Handler(io.vertx.core.Handler) JsonObject(io.vertx.core.json.JsonObject) Command(org.eclipse.hono.client.command.Command) CommandEndpoint(org.eclipse.hono.util.CommandEndpoint) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Test(org.junit.jupiter.api.Test)

Example 13 with CommandConsumer

use of org.eclipse.hono.client.command.CommandConsumer in project hono by eclipse.

the class AmqpAdapterClientCommandConsumerTest method testReceiverIsRecreatedOnConnectionFailure.

/**
 * Verifies that the proton receiver is recreated after a reconnect.
 */
@Test
public void testReceiverIsRecreatedOnConnectionFailure() {
    final AtomicReference<ReconnectListener<HonoConnection>> reconnectListener = new AtomicReference<>();
    doAnswer(invocation -> {
        reconnectListener.set(invocation.getArgument(0));
        return null;
    }).when(connection).addReconnectListener(any());
    // GIVEN a connected command consumer
    @SuppressWarnings("unchecked") final Future<CommandConsumer> consumerFuture = AmqpAdapterClientCommandConsumer.create(connection, mock(BiConsumer.class));
    final AmqpAdapterClientCommandConsumer commandConsumer = (AmqpAdapterClientCommandConsumer) consumerFuture.result();
    // WHEN the connection is re-established
    final ProtonReceiver newReceiver = createNewProtonReceiver(connection);
    reconnectListener.get().onReconnect(null);
    // THEN the receiver is recreated
    verify(connection, times(2)).createReceiver(eq("command"), eq(ProtonQoS.AT_LEAST_ONCE), any(ProtonMessageHandler.class), VertxMockSupport.anyHandler());
    final ProtonReceiver actual = commandConsumer.getReceiver();
    assertThat(actual).isNotEqualTo(originalReceiver);
    assertThat(actual).isEqualTo(newReceiver);
}
Also used : ProtonReceiver(io.vertx.proton.ProtonReceiver) ProtonMessageHandler(io.vertx.proton.ProtonMessageHandler) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) AtomicReference(java.util.concurrent.atomic.AtomicReference) ReconnectListener(org.eclipse.hono.client.ReconnectListener) BiConsumer(java.util.function.BiConsumer) Test(org.junit.jupiter.api.Test)

Example 14 with CommandConsumer

use of org.eclipse.hono.client.command.CommandConsumer in project hono by eclipse.

the class AbstractHonoResource method createCommandConsumer.

/**
 * Creates a consumer for command messages to be sent to a device.
 *
 * @param ttdSecs The number of seconds the device waits for a command.
 * @param tenantObject The tenant configuration object.
 * @param deviceId The identifier of the device.
 * @param gatewayId The identifier of the gateway that is acting on behalf of the device or {@code null} otherwise.
 * @param context The device's currently executing CoAP request context.
 * @param responseReady A future to complete once one of the following conditions are met:
 *            <ul>
 *            <li>the request did not include a <em>hono-ttd</em> query-parameter or</li>
 *            <li>a command has been received and the response ready future has not yet been completed or</li>
 *            <li>the ttd has expired</li>
 *            </ul>
 * @param uploadMessageSpan The OpenTracing Span used for tracking the processing of the request.
 * @return A future indicating the outcome of the operation.
 *         <p>
 *         The future will be completed with the created message consumer or {@code null}, if the response can be
 *         sent back to the device without waiting for a command.
 *         <p>
 *         The future will be failed with a {@code ServiceInvocationException} if the message consumer could not be
 *         created.
 * @throws NullPointerException if any of the parameters other than TTD or gatewayId is {@code null}.
 */
protected final Future<CommandConsumer> createCommandConsumer(final Integer ttdSecs, final TenantObject tenantObject, final String deviceId, final String gatewayId, final CoapContext context, final Handler<AsyncResult<Void>> responseReady, final Span uploadMessageSpan) {
    Objects.requireNonNull(tenantObject);
    Objects.requireNonNull(deviceId);
    Objects.requireNonNull(context);
    Objects.requireNonNull(responseReady);
    Objects.requireNonNull(uploadMessageSpan);
    if (ttdSecs == null || ttdSecs <= 0) {
        // no need to wait for a command
        responseReady.handle(Future.succeededFuture());
        return Future.succeededFuture();
    }
    final AtomicBoolean requestProcessed = new AtomicBoolean(false);
    uploadMessageSpan.setTag(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttdSecs);
    final Span waitForCommandSpan = TracingHelper.buildChildSpan(getTracer(), uploadMessageSpan.context(), "create consumer and wait for command", getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).start();
    final Handler<CommandContext> commandHandler = commandContext -> {
        final Span processCommandSpan = TracingHelper.buildFollowsFromSpan(getTracer(), waitForCommandSpan.context(), "process received command").withTag(Tags.COMPONENT.getKey(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).addReference(References.FOLLOWS_FROM, commandContext.getTracingContext()).start();
        Tags.COMPONENT.set(commandContext.getTracingSpan(), getAdapter().getTypeName());
        commandContext.logCommandToSpan(processCommandSpan);
        final Command command = commandContext.getCommand();
        final Sample commandSample = getAdapter().getMetrics().startTimer();
        if (isCommandValid(command, processCommandSpan)) {
            if (requestProcessed.compareAndSet(false, true)) {
                waitForCommandSpan.finish();
                getAdapter().checkMessageLimit(tenantObject, command.getPayloadSize(), processCommandSpan.context()).onComplete(result -> {
                    if (result.succeeded()) {
                        addMicrometerSample(commandContext, commandSample);
                        // put command context to routing context and notify
                        context.put(CommandContext.KEY_COMMAND_CONTEXT, commandContext);
                    } else {
                        commandContext.reject(result.cause());
                        TracingHelper.logError(processCommandSpan, "rejected command for device", result.cause());
                        getAdapter().getMetrics().reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.from(result.cause()), command.getPayloadSize(), commandSample);
                    }
                    cancelCommandReceptionTimer(context);
                    setTtdStatus(context, TtdStatus.COMMAND);
                    responseReady.handle(Future.succeededFuture());
                    processCommandSpan.finish();
                });
            } else {
                final String errorMsg = "waiting time for command has elapsed or another command has already been processed";
                LOG.debug("{} [tenantId: {}, deviceId: {}]", errorMsg, tenantObject.getTenantId(), deviceId);
                getAdapter().getMetrics().reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.UNDELIVERABLE, command.getPayloadSize(), commandSample);
                commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, errorMsg));
                TracingHelper.logError(processCommandSpan, errorMsg);
                processCommandSpan.finish();
            }
        } else {
            getAdapter().getMetrics().reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.UNPROCESSABLE, command.getPayloadSize(), commandSample);
            LOG.debug("command message is invalid: {}", command);
            commandContext.reject("malformed command message");
            TracingHelper.logError(processCommandSpan, "malformed command message");
            processCommandSpan.finish();
        }
    };
    final Future<CommandConsumer> commandConsumerFuture;
    if (gatewayId != null) {
        // gateway scenario
        commandConsumerFuture = getAdapter().getCommandConsumerFactory().createCommandConsumer(tenantObject.getTenantId(), deviceId, gatewayId, commandHandler, Duration.ofSeconds(ttdSecs), waitForCommandSpan.context());
    } else {
        commandConsumerFuture = getAdapter().getCommandConsumerFactory().createCommandConsumer(tenantObject.getTenantId(), deviceId, commandHandler, Duration.ofSeconds(ttdSecs), waitForCommandSpan.context());
    }
    return commandConsumerFuture.onFailure(thr -> {
        TracingHelper.logError(waitForCommandSpan, thr);
        waitForCommandSpan.finish();
    }).map(consumer -> {
        if (!requestProcessed.get()) {
            // if the request was not responded already, add a timer for triggering an empty response
            addCommandReceptionTimer(context, requestProcessed, responseReady, ttdSecs, waitForCommandSpan);
            context.startAcceptTimer(vertx, tenantObject, getAdapter().getConfig().getTimeoutToAck());
        }
        // for unregistering the command consumer (which is something the parent request span doesn't wait for)
        return new CommandConsumer() {

            @Override
            public Future<Void> close(final SpanContext ignored) {
                final Span closeConsumerSpan = TracingHelper.buildFollowsFromSpan(getTracer(), waitForCommandSpan.context(), "close consumer").withTag(Tags.COMPONENT.getKey(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).start();
                return consumer.close(closeConsumerSpan.context()).onFailure(thr -> TracingHelper.logError(closeConsumerSpan, thr)).onComplete(ar -> closeConsumerSpan.finish());
            }
        };
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ResponseCode(org.eclipse.californium.core.coap.CoAP.ResponseCode) CoapExchange(org.eclipse.californium.core.server.resources.CoapExchange) Response(org.eclipse.californium.core.coap.Response) Command(org.eclipse.hono.client.command.Command) LoggerFactory(org.slf4j.LoggerFactory) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) ClientErrorException(org.eclipse.hono.client.ClientErrorException) Constants(org.eclipse.hono.util.Constants) Tags(io.opentracing.tag.Tags) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) CompositeFuture(io.vertx.core.CompositeFuture) EndpointType(org.eclipse.hono.service.metric.MetricsTags.EndpointType) TtdStatus(org.eclipse.hono.service.metric.MetricsTags.TtdStatus) References(io.opentracing.References) Duration(java.time.Duration) Map(java.util.Map) MediaTypeRegistry(org.eclipse.californium.core.coap.MediaTypeRegistry) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) TracingHelper(org.eclipse.hono.tracing.TracingHelper) AsyncResult(io.vertx.core.AsyncResult) CommandConstants(org.eclipse.hono.util.CommandConstants) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) Promise(io.vertx.core.Promise) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) Vertx(io.vertx.core.Vertx) ServerErrorException(org.eclipse.hono.client.ServerErrorException) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) MessageHelper(org.eclipse.hono.util.MessageHelper) Sample(io.micrometer.core.instrument.Timer.Sample) Future(io.vertx.core.Future) Device(org.eclipse.hono.auth.Device) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) Objects(java.util.Objects) List(java.util.List) Principal(java.security.Principal) Buffer(io.vertx.core.buffer.Buffer) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Optional(java.util.Optional) Span(io.opentracing.Span) OptionSet(org.eclipse.californium.core.coap.OptionSet) Handler(io.vertx.core.Handler) SpanContext(io.opentracing.SpanContext) CommandContext(org.eclipse.hono.client.command.CommandContext) Sample(io.micrometer.core.instrument.Timer.Sample) Span(io.opentracing.Span) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Command(org.eclipse.hono.client.command.Command) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 15 with CommandConsumer

use of org.eclipse.hono.client.command.CommandConsumer in project hono by eclipse.

the class VertxBasedAmqpProtocolAdapterTest method testAdapterSkipsTtdEventOnCmdConnectionCloseIfRemoveConsumerFails.

/**
 * Verifies that the adapter doesn't send a 'disconnectedTtdEvent' on connection loss
 * when removal of the command consumer mapping entry fails (which would be the case
 * when another command consumer mapping had been registered in the mean time, meaning
 * the device has already reconnected).
 *
 * @param ctx The vert.x test context.
 * @throws InterruptedException if the test execution gets interrupted.
 */
@Test
public void testAdapterSkipsTtdEventOnCmdConnectionCloseIfRemoveConsumerFails(final VertxTestContext ctx) throws InterruptedException {
    // GIVEN an AMQP adapter
    givenAnAdapter(properties);
    givenAnEventSenderForAnyTenant();
    final Promise<Void> startupTracker = Promise.promise();
    startupTracker.future().onComplete(ctx.succeedingThenComplete());
    adapter.start(startupTracker);
    assertThat(ctx.awaitCompletion(2, TimeUnit.SECONDS)).isTrue();
    // to which a device is connected
    final Device authenticatedDevice = new Device(TEST_TENANT_ID, TEST_DEVICE);
    final Record record = new RecordImpl();
    record.set(AmqpAdapterConstants.KEY_CLIENT_DEVICE, Device.class, authenticatedDevice);
    final ProtonConnection deviceConnection = mock(ProtonConnection.class);
    when(deviceConnection.attachments()).thenReturn(record);
    final ArgumentCaptor<Handler<ProtonConnection>> connectHandler = VertxMockSupport.argumentCaptorHandler();
    verify(server).connectHandler(connectHandler.capture());
    connectHandler.getValue().handle(deviceConnection);
    // that wants to receive commands
    final CommandConsumer commandConsumer = mock(CommandConsumer.class);
    when(commandConsumer.close(any())).thenReturn(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_PRECON_FAILED)));
    when(commandConsumerFactory.createCommandConsumer(eq(TEST_TENANT_ID), eq(TEST_DEVICE), VertxMockSupport.anyHandler(), any(), any())).thenReturn(Future.succeededFuture(commandConsumer));
    final String sourceAddress = getCommandEndpoint();
    final ProtonSender sender = getSender(sourceAddress);
    adapter.handleRemoteSenderOpenForCommands(deviceConnection, sender);
    // WHEN the connection to the device is lost
    final ArgumentCaptor<Handler<AsyncResult<ProtonConnection>>> closeHandler = VertxMockSupport.argumentCaptorHandler();
    verify(deviceConnection).closeHandler(closeHandler.capture());
    closeHandler.getValue().handle(Future.succeededFuture(deviceConnection));
    // THEN the adapter closes the command consumer
    verify(commandConsumer).close(any());
    // and since closing the command consumer fails with a precon-failed exception
    // there is only one notification sent during consumer creation,
    assertEmptyNotificationHasBeenSentDownstream(TEST_TENANT_ID, TEST_DEVICE, -1);
    // no 'disconnectedTtdEvent' event with TTD = 0
    assertEmptyNotificationHasNotBeenSentDownstream(TEST_TENANT_ID, TEST_DEVICE, 0);
}
Also used : Device(org.eclipse.hono.auth.Device) Handler(io.vertx.core.Handler) RecordImpl(org.apache.qpid.proton.engine.impl.RecordImpl) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonSender(io.vertx.proton.ProtonSender) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) ClientErrorException(org.eclipse.hono.client.ClientErrorException) Record(org.apache.qpid.proton.engine.Record) Test(org.junit.jupiter.api.Test)

Aggregations

CommandConsumer (org.eclipse.hono.client.command.CommandConsumer)23 Handler (io.vertx.core.Handler)15 ClientErrorException (org.eclipse.hono.client.ClientErrorException)14 Device (org.eclipse.hono.auth.Device)12 CommandContext (org.eclipse.hono.client.command.CommandContext)11 Future (io.vertx.core.Future)10 HttpURLConnection (java.net.HttpURLConnection)10 ServerErrorException (org.eclipse.hono.client.ServerErrorException)10 Command (org.eclipse.hono.client.command.Command)10 Test (org.junit.jupiter.api.Test)10 Sample (io.micrometer.core.instrument.Timer.Sample)9 Span (io.opentracing.Span)9 SpanContext (io.opentracing.SpanContext)9 Promise (io.vertx.core.Promise)9 Buffer (io.vertx.core.buffer.Buffer)9 List (java.util.List)9 Map (java.util.Map)9 Constants (org.eclipse.hono.util.Constants)9 Tags (io.opentracing.tag.Tags)8 AsyncResult (io.vertx.core.AsyncResult)8