Browse Source

Merge remote-tracking branch 'thingsboard-ce/lts-4.3' into fix/power-button

pull/14959/head
Maksym Tsymbarov 8 months ago
parent
commit
819a045937
  1. 31
      TEST_FAST.md
  2. 3
      application/pom.xml
  3. 13
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 10
      application/src/main/java/org/thingsboard/server/actors/calculatedField/AbstractCalculatedFieldActor.java
  5. 60
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  6. 10
      application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java
  9. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java
  10. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java
  11. 15
      application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java
  12. 10
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  13. 91
      application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java
  14. 11
      application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java
  15. 12
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  16. 9
      application/src/test/java/org/thingsboard/server/controller/ApiKeyControllerTest.java
  17. 97
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  18. 14
      application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java
  19. 14
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  20. 10
      application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java
  21. 41
      application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java
  22. 19
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  23. 28
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  24. 204
      application/src/test/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityServiceTest.java
  25. 5
      application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java
  26. 14
      application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
  27. 3
      application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java
  28. 37
      application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java
  29. 14
      application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java
  30. 70
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  31. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java
  32. 41
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  33. 1
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java
  34. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  35. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  36. 13
      application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
  37. 10
      application/src/test/java/org/thingsboard/server/transport/mqtt/MqttGatewayRateLimitsTest.java
  38. 6
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/MqttTestClient.java
  39. 6
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/MqttV5TestClient.java
  40. 42
      application/src/test/java/org/thingsboard/server/utils/PortFinder.java
  41. 1
      application/src/test/resources/application-test.properties
  42. 6
      application/src/test/resources/logback-test.xml
  43. 2
      common/actor/pom.xml
  44. 2
      common/cache/pom.xml
  45. 3
      common/cache/src/test/resources/logback-test.xml
  46. 2
      common/cluster-api/pom.xml
  47. 2
      common/coap-server/pom.xml
  48. 2
      common/dao-api/pom.xml
  49. 2
      common/data/pom.xml
  50. 2
      common/discovery-api/pom.xml
  51. 7
      common/edge-api/pom.xml
  52. 11
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  53. 86
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java
  54. 2
      common/edge-api/src/main/proto/edge.proto
  55. 102
      common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java
  56. 2
      common/edqs/pom.xml
  57. 3
      common/edqs/src/main/java/org/thingsboard/server/edqs/util/TbRocksDb.java
  58. 2
      common/message/pom.xml
  59. 2
      common/pom.xml
  60. 2
      common/proto/pom.xml
  61. 2
      common/queue/pom.xml
  62. 2
      common/script/pom.xml
  63. 2
      common/script/remote-js-client/pom.xml
  64. 2
      common/script/script-api/pom.xml
  65. 2
      common/stats/pom.xml
  66. 2
      common/transport/coap/pom.xml
  67. 2
      common/transport/http/pom.xml
  68. 2
      common/transport/lwm2m/pom.xml
  69. 2
      common/transport/mqtt/pom.xml
  70. 2
      common/transport/pom.xml
  71. 2
      common/transport/snmp/pom.xml
  72. 2
      common/transport/transport-api/pom.xml
  73. 2
      common/util/pom.xml
  74. 2
      common/version-control/pom.xml
  75. 2
      dao/pom.xml
  76. 7
      dao/src/main/java/org/thingsboard/server/dao/resource/DefaultTbResourceDataCache.java
  77. 16
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
  78. 16
      dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java
  79. 154
      dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java
  80. 1
      dao/src/test/resources/application-test.properties
  81. 3
      dao/src/test/resources/logback-test.xml
  82. 2
      edqs/pom.xml
  83. 2
      monitoring/pom.xml
  84. 2
      msa/black-box-tests/pom.xml
  85. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractCoapClientTest.java
  86. 42
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/PortFinder.java
  87. 24
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java
  88. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java
  89. 21
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java
  90. 3
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/rule/node/MqttNodeTest.java
  91. 2
      msa/edqs/pom.xml
  92. 2
      msa/js-executor/package.json
  93. 2
      msa/js-executor/pom.xml
  94. 2
      msa/monitoring/pom.xml
  95. 2
      msa/pom.xml
  96. 2
      msa/tb-node/pom.xml
  97. 2
      msa/tb/pom.xml
  98. 2
      msa/transport/coap/pom.xml
  99. 2
      msa/transport/http/pom.xml
  100. 2
      msa/transport/lwm2m/pom.xml

31
TEST_FAST.md

@ -0,0 +1,31 @@
## Running tests in parallel with a reasonable memory usage
```bash
export MAVEN_OPTS="-Xmx1024m"
export NODE_OPTIONS="--max_old_space_size=4096"
export SUREFIRE_JAVA_OPTS="-Xmx1200m -Xss256k -XX:+ExitOnOutOfMemoryError"
mvn clean install -T6 -DskipTests
mvn test -pl='!application,!dao,!ui-ngx,!msa/js-executor,!msa/web-ui' -T4
mvn test -pl dao -Dparallel=packages -DforkCount=4
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.edge.**' -DforkCount=4 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.service.**' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.transport.mqtt.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.transport.coap.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='org.thingsboard.server.transport.lwm2m.**' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
mvn test -pl application -Dsurefire.excludes='**/nosql/*Test.java' -Dtest='**/*TestSuite.java' -DforkCount=4 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
#the rest of application tests
mvn test -pl application -Dtest='
!**/nosql/*Test.java,
!org.thingsboard.server.controller.**,
!org.thingsboard.server.edge.**,
!org.thingsboard.server.service.**,
!org.thingsboard.server.transport.mqtt.**,
!org.thingsboard.server.transport.coap.**,
!org.thingsboard.server.transport.lwm2m.**
' -DforkCount=6 -Dparallel=packages -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5
```

