Browse Source

Merge with master

pull/9542/head
ViacheslavKlimov 3 years ago
parent
commit
f95fcdf3dd
  1. 2
      application/pom.xml
  2. 22
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  3. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  4. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  5. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  7. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java
  8. 11
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
  9. 117
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
  10. 10
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
  13. 22
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java
  14. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
  15. 10
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java
  17. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
  18. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
  19. 27
      application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java
  20. 51
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  21. 123
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  22. 4
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  23. 11
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  24. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  25. 21
      application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java
  26. 11
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  27. 15
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  28. 9
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  29. 42
      application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java
  30. 7
      application/src/main/resources/thingsboard.yml
  31. 41
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  32. 201
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  33. 159
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java
  34. 21
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  35. 58
      application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java
  36. 62
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  37. 4
      application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java
  38. 4
      application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java
  39. 51
      application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java
  40. 42
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java
  41. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  42. 32
      application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java
  43. 309
      application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
  44. 105
      application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java
  45. 2
      common/actor/pom.xml
  46. 2
      common/cache/pom.xml
  47. 2
      common/cluster-api/pom.xml
  48. 14
      common/cluster-api/src/main/proto/queue.proto
  49. 2
      common/coap-server/pom.xml
  50. 2
      common/dao-api/pom.xml
  51. 2
      common/data/pom.xml
  52. 1
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  53. 2
      common/edge-api/pom.xml
  54. 2
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  55. 5
      common/edge-api/src/main/proto/edge.proto
  56. 2
      common/message/pom.xml
  57. 2
      common/pom.xml
  58. 2
      common/queue/pom.xml
  59. 2
      common/script/pom.xml
  60. 2
      common/script/remote-js-client/pom.xml
  61. 2
      common/script/script-api/pom.xml
  62. 24
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  63. 20
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  64. 2
      common/stats/pom.xml
  65. 2
      common/transport/coap/pom.xml
  66. 1
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/AbstractSyncSessionCallback.java
  67. 4
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java
  68. 2
      common/transport/http/pom.xml
  69. 2
      common/transport/lwm2m/pom.xml
  70. 2
      common/transport/mqtt/pom.xml
  71. 22
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java
  72. 2
      common/transport/pom.xml
  73. 2
      common/transport/snmp/pom.xml
  74. 2
      common/transport/transport-api/pom.xml
  75. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  76. 74
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultEntityLimitsCache.java
  77. 27
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitKey.java
  78. 24
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitsCache.java
  79. 48
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  80. 2
      common/util/pom.xml
  81. 2
      common/version-control/pom.xml
  82. 2
      dao/pom.xml
  83. 35
      dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java
  84. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  85. 3
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  86. 9
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  87. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java
  88. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java
  89. 10
      dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
  90. 2
      monitoring/pom.xml
  91. 2
      msa/black-box-tests/pom.xml
  92. 33
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java
  93. 2
      msa/js-executor/package.json
  94. 2
      msa/js-executor/pom.xml
  95. 2
      msa/monitoring/pom.xml
  96. 2
      msa/pom.xml
  97. 2
      msa/tb-node/pom.xml
  98. 4
      msa/tb/pom.xml
  99. 2
      msa/transport/coap/pom.xml
  100. 2
      msa/transport/http/pom.xml

2
application/pom.xml

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

22
application/src/main/java/org/thingsboard/server/controller/AuditLogController.java

@ -99,7 +99,7 @@ public class AuditLogController extends BaseController {
@RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
checkParameter("CustomerId", strCustomerId);
TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink));
}
@ -133,7 +133,7 @@ public class AuditLogController extends BaseController {
@RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
checkParameter("UserId", strUserId);
TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink));
}
@ -171,7 +171,7 @@ public class AuditLogController extends BaseController {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink));
}
@ -202,7 +202,7 @@ public class AuditLogController extends BaseController {
@RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
TenantId tenantId = getCurrentUser().getTenantId();
List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime));
return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink));
}
@ -214,4 +214,18 @@ public class AuditLogController extends BaseController {
}
return result;
}
private Long getStartTime(Long startTime) {
if (startTime == null) {
return 1L;
}
return startTime;
}
private Long getEndTime(Long endTime) {
if (endTime == null) {
return System.currentTimeMillis();
}
return endTime;
}
}

4
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -108,7 +108,7 @@ public class ThingsboardInstallService {
log.info("Migrating ThingsBoard entities data from cassandra to SQL database ...");
entitiesMigrateService.migrate();
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
systemDataLoaderService.loadSystemWidgets();
} else if ("3.0.1-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate();
@ -279,7 +279,7 @@ public class ThingsboardInstallService {
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
dataUpdateService.upgradeRuleNodes();
systemDataLoaderService.updateSystemWidgets();
systemDataLoaderService.loadSystemWidgets();
installScripts.updateSystemImages();
// installScripts.migrateTenantImages();
installScripts.loadSystemLwm2mResources();

4
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.edge;
import freemarker.template.Configuration;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
@ -87,9 +86,6 @@ public class EdgeContextComponent {
@Autowired
private AdminSettingsService adminSettingsService;
@Autowired
private Configuration freemarkerConfig;
@Autowired
private DeviceService deviceService;

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -641,7 +641,7 @@ public final class EdgeGrpcSession implements Closeable {
case WIDGETS_BUNDLE:
return ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent);
case WIDGET_TYPE:
return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent);
return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent, this.edgeVersion);
case ADMIN_SETTINGS:
return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent);
case OTA_PACKAGE:
@ -651,9 +651,9 @@ public final class EdgeGrpcSession implements Closeable {
case QUEUE:
return ctx.getQueueEdgeProcessor().convertQueueEventToDownlink(edgeEvent);
case TENANT:
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent);
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.getEdgeVersion());
case TENANT_PROFILE:
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent);
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.getEdgeVersion());
default:
log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent);
return null;

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -57,7 +57,7 @@ public class EdgeSyncCursor {
fetchers.add(new TenantEdgeEventFetcher(ctx.getTenantService()));
fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService()));
fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId());
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId()));

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java

@ -19,10 +19,12 @@ import com.google.protobuf.ByteString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils;
@Component
@TbCoreComponent
@ -31,7 +33,9 @@ public class TenantProfileMsgConstructor {
@Autowired
private DataDecodingEncodingService dataDecodingEncodingService;
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile) {
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_1) ?
ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData()));
TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(tenantProfile.getId().getId().getMostSignificantBits())
@ -39,7 +43,7 @@ public class TenantProfileMsgConstructor {
.setName(tenantProfile.getName())
.setDefault(tenantProfile.isDefault())
.setIsolatedRuleChain(tenantProfile.isIsolatedTbRuleEngine())
.setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData())));
.setProfileDataBytes(profileData);
if (tenantProfile.getDescription() != null) {
builder.setDescription(tenantProfile.getDescription());
}

11
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java

@ -20,9 +20,11 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils;
import java.util.Arrays;
@ -30,7 +32,7 @@ import java.util.Arrays;
@TbCoreComponent
public class WidgetTypeMsgConstructor {
public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails) {
public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) {
WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits())
@ -58,7 +60,12 @@ public class WidgetTypeMsgConstructor {
builder.setImage(widgetTypeDetails.getImage());
}
if (widgetTypeDetails.getDescription() != null) {
builder.setDescription(widgetTypeDetails.getDescription());
if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_0) &&
widgetTypeDetails.getDescription().length() > 255) {
builder.setDescription(widgetTypeDetails.getDescription().substring(0, 254));
} else {
builder.setDescription(widgetTypeDetails.getDescription());
}
}
builder.setDeprecated(widgetTypeDetails.isDeprecated());
if (widgetTypeDetails.getTags() != null) {

117
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java

@ -15,146 +15,51 @@
*/
package org.thingsboard.server.service.edge.rpc.fetch;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.text.WordUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@AllArgsConstructor
@Slf4j
public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
private final AdminSettingsService adminSettingsService;
private final Configuration freemarkerConfig;
private static final Pattern startPattern = Pattern.compile("<div class=\"content\".*?>");
private static final Pattern endPattern = Pattern.compile("<div class=\"footer\".*?>");
private static final List<String> templatesNames = Arrays.asList(
"account.activated.ftl",
"account.lockout.ftl",
"activation.ftl",
"password.was.reset.ftl",
"reset.password.ftl",
"test.ftl");
// TODO: @voba fix format of next templates
// "state.disabled.ftl",
// "state.enabled.ftl",
// "state.warning.ftl",
@Override
public PageLink getPageLink(int pageSize) {
return null;
}
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) throws Exception {
List<EdgeEvent> result = new ArrayList<>();
AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(systemMailSettings)));
AdminSettings tenantMailSettings = convertToTenantAdminSettings(tenantId, systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue());
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(tenantMailSettings)));
AdminSettings systemMailTemplates = loadMailTemplates(tenantId);
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(systemMailTemplates)));
AdminSettings tenantMailTemplates = convertToTenantAdminSettings(tenantId, systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue());
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(tenantMailTemplates)));
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> result = fetchAdminSettingsForKeys(tenantId, edge.getId(), List.of("general", "mail", "connectivity", "jwt"));
// return PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false);
}
private AdminSettings loadMailTemplates(TenantId tenantId) throws Exception {
Map<String, Object> mailTemplates = new HashMap<>();
for (String templatesName : templatesNames) {
Template template = freemarkerConfig.getTemplate(templatesName);
if (template != null) {
String name = validateName(template.getName());
Map<String, String> mailTemplate = getMailTemplateFromFile(template.toString());
if (mailTemplate != null) {
mailTemplates.put(name, mailTemplate);
} else {
log.error("[{}] Can't load mail template from file {}", tenantId, template.getName());
}
}
}
AdminSettings adminSettings = new AdminSettings();
adminSettings.setId(new AdminSettingsId(Uuids.timeBased()));
adminSettings.setKey("mailTemplates");
adminSettings.setJsonValue(JacksonUtil.convertValue(mailTemplates, JsonNode.class));
return adminSettings;
}
private Map<String, String> getMailTemplateFromFile(String stringTemplate) {
Map<String, String> mailTemplate = new HashMap<>();
Matcher start = startPattern.matcher(stringTemplate);
Matcher end = endPattern.matcher(stringTemplate);
if (start.find() && end.find()) {
String body = StringUtils.substringBetween(stringTemplate, start.group(), end.group()).replaceAll("\t", "");
String subject = StringUtils.substringBetween(body, "<h2>", "</h2>");
mailTemplate.put("subject", subject);
mailTemplate.put("body", body);
} else {
return null;
}
return mailTemplate;
}
private String validateName(String name) throws Exception {
StringBuilder nameBuilder = new StringBuilder();
name = name.replace(".ftl", "");
String[] nameParts = name.split("\\.");
if (nameParts.length >= 1) {
nameBuilder.append(nameParts[0]);
for (int i = 1; i < nameParts.length; i++) {
String word = WordUtils.capitalize(nameParts[i]);
nameBuilder.append(word);
private List<EdgeEvent> fetchAdminSettingsForKeys(TenantId tenantId, EdgeId edgeId, List<String> keys) {
List<EdgeEvent> result = new ArrayList<>();
for (String key : keys) {
AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key);
if (adminSettings != null) {
result.add(EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(adminSettings)));
}
return nameBuilder.toString();
} else {
throw new Exception("Error during filename validation");
}
}
private AdminSettings convertToTenantAdminSettings(TenantId tenantId, String key, ObjectNode jsonValue) {
AdminSettings tenantMailSettings = new AdminSettings();
tenantMailSettings.setTenantId(tenantId);
jsonValue.put("useSystemMailSettings", true);
tenantMailSettings.setJsonValue(jsonValue);
tenantMailSettings.setKey(key);
return tenantMailSettings;
return result;
}
}

