diff --git a/application/pom.xml b/application/pom.xml
index 902b541566..da2c0bc249 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
application
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
index ad1ed7824a..007f77aa20 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
@@ -118,8 +118,8 @@ public class TenantProfileController extends BaseController {
"Let's review the example of tenant profile data below: " +
"\n\n" + MARKDOWN_CODE_BLOCK_START +
"{\n" +
- " \"name\": \"Default\",\n" +
- " \"description\": \"Default tenant profile\",\n" +
+ " \"name\": \"Your name\",\n" +
+ " \"description\": \"Your description\",\n" +
" \"isolatedTbRuleEngine\": false,\n" +
" \"profileData\": {\n" +
" \"configuration\": {\n" +
@@ -162,7 +162,7 @@ public class TenantProfileController extends BaseController {
" \"warnThreshold\": 0\n" +
" }\n" +
" },\n" +
- " \"default\": true\n" +
+ " \"default\": false\n" +
"}" +
MARKDOWN_CODE_BLOCK_END +
"Remove 'id', from the request body example (below) to create new Tenant Profile entity." +
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
index 8abb78d9c1..9367439d4f 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.service.subscription;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
@@ -24,9 +26,9 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
-import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
+import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.Aggregation;
@@ -51,8 +53,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscript
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -143,7 +143,28 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
* Even if we cache locally the list of active subscriptions by entity id, it is still time-consuming operation to get them from cache
* Since number of subscriptions is usually much less than number of devices that are pushing data.
*/
- subscriptionsByEntityId.values().forEach(sub -> pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED)));
+ Set staleSubs = new HashSet<>();
+ subscriptionsByEntityId.forEach((id, sub) -> {
+ try {
+ pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED));
+ } catch (TenantNotFoundException e) {
+ staleSubs.add(id);
+ log.warn("Cleaning up stale subscription {} for tenant {} due to TenantNotFoundException", id, sub.getTenantId());
+ } catch (Exception e) {
+ log.error("Failed to push subscription {} to manager service", sub, e);
+ }
+ });
+ if (!staleSubs.isEmpty()) {
+ subsLock.lock();
+ try {
+ staleSubs.forEach(entityId -> {
+ subscriptionsByEntityId.remove(entityId);
+ entityUpdates.remove(entityId);
+ });
+ } finally {
+ subsLock.unlock();
+ }
+ }
}
}
diff --git a/common/actor/pom.xml b/common/actor/pom.xml
index 77ea283827..e8e9f9b9c5 100644
--- a/common/actor/pom.xml
+++ b/common/actor/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/cache/pom.xml b/common/cache/pom.xml
index 8a95b2a16d..cfff3d8dba 100644
--- a/common/cache/pom.xml
+++ b/common/cache/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml
index eda43510b5..8851b99583 100644
--- a/common/cluster-api/pom.xml
+++ b/common/cluster-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml
index 59640b586d..ddfcaf38e3 100644
--- a/common/coap-server/pom.xml
+++ b/common/coap-server/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml
index 8b3d7f7f5f..3d3eae4679 100644
--- a/common/dao-api/pom.xml
+++ b/common/dao-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/data/pom.xml b/common/data/pom.xml
index 4cd63dbc1f..91176d088f 100644
--- a/common/data/pom.xml
+++ b/common/data/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
index e60091a16d..9fe969f58d 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -51,10 +52,11 @@ public class TenantProfile extends BaseData implements HasName
@NoXss
@Schema(description = "Description of the tenant profile", example = "Any text")
private String description;
- @Schema(description = "Default Tenant profile to be used.", example = "true")
+ @Schema(description = "Default Tenant profile to be used.", example = "false")
+ @JsonProperty("default")
private boolean isDefault;
@Schema(description = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " +
- "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "true")
+ "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "false")
private boolean isolatedTbRuleEngine;
@Schema(description = "Complex JSON object that contains profile settings: queue configs, max devices, max assets, rate limits, etc.")
private transient TenantProfileData profileData;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
index 40f4b7f42b..51738c75be 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.tenant.profile;
+import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfileType;
+@Schema
@AllArgsConstructor
@NoArgsConstructor
@Builder
@@ -41,34 +43,61 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxOtaPackagesInBytes;
private long maxResourceSize;
+ @Schema(example = "1000:1,20000:60")
private String transportTenantMsgRateLimit;
+ @Schema(example = "1000:1,20000:60")
private String transportTenantTelemetryMsgRateLimit;
+ @Schema(example = "1000:1,20000:60")
private String transportTenantTelemetryDataPointsRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportDeviceMsgRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportDeviceTelemetryMsgRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportDeviceTelemetryDataPointsRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportGatewayMsgRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportGatewayTelemetryMsgRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportGatewayTelemetryDataPointsRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportGatewayDeviceMsgRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportGatewayDeviceTelemetryMsgRateLimit;
+ @Schema(example = "20:1,600:60")
private String transportGatewayDeviceTelemetryDataPointsRateLimit;
+ @Schema(example = "20:1,600:60")
private String tenantEntityExportRateLimit;
+ @Schema(example = "20:1,600:60")
private String tenantEntityImportRateLimit;
+ @Schema(example = "20:1,600:60")
private String tenantNotificationRequestsRateLimit;
+ @Schema(example = "20:1,600:60")
private String tenantNotificationRequestsPerRuleRateLimit;
+ @Schema(example = "10000000")
private long maxTransportMessages;
+ @Schema(example = "10000000")
private long maxTransportDataPoints;
+ @Schema(example = "4000000")
private long maxREExecutions;
+ @Schema(example = "5000000")
private long maxJSExecutions;
+ @Schema(example = "5000000")
private long maxTbelExecutions;
+ @Schema(example = "0")
private long maxDPStorageDays;
+ @Schema(example = "50")
private int maxRuleNodeExecutionsPerMessage;
+ @Schema(example = "0")
private long maxEmails;
+ @Schema(example = "true")
private Boolean smsEnabled;
+ @Schema(example = "0")
private long maxSms;
+ @Schema(example = "1000")
private long maxCreatedAlarms;
private String tenantServerRestLimitsConfiguration;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java
index d3238b20f8..0662765455 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java
@@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
+import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.TenantProfileType;
@@ -31,6 +33,7 @@ import java.io.Serializable;
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = DefaultTenantProfileConfiguration.class, name = "DEFAULT")})
+@Schema(discriminatorProperty = "type", discriminatorMapping = {@DiscriminatorMapping(value = "DEFAULT", schema = DefaultTenantProfileConfiguration.class)})
public interface TenantProfileConfiguration extends Serializable {
@JsonIgnore
diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml
index cf3134f09b..19ed1e0b40 100644
--- a/common/edge-api/pom.xml
+++ b/common/edge-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/message/pom.xml b/common/message/pom.xml
index 6f650c127b..64b46a9dd2 100644
--- a/common/message/pom.xml
+++ b/common/message/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/pom.xml b/common/pom.xml
index b12140012d..d3cd3d6928 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
common
diff --git a/common/proto/pom.xml b/common/proto/pom.xml
index 06ae7293ba..57652f2645 100644
--- a/common/proto/pom.xml
+++ b/common/proto/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/queue/pom.xml b/common/queue/pom.xml
index cd77d9da07..54d68afe62 100644
--- a/common/queue/pom.xml
+++ b/common/queue/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/script/pom.xml b/common/script/pom.xml
index b307d328d2..bef66f2c96 100644
--- a/common/script/pom.xml
+++ b/common/script/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml
index d196dd13ea..07dec58d66 100644
--- a/common/script/remote-js-client/pom.xml
+++ b/common/script/remote-js-client/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
script
org.thingsboard.common.script
diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml
index b760129479..b6d6731067 100644
--- a/common/script/script-api/pom.xml
+++ b/common/script/script-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
script
org.thingsboard.common.script
diff --git a/common/stats/pom.xml b/common/stats/pom.xml
index 23c507cc43..5eb96a1e1a 100644
--- a/common/stats/pom.xml
+++ b/common/stats/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml
index 48bd1cfe9c..5dff87a05b 100644
--- a/common/transport/coap/pom.xml
+++ b/common/transport/coap/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml
index e27f7cbc1b..d5946c8545 100644
--- a/common/transport/http/pom.xml
+++ b/common/transport/http/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml
index 0ec3492c99..d3cd755545 100644
--- a/common/transport/lwm2m/pom.xml
+++ b/common/transport/lwm2m/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml
index 952b94ea2f..023b9473dc 100644
--- a/common/transport/mqtt/pom.xml
+++ b/common/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/pom.xml b/common/transport/pom.xml
index 0c5c7a02c9..368dcec7d8 100644
--- a/common/transport/pom.xml
+++ b/common/transport/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml
index 8ad570fceb..96d714f032 100644
--- a/common/transport/snmp/pom.xml
+++ b/common/transport/snmp/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml
index 20d0f6e5d2..d1fead6a4a 100644
--- a/common/transport/transport-api/pom.xml
+++ b/common/transport/transport-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/util/pom.xml b/common/util/pom.xml
index c4cee40e0d..971357ea40 100644
--- a/common/util/pom.xml
+++ b/common/util/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml
index cce6e00501..ec913318a7 100644
--- a/common/version-control/pom.xml
+++ b/common/version-control/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
common
org.thingsboard.common
diff --git a/dao/pom.xml b/dao/pom.xml
index 061c130a69..6aa47ec4bd 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
dao
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
index fc4fff49d0..264ea88326 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.rule;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
@@ -51,9 +52,9 @@ public interface RuleNodeRepository extends JpaRepository
Pageable pageable);
@Query("SELECT r.id FROM RuleNodeEntity r WHERE r.type = :ruleType AND r.configurationVersion < :version")
- Page findAllRuleNodeIdsByTypeAndVersionLessThan(@Param("ruleType") String ruleType,
- @Param("version") int version,
- Pageable pageable);
+ Slice findAllRuleNodeIdsByTypeAndVersionLessThan(@Param("ruleType") String ruleType,
+ @Param("version") int version,
+ Pageable pageable);
List findRuleNodesByRuleChainIdAndExternalIdIn(UUID ruleChainId, List externalIds);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
index 383c592a31..6700e3614a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
@@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntryAggWrapper;
+import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.TbResultSet;
import org.thingsboard.server.dao.nosql.TbResultSetFuture;
@@ -239,10 +240,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs());
- TbResultSetFuture partitionsFuture = fetchPartitions(tenantId, entityId, query.getKey(), minPartition, maxPartition);
-
final SimpleListenableFuture resultFuture = new SimpleListenableFuture<>();
- final ListenableFuture> partitionsListFuture = Futures.transformAsync(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
+ final ListenableFuture> partitionsListFuture = getPartitionsFuture(tenantId, query, entityId, minPartition, maxPartition);
Futures.addCallback(partitionsListFuture, new FutureCallback>() {
@Override
@@ -416,7 +415,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
readResultsProcessingExecutor);
}
- private ListenableFuture> getPartitionsFuture(TenantId tenantId, ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
+ private ListenableFuture> getPartitionsFuture(TenantId tenantId, TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
if (isFixedPartitioning()) { //no need to fetch partitions from DB
return Futures.immediateFuture(FIXED_PARTITION);
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java
index 2da8a7ff45..5a4b20d7a8 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java
@@ -45,6 +45,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.verify;
@@ -73,6 +74,9 @@ class DeviceProfileDataValidatorTest {
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }";
+ private static final String msgErrorLwm2mRange = "LwM2M Server ShortServerId must be in range [1 - 65534]!";
+ private static final String msgErrorBsRange = "Bootstrap Server ShortServerId must be in range [0 - 65535]!";
+ private static final String msgErrorNotNull = " Server ShortServerId must not be null!";
private static final String host = "localhost";
private static final String hostBs = "localhost";
@@ -116,19 +120,50 @@ class DeviceProfileDataValidatorTest {
validator.validateDataImpl(tenantId, deviceProfile);
verify(validator).validateString("Device profile name", deviceProfile.getName());
}
+
@Test
void testValidateDeviceProfile_Lwm2mBootstrap_ShortServerId_Ok() {
Integer shortServerId = 123;
Integer shortServerIdBs = 0;
- Lwm2mDeviceProfileTransportConfiguration transportConfiguration =
- getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(shortServerId, shortServerIdBs));
- DeviceProfile deviceProfile = getDeviceProfile(transportConfiguration);
+ DeviceProfile deviceProfile = getDeviceProfile(shortServerId, shortServerIdBs);
validator.validateDataImpl(tenantId, deviceProfile);
verify(validator).validateString("Device profile name", deviceProfile.getName());
}
- private DeviceProfile getDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) {
+ @Test
+ void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_null_Error() {
+ verifyValidationError(123, null, "Bootstrap" + msgErrorNotNull);
+ }
+
+ @Test
+ void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_More_65535_Error() {
+ verifyValidationError(123, 65536, msgErrorBsRange);
+ }
+
+ @Test
+ void testValidateDeviceProfile_Lwm2mShortServerId_Ok_BootstrapShortServerId_Less_0_Error() {
+ verifyValidationError(123, -1, msgErrorBsRange);
+ }
+
+ @Test
+ void testValidateDeviceProfile_Lwm2mShortServerId_null_Error_BootstrapShortServerId_Ok() {
+ verifyValidationError(null, 1, "LwM2M" + msgErrorNotNull);
+ }
+
+ @Test
+ void testValidateDeviceProfile_Lwm2mShortServerId_More_65534_Error_BootstrapShortServerId_Ok() {
+ verifyValidationError(65535, 111, msgErrorLwm2mRange);
+ }
+
+ @Test
+ void testValidateDeviceProfile_Lwm2mShortServerId_Less_1_Error_BootstrapShortServerId_Ok() {
+ verifyValidationError(0, 111, msgErrorLwm2mRange);
+ }
+
+ private DeviceProfile getDeviceProfile(Integer shortServerId, Integer shortServerIdBs) {
+ Lwm2mDeviceProfileTransportConfiguration transportConfiguration =
+ getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(shortServerId, shortServerIdBs));
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName("default");
deviceProfile.setType(DeviceProfileType.DEFAULT);
@@ -151,7 +186,7 @@ class DeviceProfileDataValidatorTest {
return transportConfiguration;
}
- private List getBootstrapServerCredentialsNoSec(Integer shortServerId, Integer shortServerIdBs){
+ private List getBootstrapServerCredentialsNoSec(Integer shortServerId, Integer shortServerIdBs) {
List bootstrap = new ArrayList<>();
bootstrap.add(getBootstrapServerCredentialNoSec(false, shortServerId, shortServerIdBs));
bootstrap.add(getBootstrapServerCredentialNoSec(true, shortServerId, shortServerIdBs));
@@ -168,4 +203,11 @@ class DeviceProfileDataValidatorTest {
return bootstrapServerCredential;
}
+ private void verifyValidationError(Integer shortServerId, Integer shortServerIdBs, String msgError) {
+ DeviceProfile deviceProfile = getDeviceProfile(shortServerId, shortServerIdBs);
+ assertThatThrownBy(() -> validator.validateDataImpl(tenantId, deviceProfile))
+ .hasMessageContaining(msgError);
+
+ }
+
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java
index 0fa9383ab1..dcc95b2aff 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -127,20 +126,15 @@ public class JpaRuleNodeDaoTest extends AbstractJpaDaoTest {
@Test
public void testFindRuleNodeIdsByTypeAndVersionLessThan() {
+ // test - search text ignored
PageData ruleNodeIds = ruleNodeDao.findAllRuleNodeIdsByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0, PREFIX_FOR_RULE_NODE_NAME));
- assertEquals(20, ruleNodeIds.getTotalElements());
- assertEquals(2, ruleNodeIds.getTotalPages());
+ assertEquals(0, ruleNodeIds.getTotalElements()); // due to DaoUtil.pageToPageData impl for Slice
+ assertEquals(0, ruleNodeIds.getTotalPages()); // due to DaoUtil.pageToPageData impl for Slice
assertEquals(10, ruleNodeIds.getData().size());
ruleNodeIds = ruleNodeDao.findAllRuleNodeIdsByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0));
- assertEquals(20, ruleNodeIds.getTotalElements());
- assertEquals(2, ruleNodeIds.getTotalPages());
- assertEquals(10, ruleNodeIds.getData().size());
-
- // test - search text ignored
- ruleNodeIds = ruleNodeDao.findAllRuleNodeIdsByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0, StringUtils.randomAlphabetic(5)));
- assertEquals(20, ruleNodeIds.getTotalElements());
- assertEquals(2, ruleNodeIds.getTotalPages());
+ assertEquals(0, ruleNodeIds.getTotalElements()); // due to DaoUtil.pageToPageData impl for Slice
+ assertEquals(0, ruleNodeIds.getTotalPages()); // due to DaoUtil.pageToPageData impl for Slice
assertEquals(10, ruleNodeIds.getData().size());
}
diff --git a/monitoring/pom.xml b/monitoring/pom.xml
index 2dd8e54f83..2ed55e141f 100644
--- a/monitoring/pom.xml
+++ b/monitoring/pom.xml
@@ -21,7 +21,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java b/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java
index 4b95ae1aac..381aac30a0 100644
--- a/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java
@@ -30,10 +30,12 @@ import org.eclipse.leshan.client.californium.LeshanClientBuilder;
import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.client.object.Server;
+import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.servers.ServerIdentity;
+import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.californium.EndpointFactory;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.LwM2mModel;
@@ -42,6 +44,10 @@ import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
+import org.eclipse.leshan.core.request.BootstrapRequest;
+import org.eclipse.leshan.core.request.DeregisterRequest;
+import org.eclipse.leshan.core.request.RegisterRequest;
+import org.eclipse.leshan.core.request.UpdateRequest;
import org.eclipse.leshan.core.response.ReadResponse;
import org.thingsboard.monitoring.util.ResourceUtils;
@@ -96,7 +102,7 @@ public class Lwm2mClient extends BaseInstanceEnabler implements Destroyable {
LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = new ObjectsInitializer(model);
initializer.setInstancesForObject(SECURITY, security);
- initializer.setInstancesForObject(SERVER, new Server(123, TimeUnit.MINUTES.toSeconds(60)));
+ initializer.setInstancesForObject(SERVER, new Server(123, TimeUnit.MINUTES.toSeconds(5)));
initializer.setInstancesForObject(DEVICE, this);
initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
@@ -139,6 +145,89 @@ public class Lwm2mClient extends BaseInstanceEnabler implements Destroyable {
builder.setEncoder(new DefaultLwM2mEncoder(false));
leshanClient = builder.build();
+ LwM2mClientObserver observer = new LwM2mClientObserver() {
+
+ @Override
+ public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {}
+
+ @Override
+ public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {}
+
+ @Override
+ public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request,
+ ResponseCode responseCode, String errorMessage, Exception cause) {}
+
+ @Override
+ public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {}
+
+ @Override
+ public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
+ log.debug("onRegistrationStarted [{}]", request.getEndpointName());
+ }
+
+ @Override
+ public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
+ log.debug("onRegistrationSuccess [{}] [{}]", request.getEndpointName(), registrationID);
+ }
+
+ @Override
+ public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ log.debug("onRegistrationFailure [{}] [{}] [{}]", request.getEndpointName(), responseCode, errorMessage);
+ }
+
+ @Override
+ public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
+ log.debug("onRegistrationTimeout [{}]", request.getEndpointName());
+ }
+
+ @Override
+ public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
+ log.debug("onUpdateStarted [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
+ log.debug("onUpdateSuccess [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ log.debug("onUpdateFailure [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
+ log.debug("onUpdateTimeout [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
+ log.debug("onDeregistrationStarted [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
+ log.debug("onDeregistrationStarted [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
+ log.debug("onDeregistrationFailure [{}] [{}] [{}]", request.getRegistrationId(), responseCode, errorMessage);
+ }
+
+ @Override
+ public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
+ log.debug("onDeregistrationTimeout [{}]", request.getRegistrationId());
+ }
+
+ @Override
+ public void onUnexpectedError(Throwable unexpectedError) {
+ log.debug("onUnexpectedError [{}]", unexpectedError.toString());
+ }
+
+ };
+ leshanClient.addObserver(observer);
+
setLeshanClient(leshanClient);
leshanClient.start();
diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml
index 4c81e0f31f..ce4de7af17 100644
--- a/msa/black-box-tests/pom.xml
+++ b/msa/black-box-tests/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json
index d0864c013a..b7871628f8 100644
--- a/msa/js-executor/package.json
+++ b/msa/js-executor/package.json
@@ -1,7 +1,7 @@
{
"name": "thingsboard-js-executor",
"private": true,
- "version": "3.7.0",
+ "version": "3.7.1",
"description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts",
"bin": "server.js",
diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml
index 8d3592cbe2..8f67cff304 100644
--- a/msa/js-executor/pom.xml
+++ b/msa/js-executor/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml
index 60ff2e3f4a..9c6ff99615 100644
--- a/msa/monitoring/pom.xml
+++ b/msa/monitoring/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
diff --git a/msa/pom.xml b/msa/pom.xml
index 71ddc91412..9faf269a20 100644
--- a/msa/pom.xml
+++ b/msa/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
msa
diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml
index 708af73ae0..3dab1c01da 100644
--- a/msa/tb-node/pom.xml
+++ b/msa/tb-node/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml
index 8b4a6d0747..b799572468 100644
--- a/msa/tb/pom.xml
+++ b/msa/tb/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml
index 5c11fd7adf..fbe4677b8f 100644
--- a/msa/transport/coap/pom.xml
+++ b/msa/transport/coap/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml
index b7cdae9c95..efedec74ab 100644
--- a/msa/transport/http/pom.xml
+++ b/msa/transport/http/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml
index 19ea2a022c..f3c05af81a 100644
--- a/msa/transport/lwm2m/pom.xml
+++ b/msa/transport/lwm2m/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml
index f06777265e..a6d4e265a4 100644
--- a/msa/transport/mqtt/pom.xml
+++ b/msa/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml
index 13ee09c3a7..cd6685b7a5 100644
--- a/msa/transport/pom.xml
+++ b/msa/transport/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml
index 35b3809aae..350f49e7fb 100644
--- a/msa/transport/snmp/pom.xml
+++ b/msa/transport/snmp/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard.msa
transport
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
org.thingsboard.msa.transport
diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml
index e70656c49b..8111c1370d 100644
--- a/msa/vc-executor-docker/pom.xml
+++ b/msa/vc-executor-docker/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml
index 081ebd1d3c..e4f4e0afe9 100644
--- a/msa/vc-executor/pom.xml
+++ b/msa/vc-executor/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json
index 6e7cdd4bc1..6bd7266dd8 100644
--- a/msa/web-ui/package.json
+++ b/msa/web-ui/package.json
@@ -1,7 +1,7 @@
{
"name": "thingsboard-web-ui",
"private": true,
- "version": "3.7.0",
+ "version": "3.7.1",
"description": "ThingsBoard Web UI Microservice",
"main": "server.ts",
"bin": "server.js",
diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml
index 103a498a61..e592b1a8e9 100644
--- a/msa/web-ui/pom.xml
+++ b/msa/web-ui/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml
index b0111b9bcd..7f06a09be2 100644
--- a/netty-mqtt/pom.xml
+++ b/netty-mqtt/pom.xml
@@ -19,11 +19,11 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
netty-mqtt
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
jar
Netty MQTT Client
diff --git a/pom.xml b/pom.xml
index f75e782bef..13125c67f9 100755
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
pom
Thingsboard
diff --git a/rest-client/pom.xml b/rest-client/pom.xml
index e4a959a7ec..7e38a4119d 100644
--- a/rest-client/pom.xml
+++ b/rest-client/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
rest-client
diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml
index 8902ef0cee..1448fa650e 100644
--- a/rule-engine/pom.xml
+++ b/rule-engine/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
rule-engine
diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml
index 743baf1ab4..8fe01acdff 100644
--- a/rule-engine/rule-engine-api/pom.xml
+++ b/rule-engine/rule-engine-api/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
rule-engine
org.thingsboard.rule-engine
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index de48596d70..5ac1421cdb 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
rule-engine
org.thingsboard.rule-engine
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
index f0cb102778..81de1ded43 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
@@ -28,12 +28,14 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg;
+import java.util.List;
+
@Slf4j
@RuleNode(
type = ComponentType.EXTERNAL,
name = "rest api call",
configClazz = TbRestApiCallNodeConfiguration.class,
- version = 1,
+ version = 2,
nodeDescription = "Invoke REST API calls to external REST server",
nodeDetails = "Will invoke REST API call GET | POST | PUT | DELETE to external REST server. " +
"Message payload added into Request body. Configured attributes can be added into Headers from Message Metadata." +
@@ -58,9 +60,6 @@ public class TbRestApiCallNode extends TbAbstractExternalNode {
super.init(ctx);
TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class);
httpClient = new TbHttpClient(config, ctx.getSharedEventLoop());
- if (config.isUseRedisQueueForMsgPersistence()) {
- log.warn("[{}][{}] Usage of Redis Template is deprecated starting 2.5 and will have no affect", ctx.getTenantId(), ctx.getSelfId());
- }
}
@Override
@@ -88,6 +87,11 @@ public class TbRestApiCallNode extends TbAbstractExternalNode {
((ObjectNode) oldConfiguration).put(PARSE_TO_PLAIN_TEXT, oldConfiguration.get(TRIM_DOUBLE_QUOTES).booleanValue());
((ObjectNode) oldConfiguration).remove(TRIM_DOUBLE_QUOTES);
}
+ case 1:
+ if (oldConfiguration.has("useRedisQueueForMsgPersistence")) {
+ hasChanges = true;
+ ((ObjectNode) oldConfiguration).remove(List.of("useRedisQueueForMsgPersistence", "trimQueue", "maxQueueSize"));
+ }
break;
default:
break;
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
index 7f159bd6a9..7d2ff7167d 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
@@ -36,7 +36,6 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration upgrade = node.upgrade(0, configJson);
- Assertions.assertTrue(upgrade.getFirst());
- Assertions.assertTrue(JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass()).isParseToPlainText());
+ private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() {
+ return Stream.of(
+ // config for version 2 with upgrade from version 0
+ Arguments.of(0,
+ "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\":\"POST\"," +
+ "\"useSimpleClientHttpFactory\":false,\"ignoreRequestBody\":false,\"enableProxy\":false," +
+ "\"useSystemProxyProperties\":false,\"proxyScheme\":null,\"proxyHost\":null,\"proxyPort\":0," +
+ "\"proxyUser\":null,\"proxyPassword\":null,\"readTimeoutMs\":0,\"maxParallelRequestsCount\":0," +
+ "\"headers\":{\"Content-Type\":\"application/json\"},\"useRedisQueueForMsgPersistence\":false," +
+ "\"trimQueue\":null,\"maxQueueSize\":null,\"credentials\":{\"type\":\"anonymous\"},\"trimDoubleQuotes\":false}",
+ true,
+ "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," +
+ "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," +
+ "\"enableProxy\": false,\"useSystemProxyProperties\": false,\"proxyScheme\": null,\"proxyHost\": null," +
+ "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," +
+ "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," +
+ "\"credentials\": {\"type\": \"anonymous\"}}"),
+ // config for version 2 with upgrade from version 1
+ Arguments.of(1,
+ "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," +
+ "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," +
+ "\"enableProxy\": false,\"useSystemProxyProperties\": false,\"proxyScheme\": null,\"proxyHost\": null," +
+ "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," +
+ "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," +
+ "\"useRedisQueueForMsgPersistence\": false,\"trimQueue\": null,\"maxQueueSize\": null," +
+ "\"credentials\": {\"type\": \"anonymous\"}}",
+ true,
+ "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," +
+ "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," +
+ "\"enableProxy\": false,\"useSystemProxyProperties\": false,\"proxyScheme\": null,\"proxyHost\": null," +
+ "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," +
+ "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," +
+ "\"credentials\": {\"type\": \"anonymous\"}}")
+ );
+ }
+
+ @Override
+ protected TbNode getTestNode() {
+ return restNode;
}
}
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java
index db632fe3c7..9cab33fcdd 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java
@@ -19,8 +19,12 @@ import com.google.common.util.concurrent.SettableFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ListeningExecutor;
@@ -29,7 +33,10 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
@@ -37,24 +44,30 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.edge.EdgeEventService;
+import java.util.Map;
import java.util.UUID;
+import java.util.stream.Stream;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TbSendRPCReplyNodeTest {
private static final String DUMMY_SERVICE_ID = "testServiceId";
private static final int DUMMY_REQUEST_ID = 0;
- private static final UUID DUMMY_SESSION_ID = UUID.randomUUID();
- private static final String DUMMY_DATA = "{\"key\":\"value\"}";
+ private static final UUID DUMMY_SESSION_ID = UUID.fromString("4f1d94aa-f6ee-4078-8499-b8e68443f8ad");
+ private final String DUMMY_DATA = "{\"key\":\"value\"}";
- TbSendRPCReplyNode node;
+ private TbSendRPCReplyNode node;
+ private TbSendRpcReplyNodeConfiguration config;
- private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
- private final DeviceId deviceId = new DeviceId(UUID.randomUUID());
+ private final TenantId tenantId = TenantId.fromUUID(UUID.fromString("4e2e2336-3376-4238-ba0a-c669b412ca66"));
+ private final DeviceId deviceId = new DeviceId(UUID.fromString("af64d1b9-8635-47e1-8738-6389df7fe57e"));
@Mock
private TbContext ctx;
@@ -71,14 +84,13 @@ public class TbSendRPCReplyNodeTest {
@BeforeEach
public void setUp() throws TbNodeException {
node = new TbSendRPCReplyNode();
- TbSendRpcReplyNodeConfiguration config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration();
+ config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration();
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
}
@Test
public void sendReplyToTransport() {
- Mockito.when(ctx.getRpcService()).thenReturn(rpcService);
-
+ when(ctx.getRpcService()).thenReturn(rpcService);
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, getDefaultMetadata(),
TbMsgDataType.JSON, DUMMY_DATA, null, null);
@@ -91,10 +103,10 @@ public class TbSendRPCReplyNodeTest {
@Test
public void sendReplyToEdgeQueue() {
- Mockito.when(ctx.getTenantId()).thenReturn(tenantId);
- Mockito.when(ctx.getEdgeEventService()).thenReturn(edgeEventService);
- Mockito.when(edgeEventService.saveAsync(any())).thenReturn(SettableFuture.create());
- Mockito.when(ctx.getDbCallbackExecutor()).thenReturn(listeningExecutor);
+ when(ctx.getTenantId()).thenReturn(tenantId);
+ when(ctx.getEdgeEventService()).thenReturn(edgeEventService);
+ when(edgeEventService.saveAsync(any())).thenReturn(SettableFuture.create());
+ when(ctx.getDbCallbackExecutor()).thenReturn(listeningExecutor);
TbMsgMetaData defaultMetadata = getDefaultMetadata();
defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString());
@@ -108,6 +120,55 @@ public class TbSendRPCReplyNodeTest {
verify(rpcService, never()).sendRpcReplyToDevice(DUMMY_SERVICE_ID, DUMMY_SESSION_ID, DUMMY_REQUEST_ID, DUMMY_DATA);
}
+ @ParameterizedTest
+ @EnumSource(EntityType.class)
+ public void testOriginatorEntityTypes(EntityType entityType) {
+ EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, "0f386739-210f-4e23-8739-23f84a172adc");
+ TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+
+ node.onMsg(ctx, msg);
+
+ ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
+ verify(ctx).tellFailure(eq(msg), throwableCaptor.capture());
+ assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class)
+ .hasMessage(EntityType.DEVICE != entityType ? "Message originator is not a device entity!"
+ : "Request id is not present in the metadata!");
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void testForAvailabilityOfMetadataAndDataValues(TbMsgMetaData metaData, String errorMsg) {
+ TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, metaData, TbMsg.EMPTY_STRING);
+
+ node.onMsg(ctx, msg);
+
+ ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
+ verify(ctx).tellFailure(eq(msg), throwableCaptor.capture());
+ assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class).hasMessage(errorMsg);
+ }
+
+ @Test
+ public void verifyDefaultConfig() {
+ assertThat(config.getServiceIdMetaDataAttribute()).isEqualTo("serviceId");
+ assertThat(config.getSessionIdMetaDataAttribute()).isEqualTo("sessionId");
+ assertThat(config.getRequestIdMetaDataAttribute()).isEqualTo("requestId");
+ }
+
+ private static Stream testForAvailabilityOfMetadataAndDataValues() {
+ return Stream.of(
+ Arguments.of(TbMsgMetaData.EMPTY, "Request id is not present in the metadata!"),
+ Arguments.of(new TbMsgMetaData(Map.of(
+ "requestId", Integer.toString(DUMMY_REQUEST_ID))), "Service id is not present in the metadata!"),
+ Arguments.of(new TbMsgMetaData(Map.of(
+ "requestId", Integer.toString(DUMMY_REQUEST_ID),
+ "serviceId", DUMMY_SERVICE_ID)), "Session id is not present in the metadata!"),
+ Arguments.of(new TbMsgMetaData(Map.of(
+ "requestId", Integer.toString(DUMMY_REQUEST_ID),
+ "serviceId", DUMMY_SERVICE_ID, "sessionId",
+ DUMMY_SESSION_ID.toString())), "Request body is empty!")
+ );
+ }
+
private TbMsgMetaData getDefaultMetadata() {
TbSendRpcReplyNodeConfiguration config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration();
TbMsgMetaData metadata = new TbMsgMetaData();
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java
new file mode 100644
index 0000000000..e9d25e0d5e
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java
@@ -0,0 +1,421 @@
+/**
+ * Copyright © 2016-2024 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.rule.engine.rpc;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
+import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse;
+import org.thingsboard.rule.engine.api.RuleEngineRpcService;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.msg.TbMsgType;
+import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
+import org.thingsboard.server.common.data.rpc.RpcError;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.BDDMockito.willAnswer;
+import static org.mockito.Mockito.mock;
+
+@ExtendWith(MockitoExtension.class)
+public class TbSendRPCRequestNodeTest {
+
+ private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("d3a47f8b-d863-4c1f-b6f0-2c946b43f21c"));
+ private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("b052ae59-b9b4-47e8-ac71-39e7124bbd66"));
+
+ private final String MSG_DATA = """
+ {
+ "method": "setGpio",
+ "params": {
+ "pin": "23",
+ "value": 1
+ },
+ "additionalInfo": "information"
+ }
+ """;
+
+ private TbSendRPCRequestNode node;
+ private TbSendRpcRequestNodeConfiguration config;
+
+ @Mock
+ private TbContext ctxMock;
+ @Mock
+ private RuleEngineRpcService rpcServiceMock;
+
+ @BeforeEach
+ public void setUp() throws TbNodeException {
+ node = new TbSendRPCRequestNode();
+ config = new TbSendRpcRequestNodeConfiguration().defaultConfiguration();
+ var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
+ node.init(ctxMock, configuration);
+ }
+
+ @Test
+ public void verifyDefaultConfig() {
+ assertThat(config.getTimeoutInSeconds()).isEqualTo(60);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void givenOneway_whenOnMsg_thenVerifyRequest(String mdKeyValue, boolean expectedResult) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsgMetaData msgMetadata = new TbMsgMetaData();
+ msgMetadata.putValue("oneway", mdKeyValue);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, msgMetadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ var ruleEngineDeviceRpcRequestCaptor = captureRequest();
+ assertThat(ruleEngineDeviceRpcRequestCaptor.getValue().isOneway()).isEqualTo(expectedResult);
+ }
+
+ private static Stream givenOneway_whenOnMsg_thenVerifyRequest() {
+ return Stream.of(
+ Arguments.of("true", true),
+ Arguments.of("false", false),
+ Arguments.of(null, false),
+ Arguments.of("", false)
+ );
+ }
+
+ @Test
+ public void givenMsgBody_whenOnMsg_thenVerifyRequest() {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(RuleEngineDeviceRpcRequest.class);
+ then(rpcServiceMock).should().sendRpcRequestToDevice(requestCaptor.capture(), any(Consumer.class));
+ assertThat(requestCaptor.getValue())
+ .hasFieldOrPropertyWithValue("method", "setGpio")
+ .hasFieldOrPropertyWithValue("body", "{\"pin\":\"23\",\"value\":1}")
+ .hasFieldOrPropertyWithValue("deviceId", DEVICE_ID)
+ .hasFieldOrPropertyWithValue("tenantId", TENANT_ID)
+ .hasFieldOrPropertyWithValue("additionalInfo", "information");
+ }
+
+ @Test
+ public void givenRequestIdIsNotSet_whenOnMsg_thenVerifyRequest() {
+ Random randomMock = mock(Random.class);
+ given(randomMock.nextInt()).willReturn(123);
+ ReflectionTestUtils.setField(node, "random", randomMock);
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsg msg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getRequestId()).isEqualTo(123);
+ }
+
+ @Test
+ public void givenRequestId_whenOnMsg_thenVerifyRequest() {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+ String data = """
+ {
+ "method": "setGpio",
+ "params": {
+ "pin": "23",
+ "value": 1
+ },
+ "requestId": 12345
+ }
+ """;
+ TbMsg msg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, data);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getRequestId()).isEqualTo(12345);
+ }
+
+ @Test
+ public void givenRequestUUID_whenOnMsg_thenVerifyRequest() {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ String requestUUID = "b795a241-5a30-48fb-92d5-46b864d47130";
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue("requestUUID", requestUUID);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getRequestUUID()).isEqualTo(UUID.fromString(requestUUID));
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ public void givenInvalidRequestUUID_whenOnMsg_thenVerifyRequest(String requestUUID) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue("requestUUID", requestUUID);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getRequestUUID()).isNotNull();
+ }
+
+ @Test
+ public void givenOriginServiceId_whenOnMsg_thenVerifyRequest() {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ String originServiceId = "service-id-123";
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue("originServiceId", originServiceId);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getOriginServiceId()).isEqualTo(originServiceId);
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ public void givenInvalidOriginServiceId_whenOnMsg_thenVerifyRequest(String originServiceId) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue("originServiceId", originServiceId);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getOriginServiceId()).isNull();
+ }
+
+ @Test
+ public void givenExpirationTime_whenOnMsg_thenVerifyRequest() {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ String expirationTime = "2000000000000";
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue(DataConstants.EXPIRATION_TIME, expirationTime);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getExpirationTime()).isEqualTo(Long.parseLong(expirationTime));
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ public void givenInvalidExpirationTime_whenOnMsg_thenVerifyRequest(String expirationTime) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue(DataConstants.EXPIRATION_TIME, expirationTime);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getExpirationTime()).isGreaterThan(System.currentTimeMillis());
+ }
+
+ @Test
+ public void givenRetries_whenOnMsg_thenVerifyRequest() {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ Integer retries = 3;
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue(DataConstants.RETRIES, String.valueOf(retries));
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getRetries()).isEqualTo(retries);
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ public void givenInvalidRetriesValue_whenOnMsg_thenVerifyRequest(String retries) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue(DataConstants.RETRIES, retries);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().getRetries()).isNull();
+ }
+
+ @ParameterizedTest
+ @EnumSource(TbMsgType.class)
+ public void givenTbMsgType_whenOnMsg_thenVerifyRequest(TbMsgType msgType) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsg msg = TbMsg.newMsg(msgType, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ if (msgType == TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE) {
+ assertThat(requestCaptor.getValue().isRestApiCall()).isTrue();
+ return;
+ }
+ assertThat(requestCaptor.getValue().isRestApiCall()).isFalse();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void givenPersistent_whenOnMsg_thenVerifyRequest(String isPersisted, boolean expectedPersistence) {
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+
+ TbMsgMetaData metadata = new TbMsgMetaData();
+ metadata.putValue(DataConstants.PERSISTENT, isPersisted);
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, metadata, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor requestCaptor = captureRequest();
+ assertThat(requestCaptor.getValue().isPersisted()).isEqualTo(expectedPersistence);
+ }
+
+ private static Stream givenPersistent_whenOnMsg_thenVerifyRequest() {
+ return Stream.of(
+ Arguments.of("true", true),
+ Arguments.of("false", false),
+ Arguments.of(null, false),
+ Arguments.of("", false)
+ );
+ }
+
+ private ArgumentCaptor captureRequest() {
+ ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(RuleEngineDeviceRpcRequest.class);
+ then(rpcServiceMock).should().sendRpcRequestToDevice(requestCaptor.capture(), any(Consumer.class));
+ return requestCaptor;
+ }
+
+ @Test
+ public void givenRpcResponseWithoutError_whenOnMsg_thenSendsRpcRequest() {
+ TbMsg outMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+ // TODO: replace deprecated method newMsg()
+ given(ctxMock.newMsg(any(), any(String.class), any(), any(), any(), any())).willReturn(outMsg);
+ willAnswer(invocation -> {
+ Consumer consumer = invocation.getArgument(1);
+ RuleEngineDeviceRpcResponse rpcResponseMock = mock(RuleEngineDeviceRpcResponse.class);
+ given(rpcResponseMock.getError()).willReturn(Optional.empty());
+ given(rpcResponseMock.getResponse()).willReturn(Optional.of(TbMsg.EMPTY_JSON_OBJECT));
+ consumer.accept(rpcResponseMock);
+ return null;
+ }).given(rpcServiceMock).sendRpcRequestToDevice(any(RuleEngineDeviceRpcRequest.class), any(Consumer.class));
+
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ then(ctxMock).should().enqueueForTellNext(outMsg, TbNodeConnectionType.SUCCESS);
+ then(ctxMock).should().ack(msg);
+ }
+
+ @Test
+ public void givenRpcResponseWithError_whenOnMsg_thenTellFailure() {
+ TbMsg outMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+
+ given(ctxMock.getRpcService()).willReturn(rpcServiceMock);
+ given(ctxMock.getTenantId()).willReturn(TENANT_ID);
+ // TODO: replace deprecated method newMsg()
+ given(ctxMock.newMsg(any(), any(String.class), any(), any(), any(), any())).willReturn(outMsg);
+ willAnswer(invocation -> {
+ Consumer consumer = invocation.getArgument(1);
+ RuleEngineDeviceRpcResponse rpcResponseMock = mock(RuleEngineDeviceRpcResponse.class);
+ given(rpcResponseMock.getError()).willReturn(Optional.of(RpcError.NO_ACTIVE_CONNECTION));
+ consumer.accept(rpcResponseMock);
+ return null;
+ }).given(rpcServiceMock).sendRpcRequestToDevice(any(RuleEngineDeviceRpcRequest.class), any(Consumer.class));
+
+ TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, DEVICE_ID, TbMsgMetaData.EMPTY, MSG_DATA);
+ node.onMsg(ctxMock, msg);
+
+ then(ctxMock).should().enqueueForTellFailure(outMsg, RpcError.NO_ACTIVE_CONNECTION.name());
+ then(ctxMock).should().ack(msg);
+ }
+
+ @ParameterizedTest
+ @EnumSource(EntityType.class)
+ public void givenOriginatorIsNotDevice_whenOnMsg_thenThrowsException(EntityType entityType) {
+ EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, "ac21a1bb-eabf-4463-8313-24bea1f498d9");
+
+ TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT);
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
+ then(ctxMock).should().tellFailure(eq(msg), throwableCaptor.capture());
+ assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class)
+ .hasMessage(EntityType.DEVICE != entityType ? "Message originator is not a device entity!"
+ : "Method is not present in the message!");
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"method", "params"})
+ public void givenMethodOrParamsAreNotPresent_whenOnMsg_thenThrowsException(String key) {
+ TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, "{\"" + key + "\": \"value\"}");
+
+ node.onMsg(ctxMock, msg);
+
+ ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
+ then(ctxMock).should().tellFailure(eq(msg), throwableCaptor.capture());
+ assertThat(throwableCaptor.getValue()).isInstanceOf(RuntimeException.class)
+ .hasMessage(key.equals("method") ? "Params are not present in the message!" : "Method is not present in the message!");
+ }
+}
diff --git a/tools/pom.xml b/tools/pom.xml
index 38309f35b0..dfd750aa4f 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
tools
diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml
index c2c0252d9c..bdd27ae21c 100644
--- a/transport/coap/pom.xml
+++ b/transport/coap/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/http/pom.xml b/transport/http/pom.xml
index 7aaf88b59e..b48c3d801d 100644
--- a/transport/http/pom.xml
+++ b/transport/http/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml
index d0b961939a..be11fbdf2f 100644
--- a/transport/lwm2m/pom.xml
+++ b/transport/lwm2m/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml
index fe2c2be544..a10d3ff9ff 100644
--- a/transport/mqtt/pom.xml
+++ b/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/pom.xml b/transport/pom.xml
index 49009b83b0..6b1311b949 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
transport
diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml
index 5f236adb9a..a02cd85c58 100644
--- a/transport/snmp/pom.xml
+++ b/transport/snmp/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
transport
diff --git a/ui-ngx/package.json b/ui-ngx/package.json
index b8a8e4c9a9..f383054ece 100644
--- a/ui-ngx/package.json
+++ b/ui-ngx/package.json
@@ -1,6 +1,6 @@
{
"name": "thingsboard",
- "version": "3.7.0",
+ "version": "3.7.1",
"scripts": {
"ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open",
diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml
index c8b50c4149..931df3a6bc 100644
--- a/ui-ngx/pom.xml
+++ b/ui-ngx/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.7.0-SNAPSHOT
+ 3.7.1-SNAPSHOT
thingsboard
org.thingsboard
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts
index 0ae1bdc4f5..37879144cd 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts
@@ -517,7 +517,7 @@ class PersistentDatasource implements DataSource {
if (!this.executingRpcRequest || rejection.status === 504) {
this.subscription.rpcRejection = rejection;
if (rejection.status === 504) {
- this.subscription.rpcErrorText = 'Request Timeout.';
+ this.subscription.rpcErrorText = 'Request timeout';
} else {
this.subscription.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
const error = parseHttpErrorMessage(rejection, this.translate);
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 5106b622dd..695c2454ae 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -4088,7 +4088,7 @@
"target-device-is-not-set": "Target device is not set!",
"invalid-target-entity": "RPC commands are not supported by {{entityType}} entity.",
"failed-to-resolve-target-device": "Failed to resolve target device!",
- "request-timeout": "Request Timeout.",
+ "request-timeout": "Request timeout",
"rpc-http-error": "Error: {{status}} - {{statusText}}"
}
},