3
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>4.3.0-RC</version> <version>4.3.0.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>
@ -458,6 +458,7 @@
<configuration> <configuration>
<systemPropertyVariables> <systemPropertyVariables>
<spring.config.name>thingsboard</spring.config.name> <spring.config.name>thingsboard</spring.config.name>
<project.version>${project.version}</project.version>
</systemPropertyVariables> </systemPropertyVariables>
<excludes> <excludes>
<exclude>**/nosql/*Test.java</exclude> <exclude>**/nosql/*Test.java</exclude>

13
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -42,6 +42,7 @@ import org.thingsboard.rule.engine.api.notification.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService; import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.calculatedField.CalculatedFieldException;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.cache.limits.RateLimitService;
@ -831,6 +832,18 @@ public class ActorSystemContext {
Futures.addCallback(future, RULE_CHAIN_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor()); Futures.addCallback(future, RULE_CHAIN_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
} }
public void persistCalculatedFieldDebugError(CalculatedFieldException cfe) {
String message;
if (cfe.getErrorMessage() != null) {
message = cfe.getErrorMessage();
} else if (cfe.getCause() != null) {
message = cfe.getCause().getMessage();
} else {
message = "N/A";
}
persistCalculatedFieldDebugEvent(cfe.getCtx().getTenantId(), cfe.getCtx().getCfId(), cfe.getEventEntity(), cfe.getArguments(), cfe.getMsgId(), cfe.getMsgType(), null, message);
}
public void persistCalculatedFieldDebugEvent(TenantId tenantId, CalculatedFieldId calculatedFieldId, EntityId entityId, JsonNode arguments, UUID tbMsgId, String tbMsgType, String result, String errorMessage) { public void persistCalculatedFieldDebugEvent(TenantId tenantId, CalculatedFieldId calculatedFieldId, EntityId entityId, JsonNode arguments, UUID tbMsgId, String tbMsgType, String result, String errorMessage) {
if (checkLimits(tenantId)) { if (checkLimits(tenantId)) {
try { try {

10
application/src/main/java/org/thingsboard/server/actors/calculatedField/AbstractCalculatedFieldActor.java

@ -41,15 +41,7 @@ public abstract class AbstractCalculatedFieldActor extends ContextAwareActor {
return doProcessCfMsg(cfm); return doProcessCfMsg(cfm);
} catch (CalculatedFieldException cfe) { } catch (CalculatedFieldException cfe) {
if (DebugModeUtil.isDebugFailuresAvailable(cfe.getCtx().getCalculatedField())) { if (DebugModeUtil.isDebugFailuresAvailable(cfe.getCtx().getCalculatedField())) {
String message; systemContext.persistCalculatedFieldDebugError(cfe);
if (cfe.getErrorMessage() != null) {
message = cfe.getErrorMessage();
} else if (cfe.getCause() != null) {
message = cfe.getCause().getMessage();
} else {
message = "N/A";
}
systemContext.persistCalculatedFieldDebugEvent(tenantId, cfe.getCtx().getCfId(), cfe.getEventEntity(), cfe.getArguments(), cfe.getMsgId(), cfe.getMsgType(), null, message);
} }
cause = cfe.getCause(); cause = cfe.getCause();
} catch (Exception e) { } catch (Exception e) {

60
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -171,7 +171,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
if (state.isSizeOk()) { if (state.isSizeOk()) {
processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, msg.getEventType().name(), msg.getCallback()); processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, msg.getEventType().name(), msg.getCallback());
} else { } else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage()); throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build();
} }
} else { } else {
msg.getCallback().onSuccess(); msg.getCallback().onSuccess();
@ -261,7 +261,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build(); throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build();
} }
} catch (Exception e) { } catch (Exception e) {
log.debug("[{}][{}] Failed to initialize CF state", entityId, ctx.getCfId(), e); log.debug("[{}][{}] Failed to handle relation update", entityId, ctx.getCfId(), e);
if (e instanceof CalculatedFieldException cfe) { if (e instanceof CalculatedFieldException cfe) {
throw cfe; throw cfe;
} }
@ -273,31 +273,39 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
CalculatedFieldCtx ctx = msg.getCalculatedField(); CalculatedFieldCtx ctx = msg.getCalculatedField();
CalculatedFieldId cfId = ctx.getCfId(); CalculatedFieldId cfId = ctx.getCfId();
CalculatedFieldState state = states.get(cfId); CalculatedFieldState state = states.get(cfId);
if (state == null) { try {
msg.getCallback().onSuccess(); if (state == null) {
return; msg.getCallback().onSuccess();
} return;
if (state instanceof RelatedEntitiesAggregationCalculatedFieldState aggState) { }
aggState.cleanupEntityData(msg.getRelatedEntityId()); if (state instanceof RelatedEntitiesAggregationCalculatedFieldState aggState) {
aggState.cleanupEntityData(msg.getRelatedEntityId());
state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize()); state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize());
if (state.isSizeOk()) { if (state.isSizeOk()) {
processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, TbMsgType.RELATION_DELETED.name(), msg.getCallback()); processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, TbMsgType.RELATION_DELETED.name(), msg.getCallback());
} else { } else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage()); throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build();
}
return;
}
if (state instanceof PropagationCalculatedFieldState propagationState) {
PropagationArgumentEntry entry = new PropagationArgumentEntry();
entry.setRemoved(msg.getRelatedEntityId());
propagationState.update(Map.of(PROPAGATION_CONFIG_ARGUMENT, entry), ctx);
if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArgumentsJson(), null, TbMsgType.RELATION_DELETED.name(), null, null);
}
} }
return; msg.getCallback().onSuccess();
} } catch (Exception e) {
if (state instanceof PropagationCalculatedFieldState propagationState) { log.debug("[{}][{}] Failed to handle relation delete", entityId, ctx.getCfId(), e);
PropagationArgumentEntry entry = new PropagationArgumentEntry(); if (e instanceof CalculatedFieldException cfe) {
entry.setRemoved(msg.getRelatedEntityId()); throw cfe;
propagationState.update(Map.of(PROPAGATION_CONFIG_ARGUMENT, entry), ctx);
if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArgumentsJson(), null, TbMsgType.RELATION_DELETED.name(), null, null);
} }
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
} }
msg.getCallback().onSuccess();
} }
public void process(EntityCalculatedFieldTelemetryMsg msg) throws CalculatedFieldException { public void process(EntityCalculatedFieldTelemetryMsg msg) throws CalculatedFieldException {
@ -366,7 +374,11 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
} catch (Exception e) { } catch (Exception e) {
log.debug("[{}][{}] Failed to process CF telemetry msg: {}", entityId, ctx.getCfId(), proto, e); log.debug("[{}][{}] Failed to process CF telemetry msg: {}", entityId, ctx.getCfId(), proto, e);
if (e instanceof CalculatedFieldException cfe) { if (e instanceof CalculatedFieldException cfe) {
throw cfe; if (DebugModeUtil.isDebugFailuresAvailable(cfe.getCtx().getCalculatedField())) {
systemContext.persistCalculatedFieldDebugError(cfe);
}
callback.onSuccess();
return;
} }
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build(); throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
} }
@ -384,7 +396,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
log.debug("[{}][{}] Reevaluating CF state", entityId, cfId); log.debug("[{}][{}] Reevaluating CF state", entityId, cfId);
processStateIfReady(state, null, ctx, Collections.singletonList(cfId), null, CalculatedFieldEventType.REEVALUATION_MSG.name(), msg.getCallback()); processStateIfReady(state, null, ctx, Collections.singletonList(cfId), null, CalculatedFieldEventType.REEVALUATION_MSG.name(), msg.getCallback());
} else { } else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage()); throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build();
} }
} }

10
application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java

@ -77,7 +77,11 @@ public class ApiKeyController extends BaseController {
User user = checkUserId(apiKeyInfo.getUserId(), Operation.WRITE); User user = checkUserId(apiKeyInfo.getUserId(), Operation.WRITE);
apiKeyInfo.setTenantId(user.getTenantId()); apiKeyInfo.setTenantId(user.getTenantId());
checkEntity(apiKeyInfo.getId(), apiKeyInfo, Resource.API_KEY); checkEntity(apiKeyInfo.getId(), apiKeyInfo, Resource.API_KEY);
return checkNotNull(apiKeyService.saveApiKey(apiKeyInfo.getTenantId(), apiKeyInfo)); ApiKey savedApiKey = checkNotNull(apiKeyService.saveApiKey(apiKeyInfo.getTenantId(), apiKeyInfo));
if (apiKeyInfo.getId() != null) {
savedApiKey.setValue(null);
}
return savedApiKey;
} }
@ApiOperation(value = "Get User Api Keys (getUserApiKeys)", @ApiOperation(value = "Get User Api Keys (getUserApiKeys)",
@ -121,7 +125,7 @@ public class ApiKeyController extends BaseController {
ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.WRITE); ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.WRITE);
checkUserId(apiKey.getUserId(), Operation.WRITE); checkUserId(apiKey.getUserId(), Operation.WRITE);
apiKey.setDescription(description.orElse(null)); apiKey.setDescription(description.orElse(null));
return apiKeyService.saveApiKey(apiKey.getTenantId(), apiKey); return new ApiKeyInfo(apiKeyService.saveApiKey(apiKey.getTenantId(), apiKey));
} }
@ApiOperation(value = "Enable or disable API key (enableApiKey)", @ApiOperation(value = "Enable or disable API key (enableApiKey)",
@ -137,7 +141,7 @@ public class ApiKeyController extends BaseController {
ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.WRITE); ApiKey apiKey = checkApiKeyId(apiKeyId, Operation.WRITE);
checkUserId(apiKey.getUserId(), Operation.WRITE); checkUserId(apiKey.getUserId(), Operation.WRITE);
apiKey.setEnabled(enabledValue); apiKey.setEnabled(enabledValue);
return apiKeyService.saveApiKey(apiKey.getTenantId(), apiKey); return new ApiKeyInfo(apiKeyService.saveApiKey(apiKey.getTenantId(), apiKey));
} }
@ApiOperation(value = "Delete API key by ID (deleteApiKey)", @ApiOperation(value = "Delete API key by ID (deleteApiKey)",

2
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java

@ -804,7 +804,7 @@ public class CalculatedFieldCtx implements Closeable {
} }
public String getSizeExceedsLimitMessage() { public String getSizeExceedsLimitMessage() {
return "Failed to init CF state. State size exceeds limit of " + (maxStateSize / 1024) + "Kb!"; return "State size exceeds limit of " + (maxStateSize / 1024) + "Kb!";
} }
public boolean hasCurrentOwnerSourceArguments() { public boolean hasCurrentOwnerSourceArguments() {

3
application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java

@ -26,6 +26,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode; import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode;
import org.thingsboard.rule.engine.ai.TbAiNode; import org.thingsboard.rule.engine.ai.TbAiNode;
import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode; import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode;
@ -281,7 +282,7 @@ public class EdgeMsgConstructorUtils {
public static String getEntityAndFixLwm2mBootstrapShortServerId(DeviceProfile deviceProfile, EdgeVersion edgeVersion) { public static String getEntityAndFixLwm2mBootstrapShortServerId(DeviceProfile deviceProfile, EdgeVersion edgeVersion) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
if (!(transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) || edgeVersion.getNumber() >= EdgeVersion.V_4_3_0.getNumber()) { if (!(transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) || EdgeVersionComparator.INSTANCE.compare(edgeVersion, EdgeVersion.V_4_3_0) >= 0) {
return JacksonUtil.toString(deviceProfile); return JacksonUtil.toString(deviceProfile);
} }
JsonNode jsonNode = JacksonUtil.valueToTree(deviceProfile); JsonNode jsonNode = JacksonUtil.valueToTree(deviceProfile);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair; import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
@ -116,7 +117,7 @@ public class CalculatedFieldEdgeProcessor extends BaseCalculatedFieldProcessor i
} }
private boolean isValidCfToSend(CalculatedFieldType type, EdgeVersion edgeVersion) { private boolean isValidCfToSend(CalculatedFieldType type, EdgeVersion edgeVersion) {
return edgeVersion.getNumber() >= EdgeVersion.V_4_3_0.getNumber() || (type == SIMPLE || type == SCRIPT); return EdgeVersionComparator.INSTANCE.compare(edgeVersion, EdgeVersion.V_4_3_0) >= 0 || (type == SIMPLE || type == SCRIPT);
} }
@Override @Override

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java

@ -16,13 +16,14 @@
package org.thingsboard.server.service.edge.rpc.utils; package org.thingsboard.server.service.edge.rpc.utils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@Slf4j @Slf4j
public final class EdgeVersionUtils { public final class EdgeVersionUtils {
public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) { public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) {
return currentVersion.ordinal() < requiredVersion.ordinal(); return EdgeVersionComparator.INSTANCE.compare(currentVersion, requiredVersion) < 0;
} }
} }

15
application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java

@ -21,17 +21,18 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.service.install.update.DefaultDataUpdateService; import org.thingsboard.server.service.install.update.DefaultDataUpdateService;
import java.util.List; import java.util.Map;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSettingsService { public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSettingsService {
// This list should include all versions that are compatible for the upgrade in 4 digits format (like 4.2.0.0, etc.). // map of versions from which the upgrade to the current version is possible
// The compatibility cycle usually breaks when we have some scripts written in Java that may not work after a new release. // key - supported version prefix, value - display name
// TODO: don't check the "patch" number, since upgrade is not required for patch releases private static final Map<String, String> SUPPORTED_VERSIONS_FOR_UPGRADE = Map.of(
private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.1.0", "4.2.1.1", "4.2.1.2"); "4.2.1", "4.2.1.x"
);
private final ProjectInfo projectInfo; private final ProjectInfo projectInfo;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
@ -56,9 +57,9 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
onSchemaSettingsError("Upgrade failed: database already upgraded to current version. You can set SKIP_SCHEMA_VERSION_CHECK to 'true' if force re-upgrade needed."); onSchemaSettingsError("Upgrade failed: database already upgraded to current version. You can set SKIP_SCHEMA_VERSION_CHECK to 'true' if force re-upgrade needed.");
} }
if (!SUPPORTED_VERSIONS_FOR_UPGRADE.contains(dbSchemaVersion)) { if (SUPPORTED_VERSIONS_FOR_UPGRADE.keySet().stream().noneMatch(dbSchemaVersion::startsWith)) {
onSchemaSettingsError(String.format("Upgrade failed: database version '%s' is not supported for upgrade. Supported versions are: %s.", onSchemaSettingsError(String.format("Upgrade failed: database version '%s' is not supported for upgrade. Supported versions are: %s.",
dbSchemaVersion, SUPPORTED_VERSIONS_FOR_UPGRADE dbSchemaVersion, SUPPORTED_VERSIONS_FOR_UPGRADE.values()
)); ));
} }
} }

10
application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java

@ -80,11 +80,15 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
@Override @Override
public void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException { public void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException {
if (!userCredentials.isEnabled()) {
throw new DisabledException("User is not active");
}
if (!encoder.matches(password, userCredentials.getPassword())) { if (!encoder.matches(password, userCredentials.getPassword())) {
int failedLoginAttempts = userService.increaseFailedLoginAttempts(tenantId, userCredentials.getUserId()); int failedLoginAttempts = userService.increaseFailedLoginAttempts(tenantId, userCredentials.getUserId());
SecuritySettings securitySettings = securitySettingsService.getSecuritySettings(); SecuritySettings securitySettings = securitySettingsService.getSecuritySettings();
if (securitySettings.getMaxFailedLoginAttempts() != null && securitySettings.getMaxFailedLoginAttempts() > 0) { if (securitySettings.getMaxFailedLoginAttempts() != null && securitySettings.getMaxFailedLoginAttempts() > 0) {
if (failedLoginAttempts > securitySettings.getMaxFailedLoginAttempts() && userCredentials.isEnabled()) { if (failedLoginAttempts > securitySettings.getMaxFailedLoginAttempts()) {
lockAccount(userCredentials.getUserId(), username, securitySettings.getUserLockoutNotificationEmail(), securitySettings.getMaxFailedLoginAttempts()); lockAccount(userCredentials.getUserId(), username, securitySettings.getUserLockoutNotificationEmail(), securitySettings.getMaxFailedLoginAttempts());
throw new LockedException("Authentication Failed. Username was locked due to security policy."); throw new LockedException("Authentication Failed. Username was locked due to security policy.");
} }
@ -92,10 +96,6 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
throw new BadCredentialsException("Authentication Failed. Username or Password not valid."); throw new BadCredentialsException("Authentication Failed. Username or Password not valid.");
} }
if (!userCredentials.isEnabled()) {
throw new DisabledException("User is not active");
}
userService.resetFailedLoginAttempts(tenantId, userCredentials.getUserId()); userService.resetFailedLoginAttempts(tenantId, userCredentials.getUserId());
SecuritySettings securitySettings = securitySettingsService.getSecuritySettings(); SecuritySettings securitySettings = securitySettingsService.getSecuritySettings();

91
application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java

@ -20,10 +20,12 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
@ -45,7 +47,9 @@ import org.thingsboard.server.common.data.cf.configuration.geofencing.Geofencing
import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration; import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
import org.thingsboard.server.common.data.debug.DebugSettings; import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationPathLevel; import org.thingsboard.server.common.data.relation.RelationPathLevel;
@ -1390,6 +1394,93 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
}); });
} }
@Test
public void testCalculatedFieldsWhenOneIsInvalid() throws Exception {
Device testDevice = createDevice("Test device", "1234567890");
long now = System.currentTimeMillis();
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + AttributeScope.SERVER_SCOPE, JacksonUtil.toJsonNode(String.format("{\"ts\": %s, \"values\": {\"a\":5}}", now - TimeUnit.MINUTES.toMillis(3))));
// Script CF - invalid
CalculatedField invalidCF = new CalculatedField();
invalidCF.setEntityId(testDevice.getId());
invalidCF.setType(CalculatedFieldType.SCRIPT);
invalidCF.setName("Script CF");
invalidCF.setDebugSettings(DebugSettings.all());
ScriptCalculatedFieldConfiguration scriptConfig = new ScriptCalculatedFieldConfiguration();
ReferencedEntityKey refEntityKeyA = new ReferencedEntityKey("a", ArgumentType.TS_LATEST, null);
Argument argumentA = new Argument();
argumentA.setRefEntityKey(refEntityKeyA);
scriptConfig.setArguments(Map.of("a", argumentA));
scriptConfig.setExpression("""
return {
"temperature": temp
};
""");
scriptConfig.setOutput(new TimeSeriesOutput());
invalidCF.setConfiguration(scriptConfig);
invalidCF = doPost("/api/calculatedField", invalidCF, CalculatedField.class);
CalculatedFieldId invalidCfId = invalidCF.getId();
await().alias("create invalid CF -> check error").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
PageData<EventInfo> debugEvents = getDebugEvents(tenantId, invalidCfId, 1);
if (!debugEvents.getData().isEmpty()) {
EventInfo eventInfo = debugEvents.getData().get(0);
assertThat(eventInfo.getBody().has("error")).isTrue();
}
});
// Simple CF - valid
CalculatedField validCF = new CalculatedField();
validCF.setEntityId(testDevice.getId());
validCF.setType(CalculatedFieldType.SIMPLE);
validCF.setName("Simple CF");
validCF.setDebugSettings(DebugSettings.all());
SimpleCalculatedFieldConfiguration simpleConfig = new SimpleCalculatedFieldConfiguration();
simpleConfig.setArguments(Map.of("a", argumentA));
simpleConfig.setExpression("a+1");
TimeSeriesOutput simpleOutput = new TimeSeriesOutput();
simpleOutput.setName("a+1");
simpleOutput.setDecimalsByDefault(0);
simpleConfig.setOutput(simpleOutput);
validCF.setConfiguration(simpleConfig);
validCF = doPost("/api/calculatedField", validCF, CalculatedField.class);
CalculatedFieldId validCfId = validCF.getId();
await().alias("create CF -> check initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
PageData<EventInfo> debugEvents = getDebugEvents(tenantId, validCfId, 1);
if (!debugEvents.getData().isEmpty()) {
EventInfo eventInfo = debugEvents.getData().get(0);
assertThat(eventInfo.getBody().has("error")).isFalse();
}
ObjectNode result = getLatestTelemetry(testDevice.getId(), "a+1");
assertThat(result).isNotNull();
assertThat(result.get("a+1").get(0).get("value").asText()).isEqualTo("6");
});
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + AttributeScope.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"a\":6}"));
await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode result = getLatestTelemetry(testDevice.getId(), "a+1");
assertThat(result).isNotNull();
assertThat(result.get("a+1").get(0).get("value").asText()).isEqualTo("7");
});
}
private ObjectNode getLatestTelemetry(EntityId entityId, String... keys) throws Exception { private ObjectNode getLatestTelemetry(EntityId entityId, String... keys) throws Exception {
return doGetAsync("/api/plugins/telemetry/" + entityId.getEntityType() + "/" + entityId.getId() + "/values/timeseries?keys=" + String.join(",", keys), ObjectNode.class); return doGetAsync("/api/plugins/telemetry/" + entityId.getEntityType() + "/" + entityId.getId() + "/values/timeseries?keys=" + String.join(",", keys), ObjectNode.class);
} }

11
application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java

@ -205,6 +205,15 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
CustomInterval customInterval = new CustomInterval(TZ, 0L, 2L); CustomInterval customInterval = new CustomInterval(TZ, 0L, 2L);
createConsumptionCF(device.getId(), customInterval, null); createConsumptionCF(device.getId(), customInterval, null);
long interval = customInterval.getCurrentIntervalDurationMillis();
// Wait for a fresh interval
long initialIntervalStart = customInterval.getCurrentIntervalStartTs();
await().alias("wait for fresh interval")
.atMost(interval + 100, TimeUnit.MILLISECONDS)
.pollInterval(100, TimeUnit.MILLISECONDS)
.until(() -> customInterval.getCurrentIntervalStartTs() != initialIntervalStart);
long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs(); long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs();
long tsBeforeInterval = currentIntervalStartTs - 1000; long tsBeforeInterval = currentIntervalStartTs - 1000;
@ -216,8 +225,6 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":180}}", tsInInterval_2)); postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":180}}", tsInInterval_2));
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsInInterval_3)); postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsInInterval_3));
long interval = customInterval.getCurrentIntervalDurationMillis();
await().alias("create CF -> perform aggregation after interval end") await().alias("create CF -> perform aggregation after interval end")
.atMost(2 * interval, TimeUnit.MILLISECONDS) .atMost(2 * interval, TimeUnit.MILLISECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)

12
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -60,6 +60,7 @@ import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
@ -349,7 +350,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
if (this.mockMvc == null) { if (this.mockMvc == null) {
this.mockMvc = webAppContextSetup(webApplicationContext) this.mockMvc = webAppContextSetup(webApplicationContext)
.apply(springSecurity()).build(); .apply(springSecurity())
// conditional printing of non 2xx responses
.alwaysDo(result -> {
if (result.getResponse().getStatus() >= 400) {
MockMvcResultHandlers.log().handle(result);
}
})
.build();
} }
loginSysAdmin(); loginSysAdmin();
@ -423,7 +431,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
jdbcTemplate.execute("TRUNCATE TABLE notification"); jdbcTemplate.execute("TRUNCATE TABLE notification");
log.info("Executed web test teardown"); log.debug("Executed web test teardown");
} }
private void verifyNoTenantsLeft() throws Exception { private void verifyNoTenantsLeft() throws Exception {

9
application/src/test/java/org/thingsboard/server/controller/ApiKeyControllerTest.java

@ -53,7 +53,14 @@ public class ApiKeyControllerTest extends AbstractControllerTest {
Assert.assertEquals(tenantId, savedApiKey.getTenantId()); Assert.assertEquals(tenantId, savedApiKey.getTenantId());
Assert.assertEquals(tenantAdminUser.getId(), savedApiKey.getUserId()); Assert.assertEquals(tenantAdminUser.getId(), savedApiKey.getUserId());
doDelete("/api/apiKey/" + savedApiKey.getId()).andExpect(status().isOk()); String newDescription = "Updated API Key Description";
savedApiKey.setDescription(newDescription);
ApiKey updatedApiKey = doPost("/api/apiKey", savedApiKey, ApiKey.class);
Assert.assertNotNull(updatedApiKey);
Assert.assertEquals(newDescription, updatedApiKey.getDescription());
Assert.assertNull("Verify we do not expose API key value on update", updatedApiKey.getValue());
doDelete("/api/apiKey/" + updatedApiKey.getId()).andExpect(status().isOk());
} }
@Test @Test

97
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -36,7 +36,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.TestSocketUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
@ -99,6 +102,7 @@ import org.thingsboard.server.service.edge.instructions.EdgeUpgradeInstructionsS
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -121,7 +125,12 @@ import static org.thingsboard.server.edge.AbstractEdgeTest.CONNECT_MESSAGE_COUNT
public class EdgeControllerTest extends AbstractControllerTest { public class EdgeControllerTest extends AbstractControllerTest {
public static final String EDGE_HOST = "localhost"; public static final String EDGE_HOST = "localhost";
public static final int EDGE_PORT = 7070; public static final int EDGE_PORT = TestSocketUtils.findAvailableTcpPort();
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.debug("edges.rpc.port = {}", EDGE_PORT);
registry.add("edges.rpc.port", () -> EDGE_PORT);
}
private IdComparator<Edge> idComparator = new IdComparator<>(); private IdComparator<Edge> idComparator = new IdComparator<>();
@ -916,11 +925,11 @@ public class EdgeControllerTest extends AbstractControllerTest {
verifyFetchersMsgs(edgeImitator, savedDevice); verifyFetchersMsgs(edgeImitator, savedDevice);
// verify queue msgs // verify queue msgs
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1")); Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1"));
Assert.assertTrue(popDeviceCredentialsMsg(edgeImitator.getDownlinkMsgs(), savedDevice.getId())); Assert.assertTrue(popDeviceCredentialsMsg(edgeImitator.getDownlinkMsgsDeque(), savedDevice.getId()));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1")); Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1"));
printQueueMsgsIfNotEmpty(edgeImitator); printQueueMsgsIfNotEmpty(edgeImitator);
// 17 connect messages // 17 connect messages
@ -1000,32 +1009,32 @@ public class EdgeControllerTest extends AbstractControllerTest {
} }
private void verifyFetchersMsgs(EdgeImitator edgeImitator, Device savedDevice) { private void verifyFetchersMsgs(EdgeImitator edgeImitator, Device savedDevice) {
Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main")); Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main"));
Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain")); Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain"));
Assert.assertTrue(popRuleChainMetadataMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, getEdgeRootRuleChainId(edgeImitator))); Assert.assertTrue(popRuleChainMetadataMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, getEdgeRootRuleChainId(edgeImitator)));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "general")); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgsDeque(), "general"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail")); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgsDeque(), "mail"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "connectivity")); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgsDeque(), "connectivity"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "jwt")); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgsDeque(), "jwt"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popUserCredentialsMsg(edgeImitator.getDownlinkMsgs(), currentUserId)); Assert.assertTrue(popUserCredentialsMsg(edgeImitator.getDownlinkMsgsDeque(), currentUserId));
Assert.assertTrue(popUserMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, TENANT_ADMIN_EMAIL, Authority.TENANT_ADMIN)); Assert.assertTrue(popUserMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, TENANT_ADMIN_EMAIL, Authority.TENANT_ADMIN));
Assert.assertTrue(popCustomerMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Public")); Assert.assertTrue(popCustomerMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Public"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1")); Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1"));
Assert.assertTrue(popDeviceCredentialsMsg(edgeImitator.getDownlinkMsgs(), savedDevice.getId())); Assert.assertTrue(popDeviceCredentialsMsg(edgeImitator.getDownlinkMsgsDeque(), savedDevice.getId()));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1")); Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgsDeque(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1"));
Assert.assertTrue(popTenantMsg(edgeImitator.getDownlinkMsgs(), tenantId)); Assert.assertTrue(popTenantMsg(edgeImitator.getDownlinkMsgsDeque(), tenantId));
Assert.assertTrue(popTenantProfileMsg(edgeImitator.getDownlinkMsgs(), tenantProfileId)); Assert.assertTrue(popTenantProfileMsg(edgeImitator.getDownlinkMsgsDeque(), tenantProfileId));
Assert.assertTrue(popSyncCompletedMsg(edgeImitator.getDownlinkMsgs())); Assert.assertTrue(popSyncCompletedMsg(edgeImitator.getDownlinkMsgsDeque()));
} }
private boolean popQueueMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popQueueMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof QueueUpdateMsg queueUpdateMsg) { if (message instanceof QueueUpdateMsg queueUpdateMsg) {
Queue queue = JacksonUtil.fromString(queueUpdateMsg.getEntity(), Queue.class, true); Queue queue = JacksonUtil.fromString(queueUpdateMsg.getEntity(), Queue.class, true);
@ -1039,7 +1048,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popRuleChainMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popRuleChainMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof RuleChainUpdateMsg ruleChainUpdateMsg) { if (message instanceof RuleChainUpdateMsg ruleChainUpdateMsg) {
RuleChain ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true); RuleChain ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true);
@ -1055,7 +1064,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popRuleChainMetadataMsg(List<AbstractMessage> messages, UpdateMsgType msgType, RuleChainId ruleChainId) { private boolean popRuleChainMetadataMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, RuleChainId ruleChainId) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg) { if (message instanceof RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg) {
RuleChainMetaData ruleChainMetaData = JacksonUtil.fromString(ruleChainMetadataUpdateMsg.getEntity(), RuleChainMetaData.class, true); RuleChainMetaData ruleChainMetaData = JacksonUtil.fromString(ruleChainMetadataUpdateMsg.getEntity(), RuleChainMetaData.class, true);
@ -1070,7 +1079,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key) { private boolean popAdminSettingsMsg(Deque<AbstractMessage> messages, String key) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AdminSettingsUpdateMsg adminSettingsUpdateMsg) { if (message instanceof AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
AdminSettings adminSettings = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true); AdminSettings adminSettings = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true);
@ -1084,7 +1093,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popDeviceProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popDeviceProfileMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof DeviceProfileUpdateMsg deviceProfileUpdateMsg) { if (message instanceof DeviceProfileUpdateMsg deviceProfileUpdateMsg) {
DeviceProfile deviceProfile = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true); DeviceProfile deviceProfile = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true);
@ -1099,7 +1108,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popDeviceMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popDeviceMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof DeviceUpdateMsg deviceUpdateMsg) { if (message instanceof DeviceUpdateMsg deviceUpdateMsg) {
Device device = JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true); Device device = JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true);
@ -1114,7 +1123,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popDeviceCredentialsMsg(List<AbstractMessage> messages, DeviceId deviceId) { private boolean popDeviceCredentialsMsg(Deque<AbstractMessage> messages, DeviceId deviceId) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { if (message instanceof DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) {
DeviceCredentials deviceCredentials = JacksonUtil.fromString(deviceCredentialsUpdateMsg.getEntity(), DeviceCredentials.class, true); DeviceCredentials deviceCredentials = JacksonUtil.fromString(deviceCredentialsUpdateMsg.getEntity(), DeviceCredentials.class, true);
@ -1128,7 +1137,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popAssetProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popAssetProfileMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AssetProfileUpdateMsg assetProfileUpdateMsg) { if (message instanceof AssetProfileUpdateMsg assetProfileUpdateMsg) {
AssetProfile assetProfile = JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true); AssetProfile assetProfile = JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true);
@ -1143,7 +1152,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popAssetMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popAssetMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AssetUpdateMsg assetUpdateMsg) { if (message instanceof AssetUpdateMsg assetUpdateMsg) {
Asset asset = JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true); Asset asset = JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true);
@ -1158,7 +1167,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popUserCredentialsMsg(List<AbstractMessage> messages, UserId userId) { private boolean popUserCredentialsMsg(Deque<AbstractMessage> messages, UserId userId) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof UserCredentialsUpdateMsg userCredentialsUpdateMsg) { if (message instanceof UserCredentialsUpdateMsg userCredentialsUpdateMsg) {
UserCredentials userCredentials = JacksonUtil.fromString(userCredentialsUpdateMsg.getEntity(), UserCredentials.class, true); UserCredentials userCredentials = JacksonUtil.fromString(userCredentialsUpdateMsg.getEntity(), UserCredentials.class, true);
@ -1172,7 +1181,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popUserMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String email, Authority authority) { private boolean popUserMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String email, Authority authority) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof UserUpdateMsg userUpdateMsg) { if (message instanceof UserUpdateMsg userUpdateMsg) {
User user = JacksonUtil.fromString(userUpdateMsg.getEntity(), User.class, true); User user = JacksonUtil.fromString(userUpdateMsg.getEntity(), User.class, true);
@ -1188,7 +1197,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popCustomerMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String title) { private boolean popCustomerMsg(Deque<AbstractMessage> messages, UpdateMsgType msgType, String title) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof CustomerUpdateMsg customerUpdateMsg) { if (message instanceof CustomerUpdateMsg customerUpdateMsg) {
Customer customer = JacksonUtil.fromString(customerUpdateMsg.getEntity(), Customer.class, true); Customer customer = JacksonUtil.fromString(customerUpdateMsg.getEntity(), Customer.class, true);
@ -1203,7 +1212,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popTenantMsg(List<AbstractMessage> messages, TenantId tenantId1) { private boolean popTenantMsg(Deque<AbstractMessage> messages, TenantId tenantId1) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof TenantUpdateMsg tenantUpdateMsg) { if (message instanceof TenantUpdateMsg tenantUpdateMsg) {
Tenant tenant = JacksonUtil.fromString(tenantUpdateMsg.getEntity(), Tenant.class, true); Tenant tenant = JacksonUtil.fromString(tenantUpdateMsg.getEntity(), Tenant.class, true);
@ -1218,7 +1227,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popTenantProfileMsg(List<AbstractMessage> messages, TenantProfileId tenantProfileId) { private boolean popTenantProfileMsg(Deque<AbstractMessage> messages, TenantProfileId tenantProfileId) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof TenantProfileUpdateMsg tenantProfileUpdateMsg) { if (message instanceof TenantProfileUpdateMsg tenantProfileUpdateMsg) {
TenantProfile tenantProfile = JacksonUtil.fromString(tenantProfileUpdateMsg.getEntity(), TenantProfile.class, true); TenantProfile tenantProfile = JacksonUtil.fromString(tenantProfileUpdateMsg.getEntity(), TenantProfile.class, true);
@ -1233,7 +1242,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false; return false;
} }
private boolean popSyncCompletedMsg(List<AbstractMessage> messages) { private boolean popSyncCompletedMsg(Deque<AbstractMessage> messages) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof SyncCompletedMsg) { if (message instanceof SyncCompletedMsg) {
messages.remove(message); messages.remove(message);

14
application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java

@ -38,6 +38,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
@ -88,6 +90,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_PORT;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_URL;
@TestPropertySource(properties = { @TestPropertySource(properties = {
"transport.mqtt.enabled=true", "transport.mqtt.enabled=true",
@ -97,6 +101,12 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ContextConfiguration(classes = {EntityViewControllerTest.Config.class}) @ContextConfiguration(classes = {EntityViewControllerTest.Config.class})
@DaoSqlTest @DaoSqlTest
public class EntityViewControllerTest extends AbstractControllerTest { public class EntityViewControllerTest extends AbstractControllerTest {
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("transport.mqtt.bind_port = {}", MQTT_PORT);
registry.add("transport.mqtt.bind_port", () -> MQTT_PORT);
}
static final TypeReference<PageData<EntityView>> PAGE_DATA_ENTITY_VIEW_TYPE_REF = new TypeReference<>() { static final TypeReference<PageData<EntityView>> PAGE_DATA_ENTITY_VIEW_TYPE_REF = new TypeReference<>() {
}; };
static final TypeReference<PageData<EntityViewInfo>> PAGE_DATA_ENTITY_VIEW_INFO_TYPE_REF = new TypeReference<>() { static final TypeReference<PageData<EntityViewInfo>> PAGE_DATA_ENTITY_VIEW_INFO_TYPE_REF = new TypeReference<>() {
@ -683,7 +693,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
String viewDeviceId = testDevice.getId().getId().toString(); String viewDeviceId = testDevice.getId().getId().toString();
String clientId = MqttAsyncClient.generateClientId(); String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken); options.setUserName(accessToken);
@ -734,7 +744,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
assertNotNull(accessToken); assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId(); String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken); options.setUserName(accessToken);

14
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -27,7 +27,10 @@ import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.TestSocketUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
@ -118,6 +121,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
}) })
@Slf4j @Slf4j
abstract public class AbstractEdgeTest extends AbstractControllerTest { abstract public class AbstractEdgeTest extends AbstractControllerTest {
public static final String EDGE_HOST = "localhost";
public static final int EDGE_PORT = TestSocketUtils.findAvailableTcpPort();
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.debug("edges.rpc.port = {}", EDGE_PORT);
registry.add("edges.rpc.port", () -> EDGE_PORT);
}
public static final Integer CONNECT_MESSAGE_COUNT = 17; public static final Integer CONNECT_MESSAGE_COUNT = 17;
public static final Integer INSTALLATION_MESSAGE_COUNT = 8; public static final Integer INSTALLATION_MESSAGE_COUNT = 8;
public static final Integer SYNC_MESSAGE_COUNT = CONNECT_MESSAGE_COUNT + INSTALLATION_MESSAGE_COUNT; public static final Integer SYNC_MESSAGE_COUNT = CONNECT_MESSAGE_COUNT + INSTALLATION_MESSAGE_COUNT;
@ -144,7 +156,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
//8 installation messages //8 installation messages
installation(); installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret());
// 17 connect messages + 8 installation messages // 17 connect messages + 8 installation messages
edgeImitator.expectMessageAmount(SYNC_MESSAGE_COUNT); edgeImitator.expectMessageAmount(SYNC_MESSAGE_COUNT);
edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class); edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class);

10
application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java

@ -22,10 +22,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.protobuf.AbstractMessage; import com.google.protobuf.AbstractMessage;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
@ -84,12 +87,19 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.gen.edge.v1.UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE; import static org.thingsboard.server.gen.edge.v1.UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_PORT;
@TestPropertySource(properties = { @TestPropertySource(properties = {
"transport.mqtt.enabled=true" "transport.mqtt.enabled=true"
}) })
@Slf4j
@DaoSqlTest @DaoSqlTest
public class DeviceEdgeTest extends AbstractEdgeTest { public class DeviceEdgeTest extends AbstractEdgeTest {
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("transport.mqtt.bind_port = {}", MQTT_PORT);
registry.add("transport.mqtt.bind_port", () -> MQTT_PORT);
}
private static final String DEFAULT_DEVICE_TYPE = "default"; private static final String DEFAULT_DEVICE_TYPE = "default";

41
application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2026 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.server.edge;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
public class EdgeLatestVersionTest {
@Test
public void edgeLatestVersionIsSynchronizedTest() {
EdgeVersion currentHighestEdgeVersion = EdgeVersionComparator.getNewestEdgeVersion();
String projectVersion = EdgeLatestVersionTest.class.getPackage().getImplementationVersion();
if (projectVersion == null || projectVersion.isBlank()) {
projectVersion = System.getProperty("project.version", "UNKNOWN");
}
String projectVersionDigits = projectVersion.replaceAll("\\D", "");
String currentHighestEdgeVersionDigits = currentHighestEdgeVersion.name().replaceAll("\\D", "");
String msg = "EdgeVersion enum in edge.proto is out of sync. Please add respective " + projectVersionDigits + " to EdgeVersion";
Assert.assertEquals(msg, projectVersionDigits, currentHighestEdgeVersionDigits);
}
}

19
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -69,10 +69,12 @@ import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -103,8 +105,17 @@ public class EdgeImitator {
@Getter @Getter
private EdgeConfiguration configuration; private EdgeConfiguration configuration;
@Getter private final ConcurrentLinkedDeque<AbstractMessage> downlinkMsgs;
private final List<AbstractMessage> downlinkMsgs;
//Returns collection copy as Unmodifiable list
//This addressing the issue: DeviceEdgeTest>AbstractEdgeTest.setupEdgeTest:212->AbstractEdgeTest.verifyEdgeConnectionAndInitialData:306->AbstractEdgeTest.validateMsgsCnt:387 » ConcurrentModification
public List<AbstractMessage> getDownlinkMsgs() {
return downlinkMsgs.stream().toList();
}
public Deque<AbstractMessage> getDownlinkMsgsDeque() {
return downlinkMsgs;
}
@Getter @Getter
private UplinkResponseMsg latestResponseMsg; private UplinkResponseMsg latestResponseMsg;
@ -113,7 +124,7 @@ public class EdgeImitator {
edgeRpcClient = new EdgeGrpcClient(); edgeRpcClient = new EdgeGrpcClient();
messagesLatch = new CountDownLatch(0); messagesLatch = new CountDownLatch(0);
responsesLatch = new CountDownLatch(0); responsesLatch = new CountDownLatch(0);
downlinkMsgs = new ArrayList<>(); downlinkMsgs = new ConcurrentLinkedDeque<>();
ignoredTypes = new ArrayList<>(); ignoredTypes = new ArrayList<>();
this.routingKey = routingKey; this.routingKey = routingKey;
this.routingSecret = routingSecret; this.routingSecret = routingSecret;
@ -458,7 +469,7 @@ public class EdgeImitator {
} }
public AbstractMessage getLatestMessage() { public AbstractMessage getLatestMessage() {
return downlinkMsgs.get(downlinkMsgs.size() - 1); return downlinkMsgs.peekLast();
} }
public void ignoreType(Class<? extends AbstractMessage> type) { public void ignoreType(Class<? extends AbstractMessage> type) {

28
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -238,12 +238,12 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.set("createAlarm", BooleanNode.TRUE); .set("createAlarm", BooleanNode.TRUE);
postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, attr.toString()); postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, attr.toString());
await().atMost(10, TimeUnit.SECONDS) await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType) != null); .until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType) != null);
Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType); Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
await().atMost(15, TimeUnit.SECONDS) await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> clients.values().stream().allMatch(client -> client.getLastDataUpdate() != null)); .until(() -> clients.values().stream().allMatch(client -> client.getLastDataUpdate() != null));
clients.forEach((expectedDelay, wsClient) -> { clients.forEach((expectedDelay, wsClient) -> {
Notification notification = wsClient.getLastDataUpdate().getUpdate(); Notification notification = wsClient.getLastDataUpdate().getUpdate();
@ -291,8 +291,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
alarm.setOriginator(device.getId()); alarm.setOriginator(device.getId());
alarm = doPost("/api/alarm", alarm, Alarm.class); alarm = doPost("/api/alarm", alarm, Alarm.class);
await().atMost(15, TimeUnit.SECONDS) await().atMost(TIMEOUT, TimeUnit.SECONDS)
.pollDelay(2, TimeUnit.SECONDS) .pollDelay(1, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
List<Notification> notifications = getMyNotifications(false, 10); List<Notification> notifications = getMyNotifications(false, 10);
assertThat(notifications).singleElement().matches(notification -> { assertThat(notifications).singleElement().matches(notification -> {
@ -345,7 +345,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.set("createAlarm", BooleanNode.TRUE); .set("createAlarm", BooleanNode.TRUE);
postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, attr.toString()); postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, attr.toString());
await().atMost(10, TimeUnit.SECONDS) await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType) != null); .until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType) != null);
Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType); Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType);
getWsClient().waitForUpdate(true); getWsClient().waitForUpdate(true);
@ -355,14 +355,14 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(notification.getInfo()).asInstanceOf(type(AlarmNotificationInfo.class)) assertThat(notification.getInfo()).asInstanceOf(type(AlarmNotificationInfo.class))
.extracting(AlarmNotificationInfo::getAlarmId).isEqualTo(alarm.getUuidId()); .extracting(AlarmNotificationInfo::getAlarmId).isEqualTo(alarm.getUuidId());
await().atMost(10, TimeUnit.SECONDS).until(() -> findNotificationRequests(EntityType.ALARM).getTotalElements() == escalationTable.size()); await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> findNotificationRequests(EntityType.ALARM).getTotalElements() == escalationTable.size());
NotificationRequestInfo scheduledNotificationRequest = findNotificationRequests(EntityType.ALARM).getData().stream() NotificationRequestInfo scheduledNotificationRequest = findNotificationRequests(EntityType.ALARM).getData().stream()
.filter(NotificationRequest::isScheduled) .filter(NotificationRequest::isScheduled)
.findFirst().orElse(null); .findFirst().orElse(null);
assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo()); assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo());
alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), System.currentTimeMillis(), null); alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), System.currentTimeMillis(), null);
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty(); assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty();
}); });
} }
@ -480,7 +480,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
} }
loginTenantAdmin(); loginTenantAdmin();
List<Notification> notifications = await().atMost(15, TimeUnit.SECONDS) List<Notification> notifications = await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> getMyNotifications(true, 10).stream() .until(() -> getMyNotifications(true, 10).stream()
.filter(notification -> notification.getType() == NotificationType.RATE_LIMITS) .filter(notification -> notification.getType() == NotificationType.RATE_LIMITS)
.collect(Collectors.toList()), list -> list.size() == 3); .collect(Collectors.toList()), list -> list.size() == 3);
@ -500,7 +500,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
}); });
loginSysAdmin(); loginSysAdmin();
notifications = await().atMost(15, TimeUnit.SECONDS) notifications = await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> getMyNotifications(true, 10).stream() .until(() -> getMyNotifications(true, 10).stream()
.filter(notification -> notification.getType() == NotificationType.RATE_LIMITS) .filter(notification -> notification.getType() == NotificationType.RATE_LIMITS)
.collect(Collectors.toList()), list -> list.size() == 1); .collect(Collectors.toList()), list -> list.size() == 1);
@ -721,7 +721,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.updateInfo(new UpdateMessage(true, "CHANGED", "test", .updateInfo(new UpdateMessage(true, "CHANGED", "test",
"test", "test", "test")) "test", "test", "test"))
.build()); .build());
await().atMost(5, TimeUnit.SECONDS) await().atMost(TIMEOUT, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(2); assertThat(getMyNotifications(false, 100)).size().isEqualTo(2);
}); });
@ -809,7 +809,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
method.invoke(systemInfoService); method.invoke(systemInfoService);
} }
await().atMost(10, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1); await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1);
Notification notification = getMyNotifications(false, 100).get(0); Notification notification = getMyNotifications(false, 100).get(0);
assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage"); assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage");
assertThat(notification.getText()).isEqualTo("RAM shortage"); assertThat(notification.getText()).isEqualTo("RAM shortage");
@ -836,7 +836,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.build()); .build());
TimeUnit.MILLISECONDS.sleep(300); TimeUnit.MILLISECONDS.sleep(300);
} }
await().atMost(10, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1); await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1);
Notification notification = getMyNotifications(false, 100).get(0); Notification notification = getMyNotifications(false, 100).get(0);
assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage"); assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage");
assertThat(notification.getText()).isEqualTo("RAM shortage"); assertThat(notification.getText()).isEqualTo("RAM shortage");
@ -848,7 +848,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.serviceType("serviceType") .serviceType("serviceType")
.serviceId("serviceId") .serviceId("serviceId")
.build()); .build());
await("").atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(getMyNotifications(false, 100)).size().isOne()); await("").atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> assertThat(getMyNotifications(false, 100)).size().isOne());
} }
@Test @Test
@ -887,7 +887,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
method.invoke(systemInfoService); method.invoke(systemInfoService);
await().atMost(10, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1); await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> getMyNotifications(false, 100).size() == 1);
Notification notification = getMyNotifications(false, 100).get(0); Notification notification = getMyNotifications(false, 100).get(0);
assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage"); assertThat(notification.getSubject()).isEqualTo("Warning: RAM shortage");
assertThat(notification.getText()).isEqualTo("RAM shortage"); assertThat(notification.getText()).isEqualTo("RAM shortage");

204
application/src/test/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityServiceTest.java

@ -0,0 +1,204 @@
/**
* Copyright © 2016-2026 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.server.service.security.system;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.settings.SecuritySettingsService;
import org.thingsboard.server.dao.user.UserService;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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;
@RunWith(MockitoJUnitRunner.class)
public class DefaultSystemSecurityServiceTest {
@Mock
private AdminSettingsService adminSettingsService;
@Mock
private BCryptPasswordEncoder encoder;
@Mock
private UserService userService;
@Mock
private MailService mailService;
@Mock
private AuditLogService auditLogService;
@Mock
private SecuritySettingsService securitySettingsService;
private DefaultSystemSecurityService systemSecurityService;
private TenantId tenantId;
private UserId userId;
private UserCredentials userCredentials;
private SecuritySettings securitySettings;
private String username;
private String password;
private String encodedPassword;
@Before
public void setUp() {
systemSecurityService = new DefaultSystemSecurityService(adminSettingsService, encoder, userService, mailService, auditLogService, securitySettingsService);
tenantId = TenantId.fromUUID(UUID.randomUUID());
userId = new UserId(UUID.randomUUID());
username = "tenant@example.com";
password = "correctPassword";
encodedPassword = "$2a$10$encodedPasswordHash";
userCredentials = new UserCredentials();
userCredentials.setUserId(userId);
userCredentials.setEnabled(true);
userCredentials.setPassword(encodedPassword);
userCredentials.setCreatedTime(System.currentTimeMillis());
securitySettings = new SecuritySettings();
securitySettings.setMaxFailedLoginAttempts(5);
securitySettings.setPasswordPolicy(new UserPasswordPolicy());
}
@Test
public void testValidateUserCredentials_successfulLogin() {
when(encoder.matches(password, encodedPassword)).thenReturn(true);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password);
verify(encoder).matches(password, encodedPassword);
verify(userService).resetFailedLoginAttempts(tenantId, userId);
verify(userService, never()).increaseFailedLoginAttempts(any(), any());
}
@Test
public void testValidateUserCredentials_wrongPassword_incrementsFailedAttempts() {
when(encoder.matches(password, encodedPassword)).thenReturn(false);
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(3);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(BadCredentialsException.class)
.hasMessageContaining("Authentication Failed");
verify(userService).increaseFailedLoginAttempts(tenantId, userId);
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false));
}
@Test
public void testValidateUserCredentials_wrongPassword_accountLocked() {
when(encoder.matches(password, encodedPassword)).thenReturn(false);
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(6);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(LockedException.class)
.hasMessageContaining("locked due to security policy");
verify(userService).increaseFailedLoginAttempts(tenantId, userId);
verify(userService).setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false);
}
@Test
public void testValidateUserCredentials_wrongPassword_exactlyAtThreshold_noLock() {
when(encoder.matches(password, encodedPassword)).thenReturn(false);
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(5);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(BadCredentialsException.class)
.hasMessageContaining("Authentication Failed");
verify(userService).increaseFailedLoginAttempts(tenantId, userId);
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false));
}
@Test
public void testValidateUserCredentials_correctPassword_disabledUser_throwsDisabledException() {
userCredentials.setEnabled(false);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(DisabledException.class)
.hasMessage("User is not active");
verify(encoder, never()).matches(any(), any());
verify(userService, never()).increaseFailedLoginAttempts(any(), any());
}
@Test
public void testValidateUserCredentials_wrongPassword_maxAttemptsDisabled() {
securitySettings.setMaxFailedLoginAttempts(null);
when(encoder.matches(password, encodedPassword)).thenReturn(false);
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(100);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(BadCredentialsException.class);
verify(userService).increaseFailedLoginAttempts(tenantId, userId);
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false));
}
@Test
public void testValidateUserCredentials_wrongPassword_maxAttemptsSetToZero() {
securitySettings.setMaxFailedLoginAttempts(0);
when(encoder.matches(password, encodedPassword)).thenReturn(false);
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(100);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(BadCredentialsException.class);
verify(userService).increaseFailedLoginAttempts(tenantId, userId);
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false));
}
@Test
public void testValidateUserCredentials_wrongPassword_withNotificationEmail() throws ThingsboardException {
String notificationEmail = "admin@example.com";
securitySettings.setUserLockoutNotificationEmail(notificationEmail);
when(encoder.matches(password, encodedPassword)).thenReturn(false);
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(6);
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings);
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password))
.isInstanceOf(LockedException.class);
verify(userService).setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false);
verify(mailService).sendAccountLockoutEmail(eq(username), eq(notificationEmail), eq(5));
}
}

5
application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java

@ -28,10 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public abstract class AbstractTransportIntegrationTest extends AbstractControllerTest { public abstract class AbstractTransportIntegrationTest extends AbstractControllerTest {
protected static final int DEFAULT_WAIT_TIMEOUT_SECONDS = 30; public static final int DEFAULT_WAIT_TIMEOUT_SECONDS = 30;
protected static final String MQTT_URL = "tcp://localhost:1883";
protected static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/";
protected static final AtomicInteger atomicInteger = new AtomicInteger(2); protected static final AtomicInteger atomicInteger = new AtomicInteger(2);

14
application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java

@ -16,7 +16,10 @@
package org.thingsboard.server.transport.coap; package org.thingsboard.server.transport.coap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.TestSocketUtils;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
@ -40,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadCo
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.transport.AbstractTransportIntegrationTest; import org.thingsboard.server.transport.AbstractTransportIntegrationTest;
import org.thingsboard.server.utils.PortFinder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -52,6 +56,16 @@ import static org.junit.Assert.assertNotNull;
@Slf4j @Slf4j
public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest { public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest {
public static final String COAP_HOST = "localhost";
public static final int COAP_PORT = PortFinder.findAvailableUdpPort();
public static final String COAP_BASE_URL = "coap://" + COAP_HOST + ":" + COAP_PORT + "/api/v1/";
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("coap.bind_port = {}", COAP_PORT);
registry.add("coap.bind_port", () -> COAP_PORT);
}
protected final byte[] EMPTY_PAYLOAD = new byte[0]; protected final byte[] EMPTY_PAYLOAD = new byte[0];
protected CoapTestClient client; protected CoapTestClient client;
protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +

3
application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java

@ -28,9 +28,10 @@ import org.thingsboard.server.common.msg.session.FeatureType;
import java.io.IOException; import java.io.IOException;
import static org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest.COAP_BASE_URL;
public class CoapTestClient { public class CoapTestClient {
private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/";
private static final long CLIENT_REQUEST_TIMEOUT = 60000L; private static final long CLIENT_REQUEST_TIMEOUT = 60000L;
private final CoapClient client; private final CoapClient client;

37
application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java

@ -21,6 +21,8 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.CoAP;
import org.junit.Assert; import org.junit.Assert;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
@ -37,10 +39,10 @@ import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
import org.thingsboard.server.transport.coap.x509.CertPrivateKey; import org.thingsboard.server.transport.coap.x509.CertPrivateKey;
import org.thingsboard.server.transport.coap.x509.CoapClientX509Test; import org.thingsboard.server.transport.coap.x509.CoapClientX509Test;
import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import org.thingsboard.server.transport.coap.CoapTestConfigProperties;
import org.thingsboard.server.utils.PortFinder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.ServerSocket;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -51,9 +53,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -70,7 +70,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
"transport.coap.enabled=true", "transport.coap.enabled=true",
}) })
public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIntegrationTest { public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIntegrationTest {
private static final String COAPS_BASE_URL = "coaps://localhost:5684/api/v1/";
public static final String COAPS_HOST = "localhost";
public static final int COAPS_PORT = PortFinder.findAvailableUdpPort();
public static final String COAPS_BASE_URL = "coaps://" + COAPS_HOST + ":" + COAPS_PORT + "/api/v1/";
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("coap.dtls.bind_port = {}", COAPS_PORT);
registry.add("coap.dtls.bind_port", () -> COAPS_PORT);
}
protected final String CREDENTIALS_PATH = "coap/credentials/"; protected final String CREDENTIALS_PATH = "coap/credentials/";
protected final String CREDENTIALS_PATH_CLIENT = CREDENTIALS_PATH + "client/"; protected final String CREDENTIALS_PATH_CLIENT = CREDENTIALS_PATH + "client/";
protected final String CREDENTIALS_PATH_CLIENT_CERT_PEM = CREDENTIALS_PATH_CLIENT + "cert.pem"; protected final String CREDENTIALS_PATH_CLIENT_CERT_PEM = CREDENTIALS_PATH_CLIENT + "cert.pem";
@ -160,13 +170,10 @@ public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIn
CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM); CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM);
CertPrivateKey certPrivateKey_01 = new CertPrivateKey(CREDENTIALS_PATH_CLIENT + "cert_01.pem", CertPrivateKey certPrivateKey_01 = new CertPrivateKey(CREDENTIALS_PATH_CLIENT + "cert_01.pem",
CREDENTIALS_PATH_CLIENT + "key_01.pem"); CREDENTIALS_PATH_CLIENT + "key_01.pem");
Integer fixedPort = getFreePort(); int fixedPort = PortFinder.findAvailableUdpPort();
CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey, CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey,
"CoapX509TrustNo_" + FeatureType.TELEMETRY.name(), deviceProfile.getId(), fixedPort); "CoapX509TrustNo_" + FeatureType.TELEMETRY.name(), deviceProfile.getId(), fixedPort);
clientX509.disconnect(); clientX509.disconnect();
await("Need to make port " + fixedPort + " free")
.atMost(40, TimeUnit.SECONDS)
.until(() -> isPortAvailable(fixedPort));
CoapClientX509Test clientX509_01 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey_01, CoapClientX509Test clientX509_01 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey_01,
"CoapX509TrustNo_" + FeatureType.TELEMETRY.name() + "_01", deviceProfile.getId(), "CoapX509TrustNo_" + FeatureType.TELEMETRY.name() + "_01", deviceProfile.getId(),
fixedPort, PAYLOAD_VALUES_STR_01); fixedPort, PAYLOAD_VALUES_STR_01);
@ -274,19 +281,5 @@ public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIn
} }
} }
private static int getFreePort() throws IOException {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
}
}
private static boolean isPortAvailable(int port) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
serverSocket.setReuseAddress(true);
return true;
} catch (IOException e) {
return false;
}
}
} }

14
application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java

@ -51,6 +51,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.awaitility.Awaitility.await;
import static org.eclipse.californium.core.config.CoapConfig.DEFAULT_BLOCKWISE_STATUS_LIFETIME_IN_SECONDS; import static org.eclipse.californium.core.config.CoapConfig.DEFAULT_BLOCKWISE_STATUS_LIFETIME_IN_SECONDS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_AUTO_HANDSHAKE_TIMEOUT; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_AUTO_HANDSHAKE_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CIPHER_SUITES; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CIPHER_SUITES;
@ -71,6 +72,8 @@ import static org.eclipse.californium.scandium.config.DtlsConfig.MODULE;
import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_ECDSA; import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_ECDSA;
import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_RSA; import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_RSA;
import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA384_WITH_ECDSA; import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA384_WITH_ECDSA;
import static org.thingsboard.server.transport.AbstractTransportIntegrationTest.DEFAULT_WAIT_TIMEOUT_SECONDS;
import static org.thingsboard.server.utils.PortFinder.isUDPPortAvailable;
@Slf4j @Slf4j
public class CoapClientX509Test { public class CoapClientX509Test {
@ -82,6 +85,7 @@ public class CoapClientX509Test {
private final Configuration config; private final Configuration config;
private final CertPrivateKey certPrivateKey; private final CertPrivateKey certPrivateKey;
private final String coapsBaseUrl; private final String coapsBaseUrl;
private final Integer fixedPort;
@Getter @Getter
private CoAP.Type type = CoAP.Type.CON; private CoAP.Type type = CoAP.Type.CON;
@ -90,6 +94,7 @@ public class CoapClientX509Test {
this.certPrivateKey = certPrivateKey; this.certPrivateKey = certPrivateKey;
this.coapsBaseUrl = coapsBaseUrl; this.coapsBaseUrl = coapsBaseUrl;
this.config = createConfiguration(); this.config = createConfiguration();
this.fixedPort = fixedPort;
this.dtlsConnector = createDTLSConnector(fixedPort); this.dtlsConnector = createDTLSConnector(fixedPort);
this.clientX509 = createClient(getFeatureTokenUrl(featureType)); this.clientX509 = createClient(getFeatureTokenUrl(featureType));
} }
@ -97,6 +102,15 @@ public class CoapClientX509Test {
if (clientX509 != null) { if (clientX509 != null) {
clientX509.shutdown(); clientX509.shutdown();
} }
if (dtlsConnector != null) {
dtlsConnector.destroy();
if (fixedPort != null) {
log.debug("Awaiting releasing UDP fixedPort {}", fixedPort);
await("Await client UDP port " + fixedPort + " to disconnect")
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> isUDPPortAvailable(fixedPort));
}
}
} }
public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException { public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException {

70
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import jnr.ffi.annotations.In;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.awaitility.core.ConditionTimeoutException; import org.awaitility.core.ConditionTimeoutException;
@ -38,6 +39,8 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
@ -84,9 +87,8 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceUpdateResult; import org.thingsboard.server.transport.lwm2m.server.client.ResourceUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import org.thingsboard.server.utils.PortFinder;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -141,21 +143,35 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
private LwM2mClientContext clientContextTest; private LwM2mClientContext clientContextTest;
// Lwm2m Server // Lwm2m Server
public static final int port = 5685; public static final String LWM2M_HOST = "localhost";
public static final int securityPort = 5686; public static final int LWM2M_PORT = PortFinder.findAvailableUdpPort(); // 5685
public static final int portBs = 5687; public static final int LWM2MS_PORT = PortFinder.findAvailableUdpPort(); //5686
public static final int securityPortBs = 5688;
public static final String LWM2M_BOOTSTRAP_HOST = "localhost";
public static final int LWM2M_BOOTSTRAP_PORT = PortFinder.findAvailableUdpPort(); // 5687
public static final int LWM2MS_BOOTSTRAP_PORT = PortFinder.findAvailableUdpPort(); // 5688
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("transport.lwm2m.server.bind_port = {}", LWM2M_PORT);
registry.add("transport.lwm2m.server.bind_port", () -> LWM2M_PORT);
log.warn("transport.lwm2m.server.security.bind_port = {}", LWM2MS_PORT);
registry.add("transport.lwm2m.server.security.bind_port", () -> LWM2MS_PORT);
log.warn("transport.lwm2m.bootstrap.bind_port = {}", LWM2M_BOOTSTRAP_PORT);
registry.add("transport.lwm2m.bootstrap.bind_port", () -> LWM2M_BOOTSTRAP_PORT);
log.warn("transport.lwm2m.bootstrap.security.bind_port = {}", LWM2MS_BOOTSTRAP_PORT);
registry.add("transport.lwm2m.bootstrap.security.bind_port", () -> LWM2MS_BOOTSTRAP_PORT);
}
public static final String host = "localhost";
public static final String hostBs = "localhost";
public static final Integer shortServerId = 123; public static final Integer shortServerId = 123;
public static final String COAP = "coap://"; public static final String COAP = "coap://";
public static final String COAPS = "coaps://"; public static final String COAPS = "coaps://";
public static final String URI = COAP + host + ":" + port; public static final String URI = COAP + LWM2M_HOST + ":" + LWM2M_PORT;
public static final String SECURE_URI = COAPS + host + ":" + securityPort; public static final String SECURE_URI = COAPS + LWM2M_HOST + ":" + LWM2MS_PORT;
public static final String URI_BS = COAP + hostBs + ":" + portBs; public static final String URI_BS = COAP + LWM2M_BOOTSTRAP_HOST + ":" + LWM2M_BOOTSTRAP_PORT;
public static final String SECURE_URI_BS = COAPS + hostBs + ":" + securityPortBs; public static final String SECURE_URI_BS = COAPS + LWM2M_BOOTSTRAP_HOST + ":" + LWM2MS_BOOTSTRAP_PORT;
public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId); public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId);
protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS = protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS =
@ -570,12 +586,11 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
this.clientDestroy(); this.clientDestroy();
lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources);
try (ServerSocket socket = new ServerSocket(0)) { int clientPort = PortFinder.findAvailableUdpPort();
int clientPort = socket.getLocalPort(); //automatic client port assignment
lwM2MTestClient.init(security, securityBs, clientPort, isRpc, lwM2MTestClient.init(security, securityBs, 0 /* clientPort */, isRpc,
this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest,
clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR, value3_0_9); clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR, value3_0_9);
}
lwM2MTestClient.setDeviceIdStr(deviceIdStr); lwM2MTestClient.setDeviceIdStr(deviceIdStr);
} }
@ -658,10 +673,9 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
try { try {
if (lwM2MTestClient != null && lwM2MTestClient.getLeshanClient() != null) { if (lwM2MTestClient != null && lwM2MTestClient.getLeshanClient() != null) {
boolean serverAlive = false; boolean serverAlive = false;
for (int port = AbstractLwM2MIntegrationTest.port; port <= securityPortBs; port++) { List<Integer> ports = List.of(LWM2M_PORT, LWM2MS_PORT, LWM2MS_BOOTSTRAP_PORT, LWM2MS_BOOTSTRAP_PORT);
try (ServerSocket socket = new ServerSocket(port)) { for (Integer port : ports) {
log.info("Port {} is free.", port); if (!PortFinder.isUDPPortAvailable(port)) {
} catch (IOException e) {
log.debug("Port {} is busy — CoAP server still active.", port); log.debug("Port {} is busy — CoAP server still active.", port);
serverAlive = true; serverAlive = true;
break; break;
@ -730,8 +744,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
bootstrapServerCredential.setServerPublicKey(""); bootstrapServerCredential.setServerPublicKey("");
bootstrapServerCredential.setShortServerId(isBootstrap ? null : shortServerId); bootstrapServerCredential.setShortServerId(isBootstrap ? null : shortServerId);
bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setBootstrapServerIs(isBootstrap);
bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setHost(isBootstrap ? LWM2M_BOOTSTRAP_HOST : LWM2M_HOST);
bootstrapServerCredential.setPort(isBootstrap ? portBs : port); bootstrapServerCredential.setPort(isBootstrap ? LWM2M_BOOTSTRAP_PORT : LWM2M_PORT);
return bootstrapServerCredential; return bootstrapServerCredential;
} }
@ -746,11 +760,15 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
return credentials; return credentials;
} }
protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception {
awaitObserveReadAll(cntObserve, deviceIdStr, "");
}
protected void awaitObserveReadAll(int cntObserve, String deviceIdStr, String assertionAlias) throws Exception {
try { try {
await("ObserveReadAll: countObserve " + cntObserve) await("ObserveReadAll: countObserve " + cntObserve)
.atMost(40, TimeUnit.SECONDS) .alias(assertionAlias)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> cntObserve == getCntObserveAll(deviceIdStr)); .until(() -> cntObserve == getCntObserveAll(deviceIdStr));
} catch (ConditionTimeoutException e) { } catch (ConditionTimeoutException e) {
int current = getCntObserveAll(deviceIdStr); int current = getCntObserveAll(deviceIdStr);

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java

@ -33,7 +33,6 @@ import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.dao.service.OtaPackageServiceTest.TARGET_FW_VERSION; import static org.thingsboard.server.dao.service.OtaPackageServiceTest.TARGET_FW_VERSION;
import static org.thingsboard.server.dao.service.OtaPackageServiceTest.TITLE; import static org.thingsboard.server.dao.service.OtaPackageServiceTest.TITLE;
@ -137,6 +136,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
@Override @Override
public void destroy() { public void destroy() {
scheduler.shutdownNow(); scheduler.shutdownNow();
this.leshanClient = null;
} }
private void startDownloading() { private void startDownloading() {

41
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -33,7 +33,6 @@ import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.object.Server;
import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter;
@ -82,6 +81,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
@ -92,6 +92,7 @@ import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder.getDefaultPathEncoder; import static org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder.getDefaultPathEncoder;
import static org.thingsboard.server.transport.AbstractTransportIntegrationTest.DEFAULT_WAIT_TIMEOUT_SECONDS;
import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerId; import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerId;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState;
@ -119,6 +120,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.lwm2mClientResources; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.lwm2mClientResources;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength;
import static org.thingsboard.server.utils.PortFinder.isUDPPortAvailable;
@Slf4j @Slf4j
@ -139,14 +141,16 @@ public class LwM2MTestClient {
private Map<LwM2MClientState, Integer> clientDtlsCid; private Map<LwM2MClientState, Integer> clientDtlsCid;
private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
private LwM2mClientContext clientContext; private LwM2mClientContext clientContext;
private LwM2mTemperatureSensor lwM2mTemperatureSensor12; private LwM2mTemperatureSensor lwM2MTemperatureSensor12;
private String deviceIdStr; private String deviceIdStr;
private int clientPort;
public void init(Security securityLwm2m, Security securityBs, int port, boolean isRpc, public void init(Security securityLwm2m, Security securityBs, int clientPort, boolean isRpc,
LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode, LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode,
boolean supportFormatOnly_SenMLJSON_SenMLCBOR, Integer value3_0_9) throws InvalidDDFFileException, IOException { boolean supportFormatOnly_SenMLJSON_SenMLCBOR, Integer value3_0_9) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", leshanClient); Assert.assertNull("client already initialized", leshanClient);
this.clientPort = clientPort;
this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler;
this.clientContext = clientContext; this.clientContext = clientContext;
@ -156,7 +160,7 @@ public class LwM2MTestClient {
if (securityLwm2m != null && securityLwm2m.getId() != null) { if (securityLwm2m != null && securityLwm2m.getId() != null) {
forceNullSecurityId(securityLwm2m); forceNullSecurityId(securityLwm2m);
} }
if (securityBs!= null && securityBs.getId() != null) { if (securityBs != null && securityBs.getId() != null) {
forceNullSecurityId(securityBs); forceNullSecurityId(securityBs);
} }
if (securityBs != null && securityLwm2m != null) { if (securityBs != null && securityLwm2m != null) {
@ -165,7 +169,7 @@ public class LwM2MTestClient {
} else if (securityBs != null) { } else if (securityBs != null) {
log.warn("Security BS only: securityBs: [{}] ", securityBs.getId()); log.warn("Security BS only: securityBs: [{}] ", securityBs.getId());
initializer.setInstancesForObject(SECURITY, securityBs); initializer.setInstancesForObject(SECURITY, securityBs);
} else if (securityLwm2m != null){ } else if (securityLwm2m != null) {
// SECURITY // SECURITY
log.warn("Security Lwm2m only: security Lwm2m [{}]", securityLwm2m.getId()); log.warn("Security Lwm2m only: security Lwm2m [{}]", securityLwm2m.getId());
initializer.setInstancesForObject(SECURITY, securityLwm2m); initializer.setInstancesForObject(SECURITY, securityLwm2m);
@ -186,8 +190,8 @@ public class LwM2MTestClient {
locationParams.getPos(); locationParams.getPos();
initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0));
LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0); LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0);
lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); lwM2MTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12);
initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2mTemperatureSensor12); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2MTemperatureSensor12);
List<LwM2mObjectEnabler> enablers = initializer.createAll(); List<LwM2mObjectEnabler> enablers = initializer.createAll();
@ -208,7 +212,9 @@ public class LwM2MTestClient {
builder.setSessionListener(new DtlsSessionLogger(clientStates, clientDtlsCid)); builder.setSessionListener(new DtlsSessionLogger(clientStates, clientDtlsCid));
return builder; return builder;
}; }
;
}; };
} }
}; };
@ -245,7 +251,7 @@ public class LwM2MTestClient {
// Set Californium Configuration // Set Californium Configuration
endpointsBuilder.setConfiguration(clientCoapConfig); endpointsBuilder.setConfiguration(clientCoapConfig);
endpointsBuilder.setClientAddress(new InetSocketAddress(port).getAddress()); endpointsBuilder.setClientAddress(new InetSocketAddress(clientPort).getAddress());
// creates EndpointsProvider // creates EndpointsProvider
@ -438,12 +444,14 @@ public class LwM2MTestClient {
destroySafe(swLwM2MDevice); destroySafe(swLwM2MDevice);
destroySafe(lwM2MBinaryAppDataContainer); destroySafe(lwM2MBinaryAppDataContainer);
destroySafe(lwM2MTemperatureSensor); destroySafe(lwM2MTemperatureSensor);
destroySafe(lwM2MTemperatureSensor12);
lwM2MDevice = null; lwM2MDevice = null;
fwLwM2MDevice = null; fwLwM2MDevice = null;
swLwM2MDevice = null; swLwM2MDevice = null;
lwM2MBinaryAppDataContainer = null; lwM2MBinaryAppDataContainer = null;
lwM2MTemperatureSensor = null; lwM2MTemperatureSensor = null;
lwM2MTemperatureSensor12 = null;
} }
@ -462,11 +470,17 @@ public class LwM2MTestClient {
public void start(boolean isStartLw) { public void start(boolean isStartLw) {
if (leshanClient != null) { if (leshanClient != null) {
if (clientPort > 0) {
log.error("Await UDP clientPort {} to be available before leshanClient.start()", clientPort);
await("Await UDP clientPort " + clientPort + " to be available before leshanClient.start()")
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> isUDPPortAvailable(clientPort));
}
leshanClient.start(); leshanClient.start();
if (isStartLw) { if (isStartLw) {
this.awaitClientAfterStartConnectLw(); this.awaitClientAfterStartConnectLw();
} }
lwM2mTemperatureSensor12.setLeshanClient(leshanClient); lwM2MTemperatureSensor12.setLeshanClient(leshanClient);
fwLwM2MDevice.setLeshanClient(leshanClient); fwLwM2MDevice.setLeshanClient(leshanClient);
} }
} }
@ -474,6 +488,12 @@ public class LwM2MTestClient {
public void stop(boolean deregister) { public void stop(boolean deregister) {
if (leshanClient != null) { if (leshanClient != null) {
leshanClient.stop(deregister); leshanClient.stop(deregister);
if (clientPort > 0) {
log.error("Await UDP clientPort {} to disconnect after leshanClient.stop(deregister)", clientPort);
await("Await client UDP port " + clientPort + " to disconnect after leshanClient.stop(deregister)")
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> isUDPPortAvailable(clientPort));
}
} }
} }
@ -535,5 +555,6 @@ public class LwM2MTestClient {
log.error("[forceNullSecurityId] Failed to set id=null for {}", security.getClass(), e); log.error("[forceNullSecurityId] Failed to set id=null for {}", security.getClass(), e);
} }
} }
} }