10
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -467,9 +467,9 @@ public abstract class BaseEdgeProcessor {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getSourceEdgeIdMSB(), edgeNotificationMsg.getSourceEdgeIdLSB());
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
if (type.isAllEdgesRelated()) {
return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId, sourceEdgeId);
return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId, originatorEdgeId);
} else {
JsonNode body = JacksonUtil.toJsonNode(edgeNotificationMsg.getBody());
EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB());
@ -481,19 +481,19 @@ public abstract class BaseEdgeProcessor {
if (edgeId != null) {
return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body);
} else {
return processNotificationToRelatedEdges(tenantId, entityId, type, actionType, sourceEdgeId);
return processNotificationToRelatedEdges(tenantId, entityId, type, actionType, originatorEdgeId);
}
case DELETED:
EdgeEventActionType deleted = EdgeEventActionType.DELETED;
if (edgeId != null) {
return saveEdgeEvent(tenantId, edgeId, type, deleted, entityId, body);
} else {
return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body, sourceEdgeId)),
return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body, originatorEdgeId)),
voids -> null, dbCallbackExecutorService);
}
case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE:
if (sourceEdgeId == null) {
if (originatorEdgeId == null) {
ListenableFuture<Void> future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body);
return Futures.transformAsync(future, unused -> {
if (type.equals(EdgeEventType.RULE_CHAIN)) {

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java

@ -71,7 +71,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor {
public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getSourceEdgeIdMSB(), edgeNotificationMsg.getSourceEdgeIdLSB());
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
switch (actionType) {
case DELETED:
Alarm deletedAlarm = JacksonUtil.fromString(edgeNotificationMsg.getBody(), Alarm.class);
@ -79,7 +79,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor {
return Futures.immediateFuture(null);
}
List<ListenableFuture<Void>> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(),
alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), sourceEdgeId);
alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), originatorEdgeId);
return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService);
default:
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
@ -92,7 +92,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor {
return Futures.immediateFuture(null);
}
List<ListenableFuture<Void>> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(),
alarmId, actionType, null, sourceEdgeId);
alarmId, actionType, null, originatorEdgeId);
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}, dbCallbackExecutorService);
}

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java

@ -71,10 +71,12 @@ public class RelationEdgeProcessor extends BaseRelationProcessor {
if (relation == null || (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE))) {
return Futures.immediateFuture(null);
}
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
Set<EdgeId> uniqueEdgeIds = new HashSet<>();
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo()));
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom()));
uniqueEdgeIds.remove(originatorEdgeId);
if (uniqueEdgeIds.isEmpty()) {
return Futures.immediateFuture(null);
}

22
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java

@ -18,17 +18,20 @@ package org.thingsboard.server.service.edge.rpc.processor.resource;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@Slf4j
public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
protected void saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) {
protected boolean saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) {
boolean resourceKeyUpdated = false;
try {
boolean created = false;
TbResource resource = resourceService.findResourceById(tenantId, tbResourceId);
@ -42,9 +45,21 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
resource.setCreatedTime(Uuids.unixTimestamp(tbResourceId.getId()));
created = true;
}
String resourceKey = resourceUpdateMsg.getResourceKey();
ResourceType resourceType = ResourceType.valueOf(resourceUpdateMsg.getResourceType());
PageDataIterable<TbResource> resourcesIterable = new PageDataIterable<>(
link -> resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, resourceType, link), 1024);
for (TbResource tbResource : resourcesIterable) {
if (tbResource.getResourceKey().equals(resourceUpdateMsg.getResourceKey()) && !tbResourceId.equals(tbResource.getId())) {
resourceKey = StringUtils.randomAlphabetic(15) + "_" + resourceKey;
log.warn("[{}] Resource with resource type {} and key {} already exists. Renaming resource key to {}",
tenantId, resourceType, resourceUpdateMsg.getResourceKey(), resourceKey);
resourceKeyUpdated = true;
}
}
resource.setTitle(resourceUpdateMsg.getTitle());
resource.setResourceKey(resourceUpdateMsg.getResourceKey());
resource.setResourceType(ResourceType.valueOf(resourceUpdateMsg.getResourceType()));
resource.setResourceKey(resourceKey);
resource.setResourceType(resourceType);
resource.setFileName(resourceUpdateMsg.getFileName());
resource.setEncodedData(resourceUpdateMsg.hasData() ? resourceUpdateMsg.getData() : null);
resource.setEtag(resourceUpdateMsg.hasEtag() ? resourceUpdateMsg.getEtag() : null);
@ -57,5 +72,6 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
log.error("[{}] Failed to process resource update msg [{}]", tenantId, resourceUpdateMsg, e);
throw e;
}
return resourceKeyUpdated;
}
}

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java

@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -46,14 +48,12 @@ public class ResourceEdgeProcessor extends BaseResourceProcessor {
switch (resourceUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
super.saveOrUpdateTbResource(tenantId, tbResourceId, resourceUpdateMsg);
break;
case ENTITY_DELETED_RPC_MESSAGE:
TbResource tbResourceToDelete = resourceService.findResourceById(tenantId, tbResourceId);
if (tbResourceToDelete != null) {
resourceService.deleteResource(tenantId, tbResourceId);
boolean resourceKeyUpdated = super.saveOrUpdateTbResource(tenantId, tbResourceId, resourceUpdateMsg);
if (resourceKeyUpdated) {
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.TB_RESOURCE, EdgeEventActionType.UPDATED, tbResourceId, null);
}
break;
case ENTITY_DELETED_RPC_MESSAGE:
case UNRECOGNIZED:
return handleUnsupportedMsgType(resourceUpdateMsg.getMsgType());
}

10
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java

@ -21,6 +21,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
@ -37,6 +38,15 @@ public class TelemetryEdgeProcessor extends BaseTelemetryProcessor {
}
public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException {
if (edgeEvent.getBody() != null) {
String bodyStr = edgeEvent.getBody().toString();
if (bodyStr.length() > 1000) {
log.debug("[{}][{}][{}] Conversion to a DownlinkMsg telemetry event failed due to a size limit violation. " +
"Current size is {}, but the limit is 1000. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(),
edgeEvent.getEntityId(), bodyStr.length(), StringUtils.truncate(bodyStr, 100));
return null;
}
}
EntityType entityType = EntityType.valueOf(edgeEvent.getType().name());
EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto(
edgeEvent.getTenantId(), entityType, edgeEvent.getEntityId(),

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@TbCoreComponent
public class TenantEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantId tenantId = new TenantId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
@ -44,7 +45,7 @@ public class TenantEdgeProcessor extends BaseEdgeProcessor {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
TenantUpdateMsg tenantUpdateMsg = tenantMsgConstructor.constructTenantUpdateMsg(msgType, tenant);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
TenantProfileUpdateMsg tenantProfileUpdateMsg = tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile);
TenantProfileUpdateMsg tenantProfileUpdateMsg = tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addTenantUpdateMsg(tenantUpdateMsg)

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -33,7 +34,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@TbCoreComponent
public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
@ -41,7 +42,7 @@ public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
if (tenantProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
TenantProfileUpdateMsg tenantProfileUpdateMsg =
tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile);
tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addTenantProfileUpdateMsg(tenantProfileUpdateMsg)

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -32,7 +33,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@TbCoreComponent
public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
@ -42,7 +43,7 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
if (widgetTypeDetails != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
WidgetTypeUpdateMsg widgetTypeUpdateMsg =
widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails);
widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails, edgeVersion);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addWidgetTypeUpdateMsg(widgetTypeUpdateMsg)

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

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.utils;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@Slf4j
public final class EdgeVersionUtils {
public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) {
return currentVersion.ordinal() < requiredVersion.ordinal();
}
}

51
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -63,7 +63,6 @@ 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.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
@ -84,9 +83,6 @@ import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
@ -536,58 +532,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
installScripts.loadDashboards(demoTenant.getId(), null);
}
@Override
public void deleteSystemWidgetBundle(String bundleAlias) throws Exception {
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias);
if (widgetsBundle != null) {
PageData<WidgetTypeInfo> widgetTypes;
var pageLink = new PageLink(1024);
do {
widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink);
for (var widgetType : widgetTypes.getData()) {
widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId());
}
pageLink.nextPageLink();
} while (widgetTypes.hasNext());
widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId());
}
}
@Override
public void loadSystemWidgets() throws Exception {
installScripts.loadSystemWidgets();
}
@Override
public void updateSystemWidgets() throws Exception {
this.deleteSystemWidgetBundle("charts");
this.deleteSystemWidgetBundle("cards");
this.deleteSystemWidgetBundle("maps");
this.deleteSystemWidgetBundle("analogue_gauges");
this.deleteSystemWidgetBundle("digital_gauges");
this.deleteSystemWidgetBundle("gpio_widgets");
this.deleteSystemWidgetBundle("alarm_widgets");
this.deleteSystemWidgetBundle("control_widgets");
this.deleteSystemWidgetBundle("maps_v2");
this.deleteSystemWidgetBundle("gateway_widgets");
this.deleteSystemWidgetBundle("input_widgets");
this.deleteSystemWidgetBundle("date");
this.deleteSystemWidgetBundle("entity_admin_widgets");
this.deleteSystemWidgetBundle("navigation_widgets");
this.deleteSystemWidgetBundle("edge_widgets");
this.deleteSystemWidgetBundle("home_page_widgets");
this.deleteSystemWidgetBundle("entity_widgets");
this.deleteSystemWidgetBundle("html_widgets");
this.deleteSystemWidgetBundle("tables");
this.deleteSystemWidgetBundle("count_widgets");
this.deleteSystemWidgetBundle("status_indicators");
this.deleteSystemWidgetBundle("outdoor_environment");
this.deleteSystemWidgetBundle("indoor_environment");
this.deleteSystemWidgetBundle("air_quality");
this.deleteSystemWidgetBundle("liquid_level_tanks");
installScripts.loadSystemWidgets();
}
private User createUser(Authority authority,
TenantId tenantId,
CustomerId customerId,

123
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -33,10 +33,14 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -53,6 +57,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -198,6 +203,38 @@ public class InstallScripts {
public void loadSystemWidgets() throws Exception {
log.info("Loading system widgets");
Map<Path, JsonNode> widgetsBundlesMap = new HashMap<>();
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
JsonNode widgetsBundleDescriptorJson;
try {
widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile());
} catch (Exception e) {
log.error("Unable to parse widgets bundle from json: [{}]", path);
throw new RuntimeException("Unable to parse widgets bundle from json", e);
}
if (widgetsBundleDescriptorJson == null || !widgetsBundleDescriptorJson.has("widgetsBundle")) {
log.error("Invalid widgets bundle json: [{}]", path);
throw new RuntimeException("Invalid widgets bundle json: [" + path + "]");
}
widgetsBundlesMap.put(path, widgetsBundleDescriptorJson);
JsonNode bundleAliasNode = widgetsBundleDescriptorJson.get("widgetsBundle").get("alias");
if (bundleAliasNode == null || !bundleAliasNode.isTextual()) {
log.error("Invalid widgets bundle json: [{}]", path);
throw new RuntimeException("Invalid widgets bundle json: [" + path + "]");
}
String bundleAlias = bundleAliasNode.asText();
try {
this.deleteSystemWidgetBundle(bundleAlias);
} catch (Exception e) {
log.error("Failed to delete system widgets bundle: [{}]", bundleAlias);
throw new RuntimeException("Failed to delete system widgets bundle: [" + bundleAlias + "]", e);
}
}
);
}
Path widgetTypesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_TYPES_DIR);
if (Files.exists(widgetTypesDir)) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetTypesDir, path -> path.toString().endsWith(JSON_EXT))) {
@ -215,44 +252,56 @@ public class InstallScripts {
);
}
}
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile());
JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class);
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
List<String> widgetTypeFqns = new ArrayList<>();
if (widgetsBundleDescriptorJson.has("widgetTypes")) {
JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
widgetTypesArrayJson.forEach(
widgetTypeJson -> {
try {
WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class);
var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails);
widgetTypeFqns.add(savedWidgetType.getFqn());
} catch (Exception e) {
log.error("Unable to load widget type from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widget type from json", e);
}
}
);
}
if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) {
JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns");
widgetFqnsArrayJson.forEach(fqnJson -> {
widgetTypeFqns.add(fqnJson.asText());
});
for (var widgetsBundleDescriptorEntry : widgetsBundlesMap.entrySet()) {
Path path = widgetsBundleDescriptorEntry.getKey();
try {
JsonNode widgetsBundleDescriptorJson = widgetsBundleDescriptorEntry.getValue();
JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class);
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
List<String> widgetTypeFqns = new ArrayList<>();
if (widgetsBundleDescriptorJson.has("widgetTypes")) {
JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
widgetTypesArrayJson.forEach(
widgetTypeJson -> {
try {
WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class);
var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails);
widgetTypeFqns.add(savedWidgetType.getFqn());
} catch (Exception e) {
log.error("Unable to load widget type from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widget type from json", e);
}
}
widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns);
} catch (Exception e) {
log.error("Unable to load widgets bundle from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widgets bundle from json", e);
}
}
);
);
}
if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) {
JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns");
widgetFqnsArrayJson.forEach(fqnJson -> {
widgetTypeFqns.add(fqnJson.asText());
});
}
widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns);
} catch (Exception e) {
log.error("Unable to load widgets bundle from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widgets bundle from json", e);
}
}
}
private void deleteSystemWidgetBundle(String bundleAlias) {
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias);
if (widgetsBundle != null) {
PageData<WidgetTypeInfo> widgetTypes;
var pageLink = new PageLink(1024);
do {
widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink);
for (var widgetType : widgetTypes.getData()) {
widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId());
}
pageLink.nextPageLink();
} while (widgetTypes.hasNext());
widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId());
}
}

