use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class HttpBasedMessageMappingTest method testMappingFailsForWhenPayloadCannotMapped.
/**
* Verifies that the downstream mapper returns a failed future with a ServerErrorException if the downstream mapper has been configured
* for an adapter but the remote service returns a 403 status code indicating that the device payload cannot be mapped.
*
* @param ctx The Vert.x test context.
*/
@Test
@SuppressWarnings("unchecked")
public void testMappingFailsForWhenPayloadCannotMapped(final VertxTestContext ctx) {
config.setMapperEndpoints(Map.of("mapper", MapperEndpoint.from("host", 1234, "/uri", false)));
final ResourceIdentifier targetAddress = ResourceIdentifier.from(TelemetryConstants.TELEMETRY_ENDPOINT, TEST_TENANT_ID, "gateway");
final HttpRequest<Buffer> httpRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN);
when(mapperWebClient.post(anyInt(), anyString(), anyString())).thenReturn(httpRequest);
final MqttPublishMessage message = newMessage(MqttQoS.AT_LEAST_ONCE, "mqtt-topic");
final MqttContext context = newContext(message, span, new Device(TEST_TENANT_ID, "gateway"));
final RegistrationAssertion assertion = new RegistrationAssertion("gateway").setDownstreamMessageMapper("mapper");
messageMapping.mapDownstreamMessage(context, targetAddress, assertion).onComplete(ctx.failing(t -> {
ctx.verify(() -> {
assertThat(t).isInstanceOf(ServerErrorException.class);
assertThat((((ServerErrorException) t).getErrorCode())).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
});
ctx.completeNow();
}));
final ArgumentCaptor<Handler<AsyncResult<HttpResponse<Buffer>>>> handlerCaptor = VertxMockSupport.argumentCaptorHandler();
verify(httpRequest).sendBuffer(any(Buffer.class), handlerCaptor.capture());
handlerCaptor.getValue().handle(Future.succeededFuture(httpResponse));
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class HttpBasedMessageMappingTest method testMappingCommandFailsForWhenPayloadCannotMapped.
/**
* Verifies that the upstream mapper returns a failed future with a ServerErrorException if the upstream mapper has been configured
* for an adapter but the remote service returns a 403 status code indicating that the device payload cannot be mapped.
*
* @param ctx The Vert.x test context.
*/
@Test
@SuppressWarnings("unchecked")
public void testMappingCommandFailsForWhenPayloadCannotMapped(final VertxTestContext ctx) {
config.setMapperEndpoints(Map.of("mapper", MapperEndpoint.from("host", 1234, "/uri", false)));
final HttpRequest<Buffer> httpRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
final Buffer payload = Buffer.buffer("payload");
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN);
when(mapperWebClient.post(anyInt(), anyString(), anyString())).thenReturn(httpRequest);
final Command command = mock(Command.class);
when(command.getPayload()).thenReturn(payload);
final RegistrationAssertion assertion = new RegistrationAssertion("gateway").setUpstreamMessageMapper("mapper");
messageMapping.mapUpstreamMessage(assertion, command).onComplete(ctx.failing(t -> {
ctx.verify(() -> {
assertThat(t).isInstanceOf(ServerErrorException.class);
assertThat((((ServerErrorException) t).getErrorCode())).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
});
ctx.completeNow();
}));
final ArgumentCaptor<Handler<AsyncResult<HttpResponse<Buffer>>>> handleCaptor = VertxMockSupport.argumentCaptorHandler();
verify(httpRequest).sendBuffer(any(Buffer.class), handleCaptor.capture());
handleCaptor.getValue().handle(Future.succeededFuture(httpResponse));
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class ProtocolAdapterMockSupport method prepareClients.
/**
* Prepares the (mock) service clients with default behavior.
* <p>
* This method is separate from {@link #createClients()} in order to
* support setups where the clients are created once for all test cases but
* the client instances need to be (re-)set for each individual test case.
* <p>
* This method configures
* <ul>
* <li>the Tenant client to always return a succeeded future containing a TenantObject
* for the given tenant</li>
* <li>the Device Registration service client to always return a succeeded future containing
* a {@link RegistrationAssertion} for the given device</li>
* </ul>
*/
protected void prepareClients() {
if (tenantClient != null) {
when(tenantClient.get(anyString(), (SpanContext) any())).thenAnswer(invocation -> {
return Future.succeededFuture(TenantObject.from(invocation.getArgument(0), true));
});
}
if (registrationClient != null) {
when(registrationClient.assertRegistration(anyString(), anyString(), any(), (SpanContext) any())).thenAnswer(invocation -> {
final String deviceId = invocation.getArgument(1);
final RegistrationAssertion regAssertion = new RegistrationAssertion(deviceId);
return Future.succeededFuture(regAssertion);
});
}
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method createCommandConsumer.
private Future<CommandConsumer> createCommandConsumer(final ProtonSender sender, final ResourceIdentifier sourceAddress, final Device authenticatedDevice, final Span span) {
final Handler<CommandContext> commandHandler = commandContext -> {
final Sample timer = metrics.startTimer();
addMicrometerSample(commandContext, timer);
Tags.COMPONENT.set(commandContext.getTracingSpan(), getTypeName());
final Command command = commandContext.getCommand();
final Future<TenantObject> tenantTracker = getTenantConfiguration(sourceAddress.getTenantId(), commandContext.getTracingContext());
tenantTracker.compose(tenantObject -> {
if (!command.isValid()) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "malformed command message"));
}
if (!HonoProtonHelper.isLinkOpenAndConnected(sender)) {
return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "sender link is not open"));
}
return checkMessageLimit(tenantObject, command.getPayloadSize(), commandContext.getTracingContext());
}).compose(success -> {
// check the via-gateways, ensuring that the gateway may act on behalf of the device at this point in time
if (authenticatedDevice != null && !authenticatedDevice.getDeviceId().equals(sourceAddress.getResourceId())) {
return getRegistrationAssertion(authenticatedDevice.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice, commandContext.getTracingContext());
}
return Future.succeededFuture();
}).compose(success -> {
onCommandReceived(tenantTracker.result(), sender, commandContext);
return Future.succeededFuture();
}).otherwise(failure -> {
if (failure instanceof ClientErrorException) {
commandContext.reject(failure);
} else {
commandContext.release(failure);
}
metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, sourceAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.from(failure), command.getPayloadSize(), timer);
return null;
});
};
final Future<RegistrationAssertion> tokenTracker = Optional.ofNullable(authenticatedDevice).map(v -> getRegistrationAssertion(authenticatedDevice.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice, span.context())).orElseGet(Future::succeededFuture);
if (authenticatedDevice != null && !authenticatedDevice.getDeviceId().equals(sourceAddress.getResourceId())) {
// gateway scenario
return tokenTracker.compose(v -> getCommandConsumerFactory().createCommandConsumer(sourceAddress.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice.getDeviceId(), commandHandler, null, span.context()));
} else {
return tokenTracker.compose(v -> getCommandConsumerFactory().createCommandConsumer(sourceAddress.getTenantId(), sourceAddress.getResourceId(), commandHandler, null, span.context()));
}
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class AbstractHonoResource method doUploadMessage.
/**
* Forwards a message to the south bound Telemetry or Event API of the messaging infrastructure configured
* for the tenant that the origin device belongs to.
* <p>
* Depending on the outcome of the attempt to upload the message, the CoAP response code is set as
* described by the <a href="https://www.eclipse.org/hono/docs/user-guide/coap-adapter/">CoAP adapter user guide</a>
*
* @param context The request that contains the uploaded message.
* @param endpoint The type of API endpoint to forward the message to.
* @return A future indicating the outcome of the operation.
* The future will be succeeded if the message has been forwarded successfully.
* In this case one of the context's <em>respond</em> methods will have been invoked to send a CoAP response
* back to the device.
* Otherwise the future will be failed with a {@link org.eclipse.hono.client.ServiceInvocationException}.
* @throws NullPointerException if any of the parameters are {@code null}.
*/
protected final Future<Void> doUploadMessage(final CoapContext context, final MetricsTags.EndpointType endpoint) {
Objects.requireNonNull(context);
Objects.requireNonNull(endpoint);
final String contentType = context.getContentType();
final Buffer payload = context.getPayload();
if (contentType == null) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "request message must contain content-format option"));
} else if (payload.length() == 0 && !context.isEmptyNotification()) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "request contains no body but is not marked as empty notification"));
} else {
final String gatewayId = context.getGatewayId();
final String tenantId = context.getOriginDevice().getTenantId();
final String deviceId = context.getOriginDevice().getDeviceId();
final MetricsTags.QoS qos = context.isConfirmable() ? MetricsTags.QoS.AT_LEAST_ONCE : MetricsTags.QoS.AT_MOST_ONCE;
final Span currentSpan = TracingHelper.buildChildSpan(getTracer(), context.getTracingContext(), "upload " + endpoint.getCanonicalName(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantId).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), context.isDeviceAuthenticated()).withTag(Constants.HEADER_QOS_LEVEL, qos.asTag().getValue()).start();
final Promise<Void> responseReady = Promise.promise();
final Future<RegistrationAssertion> tokenTracker = getAdapter().getRegistrationAssertion(tenantId, deviceId, context.getAuthenticatedDevice(), currentSpan.context());
final Future<TenantObject> tenantTracker = getAdapter().getTenantClient().get(tenantId, currentSpan.context());
final Future<TenantObject> tenantValidationTracker = tenantTracker.compose(tenantObject -> CompositeFuture.all(getAdapter().isAdapterEnabled(tenantObject), getAdapter().checkMessageLimit(tenantObject, payload.length(), currentSpan.context())).map(tenantObject));
// we only need to consider TTD if the device and tenant are enabled and the adapter
// is enabled for the tenant
final Future<Integer> ttdTracker = CompositeFuture.all(tenantValidationTracker, tokenTracker).compose(ok -> {
final Integer ttdParam = context.getTimeUntilDisconnect();
return getAdapter().getTimeUntilDisconnect(tenantTracker.result(), ttdParam).map(effectiveTtd -> {
if (effectiveTtd != null) {
currentSpan.setTag(MessageHelper.APP_PROPERTY_DEVICE_TTD, effectiveTtd);
}
return effectiveTtd;
});
});
final Future<CommandConsumer> commandConsumerTracker = ttdTracker.compose(ttd -> createCommandConsumer(ttd, tenantTracker.result(), deviceId, gatewayId, context, responseReady, currentSpan));
return commandConsumerTracker.compose(commandConsumer -> {
final Map<String, Object> props = getAdapter().getDownstreamMessageProperties(context);
Optional.ofNullable(commandConsumer).map(c -> ttdTracker.result()).ifPresent(ttd -> props.put(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttd));
customizeDownstreamMessageProperties(props, context);
if (context.isConfirmable()) {
context.startAcceptTimer(vertx, tenantTracker.result(), getAdapter().getConfig().getTimeoutToAck());
}
final Future<Void> sendResult;
if (endpoint == EndpointType.EVENT) {
sendResult = getAdapter().getEventSender(tenantValidationTracker.result()).sendEvent(tenantTracker.result(), tokenTracker.result(), contentType, payload, props, currentSpan.context());
} else {
sendResult = getAdapter().getTelemetrySender(tenantValidationTracker.result()).sendTelemetry(tenantTracker.result(), tokenTracker.result(), context.getRequestedQos(), contentType, payload, props, currentSpan.context());
}
return CompositeFuture.all(sendResult, responseReady.future()).mapEmpty();
}).compose(proceed -> {
// request and the CommandConsumer from the current request has not been closed yet
return Optional.ofNullable(commandConsumerTracker.result()).map(consumer -> consumer.close(currentSpan.context()).otherwise(thr -> {
TracingHelper.logError(currentSpan, thr);
return (Void) null;
})).orElseGet(Future::succeededFuture);
}).map(proceed -> {
final CommandContext commandContext = context.get(CommandContext.KEY_COMMAND_CONTEXT);
final Response response = new Response(ResponseCode.CHANGED);
if (commandContext != null) {
addCommandToResponse(response, commandContext, currentSpan);
commandContext.accept();
getAdapter().getMetrics().reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantId, tenantTracker.result(), ProcessingOutcome.FORWARDED, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
}
LOG.trace("successfully processed message for device [tenantId: {}, deviceId: {}, endpoint: {}]", tenantId, deviceId, endpoint.getCanonicalName());
getAdapter().getMetrics().reportTelemetry(endpoint, tenantId, tenantTracker.result(), MetricsTags.ProcessingOutcome.FORWARDED, qos, payload.length(), getTtdStatus(context), context.getTimer());
context.respond(response);
currentSpan.finish();
return (Void) null;
}).recover(t -> {
LOG.debug("cannot process message from device [tenantId: {}, deviceId: {}, endpoint: {}]", tenantId, deviceId, endpoint.getCanonicalName(), t);
final Future<Void> commandConsumerClosedTracker = Optional.ofNullable(commandConsumerTracker.result()).map(consumer -> consumer.close(currentSpan.context()).onFailure(thr -> TracingHelper.logError(currentSpan, thr))).orElseGet(Future::succeededFuture);
final CommandContext commandContext = context.get(CommandContext.KEY_COMMAND_CONTEXT);
if (commandContext != null) {
TracingHelper.logError(commandContext.getTracingSpan(), "command won't be forwarded to device in CoAP response, CoAP request handling failed", t);
commandContext.release(t);
currentSpan.log("released command for device");
}
getAdapter().getMetrics().reportTelemetry(endpoint, tenantId, tenantTracker.result(), ClientErrorException.class.isInstance(t) ? MetricsTags.ProcessingOutcome.UNPROCESSABLE : MetricsTags.ProcessingOutcome.UNDELIVERABLE, qos, payload.length(), getTtdStatus(context), context.getTimer());
TracingHelper.logError(currentSpan, t);
commandConsumerClosedTracker.onComplete(res -> currentSpan.finish());
return Future.failedFuture(t);
});
}
}
Aggregations