1
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java

@ -169,6 +169,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
@Override @Override
public void destroy() { public void destroy() {
this.leshanClient = null;
} }
private void sendCollected() { private void sendCollected() {

4
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java

@ -366,8 +366,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M
} }
bootstrapServerCredential.setShortServerId(isBootstrap ? null : shortServerId); bootstrapServerCredential.setShortServerId(isBootstrap ? null : shortServerId);
bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setBootstrapServerIs(isBootstrap);
bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setHost(isBootstrap ? LWM2M_BOOTSTRAP_HOST : LWM2M_HOST);
bootstrapServerCredential.setPort(isBootstrap ? securityPortBs : securityPort); bootstrapServerCredential.setPort(isBootstrap ? LWM2MS_BOOTSTRAP_PORT : LWM2MS_PORT);
return bootstrapServerCredential; return bootstrapServerCredential;
} }

4
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java

@ -133,7 +133,7 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes
ON_REGISTRATION_SUCCESS, ON_REGISTRATION_SUCCESS,
true); true);
awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString()); awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString(), "before client stops for the first time");
lwM2MTestClient.stop(true); lwM2MTestClient.stop(true);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + lwm2mDevice.getDeviceProfileId().getId().toString(), DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + lwm2mDevice.getDeviceProfileId().getId().toString(), DeviceProfile.class);
@ -143,7 +143,7 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes
Assert.assertNotNull(lwm2mDeviceProfileManyParams); Assert.assertNotNull(lwm2mDeviceProfileManyParams);
lwM2MTestClient.start(true); lwM2MTestClient.start(true);
awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString()); awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString(), "second after client restart");
awaitUpdateReg(3); awaitUpdateReg(3);
} }