4
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -31,12 +31,8 @@ public interface SystemDataLoaderService {
void loadSystemWidgets() throws Exception;
void updateSystemWidgets() throws Exception;
void loadDemoData() throws Exception;
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
void createQueues();
void createDefaultNotificationConfigs();

11
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -27,7 +27,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
@ -67,7 +66,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.audit.AuditLogDao;
@ -90,6 +88,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList;
import java.util.Collections;
@ -291,17 +290,11 @@ public class DefaultDataUpdateService implements DataUpdateService {
continue;
}
var ruleNodeId = ruleNode.getId();
var oldConfiguration = ruleNode.getConfiguration();
int fromVersion = ruleNode.getConfigurationVersion();
log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, fromVersion, toVersion);
try {
var tbVersionedNode = (TbNode) ruleNodeClassInfo.getClazz().getDeclaredConstructor().newInstance();
TbPair<Boolean, JsonNode> upgradeRuleNodeConfigurationResult = tbVersionedNode.upgrade(fromVersion, oldConfiguration);
if (upgradeRuleNodeConfigurationResult.getFirst()) {
ruleNode.setConfiguration(upgradeRuleNodeConfigurationResult.getSecond());
}
ruleNode.setConfigurationVersion(toVersion);
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(ruleNode, ruleNodeClassInfo);
saveFutures.add(jpaExecutorService.submit(() -> {
ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode);
log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",

8
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -486,7 +486,7 @@ public class DefaultTbClusterService implements TbClusterService {
}
@Override
public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId sourceEdgeId) {
public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId originatorEdgeId) {
if (!edgesEnabled) {
return;
}
@ -519,9 +519,9 @@ public class DefaultTbClusterService implements TbClusterService {
if (body != null) {
builder.setBody(body);
}
if (sourceEdgeId != null) {
builder.setSourceEdgeIdMSB(sourceEdgeId.getId().getMostSignificantBits());
builder.setSourceEdgeIdLSB(sourceEdgeId.getId().getLeastSignificantBits());
if (originatorEdgeId != null) {
builder.setOriginatorEdgeIdMSB(originatorEdgeId.getId().getMostSignificantBits());
builder.setOriginatorEdgeIdLSB(originatorEdgeId.getId().getLeastSignificantBits());
}
TransportProtos.EdgeNotificationMsgProto msg = builder.build();
log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);

21
application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java

@ -152,22 +152,29 @@ public class ProtoUtils {
}
private static TransportProtos.DeviceEdgeUpdateMsgProto toProto(DeviceEdgeUpdateMsg msg) {
return TransportProtos.DeviceEdgeUpdateMsgProto.newBuilder()
TransportProtos.DeviceEdgeUpdateMsgProto.Builder builder = TransportProtos.DeviceEdgeUpdateMsgProto.newBuilder()
.setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceId().getId().getMostSignificantBits())
.setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits())
.setEdgeIdMSB(msg.getEdgeId().getId().getMostSignificantBits())
.setEdgeIdLSB(msg.getEdgeId().getId().getLeastSignificantBits())
.build();
.setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits());
if (msg.getEdgeId() != null) {
builder.setEdgeIdMSB(msg.getEdgeId().getId().getMostSignificantBits())
.setEdgeIdLSB(msg.getEdgeId().getId().getLeastSignificantBits());
}
return builder.build();
}
private static DeviceEdgeUpdateMsg fromProto(TransportProtos.DeviceEdgeUpdateMsgProto proto) {
EdgeId edgeId = null;
if (proto.hasEdgeIdMSB() && proto.hasEdgeIdLSB()) {
edgeId = new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()));
}
return new DeviceEdgeUpdateMsg(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB())),
new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()))
);
edgeId);
}
private static TransportProtos.DeviceNameOrTypeUpdateMsgProto toProto(DeviceNameOrTypeUpdateMsg msg) {

11
application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java

@ -15,12 +15,10 @@
*/
package org.thingsboard.server.service.rule;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
@ -44,13 +42,13 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList;
import java.util.Collections;
@ -402,18 +400,13 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
var ruleNodeClass = componentDiscoveryService.getRuleNodeInfo(ruleNodeType)
.orElseThrow(() -> new RuntimeException("Rule node " + ruleNodeType + " is not supported!"));
if (ruleNodeClass.isVersioned()) {
TbNode tbVersionedNode = (TbNode) ruleNodeClass.getClazz().getDeclaredConstructor().newInstance();
int fromVersion = node.getConfigurationVersion();
int toVersion = ruleNodeClass.getCurrentVersion();
if (fromVersion < toVersion) {
log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, fromVersion, toVersion);
try {
TbPair<Boolean, JsonNode> upgradeResult = tbVersionedNode.upgrade(fromVersion, node.getConfiguration());
if (upgradeResult.getFirst()) {
node.setConfiguration(upgradeResult.getSecond());
}
node.setConfigurationVersion(toVersion);
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, ruleNodeClass);
log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion);
} catch (TbNodeException e) {

15
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -175,20 +175,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
addEntityViewCallback(tenantId, entityId, ts);
}
private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
}
private void addCallbacks(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback, ListenableFuture<Integer> saveFuture) {
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
}
private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) {
Futures.addCallback(this.tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId),
new FutureCallback<List<EntityView>>() {
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<EntityView> result) {
if (result != null && !result.isEmpty()) {
@ -212,7 +213,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
if (!entityViewLatest.isEmpty()) {
saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<Void>() {
saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
}

9
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -78,6 +78,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
@ -165,6 +166,7 @@ public class DefaultTransportApiService implements TransportApiService {
public void init() {
handlerExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(maxCoreHandlerThreads, "transport-api-service-core-handler"));
}
@PreDestroy
public void destroy() {
if (handlerExecutor != null) {
@ -401,6 +403,13 @@ public class DefaultTransportApiService implements TransportApiService {
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by gateway id and name: [{}]", gatewayId, requestMsg.getDeviceName(), e);
throw new RuntimeException(e);
} catch (EntitiesLimitException e) {
log.warn("[{}][{}] API limit exception: [{}]", e.getTenantId(), gatewayId, e.getMessage());
return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(
GetOrCreateDeviceFromGatewayResponseMsg.newBuilder()
.setError(TransportProtos.TransportApiRequestErrorCode.ENTITY_LIMIT))
.build();
} finally {
deviceCreationLock.unlock();
}

42
application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.utils;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
public class TbNodeUpgradeUtils {
public static void upgradeConfigurationAndVersion(RuleNode node, RuleNodeClassInfo nodeInfo) throws Exception {
JsonNode oldConfiguration = node.getConfiguration();
if (oldConfiguration == null || !oldConfiguration.isObject()) {
var configClass = nodeInfo.getAnnotation().configClazz();
node.setConfiguration(JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration()));
} else {
var tbVersionedNode = (TbNode) nodeInfo.getClazz().getDeclaredConstructor().newInstance();
TbPair<Boolean, JsonNode> upgradeResult = tbVersionedNode.upgrade(node.getConfigurationVersion(), oldConfiguration);
if (upgradeResult.getFirst()) {
node.setConfiguration(upgradeResult.getSecond());
}
}
node.setConfigurationVersion(nodeInfo.getCurrentVersion());
}
}

7
application/src/main/resources/thingsboard.yml

@ -191,7 +191,7 @@ ui:
# Help parameters
help:
# Base URL for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.6}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.6.1}"
# Database telemetry parameters
database:
@ -583,6 +583,9 @@ cache:
rateLimits:
timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:120}" # Rate limits cache TTL
maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:200000}" # 0 means the cache is disabled
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
# Spring data parameters
spring.data.redis.repositories.enabled: false # Disable this because it is not required.
@ -875,7 +878,7 @@ transport:
ip_limits_enabled: "${TB_TRANSPORT_IP_RATE_LIMITS_ENABLED:false}"
# Maximum number of connect attempts with invalid credentials
max_wrong_credentials_per_ip: "${TB_TRANSPORT_MAX_WRONG_CREDENTIALS_PER_IP:10}"
# Timeout to expire block IP addresses
# Timeout (in milliseconds) to expire block IP addresses
ip_block_timeout: "${TB_TRANSPORT_IP_BLOCK_TIMEOUT:60000}"
# Local HTTP transport parameters
http:

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

@ -25,6 +25,7 @@ import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.hibernate.exception.ConstraintViolationException;
@ -177,6 +178,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/
private static final long DEFAULT_TIMEOUT = -1L;
private static final int CLEANUP_TENANT_RETRIES_COUNT = 3;
protected MediaType contentType = MediaType.APPLICATION_JSON;
@ -330,10 +332,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
log.debug("Executing web test teardown");
loginSysAdmin();
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
deleteDifferentTenant();
verifyNoTenantsLeft();
tenantProfileService.deleteTenantProfiles(TenantId.SYS_TENANT_ID);
@ -341,7 +341,34 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
log.info("Executed web test teardown");
}
void verifyNoTenantsLeft() throws Exception {
private void verifyNoTenantsLeft() throws Exception {
List<Tenant> loadedTenants = getAllTenants();
if (!loadedTenants.isEmpty()) {
loadedTenants.forEach(tenant -> deleteTenant(tenant.getId()));
loadedTenants = getAllTenants();
}
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
}
private void deleteTenant(TenantId tenantId) {
int status = 0;
int retries = 0;
while (status != HttpStatus.SC_OK && retries < CLEANUP_TENANT_RETRIES_COUNT) {
retries++;
try {
status = doDelete("/api/tenant/" + tenantId.getId().toString())
.andReturn().getResponse().getStatus();
if (status != HttpStatus.SC_OK) {
log.warn("Tenant deletion failed, tenantId: {}", tenantId.getId().toString());
Thread.sleep(1000L);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private List<Tenant> getAllTenants() throws Exception {
List<Tenant> loadedTenants = new ArrayList<>();
PageLink pageLink = new PageLink(10);
PageData<Tenant> pageData;
@ -353,8 +380,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
return loadedTenants;
}
protected void loginSysAdmin() throws Exception {
@ -465,8 +491,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected void deleteDifferentTenant() throws Exception {
if (savedDifferentTenant != null) {
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedDifferentTenant.getId());
savedDifferentTenant = null;
}
}

201
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -24,7 +24,6 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
@ -100,8 +99,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"C3swkUdrR3pezg==\n" +
"-----END PRIVATE KEY-----\n";
ListeningExecutorService executor;
private Tenant savedTenant;
private User tenantAdmin;
private DeviceProfileId mqttDeviceProfileId;
@ -117,8 +114,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
@ -208,8 +203,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
@After
public void afterTest() throws Exception {
executor.shutdownNow();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
@ -248,19 +241,25 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
" -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
credentials.getCredentialsId()));
JsonNode linuxCoapCommands = commands.get(COAP);
assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " +
assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry " +
"-t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" +
assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry" +
" -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
}
@Test
@ -286,10 +285,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t %s -u \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
" -p 1883 -t %s -u \"%s\" -m \"{temperature:25}\"",
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -u \"%s\" -m \"{temperature:25}\"\"",
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
@ -330,10 +329,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
" -p 1883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"",
DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"\"",
DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
@ -378,9 +377,9 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
assertThat(commands).hasSize(1);
JsonNode linuxCommands = commands.get(COAP);
assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"",
assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"",
assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"",
credentials.getCredentialsId()));
}
@ -445,4 +444,172 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
public void testDownloadCertWithUnknownProtocol() throws Exception {
doGet("/api/device-connectivity/unknownProtocol/certificate/download").andExpect(status().isNotFound());
}
@Test
public void testFetchPublishTelemetryCommandsForDefaultDeviceIfPortsSetToDefault() throws Exception {
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
ObjectNode http = JacksonUtil.newObjectNode();
http.put("enabled", true);
http.put("host", "");
http.put("port", 80);
config.set("http", http);
ObjectNode https = JacksonUtil.newObjectNode();
https.put("enabled", true);
https.put("host", "");
https.put("port", 443);
config.set("https", https);
ObjectNode mqtt = JacksonUtil.newObjectNode();
mqtt.put("enabled", false);
mqtt.put("host", "");
mqtt.put("port", 1883);
config.set("mqtt", mqtt);
ObjectNode mqtts = JacksonUtil.newObjectNode();
mqtts.put("enabled", false);
mqtts.put("host", "");
mqtts.put("port", 8883);
config.set("mqtts", mqtts);
ObjectNode coap = JacksonUtil.newObjectNode();
coap.put("enabled", false);
coap.put("host", "");
coap.put("port", 5683);
config.set("coap", coap);
ObjectNode coaps = JacksonUtil.newObjectNode();
coaps.put("enabled", false);
coaps.put("host", "");
coaps.put("port", 5684);
config.set("coaps", coaps);
AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
adminSettings.setJsonValue(config);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
login("tenant2@thingsboard.org", "testPassword1");
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(1);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
}
@Test
public void testFetchPublishTelemetryCommandsForDefaultDeviceIfHostIsNotLocalhost() throws Exception {
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
ObjectNode http = JacksonUtil.newObjectNode();
http.put("enabled", true);
http.put("host", "test.domain");
http.put("port", 8080);
config.set("http", http);
ObjectNode https = JacksonUtil.newObjectNode();
https.put("enabled", true);
https.put("host", "test.domain");
https.put("port", 443);
config.set("https", https);
ObjectNode mqtt = JacksonUtil.newObjectNode();
mqtt.put("enabled", true);
mqtt.put("host", "test.domain");
mqtt.put("port", 1883);
config.set("mqtt", mqtt);
ObjectNode mqtts = JacksonUtil.newObjectNode();
mqtts.put("enabled", true);
mqtts.put("host", "test.domain");
mqtts.put("port", 8883);
config.set("mqtts", mqtts);
ObjectNode coap = JacksonUtil.newObjectNode();
coap.put("enabled", true);
coap.put("host", "test.domain");
coap.put("port", 5683);
config.set("coap", coap);
ObjectNode coaps = JacksonUtil.newObjectNode();
coaps.put("enabled", true);
coaps.put("host", "test.domain");
coaps.put("port", 5684);
config.set("coaps", coaps);
AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
adminSettings.setJsonValue(config);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
login("tenant2@thingsboard.org", "testPassword1");
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(3);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://test.domain:8080/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://test.domain/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
JsonNode mqttCommands = commands.get(MQTT);
assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h test.domain -p 1883 -t v1/devices/me/telemetry " +
"-u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download");
assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile ca-root.pem -h test.domain -p 8883 " +
"-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h test.domain" +
" -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h test.domain -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
credentials.getCredentialsId()));
JsonNode linuxCoapCommands = commands.get(COAP);
assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://test.domain:5683/api/v1/%s/telemetry " +
"-t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry" +
" -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " +
"thingsboard/coap-clients coap-client -v 6 -m POST coap://test.domain:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it " +
"thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
}
}

