diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java b/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java index 2daf5c5b7b..399d0f4a56 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java @@ -16,21 +16,47 @@ package org.thingsboard.monitoring; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.EnableScheduling; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.monitoring.service.BaseMonitoringService; +import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; @SpringBootApplication @EnableScheduling @Slf4j public class ThingsboardMonitoringApplication { + @Autowired + private List> monitoringServices; + + @Value("${monitoring.monitoring_rate_ms}") + private int monitoringRateMs; + public static void main(String[] args) { new SpringApplicationBuilder(ThingsboardMonitoringApplication.class) .properties(Map.of("spring.config.name", "tb-monitoring")) .run(args); } + @EventListener(ApplicationReadyEvent.class) + public void startMonitoring() { + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("monitoring-executor")); + scheduler.scheduleWithFixedDelay(() -> { + monitoringServices.forEach(monitoringService -> { + monitoringService.runChecks(); + }); + }, 0, monitoringRateMs, TimeUnit.MILLISECONDS); + } + } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTargetConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringConfig.java similarity index 84% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTargetConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringConfig.java index 5f1ab49e91..4304ecdf0e 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTargetConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringConfig.java @@ -15,12 +15,10 @@ */ package org.thingsboard.monitoring.config; -import lombok.Data; +import java.util.List; -@Data -public class MonitoringTargetConfig { +public interface MonitoringConfig { - private String baseUrl; - private DeviceConfig device; + List getTargets(); } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java new file mode 100644 index 0000000000..0e62670f81 --- /dev/null +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.monitoring.config; + +import java.util.UUID; + +public interface MonitoringTarget { + + UUID getDeviceId(); + +} diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/CoapTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/CoapTransportMonitoringConfig.java similarity index 91% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/service/CoapTransportMonitoringConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/CoapTransportMonitoringConfig.java index 905858b4eb..a95aca18f4 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/CoapTransportMonitoringConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/CoapTransportMonitoringConfig.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config.service; +package org.thingsboard.monitoring.config.transport; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import org.thingsboard.monitoring.config.TransportType; @Component @ConditionalOnProperty(name = "monitoring.transports.coap.enabled", havingValue = "true") diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/DeviceConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/DeviceConfig.java similarity index 92% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/DeviceConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/DeviceConfig.java index 548ad6d08b..94c0ea3a84 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/DeviceConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/DeviceConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config; +package org.thingsboard.monitoring.config.transport; import lombok.Data; import org.apache.commons.lang3.StringUtils; @@ -25,6 +25,7 @@ import java.util.UUID; public class DeviceConfig { private UUID id; + private String name; private DeviceCredentials credentials; public void setId(String id) { diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/HttpTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/HttpTransportMonitoringConfig.java similarity index 91% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/service/HttpTransportMonitoringConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/HttpTransportMonitoringConfig.java index 3a3e8f612c..3b2be2343d 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/HttpTransportMonitoringConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/HttpTransportMonitoringConfig.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config.service; +package org.thingsboard.monitoring.config.transport; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import org.thingsboard.monitoring.config.TransportType; @Component @ConditionalOnProperty(name = "monitoring.transports.http.enabled", havingValue = "true") diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/Lwm2mTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/Lwm2mTransportMonitoringConfig.java similarity index 91% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/service/Lwm2mTransportMonitoringConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/Lwm2mTransportMonitoringConfig.java index 4d97de8366..7d375abd6e 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/Lwm2mTransportMonitoringConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/Lwm2mTransportMonitoringConfig.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config.service; +package org.thingsboard.monitoring.config.transport; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import org.thingsboard.monitoring.config.TransportType; @Component @ConditionalOnProperty(name = "monitoring.transports.lwm2m.enabled", havingValue = "true") diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/MqttTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/MqttTransportMonitoringConfig.java similarity index 92% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/service/MqttTransportMonitoringConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/MqttTransportMonitoringConfig.java index 7fef5b95dc..ebc91a327e 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/MqttTransportMonitoringConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/MqttTransportMonitoringConfig.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config.service; +package org.thingsboard.monitoring.config.transport; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import org.thingsboard.monitoring.config.TransportType; @Component @ConditionalOnProperty(name = "monitoring.transports.mqtt.enabled", havingValue = "true") diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportInfo.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportInfo.java similarity index 80% rename from monitoring/src/main/java/org/thingsboard/monitoring/data/TransportInfo.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportInfo.java index d7619ea32d..6fb2e25ec9 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportInfo.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportInfo.java @@ -13,20 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.data; +package org.thingsboard.monitoring.config.transport; import lombok.Data; -import org.thingsboard.monitoring.config.TransportType; @Data public class TransportInfo { private final TransportType transportType; - private final String url; + private final String baseUrl; @Override public String toString() { - return String.format("%s (%s)", transportType, url); + return String.format("%s transport (%s)", transportType, baseUrl); } } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/TransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java similarity index 73% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/service/TransportMonitoringConfig.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java index 0712d1d919..77d702f779 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/TransportMonitoringConfig.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringConfig.java @@ -13,20 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config.service; +package org.thingsboard.monitoring.config.transport; import lombok.Data; -import org.thingsboard.monitoring.config.MonitoringTargetConfig; -import org.thingsboard.monitoring.config.TransportType; +import org.thingsboard.monitoring.config.MonitoringConfig; import java.util.List; @Data -public abstract class TransportMonitoringConfig { +public abstract class TransportMonitoringConfig implements MonitoringConfig { private int requestTimeoutMs; - private List targets; + private List targets; public abstract TransportType getTransportType(); diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java new file mode 100644 index 0000000000..816f64fbce --- /dev/null +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.monitoring.config.transport; + +import lombok.Data; +import org.thingsboard.monitoring.config.MonitoringTarget; + +import java.util.UUID; + +@Data +public class TransportMonitoringTarget implements MonitoringTarget { + + private String baseUrl; + private DeviceConfig device; // set manually during initialization + + @Override + public UUID getDeviceId() { + return device.getId(); + } + +} diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/TransportType.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportType.java similarity index 67% rename from monitoring/src/main/java/org/thingsboard/monitoring/config/TransportType.java rename to monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportType.java index a3aaa7ec98..eeb085348b 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/config/TransportType.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportType.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.config; +package org.thingsboard.monitoring.config.transport; import lombok.AllArgsConstructor; import lombok.Getter; -import org.thingsboard.monitoring.transport.TransportHealthChecker; -import org.thingsboard.monitoring.transport.impl.CoapTransportHealthChecker; -import org.thingsboard.monitoring.transport.impl.HttpTransportHealthChecker; -import org.thingsboard.monitoring.transport.impl.Lwm2mTransportHealthChecker; -import org.thingsboard.monitoring.transport.impl.MqttTransportHealthChecker; +import org.thingsboard.monitoring.service.transport.TransportHealthChecker; +import org.thingsboard.monitoring.service.transport.impl.CoapTransportHealthChecker; +import org.thingsboard.monitoring.service.transport.impl.HttpTransportHealthChecker; +import org.thingsboard.monitoring.service.transport.impl.Lwm2mTransportHealthChecker; +import org.thingsboard.monitoring.service.transport.impl.MqttTransportHealthChecker; @AllArgsConstructor @Getter diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java index 8141c34586..3370d42462 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java @@ -15,16 +15,14 @@ */ package org.thingsboard.monitoring.data; -import org.thingsboard.monitoring.config.TransportType; - public class Latencies { public static final String WS_UPDATE = "wsUpdate"; public static final String WS_CONNECT = "wsConnect"; public static final String LOG_IN = "logIn"; - public static String transportRequest(TransportType transportType) { - return String.format("%sTransportRequest", transportType.name().toLowerCase()); + public static String request(String key) { + return String.format("%sRequest", key); } } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportFailureException.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/ServiceFailureException.java similarity index 80% rename from monitoring/src/main/java/org/thingsboard/monitoring/data/TransportFailureException.java rename to monitoring/src/main/java/org/thingsboard/monitoring/data/ServiceFailureException.java index 8157c4af5f..dc91a56a3c 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportFailureException.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/ServiceFailureException.java @@ -15,13 +15,13 @@ */ package org.thingsboard.monitoring.data; -public class TransportFailureException extends RuntimeException { +public class ServiceFailureException extends RuntimeException { - public TransportFailureException(Throwable cause) { + public ServiceFailureException(Throwable cause) { super(cause.getMessage(), cause); } - public TransportFailureException(String message) { + public ServiceFailureException(String message) { super(message); } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java similarity index 54% rename from monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java rename to monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java index 0c742e7709..affca1ce09 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java @@ -13,34 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.transport; +package org.thingsboard.monitoring.service; -import com.fasterxml.jackson.databind.node.TextNode; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.monitoring.client.TbClient; import org.thingsboard.monitoring.client.WsClient; -import org.thingsboard.monitoring.config.MonitoringTargetConfig; -import org.thingsboard.monitoring.config.TransportType; -import org.thingsboard.monitoring.config.service.TransportMonitoringConfig; +import org.thingsboard.monitoring.config.MonitoringConfig; +import org.thingsboard.monitoring.config.MonitoringTarget; import org.thingsboard.monitoring.data.Latencies; import org.thingsboard.monitoring.data.MonitoredServiceKey; -import org.thingsboard.monitoring.data.TransportFailureException; -import org.thingsboard.monitoring.data.TransportInfo; -import org.thingsboard.monitoring.service.MonitoringReporter; +import org.thingsboard.monitoring.data.ServiceFailureException; import org.thingsboard.monitoring.util.TbStopWatch; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.UUID; +@RequiredArgsConstructor @Slf4j -public abstract class TransportHealthChecker { +public abstract class BaseHealthChecker { protected final C config; - protected final MonitoringTargetConfig target; - private TransportInfo transportInfo; + protected final T target; + + private Object info; @Autowired private MonitoringReporter reporter; @@ -51,73 +50,66 @@ public abstract class TransportHealthChecker, T extends MonitoringTarget> { + + @Autowired + private List configs; + private final List> healthCheckers = new LinkedList<>(); + private final List devices = new LinkedList<>(); + + @Autowired + private TbClient tbClient; + @Autowired + private WsClientFactory wsClientFactory; + @Autowired + private TbStopWatch stopWatch; + @Autowired + private MonitoringReporter reporter; + @Autowired + protected ApplicationContext applicationContext; + + @PostConstruct + private void init() { + tbClient.logIn(); + configs.forEach(config -> { + config.getTargets().forEach(target -> { + BaseHealthChecker healthChecker = (BaseHealthChecker) createHealthChecker(config, target); + log.info("Initializing {}", healthChecker.getClass().getSimpleName()); + healthChecker.initialize(tbClient); + devices.add(target.getDeviceId()); + healthCheckers.add(healthChecker); + }); + }); + } + + public final void runChecks() { + if (healthCheckers.isEmpty()) { + return; + } + try { + log.info("Starting {}", getName()); + stopWatch.start(); + String accessToken = tbClient.logIn(); + reporter.reportLatency(Latencies.LOG_IN, stopWatch.getTime()); + + try (WsClient wsClient = wsClientFactory.createClient(accessToken)) { + wsClient.subscribeForTelemetry(devices, TransportHealthChecker.TEST_TELEMETRY_KEY).waitForReply(); + + for (BaseHealthChecker healthChecker : healthCheckers) { + healthChecker.check(wsClient); + } + } + reporter.reportLatencies(tbClient); + log.debug("Finished {}", getName()); + } catch (Throwable error) { + try { + reporter.serviceFailure(MonitoredServiceKey.GENERAL, error); + } catch (Throwable reportError) { + log.error("Error occurred during service failure reporting", reportError); + } + } + } + + protected abstract BaseHealthChecker createHealthChecker(C config, T target); + + protected abstract String getName(); + +} diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java index 4649e31ac4..f41454a76a 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java @@ -52,8 +52,8 @@ public class MonitoringReporter { @Value("${monitoring.failures_threshold}") private int failuresThreshold; - @Value("${monitoring.send_repeated_failure_notification}") - private boolean sendRepeatedFailureNotification; + @Value("${monitoring.repeated_failure_notification}") + private int repeatedFailureNotification; @Value("${monitoring.latency.enabled}") private boolean latencyReportingEnabled; @@ -75,7 +75,7 @@ public class MonitoringReporter { return; } log.info("Latencies:\n{}", latencies.stream().map(latency -> latency.getKey() + ": " + latency.getAvg() + " ms") - .collect(Collectors.joining("\n"))); + .collect(Collectors.joining("\n")) + "\n"); if (!latencyReportingEnabled) return; @@ -86,7 +86,7 @@ public class MonitoringReporter { try { if (StringUtils.isBlank(reportingAssetId)) { - String assetName = "Monitoring"; + String assetName = "[Monitoring] Latencies"; Asset monitoringAsset = tbClient.findAsset(assetName).orElseGet(() -> { Asset asset = new Asset(); asset.setType("Monitoring"); @@ -122,7 +122,7 @@ public class MonitoringReporter { int failuresCount = failuresCounters.computeIfAbsent(serviceKey, k -> new AtomicInteger()).incrementAndGet(); ServiceFailureNotification notification = new ServiceFailureNotification(serviceKey, error, failuresCount); log.error(notification.getText()); - if (failuresCount == failuresThreshold || (sendRepeatedFailureNotification && failuresCount % failuresThreshold == 0)) { + if (failuresCount == failuresThreshold || (repeatedFailureNotification != 0 && failuresCount % repeatedFailureNotification == 0)) { notificationService.sendNotification(notification); } } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java new file mode 100644 index 0000000000..c822720f23 --- /dev/null +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java @@ -0,0 +1,139 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.monitoring.service.transport; + +import com.fasterxml.jackson.databind.node.TextNode; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.monitoring.client.TbClient; +import org.thingsboard.monitoring.config.transport.DeviceConfig; +import org.thingsboard.monitoring.config.transport.TransportInfo; +import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig; +import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; +import org.thingsboard.monitoring.config.transport.TransportType; +import org.thingsboard.monitoring.service.BaseHealthChecker; +import org.thingsboard.monitoring.util.ResourceUtils; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; +import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; + +@Slf4j +public abstract class TransportHealthChecker extends BaseHealthChecker { + + private static final String DEFAULT_DEVICE_NAME = "[Monitoring] %s transport (%s)"; + private static final String DEFAULT_PROFILE_NAME = "[Monitoring] %s"; + + public TransportHealthChecker(C config, TransportMonitoringTarget target) { + super(config, target); + } + + @Override + protected void initialize(TbClient tbClient) { + String deviceName = String.format(DEFAULT_DEVICE_NAME, config.getTransportType(), target.getBaseUrl()); + Device device = tbClient.getTenantDevice(deviceName) + .orElseGet(() -> { + log.info("Creating new device '{}'", deviceName); + return createDevice(config.getTransportType(), deviceName, tbClient); + }); + DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId()) + .orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId())); + + DeviceConfig deviceConfig = new DeviceConfig(); + deviceConfig.setId(device.getId().toString()); + deviceConfig.setName(deviceName); + deviceConfig.setCredentials(credentials); + target.setDevice(deviceConfig); + } + + @Override + protected String createTestPayload(String testValue) { + return JacksonUtil.newObjectNode().set(TEST_TELEMETRY_KEY, new TextNode(testValue)).toString(); + } + + @Override + protected Object getInfo() { + return new TransportInfo(getTransportType(), target.getBaseUrl()); + } + + @Override + protected String getKey() { + return getTransportType().name().toLowerCase() + "Transport"; + } + + protected abstract TransportType getTransportType(); + + + private Device createDevice(TransportType transportType, String name, TbClient tbClient) { + Device device = new Device(); + device.setName(name); + + DeviceCredentials credentials = new DeviceCredentials(); + credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20)); + + DeviceData deviceData = new DeviceData(); + deviceData.setConfiguration(new DefaultDeviceConfiguration()); + if (transportType != TransportType.LWM2M) { + device.setType("default"); + deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); + credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + } else { + tbClient.getResources(new PageLink(1, 0, "lwm2m monitoring")).getData() + .stream().findFirst() + .orElseGet(() -> { + TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); + log.info("Creating LwM2M resource"); + return tbClient.saveResource(newResource); + }); + String profileName = String.format(DEFAULT_PROFILE_NAME, transportType); + DeviceProfile profile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData() + .stream().findFirst() + .orElseGet(() -> { + DeviceProfile newProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class); + newProfile.setName(profileName); + log.info("Creating LwM2M device profile"); + return tbClient.saveDeviceProfile(newProfile); + }); + device.setType(profileName); + device.setDeviceProfileId(profile.getId()); + deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); + + credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials(); + NoSecClientCredential client = new NoSecClientCredential(); + client.setEndpoint(credentials.getCredentialsId()); + lwm2mCreds.setClient(client); + LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials(); + bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential()); + bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential()); + lwm2mCreds.setBootstrap(bootstrap); + credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds)); + } + return tbClient.saveDeviceWithCredentials(device, credentials).get(); + } + +} diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java new file mode 100644 index 0000000000..b3ce86e799 --- /dev/null +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportsMonitoringService.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.monitoring.service.transport; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig; +import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; +import org.thingsboard.monitoring.service.BaseHealthChecker; +import org.thingsboard.monitoring.service.BaseMonitoringService; + +@Service +@RequiredArgsConstructor +@Slf4j +public final class TransportsMonitoringService extends BaseMonitoringService { + + @Override + protected BaseHealthChecker createHealthChecker(TransportMonitoringConfig config, TransportMonitoringTarget target) { + return applicationContext.getBean(config.getTransportType().getServiceClass(), config, target); + } + + @Override + protected String getName() { + return "transports check"; + } + +} diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/transport/impl/CoapTransportHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/impl/CoapTransportHealthChecker.java similarity index 86% rename from monitoring/src/main/java/org/thingsboard/monitoring/transport/impl/CoapTransportHealthChecker.java rename to monitoring/src/main/java/org/thingsboard/monitoring/service/transport/impl/CoapTransportHealthChecker.java index 3e36e62c58..f56415fa6a 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/transport/impl/CoapTransportHealthChecker.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/impl/CoapTransportHealthChecker.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.monitoring.transport.impl; +package org.thingsboard.monitoring.service.transport.impl; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapClient; @@ -23,10 +23,10 @@ import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import org.thingsboard.monitoring.config.MonitoringTargetConfig; -import org.thingsboard.monitoring.config.TransportType; -import org.thingsboard.monitoring.config.service.CoapTransportMonitoringConfig; -import org.thingsboard.monitoring.transport.TransportHealthChecker; +import org.thingsboard.monitoring.config.transport.CoapTransportMonitoringConfig; +import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; +import org.thingsboard.monitoring.config.transport.TransportType; +import org.thingsboard.monitoring.service.transport.TransportHealthChecker; import java.io.IOException; @@ -37,7 +37,7 @@ public class CoapTransportHealthChecker extends TransportHealthChecker configs; - private final List> transportHealthCheckers = new LinkedList<>(); - private final List devices = new LinkedList<>(); - - private final TbClient tbClient; - private final WsClientFactory wsClientFactory; - private final TbStopWatch stopWatch; - private final MonitoringReporter reporter; - private final ApplicationContext applicationContext; - private ScheduledExecutorService scheduler; - @Value("${monitoring.transports.monitoring_rate_ms}") - private int monitoringRateMs; - - @PostConstruct - private void init() { - configs.forEach(config -> { - config.getTargets().stream() - .filter(target -> StringUtils.isNotBlank(target.getBaseUrl())) - .peek(target -> checkMonitoringTarget(config, target, tbClient)) - .forEach(target -> { - TransportHealthChecker transportHealthChecker = applicationContext.getBean(config.getTransportType().getServiceClass(), config, target); - transportHealthCheckers.add(transportHealthChecker); - devices.add(target.getDevice().getId()); - }); - }); - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("monitoring-executor")); - } - - @EventListener(ApplicationReadyEvent.class) - public void startMonitoring() { - scheduler.scheduleWithFixedDelay(() -> { - try { - log.debug("Starting transports check"); - stopWatch.start(); - String accessToken = tbClient.logIn(); - reporter.reportLatency(Latencies.LOG_IN, stopWatch.getTime()); - - try (WsClient wsClient = wsClientFactory.createClient(accessToken)) { - wsClient.subscribeForTelemetry(devices, TransportHealthChecker.TEST_TELEMETRY_KEY).waitForReply(); - - for (TransportHealthChecker transportHealthChecker : transportHealthCheckers) { - transportHealthChecker.check(wsClient); - } - } - reporter.reportLatencies(tbClient); - log.debug("Finished transports check"); - } catch (Throwable error) { - try { - reporter.serviceFailure(MonitoredServiceKey.GENERAL, error); - } catch (Throwable reportError) { - log.error("Error occurred during service failure reporting", reportError); - } - } - }, 0, monitoringRateMs, TimeUnit.MILLISECONDS); - } - - private void checkMonitoringTarget(TransportMonitoringConfig config, MonitoringTargetConfig target, TbClient tbClient) { - DeviceConfig deviceConfig = target.getDevice(); - tbClient.logIn(); - - DeviceId deviceId; - if (deviceConfig == null || deviceConfig.getId() == null) { - String deviceName = String.format("[%s] Monitoring device (%s)", config.getTransportType(), target.getBaseUrl()); - Device device = tbClient.getTenantDevice(deviceName) - .orElseGet(() -> { - log.info("Creating new device '{}'", deviceName); - return createDevice(config.getTransportType(), deviceName, tbClient); - }); - deviceId = device.getId(); - target.getDevice().setId(deviceId.toString()); - } else { - deviceId = new DeviceId(deviceConfig.getId()); - } - - log.info("Using device {} for {} monitoring", deviceId, config.getTransportType()); - DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(deviceId) - .orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + deviceId)); - target.getDevice().setCredentials(credentials); - } - - private Device createDevice(TransportType transportType, String name, TbClient tbClient) { - Device device = new Device(); - device.setName(name); - - DeviceCredentials credentials = new DeviceCredentials(); - credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20)); - - DeviceData deviceData = new DeviceData(); - deviceData.setConfiguration(new DefaultDeviceConfiguration()); - if (transportType != TransportType.LWM2M) { - device.setType("default"); - deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); - credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - } else { - tbClient.getResources(new PageLink(1, 0, "lwm2m monitoring")).getData() - .stream().findFirst() - .orElseGet(() -> { - TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); - log.info("Creating LwM2M resource"); - return tbClient.saveResource(newResource); - }); - String profileName = "LwM2M Monitoring"; - DeviceProfile profile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData() - .stream().findFirst() - .orElseGet(() -> { - DeviceProfile newProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class); - newProfile.setName(profileName); - log.info("Creating LwM2M device profile"); - return tbClient.saveDeviceProfile(newProfile); - }); - device.setType(profileName); - device.setDeviceProfileId(profile.getId()); - deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); - - credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials(); - NoSecClientCredential client = new NoSecClientCredential(); - client.setEndpoint(credentials.getCredentialsId()); - lwm2mCreds.setClient(client); - LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials(); - bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential()); - bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential()); - lwm2mCreds.setBootstrap(bootstrap); - credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds)); - } - return tbClient.saveDeviceWithCredentials(device, credentials).get(); - } - -} diff --git a/monitoring/src/main/resources/lwm2m/device_profile.json b/monitoring/src/main/resources/lwm2m/device_profile.json index 7f93a7e6b6..095f4859e9 100644 --- a/monitoring/src/main/resources/lwm2m/device_profile.json +++ b/monitoring/src/main/resources/lwm2m/device_profile.json @@ -1,5 +1,4 @@ { - "name": "LwM2M Monitoring", "type": "DEFAULT", "image": null, "defaultQueueName": null, diff --git a/monitoring/src/main/resources/tb-monitoring.yml b/monitoring/src/main/resources/tb-monitoring.yml index d89886cd72..88979bff1a 100644 --- a/monitoring/src/main/resources/tb-monitoring.yml +++ b/monitoring/src/main/resources/tb-monitoring.yml @@ -32,79 +32,62 @@ monitoring: # WebSocket request timeout request_timeout_ms: '${WS_REQUEST_TIMEOUT_MS:3000}' + # Checks frequency in milliseconds + monitoring_rate_ms: '${MONITORING_RATE_MS:10000}' # Maximum time between request to transport and WebSocket update check_timeout_ms: '${CHECK_TIMEOUT_MS:5000}' # Failures threshold for notifying failures_threshold: '${FAILURES_THRESHOLD:2}' - # Whether to notify about next failures after first notification (will notify after each FAILURES_THRESHOLD failures) - send_repeated_failure_notification: '${SEND_REPEATED_FAILURE_NOTIFICATION:true}' + # Notify after each REPEATED_FAILURE_NOTIFICATION subsequent failures, 0 to notify only once on first failure + repeated_failure_notification: '${REPEATED_FAILURE_NOTIFICATION:4}' transports: - # Transports check frequency in milliseconds - monitoring_rate_ms: '${TRANSPORTS_MONITORING_RATE_MS:10000}' - mqtt: - # Enable MQTT checks + # Enable MQTT transport checks enabled: '${MQTT_TRANSPORT_MONITORING_ENABLED:true}' # MQTT request timeout in milliseconds request_timeout_ms: '${MQTT_REQUEST_TIMEOUT_MS:4000}' # MQTT QoS qos: '${MQTT_QOS_LEVEL:1}' targets: - # MQTT base url, tcp://DOMAIN:1883 by default + # MQTT transport base url, tcp://DOMAIN:1883 by default - base_url: '${MQTT_TRANSPORT_BASE_URL:tcp://${monitoring.domain}:1883}' - device: - # MQTT device to push telemetry for. If not set - device will be found or created automatically - id: '${MQTT_TRANSPORT_TARGET_DEVICE_ID:}' # To add more targets, use following environment variables: - # monitoring.transports.mqtt.targets[1].base_url, monitoring.transports.mqtt.targets[1].device.id, - # monitoring.transports.mqtt.targets[2].base_url, monitoring.transports.mqtt.targets[2].device.id, etc. + # monitoring.transports.mqtt.targets[1].base_url, monitoring.transports.mqtt.targets[2].base_url, etc. coap: - # Enable CoAP checks + # Enable CoAP transport checks enabled: '${COAP_TRANSPORT_MONITORING_ENABLED:true}' # CoAP request timeout in milliseconds request_timeout_ms: '${COAP_REQUEST_TIMEOUT_MS:4000}' targets: - # CoAP base url, coap://DOMAIN by default + # CoAP transport base url, coap://DOMAIN by default - base_url: '${COAP_TRANSPORT_BASE_URL:coap://${monitoring.domain}}' - # CoAP device to push telemetry for. If not set - device will be found or created automatically - device: - id: '${COAP_TRANSPORT_TARGET_DEVICE_ID:}' # To add more targets, use following environment variables: - # monitoring.transports.coap.targets[1].base_url, monitoring.transports.coap.targets[1].device.id, - # monitoring.transports.coap.targets[2].base_url, monitoring.transports.coap.targets[2].device.id, etc. + # monitoring.transports.coap.targets[1].base_url, monitoring.transports.coap.targets[2].base_url, etc. http: - # Enable HTTP checks + # Enable HTTP transport checks enabled: '${HTTP_TRANSPORT_MONITORING_ENABLED:true}' # HTTP request timeout in milliseconds request_timeout_ms: '${HTTP_REQUEST_TIMEOUT_MS:4000}' targets: - # HTTP base url, https://DOMAIN by default - - base_url: '${HTTP_TRANSPORT_BASE_URL:https://${monitoring.domain}}' - device: - # HTTP device to push telemetry for. If not set - device will be found or created automatically - id: '${HTTP_TRANSPORT_TARGET_DEVICE_ID:}' + # HTTP transport base url, http://DOMAIN by default + - base_url: '${HTTP_TRANSPORT_BASE_URL:http://${monitoring.domain}}' # To add more targets, use following environment variables: - # monitoring.transports.http.targets[1].base_url, monitoring.transports.http.targets[1].device.id, - # monitoring.transports.http.targets[2].base_url, monitoring.transports.http.targets[2].device.id, etc. + # monitoring.transports.http.targets[1].base_url, monitoring.transports.http.targets[2].base_url, etc. lwm2m: - # Enable LwM2M checks + # Enable LwM2M transport checks enabled: '${LWM2M_TRANSPORT_MONITORING_ENABLED:true}' # LwM2M request timeout in milliseconds request_timeout_ms: '${LWM2M_REQUEST_TIMEOUT_MS:4000}' targets: - # LwM2M base url, coap://DOMAIN:5685 by default + # LwM2M transport base url, coap://DOMAIN:5685 by default - base_url: '${LWM2M_TRANSPORT_BASE_URL:coap://${monitoring.domain}:5685}' - # LwM2M device to push telemetry for. If not set - device will be found or created automatically - device: - id: '${LWM2M_TRANSPORT_TARGET_DEVICE_ID:}' # To add more targets, use following environment variables: - # monitoring.transports.lwm2m.targets[1].base_url, monitoring.transports.lwm2m.targets[1].device.id, - # monitoring.transports.lwm2m.targets[2].base_url, monitoring.transports.lwm2m.targets[2].device.id, etc. + # monitoring.transports.lwm2m.targets[1].base_url, monitoring.transports.lwm2m.targets[2].base_url, etc. notification_channels: slack: