diff --git a/adapter-base/src/main/java/org/eclipse/hono/adapter/AbstractProtocolAdapterBase.java b/adapter-base/src/main/java/org/eclipse/hono/adapter/AbstractProtocolAdapterBase.java index dfde5fafd0..ff974104e2 100644 --- a/adapter-base/src/main/java/org/eclipse/hono/adapter/AbstractProtocolAdapterBase.java +++ b/adapter-base/src/main/java/org/eclipse/hono/adapter/AbstractProtocolAdapterBase.java @@ -42,6 +42,7 @@ import org.eclipse.hono.client.telemetry.TelemetrySender; import org.eclipse.hono.client.util.ServiceClient; import org.eclipse.hono.service.AbstractServiceBase; +import org.eclipse.hono.service.AdapterDisabledException; import org.eclipse.hono.service.auth.ValidityBasedTrustOptions; import org.eclipse.hono.service.metric.MetricsTags.ConnectionAttemptOutcome; import org.eclipse.hono.service.util.ServiceBaseUtils; diff --git a/adapters/amqp/src/main/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapter.java b/adapters/amqp/src/main/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapter.java index 338a1c1cd2..ec779fc3a4 100644 --- a/adapters/amqp/src/main/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapter.java +++ b/adapters/amqp/src/main/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapter.java @@ -41,7 +41,6 @@ import org.apache.qpid.proton.message.Message; import org.eclipse.hono.adapter.AbstractProtocolAdapterBase; import org.eclipse.hono.adapter.AdapterConnectionsExceededException; -import org.eclipse.hono.adapter.AdapterDisabledException; import org.eclipse.hono.adapter.AuthorizationException; import org.eclipse.hono.adapter.auth.device.CredentialsApiAuthProvider; import org.eclipse.hono.adapter.auth.device.DeviceCredentials; @@ -67,8 +66,10 @@ import org.eclipse.hono.notification.deviceregistry.DeviceChangeNotification; import org.eclipse.hono.notification.deviceregistry.LifecycleChange; import org.eclipse.hono.notification.deviceregistry.TenantChangeNotification; +import org.eclipse.hono.service.AdapterDisabledException; import org.eclipse.hono.service.auth.DeviceUser; import org.eclipse.hono.service.http.HttpUtils; +import org.eclipse.hono.service.metric.MetricsTags; import org.eclipse.hono.service.metric.MetricsTags.ConnectionAttemptOutcome; import org.eclipse.hono.service.metric.MetricsTags.Direction; import org.eclipse.hono.service.metric.MetricsTags.EndpointType; @@ -1325,7 +1326,8 @@ private Future doUploadMessage( ProcessingOutcome.from(t), context.isRemotelySettled() ? QoS.AT_MOST_ONCE : QoS.AT_LEAST_ONCE, context.getPayloadSize(), - context.getTimer()); + context.getTimer(), + MetricsTags.ProcessingOutcomeReason.from(t)); return Future.failedFuture(t); }).map(ok -> { diff --git a/adapters/amqp/src/test/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapterTest.java b/adapters/amqp/src/test/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapterTest.java index fcfb04f4a4..293eed96d9 100644 --- a/adapters/amqp/src/test/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapterTest.java +++ b/adapters/amqp/src/test/java/org/eclipse/hono/adapter/amqp/VertxBasedAmqpProtocolAdapterTest.java @@ -388,7 +388,8 @@ public void testUploadTelemetryMessageFailsForDisabledAdapter(final VertxTestCon eq(ProcessingOutcome.UNPROCESSABLE), eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.TENANT_DISABLED_FOR_ADAPTER)); }); ctx.completeNow(); })); @@ -1000,7 +1001,8 @@ public void testMessageLimitExceededForATelemetryMessage(final VertxTestContext eq(ProcessingOutcome.UNPROCESSABLE), eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); }); } @@ -1027,7 +1029,8 @@ public void testMessageLimitExceededForAnEventMessage(final VertxTestContext ctx eq(ProcessingOutcome.UNPROCESSABLE), eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); }); } diff --git a/adapters/coap/src/main/java/org/eclipse/hono/adapter/coap/AbstractHonoResource.java b/adapters/coap/src/main/java/org/eclipse/hono/adapter/coap/AbstractHonoResource.java index 0967591ae8..ddc92169d0 100644 --- a/adapters/coap/src/main/java/org/eclipse/hono/adapter/coap/AbstractHonoResource.java +++ b/adapters/coap/src/main/java/org/eclipse/hono/adapter/coap/AbstractHonoResource.java @@ -413,7 +413,8 @@ protected final Future doUploadMessage( qos, payload.length(), getTtdStatus(context), - context.getTimer()); + context.getTimer(), + MetricsTags.ProcessingOutcomeReason.from(t)); TracingHelper.logError(currentSpan, t); commandConsumerClosedTracker.onComplete(res -> currentSpan.finish()); return Future.failedFuture(t); diff --git a/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/EventResourceTest.java b/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/EventResourceTest.java index ce79003ae7..e65faf608a 100644 --- a/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/EventResourceTest.java +++ b/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/EventResourceTest.java @@ -142,7 +142,8 @@ public void testUploadEventFailsForRejectedOutcome(final VertxTestContext ctx) { eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.UNKNOWN)); }); ctx.completeNow(); })); @@ -187,7 +188,8 @@ public void testMessageLimitExceededForAnEventMessage(final VertxTestContext ctx eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); }); ctx.completeNow(); })); diff --git a/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/TelemetryResourceTest.java b/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/TelemetryResourceTest.java index 0566e1fdb7..97318156af 100644 --- a/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/TelemetryResourceTest.java +++ b/adapters/coap/src/test/java/org/eclipse/hono/adapter/coap/TelemetryResourceTest.java @@ -39,6 +39,7 @@ import org.eclipse.hono.client.ClientErrorException; import org.eclipse.hono.client.ServerErrorException; import org.eclipse.hono.client.command.CommandContext; +import org.eclipse.hono.service.AdapterDisabledException; import org.eclipse.hono.service.auth.DeviceUser; import org.eclipse.hono.service.metric.MetricsTags; import org.eclipse.hono.service.metric.MetricsTags.Direction; @@ -92,7 +93,7 @@ public void testUploadTelemetryFailsForDisabledTenant(final VertxTestContext ctx final var resource = givenAResource(adapter); // which is disabled for tenant "my-tenant" when(adapter.isAdapterEnabled(any(TenantObject.class))) - .thenReturn(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN))); + .thenReturn(Future.failedFuture(new AdapterDisabledException("my-tenant"))); // WHEN a device that belongs to "my-tenant" publishes a telemetry message final Buffer payload = Buffer.buffer("some payload"); @@ -118,7 +119,8 @@ public void testUploadTelemetryFailsForDisabledTenant(final VertxTestContext ctx eq(MetricsTags.QoS.AT_MOST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.TENANT_DISABLED_FOR_ADAPTER)); }); ctx.completeNow(); })); @@ -371,7 +373,8 @@ public void testMessageLimitExceededForATelemetryMessage(final VertxTestContext eq(MetricsTags.QoS.AT_MOST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); }); ctx.completeNow(); })); @@ -560,7 +563,8 @@ public void testUploadTelemetryReleasesCommandForFailedDownstreamSender(final Ve eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), eq(TtdStatus.COMMAND), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.UNKNOWN)); // and the command delivery is released verify(commandContext).release(any(Throwable.class)); }); diff --git a/adapters/http-base/src/main/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapter.java b/adapters/http-base/src/main/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapter.java index 9000e9b19a..5b0c82764c 100644 --- a/adapters/http-base/src/main/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapter.java +++ b/adapters/http-base/src/main/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapter.java @@ -45,6 +45,7 @@ import org.eclipse.hono.util.CommandConstants; import org.eclipse.hono.util.Constants; import org.eclipse.hono.util.MessageHelper; +import org.eclipse.hono.util.QoS; import org.eclipse.hono.util.RegistrationAssertion; import org.eclipse.hono.util.Strings; import org.eclipse.hono.util.TenantObject; @@ -85,7 +86,7 @@ public abstract class AbstractVertxBasedHttpProtocolAdapter startPromise) { .onComplete(startPromise); } - private Sample getMicrometerSample(final RoutingContext ctx) { + /** + * Gets the timer used to track the processing of a telemetry message. + * + * @param ctx The routing context to extract the sample from. + * @return The sample or {@code null} if the context does not + * contain a sample. + * @throws NullPointerException if ctx is {@code null}. + */ + protected Sample getMicrometerSample(final RoutingContext ctx) { return ctx.get(KEY_MICROMETER_SAMPLE); } @@ -778,7 +787,8 @@ private void doUploadMessage( qos, payloadSize, ctx.getTtdStatus(), - getMicrometerSample(ctx.getRoutingContext())); + getMicrometerSample(ctx.getRoutingContext()), + MetricsTags.ProcessingOutcomeReason.from(t)); TracingHelper.logError(currentSpan, t); currentSpan.finish(); return Future.failedFuture(t); @@ -1295,9 +1305,16 @@ public final void uploadCommandResponseMessage( }); } - private static MetricsTags.QoS getQoSLevel( - final EndpointType endpoint, - final org.eclipse.hono.util.QoS requestedQos) { + /** + * Get the QoS based on the endpoint and the requested QoS. + * + * @param endpoint The endpoint the message was sent to. + * @param requestedQos The QoS requested by the sender. + * @return The resulting QoS. + */ + protected static MetricsTags.QoS getQoSLevel( + final EndpointType endpoint, + final QoS requestedQos) { if (endpoint == EndpointType.EVENT) { return MetricsTags.QoS.AT_LEAST_ONCE; diff --git a/adapters/http-base/src/test/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapterTest.java b/adapters/http-base/src/test/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapterTest.java index 538184b6cc..8084468285 100644 --- a/adapters/http-base/src/test/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapterTest.java +++ b/adapters/http-base/src/test/java/org/eclipse/hono/adapter/http/AbstractVertxBasedHttpProtocolAdapterTest.java @@ -703,7 +703,8 @@ public void testUploadTelemetryWithTtdClosesCommandConsumerIfSendingFails() { eq(MetricsTags.QoS.AT_MOST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.UNKNOWN)); // and the command consumer is closed verify(commandConsumer).close(eq(false), any()); } @@ -786,7 +787,8 @@ public void testMessageLimitExceededForATelemetryMessage() { eq(MetricsTags.QoS.AT_MOST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); } /** @@ -825,7 +827,8 @@ public void testMessageLimitExceededForAnEventMessage() { eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), eq(TtdStatus.NONE), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); } /** diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapter.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapter.java index 5f29548a27..a4dd7dbeeb 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapter.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapter.java @@ -236,46 +236,83 @@ void handleProviderRoute(final HttpContext ctx, final LoraProvider provider) { final var gatewayDevice = ctx.getAuthenticatedDevice(); TracingHelper.setDeviceTags(currentSpan, gatewayDevice.getTenantId(), gatewayDevice.getDeviceId()); - try { - final LoraMessage loraMessage = provider.getMessage(ctx.getRoutingContext()); - final LoraMessageType type = loraMessage.getType(); - currentSpan.log(Map.of("message type", type)); - final String deviceId = loraMessage.getDevEUIAsString(); - currentSpan.setTag(TAG_LORA_DEVICE_ID, deviceId); - - switch (type) { - case UPLINK: - final UplinkLoraMessage uplinkMessage = (UplinkLoraMessage) loraMessage; - final Buffer payload = uplinkMessage.getPayload(); - - Optional.ofNullable(uplinkMessage.getMetaData()) - .ifPresent(metaData -> ctx.put(LoraConstants.APP_PROPERTY_META_DATA, metaData)); - - Optional.ofNullable(uplinkMessage.getAdditionalData()) - .ifPresent(additionalData -> ctx.put(LoraConstants.APP_PROPERTY_ADDITIONAL_DATA, additionalData)); - - final String contentType = payload.length() > 0 - ? LoraConstants.CONTENT_TYPE_LORA_BASE + provider.getProviderName() - : EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION; - - currentSpan.finish(); // uploadTelemetryMessage will finish the root span, therefore finish child span here already - uploadTelemetryMessage(ctx, gatewayDevice.getTenantId(), deviceId, payload, contentType); - registerCommandConsumerIfNeeded(provider, gatewayDevice, currentSpan.context()); - break; - default: - LOG.debug("discarding message of unsupported type [tenant: {}, device-id: {}, type: {}]", - gatewayDevice.getTenantId(), deviceId, type); - currentSpan.log("discarding message of unsupported type"); + + final MetricsTags.EndpointType endpoint = MetricsTags.EndpointType.fromString(ctx.getRequestedResource().getEndpoint()); + final MetricsTags.QoS qos = getQoSLevel(endpoint, ctx.getRequestedQos()); + + getTenantConfiguration(gatewayDevice.getTenantId(), currentSpan.context()) + .map(tenantObject -> { + final LoraMessage loraMessage; + try { + loraMessage = provider.getMessage(ctx.getRoutingContext()); + + } catch (final LoraProviderMalformedPayloadException e) { + LOG.debug("error processing request from provider [name: {}]", provider.getProviderName(), e); + TracingHelper.logError(currentSpan, "error processing request", e); + currentSpan.finish(); + handle400(ctx.getRoutingContext(), ERROR_MSG_INVALID_PAYLOAD); + metrics.reportTelemetry( + endpoint, + gatewayDevice.getTenantId(), + tenantObject, + MetricsTags.ProcessingOutcome.UNPROCESSABLE, + qos, + ctx.getRoutingContext().body().buffer().length(), + ctx.getTtdStatus(), + getMicrometerSample(ctx.getRoutingContext()), + MetricsTags.ProcessingOutcomeReason.BAD_SYNTAX); + return tenantObject; + } + final LoraMessageType type = loraMessage.getType(); + currentSpan.log(Map.of("message type", type)); + final String deviceId = loraMessage.getDevEUIAsString(); + currentSpan.setTag(TAG_LORA_DEVICE_ID, deviceId); + + switch (type) { + case UPLINK: + final UplinkLoraMessage uplinkMessage = (UplinkLoraMessage) loraMessage; + final Buffer payload = uplinkMessage.getPayload(); + + Optional.ofNullable(uplinkMessage.getMetaData()) + .ifPresent(metaData -> ctx.put(LoraConstants.APP_PROPERTY_META_DATA, metaData)); + + Optional.ofNullable(uplinkMessage.getAdditionalData()) + .ifPresent(additionalData -> ctx.put(LoraConstants.APP_PROPERTY_ADDITIONAL_DATA, additionalData)); + + final String contentType = payload.length() > 0 + ? LoraConstants.CONTENT_TYPE_LORA_BASE + provider.getProviderName() + : EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION; + + currentSpan.finish(); // uploadTelemetryMessage will finish the root span, therefore finish child span here already + uploadTelemetryMessage(ctx, gatewayDevice.getTenantId(), deviceId, payload, contentType); + registerCommandConsumerIfNeeded(provider, gatewayDevice, currentSpan.context()); + break; + default: + + LOG.debug("discarding message of unsupported type [tenant: {}, device-id: {}, type: {}]", + gatewayDevice.getTenantId(), deviceId, type); + currentSpan.log("discarding message of unsupported type"); + currentSpan.finish(); + // discard the message but return 202 to not cause errors on the LoRa provider side + handle202(ctx.getRoutingContext()); + + final MetricsTags.ProcessingOutcomeReason reason = type == LoraMessageType.UNKNOWN ? MetricsTags.ProcessingOutcomeReason.UNKNOWN_TYPE : MetricsTags.ProcessingOutcomeReason.UNSUPPORTED_TYPE; + metrics.reportTelemetry( + endpoint, + gatewayDevice.getTenantId(), + tenantObject, + MetricsTags.ProcessingOutcome.UNPROCESSABLE, + qos, + ctx.getRoutingContext().body().buffer().length(), + ctx.getTtdStatus(), + getMicrometerSample(ctx.getRoutingContext()), + reason); + } + return tenantObject; + }).onFailure(e -> { currentSpan.finish(); - // discard the message but return 202 to not cause errors on the LoRa provider side - handle202(ctx.getRoutingContext()); - } - } catch (final LoraProviderMalformedPayloadException e) { - LOG.debug("error processing request from provider [name: {}]", provider.getProviderName(), e); - TracingHelper.logError(currentSpan, "error processing request", e); - currentSpan.finish(); - handle400(ctx.getRoutingContext(), ERROR_MSG_INVALID_PAYLOAD); - } + LOG.debug("error processing request from provider [name: {}]", provider.getProviderName(), e); + }); } private void registerCommandConsumerIfNeeded(final LoraProvider provider, final Device gatewayDevice, diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/UnknownLoraMessage.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/UnknownLoraMessage.java new file mode 100644 index 0000000000..595b343b4d --- /dev/null +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/UnknownLoraMessage.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + + +package org.eclipse.hono.adapter.lora; + +import io.vertx.core.buffer.Buffer; + + +/** + * A Lora message that contains unknown data sent from an end-device to a Network Server. + * + */ +public class UnknownLoraMessage implements LoraMessage { + + /** + * {@inheritDoc} + */ + @Override + public final byte[] getDevEUI() { + return new byte[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public final String getDevEUIAsString() { + return ""; + } + + /** + * {@inheritDoc} + */ + @Override + public final LoraMessageType getType() { + return LoraMessageType.UNKNOWN; + } + + /** + * {@inheritDoc} + */ + @Override + public final Buffer getPayload() { + return Buffer.buffer(); + } +} diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/JsonBasedLoraProvider.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/JsonBasedLoraProvider.java index 60e1c3a1d1..8aa2e30d28 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/JsonBasedLoraProvider.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/JsonBasedLoraProvider.java @@ -20,6 +20,7 @@ import org.eclipse.hono.adapter.lora.LoraMessage; import org.eclipse.hono.adapter.lora.LoraMessageType; import org.eclipse.hono.adapter.lora.LoraMetaData; +import org.eclipse.hono.adapter.lora.UnknownLoraMessage; import org.eclipse.hono.adapter.lora.UplinkLoraMessage; import org.eclipse.hono.util.CommandEndpoint; import org.eclipse.hono.util.Strings; @@ -51,7 +52,7 @@ public LoraMessage getMessage(final RoutingContext ctx) { case UPLINK: return createUplinkMessage(ctx.request(), message); default: - throw new LoraProviderMalformedPayloadException(String.format("unsupported message type [%s]", type)); + return createUnknownMessage(ctx.request(), message); } } catch (final RuntimeException e) { // catch generic exception in order to also cover any (runtime) exceptions @@ -184,4 +185,26 @@ protected UplinkLoraMessage createUplinkMessage(final HttpServerRequest request, message.setAdditionalData(getAdditionalData(requestBody)); return message; } + + /** + * Creates an object representation of a Lora unknown message. + *

+ * This method uses the {@link #getDevEui(JsonObject)} + * method to extract relevant information from the request body to add + * to the returned message. + * + * @param request The request sent by the provider's Network Server. + * @param requestBody The JSON object contained in the request's body. + * @return The message. + * @throws RuntimeException if the message cannot be parsed. + */ + protected UnknownLoraMessage createUnknownMessage(final HttpServerRequest request, final JsonObject requestBody) { + + Objects.requireNonNull(requestBody); + + final UnknownLoraMessage message = new UnknownLoraMessage(); + return message; + } + + } diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProvider.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProvider.java index c26b355790..486673389c 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProvider.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProvider.java @@ -79,7 +79,10 @@ public Set pathPrefixes() { */ @Override public LoraMessageType getMessageType(final JsonObject loraMessage) { - return LoraMessageType.UPLINK; + if (loraMessage.containsKey(FIELD_KERLINK_PAYLOAD)) { + return LoraMessageType.UPLINK; + } + return LoraMessageType.UNKNOWN; } @Override diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProviderCustomContentType.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProviderCustomContentType.java index 84e4898eee..d830979e17 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProviderCustomContentType.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/KerlinkProviderCustomContentType.java @@ -15,6 +15,7 @@ import java.util.Base64; import java.util.Objects; +import java.util.Optional; import java.util.Set; import javax.enterprise.context.ApplicationScoped; @@ -57,7 +58,12 @@ public Set pathPrefixes() { */ @Override public LoraMessageType getMessageType(final JsonObject loraMessage) { - return LoraMessageType.UPLINK; + final Optional payload = LoraUtils.getChildObject(loraMessage, FIELD_UPLINK_USER_DATA, JsonObject.class) + .map(userData -> userData.getValue(FIELD_UPLINK_PAYLOAD)); + if (payload.isPresent()) { + return LoraMessageType.UPLINK; + } + return LoraMessageType.UNKNOWN; } /** diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/OrbiwiseProvider.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/OrbiwiseProvider.java index e8b81c9516..441d1cd7f4 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/OrbiwiseProvider.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/OrbiwiseProvider.java @@ -82,7 +82,10 @@ protected Buffer getPayload(final JsonObject loraMessage) { protected LoraMessageType getMessageType(final JsonObject loraMessage) { Objects.requireNonNull(loraMessage); - return LoraMessageType.UPLINK; + if (loraMessage.containsKey(FIELD_ORBIWISE_PAYLOAD)) { + return LoraMessageType.UPLINK; + } + return LoraMessageType.UNKNOWN; } @Override diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ProximusProvider.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ProximusProvider.java index a148532773..e73efd398f 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ProximusProvider.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ProximusProvider.java @@ -66,7 +66,11 @@ public Set pathPrefixes() { */ @Override protected LoraMessageType getMessageType(final JsonObject loraMessage) { - return LoraMessageType.UPLINK; + + if (loraMessage.containsKey(FIELD_PROXIMUS_PAYLOAD)) { + return LoraMessageType.UPLINK; + } + return LoraMessageType.UNKNOWN; } @Override diff --git a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ThingsNetworkProvider.java b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ThingsNetworkProvider.java index 2007efb841..3a9c4d5288 100644 --- a/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ThingsNetworkProvider.java +++ b/adapters/lora/src/main/java/org/eclipse/hono/adapter/lora/providers/ThingsNetworkProvider.java @@ -76,7 +76,10 @@ public Set pathPrefixes() { */ @Override protected LoraMessageType getMessageType(final JsonObject loraMessage) { - return LoraMessageType.UPLINK; + if (loraMessage.containsKey(FIELD_TTN_PAYLOAD_RAW)) { + return LoraMessageType.UPLINK; + } + return LoraMessageType.UNKNOWN; } @Override diff --git a/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapterTest.java b/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapterTest.java index 0bd731572c..c1216a914c 100644 --- a/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapterTest.java +++ b/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/LoraProtocolAdapterTest.java @@ -89,6 +89,7 @@ public class LoraProtocolAdapterTest extends ProtocolAdapterTestSupport !opName.equals(LoraProtocolAdapter.SPAN_NAME_PROCESS_MESSAGE)))).thenReturn(otherSpanBuilder); - final HttpAdapterMetrics metrics = mock(HttpAdapterMetrics.class); + metrics = mock(HttpAdapterMetrics.class); when(metrics.startTimer()).thenReturn(mock(Sample.class)); final LoraCommandSubscriptions commandSubscriptions = new LoraCommandSubscriptions(vertx, tracer); diff --git a/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/providers/LoraProviderTestBase.java b/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/providers/LoraProviderTestBase.java index 0c108f66f9..f272ce9b05 100644 --- a/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/providers/LoraProviderTestBase.java +++ b/adapters/lora/src/test/java/org/eclipse/hono/adapter/lora/providers/LoraProviderTestBase.java @@ -24,6 +24,7 @@ import org.eclipse.hono.adapter.lora.LoraCommand; import org.eclipse.hono.adapter.lora.LoraMessageType; +import org.eclipse.hono.adapter.lora.UnknownLoraMessage; import org.eclipse.hono.adapter.lora.UplinkLoraMessage; import org.eclipse.hono.util.CommandEndpoint; import org.junit.jupiter.api.BeforeEach; @@ -65,7 +66,7 @@ public abstract class LoraProviderTestBase { */ protected final RoutingContext getRequestContext(final LoraMessageType type, final String... classifiers) throws IOException { - final Buffer message = LoraTestUtil.loadTestFile(provider.getProviderName(), LoraMessageType.UPLINK, classifiers); + final Buffer message = LoraTestUtil.loadTestFile(provider.getProviderName(), type, classifiers); final HttpServerRequest request = mock(HttpServerRequest.class); final RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); @@ -99,6 +100,19 @@ public void testGetMessageSucceedsForUplinkMessage() throws IOException { assertMetaDataForUplinkMessage(loraMessage); } + /** + * Verifies that uplink messages are parsed correctly. + * + * @throws IOException If the file containing the example message could not be loaded. + */ + @Test + public void testGetMessageSucceedsForUnknownMessage() throws IOException { + + final RoutingContext request = getRequestContext(LoraMessageType.UNKNOWN); + final UnknownLoraMessage loraMessage = (UnknownLoraMessage) provider.getMessage(request); + assertThat(loraMessage.getType()).isEqualTo(LoraMessageType.UNKNOWN); + } + /** * Asserts presence of common properties in an uplink message. * diff --git a/adapters/lora/src/test/resources/payload/actilityEnterprise.unknown.json b/adapters/lora/src/test/resources/payload/actilityEnterprise.unknown.json new file mode 100644 index 0000000000..ffbec55de5 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/actilityEnterprise.unknown.json @@ -0,0 +1,5 @@ +{ + "DevEUI_unknown": { + + } +} diff --git a/adapters/lora/src/test/resources/payload/actilityWireless.unknown.json b/adapters/lora/src/test/resources/payload/actilityWireless.unknown.json new file mode 100644 index 0000000000..d8ee1dfe7f --- /dev/null +++ b/adapters/lora/src/test/resources/payload/actilityWireless.unknown.json @@ -0,0 +1,4 @@ +{ + "DevEUI_unknown": { + } +} diff --git a/adapters/lora/src/test/resources/payload/chirpStack.unknown.json b/adapters/lora/src/test/resources/payload/chirpStack.unknown.json new file mode 100644 index 0000000000..5abcdd80bb --- /dev/null +++ b/adapters/lora/src/test/resources/payload/chirpStack.unknown.json @@ -0,0 +1,42 @@ +{ + "applicationID": "123", + "applicationName": "temperature-sensor", + "deviceName": "garden-sensor", + "devEUI": "AgICAgICAgI=", + "devAddr": "AFE5Qg==", + "rxInfo": [ + { + "gatewayID": "AwMDAwMDAwM=", + "time": "2019-11-08T13:59:25.048445Z", + "timeSinceGPSEpoch": null, + "rssi": -48, + "loRaSNR": 9, + "channel": 5, + "rfChain": 0, + "board": 0, + "antenna": 0, + "location": { + "latitude": 52.3740364, + "longitude": 4.9144401, + "altitude": 10.5 + }, + "fineTimestampType": "NONE", + "context": "9u/uvA==", + "uplinkID": "jhMh8Gq6RAOChSKbi83RHQ==" + } + ], + "txInfo": { + "frequency": 868100000, + "modulation": "LORA", + "loRaModulationInfo": { + "bandwidth": 125, + "spreadingFactor": 11, + "codeRate": "4/5", + "polarizationInversion": false + } + }, + "dr": 1, + "tags": { + "key": "value" + } +} \ No newline at end of file diff --git a/adapters/lora/src/test/resources/payload/chirpStackV4.unknown.json b/adapters/lora/src/test/resources/payload/chirpStackV4.unknown.json new file mode 100644 index 0000000000..03a32ee320 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/chirpStackV4.unknown.json @@ -0,0 +1,18 @@ +{ + "deduplicationId": "c9dbe358-2578-4fb7-b295-66b44edc45a6", + "time": "2022-07-18T09:33:28.823500726+00:00", + "deviceInfo": { + "tenantId": "52f14cd4-c6f1-4fbd-8f87-4025e1d49242", + "tenantName": "ChirpStack", + "applicationId": "17c82e96-be03-4f38-aef3-f83d48582d97", + "applicationName": "Test application", + "deviceProfileId": "14855bf7-d10d-4aee-b618-ebfcb64dc7ad", + "deviceProfileName": "Test device-profile", + "deviceName": "Test device", + "devEui": "0101010101010101", + "tags": { + "key": "value" + } + }, + "devAddr": "00189440" +} \ No newline at end of file diff --git a/adapters/lora/src/test/resources/payload/everynet.unknown.json b/adapters/lora/src/test/resources/payload/everynet.unknown.json new file mode 100644 index 0000000000..835bba126d --- /dev/null +++ b/adapters/lora/src/test/resources/payload/everynet.unknown.json @@ -0,0 +1,18 @@ +{ + "meta": { + "network": "9e9bf02a", + "packet_hash": "adc6bcac0d06195bc0329c3ef6a2d6ea", + "application": "b3a1067cf7085309", + "time": 1504638900.866375, + "device": "8c30dd074be218cb", + "packet_id": "287f9555a3e8b000ffc8c3c50f60e309", + "gateway": "017e8cd996cd3a0e" + }, + "params": { + "dev_eui": "8c30dd074be218cb", + "dev_addr": "01d6dcd6", + "dev_nonce": "f9e7", + "net_id": "000000" + }, + "type": "join_request" +} \ No newline at end of file diff --git a/adapters/lora/src/test/resources/payload/firefly.unknown.json b/adapters/lora/src/test/resources/payload/firefly.unknown.json new file mode 100644 index 0000000000..11a2e0ed3c --- /dev/null +++ b/adapters/lora/src/test/resources/payload/firefly.unknown.json @@ -0,0 +1,9 @@ +{ + "uid": "8a569133-aa44-4d7e-810d-af62faf9f722", + "type": "join_accept", + "received_at": "2016-07-15T14:31:11", + "frame_counter": null, + "for_frame_counter": null, + "direction": "down", + "device_eui": "2564927382738492" +} \ No newline at end of file diff --git a/adapters/lora/src/test/resources/payload/kerlink-custom-content-type.unknown.json b/adapters/lora/src/test/resources/payload/kerlink-custom-content-type.unknown.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/kerlink-custom-content-type.unknown.json @@ -0,0 +1,2 @@ +{ +} diff --git a/adapters/lora/src/test/resources/payload/kerlink.unknown.json b/adapters/lora/src/test/resources/payload/kerlink.unknown.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/kerlink.unknown.json @@ -0,0 +1,2 @@ +{ +} diff --git a/adapters/lora/src/test/resources/payload/liveObjects.unknown.json b/adapters/lora/src/test/resources/payload/liveObjects.unknown.json new file mode 100644 index 0000000000..dd7c2ed826 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/liveObjects.unknown.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "source": "urn:lo:nsid:lora:4883C7DF3001114B", + "encoding": "siconia_temperature_humidity_pressure", + "group": { + "path": "/", + "id": "root" + }, + "device": { + "location": { + "provider": "static", + "lon": 2.545599, + "lat": 48.82198 + } + }, + "network": { + "lora": { + "rssi": -110, + "esp": -111.76, + "ack": false, + "fcnt": 27517, + "devEUI": "01020304050607AB", + "frequency": 868.5, + "signalLevel": 5, + "gatewayCnt": 5, + "sf": 10, + "messageType": "JOIN", + "port": 10, + "snr": 3, + "location": { + "provider": "lora", + "alt": 0, + "accuracy": 10000, + "lon": 2.526197, + "lat": 48.83361 + }, + "missingFcnt": 0 + } + } + }, + "streamId": "urn:lora:20635F0108000C8D!uplink", + "created": "2021-05-30T19:02:46.789Z", + "extra": { + }, + "location": { + "provider": "static", + "alt": null, + "accuracy": null, + "lon": 2.545599, + "lat": 48.82198 + }, + "model": "lora_v0", + "id": "60b3e15603b409370d2d2b10", + "value": { + "payload": "62756D6C7578", + "temperature": 21.81, + "humidity": 46.3, + "pressure": 1013.08 + }, + "timestamp": "2021-05-30T19:02:46.161Z", + "tags": [] +} diff --git a/adapters/lora/src/test/resources/payload/loriot.unknown.json b/adapters/lora/src/test/resources/payload/loriot.unknown.json new file mode 100644 index 0000000000..2c674e5e99 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/loriot.unknown.json @@ -0,0 +1,24 @@ +{ + "cmd": "join", + "seqno": 136, + "EUI": "01020304050607AB", + "ts": 1573574479110, + "fcnt": 135, + "port": 2, + "freq": 868300000, + "dr": "SF7 BW125 4/5", + "ack": false, + "gws": [ + { + "rssi": -63, + "snr": 10, + "ts": 1573574479110, + "time": "2019-11-12T16:01:19.103417Z", + "gweui": "0101010101010101", + "lat": 12, + "lon": 4.4007817 + } + ], + "bat": 255, + "data": "62756D6C7578" +} diff --git a/adapters/lora/src/test/resources/payload/multiTech.unknown.json b/adapters/lora/src/test/resources/payload/multiTech.unknown.json new file mode 100644 index 0000000000..795763142b --- /dev/null +++ b/adapters/lora/src/test/resources/payload/multiTech.unknown.json @@ -0,0 +1,26 @@ +{ + "tmst": 320955108, + "chan": 2, + "rfch": 0, + "freq": 868.5, + "stat": 1, + "modu": "LORA", + "datr": "SF12BW125", + "codr": "4/5", + "lsnr": 8, + "rssi": -35, + "opts": "", + "size": 31, + "fcnt": 2, + "cls": 0, + "port": 2, + "mhdr": "80f97d5e01800200", + "appeui": "70-b3-d5-7b-b1-c0-ff-ee", + "deveui": "01-02-03-04-05-06-07-AB", + "devaddr": "015e7df9", + "ack": false, + "adr": true, + "gweui": "00-80-00-00-a0-00-4b-95", + "seqn": 2, + "time": "2021-04-19T06:19:30.762055Z" +} diff --git a/adapters/lora/src/test/resources/payload/objenious.unknown.json b/adapters/lora/src/test/resources/payload/objenious.unknown.json new file mode 100644 index 0000000000..8093e95bf1 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/objenious.unknown.json @@ -0,0 +1,40 @@ +{ + "id": "uplink-7-1221", + "device_id": 1325751707030347, + "group_id": 1, + "group": "hono", + "profile_id": 4, + "profile": "test-tag", + "type": "unknown", + "timestamp": "2019-02-27T14:48:35.544Z", + "count": 7, + "payload_encrypted": "4b503954fe7a25629aa9d18b3dc4daa97635c4f5e391e6b7471d4e31d04958b156aa", + "payload_cleartext": "62756D6C7578", + "device_properties": { + "appeui": "be4800dfcb4f4333", + "deveui": "01020304050607AB", + "external_id": "" + }, + "protocol_data": { + "AppNonce": "d04ade", + "DevAddr": "d508e11e", + "DevNonce": "1221", + "NetID": "000007", + "best_gateway_id": "O24614", + "gateways": 25, + "lora_version": 0, + "noise": -10.22221891083863, + "port": 1, + "rssi": -103, + "sf": 11, + "signal": -102.12901394045883, + "snr": 1 + }, + "lat": 53.108805, + "lng": 9.193430, + "geolocation_type": "network", + "geolocation_precision": 1000, + "city_code": "12345", + "city_name": "none", + "delivered_at": null +} diff --git a/adapters/lora/src/test/resources/payload/orbiwise.unknown.json b/adapters/lora/src/test/resources/payload/orbiwise.unknown.json new file mode 100644 index 0000000000..0b67b62797 --- /dev/null +++ b/adapters/lora/src/test/resources/payload/orbiwise.unknown.json @@ -0,0 +1,27 @@ +{ + "confirmed":true, + "cr_used":"4/5", + "data_format":"hex", + "decrypted":true, + "devaddr":277690613, + "deveui":"01020304050607AB", + "device_redundancy":1, + "dr_used":"SF8BW500", + "early":false, + "fcnt":"57", + "freq":868500000000000, + "gtw_info":[{"ant":0, + "gtw_id":"10000001", + "rssi":-112, + "snr":-7.75}], + "id":1595970491225, + "live":false, + "mac_msg":"80f5388d1080581502958f3e69ed4ee658ca687c4bda6d9413b865d19f", + "port":2, + "rssi":-32, + "session_id":"e80181c0-b4c0-4a82-87b1-4d6844db553d", + "sf_used":10, + "snr":10, + "time_on_air_ms":30.848, + "timestamp":"2020-07-28T21:08:11.225Z" +} diff --git a/adapters/lora/src/test/resources/payload/proximus.unknown.json b/adapters/lora/src/test/resources/payload/proximus.unknown.json new file mode 100644 index 0000000000..f0bd22e1cb --- /dev/null +++ b/adapters/lora/src/test/resources/payload/proximus.unknown.json @@ -0,0 +1,38 @@ +{ + "companyName": "Hono Demo", + "thingName": "Temperature and humidity", + "thingVersion": "1", + "thingType": "Temperature and humidity", + "vendor": "BLX", + "latitude": "", + "longitude": "", + "description": "", + "locationFriendlyName1": "Home", + "locationFriendlyName2": "", + "containerFriendlyName": "Temperature", + "container": "0x0301.0x0000.0.m2m", + "value": "12.45", + "postfix": "°", + "timestamp": "1551278360", + "DevEUI": "01020304050607AB", + "DevAddr": "158205C6", + "FPort": "6", + "Fcntup": "23", + "Ackbit": "", + "Adrbit": "1", + "Fcntdn": "985", + "Lrcid": "00000239", + "Lrrrssi": "-54.000000", + "Lrrsnr": "2.000000", + "Spfact": "11", + "Subband": "G1", + "Channel": "LC2", + "Dvlrrcnt": "2", + "Lrrid": "038B0321", + "Customerid": "330220929", + "Customerdata": "{\"loc\":{\"lat\":\"53.108805\",\"lon\":\"9.193430\"},\"alr\":{\"pro\":\"UNOS/IFWKU\",\"ver\":\"1\"}}", + "Modelcfg": "0", + "Batterylevel": "", + "Batterytime": "", + "margin": "" +} diff --git a/adapters/lora/src/test/resources/payload/theThingsStack.unknown.json b/adapters/lora/src/test/resources/payload/theThingsStack.unknown.json new file mode 100644 index 0000000000..50d19db50d --- /dev/null +++ b/adapters/lora/src/test/resources/payload/theThingsStack.unknown.json @@ -0,0 +1,16 @@ +{ + "end_device_ids": { + "device_id": "dev1", + "application_ids": { + "application_id": "app1" + }, + "dev_eui": "01020304050607AB", + "join_eui": "800000000000000C", + "dev_addr": "00BCB929" + }, + "correlation_ids": [ + "as:up:01..." + ], + "received_at": "2020-02-12T15:15...", + "simulated": true +} diff --git a/adapters/lora/src/test/resources/payload/ttn.unknown.json b/adapters/lora/src/test/resources/payload/ttn.unknown.json new file mode 100644 index 0000000000..3a859ed69b --- /dev/null +++ b/adapters/lora/src/test/resources/payload/ttn.unknown.json @@ -0,0 +1,36 @@ +{ + "app_id": "ttn-app-id", + "dev_id": "ttn-dev-id", + "hardware_serial": "01020304050607AB", + "port": 1, + "counter": 9, + "is_retry": false, + "confirmed": false, + "payload_fields": {}, + "metadata": { + "airtime": 32527000, + "time": "2019-03-03T04:20:04Z", + "frequency": 868.1, + "modulation": "LORA", + "data_rate": "SF7BW125", + "bit_rate": 50000, + "coding_rate": "4/5", + "gateways": [ + { + "gtw_id": "0203040506070809", + "timestamp": 12345, + "time": "2019-03-03T04:19:32Z", + "channel": 0, + "rssi": -25, + "snr": 5, + "rf_chain": 0, + "latitude": 53.1088, + "longitude": 9.1934, + "altitude": 90 + } + ], + "latitude": 53.1088, + "longitude": 9.1934, + "altitude": 90 + } +} diff --git a/adapters/mqtt-base/src/main/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapter.java b/adapters/mqtt-base/src/main/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapter.java index 13910a0fea..fbe69a749f 100644 --- a/adapters/mqtt-base/src/main/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapter.java +++ b/adapters/mqtt-base/src/main/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapter.java @@ -731,7 +731,8 @@ public final Future uploadTelemetryMessage(final MqttContext ctx) { ProcessingOutcome.from(t), qos, payload.length(), - ctx.getTimer()); + ctx.getTimer(), + MetricsTags.ProcessingOutcomeReason.from(t)); return Future.failedFuture(t); }); } @@ -781,7 +782,8 @@ public final Future uploadEventMessage(final MqttContext ctx) { ProcessingOutcome.from(t), qos, payload.length(), - ctx.getTimer()); + ctx.getTimer(), + MetricsTags.ProcessingOutcomeReason.from(t)); return Future.failedFuture(t); }); } diff --git a/adapters/mqtt-base/src/test/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapterTest.java b/adapters/mqtt-base/src/test/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapterTest.java index 5e0eda7a2d..650ac312ba 100644 --- a/adapters/mqtt-base/src/test/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapterTest.java +++ b/adapters/mqtt-base/src/test/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapterTest.java @@ -1366,7 +1366,8 @@ public void testMessageLimitExceededForATelemetryMessage(final VertxTestContext eq(MetricsTags.ProcessingOutcome.UNPROCESSABLE), any(MetricsTags.QoS.class), anyInt(), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); }); ctx.completeNow(); })); @@ -1413,7 +1414,8 @@ public void testMessageLimitExceededForAnEventMessage(final VertxTestContext ctx eq(MetricsTags.ProcessingOutcome.UNPROCESSABLE), any(MetricsTags.QoS.class), anyInt(), - any()); + any(), + eq(MetricsTags.ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED)); }); ctx.completeNow(); })); diff --git a/service-base/pom.xml b/service-base/pom.xml index 4fa5b432ad..ba6b7009f9 100644 --- a/service-base/pom.xml +++ b/service-base/pom.xml @@ -196,6 +196,14 @@ micrometer-registry-prometheus test + + org.eclipse.hono + hono-client-registry + + + org.eclipse.hono + hono-client-registry + diff --git a/adapter-base/src/main/java/org/eclipse/hono/adapter/AdapterDisabledException.java b/service-base/src/main/java/org/eclipse/hono/service/AdapterDisabledException.java similarity index 97% rename from adapter-base/src/main/java/org/eclipse/hono/adapter/AdapterDisabledException.java rename to service-base/src/main/java/org/eclipse/hono/service/AdapterDisabledException.java index 12b86dbdd5..72301bb6d5 100644 --- a/adapter-base/src/main/java/org/eclipse/hono/adapter/AdapterDisabledException.java +++ b/service-base/src/main/java/org/eclipse/hono/service/AdapterDisabledException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hono.adapter; +package org.eclipse.hono.service; import java.net.HttpURLConnection; diff --git a/service-base/src/main/java/org/eclipse/hono/service/metric/Metrics.java b/service-base/src/main/java/org/eclipse/hono/service/metric/Metrics.java index c2478626bf..e9286f5757 100644 --- a/service-base/src/main/java/org/eclipse/hono/service/metric/Metrics.java +++ b/service-base/src/main/java/org/eclipse/hono/service/metric/Metrics.java @@ -116,6 +116,38 @@ void reportTelemetry( int payloadSize, Sample timer); + /** + * Reports a telemetry message or event received from a device. + *

+ * The payload size of the message is calculated based on the configured + * minimum message size and the calculated size is reported. + *

+ * The configured minimum message size is retrieved from the tenant + * configuration object. + * + * @param type The type of message received, e.g. telemetry or event. + * @param tenantId The tenant that the device belongs to. + * @param tenantObject The tenant configuration object or {@code null}. + * @param outcome The outcome of processing the message. + * @param qos The delivery semantics used for sending the message downstream. + * @param payloadSize The number of bytes contained in the message's payload. + * @param timer The timer indicating the amount of time used + * for processing the message. + * @param reason The reason providing more information about the outcome of processing the message or {@code null}. + * @throws NullPointerException if any of the parameters except the tenant object or reason are {@code null}. + * @throws IllegalArgumentException if type is neither telemetry nor event or + * if payload size is negative. + */ + void reportTelemetry( + MetricsTags.EndpointType type, + String tenantId, + TenantObject tenantObject, + MetricsTags.ProcessingOutcome outcome, + MetricsTags.QoS qos, + int payloadSize, + Sample timer, + MetricsTags.ProcessingOutcomeReason reason); + /** * Reports a telemetry message or event received from a device. *

@@ -148,6 +180,40 @@ void reportTelemetry( MetricsTags.TtdStatus ttdStatus, Sample timer); + /** + * Reports a telemetry message or event received from a device. + *

+ * The payload size of the message is calculated based on the configured + * minimum message size and the calculated size is reported. + *

+ * The configured minimum message size is retrieved from the tenant + * configuration object. + * + * @param type The type of message received, e.g. telemetry or event. + * @param tenantId The tenant that the device belongs to. + * @param tenantObject The tenant configuration object or {@code null}. + * @param outcome The outcome of processing the message. + * @param qos The delivery semantics used for sending the message downstream. + * @param payloadSize The number of bytes contained in the message's payload. + * @param ttdStatus The outcome of processing the TTD value contained in the message. + * @param timer The timer indicating the amount of time used + * for processing the message. + * @param reason The reason providing more information about the outcome of processing the message. + * @throws NullPointerException if any of the parameters except the tenant object are {@code null}. + * @throws IllegalArgumentException if type is neither telemetry nor event or + * if payload size is negative. + */ + void reportTelemetry( + MetricsTags.EndpointType type, + String tenantId, + TenantObject tenantObject, + MetricsTags.ProcessingOutcome outcome, + MetricsTags.QoS qos, + int payloadSize, + MetricsTags.TtdStatus ttdStatus, + Sample timer, + MetricsTags.ProcessingOutcomeReason reason); + /** * Reports a command & control message being transferred to/from a device. *

diff --git a/service-base/src/main/java/org/eclipse/hono/service/metric/MetricsTags.java b/service-base/src/main/java/org/eclipse/hono/service/metric/MetricsTags.java index 32c8e8b526..f970a25d74 100644 --- a/service-base/src/main/java/org/eclipse/hono/service/metric/MetricsTags.java +++ b/service-base/src/main/java/org/eclipse/hono/service/metric/MetricsTags.java @@ -14,6 +14,9 @@ package org.eclipse.hono.service.metric; import org.eclipse.hono.client.ClientErrorException; +import org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException; +import org.eclipse.hono.service.AdapterDisabledException; +import org.eclipse.hono.service.http.HttpUtils; import org.eclipse.hono.util.CommandConstants; import org.eclipse.hono.util.EventConstants; import org.eclipse.hono.util.Hostnames; @@ -241,6 +244,82 @@ public Tag asTag() { } } + /** + * A status providing more information about the outcome of processing a message received from a device. + *

+ * The immutable enum check is disabled because the offending field's (tag) type + * is in fact immutable but has no corresponding annotation. + */ + @SuppressWarnings("ImmutableEnumChecker") + public enum ProcessingOutcomeReason { + + /** + * The message received has a bad syntax and could not be parsed. + */ + BAD_SYNTAX("bad-syntax"), + /** + * The type of the message could not be deduced from the message. + */ + UNKNOWN_TYPE("unknown-type"), + /** + * The type of the message is not supported for this action. + */ + UNSUPPORTED_TYPE("unsupported-type"), + /** + * Tenant is disabled or not registered. + */ + TENANT_DISABLED_OR_NOT_REGISTERED("tenant-disabled-or-not-registered"), + /** + * Tenant is disabled for this adapter. + */ + TENANT_DISABLED_FOR_ADAPTER("tenant-disabled-for-adapter"), + /** + * Message limit exceeded. + */ + MESSAGE_LIMIT_EXCEEDED("message-limit-exceeded"), + /** + * Unknown reason. + */ + UNKNOWN("unknown"); + + static final String TAG_NAME = "reason"; + + private final Tag tag; + + ProcessingOutcomeReason(final String tagValue) { + this.tag = Tag.of(TAG_NAME, tagValue); + } + + /** + * Gets a reason for an error. + * + * @param t The error. + * @return The reason. + */ + public static ProcessingOutcomeReason from(final Throwable t) { + if (TenantDisabledOrNotRegisteredException.class.isInstance(t)) { + return ProcessingOutcomeReason.TENANT_DISABLED_OR_NOT_REGISTERED; + } else if (AdapterDisabledException.class.isInstance(t)) { + return ProcessingOutcomeReason.TENANT_DISABLED_FOR_ADAPTER; + } else if (ClientErrorException.class.isInstance(t)) { + final ClientErrorException exception = (ClientErrorException) t; + if (exception.getErrorCode() == HttpUtils.HTTP_TOO_MANY_REQUESTS) { + return ProcessingOutcomeReason.MESSAGE_LIMIT_EXCEEDED; + } + } + return ProcessingOutcomeReason.UNKNOWN; + } + + /** + * Gets a Micrometer tag for the outcome. + * + * @return The tag. + */ + public Tag asTag() { + return tag; + } + } + /** * Status indicating the outcome of processing a TTD value contained in a message received from a device. *

diff --git a/service-base/src/main/java/org/eclipse/hono/service/metric/MicrometerBasedMetrics.java b/service-base/src/main/java/org/eclipse/hono/service/metric/MicrometerBasedMetrics.java index 33c0b264bd..684ea2099f 100644 --- a/service-base/src/main/java/org/eclipse/hono/service/metric/MicrometerBasedMetrics.java +++ b/service-base/src/main/java/org/eclipse/hono/service/metric/MicrometerBasedMetrics.java @@ -259,6 +259,19 @@ public void reportTelemetry( reportTelemetry(type, tenantId, tenantObject, outcome, qos, payloadSize, MetricsTags.TtdStatus.NONE, timer); } + @Override + public void reportTelemetry( + final MetricsTags.EndpointType type, + final String tenantId, + final TenantObject tenantObject, + final ProcessingOutcome outcome, + final MetricsTags.QoS qos, + final int payloadSize, + final Timer.Sample timer, + final MetricsTags.ProcessingOutcomeReason reason) { + reportTelemetry(type, tenantId, tenantObject, outcome, qos, payloadSize, MetricsTags.TtdStatus.NONE, timer, reason); + } + @Override public final void reportTelemetry( final MetricsTags.EndpointType type, @@ -270,6 +283,21 @@ public final void reportTelemetry( final MetricsTags.TtdStatus ttdStatus, final Timer.Sample timer) { + reportTelemetry(type, tenantId, tenantObject, outcome, qos, payloadSize, ttdStatus, timer, null); + } + + @Override + public void reportTelemetry( + final MetricsTags.EndpointType type, + final String tenantId, + final TenantObject tenantObject, + final ProcessingOutcome outcome, + final MetricsTags.QoS qos, + final int payloadSize, + final MetricsTags.TtdStatus ttdStatus, + final Timer.Sample timer, + final MetricsTags.ProcessingOutcomeReason reason) { + Objects.requireNonNull(type); Objects.requireNonNull(tenantId); Objects.requireNonNull(outcome); @@ -284,10 +312,14 @@ public final void reportTelemetry( } final Tags tags = Tags.of(type.asTag()) - .and(MetricsTags.getTenantTag(tenantId)) - .and(outcome.asTag()) - .and(qos.asTag()) - .and(ttdStatus.asTag()); + .and(MetricsTags.getTenantTag(tenantId)) + .and(outcome.asTag()) + .and(qos.asTag()) + .and(ttdStatus.asTag()); + + if (reason != null) { + tags.and(reason.asTag()); + } timer.stop(this.registry.timer(METER_TELEMETRY_PROCESSING_DURATION, tags)); diff --git a/service-base/src/main/java/org/eclipse/hono/service/metric/NoopBasedMetrics.java b/service-base/src/main/java/org/eclipse/hono/service/metric/NoopBasedMetrics.java index 8ecd30946b..3cf13952bd 100644 --- a/service-base/src/main/java/org/eclipse/hono/service/metric/NoopBasedMetrics.java +++ b/service-base/src/main/java/org/eclipse/hono/service/metric/NoopBasedMetrics.java @@ -99,6 +99,19 @@ public void reportTelemetry( } } + @Override + public void reportTelemetry( + final EndpointType type, + final String tenantId, + final TenantObject tenantObject, + final ProcessingOutcome outcome, + final MetricsTags.QoS qos, + final int payloadSize, + final Sample timer, + final MetricsTags.ProcessingOutcomeReason reason) { + reportTelemetry(type, tenantId, tenantObject, outcome, qos, payloadSize, timer); + } + @Override public void reportTelemetry( final MetricsTags.EndpointType type, @@ -112,6 +125,20 @@ public void reportTelemetry( reportTelemetry(type, tenantId, tenantObject, outcome, qos, payloadSize, timer); } + @Override + public void reportTelemetry( + final EndpointType type, + final String tenantId, + final TenantObject tenantObject, + final ProcessingOutcome outcome, + final MetricsTags.QoS qos, + final int payloadSize, + final MetricsTags.TtdStatus ttdStatus, + final Sample timer, + final MetricsTags.ProcessingOutcomeReason reason) { + reportTelemetry(type, tenantId, tenantObject, outcome, qos, payloadSize, timer); + } + @Override public void reportCommand( final Direction direction,