159
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java

@ -1,159 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS;
@ContextConfiguration(classes = {DeviceConnectivityControllerWithDefaultPortTest.Config.class})
@DaoSqlTest
public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractControllerTest {
ListeningExecutorService executor;
private Tenant savedTenant;
static class Config {
@Bean
@Primary
public DeviceDao deviceDao(DeviceDao deviceDao) {
return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao));
}
}
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin();
ObjectNode config = JacksonUtil.newObjectNode();
ObjectNode http = JacksonUtil.newObjectNode();
http.put("enabled", true);
http.put("host", "");
http.put("port", 80);
config.set("http", http);
ObjectNode https = JacksonUtil.newObjectNode();
https.put("enabled", true);
https.put("host", "");
https.put("port", 443);
config.set("https", https);
ObjectNode mqtt = JacksonUtil.newObjectNode();
mqtt.put("enabled", false);
mqtt.put("host", "");
mqtt.put("port", 1883);
config.set("mqtt", mqtt);
ObjectNode mqtts = JacksonUtil.newObjectNode();
mqtts.put("enabled", false);
mqtts.put("host", "");
mqtts.put("port", 8883);
config.set("mqtts", mqtts);
ObjectNode coap = JacksonUtil.newObjectNode();
coap.put("enabled", false);
coap.put("host", "");
coap.put("port", 5683);
config.set("coap", coap);
ObjectNode coaps = JacksonUtil.newObjectNode();
coaps.put("enabled", false);
coaps.put("host", "");
coaps.put("port", 5684);
config.set("coaps", coaps);
AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class);
adminSettings.setJsonValue(config);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
executor.shutdownNow();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
}
@Test
public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(1);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
}
}

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

