Browse Source

Merge branch 'master' into fix/3906-removed-opcua-asyncio

pull/11062/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
438f9ce759
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/pom.xml
  2. 6
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  3. 29
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  4. 2
      common/actor/pom.xml
  5. 2
      common/cache/pom.xml
  6. 2
      common/cluster-api/pom.xml
  7. 2
      common/coap-server/pom.xml
  8. 2
      common/dao-api/pom.xml
  9. 2
      common/data/pom.xml
  10. 6
      common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
  11. 29
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  12. 3
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java
  13. 2
      common/edge-api/pom.xml
  14. 2
      common/message/pom.xml
  15. 2
      common/pom.xml
  16. 2
      common/proto/pom.xml
  17. 2
      common/queue/pom.xml
  18. 2
      common/script/pom.xml
  19. 2
      common/script/remote-js-client/pom.xml
  20. 2
      common/script/script-api/pom.xml
  21. 2
      common/stats/pom.xml
  22. 2
      common/transport/coap/pom.xml
  23. 2
      common/transport/http/pom.xml
  24. 2
      common/transport/lwm2m/pom.xml
  25. 2
      common/transport/mqtt/pom.xml
  26. 2
      common/transport/pom.xml
  27. 2
      common/transport/snmp/pom.xml
  28. 2
      common/transport/transport-api/pom.xml
  29. 2
      common/util/pom.xml
  30. 2
      common/version-control/pom.xml
  31. 2
      dao/pom.xml
  32. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
  33. 7
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  34. 52
      dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidatorTest.java
  35. 16
      dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java
  36. 2
      monitoring/pom.xml
  37. 91
      monitoring/src/main/java/org/thingsboard/monitoring/client/Lwm2mClient.java
  38. 2
      msa/black-box-tests/pom.xml
  39. 2
      msa/js-executor/package.json
  40. 2
      msa/js-executor/pom.xml
  41. 2
      msa/monitoring/pom.xml
  42. 2
      msa/pom.xml
  43. 2
      msa/tb-node/pom.xml
  44. 2
      msa/tb/pom.xml
  45. 2
      msa/transport/coap/pom.xml
  46. 2
      msa/transport/http/pom.xml
  47. 2
      msa/transport/lwm2m/pom.xml
  48. 2
      msa/transport/mqtt/pom.xml
  49. 2
      msa/transport/pom.xml
  50. 2
      msa/transport/snmp/pom.xml
  51. 2
      msa/vc-executor-docker/pom.xml
  52. 2
      msa/vc-executor/pom.xml
  53. 2
      msa/web-ui/package.json
  54. 2
      msa/web-ui/pom.xml
  55. 4
      netty-mqtt/pom.xml
  56. 2
      pom.xml
  57. 2
      rest-client/pom.xml
  58. 2
      rule-engine/pom.xml
  59. 2
      rule-engine/rule-engine-api/pom.xml
  60. 2
      rule-engine/rule-engine-components/pom.xml
  61. 12
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
  62. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
  63. 64
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java
  64. 87
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java
  65. 421
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNodeTest.java
  66. 2
      tools/pom.xml
  67. 2
      transport/coap/pom.xml
  68. 2
      transport/http/pom.xml
  69. 2
      transport/lwm2m/pom.xml
  70. 2
      transport/mqtt/pom.xml
  71. 2
      transport/pom.xml
  72. 2
      transport/snmp/pom.xml
  73. 2
      ui-ngx/package.json
  74. 2
      ui-ngx/pom.xml
  75. 2
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts
  76. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

6
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." +

29
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<UUID> 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();
}
}
}
}

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

6
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<TenantProfileId> 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;

29
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;

3
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

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>common</artifactId>

2
common/proto/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/version-control/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>dao</artifactId>

7
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<RuleNodeEntity, UUID>
Pageable pageable);
@Query("SELECT r.id FROM RuleNodeEntity r WHERE r.type = :ruleType AND r.configurationVersion < :version")
Page<UUID> findAllRuleNodeIdsByTypeAndVersionLessThan(@Param("ruleType") String ruleType,
@Param("version") int version,
Pageable pageable);
Slice<UUID> findAllRuleNodeIdsByTypeAndVersionLessThan(@Param("ruleType") String ruleType,
@Param("version") int version,
Pageable pageable);
List<RuleNodeEntity> findRuleNodesByRuleChainIdAndExternalIdIn(UUID ruleChainId, List<UUID> externalIds);