13
application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java

@ -19,7 +19,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.TestSocketUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
@ -58,6 +61,16 @@ import static org.junit.Assert.assertNotNull;
@Slf4j @Slf4j
public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest { public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest {
public static final String MQTT_HOST = "localhost";
public static final int MQTT_PORT = TestSocketUtils.findAvailableTcpPort();
public static final String MQTT_URL = "tcp://" + MQTT_HOST + ":" + MQTT_PORT;
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("transport.mqtt.bind_port = {}", MQTT_PORT);
registry.add("transport.mqtt.bind_port", () -> MQTT_PORT);
}
protected Device savedGateway; protected Device savedGateway;
protected String gatewayAccessToken; protected String gatewayAccessToken;

10
application/src/test/java/org/thingsboard/server/transport/mqtt/MqttGatewayRateLimitsTest.java

@ -16,12 +16,15 @@
package org.thingsboard.server.transport.mqtt; package org.thingsboard.server.transport.mqtt;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -43,13 +46,20 @@ import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY; import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY;
import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE; import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_PORT;
@DaoSqlTest @DaoSqlTest
@TestPropertySource(properties = { @TestPropertySource(properties = {
"service.integrations.supported=ALL", "service.integrations.supported=ALL",
"transport.mqtt.enabled=true", "transport.mqtt.enabled=true",
}) })
@Slf4j
public class MqttGatewayRateLimitsTest extends AbstractControllerTest { public class MqttGatewayRateLimitsTest extends AbstractControllerTest {
@DynamicPropertySource
static void props(DynamicPropertyRegistry registry) {
log.warn("transport.mqtt.bind_port = {}", MQTT_PORT);
registry.add("transport.mqtt.bind_port", () -> MQTT_PORT);
}
private static final String GATEWAY_TOPIC = "v1/gateway/telemetry"; private static final String GATEWAY_TOPIC = "v1/gateway/telemetry";
private static final String DEVICE_TOPIC = "v1/devices/me/telemetry"; private static final String DEVICE_TOPIC = "v1/devices/me/telemetry";

6
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/MqttTestClient.java

@ -27,12 +27,16 @@ import org.thingsboard.server.common.data.StringUtils;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_HOST;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_PORT;
public class MqttTestClient { public class MqttTestClient {
private static final String MQTT_URL = "tcp://localhost:1883";
private static final int TIMEOUT = 30; // seconds private static final int TIMEOUT = 30; // seconds
public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT); public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT);
private String MQTT_URL = "tcp://" + MQTT_HOST + ":" + MQTT_PORT;
private final MqttAsyncClient client; private final MqttAsyncClient client;
public void setCallback(MqttTestCallback callback) { public void setCallback(MqttTestCallback callback) {

6
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/MqttV5TestClient.java

@ -27,12 +27,16 @@ import org.thingsboard.server.common.data.StringUtils;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_HOST;
import static org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest.MQTT_PORT;
public class MqttV5TestClient { // We should copy part of MqttV3TestClient, due to different package names in import public class MqttV5TestClient { // We should copy part of MqttV3TestClient, due to different package names in import
private static final String MQTT_URL = "tcp://localhost:1883";
private static final int TIMEOUT = 30; // seconds private static final int TIMEOUT = 30; // seconds
private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT); private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT);
private String MQTT_URL = "tcp://" + MQTT_HOST + ":" + MQTT_PORT;
private final MqttAsyncClient client; private final MqttAsyncClient client;
public void setCallback(MqttCallback callback) { public void setCallback(MqttCallback callback) {

42
application/src/test/java/org/thingsboard/server/utils/PortFinder.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2026 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.server.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.SocketException;
@Slf4j
public class PortFinder {
public static int findAvailableUdpPort() {
try (DatagramSocket socket = new DatagramSocket(0)) {
return socket.getLocalPort();
} catch (SocketException e) {
throw new IllegalStateException("No available UDP ports found", e);
}
}
public static boolean isUDPPortAvailable(int port) {
try (DatagramSocket socket = new DatagramSocket(port)) {
return true;
} catch (IOException e) {
log.debug("Failed to open UDP port {}", port, e);
return false;
}
}
}

1
application/src/test/resources/application-test.properties

@ -1,3 +1,4 @@
spring.main.banner-mode=off
js.evaluator=mock js.evaluator=mock
transport.lwm2m.server.security.credentials.enabled=true transport.lwm2m.server.security.credentials.enabled=true
transport.lwm2m.server.security.credentials.type=KEYSTORE transport.lwm2m.server.security.credentials.type=KEYSTORE

6
application/src/test/resources/logback-test.xml

@ -42,6 +42,12 @@
<!-- <logger name="org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mRedisRegistrationStore" level="TRACE"/>--> <!-- <logger name="org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mRedisRegistrationStore" level="TRACE"/>-->
<!-- <logger name="org.thingsboard.server.transport.lwm2m.security.diffPort" level="TRACE"/>--> <!-- <logger name="org.thingsboard.server.transport.lwm2m.security.diffPort" level="TRACE"/>-->
<!-- Avoiding multiple logs like: The cache 'relations' is not recording statistics. No meters except 'cache.size' will be registered. Call 'Caffeine#recordStats()' prior to building the cache for metrics to be recorded.-->
<logger name="io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics" level="ERROR"/>
<!-- Log MvcMock result with status >=400 -->
<logger name="org.springframework.test.web.servlet.result" level="DEBUG"/>
<root level="WARN"> <root level="WARN">
<appender-ref ref="console"/> <appender-ref ref="console"/>
</root> </root>

2
common/actor/pom.xml

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

2
common/cache/pom.xml

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

3
common/cache/src/test/resources/logback-test.xml

@ -9,6 +9,9 @@
<logger name="org.thingsboard.server.cache" level="TRACE"/> <logger name="org.thingsboard.server.cache" level="TRACE"/>
<!-- Avoiding multiple logs like: The cache 'relations' is not recording statistics. No meters except 'cache.size' will be registered. Call 'Caffeine#recordStats()' prior to building the cache for metrics to be recorded.-->
<logger name="io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics" level="ERROR"/>
<root level="INFO"> <root level="INFO">
<appender-ref ref="console"/> <appender-ref ref="console"/>
</root> </root>

2
common/cluster-api/pom.xml

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

2
common/coap-server/pom.xml

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

2
common/dao-api/pom.xml

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

2
common/data/pom.xml

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

2
common/discovery-api/pom.xml

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

7
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>4.3.0-RC</version> <version>4.3.0.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
@ -106,6 +106,11 @@
<groupId>com.google.protobuf</groupId> <groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId> <artifactId>protobuf-java</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

11
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -143,16 +143,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
} }
public static EdgeVersion getNewestEdgeVersion() { public static EdgeVersion getNewestEdgeVersion() {
EdgeVersion newest = null; return EdgeVersionComparator.getNewestEdgeVersion();
for (EdgeVersion v : EdgeVersion.values()) {
if (v == EdgeVersion.V_LATEST || v == EdgeVersion.UNRECOGNIZED) {
continue;
}
if (newest == null || v.getNumber() > newest.getNumber()) {
newest = v;
}
}
return newest;
} }
private StreamObserver<ResponseMsg> initOutputStream(String edgeKey, private StreamObserver<ResponseMsg> initOutputStream(String edgeKey,

86
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2026 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.edge.rpc;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import java.util.Comparator;
public class EdgeVersionComparator implements Comparator<EdgeVersion> {
public static final EdgeVersionComparator INSTANCE = new EdgeVersionComparator();
@Override
public int compare(EdgeVersion v1, EdgeVersion v2) {
if (v1 == v2) {
return 0;
}
// UNRECOGNIZED is less than any other version
if (v1 == EdgeVersion.UNRECOGNIZED) {
return -1;
}
if (v2 == EdgeVersion.UNRECOGNIZED) {
return 1;
}
// V_LATEST is treated as the newest version
if (v1 == EdgeVersion.V_LATEST) {
v1 = getNewestEdgeVersion();
}
if (v2 == EdgeVersion.V_LATEST) {
v2 = getNewestEdgeVersion();
}
return compareVersionParts(parseVersionParts(v1), parseVersionParts(v2));
}
public static EdgeVersion getNewestEdgeVersion() {
EdgeVersion newest = null;
for (EdgeVersion v : EdgeVersion.values()) {
if (v == EdgeVersion.V_LATEST || v == EdgeVersion.UNRECOGNIZED) {
continue;
}
if (newest == null || INSTANCE.compare(v, newest) > 0) {
newest = v;
}
}
return newest;
}
private static int[] parseVersionParts(EdgeVersion version) {
String name = version.name();
if (name.startsWith("V_")) {
name = name.substring(2);
}
String[] parts = name.split("_");
int[] result = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
result[i] = Integer.parseInt(parts[i]);
}
return result;
}
private static int compareVersionParts(int[] a, int[] b) {
int maxLen = Math.max(a.length, b.length);
for (int i = 0; i < maxLen; i++) {
int partA = i < a.length ? a[i] : 0;
int partB = i < b.length ? b[i] : 0;
if (partA != partB) {
return Integer.compare(partA, partB);
}
}
return 0;
}
}

2
common/edge-api/src/main/proto/edge.proto

@ -45,6 +45,8 @@ enum EdgeVersion {
V_4_1_0 = 11; V_4_1_0 = 11;
V_4_2_0 = 12; V_4_2_0 = 12;
V_4_3_0 = 13; V_4_3_0 = 13;
V_4_2_1_2 = 14;
V_4_3_0_1 = 15;
V_LATEST = 999; V_LATEST = 999;
} }

102
common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java

@ -0,0 +1,102 @@
/**
* Copyright © 2016-2026 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.edge.rpc;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import static org.assertj.core.api.Assertions.assertThat;
class EdgeVersionComparatorTest {
@Test
void compare_sameVersion_returnsZero() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_3_3_0)).isEqualTo(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_4_0_0)).isEqualTo(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_1_2, EdgeVersion.V_4_2_1_2)).isEqualTo(0);
}
@Test
void compare_majorVersionDifference_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_4_0_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_3_3_0)).isGreaterThan(0);
}
@Test
void compare_minorVersionDifference_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_3_6_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_0, EdgeVersion.V_3_3_0)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_4_1_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_1_0, EdgeVersion.V_4_0_0)).isGreaterThan(0);
}
@Test
void compare_patchVersionDifference_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_0, EdgeVersion.V_3_6_1)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_1, EdgeVersion.V_3_6_0)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_1, EdgeVersion.V_3_6_2)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_2, EdgeVersion.V_3_6_4)).isLessThan(0);
}
@Test
void compare_fourPartVersion_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_0, EdgeVersion.V_4_2_1_2)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_1_2, EdgeVersion.V_4_2_0)).isGreaterThan(0);
}
@Test
void compare_threePartVsFourPart_treatsImplicitZero() {
// V_4_2_0 should be less than V_4_2_1_2 (4.2.0.0 < 4.2.1.2)
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_0, EdgeVersion.V_4_2_1_2)).isLessThan(0);
}
@Test
void getNewestEdgeVersion_excludesLatestAndUnrecognized() {
EdgeVersion newest = EdgeVersionComparator.getNewestEdgeVersion();
assertThat(newest).isNotNull();
assertThat(newest).isNotEqualTo(EdgeVersion.V_LATEST);
assertThat(newest).isNotEqualTo(EdgeVersion.UNRECOGNIZED);
}
@Test
void compare_vLatest_treatedAsNewestVersion() {
EdgeVersion newest = EdgeVersionComparator.getNewestEdgeVersion();
// V_LATEST equals the newest version
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, newest)).isEqualTo(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(newest, EdgeVersion.V_LATEST)).isEqualTo(0);
// V_LATEST is greater than older versions
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.V_3_3_0)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_LATEST)).isLessThan(0);
}
@Test
void compare_vLatest_withItself_returnsZero() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.V_LATEST)).isEqualTo(0);
}
@Test
void compare_unrecognized_isLessThanAnyVersion() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.V_3_3_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.V_LATEST)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.UNRECOGNIZED)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.UNRECOGNIZED)).isGreaterThan(0);
}
@Test
void compare_unrecognized_withItself_returnsZero() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.UNRECOGNIZED)).isEqualTo(0);
}
}

2
common/edqs/pom.xml

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

3
common/edqs/src/main/java/org/thingsboard/server/edqs/util/TbRocksDb.java

@ -16,6 +16,7 @@
package org.thingsboard.server.edqs.util; package org.thingsboard.server.edqs.util;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.rocksdb.Options; import org.rocksdb.Options;
import org.rocksdb.RocksDB; import org.rocksdb.RocksDB;
import org.rocksdb.RocksIterator; import org.rocksdb.RocksIterator;
@ -26,6 +27,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@Slf4j
public class TbRocksDb { public class TbRocksDb {
protected final String path; protected final String path;
@ -45,6 +47,7 @@ public class TbRocksDb {
@SneakyThrows @SneakyThrows
public void init() { public void init() {
log.debug("RocksDB init in {}", path);
Files.createDirectories(Path.of(path).getParent()); Files.createDirectories(Path.of(path).getParent());
db = RocksDB.open(dbOptions, path); db = RocksDB.open(dbOptions, path);
} }

2
common/message/pom.xml

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

2
common/pom.xml

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

2
common/proto/pom.xml

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

2
common/queue/pom.xml

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

2
common/script/pom.xml

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

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

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

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

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

2
common/stats/pom.xml

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

2
common/transport/coap/pom.xml

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

2
common/transport/http/pom.xml

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

2
common/transport/lwm2m/pom.xml

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

2
common/transport/mqtt/pom.xml

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

2
common/transport/pom.xml

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

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>4.3.0-RC</version> <version>4.3.0.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>

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

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

2
common/util/pom.xml

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

2
common/version-control/pom.xml

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

2
dao/pom.xml

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

7
dao/src/main/java/org/thingsboard/server/dao/resource/DefaultTbResourceDataCache.java

@ -19,6 +19,7 @@ import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.FluentFuture;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -55,6 +56,12 @@ public class DefaultTbResourceDataCache implements TbResourceDataCache {
.buildAsync((key, executor) -> CompletableFuture.supplyAsync(() -> resourceService.getResourceDataInfo(key.tenantId(), key.resourceId()), executor)); .buildAsync((key, executor) -> CompletableFuture.supplyAsync(() -> resourceService.getResourceDataInfo(key.tenantId(), key.resourceId()), executor));
} }
@PreDestroy
private void destroy() {
cache.synchronous().invalidateAll();
cache = null;
}
@Override @Override
public FluentFuture<TbResourceDataInfo> getResourceDataInfoAsync(TenantId tenantId, TbResourceId resourceId) { public FluentFuture<TbResourceDataInfo> getResourceDataInfoAsync(TenantId tenantId, TbResourceId resourceId) {
log.trace("Retrieving resource data info by id [{}], tenant id [{}] from cache", resourceId, tenantId); log.trace("Retrieving resource data info by id [{}], tenant id [{}] from cache", resourceId, tenantId);

16
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java

@ -100,6 +100,18 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
@Autowired @Autowired
private DashboardService dashboardService; private DashboardService dashboardService;
@Value("${transport.lwm2m.server.bind_port:5685}")
private Integer lwm2mPort;
@Value("${transport.lwm2m.server.security.bind_port:5686}")
private Integer lwm2mSecurePort;
@Value("${transport.lwm2m.bootstrap.bind_port:5687}")
private Integer lwm2mBootstrapPort;
@Value("${transport.lwm2m.bootstrap.security.bind_port:5688}")
private Integer lwm2mBootstrapSecurePort;
@Value("${security.java_cacerts.path:}") @Value("${security.java_cacerts.path:}")
private String javaCacertsPath; private String javaCacertsPath;
@ -371,9 +383,9 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
} }
int port; int port;
if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) { if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) {
port = serverConfig.isBootstrapServerIs() ? 5687 : 5685; port = serverConfig.isBootstrapServerIs() ? lwm2mBootstrapPort : lwm2mPort;
} else { } else {
port = serverConfig.isBootstrapServerIs() ? 5688 : 5686; port = serverConfig.isBootstrapServerIs() ? lwm2mBootstrapSecurePort : lwm2mSecurePort;
} }
if (serverConfig.getPort() == null || serverConfig.getPort() != port) { if (serverConfig.getPort() == null || serverConfig.getPort() != port) {
throw new DeviceCredentialsValidationException(server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!"); throw new DeviceCredentialsValidationException(server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!");

16
dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java

@ -16,16 +16,12 @@
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import org.junit.Assert; import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.exception.DataValidationException;
class ConstraintValidatorTest { class ConstraintValidatorTest {
private static final int MIN_IN_MS = 60000;
private static final int _1M = 1_000_000;
@Test @Test
void validateFields() { void validateFields() {
StringDataEntry stringDataEntryValid = new StringDataEntry("key", "value"); StringDataEntry stringDataEntryValid = new StringDataEntry("key", "value");
@ -35,16 +31,4 @@ class ConstraintValidatorTest {
ConstraintValidator.validateFields(stringDataEntryValid); ConstraintValidator.validateFields(stringDataEntryValid);
} }
@Test
void validatePerMinute() {
StringDataEntry stringDataEntryValid = new StringDataEntry("key", "value");
long start = System.currentTimeMillis();
for (int i = 0; i < _1M; i++) {
ConstraintValidator.validateFields(stringDataEntryValid);
}
long end = System.currentTimeMillis();
Assertions.assertTrue(MIN_IN_MS > end - start);
}
} }

154
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java

@ -1,154 +0,0 @@
/**
* Copyright © 2016-2026 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.server.dao.service.timeseries.sql;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.timeseries.TimeseriesLatestDao;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@DaoSqlTest
@Slf4j
public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
private static final String STRING_KEY = "stringKey";
private static final String LONG_KEY = "longKey";
private static final String DOUBLE_KEY = "doubleKey";
private static final String BOOLEAN_KEY = "booleanKey";
private static final int AMOUNT_OF_UNIQ_KEY = 10000;
private static final int TIMEOUT = 100;
private final Random random = new Random();
@Autowired
private TimeseriesLatestDao timeseriesLatestDao;
private ListeningExecutorService testExecutor;
private EntityId entityId;
private AtomicLong saveCounter;
@Before
public void before() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
entityId = new DeviceId(UUID.randomUUID());
saveCounter = new AtomicLong(0);
testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(200, ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope")));
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
if (testExecutor != null) {
testExecutor.shutdownNow();
}
}
@Test
public void test_save_latest_timeseries() throws Exception {
warmup();
saveCounter.set(0);
long startTime = System.currentTimeMillis();
List<ListenableFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < 25_000; i++) {
futures.add(save(generateStrEntry(getRandomKey())));
futures.add(save(generateLngEntry(getRandomKey())));
futures.add(save(generateDblEntry(getRandomKey())));
futures.add(save(generateBoolEntry(getRandomKey())));
}
Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
log.info("Total time: {}", totalTime);
log.info("Saved count: {}", saveCounter.get());
log.warn("Saved per 1 sec: {}", saveCounter.get() * 1000 / totalTime);
}
private void warmup() throws Exception {
List<ListenableFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < AMOUNT_OF_UNIQ_KEY; i++) {
futures.add(save(generateStrEntry(i)));
futures.add(save(generateLngEntry(i)));
futures.add(save(generateDblEntry(i)));
futures.add(save(generateBoolEntry(i)));
}
Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
}
private ListenableFuture<?> save(TsKvEntry tsKvEntry) {
return Futures.transformAsync(testExecutor.submit(() -> timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry)), result -> {
saveCounter.incrementAndGet();
return result;
}, testExecutor);
}
private TsKvEntry generateStrEntry(int keyIndex) {
return new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(STRING_KEY + keyIndex, RandomStringUtils.random(10)));
}
private TsKvEntry generateLngEntry(int keyIndex) {
return new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(LONG_KEY + keyIndex, random.nextLong()));
}
private TsKvEntry generateDblEntry(int keyIndex) {
return new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry(DOUBLE_KEY + keyIndex, random.nextDouble()));
}
private TsKvEntry generateBoolEntry(int keyIndex) {
return new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(BOOLEAN_KEY + keyIndex, random.nextBoolean()));
}
private int getRandomKey() {
return random.nextInt(AMOUNT_OF_UNIQ_KEY);
}
}

1
dao/src/test/resources/application-test.properties

@ -1,3 +1,4 @@
spring.main.banner-mode=off
zk.enabled=false zk.enabled=false
zk.url=localhost:2181 zk.url=localhost:2181
zk.zk_dir=/thingsboard zk.zk_dir=/thingsboard

3
dao/src/test/resources/logback-test.xml

@ -19,6 +19,9 @@
<!-- <logger name="org.hibernate.SQL" level="DEBUG"/> --> <!-- <logger name="org.hibernate.SQL" level="DEBUG"/> -->
<!-- <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG" /> --> <!-- <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG" /> -->
<!-- Avoiding multiple logs like: The cache 'relations' is not recording statistics. No meters except 'cache.size' will be registered. Call 'Caffeine#recordStats()' prior to building the cache for metrics to be recorded.-->
<logger name="io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics" level="ERROR"/>
<root level="WARN"> <root level="WARN">
<appender-ref ref="console"/> <appender-ref ref="console"/>
</root> </root>

2
edqs/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>4.3.0-RC</version> <version>4.3.0.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>edqs</artifactId> <artifactId>edqs</artifactId>

2
monitoring/pom.xml

@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>4.3.0-RC</version> <version>4.3.0.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>

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

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>4.3.0-RC</version> <version>4.3.0.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractCoapClientTest.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.session.FeatureType;
public abstract class AbstractCoapClientTest extends AbstractContainerTest{ public abstract class AbstractCoapClientTest extends AbstractContainerTest{
private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; private static final String CONTAINER_COAP_BASE_URL = "coap://localhost:5683/api/v1/";
private static final long CLIENT_REQUEST_TIMEOUT = 60000L; private static final long CLIENT_REQUEST_TIMEOUT = 60000L;
@ -54,7 +54,7 @@ public abstract class AbstractCoapClientTest extends AbstractContainerTest{
protected byte[] createCoapClientAndPublish(String deviceName) throws Exception { protected byte[] createCoapClientAndPublish(String deviceName) throws Exception {
String provisionRequestMsg = createTestProvisionMessage(deviceName); String provisionRequestMsg = createTestProvisionMessage(deviceName);
Configuration.addDefaultModule(MODULE_DEFINITIONS_PROVIDER); Configuration.addDefaultModule(MODULE_DEFINITIONS_PROVIDER);
String featureTokenUrl = COAP_BASE_URL + FeatureType.PROVISION.name().toLowerCase(); String featureTokenUrl = CONTAINER_COAP_BASE_URL + FeatureType.PROVISION.name().toLowerCase();
client = new CoapClient(featureTokenUrl); client = new CoapClient(featureTokenUrl);
return client.setTimeout(CLIENT_REQUEST_TIMEOUT) return client.setTimeout(CLIENT_REQUEST_TIMEOUT)
.post(provisionRequestMsg.getBytes(), MediaTypeRegistry.APPLICATION_JSON) .post(provisionRequestMsg.getBytes(), MediaTypeRegistry.APPLICATION_JSON)

42
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/PortFinder.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2026 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.server.msa;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.SocketException;
@Slf4j
public class PortFinder {
public static int findAvailableUdpPort() {
try (DatagramSocket socket = new DatagramSocket(0)) {
return socket.getLocalPort();
} catch (SocketException e) {
throw new IllegalStateException("No available UDP ports found", e);
}
}
public static boolean isUDPPortAvailable(int port) {
try (DatagramSocket socket = new DatagramSocket(port)) {
return true;
} catch (IOException e) {
log.debug("Failed to open UDP port {}", port, e);
return false;
}
}
}

24
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

@ -810,18 +810,27 @@ public class CalculatedFieldTest extends AbstractContainerTest {
// --- Build CF: PROPAGATION --- // --- Build CF: PROPAGATION ---
CalculatedField saved = createPropagationCF(device.getId()); CalculatedField saved = createPropagationCF(device.getId());
// Wait for INITIALIZED event
waitForDebugEvent(saved, CalculatedFieldEventType.INITIALIZED.name(), 1);
// Create relations FROM asset 2 TO device // Create relations FROM asset 2 TO device
Asset asset2 = testRestClient.postAsset(createAsset("Propagated Asset 2", null)); Asset asset2 = testRestClient.postAsset(createAsset("Propagated Asset 2", null));
EntityRelation rel2 = new EntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); EntityRelation rel2 = new EntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE);
testRestClient.postEntityRelation(rel2); testRestClient.postEntityRelation(rel2);
// Wait for RELATION_ADD_OR_UPDATE event
waitForDebugEvent(saved, CalculatedFieldEventType.RELATION_ADD_OR_UPDATE.name(), 2);
// Telemetry on device // Telemetry on device
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":25.1}")); testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":25.1}"));
// Wait for POST_TELEMETRY_REQUEST event
waitForDebugEvent(saved, TbMsgType.POST_TELEMETRY_REQUEST.name(), 3);
// Delete relation between asset 1 and device // Delete relation between asset 1 and device
testRestClient.deleteEntityRelation(asset1.getId(), EntityRelation.CONTAINS_TYPE, device.getId()); testRestClient.deleteEntityRelation(asset1.getId(), EntityRelation.CONTAINS_TYPE, device.getId());
// --- Assert propagated calculation (arguments-only mode) --- // --- Assert all debug events in correct sequence ---
await().alias("check debug events") await().alias("check debug events")
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
@ -845,6 +854,19 @@ public class CalculatedFieldTest extends AbstractContainerTest {
testRestClient.deleteAsset(asset2.getId()); testRestClient.deleteAsset(asset2.getId());
} }
private void waitForDebugEvent(CalculatedField cf, String expectedEventType, int expectedTotalEvents) {
await().alias("wait for debug event: " + expectedEventType)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
List<String> eventTypes = testRestClient.getEvents(cf.getId(), EventType.DEBUG_CALCULATED_FIELD, tenantId, new TimePageLink(expectedTotalEvents, 0, null, SortOrder.BY_CREATED_TIME_DESC)).getData().stream()
.map(e -> e.getBody().get("msgType").asText())
.toList();
assertThat(eventTypes).hasSize(expectedTotalEvents);
assertThat(eventTypes.get(0)).isEqualTo(expectedEventType);
});
}
private CalculatedField createPropagationCF(EntityId entityId) { private CalculatedField createPropagationCF(EntityId entityId) {
CalculatedField cf = new CalculatedField(); CalculatedField cf = new CalculatedField();
cf.setEntityId(entityId); cf.setEntityId(entityId);

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java

@ -57,12 +57,12 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.msa.AbstractContainerTest; import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.PortFinder;
import org.thingsboard.server.msa.WsClient; import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState; import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse; import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
@ -229,10 +229,8 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest {
String endpoint, ScheduledExecutorService executor) throws Exception { String endpoint, ScheduledExecutorService executor) throws Exception {
this.executor = executor; this.executor = executor;
LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(executor, endpoint); LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(executor, endpoint);
try (ServerSocket socket = new ServerSocket(0)) { int clientPort = PortFinder.findAvailableUdpPort();
int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, clientPort);
lwM2MTestClient.init(security, clientPort);
}
return lwM2MTestClient; return lwM2MTestClient;
} }

21
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java

@ -65,6 +65,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
@ -72,6 +73,7 @@ import static org.eclipse.leshan.core.LwM2mId.DEVICE;
import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.thingsboard.server.msa.PortFinder.isUDPPortAvailable;
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE;
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
@ -95,7 +97,6 @@ import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelp
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.serverId; import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.serverId;
import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId; import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId;
@Slf4j @Slf4j
@Data @Data
public class LwM2MTestClient { public class LwM2MTestClient {
@ -115,8 +116,11 @@ public class LwM2MTestClient {
private int countUpdateRegistrationSuccess; private int countUpdateRegistrationSuccess;
private int countReadObserveAfterUpdateRegistrationSuccess; private int countReadObserveAfterUpdateRegistrationSuccess;
private int clientPort;
public void init(Security security, int clientPort) throws InvalidDDFFileException, IOException { public void init(Security security, int clientPort) throws InvalidDDFFileException, IOException {
assertThat(leshanClient).as("client already initialized").isNull(); assertThat(leshanClient).as("client already initialized").isNull();
this.clientPort = clientPort;
List<ObjectModel> models = new ArrayList<>(); List<ObjectModel> models = new ArrayList<>();
for (String resourceName : resources) { for (String resourceName : resources) {
@ -342,12 +346,27 @@ public class LwM2MTestClient {
} }
}); });
}
public void start() {
leshanClient.start(); leshanClient.start();
if (clientPort > 0) {
log.error("Await UDP clientPort {} to be available before leshanClient.start()", clientPort);
await("Await UDP clientPort " + clientPort + " to be available before leshanClient.start()")
.atMost(30, TimeUnit.SECONDS)
.until(() -> isUDPPortAvailable(clientPort));
}
} }
public void destroy() { public void destroy() {
if (leshanClient != null) { if (leshanClient != null) {
leshanClient.destroy(true); leshanClient.destroy(true);
if (clientPort > 0) {
log.error("Await UDP clientPort {} to disconnect after leshanClient.stop(deregister)", clientPort);
await("Await client UDP port " + clientPort + " to disconnect after leshanClient.stop(deregister)")
.atMost(30, TimeUnit.SECONDS)
.until(() -> isUDPPortAvailable(clientPort));
}
} }
if (lwM2MDevice != null) { if (lwM2MDevice != null) {
lwM2MDevice.destroy(); lwM2MDevice.destroy();

3
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/rule/node/MqttNodeTest.java

@ -65,6 +65,7 @@ import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevi
public class MqttNodeTest extends AbstractContainerTest { public class MqttNodeTest extends AbstractContainerTest {
private static final String TOPIC = "tb/mqtt/device"; private static final String TOPIC = "tb/mqtt/device";
private static final String CONTAINER_MQTT_URL = "tcp://localhost:1883";
private Device device; private Device device;
@ -94,7 +95,7 @@ public class MqttNodeTest extends AbstractContainerTest {
responseClient.connect(); responseClient.connect();
responseClient.subscribe(TOPIC, messageListener); responseClient.subscribe(TOPIC, messageListener);
MqttClient mqttClient = new MqttClient("tcp://localhost:1883", StringUtils.randomAlphanumeric(10), new MemoryPersistence()); MqttClient mqttClient = new MqttClient(CONTAINER_MQTT_URL, StringUtils.randomAlphanumeric(10), new MemoryPersistence());
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions(); MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setUserName(deviceCredentials.getCredentialsId()); mqttConnectOptions.setUserName(deviceCredentials.getCredentialsId());
mqttClient.connect(mqttConnectOptions); mqttClient.connect(mqttConnectOptions);

2
msa/edqs/pom.xml

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

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "4.3.0", "version": "4.3.0.1",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

2
msa/js-executor/pom.xml

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

2
msa/monitoring/pom.xml

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

2
msa/pom.xml

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

2
msa/tb-node/pom.xml

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

2
msa/tb/pom.xml

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

2
msa/transport/coap/pom.xml

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

2
msa/transport/http/pom.xml

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

2
msa/transport/lwm2m/pom.xml

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save