@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
@ -843,6 +844,13 @@ public class EdgeControllerTest extends AbstractControllerTest {
@Test
public void testSyncEdge() throws Exception {
loginSysAdmin();
// get jwt settings from yaml config
JwtSettings settings = doGet("/api/admin/jwtSettings", JwtSettings.class);
// save jwt settings into db
doPost("/api/admin/jwtSettings", settings).andExpect(status().isOk());
loginTenantAdmin();
Asset asset = new Asset();
asset.setName("Test Sync Edge Asset 1");
asset.setType("test");
@ -904,10 +912,10 @@ public class EdgeControllerTest extends AbstractControllerTest {
private void verifyFetchersMsgs(EdgeImitator edgeImitator) {
Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main"));
Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", true));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", false));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", true));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", false));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "general"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "connectivity"));
Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "jwt"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
@ -953,12 +961,11 @@ public class EdgeControllerTest extends AbstractControllerTest {
return false;
}
private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key, boolean isSystem) {
private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key) {
for (AbstractMessage message : messages) {
if (message instanceof AdminSettingsUpdateMsg) {
AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message;
if (key.equals(adminSettingsUpdateMsg.getKey())
&& isSystem == adminSettingsUpdateMsg.getIsSystem()) {
if (key.equals(adminSettingsUpdateMsg.getKey())) {
messages.remove(message);
return true;
}

58
application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
@ -664,6 +666,34 @@ public class TbResourceControllerTest extends AbstractControllerTest {
assertThat(download(savedResource.getId())).asBase64Encoded().isEqualTo(newData);
}
@Test
public void testGetLwm2mListObjectsPage() throws Exception {
loginTenantAdmin();
List<TbResource> resources = loadLwm2mResources();
List<LwM2mObject> objects =
doGetTyped("/api/resource/lwm2m/page?pageSize=100&page=0", new TypeReference<>() {});
Assert.assertNotNull(objects);
Assert.assertEquals(resources.size(), objects.size());
removeLoadResources(resources);
}
@Test
public void testGetLwm2mListObjects() throws Exception {
loginTenantAdmin();
List<TbResource> resources = loadLwm2mResources();
List<LwM2mObject> objects =
doGetTyped("/api/resource/lwm2m?sortProperty=id&sortOrder=ASC&objectIds=3_1.0,5_1.0,19_1.1", new TypeReference<>() {});
Assert.assertNotNull(objects);
Assert.assertEquals(3, objects.size());
removeLoadResources(resources);
}
private TbResource save(TbResource tbResource) throws Exception {
return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>() {
});
@ -678,4 +708,32 @@ public class TbResourceControllerTest extends AbstractControllerTest {
private String getImageLink(TbResourceInfo resourceInfo) {
return "/api/images/" + (resourceInfo.getTenantId().isSysTenantId() ? "system/" : "") + resourceInfo.getResourceKey();
}
private List<TbResource> loadLwm2mResources() throws Exception {
var models = List.of("1", "2", "3", "5", "6", "9", "19", "3303");
List<TbResource> resources = new ArrayList<>(models.size());
for (String model : models) {
String fileName = model + ".xml";
byte[] bytes = IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream("lwm2m/" + fileName));
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setFileName(fileName);
resource.setData(bytes);
resources.add(save(resource));
}
return resources;
}
private void removeLoadResources(List<TbResource> resources) throws Exception {
for (TbResourceInfo resource : resources) {
doDelete("/api/resource/" + resource.getId().getId().toString())
.andExpect(status().isOk());
}
}
}

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

@ -71,6 +71,7 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.edge.imitator.EdgeImitator;
@ -98,6 +99,7 @@ import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -127,6 +129,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
@Before
public void setupEdgeTest() throws Exception {
loginSysAdmin();
// get jwt settings from yaml config
JwtSettings settings = doGet("/api/admin/jwtSettings", JwtSettings.class);
// save jwt settings into db
doPost("/api/admin/jwtSettings", settings).andExpect(status().isOk());
loginTenantAdmin();
installation();
@ -173,7 +182,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
} catch (Exception ignored) {}
}
private void installation() {
private void installation() throws Exception {
thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME,
createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false));
extendDeviceProfileData(thermostatDeviceProfile);
@ -189,6 +198,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
+ "/device/" + savedDevice.getUuidId(), Device.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Asset.class);
// wait until assign device and asset events are fully processed by edge notification service
TimeUnit.MILLISECONDS.sleep(500);
}
protected void extendDeviceProfileData(DeviceProfile deviceProfile) {
@ -237,25 +249,23 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 1);
validateRuleChainMetadataUpdates(ruleChainUUID);
// 4 messages
// - 2 from fetcher - system level ('mail', 'mailTemplates')
// - 2 from fetcher - admin level ('mail', 'mailTemplates')
// 4 messages ('general', 'mail', 'connectivity', 'jwt')
validateMsgsCnt(AdminSettingsUpdateMsg.class, 4);
validateAdminSettings();
validateAdminSettings(4);
// 4 messages
// - 1 from default profile fetcher
// - 2 from device profile fetcher (default and thermostat)
// - 1 from device fetcher
validateMsgsCnt(DeviceProfileUpdateMsg.class, 4);
validateDeviceProfiles();
validateDeviceProfiles(4);
// 3 messages
// - 1 from default profile fetcher
// - 1 message from asset profile fetcher
// - 1 message from asset fetcher
validateMsgsCnt(AssetProfileUpdateMsg.class, 3);
validateAssetProfiles();
validateAssetProfiles(3);
// 1 from device fetcher
validateMsgsCnt(DeviceUpdateMsg.class, 1);
@ -326,13 +336,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(tenantProfileUpdateMsg);
}
private void validateDeviceProfiles() throws Exception {
private void validateDeviceProfiles(int expectedMsgCnt) throws Exception {
List<DeviceProfileUpdateMsg> deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class);
// default msg default device profile from fetcher
// default msg device profile from fetcher
// thermostat msg from device profile fetcher
// thermostat msg from device fetcher
Assert.assertEquals(4, deviceProfileUpdateMsgList.size());
Assert.assertEquals(expectedMsgCnt, deviceProfileUpdateMsgList.size());
Optional<DeviceProfileUpdateMsg> thermostatProfileUpdateMsgOpt =
deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny();
Assert.assertTrue(thermostatProfileUpdateMsgOpt.isPresent());
@ -413,20 +423,28 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID);
}
private void validateAdminSettings() {
private void validateAdminSettings(int expectedMsgCnt) {
List<AdminSettingsUpdateMsg> adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class);
Assert.assertEquals(4, adminSettingsUpdateMsgs.size());
Assert.assertEquals(expectedMsgCnt, adminSettingsUpdateMsgs.size());
for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) {
if (adminSettingsUpdateMsg.getKey().equals("general")) {
validateGeneralAdminSettings(adminSettingsUpdateMsg);
}
if (adminSettingsUpdateMsg.getKey().equals("mail")) {
validateMailAdminSettings(adminSettingsUpdateMsg);
}
if (adminSettingsUpdateMsg.getKey().equals("mailTemplates")) {
validateMailTemplatesAdminSettings(adminSettingsUpdateMsg);
if (adminSettingsUpdateMsg.getKey().equals("connectivity")) {
validateConnectivityAdminSettings(adminSettingsUpdateMsg);
}
}
}
private void validateGeneralAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("baseUrl"));
}
private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("mailFrom"));
@ -436,19 +454,19 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Assert.assertNotNull(jsonNode.get("timeout"));
}
private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
private void validateConnectivityAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("accountActivated"));
Assert.assertNotNull(jsonNode.get("accountLockout"));
Assert.assertNotNull(jsonNode.get("activation"));
Assert.assertNotNull(jsonNode.get("passwordWasReset"));
Assert.assertNotNull(jsonNode.get("resetPassword"));
Assert.assertNotNull(jsonNode.get("test"));
Assert.assertNotNull(jsonNode.get("http"));
Assert.assertNotNull(jsonNode.get("https"));
Assert.assertNotNull(jsonNode.get("mqtt"));
Assert.assertNotNull(jsonNode.get("mqtts"));
Assert.assertNotNull(jsonNode.get("coap"));
Assert.assertNotNull(jsonNode.get("coaps"));
}
private void validateAssetProfiles() throws Exception {
private void validateAssetProfiles(int expectedMsgCnt) throws Exception {
List<AssetProfileUpdateMsg> assetProfileUpdateMsgs = edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class);
Assert.assertEquals(3, assetProfileUpdateMsgs.size());
Assert.assertEquals(expectedMsgCnt, assetProfileUpdateMsgs.size());
AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileUpdateMsgs.get(0);
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType());
UUID assetProfileUUID = new UUID(assetProfileUpdateMsg.getIdMSB(), assetProfileUpdateMsg.getIdLSB());

4
application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java

@ -173,7 +173,7 @@ public class AssetEdgeTest extends AbstractEdgeTest {
assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetUpdateMsgBuilder.setName("Asset Edge 2");
assetUpdateMsgBuilder.setType("test");
assetUpdateMsgBuilder.setType("default");
assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder);
uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build());
@ -205,7 +205,7 @@ public class AssetEdgeTest extends AbstractEdgeTest {
assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetUpdateMsgBuilder.setName(assetOnCloudName);
assetUpdateMsgBuilder.setType("test");
assetUpdateMsgBuilder.setType("default");
assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder);
uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build());

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

@ -492,7 +492,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceUpdateMsgBuilder.setName(deviceOnCloudName);
deviceUpdateMsgBuilder.setType("test");
deviceUpdateMsgBuilder.setType("default");
deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder);
uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());
@ -541,7 +541,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceUpdateMsgBuilder.setName("Edge Device 2");
deviceUpdateMsgBuilder.setType("test");
deviceUpdateMsgBuilder.setType("default");
deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());

51
application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java

@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import java.util.Optional;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -121,4 +122,54 @@ public class ResourceEdgeTest extends AbstractEdgeTest {
Assert.assertEquals("Edge Test Resource", tbResource.getName());
Assert.assertEquals(TEST_DATA, tbResource.getEncodedData());
}
@Test
public void testResourceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle("Edge Test Resource");
resource.setFileName(FILE_NAME);
resource.setData(TEST_DATA);
edgeImitator.expectMessageAmount(1);
TbResource savedResource = doPost("/api/resource", resource, TbResource.class);
Assert.assertTrue(edgeImitator.waitForMessages());
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder();
resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
resourceUpdateMsgBuilder.setTitle("Edge Test Resource");
resourceUpdateMsgBuilder.setResourceType(ResourceType.JKS.name());
resourceUpdateMsgBuilder.setResourceKey(FILE_NAME);
resourceUpdateMsgBuilder.setFileName(FILE_NAME);
resourceUpdateMsgBuilder.setData(TEST_DATA);
resourceUpdateMsgBuilder.setIsSystem(false);
resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder);
uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<ResourceUpdateMsg> resourceUpdateMsgOpt = edgeImitator.findMessageByType(ResourceUpdateMsg.class);
Assert.assertTrue(resourceUpdateMsgOpt.isPresent());
ResourceUpdateMsg latestResourceUpdateMsg = resourceUpdateMsgOpt.get();
Assert.assertNotEquals(FILE_NAME, latestResourceUpdateMsg.getResourceKey());
Assert.assertNotEquals(savedResource.getUuidId(), uuid);
TbResource tbResource = doGet("/api/resource/" + uuid, TbResource.class);
Assert.assertNotNull(tbResource);
Assert.assertNotEquals(FILE_NAME, tbResource.getName());
}
}

42
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.telemetry;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class TelemetryEdgeProcessorTest {
@Test
public void testConvert_maxSizeLimit() throws Exception {
EdgeEvent edgeEvent = new EdgeEvent();
ObjectNode body = JacksonUtil.newObjectNode();
body.put("value", StringUtils.randomAlphanumeric(10000));
edgeEvent.setBody(body);
DownlinkMsg downlinkMsg = new TelemetryEdgeProcessor().convertTelemetryEventToDownlink(edgeEvent);
Assert.assertNull(downlinkMsg);
}
}