7
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<Void> resultFuture = new SimpleListenableFuture<>();
final ListenableFuture<List<Long>> partitionsListFuture = Futures.transformAsync(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
final ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(tenantId, query, entityId, minPartition, maxPartition);
Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
@Override
@ -416,7 +415,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
readResultsProcessingExecutor);
}
private ListenableFuture<List<Long>> getPartitionsFuture(TenantId tenantId, ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
private ListenableFuture<List<Long>> 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);
}

52
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<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsNoSec(Integer shortServerId, Integer shortServerIdBs){
private List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsNoSec(Integer shortServerId, Integer shortServerIdBs) {
List<LwM2MBootstrapServerCredential> 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);
}
}

16
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<RuleNodeId> 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());
}

2
monitoring/pom.xml

@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>

91
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();

2
msa/black-box-tests/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
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",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/monitoring/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.msa</groupId>
<artifactId>transport</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/vc-executor-docker/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/vc-executor/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
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",

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>netty-mqtt</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Netty MQTT Client</name>

2
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rest-client</artifactId>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

12
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 <code>GET | POST | PUT | DELETE</code> 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;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java

@ -36,7 +36,6 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
private boolean useSimpleClientHttpFactory;
private int readTimeoutMs;
private int maxParallelRequestsCount;
private boolean useRedisQueueForMsgPersistence;
private boolean parseToPlainText;
private boolean enableProxy;
private boolean useSystemProxyProperties;
@ -57,7 +56,6 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
configuration.setUseSimpleClientHttpFactory(false);
configuration.setReadTimeoutMs(0);
configuration.setMaxParallelRequestsCount(0);
configuration.setUseRedisQueueForMsgPersistence(false);
configuration.setParseToPlainText(false);
configuration.setEnableProxy(false);
configuration.setCredentials(new AnonymousCredentials());

64
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java

@ -16,7 +16,6 @@
package org.thingsboard.rule.engine.rest;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@ -27,13 +26,16 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.provider.Arguments;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.id.DeviceId;
@ -41,7 +43,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -50,6 +51,7 @@ import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
@ -57,8 +59,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class TbRestApiCallNodeTest {
public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
@Spy
private TbRestApiCallNode restNode;
@Mock
@ -214,20 +217,45 @@ public class TbRestApiCallNodeTest {
assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue());
}
@Test
public void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception {
var defaultConfig = new TbRestApiCallNodeConfiguration().defaultConfiguration();
var node = new TbRestApiCallNode();
String oldConfig = "{\"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\":true}";
JsonNode configJson = JacksonUtil.toJsonNode(oldConfig);
TbPair<Boolean, JsonNode> upgrade = node.upgrade(0, configJson);
Assertions.assertTrue(upgrade.getFirst());
Assertions.assertTrue(JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass()).isParseToPlainText());
private static Stream<Arguments> 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;
}
}

87
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<Throwable> 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<Throwable> 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<Arguments> 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();

421
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<Arguments> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcRequest> requestCaptor = captureRequest();
assertThat(requestCaptor.getValue().isPersisted()).isEqualTo(expectedPersistence);
}
private static Stream<Arguments> givenPersistent_whenOnMsg_thenVerifyRequest() {
return Stream.of(
Arguments.of("true", true),
Arguments.of("false", false),
Arguments.of(null, false),
Arguments.of("", false)
);
}
private ArgumentCaptor<RuleEngineDeviceRpcRequest> captureRequest() {
ArgumentCaptor<RuleEngineDeviceRpcRequest> 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<RuleEngineDeviceRpcResponse> 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<RuleEngineDeviceRpcResponse> 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<Throwable> 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<Throwable> 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!");
}
}

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>tools</artifactId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>transport</artifactId>

2
transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>

2
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",

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

2
ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts

@ -517,7 +517,7 @@ class PersistentDatasource implements DataSource<PersistentRpcData> {
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);

2
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 <b>{{entityType}}</b> 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}}"
}
},

Loading…
Cancel
Save