use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class AbstractVertxBasedHttpProtocolAdapter method doUploadMessage.
private void doUploadMessage(final HttpContext ctx, final String tenant, final String deviceId, final Buffer payload, final String contentType, final MetricsTags.EndpointType endpoint) {
if (!ctx.hasValidQoS()) {
HttpUtils.badRequest(ctx.getRoutingContext(), "unsupported QoS-Level header value");
return;
}
if (!isPayloadOfIndicatedType(payload, contentType)) {
HttpUtils.badRequest(ctx.getRoutingContext(), String.format("content type [%s] does not match payload", contentType));
return;
}
final MetricsTags.QoS qos = getQoSLevel(endpoint, ctx.getRequestedQos());
final Device authenticatedDevice = ctx.getAuthenticatedDevice();
final String gatewayId = authenticatedDevice != null && !deviceId.equals(authenticatedDevice.getDeviceId()) ? authenticatedDevice.getDeviceId() : null;
final Span currentSpan = TracingHelper.buildChildSpan(tracer, TracingHandler.serverSpanContext(ctx.getRoutingContext()), "upload " + endpoint.getCanonicalName(), getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenant).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), authenticatedDevice != null).withTag(TracingHelper.TAG_QOS, qos.name()).start();
final Promise<Void> responseReady = Promise.promise();
final Future<RegistrationAssertion> tokenTracker = getRegistrationAssertion(tenant, deviceId, authenticatedDevice, currentSpan.context());
final int payloadSize = Optional.ofNullable(payload).map(ok -> payload.length()).orElse(0);
final Future<TenantObject> tenantTracker = getTenantConfiguration(tenant, currentSpan.context());
final Future<TenantObject> tenantValidationTracker = tenantTracker.compose(tenantObject -> CompositeFuture.all(isAdapterEnabled(tenantObject), checkMessageLimit(tenantObject, payloadSize, currentSpan.context())).map(success -> 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 = getTimeUntilDisconnectFromRequest(ctx);
return 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, ctx.getRoutingContext(), responseReady, currentSpan));
commandConsumerTracker.compose(ok -> {
final Map<String, Object> props = getDownstreamMessageProperties(ctx);
Optional.ofNullable(commandConsumerTracker.result()).map(c -> ttdTracker.result()).ifPresent(ttd -> props.put(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttd));
props.put(MessageHelper.APP_PROPERTY_QOS, ctx.getRequestedQos().ordinal());
customizeDownstreamMessageProperties(props, ctx);
setTtdRequestConnectionCloseHandler(ctx.getRoutingContext(), commandConsumerTracker.result(), tenant, deviceId, currentSpan);
if (EndpointType.EVENT.equals(endpoint)) {
ctx.getTimeToLive().ifPresent(ttl -> props.put(MessageHelper.SYS_HEADER_PROPERTY_TTL, ttl.toSeconds()));
return CompositeFuture.all(getEventSender(tenantValidationTracker.result()).sendEvent(tenantTracker.result(), tokenTracker.result(), contentType, payload, props, currentSpan.context()), responseReady.future()).map(s -> (Void) null);
} else {
// unsettled
return CompositeFuture.all(getTelemetrySender(tenantValidationTracker.result()).sendTelemetry(tenantTracker.result(), tokenTracker.result(), ctx.getRequestedQos(), contentType, payload, props, currentSpan.context()), responseReady.future()).map(s -> (Void) null);
}
}).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 -> {
if (ctx.response().closed()) {
log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]: response already closed", endpoint, tenant, deviceId);
TracingHelper.logError(currentSpan, "failed to send HTTP response to device: response already closed");
currentSpan.finish();
// close the response here, ensuring that the TracingHandler bodyEndHandler gets called
ctx.response().end();
} else {
final CommandContext commandContext = ctx.get(CommandContext.KEY_COMMAND_CONTEXT);
setResponsePayload(ctx.response(), commandContext, currentSpan);
ctx.getRoutingContext().addBodyEndHandler(ok -> {
log.trace("successfully processed [{}] message for device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId);
if (commandContext != null) {
commandContext.getTracingSpan().log("forwarded command to device in HTTP response body");
commandContext.accept();
metrics.reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
}
metrics.reportTelemetry(endpoint, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, qos, payloadSize, ctx.getTtdStatus(), getMicrometerSample(ctx.getRoutingContext()));
currentSpan.finish();
});
ctx.response().exceptionHandler(t -> {
log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId, t);
if (commandContext != null) {
TracingHelper.logError(commandContext.getTracingSpan(), "failed to forward command to device in HTTP response body", t);
commandContext.release(t);
metrics.reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), ProcessingOutcome.UNDELIVERABLE, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
}
currentSpan.log("failed to send HTTP response to device");
TracingHelper.logError(currentSpan, t);
currentSpan.finish();
});
ctx.response().end();
}
return proceed;
}).recover(t -> {
log.debug("cannot process [{}] message from device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId, t);
final boolean responseClosedPrematurely = ctx.response().closed();
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 = ctx.get(CommandContext.KEY_COMMAND_CONTEXT);
if (commandContext != null) {
TracingHelper.logError(commandContext.getTracingSpan(), "command won't be forwarded to device in HTTP response body, HTTP request handling failed", t);
commandContext.release(t);
currentSpan.log("released command for device");
}
final ProcessingOutcome outcome;
if (ClientErrorException.class.isInstance(t)) {
outcome = ProcessingOutcome.UNPROCESSABLE;
ctx.fail(t);
} else {
outcome = ProcessingOutcome.UNDELIVERABLE;
final String errorMessage = t instanceof ServerErrorException ? ((ServerErrorException) t).getClientFacingMessage() : null;
HttpUtils.serviceUnavailable(ctx.getRoutingContext(), 2, Strings.isNullOrEmpty(errorMessage) ? "temporarily unavailable" : errorMessage);
}
if (responseClosedPrematurely) {
log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]: response already closed", endpoint, tenant, deviceId);
TracingHelper.logError(currentSpan, "failed to send HTTP response to device: response already closed");
}
metrics.reportTelemetry(endpoint, tenant, tenantTracker.result(), outcome, qos, payloadSize, ctx.getTtdStatus(), getMicrometerSample(ctx.getRoutingContext()));
TracingHelper.logError(currentSpan, t);
commandConsumerClosedTracker.onComplete(res -> currentSpan.finish());
return Future.failedFuture(t);
});
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class AbstractVertxBasedHttpProtocolAdapter method uploadCommandResponseMessage.
/**
* Uploads a command response message to Hono.
*
* @param ctx The routing context of the HTTP request.
* @param tenant The tenant of the device from which the command response was received.
* @param deviceId The device from which the command response was received.
* @param commandRequestId The id of the command that the response has been sent in reply to.
* @param responseStatus The HTTP status code that the device has provided in its request to indicate
* the outcome of processing the command (may be {@code null}).
* @throws NullPointerException if ctx, tenant or deviceId are {@code null}.
*/
public final void uploadCommandResponseMessage(final HttpContext ctx, final String tenant, final String deviceId, final String commandRequestId, final Integer responseStatus) {
Objects.requireNonNull(ctx);
Objects.requireNonNull(tenant);
Objects.requireNonNull(deviceId);
final Buffer payload = ctx.getRoutingContext().getBody();
final String contentType = ctx.getContentType();
log.debug("processing response to command [tenantId: {}, deviceId: {}, cmd-req-id: {}, status code: {}]", tenant, deviceId, commandRequestId, responseStatus);
final Device authenticatedDevice = ctx.getAuthenticatedDevice();
final Span currentSpan = TracingHelper.buildChildSpan(tracer, TracingHandler.serverSpanContext(ctx.getRoutingContext()), "upload Command response", getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenant).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(Constants.HEADER_COMMAND_RESPONSE_STATUS, responseStatus).withTag(Constants.HEADER_COMMAND_REQUEST_ID, commandRequestId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), authenticatedDevice != null).start();
final CommandResponse cmdResponseOrNull = CommandResponse.fromRequestId(commandRequestId, tenant, deviceId, payload, contentType, responseStatus);
final Future<TenantObject> tenantTracker = getTenantConfiguration(tenant, currentSpan.context());
final Future<CommandResponse> commandResponseTracker = cmdResponseOrNull != null ? Future.succeededFuture(cmdResponseOrNull) : Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, String.format("command-request-id [%s] or status code [%s] is missing/invalid", commandRequestId, responseStatus)));
final int payloadSize = Optional.ofNullable(payload).map(Buffer::length).orElse(0);
CompositeFuture.all(tenantTracker, commandResponseTracker).compose(commandResponse -> {
final Future<RegistrationAssertion> deviceRegistrationTracker = getRegistrationAssertion(tenant, deviceId, authenticatedDevice, currentSpan.context());
final Future<Void> tenantValidationTracker = CompositeFuture.all(isAdapterEnabled(tenantTracker.result()), checkMessageLimit(tenantTracker.result(), payloadSize, currentSpan.context())).map(ok -> null);
return CompositeFuture.all(tenantValidationTracker, deviceRegistrationTracker).compose(ok -> sendCommandResponse(tenantTracker.result(), deviceRegistrationTracker.result(), commandResponseTracker.result(), currentSpan.context())).map(delivery -> {
log.trace("delivered command response [command-request-id: {}] to application", commandRequestId);
currentSpan.log("delivered command response to application");
currentSpan.finish();
metrics.reportCommand(Direction.RESPONSE, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, payloadSize, getMicrometerSample(ctx.getRoutingContext()));
ctx.response().setStatusCode(HttpURLConnection.HTTP_ACCEPTED);
ctx.response().end();
return delivery;
});
}).otherwise(t -> {
log.debug("could not send command response [command-request-id: {}] to application", commandRequestId, t);
TracingHelper.logError(currentSpan, t);
currentSpan.finish();
metrics.reportCommand(Direction.RESPONSE, tenant, tenantTracker.result(), ProcessingOutcome.from(t), payloadSize, getMicrometerSample(ctx.getRoutingContext()));
ctx.fail(t);
return null;
});
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class ResourceTestBase method givenAnAdapter.
CoapProtocolAdapter givenAnAdapter(final CoapAdapterProperties configuration) {
adapter = mock(CoapProtocolAdapter.class);
when(adapter.checkMessageLimit(any(TenantObject.class), anyLong(), any())).thenReturn(Future.succeededFuture());
when(adapter.getCommandConsumerFactory()).thenReturn(commandConsumerFactory);
when(adapter.getCommandResponseSender(any(MessagingType.class), any(TenantObject.class))).thenReturn(commandResponseSender);
when(adapter.getConfig()).thenReturn(configuration);
when(adapter.getDownstreamMessageProperties(any(TelemetryExecutionContext.class))).thenReturn(new HashMap<>());
when(adapter.getEventSender(any(TenantObject.class))).thenReturn(eventSender);
when(adapter.getInsecureEndpoint()).thenReturn(mock(Endpoint.class));
when(adapter.getMetrics()).thenReturn(metrics);
when(adapter.getRegistrationAssertion(anyString(), anyString(), any(), (SpanContext) any())).thenAnswer(invocation -> {
final String deviceId = invocation.getArgument(1);
final RegistrationAssertion regAssertion = new RegistrationAssertion(deviceId);
return Future.succeededFuture(regAssertion);
});
when(adapter.getSecureEndpoint()).thenReturn(mock(Endpoint.class));
when(adapter.getTelemetrySender(any(TenantObject.class))).thenReturn(telemetrySender);
when(adapter.getTenantClient()).thenReturn(tenantClient);
when(adapter.getTimeUntilDisconnect(any(TenantObject.class), any())).thenCallRealMethod();
when(adapter.getTypeName()).thenReturn(Constants.PROTOCOL_ADAPTER_TYPE_COAP);
when(adapter.isAdapterEnabled(any(TenantObject.class))).thenAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0)));
doAnswer(invocation -> {
final Handler<Void> codeToRun = invocation.getArgument(0);
codeToRun.handle(null);
return null;
}).when(adapter).runOnContext(VertxMockSupport.anyHandler());
when(adapter.sendTtdEvent(anyString(), anyString(), any(), anyInt(), any())).thenReturn(Future.succeededFuture());
return adapter;
}
use of org.eclipse.hono.util.RegistrationAssertion in project hono by eclipse.
the class HttpBasedMessageMappingTest method testMapCommandSucceeds.
/**
* Verifies that the result returned by the upstream mapping service contains the
* mapped payload.
*
* @param ctx The helper to use for running tests on vert.x.
*/
@SuppressWarnings("unchecked")
@Test
public void testMapCommandSucceeds(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 Buffer responseBody = Buffer.buffer("changed");
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.bodyAsBuffer()).thenReturn(responseBody);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
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.succeeding(mappedBuffer -> {
ctx.verify(() -> {
assertThat(mappedBuffer).isEqualTo(responseBody);
verify(mapperWebClient, times(1)).post(anyInt(), anyString(), anyString());
});
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 HttpBasedMessageMappingTest method testMapMessageSucceeds.
/**
* Verifies that the result returned by the mapping service contains the
* mapped payload, device ID and additional properties.
*
* @param ctx The helper to use for running tests on vert.x.
*/
@SuppressWarnings("unchecked")
@Test
public void testMapMessageSucceeds(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 String newDeviceId = "new-device";
final HttpRequest<Buffer> httpRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap();
responseHeaders.add(MessageHelper.APP_PROPERTY_DEVICE_ID, newDeviceId);
responseHeaders.add("foo", "bar");
final Buffer responseBody = Buffer.buffer("changed");
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.headers()).thenReturn(responseHeaders);
when(httpResponse.bodyAsBuffer()).thenReturn(responseBody);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mapperWebClient.post(anyInt(), anyString(), anyString())).thenReturn(httpRequest);
final String topic = String.format("%s/?content-type=%s", TelemetryConstants.TELEMETRY_ENDPOINT, URLEncoder.encode("text/plain", StandardCharsets.UTF_8));
final MqttPublishMessage message = newMessage(MqttQoS.AT_LEAST_ONCE, 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.succeeding(mappedMessage -> {
ctx.verify(() -> {
assertThat(mappedMessage.getTargetAddress().getResourceId()).isEqualTo("new-device");
assertThat(mappedMessage.getPayload()).isEqualTo(responseBody);
assertThat(mappedMessage.getAdditionalProperties()).doesNotContainKey(MessageHelper.APP_PROPERTY_DEVICE_ID);
assertThat(mappedMessage.getAdditionalProperties()).containsEntry("foo", "bar");
});
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));
final ArgumentCaptor<MultiMap> headersCaptor = ArgumentCaptor.forClass(MultiMap.class);
verify(httpRequest).putHeaders(headersCaptor.capture());
final MultiMap addedHeaders = headersCaptor.getValue();
assertThat(addedHeaders.contains(MessageHelper.APP_PROPERTY_ORIG_ADDRESS, topic, false)).isTrue();
assertThat(addedHeaders.contains(HttpHeaders.CONTENT_TYPE.toString(), "text/plain", false));
}
Aggregations