4
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -113,15 +113,13 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testSubscribingToUnreadNotificationsCount() {
wsClient.subscribeForUnreadNotificationsCount().waitForReply(true);
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
String notificationText1 = "Notification 1";
submitNotificationRequest(notificationTarget.getId(), notificationText1);
String notificationText2 = "Notification 2";
submitNotificationRequest(notificationTarget.getId(), notificationText2);
wsClient.subscribeForUnreadNotificationsCount();
wsClient.waitForReply(true);
await().atMost(2, TimeUnit.SECONDS)
.until(() -> wsClient.getLastCountUpdate().getTotalUnreadCount() == 2);
}

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.coap;
import lombok.Getter;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
@ -34,7 +35,10 @@ public class CoapTestClient {
private final CoapClient client;
public CoapTestClient(){
@Getter
private CoAP.Type type = CoAP.Type.CON;
public CoapTestClient() {
this.client = createClient();
}
@ -80,9 +84,13 @@ public class CoapTestClient {
return client.setTimeout(CLIENT_REQUEST_TIMEOUT).get();
}
public CoapObserveRelation getObserveRelation(CoapTestCallback callback){
public CoapObserveRelation getObserveRelation(CoapTestCallback callback) {
return getObserveRelation(callback, true);
}
public CoapObserveRelation getObserveRelation(CoapTestCallback callback, boolean confirmable) {
Request request = Request.newGet().setObserve();
request.setType(CoAP.Type.CON);
request.setType(confirmable ? CoAP.Type.CON : CoAP.Type.NON);
return client.observe(request, callback);
}
@ -94,12 +102,28 @@ public class CoapTestClient {
}
public void setURI(String accessToken, FeatureType featureType) {
if (featureType == null){
if (featureType == null) {
featureType = FeatureType.ATTRIBUTES;
}
setURI(getFeatureTokenUrl(accessToken, featureType));
}
public void useCONs() {
if (client == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
type = CoAP.Type.CON;
client.useCONs();
}
public void useNONs() {
if (client == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
type = CoAP.Type.NON;
client.useNONs();
}
private CoapClient createClient() {
return new CoapClient();
}

309
application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java

@ -0,0 +1,309 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap.client;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
import org.thingsboard.server.transport.coap.CoapTestCallback;
import org.thingsboard.server.transport.coap.CoapTestClient;
import org.thingsboard.server.transport.coap.CoapTestConfigProperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.query.EntityKeyType.CLIENT_ATTRIBUTE;
import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTRIBUTE;
@Slf4j
@DaoSqlTest
public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
private static final List<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
@Before
public void beforeTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
.deviceName("Test Post Attributes device")
.build();
processBeforeTest(configProperties);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testConfirmableRequests() throws Exception {
boolean confirmable = true;
processAttributesTest(confirmable);
processTwoWayRpcTest(confirmable);
processTestRequestAttributesValuesFromTheServer(confirmable);
}
@Test
public void testNonConfirmableRequests() throws Exception {
boolean confirmable = false;
processAttributesTest(confirmable);
processTwoWayRpcTest(confirmable);
processTestRequestAttributesValuesFromTheServer(confirmable);
}
protected void processAttributesTest(boolean confirmable) throws Exception {
client = createClientForFeatureWithConfirmableParameter(FeatureType.ATTRIBUTES, confirmable);
CoapResponse coapResponse = client.postMethod(PAYLOAD_VALUES_STR.getBytes());
assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode());
assertEquals("CoAP response type is wrong!", client.getType(), coapResponse.advanced().getType());
DeviceId deviceId = savedDevice.getId();
List<String> actualKeys = getActualKeysList(deviceId);
assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys);
Set<String> expectedKeySet = new HashSet<>(EXPECTED_KEYS);
assertEquals(expectedKeySet, actualKeySet);
String attributesValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/attributes/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
;
List<Map<String, Object>> values = doGetAsyncTyped(attributesValuesUrl, new TypeReference<>() {
});
assertAttributesValues(values, actualKeySet);
String deleteAttributesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
doDelete(deleteAttributesUrl);
}
protected void processTwoWayRpcTest(boolean confirmable) throws Exception {
client = createClientForFeatureWithConfirmableParameter(FeatureType.RPC, confirmable);
CoapTestCallback callbackCoap = new TestCoapCallbackForRPC(client);
CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap, confirmable);
String awaitAlias = "await Two Way Rpc (client.getObserveRelation)";
await(awaitAlias)
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> CoAP.ResponseCode.VALID.equals(callbackCoap.getResponseCode()) &&
callbackCoap.getObserve() != null &&
0 == callbackCoap.getObserve());
validateCurrentStateNotification(callbackCoap);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
int expectedObserveCountAfterGpioRequest1 = callbackCoap.getObserve() + 1;
String actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
awaitAlias = "await Two Way Rpc (setGpio(method, params, value) first";
await(awaitAlias)
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) &&
callbackCoap.getObserve() != null &&
expectedObserveCountAfterGpioRequest1 == callbackCoap.getObserve());
validateTwoWayStateChangedNotification(callbackCoap, actualResult);
int expectedObserveCountAfterGpioRequest2 = callbackCoap.getObserve() + 1;
actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
awaitAlias = "await Two Way Rpc (setGpio(method, params, value) second";
await(awaitAlias)
.atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) &&
callbackCoap.getObserve() != null &&
expectedObserveCountAfterGpioRequest2 == callbackCoap.getObserve());
validateTwoWayStateChangedNotification(callbackCoap, actualResult);
observeRelation.proactiveCancel();
assertTrue(observeRelation.isCanceled());
}
protected void processTestRequestAttributesValuesFromTheServer(boolean confirmable) throws Exception {
client = createClientForFeatureWithConfirmableParameter(FeatureType.ATTRIBUTES, confirmable);
SingleEntityFilter dtf = new SingleEntityFilter();
dtf.setSingleEntity(savedDevice.getId());
List<EntityKey> csKeys = getEntityKeys(CLIENT_ATTRIBUTE);
List<EntityKey> shKeys = getEntityKeys(SHARED_ATTRIBUTE);
List<EntityKey> keys = new ArrayList<>();
keys.addAll(csKeys);
keys.addAll(shKeys);
getWsClient().subscribeLatestUpdate(keys, dtf);
getWsClient().registerWaitForUpdate(2);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE",
PAYLOAD_VALUES_STR, String.class, status().isOk());
CoapResponse coapResponse = client.postMethod(PAYLOAD_VALUES_STR);
assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode());
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
String keysParam = String.join(",", EXPECTED_KEYS);
String featureTokenUrl = CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + keysParam + "&sharedKeys=" + keysParam;
client.setURI(featureTokenUrl);
CoapResponse response = client.getMethod();
assertEquals("CoAP response type is wrong!", client.getType(), response.advanced().getType());
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected void assertAttributesValues(List<Map<String, Object>> deviceValues, Set<String> keySet) {
for (Map<String, Object> map : deviceValues) {
String key = (String) map.get("key");
Object value = map.get("value");
assertTrue(keySet.contains(key));
switch (key) {
case "key1":
assertEquals("value1", value);
break;
case "key2":
assertEquals(true, value);
break;
case "key3":
assertEquals(3.0, value);
break;
case "key4":
assertEquals(4, value);
break;
case "key5":
assertNotNull(value);
assertEquals(3, ((LinkedHashMap) value).size());
assertEquals(42, ((LinkedHashMap) value).get("someNumber"));
assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray"));
LinkedHashMap<String, String> someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject");
assertEquals("value", someNestedObject.get("key"));
break;
}
}
}
private List<String> getActualKeysList(DeviceId deviceId) throws Exception {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 5000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {
});
if (actualKeys.size() == EXPECTED_KEYS.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
return actualKeys;
}
private void validateCurrentStateNotification(CoapTestCallback callback) {
assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes());
}
private void validateTwoWayStateChangedNotification(CoapTestCallback callback, String actualResult) {
assertEquals(DEVICE_RESPONSE, actualResult);
assertNotNull(callback.getPayloadBytes());
}
protected class TestCoapCallbackForRPC extends CoapTestCallback {
private final CoapTestClient client;
@Getter
private boolean wasSuccessful = false;
TestCoapCallbackForRPC(CoapTestClient client) {
this.client = client;
}
@Override
public void onLoad(CoapResponse response) {
payloadBytes = response.getPayload();
responseCode = response.getCode();
observe = response.getOptions().getObserve();
wasSuccessful = client.getType().equals(response.advanced().getType());
if (observe != null) {
if (observe > 0) {
processOnLoadResponse(response, client);
}
}
}
@Override
public void onError() {
log.warn("Command Response Ack Error, No connect");
}
}
protected void processOnLoadResponse(CoapResponse response, CoapTestClient client) {
JsonNode responseJson = JacksonUtil.fromBytes(response.getPayload());
int requestId = responseJson.get("id").asInt();
client.setURI(CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.RPC, requestId));
client.postMethod(new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
log.warn("RPC {} command response ack: {}", requestId, response.getCode());
}
@Override
public void onError() {
log.warn("RPC {} command response ack error, no connect", requestId);
}
}, DEVICE_RESPONSE, MediaTypeRegistry.APPLICATION_JSON);
}
private CoapTestClient createClientForFeatureWithConfirmableParameter(FeatureType featureType, boolean confirmable) {
CoapTestClient coapTestClient = new CoapTestClient(accessToken, featureType);
if (confirmable) {
coapTestClient.useCONs();
} else {
coapTestClient.useNONs();
}
return coapTestClient;
}
private List<EntityKey> getEntityKeys(EntityKeyType scope) {
return CoapClientIntegrationTest.EXPECTED_KEYS.stream().map(key -> new EntityKey(scope, key)).collect(Collectors.toList());
}
}

105
application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java

@ -0,0 +1,105 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.utils;
import com.fasterxml.jackson.databind.node.NullNode;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TbNodeUpgradeUtilsTest {
@Test
public void testUpgradeRuleNodeConfigurationWithNullConfig() throws Exception {
// GIVEN
var node = new RuleNode();
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetAttributesNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
@Test
public void testUpgradeRuleNodeConfigurationWithNullNodeConfig() throws Exception {
// GIVEN
var node = new RuleNode();
node.setConfiguration(NullNode.instance);
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetAttributesNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
@Test
public void testUpgradeRuleNodeConfigurationWithNonNullConfig() throws Exception {
// GIVEN
var node = new RuleNode();
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetAttributesNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
String versionZeroDefaultConfigStr = "{\"fetchToData\":false," +
"\"clientAttributeNames\":[]," +
"\"sharedAttributeNames\":[]," +
"\"serverAttributeNames\":[]," +
"\"latestTsKeyNames\":[]," +
"\"tellFailureIfAbsent\":true," +
"\"getLatestValueWithTs\":false}";
node.setConfiguration(JacksonUtil.toJsonNode(versionZeroDefaultConfigStr));
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
}

2
common/actor/pom.xml

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

2
common/cache/pom.xml

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

2
common/cluster-api/pom.xml

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

14
common/cluster-api/src/main/proto/queue.proto

@ -255,6 +255,12 @@ message GetOrCreateDeviceFromGatewayRequestMsg {
message GetOrCreateDeviceFromGatewayResponseMsg {
DeviceInfoProto deviceInfo = 1;
bytes profileBody = 2;
TransportApiRequestErrorCode error = 3;
}
enum TransportApiRequestErrorCode {
UNKNOWN_TRANSPORT_API_ERROR = 0;
ENTITY_LIMIT = 1;
}
message GetEntityProfileRequestMsg {
@ -910,8 +916,8 @@ message EdgeNotificationMsgProto {
string body = 10;
PostTelemetryMsg postTelemetryMsg = 11;
PostAttributeMsg postAttributesMsg = 12;
int64 sourceEdgeIdMSB = 13;
int64 sourceEdgeIdLSB = 14;
int64 originatorEdgeIdMSB = 13;
int64 originatorEdgeIdLSB = 14;
}
message EdgeEventUpdateMsgProto {
@ -945,8 +951,8 @@ message DeviceEdgeUpdateMsgProto {
int64 tenantIdLSB = 2;
int64 deviceIdMSB = 3;
int64 deviceIdLSB = 4;
int64 edgeIdMSB = 5;
int64 edgeIdLSB = 6;
optional int64 edgeIdMSB = 5;
optional int64 edgeIdLSB = 6;
}
message DeviceNameOrTypeUpdateMsgProto {

2
common/coap-server/pom.xml

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

2
common/dao-api/pom.xml

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

2
common/data/pom.xml

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

1
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -49,6 +49,7 @@ public class DataConstants {
public static final String MQTT_TRANSPORT_NAME = "MQTT";
public static final String HTTP_TRANSPORT_NAME = "HTTP";
public static final String SNMP_TRANSPORT_NAME = "SNMP";
public static final String MAXIMUM_NUMBER_OF_DEVICES_REACHED = "Maximum number of devices reached!";
public static final String[] allScopes() {

2
common/edge-api/pom.xml

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

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

@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.setConnectRequestMsg(ConnectRequestMsg.newBuilder()
.setEdgeRoutingKey(edgeKey)
.setEdgeSecret(edgeSecret)
.setEdgeVersion(EdgeVersion.V_3_6_0)
.setEdgeVersion(EdgeVersion.V_3_6_1)
.setMaxInboundMessageSize(maxInboundMessageSize)
.build())
.build());

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

@ -35,6 +35,7 @@ enum EdgeVersion {
V_3_3_3 = 1;
V_3_4_0 = 2;
V_3_6_0 = 3;
V_3_6_1 = 4;
}
/**
@ -361,8 +362,8 @@ message WidgetsBundleUpdateMsg {
optional bytes image = 6;
bool isSystem = 7;
optional string description = 8;
optional int32 order = 9;
optional string widgets = 10;
optional string widgets = 9;
optional int32 order = 10;
}
message WidgetTypeUpdateMsg {

2
common/message/pom.xml

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

2
common/pom.xml

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

2
common/queue/pom.xml

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

2
common/script/pom.xml

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

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

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

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

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

24
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -61,9 +61,9 @@ public class TbUtils {
parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson",
ExecutionContext.class, String.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class)));
ExecutionContext.class, Object.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class, String.class)));
ExecutionContext.class, Object.class, String.class)));
parserConfig.registerNonConvertableMethods(TbUtils.class, Collections.singleton("stringToBytes"));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",
String.class)));
@ -192,14 +192,22 @@ public class TbUtils {
return new String(bytes, charsetName);
}
public static List<Byte> stringToBytes(ExecutionContext ctx, String str) {
byte[] bytes = str.getBytes();
return bytesToList(ctx, bytes);
public static List<Byte> stringToBytes(ExecutionContext ctx, Object str) throws IllegalAccessException {
if (str instanceof String) {
byte[] bytes = str.toString().getBytes();
return bytesToList(ctx, bytes);
} else {
throw new IllegalAccessException("Invalid type parameter [" + str.getClass().getSimpleName() + "]. Expected 'String'");
}
}
public static List<Byte> stringToBytes(ExecutionContext ctx, String str, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = str.getBytes(charsetName);
return bytesToList(ctx, bytes);
public static List<Byte> stringToBytes(ExecutionContext ctx, Object str, String charsetName) throws UnsupportedEncodingException, IllegalAccessException {
if (str instanceof String) {
byte[] bytes = str.toString().getBytes(charsetName);
return bytesToList(ctx, bytes);
} else {
throw new IllegalAccessException("Invalid type parameter [" + str.getClass().getSimpleName() + "]. Expected 'String'");
}
}
private static byte[] bytesFromList(List<Byte> bytesList) {

20
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -350,7 +350,7 @@ public class TbUtilsTest {
}
@Test
public void parseBytesDecodeToJson() throws IOException {
public void parseBytesDecodeToJson() throws IOException, IllegalAccessException {
String expectedStr = "{\"hello\": \"world\"}";
ExecutionHashMap<String, Object> expectedJson = new ExecutionHashMap<>(1, ctx);
expectedJson.put("hello", "world");
@ -367,6 +367,24 @@ public class TbUtilsTest {
Assert.assertEquals(expectedJson,actualJson);
}
@Test
public void stringToBytesInputObjectTest() throws IOException, IllegalAccessException {
String expectedStr = "{\"hello\": \"world\"}";
Object inputJson = TbUtils.decodeToJson(ctx, expectedStr);
byte[] arrayBytes = {119, 111, 114, 108, 100};
List<Byte> expectedBytes = Bytes.asList(arrayBytes);
List<Byte> actualBytes = TbUtils.stringToBytes(ctx, ((ExecutionHashMap) inputJson).get("hello"));
Assert.assertEquals(expectedBytes, actualBytes);
actualBytes = TbUtils.stringToBytes(ctx, ((ExecutionHashMap) inputJson).get("hello"), "UTF-8");
Assert.assertEquals(expectedBytes, actualBytes);
expectedStr = "{\"hello\": 1234}";
inputJson = TbUtils.decodeToJson(ctx, expectedStr);
Object finalInputJson = inputJson;
Assert.assertThrows(IllegalAccessException.class, () -> TbUtils.stringToBytes(ctx, ((ExecutionHashMap) finalInputJson).get("hello")));
Assert.assertThrows(IllegalAccessException.class, () -> TbUtils.stringToBytes(ctx, ((ExecutionHashMap) finalInputJson).get("hello"), "UTF-8"));
}
private static List<Byte> toList(byte[] data) {
List<Byte> result = new ArrayList<>(data.length);
for (Byte b : data) {

2
common/stats/pom.xml

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

2
common/transport/coap/pom.xml

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

1
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/AbstractSyncSessionCallback.java

@ -81,6 +81,7 @@ public abstract class AbstractSyncSessionCallback implements SessionMsgListener
protected void respond(Response response) {
response.getOptions().setContentFormat(TbCoapContentFormatUtil.getContentFormat(exchange.getRequestOptions().getContentFormat(), state.getContentFormat()));
response.setConfirmable(exchange.advanced().getRequest().isConfirmable());
exchange.respond(response);
}

4
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java

@ -34,7 +34,9 @@ public class CoapOkCallback implements TransportServiceCallback<Void> {
@Override
public void onSuccess(Void msg) {
exchange.respond(new Response(onSuccessResponse));
Response response = new Response(onSuccessResponse);
response.setConfirmable(isConRequest());
exchange.respond(response);
}
@Override

2
common/transport/http/pom.xml

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

2
common/transport/lwm2m/pom.xml

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

2
common/transport/mqtt/pom.xml

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

22
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java

@ -35,6 +35,7 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.TransportService;
@ -215,8 +216,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
@Override
public void onFailure(Throwable t) {
log.warn("[{}][{}][{}] Failed to process device connect command: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, t);
logDeviceCreationError(t, deviceName);
}
}, context.getExecutor());
}
@ -248,7 +248,8 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
return future;
}
try {
transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
transportService.process(gateway.getTenantId(),
GetOrCreateDeviceFromGatewayRequestMsg.newBuilder()
.setDeviceName(deviceName)
.setDeviceType(deviceType)
.setGatewayIdMSB(gateway.getDeviceId().getId().getMostSignificantBits())
@ -274,9 +275,9 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
}
@Override
public void onError(Throwable e) {
log.warn("[{}][{}][{}] Failed to process device connect command at getDeviceCreationFuture: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, e);
futureToSet.setException(e);
public void onError(Throwable t) {
logDeviceCreationError(t, deviceName);
futureToSet.setException(t);
deviceFutures.remove(deviceName);
}
});
@ -287,6 +288,15 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
}
}
private void logDeviceCreationError(Throwable t, String deviceName) {
if (DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED.equals(t.getMessage())) {
log.info("[{}][{}][{}] Failed to process device connect command: [{}] due to [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName,
DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED);
} else {
log.warn("[{}][{}][{}] Failed to process device connect command: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName, t);
}
}
protected abstract T newDeviceSessionCtx(GetOrCreateDeviceFromGatewayResponse msg);
protected int getMsgId(MqttPublishMessage mqttMsg) {

2
common/transport/pom.xml

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

2
common/transport/snmp/pom.xml

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

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

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

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -98,7 +98,7 @@ public interface TransportService {
void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
void process(TenantId tenantId, GetOrCreateDeviceFromGatewayRequestMsg msg,
TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
void process(ProvisionDeviceRequestMsg msg,

74
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultEntityLimitsCache.java

@ -0,0 +1,74 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.limits;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Service
@TbTransportComponent
@Slf4j
public class DefaultEntityLimitsCache implements EntityLimitsCache {
private static final int DEVIATION = 10;
private final Cache<EntityLimitKey, Boolean> cache;
public DefaultEntityLimitsCache(@Value("${cache.entityLimits.timeToLiveInMinutes:5}") int ttl,
@Value("${cache.entityLimits.maxSize:100000}") int maxSize) {
// We use the 'random' expiration time to avoid peak loads.
long mainPart = (TimeUnit.MINUTES.toNanos(ttl) / 100) * (100 - DEVIATION);
long randomPart = (TimeUnit.MINUTES.toNanos(ttl) / 100) * DEVIATION;
cache = Caffeine.newBuilder()
.expireAfter(new Expiry<EntityLimitKey, Boolean>() {
@Override
public long expireAfterCreate(@NotNull EntityLimitKey key, @NotNull Boolean value, long currentTime) {
return mainPart + (long) (randomPart * ThreadLocalRandom.current().nextDouble());
}
@Override
public long expireAfterUpdate(@NotNull EntityLimitKey key, @NotNull Boolean value, long currentTime, long currentDuration) {
return currentDuration;
}
@Override
public long expireAfterRead(@NotNull EntityLimitKey key, @NotNull Boolean value, long currentTime, long currentDuration) {
return currentDuration;
}
})
.maximumSize(maxSize)
.build();
}
@Override
public boolean get(EntityLimitKey key) {
var result = cache.getIfPresent(key);
return result != null ? result : false;
}
@Override
public void put(EntityLimitKey key, boolean value) {
cache.put(key, value);
}
}

27
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitKey.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.limits;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class EntityLimitKey {
private final TenantId tenantId;
private final String deviceName;
}

24
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitsCache.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.limits;
public interface EntityLimitsCache {
boolean get(EntityLimitKey key);
void put(EntityLimitKey key, boolean value);
}

48
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -76,6 +76,8 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.limits.EntityLimitKey;
import org.thingsboard.server.common.transport.limits.EntityLimitsCache;
import org.thingsboard.server.common.transport.limits.TransportRateLimitService;
import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -161,6 +163,7 @@ public class DefaultTransportService implements TransportService {
@Value("${transport.stats.enabled:false}")
private boolean statsEnabled;
@Autowired
@Lazy
private TbApiUsageReportClient apiUsageClient;
@ -184,6 +187,8 @@ public class DefaultTransportService implements TransportService {
private final TransportResourceCache transportResourceCache;
private final NotificationRuleProcessor notificationRuleProcessor;
private final EntityLimitsCache entityLimitsCache;
protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate;
protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
protected TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> tbCoreMsgProducer;
@ -212,7 +217,8 @@ public class DefaultTransportService implements TransportService {
TransportTenantProfileCache tenantProfileCache,
TransportRateLimitService rateLimitService,
DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler, TransportResourceCache transportResourceCache,
ApplicationEventPublisher eventPublisher, NotificationRuleProcessor notificationRuleProcessor) {
ApplicationEventPublisher eventPublisher, NotificationRuleProcessor notificationRuleProcessor,
EntityLimitsCache entityLimitsCache) {
this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.queueProvider = queueProvider;
@ -227,6 +233,7 @@ public class DefaultTransportService implements TransportService {
this.transportResourceCache = transportResourceCache;
this.eventPublisher = eventPublisher;
this.notificationRuleProcessor = notificationRuleProcessor;
this.entityLimitsCache = entityLimitsCache;
}
@PostConstruct
@ -249,7 +256,7 @@ public class DefaultTransportService implements TransportService {
}
@AfterStartUp(order = AfterStartUp.TRANSPORT_SERVICE)
private void start() {
public void start() {
mainConsumerExecutor.execute(() -> {
while (!stopped) {
try {
@ -473,24 +480,33 @@ public class DefaultTransportService implements TransportService {
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
public void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback) {
public void process(TenantId tenantId, TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback) {
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build());
log.trace("Processing msg: {}", requestMsg);
ListenableFuture<GetOrCreateDeviceFromGatewayResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg();
GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder();
if (msg.hasDeviceInfo()) {
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) {
result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody));
var key = new EntityLimitKey(tenantId, StringUtils.truncate(requestMsg.getDeviceName(), 256));
if (entityLimitsCache.get(key)) {
transportCallbackExecutor.submit(() -> callback.onError(new RuntimeException(DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED)));
} else {
ListenableFuture<GetOrCreateDeviceFromGatewayResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg();
GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder();
if (msg.hasDeviceInfo()) {
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (!profileBody.isEmpty()) {
result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody));
}
} else if (TransportProtos.TransportApiRequestErrorCode.ENTITY_LIMIT.equals(msg.getError())) {
entityLimitsCache.put(key, true);
throw new RuntimeException(DataConstants.MAXIMUM_NUMBER_OF_DEVICES_REACHED);
}
}
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
}
@Override

2
common/util/pom.xml

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

2
common/version-control/pom.xml

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

2
dao/pom.xml

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

35
dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.exception;
import lombok.Getter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
public class EntitiesLimitException extends DataValidationException {
private static final long serialVersionUID = -9211462514373279196L;
@Getter
private final TenantId tenantId;
@Getter
private final EntityType entityType;
public EntitiesLimitException(TenantId tenantId, EntityType entityType) {
super(entityType.getNormalName() + "s limit reached");
this.tenantId = tenantId;
this.entityType = entityType;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -155,7 +155,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
if (ruleChain == null) {
return RuleChainUpdateResult.failed();
}
RuleChainDataValidator.validateMetaData(ruleChainMetaData);
RuleChainDataValidator.validateMetaDataFieldsAndConnections(ruleChainMetaData);
List<RuleNode> nodes = ruleChainMetaData.getNodes();
List<RuleNode> toAddOrUpdate = new ArrayList<>();
@ -194,6 +194,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
for (RuleNode node : toAddOrUpdate) {
node.setRuleChainId(ruleChainId);
node = ruleNodeUpdater.apply(node);
RuleChainDataValidator.validateRuleNode(node);
RuleNode savedNode = ruleNodeDao.save(tenantId, node);
relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));

3
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet;
@ -117,7 +118,7 @@ public abstract class DataValidator<D extends BaseData<?>> {
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
EntityType entityType) {
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
throw new DataValidationException(entityType.getNormalName() + "s limit reached");
throw new EntitiesLimitException(tenantId, entityType);
}
}

9
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -87,15 +87,18 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
}
public static List<Throwable> validateMetaData(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData);
List<Throwable> throwables = ruleChainMetaData.getNodes().stream()
validateMetaDataFieldsAndConnections(ruleChainMetaData);
return ruleChainMetaData.getNodes().stream()
.map(RuleChainDataValidator::validateRuleNode)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static void validateMetaDataFieldsAndConnections(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData);
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
}
return throwables;
}
public static Throwable validateRuleNode(RuleNode ruleNode) {

8
dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java

@ -91,10 +91,10 @@ public interface EdgeRepository extends JpaRepository<EdgeEntity, UUID> {
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId " +
"AND (:searchText IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)")
"AND (:textSearch IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)")
Page<EdgeInfoEntity> findEdgeInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.EdgeInfoEntity(a, c.title, c.additionalInfo) " +
@ -113,11 +113,11 @@ public interface EdgeRepository extends JpaRepository<EdgeEntity, UUID> {
@Query("SELECT ee FROM EdgeEntity ee, RelationEntity re WHERE ee.tenantId = :tenantId " +
"AND ee.id = re.fromId AND re.fromType = 'EDGE' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.toId = :entityId AND re.toType = :entityType " +
"AND (:searchText IS NULL OR ilike(ee.name, CONCAT('%', :searchText, '%')) = true)")
"AND (:textSearch IS NULL OR ilike(ee.name, CONCAT('%', :textSearch, '%')) = true)")
Page<EdgeEntity> findByTenantIdAndEntityId(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
@Param("entityType") String entityType,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT ee FROM EdgeEntity ee, TenantEntity te WHERE ee.tenantId = te.id AND te.tenantProfileId = :tenantProfileId ")

4
dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java

@ -46,7 +46,7 @@ public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UU
@Param("tenantId") UUID tenantId,
@Param("systemAdminId") UUID sysAdminId,
@Param("resourceType") String resourceType,
@Param("searchText") String search,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT tr FROM TbResourceEntity tr " +
@ -62,7 +62,7 @@ public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UU
List<TbResourceEntity> findResources(@Param("tenantId") UUID tenantId,
@Param("systemAdminId") UUID sysAdminId,
@Param("resourceType") String resourceType,
@Param("searchText") String search);
@Param("searchText") String searchText);
@Query("SELECT tr FROM TbResourceEntity tr " +
"WHERE tr.resourceType = :resourceType " +

10
dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java

@ -20,6 +20,9 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import java.util.Arrays;
import java.util.List;
public class DeviceConnectivityUtil {
public static final String HTTP = "http";
@ -38,6 +41,7 @@ public class DeviceConnectivityUtil {
public static final String GATEWAY_DOCKER_RUN = "docker run -it ";
public static final String MQTT_IMAGE = "thingsboard/mosquitto-clients ";
public static final String COAP_IMAGE = "thingsboard/coap-clients ";
public static final List<String> LOCAL_HOSTS = Arrays.asList("localhost", "127.0.0.1");
public static String getHttpPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) {
return String.format("curl -v -X POST %s://%s%s/api/v1/%s/telemetry --header Content-Type:application/json --data " + JSON_EXAMPLE_PAYLOAD,
@ -135,7 +139,7 @@ public class DeviceConnectivityUtil {
}
StringBuilder mqttDockerCommand = new StringBuilder();
mqttDockerCommand.append(DOCKER_RUN).append(MQTT_IMAGE);
mqttDockerCommand.append(DOCKER_RUN).append(LOCAL_HOSTS.contains(host) ? "--network=host ":"").append(MQTT_IMAGE);
if (MQTTS.equals(protocol)) {
mqttDockerCommand.append("/bin/sh -c \"")
@ -158,7 +162,7 @@ public class DeviceConnectivityUtil {
switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN:
String client = COAPS.equals(protocol) ? "coap-client-openssl" : "coap-client";
return String.format("%s -m POST %s://%s%s/api/v1/%s/telemetry -t json -e %s",
return String.format("%s -v 6 -m POST %s://%s%s/api/v1/%s/telemetry -t json -e %s",
client, protocol, host, port, deviceCredentials.getCredentialsId(), JSON_EXAMPLE_PAYLOAD);
default:
return null;
@ -167,6 +171,6 @@ public class DeviceConnectivityUtil {
public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) {
String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials);
return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN, COAP_IMAGE, coapCommand) : null;
return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN + (LOCAL_HOSTS.contains(host) ? "--network=host ":""), COAP_IMAGE, coapCommand) : null;
}
}

2
monitoring/pom.xml

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

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

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

33
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java

@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@ -74,8 +75,8 @@ public class ThingsBoardDbInstaller {
new File("./../../docker/docker-compose.yml"),
new File("./../../docker/docker-compose.volumes.yml"),
IS_HYBRID_MODE
? new File("./../../docker/docker-compose.hybrid.yml")
: new File("./../../docker/docker-compose.postgres.yml"),
? new File("./../../docker/docker-compose.hybrid.yml")
: new File("./../../docker/docker-compose.postgres.yml"),
new File("./../../docker/docker-compose.postgres.volumes.yml"),
resolveRedisComposeFile(),
resolveRedisComposeVolumesFile()
@ -156,7 +157,7 @@ public class ThingsBoardDbInstaller {
return env;
}
public void createVolumes() {
public void createVolumes() {
try {
dockerCompose.withCommand("volume create " + postgresDataVolume);
@ -200,7 +201,7 @@ public class ThingsBoardDbInstaller {
}
} else if (IS_REDIS_SENTINEL) {
additionalServices.append(" redis-master");
dockerCompose.withCommand("volume create " + redisSentinelDataVolume +"-" + "master");
dockerCompose.withCommand("volume create " + redisSentinelDataVolume + "-" + "master");
dockerCompose.invokeDocker();
additionalServices.append(" redis-slave");
@ -226,7 +227,8 @@ public class ThingsBoardDbInstaller {
try {
dockerCompose.withCommand("down -v");
dockerCompose.invokeCompose();
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
}
}
@ -239,15 +241,24 @@ public class ThingsBoardDbInstaller {
copyLogs(tbSnmpTransportLogVolume, "./target/tb-snmp-transport-logs/");
copyLogs(tbVcExecutorLogVolume, "./target/tb-vc-executor-logs/");
dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume +
" " + tbCoapTransportLogVolume + " " + tbLwm2mTransportLogVolume + " " + tbHttpTransportLogVolume +
" " + tbMqttTransportLogVolume + " " + tbSnmpTransportLogVolume + " " + tbVcExecutorLogVolume + resolveRedisComposeVolumeLog());
dockerCompose.invokeDocker();
StringJoiner rmVolumesCommand = new StringJoiner(" ")
.add("volume rm -f")
.add(postgresDataVolume)
.add(tbLogVolume)
.add(tbCoapTransportLogVolume)
.add(tbLwm2mTransportLogVolume)
.add(tbHttpTransportLogVolume)
.add(tbMqttTransportLogVolume)
.add(tbSnmpTransportLogVolume)
.add(tbVcExecutorLogVolume)
.add(resolveRedisComposeVolumeLog());
dockerCompose.withCommand(rmVolumesCommand.toString());
}
private String resolveRedisComposeVolumeLog() {
if (IS_REDIS_CLUSTER) {
return IntStream.range(0, 6).mapToObj(i -> " " + redisClusterDataVolume + "-" + i).collect(Collectors.joining());
return IntStream.range(0, 6).mapToObj(i -> redisClusterDataVolume + "-" + i).collect(Collectors.joining());
}
if (IS_REDIS_SENTINEL) {
return redisSentinelDataVolume + "-" + "master " + " " +
@ -266,7 +277,7 @@ public class ThingsBoardDbInstaller {
dockerCompose.withCommand("run -d --rm --name " + logsContainerName + " -v " + volumeName + ":/root alpine tail -f /dev/null");
dockerCompose.invokeDocker();
dockerCompose.withCommand("cp " + logsContainerName + ":/root/. "+tbLogsDir.getAbsolutePath());
dockerCompose.withCommand("cp " + logsContainerName + ":/root/. " + tbLogsDir.getAbsolutePath());
dockerCompose.invokeDocker();
dockerCompose.withCommand("rm -f " + logsContainerName);

2
msa/js-executor/package.json

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

2
msa/js-executor/pom.xml

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

2
msa/monitoring/pom.xml

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

2
msa/pom.xml

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

2
msa/tb-node/pom.xml

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

4
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>
@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.6.0</pkg.upgradeVersion>
<pkg.upgradeVersion>3.6.2</pkg.upgradeVersion>
</properties>
<dependencies>

2
msa/transport/coap/pom.xml

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

2
msa/transport/http/pom.xml

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

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

Loading…
Cancel
Save