Browse Source

Merge remote-tracking branch 'upstream/develop/2.5.5' into feature/edge

pull/2436/head
Volodymyr Babak 6 years ago
parent
commit
c526d13e45
  1. 2
      application/pom.xml
  2. 1
      application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
  3. 21
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  4. 8
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  5. 28
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  6. 59
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  7. 6
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  8. 5
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  9. 1
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java
  13. 31
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  15. 18
      application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java
  19. 3
      application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java
  20. 2
      application/src/main/resources/logback.xml
  21. 24
      application/src/main/resources/thingsboard.yml
  22. 23
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  23. 440
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  24. 2
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
  25. 2
      common/actor/pom.xml
  26. 2
      common/dao-api/pom.xml
  27. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
  28. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  29. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  30. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  31. 2
      common/data/pom.xml
  32. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  34. 2
      common/edge-api/pom.xml
  35. 2
      common/message/pom.xml
  36. 29
      common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java
  37. 52
      common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java
  38. 2
      common/pom.xml
  39. 2
      common/queue/pom.xml
  40. 10
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java
  41. 42
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  42. 9
      common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java
  43. 9
      common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java
  44. 13
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsAdmin.java
  45. 16
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsConsumerTemplate.java
  46. 16
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java
  47. 3
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsSettings.java
  48. 1
      common/queue/src/main/proto/queue.proto
  49. 2
      common/stats/pom.xml
  50. 2
      common/transport/coap/pom.xml
  51. 2
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
  52. 2
      common/transport/http/pom.xml
  53. 2
      common/transport/mqtt/pom.xml
  54. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  55. 1
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  56. 2
      common/transport/pom.xml
  57. 2
      common/transport/transport-api/pom.xml
  58. 35
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  59. 2
      common/util/pom.xml
  60. 2
      dao/pom.xml
  61. 2
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  62. 3
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java
  63. 18
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  64. 3
      dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
  65. 16
      dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
  66. 17
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  67. 45
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  68. 15
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  69. 16
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  70. 8
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
  71. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  72. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  73. 1
      dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java
  74. 4
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java
  75. 2
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  76. 11
      dao/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java
  77. 1
      dao/src/main/resources/sql/schema-ts-psql.sql
  78. 1
      dao/src/test/resources/sql/hsql/drop-all-tables.sql
  79. 5
      docker/compose-utils.sh
  80. 57
      docker/docker-compose.confluent.yml
  81. 18
      docker/queue-confluent.env
  82. 1
      docker/queue-kafka.env
  83. 2
      msa/black-box-tests/pom.xml
  84. 39
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
  85. 382
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
  86. 6
      msa/js-executor/config/custom-environment-variables.yml
  87. 6
      msa/js-executor/config/default.yml
  88. 23
      msa/js-executor/package-lock.json
  89. 2
      msa/js-executor/package.json
  90. 2
      msa/js-executor/pom.xml
  91. 38
      msa/js-executor/queue/kafkaTemplate.js
  92. 2
      msa/pom.xml
  93. 2
      msa/tb-node/pom.xml
  94. 2
      msa/tb/pom.xml
  95. 2
      msa/transport/coap/pom.xml
  96. 2
      msa/transport/http/pom.xml
  97. 2
      msa/transport/mqtt/pom.xml
  98. 2
      msa/transport/pom.xml
  99. 2
      msa/web-ui/package.json
  100. 2
      msa/web-ui/pom.xml

2
application/pom.xml

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

1
application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql

@ -64,6 +64,7 @@ BEGIN
AND tablename like 'ts_kv_' || '%' AND tablename like 'ts_kv_' || '%'
AND tablename != 'ts_kv_latest' AND tablename != 'ts_kv_latest'
AND tablename != 'ts_kv_dictionary' AND tablename != 'ts_kv_dictionary'
AND tablename != 'ts_kv_indefinite'
LOOP LOOP
IF partition != partition_by_max_ttl_date THEN IF partition != partition_by_max_ttl_date THEN
IF partition_year IS NOT NULL THEN IF partition_year IS NOT NULL THEN

21
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -79,8 +80,6 @@ import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
@ -279,17 +278,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture; ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture; ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE); clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE);
sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE); sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE);
} else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE); clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE); sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
} else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = Futures.immediateFuture(Collections.emptyList()); clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE); sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
} else { } else {
sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList()); sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE); clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
} }
return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)); return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
} }
@ -316,7 +315,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder(); AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
if (msg.isDeleted()) { if (msg.isDeleted()) {
List<String> sharedKeys = msg.getDeletedKeys().stream() List<String> sharedKeys = msg.getDeletedKeys().stream()
.filter(key -> SHARED_SCOPE.equals(key.getScope())) .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.map(AttributeKey::getAttributeKey) .map(AttributeKey::getAttributeKey)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!sharedKeys.isEmpty()) { if (!sharedKeys.isEmpty()) {
@ -324,7 +323,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
hasNotificationData = true; hasNotificationData = true;
} }
} else { } else {
if (SHARED_SCOPE.equals(msg.getScope())) { if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues()); List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
if (attributes.size() > 0) { if (attributes.size() > 0) {
List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto) List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
@ -334,7 +333,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
hasNotificationData = true; hasNotificationData = true;
} }
} else { } else {
log.debug("[{}] No public server side attributes changed!", deviceId); log.debug("[{}] No public shared side attributes changed!", deviceId);
} }
} }
} }

8
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -59,7 +60,11 @@ public class AdminController extends BaseController {
public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException { public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException {
try { try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key));
if (adminSettings.getKey().equals("mail")) {
((ObjectNode) adminSettings.getJsonValue()).put("password", "");
}
return adminSettings;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -74,6 +79,7 @@ public class AdminController extends BaseController {
adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
if (adminSettings.getKey().equals("mail")) { if (adminSettings.getKey().equals("mail")) {
mailService.updateMailConfiguration(); mailService.updateMailConfiguration();
((ObjectNode) adminSettings.getJsonValue()).put("password", "");
} }
return adminSettings; return adminSettings;
} catch (Exception e) { } catch (Exception e) {

28
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@ -624,6 +625,12 @@ public abstract class BaseController {
case ALARM_CLEAR: case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR; msgType = DataConstants.ALARM_CLEAR;
break; break;
case ASSIGNED_FROM_TENANT:
msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
break;
case ASSIGNED_TO_TENANT:
msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
break;
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
break; break;
@ -649,7 +656,17 @@ public abstract class BaseController {
String strCustomerName = extractParameter(String.class, 2, additionalInfo); String strCustomerName = extractParameter(String.class, 2, additionalInfo);
metaData.putValue("unassignedCustomerId", strCustomerId); metaData.putValue("unassignedCustomerId", strCustomerId);
metaData.putValue("unassignedCustomerName", strCustomerName); metaData.putValue("unassignedCustomerName", strCustomerName);
} if (actionType == ActionType.ASSIGNED_TO_EDGE) { } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
String strTenantId = extractParameter(String.class, 0, additionalInfo);
String strTenantName = extractParameter(String.class, 1, additionalInfo);
metaData.putValue("assignedFromTenantId", strTenantId);
metaData.putValue("assignedFromTenantName", strTenantName);
} else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
String strTenantId = extractParameter(String.class, 0, additionalInfo);
String strTenantName = extractParameter(String.class, 1, additionalInfo);
metaData.putValue("assignedToTenantId", strTenantId);
metaData.putValue("assignedToTenantName", strTenantName);
} else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
String strEdgeId = extractParameter(String.class, 1, additionalInfo); String strEdgeId = extractParameter(String.class, 1, additionalInfo);
metaData.putValue("assignedEdgeId", strEdgeId); metaData.putValue("assignedEdgeId", strEdgeId);
} else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) { } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
@ -718,6 +735,15 @@ public abstract class BaseController {
return result; return result;
} }
protected <E extends HasName> String entityToStr(E entity) {
try {
return json.writeValueAsString(json.valueToTree(entity));
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to convert entity to string!", entity, e);
}
return null;
}
protected void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, CustomerId customerId, ActionType edgeEventAction) { protected void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, CustomerId customerId, ActionType edgeEventAction) {
try { try {
sendNotificationMsgToEdgeService(tenantId, edgeId, null, json.writeValueAsString(customerId), EdgeEventType.EDGE, edgeEventAction); sendNotificationMsgToEdgeService(tenantId, edgeId, null, json.writeValueAsString(customerId), EdgeEventType.EDGE, edgeEventAction);

59
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -39,10 +39,11 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -52,13 +53,14 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
@ -79,6 +81,7 @@ public class DeviceController extends BaseController {
private static final String DEVICE_ID = "deviceId"; private static final String DEVICE_ID = "deviceId";
private static final String DEVICE_NAME = "deviceName"; private static final String DEVICE_NAME = "deviceName";
private static final String TENANT_ID = "tenantId";
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET) @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
@ -505,6 +508,56 @@ public class DeviceController extends BaseController {
return DataConstants.DEFAULT_SECRET_KEY; return DataConstants.DEFAULT_SECRET_KEY;
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId,
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(TENANT_ID, strTenantId);
checkParameter(DEVICE_ID, strDeviceId);
try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT);
TenantId newTenantId = new TenantId(toUUID(strTenantId));
Tenant newTenant = tenantService.findTenantById(newTenantId);
if (newTenant == null) {
throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device);
logEntityAction(getCurrentUser(), deviceId, assignedDevice,
assignedDevice.getCustomerId(),
ActionType.ASSIGNED_TO_TENANT, null, strTenantId, newTenant.getName());
Tenant currentTenant = tenantService.findTenantById(getTenantId());
pushAssignedFromNotification(currentTenant, newTenantId, assignedDevice);
return assignedDevice;
} catch (Exception e) {
logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null,
null,
ActionType.ASSIGNED_TO_TENANT, e, strTenantId);
throw handleException(e);
}
}
private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) {
String data = entityToStr(assignedDevice);
if (data != null) {
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data);
tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null);
}
}
private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString());
metaData.putValue("assignedFromTenantName", tenant.getName());
return metaData;
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody

6
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -197,19 +197,21 @@ public class TelemetryController extends BaseController {
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"}) @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
@ResponseBody @ResponseBody
public DeferredResult<ResponseEntity> getTimeseries( public DeferredResult<ResponseEntity> getTimeseries(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr, @PathVariable("entityType") String entityType,
@PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys") String keys, @RequestParam(name = "keys") String keys,
@RequestParam(name = "startTs") Long startTs, @RequestParam(name = "startTs") Long startTs,
@RequestParam(name = "endTs") Long endTs, @RequestParam(name = "endTs") Long endTs,
@RequestParam(name = "interval", defaultValue = "0") Long interval, @RequestParam(name = "interval", defaultValue = "0") Long interval,
@RequestParam(name = "limit", defaultValue = "100") Integer limit, @RequestParam(name = "limit", defaultValue = "100") Integer limit,
@RequestParam(name = "agg", defaultValue = "NONE") String aggStr, @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
@RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy,
@RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> { (result, tenantId, entityId) -> {
// If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr); Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg)) List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
.collect(Collectors.toList()); .collect(Collectors.toList());
Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());

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

@ -146,6 +146,11 @@ public class ThingsboardInstallService {
databaseTsUpgradeService.upgradeDatabase("2.5.0"); databaseTsUpgradeService.upgradeDatabase("2.5.0");
} }
case "2.5.4":
log.info("Upgrading ThingsBoard from version 2.5.4 to 2.5.5 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.5.4");
}
log.info("Updating system data..."); log.info("Updating system data...");

1
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -49,6 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
log.info("Schema updated."); log.info("Schema updated.");
break; break;
case "2.5.0": case "2.5.0":
case "2.5.4":
break; break;
default: default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

6
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -195,6 +195,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001"); executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
} }
break; break;
case "2.5.4":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Load Drop Partitions functions ...");
loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

2
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -177,6 +177,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001"); executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
} }
break; break;
case "2.5.4":
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

2
application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java

@ -18,7 +18,7 @@ package org.thingsboard.server.service.security.permission;
public enum Operation { public enum Operation {
ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL, ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL,
READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, ASSIGN_TO_TENANT,
ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE
} }

31
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -15,7 +15,7 @@
*/ */
package org.thingsboard.server.service.state; package org.thingsboard.server.service.state;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
@ -45,16 +45,17 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
@ -90,7 +91,6 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@Slf4j @Slf4j
public class DefaultDeviceStateService implements DeviceStateService { public class DefaultDeviceStateService implements DeviceStateService {
private static final ObjectMapper json = new ObjectMapper();
public static final String ACTIVITY_STATE = "active"; public static final String ACTIVITY_STATE = "active";
public static final String LAST_CONNECT_TIME = "lastConnectTime"; public static final String LAST_CONNECT_TIME = "lastConnectTime";
public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime"; public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime";
@ -197,15 +197,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) { if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) {
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (stateData != null) { if (stateData != null) {
DeviceState state = stateData.getState();
stateData.getState().setLastActivityTime(lastReportedActivity);
stateData.getMetaData().putValue("scope", SERVER_SCOPE);
pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity); save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity);
deviceLastSavedActivity.put(deviceId, lastReportedActivity); deviceLastSavedActivity.put(deviceId, lastReportedActivity);
DeviceState state = stateData.getState();
state.setLastActivityTime(lastReportedActivity);
if (!state.isActive()) { if (!state.isActive()) {
state.setActive(true); state.setActive(true);
save(deviceId, ACTIVITY_STATE, state.isActive()); save(deviceId, ACTIVITY_STATE, state.isActive());
stateData.getMetaData().putValue("scope", SERVER_SCOPE);
pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
} }
} }
} }
@ -503,8 +503,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) { private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
DeviceState state = stateData.getState(); DeviceState state = stateData.getState();
try { try {
TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON String data;
, json.writeValueAsString(state)); if (msgType.equals(CONNECT_EVENT)) {
ObjectNode stateNode = JacksonUtil.convertValue(state, ObjectNode.class);
stateNode.remove(ACTIVITY_STATE);
data = JacksonUtil.toString(stateNode);
} else {
data = JacksonUtil.toString(state);
}
TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON, data);
clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null); clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e); log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);

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

@ -154,7 +154,7 @@ public class DefaultTransportApiService implements TransportApiService {
return TransportApiResponseMsg.newBuilder() return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e); log.warn("[{}][{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e);
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
deviceCreationLock.unlock(); deviceCreationLock.unlock();

18
application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java

@ -38,19 +38,15 @@ public abstract class AbstractCleanUpService {
@Value("${spring.datasource.password}") @Value("${spring.datasource.password}")
protected String dbPassword; protected String dbPassword;
protected long executeQuery(Connection conn, String query) { protected long executeQuery(Connection conn, String query) throws SQLException {
long removed = 0L; try (Statement statement = conn.createStatement()) {
try {
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(query); ResultSet resultSet = statement.executeQuery(query);
getWarnings(statement); if (log.isDebugEnabled()) {
getWarnings(statement);
}
resultSet.next(); resultSet.next();
removed = resultSet.getLong(1); return resultSet.getLong(1);
log.debug("Successfully executed query: {}", query);
} catch (SQLException e) {
log.debug("Failed to execute query: {} due to: {}", query, e.getMessage());
} }
return removed;
} }
protected void getWarnings(Statement statement) throws SQLException { protected void getWarnings(Statement statement) throws SQLException {
@ -65,6 +61,6 @@ public abstract class AbstractCleanUpService {
} }
} }
protected abstract void doCleanUp(Connection connection); protected abstract void doCleanUp(Connection connection) throws SQLException;
} }

2
application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java

@ -49,7 +49,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService {
} }
@Override @Override
protected void doCleanUp(Connection connection) { protected void doCleanUp(Connection connection) throws SQLException {
long totalEdgeEventsRemoved = executeQuery(connection, "call cleanup_edge_events_by_ttl(" + ttl + ", 0);"); long totalEdgeEventsRemoved = executeQuery(connection, "call cleanup_edge_events_by_ttl(" + ttl + ", 0);");
log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved);
} }

2
application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java

@ -54,7 +54,7 @@ public class EventsCleanUpService extends AbstractCleanUpService {
} }
@Override @Override
protected void doCleanUp(Connection connection) { protected void doCleanUp(Connection connection) throws SQLException {
long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);"); long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);");
log.info("Total events removed by TTL: [{}]", totalEventsRemoved); log.info("Total events removed by TTL: [{}]", totalEventsRemoved);
} }

3
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java

@ -22,6 +22,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.PsqlTsDao; import org.thingsboard.server.dao.util.PsqlTsDao;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
@PsqlTsDao @PsqlTsDao
@Service @Service
@ -32,7 +33,7 @@ public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpServi
private String partitionType; private String partitionType;
@Override @Override
protected void doCleanUp(Connection connection) { protected void doCleanUp(Connection connection) throws SQLException {
long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);"); long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);");
log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved); log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved);
long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);"); long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);");

3
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
@TimescaleDBTsDao @TimescaleDBTsDao
@Service @Service
@ -28,7 +29,7 @@ import java.sql.Connection;
public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService { public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService {
@Override @Override
protected void doCleanUp(Connection connection) { protected void doCleanUp(Connection connection) throws SQLException {
long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);"); long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);");
log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved); log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
} }

2
application/src/main/resources/logback.xml

@ -30,6 +30,8 @@
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />--> <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />--> <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />-->
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />

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

@ -589,7 +589,7 @@ transport:
edges: edges:
rpc: rpc:
enabled: "${EDGES_RPC_ENABLED:true}" enabled: "${EDGES_RPC_ENABLED:true}"
port: "${EDGES_RPC_PORT:60100}" port: "${EDGES_RPC_PORT:7070}"
ssl: ssl:
# Enable/disable SSL support # Enable/disable SSL support
enabled: "${EDGES_RPC_SSL_ENABLED:false}" enabled: "${EDGES_RPC_SSL_ENABLED:false}"
@ -619,6 +619,10 @@ swagger:
queue: queue:
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
in_memory:
stats:
# For debug lvl
print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}"
kafka: kafka:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
@ -630,13 +634,21 @@ queue:
max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}" max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}"
max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}" max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}"
fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}" fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
confluent:
ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}"
security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
other:
topic-properties: topic-properties:
rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}" js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
aws_sqs: aws_sqs:
use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}" secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}" region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"

23
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -79,8 +79,14 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@ActiveProfiles("test") @ActiveProfiles("test")
@ -221,6 +227,7 @@ public abstract class AbstractControllerTest {
} }
private Tenant savedDifferentTenant; private Tenant savedDifferentTenant;
protected void loginDifferentTenant() throws Exception { protected void loginDifferentTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -316,6 +323,10 @@ public abstract class AbstractControllerTest {
return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
} }
protected <T> T doGet(String urlTemplate, Class<T> responseClass, ResultMatcher resultMatcher, Object... urlVariables) throws Exception {
return readResponse(doGet(urlTemplate, urlVariables).andExpect(resultMatcher), responseClass);
}
protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception { protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception {
return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
} }
@ -357,9 +368,9 @@ public abstract class AbstractControllerTest {
return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
} }
protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType,
TimePageLink pageLink, TimePageLink pageLink,
Object... urlVariables) throws Exception { Object... urlVariables) throws Exception {
List<Object> pageLinkVariables = new ArrayList<>(); List<Object> pageLinkVariables = new ArrayList<>();
urlTemplate += "limit={limit}"; urlTemplate += "limit={limit}";
pageLinkVariables.add(pageLink.getLimit()); pageLinkVariables.add(pageLink.getLimit());
@ -425,7 +436,7 @@ public abstract class AbstractControllerTest {
return mockMvc.perform(postRequest); return mockMvc.perform(postRequest);
} }
protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception { protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception {
MockHttpServletRequestBuilder postRequest = post(urlTemplate); MockHttpServletRequestBuilder postRequest = post(urlTemplate);
setJwtToken(postRequest); setJwtToken(postRequest);
String json = json(content); String json = json(content);

440
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -15,74 +15,79 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import static org.hamcrest.Matchers.containsString;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.server.common.data.*; import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public abstract class BaseDeviceControllerTest extends AbstractControllerTest { public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
private IdComparator<Device> idComparator = new IdComparator<>(); private IdComparator<Device> idComparator = new IdComparator<>();
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class); savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
tenantAdmin = new User(); tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId()); tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
} }
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveDevice() throws Exception { public void testSaveDevice() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
Assert.assertNotNull(savedDevice); Assert.assertNotNull(savedDevice);
Assert.assertNotNull(savedDevice.getId()); Assert.assertNotNull(savedDevice.getId());
Assert.assertTrue(savedDevice.getCreatedTime() > 0); Assert.assertTrue(savedDevice.getCreatedTime() > 0);
@ -90,9 +95,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertNotNull(savedDevice.getCustomerId()); Assert.assertNotNull(savedDevice.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId()); Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
Assert.assertEquals(device.getName(), savedDevice.getName()); Assert.assertEquals(device.getName(), savedDevice.getName());
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(deviceCredentials); Assert.assertNotNull(deviceCredentials);
Assert.assertNotNull(deviceCredentials.getId()); Assert.assertNotNull(deviceCredentials.getId());
@ -100,10 +105,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType()); Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType());
Assert.assertNotNull(deviceCredentials.getCredentialsId()); Assert.assertNotNull(deviceCredentials.getCredentialsId());
Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
savedDevice.setName("My new device"); savedDevice.setName("My new device");
doPost("/api/device", savedDevice, Device.class); doPost("/api/device", savedDevice, Device.class);
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
} }
@ -115,10 +120,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
loginDifferentTenant(); loginDifferentTenant();
doPost("/api/device", savedDevice, Device.class, status().isForbidden()); doPost("/api/device", savedDevice, Device.class, status().isNotFound());
deleteDifferentTenant(); deleteDifferentTenant();
} }
@Test @Test
public void testFindDeviceById() throws Exception { public void testFindDeviceById() throws Exception {
Device device = new Device(); Device device = new Device();
@ -133,26 +138,27 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindDeviceTypesByTenantId() throws Exception { public void testFindDeviceTypesByTenantId() throws Exception {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
for (int i=0;i<3;i++) { for (int i = 0; i < 3; i++) {
Device device = new Device(); Device device = new Device();
device.setName("My device B"+i); device.setName("My device B" + i);
device.setType("typeB"); device.setType("typeB");
devices.add(doPost("/api/device", device, Device.class)); devices.add(doPost("/api/device", device, Device.class));
} }
for (int i=0;i<7;i++) { for (int i = 0; i < 7; i++) {
Device device = new Device(); Device device = new Device();
device.setName("My device C"+i); device.setName("My device C" + i);
device.setType("typeC"); device.setType("typeC");
devices.add(doPost("/api/device", device, Device.class)); devices.add(doPost("/api/device", device, Device.class));
} }
for (int i=0;i<9;i++) { for (int i = 0; i < 9; i++) {
Device device = new Device(); Device device = new Device();
device.setName("My device A"+i); device.setName("My device A" + i);
device.setType("typeA"); device.setType("typeA");
devices.add(doPost("/api/device", device, Device.class)); devices.add(doPost("/api/device", device, Device.class));
} }
List<EntitySubtype> deviceTypes = doGetTyped("/api/device/types", List<EntitySubtype> deviceTypes = doGetTyped("/api/device/types",
new TypeReference<List<EntitySubtype>>(){}); new TypeReference<List<EntitySubtype>>() {
});
Assert.assertNotNull(deviceTypes); Assert.assertNotNull(deviceTypes);
Assert.assertEquals(3, deviceTypes.size()); Assert.assertEquals(3, deviceTypes.size());
@ -160,19 +166,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals("typeB", deviceTypes.get(1).getType()); Assert.assertEquals("typeB", deviceTypes.get(1).getType());
Assert.assertEquals("typeC", deviceTypes.get(2).getType()); Assert.assertEquals("typeC", deviceTypes.get(2).getType());
} }
@Test @Test
public void testDeleteDevice() throws Exception { public void testDeleteDevice() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doDelete("/api/device/"+savedDevice.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/device/"+savedDevice.getId().getId().toString()) doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isOk());
doGet("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isNotFound());
} }
@Test @Test
@ -189,52 +195,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Device device = new Device(); Device device = new Device();
device.setType("default"); device.setType("default");
doPost("/api/device", device) doPost("/api/device", device)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device name should be specified"))); .andExpect(statusReason(containsString("Device name should be specified")));
} }
@Test @Test
public void testAssignUnassignDeviceToCustomer() throws Exception { public void testAssignUnassignDeviceToCustomer() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString() Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId());
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId()); Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId());
Device unassignedDevice = Device unassignedDevice =
doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class); doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId());
foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId()); Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId());
} }
@Test @Test
public void testAssignDeviceToNonExistentCustomer() throws Exception { public void testAssignDeviceToNonExistentCustomer() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + UUIDs.timeBased().toString() doPost("/api/customer/" + UUIDs.timeBased().toString()
+ "/device/" + savedDevice.getId().getId().toString()) + "/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@Test @Test
public void testAssignDeviceToCustomerFromDifferentTenant() throws Exception { public void testAssignDeviceToCustomerFromDifferentTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant2 = new Tenant(); Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant"); tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
@ -246,103 +252,103 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
tenantAdmin2.setEmail("tenant3@thingsboard.org"); tenantAdmin2.setEmail("tenant3@thingsboard.org");
tenantAdmin2.setFirstName("Joe"); tenantAdmin2.setFirstName("Joe");
tenantAdmin2.setLastName("Downs"); tenantAdmin2.setLastName("Downs");
tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("Different customer"); customer.setTitle("Different customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
login(tenantAdmin.getEmail(), "testPassword1"); login(tenantAdmin.getEmail(), "testPassword1");
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString() doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString()) + "/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/"+savedTenant2.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindDeviceCredentialsByDeviceId() throws Exception { public void testFindDeviceCredentialsByDeviceId() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
} }
@Test @Test
public void testSaveDeviceCredentials() throws Exception { public void testSaveDeviceCredentials() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId("access_token"); deviceCredentials.setCredentialsId("access_token");
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isOk()); .andExpect(status().isOk());
DeviceCredentials foundDeviceCredentials = DeviceCredentials foundDeviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(deviceCredentials, foundDeviceCredentials); Assert.assertEquals(deviceCredentials, foundDeviceCredentials);
} }
@Test @Test
public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception { public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception {
DeviceCredentials deviceCredentials = new DeviceCredentials(); DeviceCredentials deviceCredentials = new DeviceCredentials();
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
} }
@Test @Test
public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception { public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsType(null); deviceCredentials.setCredentialsType(null);
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device credentials type should be specified"))); .andExpect(statusReason(containsString("Device credentials type should be specified")));
} }
@Test @Test
public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception { public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsId(null); deviceCredentials.setCredentialsId(null);
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device credentials id should be specified"))); .andExpect(statusReason(containsString("Device credentials id should be specified")));
} }
@Test @Test
public void testSaveNonExistentDeviceCredentials() throws Exception { public void testSaveNonExistentDeviceCredentials() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(UUIDs.timeBased())); DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(UUIDs.timeBased()));
newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime()); newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime());
@ -350,29 +356,29 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType()); newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType());
newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId()); newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId());
doPost("/api/device/credentials", newDeviceCredentials) doPost("/api/device/credentials", newDeviceCredentials)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Unable to update non-existent device credentials"))); .andExpect(statusReason(containsString("Unable to update non-existent device credentials")));
} }
@Test @Test
public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception { public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
deviceCredentials.setDeviceId(new DeviceId(UUIDs.timeBased())); deviceCredentials.setDeviceId(new DeviceId(UUIDs.timeBased()));
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@Test @Test
public void testFindTenantDevices() throws Exception { public void testFindTenantDevices() throws Exception {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
for (int i=0;i<178;i++) { for (int i = 0; i < 178; i++) {
Device device = new Device(); Device device = new Device();
device.setName("Device"+i); device.setName("Device" + i);
device.setType("default"); device.setType("default");
devices.add(doPost("/api/device", device, Device.class)); devices.add(doPost("/api/device", device, Device.class));
} }
@ -380,28 +386,29 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
TextPageLink pageLink = new TextPageLink(23); TextPageLink pageLink = new TextPageLink(23);
TextPageData<Device> pageData = null; TextPageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
loadedDevices.addAll(pageData.getData()); loadedDevices.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devices, idComparator); Collections.sort(devices, idComparator);
Collections.sort(loadedDevices, idComparator); Collections.sort(loadedDevices, idComparator);
Assert.assertEquals(devices, loadedDevices); Assert.assertEquals(devices, loadedDevices);
} }
@Test @Test
public void testFindTenantDevicesByName() throws Exception { public void testFindTenantDevicesByName() throws Exception {
String title1 = "Device title 1"; String title1 = "Device title 1";
List<Device> devicesTitle1 = new ArrayList<>(); List<Device> devicesTitle1 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1+suffix; String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
@ -409,38 +416,40 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
} }
String title2 = "Device title 2"; String title2 = "Device title 2";
List<Device> devicesTitle2 = new ArrayList<>(); List<Device> devicesTitle2 = new ArrayList<>();
for (int i=0;i<75;i++) { for (int i = 0; i < 75; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2+suffix; String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
devicesTitle2.add(doPost("/api/device", device, Device.class)); devicesTitle2.add(doPost("/api/device", device, Device.class));
} }
List<Device> loadedDevicesTitle1 = new ArrayList<>(); List<Device> loadedDevicesTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(15, title1); TextPageLink pageLink = new TextPageLink(15, title1);
TextPageData<Device> pageData = null; TextPageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
loadedDevicesTitle1.addAll(pageData.getData()); loadedDevicesTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesTitle1, idComparator); Collections.sort(devicesTitle1, idComparator);
Collections.sort(loadedDevicesTitle1, idComparator); Collections.sort(loadedDevicesTitle1, idComparator);
Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
List<Device> loadedDevicesTitle2 = new ArrayList<>(); List<Device> loadedDevicesTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
loadedDevicesTitle2.addAll(pageData.getData()); loadedDevicesTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -449,28 +458,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Collections.sort(devicesTitle2, idComparator); Collections.sort(devicesTitle2, idComparator);
Collections.sort(loadedDevicesTitle2, idComparator); Collections.sort(loadedDevicesTitle2, idComparator);
Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
for (Device device : loadedDevicesTitle1) { for (Device device : loadedDevicesTitle1) {
doDelete("/api/device/"+device.getId().getId().toString()) doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title1); pageLink = new TextPageLink(4, title1);
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesTitle2) { for (Device device : loadedDevicesTitle2) {
doDelete("/api/device/"+device.getId().getId().toString()) doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -480,10 +491,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String title1 = "Device title 1"; String title1 = "Device title 1";
String type1 = "typeA"; String type1 = "typeA";
List<Device> devicesType1 = new ArrayList<>(); List<Device> devicesType1 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1+suffix; String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type1); device.setType(type1);
@ -492,10 +503,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String title2 = "Device title 2"; String title2 = "Device title 2";
String type2 = "typeB"; String type2 = "typeB";
List<Device> devicesType2 = new ArrayList<>(); List<Device> devicesType2 = new ArrayList<>();
for (int i=0;i<75;i++) { for (int i = 0; i < 75; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2+suffix; String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type2); device.setType(type2);
@ -507,7 +518,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
TextPageData<Device> pageData = null; TextPageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type1); new TypeReference<TextPageData<Device>>() {
}, pageLink, type1);
loadedDevicesType1.addAll(pageData.getData()); loadedDevicesType1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -523,7 +535,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
pageLink = new TextPageLink(4); pageLink = new TextPageLink(4);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type2); new TypeReference<TextPageData<Device>>() {
}, pageLink, type2);
loadedDevicesType2.addAll(pageData.getData()); loadedDevicesType2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -536,63 +549,66 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(devicesType2, loadedDevicesType2); Assert.assertEquals(devicesType2, loadedDevicesType2);
for (Device device : loadedDevicesType1) { for (Device device : loadedDevicesType1) {
doDelete("/api/device/"+device.getId().getId().toString()) doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4); pageLink = new TextPageLink(4);
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type1); new TypeReference<TextPageData<Device>>() {
}, pageLink, type1);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesType2) { for (Device device : loadedDevicesType2) {
doDelete("/api/device/"+device.getId().getId().toString()) doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4); pageLink = new TextPageLink(4);
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type2); new TypeReference<TextPageData<Device>>() {
}, pageLink, type2);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@Test @Test
public void testFindCustomerDevices() throws Exception { public void testFindCustomerDevices() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("Test customer"); customer.setTitle("Test customer");
customer = doPost("/api/customer", customer, Customer.class); customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
for (int i=0;i<128;i++) { for (int i = 0; i < 128; i++) {
Device device = new Device(); Device device = new Device();
device.setName("Device"+i); device.setName("Device" + i);
device.setType("default"); device.setType("default");
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
devices.add(doPost("/api/customer/" + customerId.getId().toString() devices.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class)); + "/device/" + device.getId().getId().toString(), Device.class));
} }
List<Device> loadedDevices = new ArrayList<>(); List<Device> loadedDevices = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23); TextPageLink pageLink = new TextPageLink(23);
TextPageData<Device> pageData = null; TextPageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
loadedDevices.addAll(pageData.getData()); loadedDevices.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devices, idComparator); Collections.sort(devices, idComparator);
Collections.sort(loadedDevices, idComparator); Collections.sort(loadedDevices, idComparator);
Assert.assertEquals(devices, loadedDevices); Assert.assertEquals(devices, loadedDevices);
} }
@Test @Test
public void testFindCustomerDevicesByName() throws Exception { public void testFindCustomerDevicesByName() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
@ -602,53 +618,55 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String title1 = "Device title 1"; String title1 = "Device title 1";
List<Device> devicesTitle1 = new ArrayList<>(); List<Device> devicesTitle1 = new ArrayList<>();
for (int i=0;i<125;i++) { for (int i = 0; i < 125; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1+suffix; String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString() devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class)); + "/device/" + device.getId().getId().toString(), Device.class));
} }
String title2 = "Device title 2"; String title2 = "Device title 2";
List<Device> devicesTitle2 = new ArrayList<>(); List<Device> devicesTitle2 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2+suffix; String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString() devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class)); + "/device/" + device.getId().getId().toString(), Device.class));
} }
List<Device> loadedDevicesTitle1 = new ArrayList<>(); List<Device> loadedDevicesTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(15, title1); TextPageLink pageLink = new TextPageLink(15, title1);
TextPageData<Device> pageData = null; TextPageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
loadedDevicesTitle1.addAll(pageData.getData()); loadedDevicesTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesTitle1, idComparator); Collections.sort(devicesTitle1, idComparator);
Collections.sort(loadedDevicesTitle1, idComparator); Collections.sort(loadedDevicesTitle1, idComparator);
Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
List<Device> loadedDevicesTitle2 = new ArrayList<>(); List<Device> loadedDevicesTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
loadedDevicesTitle2.addAll(pageData.getData()); loadedDevicesTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -657,28 +675,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Collections.sort(devicesTitle2, idComparator); Collections.sort(devicesTitle2, idComparator);
Collections.sort(loadedDevicesTitle2, idComparator); Collections.sort(loadedDevicesTitle2, idComparator);
Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
for (Device device : loadedDevicesTitle1) { for (Device device : loadedDevicesTitle1) {
doDelete("/api/customer/device/" + device.getId().getId().toString()) doDelete("/api/customer/device/" + device.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title1); pageLink = new TextPageLink(4, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesTitle2) { for (Device device : loadedDevicesTitle2) {
doDelete("/api/customer/device/" + device.getId().getId().toString()) doDelete("/api/customer/device/" + device.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
new TypeReference<TextPageData<Device>>(){}, pageLink); new TypeReference<TextPageData<Device>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -693,10 +713,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String title1 = "Device title 1"; String title1 = "Device title 1";
String type1 = "typeC"; String type1 = "typeC";
List<Device> devicesType1 = new ArrayList<>(); List<Device> devicesType1 = new ArrayList<>();
for (int i=0;i<125;i++) { for (int i = 0; i < 125; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1+suffix; String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type1); device.setType(type1);
@ -707,10 +727,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String title2 = "Device title 2"; String title2 = "Device title 2";
String type2 = "typeD"; String type2 = "typeD";
List<Device> devicesType2 = new ArrayList<>(); List<Device> devicesType2 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2+suffix; String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type2); device.setType(type2);
@ -724,7 +744,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
TextPageData<Device> pageData = null; TextPageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type1); new TypeReference<TextPageData<Device>>() {
}, pageLink, type1);
loadedDevicesType1.addAll(pageData.getData()); loadedDevicesType1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -740,7 +761,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
pageLink = new TextPageLink(4); pageLink = new TextPageLink(4);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type2); new TypeReference<TextPageData<Device>>() {
}, pageLink, type2);
loadedDevicesType2.addAll(pageData.getData()); loadedDevicesType2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -759,7 +781,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
pageLink = new TextPageLink(4); pageLink = new TextPageLink(4);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type1); new TypeReference<TextPageData<Device>>() {
}, pageLink, type1);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
@ -770,9 +793,60 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
pageLink = new TextPageLink(4); pageLink = new TextPageLink(4);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
new TypeReference<TextPageData<Device>>(){}, pageLink, type2); new TypeReference<TextPageData<Device>>() {
}, pageLink, type2);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@Test
public void testAssignDeviceToTenant() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Device anotherDevice = new Device();
anotherDevice.setName("My device1");
anotherDevice.setType("default");
Device savedAnotherDevice = doPost("/api/device", anotherDevice, Device.class);
EntityRelation relation = new EntityRelation();
relation.setFrom(savedDevice.getId());
relation.setTo(savedAnotherDevice.getId());
relation.setTypeGroup(RelationTypeGroup.COMMON);
relation.setType("Contains");
doPost("/api/relation", relation).andExpect(status().isOk());
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
Tenant savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedDifferentTenant);
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedDifferentTenant.getId());
user.setEmail("tenant9@thingsboard.org");
user.setFirstName("Sam");
user.setLastName("Downs");
createUserAndLogin(user, "testPassword1");
login("tenant2@thingsboard.org", "testPassword1");
Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class);
doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class, status().isNotFound());
login("tenant9@thingsboard.org", "testPassword1");
Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class);
Assert.assertNotNull(foundDevice1);
doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound());
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
}
} }

2
application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java

@ -93,7 +93,7 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
doPost("/api/relation", relation); doPost("/api/relation", relation);
Thread.sleep(1000); Thread.sleep(2000);
List<EdgeEvent> edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", List<EdgeEvent> edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?",
new TypeReference<TimePageData<EdgeEvent>>() { new TypeReference<TimePageData<EdgeEvent>>() {

2
common/actor/pom.xml

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

2
common/dao-api/pom.xml

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

3
common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java

@ -16,14 +16,12 @@
package org.thingsboard.server.dao.audit; package org.thingsboard.server.dao.audit;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
@ -49,5 +47,4 @@ public interface AuditLogService {
E entity, E entity,
ActionType actionType, ActionType actionType,
Exception e, Object... additionalInfo); Exception e, Object... additionalInfo);
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -68,6 +68,8 @@ public interface DeviceService {
ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId); ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId);
Device assignDeviceToTenant(TenantId tenantId, Device device);
Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -41,4 +41,6 @@ public interface EventService {
List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
void removeEvents(TenantId tenantId, EntityId entityId);
} }

3
common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
/** /**
* Created by ashvayka on 27.04.17. * Created by ashvayka on 27.04.17.
@ -77,6 +76,8 @@ public interface RelationService {
ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(TenantId tenantId, EntityRelationsQuery query); ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(TenantId tenantId, EntityRelationsQuery query);
void removeRelations(TenantId tenantId, EntityId entityId);
// TODO: This method may be useful for some validations in the future // TODO: This method may be useful for some validations in the future
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);

2
common/data/pom.xml

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

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

@ -57,6 +57,8 @@ public class DataConstants {
public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED";
public static final String ALARM_ACK = "ALARM_ACK"; public static final String ALARM_ACK = "ALARM_ACK";
public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String ALARM_CLEAR = "ALARM_CLEAR";
public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
public static final String ENTITY_ASSIGNED_TO_EDGE = "ENTITY_ASSIGNED_TO_EDGE"; public static final String ENTITY_ASSIGNED_TO_EDGE = "ENTITY_ASSIGNED_TO_EDGE";
public static final String ENTITY_UNASSIGNED_FROM_EDGE = "ENTITY_UNASSIGNED_FROM_EDGE"; public static final String ENTITY_UNASSIGNED_FROM_EDGE = "ENTITY_UNASSIGNED_FROM_EDGE";

2
common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java

@ -42,6 +42,8 @@ public enum ActionType {
LOGIN(false), LOGIN(false),
LOGOUT(false), LOGOUT(false),
LOCKOUT(false), LOCKOUT(false),
ASSIGNED_FROM_TENANT(false),
ASSIGNED_TO_TENANT(false),
ASSIGNED_TO_EDGE(false), // log edge name ASSIGNED_TO_EDGE(false), // log edge name
UNASSIGNED_FROM_EDGE(false), // log edge name UNASSIGNED_FROM_EDGE(false), // log edge name
CREDENTIALS_REQUEST(false), // request credentials from edge CREDENTIALS_REQUEST(false), // request credentials from edge

2
common/edge-api/pom.xml

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

2
common/message/pom.xml

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

29
common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java

@ -1,29 +0,0 @@
/**
* Copyright © 2016-2020 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.msg.kv;
import java.io.Serializable;
import java.util.List;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
public interface AttributesKVMsg extends Serializable {
List<AttributeKvEntry> getClientAttributes();
List<AttributeKvEntry> getSharedAttributes();
List<AttributeKey> getDeletedAttributes();
}

52
common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java

@ -1,52 +0,0 @@
/**
* Copyright © 2016-2020 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.msg.kv;
import lombok.AccessLevel;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import java.util.Collections;
import java.util.List;
@Data
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class BasicAttributeKVMsg implements AttributesKVMsg {
private static final long serialVersionUID = 1L;
private final List<AttributeKvEntry> clientAttributes;
private final List<AttributeKvEntry> sharedAttributes;
private final List<AttributeKey> deletedAttributes;
public static BasicAttributeKVMsg fromClient(List<AttributeKvEntry> attributes) {
return new BasicAttributeKVMsg(attributes, Collections.emptyList(), Collections.emptyList());
}
public static BasicAttributeKVMsg fromShared(List<AttributeKvEntry> attributes) {
return new BasicAttributeKVMsg(Collections.emptyList(), attributes, Collections.emptyList());
}
public static BasicAttributeKVMsg from(List<AttributeKvEntry> client, List<AttributeKvEntry> shared) {
return new BasicAttributeKVMsg(client, shared, Collections.emptyList());
}
public static AttributesKVMsg fromDeleted(List<AttributeKey> shared) {
return new BasicAttributeKVMsg(Collections.emptyList(), Collections.emptyList(), shared);
}
}

2
common/pom.xml

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

2
common/queue/pom.xml

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

10
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java

@ -37,6 +37,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
private final AdminClient client; private final AdminClient client;
private final Map<String, String> topicConfigs; private final Map<String, String> topicConfigs;
private final Set<String> topics = ConcurrentHashMap.newKeySet(); private final Set<String> topics = ConcurrentHashMap.newKeySet();
private final int numPartitions;
private final short replicationFactor; private final short replicationFactor;
@ -50,6 +51,13 @@ public class TbKafkaAdmin implements TbQueueAdmin {
log.error("Failed to get all topics.", e); log.error("Failed to get all topics.", e);
} }
String numPartitionsStr = topicConfigs.get("partitions");
if (numPartitionsStr != null) {
numPartitions = Integer.parseInt(numPartitionsStr);
topicConfigs.remove("partitions");
} else {
numPartitions = 1;
}
replicationFactor = settings.getReplicationFactor(); replicationFactor = settings.getReplicationFactor();
} }
@ -59,7 +67,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
return; return;
} }
try { try {
NewTopic newTopic = new NewTopic(topic, 1, replicationFactor).configs(topicConfigs); NewTopic newTopic = new NewTopic(topic, numPartitions, replicationFactor).configs(topicConfigs);
createTopic(newTopic).values().get(topic).get(); createTopic(newTopic).values().get(topic).get();
topics.add(topic); topics.add(topic);
} catch (ExecutionException ee) { } catch (ExecutionException ee) {

42
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java

@ -16,10 +16,13 @@
package org.thingsboard.server.queue.kafka; package org.thingsboard.server.queue.kafka;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
@ -30,6 +33,7 @@ import java.util.Properties;
*/ */
@Slf4j @Slf4j
@ConditionalOnExpression("'${queue.type:null}'=='kafka'") @ConditionalOnExpression("'${queue.type:null}'=='kafka'")
@ConfigurationProperties(prefix = "queue.kafka")
@Component @Component
public class TbKafkaSettings { public class TbKafkaSettings {
@ -65,20 +69,44 @@ public class TbKafkaSettings {
@Value("${queue.kafka.fetch_max_bytes:134217728}") @Value("${queue.kafka.fetch_max_bytes:134217728}")
@Getter @Getter
private int fetchMaxBytes; private int fetchMaxBytes;
@Value("${kafka.other:#{null}}") @Value("${queue.kafka.use_confluent_cloud:false}")
private boolean useConfluent;
@Value("${queue.kafka.confluent.ssl.algorithm}")
private String sslAlgorithm;
@Value("${queue.kafka.confluent.sasl.mechanism}")
private String saslMechanism;
@Value("${queue.kafka.confluent.sasl.config}")
private String saslConfig;
@Value("${queue.kafka.confluent.security.protocol}")
private String securityProtocol;
@Setter
private List<TbKafkaProperty> other; private List<TbKafkaProperty> other;
public Properties toProps() { public Properties toProps() {
Properties props = new Properties(); Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
props.put(ProducerConfig.ACKS_CONFIG, acks);
props.put(ProducerConfig.RETRIES_CONFIG, retries); props.put(ProducerConfig.RETRIES_CONFIG, retries);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs); if (useConfluent) {
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); props.put("ssl.endpoint.identification.algorithm", sslAlgorithm);
if(other != null){ props.put("sasl.mechanism", saslMechanism);
props.put("sasl.jaas.config", saslConfig);
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
} else {
props.put(ProducerConfig.ACKS_CONFIG, acks);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
}
if (other != null) {
other.forEach(kv -> props.put(kv.getKey(), kv.getValue())); other.forEach(kv -> props.put(kv.getKey(), kv.getValue()));
} }
return props; return props;

9
common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java

@ -24,7 +24,6 @@ import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public final class InMemoryStorage { public final class InMemoryStorage {
@ -35,6 +34,14 @@ public final class InMemoryStorage {
storage = new ConcurrentHashMap<>(); storage = new ConcurrentHashMap<>();
} }
public void printStats() {
storage.forEach((topic, queue) -> {
if (queue.size() > 0) {
log.debug("[{}] Queue Size [{}]", topic, queue.size());
}
});
}
public static InMemoryStorage getInstance() { public static InMemoryStorage getInstance() {
if (instance == null) { if (instance == null) {
synchronized (InMemoryStorage.class) { synchronized (InMemoryStorage.class) {

9
common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.provider;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.js.JsInvokeProtos;
@ -28,6 +29,7 @@ import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import org.thingsboard.server.queue.memory.InMemoryTbQueueConsumer; import org.thingsboard.server.queue.memory.InMemoryTbQueueConsumer;
import org.thingsboard.server.queue.memory.InMemoryTbQueueProducer; import org.thingsboard.server.queue.memory.InMemoryTbQueueProducer;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
@ -47,6 +49,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
private final TbQueueRuleEngineSettings ruleEngineSettings; private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueTransportApiSettings transportApiSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final InMemoryStorage storage;
public InMemoryMonolithQueueFactory(PartitionService partitionService, TbQueueCoreSettings coreSettings, public InMemoryMonolithQueueFactory(PartitionService partitionService, TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings, TbQueueRuleEngineSettings ruleEngineSettings,
@ -59,6 +62,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
this.ruleEngineSettings = ruleEngineSettings; this.ruleEngineSettings = ruleEngineSettings;
this.transportApiSettings = transportApiSettings; this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings; this.transportNotificationSettings = transportNotificationSettings;
this.storage = InMemoryStorage.getInstance();
} }
@Override @Override
@ -120,4 +124,9 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() { public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() {
return null; return null;
} }
@Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}")
private void printInMemoryStats() {
storage.printStats();
}
} }

13
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsAdmin.java

@ -16,8 +16,10 @@
package org.thingsboard.server.queue.sqs; package org.thingsboard.server.queue.sqs;
import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.sqs.AmazonSQS; import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder; import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.CreateQueueRequest; import com.amazonaws.services.sqs.model.CreateQueueRequest;
@ -37,9 +39,16 @@ public class TbAwsSqsAdmin implements TbQueueAdmin {
public TbAwsSqsAdmin(TbAwsSqsSettings sqsSettings, Map<String, String> attributes) { public TbAwsSqsAdmin(TbAwsSqsSettings sqsSettings, Map<String, String> attributes) {
this.attributes = attributes; this.attributes = attributes;
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey()); AWSCredentialsProvider credentialsProvider;
if (sqsSettings.getUseDefaultCredentialProviderChain()) {
credentialsProvider = new DefaultAWSCredentialsProviderChain();
} else {
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
}
sqsClient = AmazonSQSClientBuilder.standard() sqsClient = AmazonSQSClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) .withCredentials(credentialsProvider)
.withRegion(sqsSettings.getRegion()) .withRegion(sqsSettings.getRegion())
.build(); .build();

16
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsConsumerTemplate.java

@ -16,8 +16,10 @@
package org.thingsboard.server.queue.sqs; package org.thingsboard.server.queue.sqs;
import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.sqs.AmazonSQS; import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder; import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry; import com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry;
@ -67,13 +69,19 @@ public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> extends AbstractPara
this.decoder = decoder; this.decoder = decoder;
this.sqsSettings = sqsSettings; this.sqsSettings = sqsSettings;
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey()); AWSCredentialsProvider credentialsProvider;
AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); if (sqsSettings.getUseDefaultCredentialProviderChain()) {
credentialsProvider = new DefaultAWSCredentialsProviderChain();
} else {
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
}
this.sqsClient = AmazonSQSClientBuilder.standard() sqsClient = AmazonSQSClientBuilder.standard()
.withCredentials(credProvider) .withCredentials(credentialsProvider)
.withRegion(sqsSettings.getRegion()) .withRegion(sqsSettings.getRegion())
.build(); .build();
} }
@Override @Override

16
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java

@ -16,8 +16,10 @@
package org.thingsboard.server.queue.sqs; package org.thingsboard.server.queue.sqs;
import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.sqs.AmazonSQS; import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder; import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.SendMessageRequest; import com.amazonaws.services.sqs.model.SendMessageRequest;
@ -54,14 +56,18 @@ public class TbAwsSqsProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
this.admin = admin; this.admin = admin;
this.defaultTopic = defaultTopic; this.defaultTopic = defaultTopic;
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey()); AWSCredentialsProvider credentialsProvider;
AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); if (sqsSettings.getUseDefaultCredentialProviderChain()) {
credentialsProvider = new DefaultAWSCredentialsProviderChain();
} else {
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
}
this.sqsClient = AmazonSQSClientBuilder.standard() sqsClient = AmazonSQSClientBuilder.standard()
.withCredentials(credProvider) .withCredentials(credentialsProvider)
.withRegion(sqsSettings.getRegion()) .withRegion(sqsSettings.getRegion())
.build(); .build();
producerExecutor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); producerExecutor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
} }

3
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsSettings.java

@ -27,6 +27,9 @@ import org.springframework.stereotype.Component;
@Data @Data
public class TbAwsSqsSettings { public class TbAwsSqsSettings {
@Value("${queue.aws_sqs.use_default_credential_provider_chain}")
private Boolean useDefaultCredentialProviderChain;
@Value("${queue.aws_sqs.access_key_id}") @Value("${queue.aws_sqs.access_key_id}")
private String accessKeyId; private String accessKeyId;

1
common/queue/src/main/proto/queue.proto

@ -127,7 +127,6 @@ message GetAttributeResponseMsg {
int32 requestId = 1; int32 requestId = 1;
repeated TsKvProto clientAttributeList = 2; repeated TsKvProto clientAttributeList = 2;
repeated TsKvProto sharedAttributeList = 3; repeated TsKvProto sharedAttributeList = 3;
repeated string deletedAttributeKeys = 4;
string error = 5; string error = 5;
} }

2
common/stats/pom.xml

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

2
common/transport/coap/pom.xml

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

2
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java

@ -125,7 +125,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
@Override @Override
public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException {
if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0 && msg.getDeletedAttributeKeysCount() == 0) { if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) {
return new Response(CoAP.ResponseCode.NOT_FOUND); return new Response(CoAP.ResponseCode.NOT_FOUND);
} else { } else {
Response response = new Response(CoAP.ResponseCode.CONTENT); Response response = new Response(CoAP.ResponseCode.CONTENT);

2
common/transport/http/pom.xml

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

2
common/transport/mqtt/pom.xml

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

2
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java

@ -53,7 +53,7 @@ import java.util.concurrent.TimeUnit;
*/ */
@Slf4j @Slf4j
@Component("MqttSslHandlerProvider") @Component("MqttSslHandlerProvider")
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')") @ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.mqtt.enabled}'=='true')")
@ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false) @ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
public class MqttSslHandlerProvider { public class MqttSslHandlerProvider {

1
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -207,6 +207,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return new JsonParser().parse(payload); return new JsonParser().parse(payload);
} catch (JsonSyntaxException ex) { } catch (JsonSyntaxException ex) {
log.warn("Payload is in incorrect format: {}", payload);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }

2
common/transport/pom.xml

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

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

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

35
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
@ -273,11 +272,6 @@ public class JsonConverter {
payload.getSharedAttributeListList().forEach(addToObjectFromProto(attrObject)); payload.getSharedAttributeListList().forEach(addToObjectFromProto(attrObject));
result.add("shared", attrObject); result.add("shared", attrObject);
} }
if (payload.getDeletedAttributeKeysCount() > 0) {
JsonArray attrObject = new JsonArray();
payload.getDeletedAttributeKeysList().forEach(attrObject::add);
result.add("deleted", attrObject);
}
return result; return result;
} }
@ -294,31 +288,6 @@ public class JsonConverter {
return result; return result;
} }
public static JsonObject toJson(AttributesKVMsg payload, boolean asMap) {
JsonObject result = new JsonObject();
if (asMap) {
if (!payload.getClientAttributes().isEmpty()) {
JsonObject attrObject = new JsonObject();
payload.getClientAttributes().forEach(addToObject(attrObject));
result.add("client", attrObject);
}
if (!payload.getSharedAttributes().isEmpty()) {
JsonObject attrObject = new JsonObject();
payload.getSharedAttributes().forEach(addToObject(attrObject));
result.add("shared", attrObject);
}
} else {
payload.getClientAttributes().forEach(addToObject(result));
payload.getSharedAttributes().forEach(addToObject(result));
}
if (!payload.getDeletedAttributes().isEmpty()) {
JsonArray attrObject = new JsonArray();
payload.getDeletedAttributes().forEach(addToObject(attrObject));
result.add("deleted", attrObject);
}
return result;
}
public static JsonObject getJsonObjectForGateway(String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) { public static JsonObject getJsonObjectForGateway(String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) {
JsonObject result = new JsonObject(); JsonObject result = new JsonObject();
result.addProperty("id", responseMsg.getRequestId()); result.addProperty("id", responseMsg.getRequestId());
@ -374,10 +343,6 @@ public class JsonConverter {
} }
} }
private static Consumer<AttributeKey> addToObject(JsonArray result) {
return key -> result.add(key.getAttributeKey());
}
private static Consumer<TsKvProto> addToObjectFromProto(JsonObject result) { private static Consumer<TsKvProto> addToObjectFromProto(JsonObject result) {
return de -> { return de -> {
switch (de.getKv().getType()) { switch (de.getKv().getType()) {

2
common/util/pom.xml

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

2
dao/pom.xml

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

2
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -166,7 +166,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
try { try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get(); List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
if (entityViews != null && !entityViews.isEmpty()) { if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete asset that is assigned to entity views!"); throw new DataValidationException("Can't delete asset that has entity views!");
} }
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for assetId [{}]", assetId, e); log.error("Exception while finding entity views for assetId [{}]", assetId, e);

3
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java

@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface AuditLogDao { public interface AuditLogDao extends Dao<AuditLog> {
ListenableFuture<Void> saveByTenantId(AuditLog auditLog); ListenableFuture<Void> saveByTenantId(AuditLog auditLog);

18
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionStatus;
@ -38,7 +37,6 @@ import org.thingsboard.server.common.data.id.AuditLogId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
@ -117,8 +115,8 @@ public class AuditLogServiceImpl implements AuditLogService {
@Override @Override
public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>>
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
ActionType actionType, Exception e, Object... additionalInfo) { ActionType actionType, Exception e, Object... additionalInfo) {
if (canLog(entityId.getEntityType(), actionType)) { if (canLog(entityId.getEntityType(), actionType)) {
JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo); JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo);
ActionStatus actionStatus = ActionStatus.SUCCESS; ActionStatus actionStatus = ActionStatus.SUCCESS;
@ -129,7 +127,8 @@ public class AuditLogServiceImpl implements AuditLogService {
} else { } else {
try { try {
entityName = entityService.fetchEntityNameAsync(tenantId, entityId).get(); entityName = entityService.fetchEntityNameAsync(tenantId, entityId).get();
} catch (Exception ex) {} } catch (Exception ex) {
}
} }
if (e != null) { if (e != null) {
actionStatus = ActionStatus.FAILURE; actionStatus = ActionStatus.FAILURE;
@ -158,15 +157,16 @@ public class AuditLogServiceImpl implements AuditLogService {
} }
private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity, private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
ActionType actionType, ActionType actionType,
Object... additionalInfo) { Object... additionalInfo) {
ObjectNode actionData = objectMapper.createObjectNode(); ObjectNode actionData = objectMapper.createObjectNode();
switch(actionType) { switch (actionType) {
case ADDED: case ADDED:
case UPDATED: case UPDATED:
case ALARM_ACK: case ALARM_ACK:
case ALARM_CLEAR: case ALARM_CLEAR:
case RELATIONS_DELETED: case RELATIONS_DELETED:
case ASSIGNED_TO_TENANT:
if (entity != null) { if (entity != null) {
ObjectNode entityNode = objectMapper.valueToTree(entity); ObjectNode entityNode = objectMapper.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) { if (entityId.getEntityType() == EntityType.DASHBOARD) {
@ -208,7 +208,7 @@ public class AuditLogServiceImpl implements AuditLogService {
scope = extractParameter(String.class, 0, additionalInfo); scope = extractParameter(String.class, 0, additionalInfo);
actionData.put("scope", scope); actionData.put("scope", scope);
List<String> keys = extractParameter(List.class, 1, additionalInfo); List<String> keys = extractParameter(List.class, 1, additionalInfo);
ArrayNode attrsArrayNode = actionData.putArray("attributes"); ArrayNode attrsArrayNode = actionData.putArray("attributes");
if (keys != null) { if (keys != null) {
keys.forEach(attrsArrayNode::add); keys.forEach(attrsArrayNode::add);
} }

3
dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java

@ -18,14 +18,12 @@ package org.thingsboard.server.dao.audit;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
@ -60,5 +58,4 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) { public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
return null; return null;
} }
} }

16
dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity; import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.nosql.DeviceEntity; import org.thingsboard.server.dao.model.nosql.DeviceEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.relation.RelationDao;
@ -198,6 +199,21 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
} }
@Override
public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) {
Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.TENANT_ID_PROPERTY, tenantId.getId())).and(eq(ModelConstants.ID_PROPERTY, id));
log.trace("Execute query {}", query);
DeviceEntity entity = findOneByStatement(tenantId, query);
return DaoUtil.getData(entity);
}
@Override
public ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) {
Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.TENANT_ID_PROPERTY, tenantId.getId())).and(eq(ModelConstants.ID_PROPERTY, id));
log.trace("Execute query {}", query);
return findOneByStatementAsync(tenantId, query);
}
@Override @Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) { public ListenableFuture<List<Device>> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
log.debug("Try to find devices by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); log.debug("Try to find devices by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);

17
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -117,6 +117,23 @@ public interface DeviceDao extends Dao<Device> {
*/ */
ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId); ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId);
/**
* Find devices by tenantId and device id.
* @param tenantId the tenant Id
* @param id the device Id
* @return the device object
*/
Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id);
/**
* Find devices by tenantId and device id.
* @param tenantId tenantId the tenantId
* @param id the deviceId
* @return the device object
*/
ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
/** /**
* Find devices by tenantId, edgeId and page link. * Find devices by tenantId, edgeId and page link.
* *

45
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -28,6 +28,8 @@ import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -54,6 +56,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
@ -104,18 +107,29 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@Autowired
private EventService eventService;
@Override @Override
public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { public Device findDeviceById(TenantId tenantId, DeviceId deviceId) {
log.trace("Executing findDeviceById [{}]", deviceId); log.trace("Executing findDeviceById [{}]", deviceId);
validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
return deviceDao.findById(tenantId, deviceId.getId()); if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
return deviceDao.findById(tenantId, deviceId.getId());
} else {
return deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId());
}
} }
@Override @Override
public ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId) { public ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId) {
log.trace("Executing findDeviceById [{}]", deviceId); log.trace("Executing findDeviceById [{}]", deviceId);
validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
return deviceDao.findByIdAsync(tenantId, deviceId.getId()); if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
return deviceDao.findByIdAsync(tenantId, deviceId.getId());
} else {
return deviceDao.findDeviceByTenantIdAndIdAsync(tenantId, deviceId.getId());
}
} }
@Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}") @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}")
@ -190,7 +204,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
try { try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get(); List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
if (entityViews != null && !entityViews.isEmpty()) { if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete device that is assigned to entity views!"); throw new DataValidationException("Can't delete device that has entity views!");
} }
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for deviceId [{}]", deviceId, e); log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
@ -378,6 +392,31 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
} }
@Transactional
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
@Override
public Device assignDeviceToTenant(TenantId tenantId, Device device) {
log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
if (!CollectionUtils.isEmpty(entityViews)) {
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
}
eventService.removeEvents(device.getTenantId(), device.getId());
relationService.removeRelations(device.getTenantId(), device.getId());
device.setTenantId(tenantId);
device.setCustomerId(null);
return doSaveDevice(device, null);
}
private DataValidator<Device> deviceValidator = private DataValidator<Device> deviceValidator =
new DataValidator<Device>() { new DataValidator<Device>() {

15
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -94,6 +94,21 @@ public class BaseEventService implements EventService {
return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit); return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit);
} }
@Override
public void removeEvents(TenantId tenantId, EntityId entityId) {
TimePageData<Event> eventPageData;
TimePageLink eventPageLink = new TimePageLink(1000);
do {
eventPageData = findEvents(tenantId, entityId, eventPageLink);
for (Event event : eventPageData.getData()) {
eventDao.removeById(tenantId, event.getUuidId());
}
if (eventPageData.hasNext()) {
eventPageLink = eventPageData.getNextPageLink();
}
} while (eventPageData.hasNext());
}
private DataValidator<Event> eventValidator = private DataValidator<Event> eventValidator =
new DataValidator<Event>() { new DataValidator<Event>() {
@Override @Override

16
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -506,6 +506,22 @@ public class BaseRelationService implements RelationService {
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
} }
@Override
public void removeRelations(TenantId tenantId, EntityId entityId) {
Cache cache = cacheManager.getCache(RELATIONS_CACHE);
List<EntityRelation> relations = new ArrayList<>();
for (RelationTypeGroup relationTypeGroup : RelationTypeGroup.values()) {
relations.addAll(findByFrom(tenantId, entityId, relationTypeGroup));
relations.addAll(findByTo(tenantId, entityId, relationTypeGroup));
}
for (EntityRelation relation : relations) {
cacheEviction(relation, cache);
deleteRelation(tenantId, relation);
}
}
protected void validate(EntityRelation relation) { protected void validate(EntityRelation relation) {
if (relation == null) { if (relation == null) {
throw new DataValidationException("Relation type should be specified!"); throw new DataValidationException("Relation type should be specified!");

8
dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.settings; package org.thingsboard.server.dao.settings;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -52,6 +53,13 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) { public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) {
log.trace("Executing saveAdminSettings [{}]", adminSettings); log.trace("Executing saveAdminSettings [{}]", adminSettings);
adminSettingsValidator.validate(adminSettings, data -> tenantId); adminSettingsValidator.validate(adminSettings, data -> tenantId);
if (adminSettings.getKey().equals("mail") && "".equals(adminSettings.getJsonValue().get("password").asText())) {
AdminSettings mailSettings = findAdminSettingsByKey(tenantId, "mail");
if (mailSettings != null) {
((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
}
}
return adminSettingsDao.save(tenantId, adminSettings); return adminSettingsDao.save(tenantId, adminSettings);
} }

3
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -85,4 +85,7 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> {
List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds); List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds);
List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds); List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds);
DeviceEntity findByTenantIdAndId(String tenantId, String id);
} }

10
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -151,6 +151,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId))));
} }
@Override
public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) {
return DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id)));
}
@Override
public ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) {
return service.submit(() -> DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id))));
}
private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) { private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList(); List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) { if (types != null && !types.isEmpty()) {

1
dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java

@ -46,6 +46,7 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null));
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
log.trace("Saving entity: {}", entity); log.trace("Saving entity: {}", entity);
return tsQueue.add(entity); return tsQueue.add(entity);
} }

4
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java

@ -41,8 +41,8 @@ public class HsqlLatestInsertTsRepository extends AbstractInsertRepository imple
"ON (ts_kv_latest.entity_id=T.entity_id " + "ON (ts_kv_latest.entity_id=T.entity_id " +
"AND ts_kv_latest.key=T.key) " + "AND ts_kv_latest.key=T.key) " +
"WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v, ts_kv_latest.json_v = T.json_v " + "WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v, ts_kv_latest.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
@Override @Override
public void saveOrUpdate(List<TsKvLatestEntity> entities) { public void saveOrUpdate(List<TsKvLatestEntity> entities) {

2
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -114,7 +114,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
public User saveUser(User user) { public User saveUser(User user) {
log.trace("Executing saveUser [{}]", user); log.trace("Executing saveUser [{}]", user);
userValidator.validate(user, User::getTenantId); userValidator.validate(user, User::getTenantId);
if (user.getId() == null && !userLoginCaseSensitive) { if (!userLoginCaseSensitive) {
user.setEmail(user.getEmail().toLowerCase()); user.setEmail(user.getEmail().toLowerCase());
} }
User savedUser = userDao.save(user.getTenantId(), user); User savedUser = userDao.save(user.getTenantId(), user);

11
dao/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java

@ -28,6 +28,15 @@ public class JacksonUtil {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
try {
return OBJECT_MAPPER.convertValue(fromValue, toValueType);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The given object value: "
+ fromValue + " cannot be converted to " + toValueType);
}
}
public static <T> T fromString(String string, Class<T> clazz) { public static <T> T fromString(String string, Class<T> clazz) {
try { try {
return OBJECT_MAPPER.readValue(string, clazz); return OBJECT_MAPPER.readValue(string, clazz);
@ -60,4 +69,4 @@ public class JacksonUtil {
public static <T> T clone(T value) { public static <T> T clone(T value) {
return fromString(toString(value), (Class<T>) value.getClass()); return fromString(toString(value), (Class<T>) value.getClass());
} }
} }

1
dao/src/main/resources/sql/schema-ts-psql.sql

@ -105,6 +105,7 @@ BEGIN
AND tablename like 'ts_kv_' || '%' AND tablename like 'ts_kv_' || '%'
AND tablename != 'ts_kv_latest' AND tablename != 'ts_kv_latest'
AND tablename != 'ts_kv_dictionary' AND tablename != 'ts_kv_dictionary'
AND tablename != 'ts_kv_indefinite'
LOOP LOOP
IF partition != partition_by_max_ttl_date THEN IF partition != partition_by_max_ttl_date THEN
IF partition_year IS NOT NULL THEN IF partition_year IS NOT NULL THEN

1
dao/src/test/resources/sql/hsql/drop-all-tables.sql

@ -13,6 +13,7 @@ DROP TABLE IF EXISTS relation;
DROP TABLE IF EXISTS tb_user; DROP TABLE IF EXISTS tb_user;
DROP TABLE IF EXISTS tenant; DROP TABLE IF EXISTS tenant;
DROP TABLE IF EXISTS ts_kv; DROP TABLE IF EXISTS ts_kv;
DROP TABLE IF EXISTS ts_kv_dictionary;
DROP TABLE IF EXISTS ts_kv_latest; DROP TABLE IF EXISTS ts_kv_latest;
DROP TABLE IF EXISTS user_credentials; DROP TABLE IF EXISTS user_credentials;
DROP TABLE IF EXISTS widget_type; DROP TABLE IF EXISTS widget_type;

5
docker/compose-utils.sh

@ -39,6 +39,9 @@ function additionalComposeQueueArgs() {
kafka) kafka)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.kafka.yml" ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.kafka.yml"
;; ;;
confluent)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.confluent.yml"
;;
aws-sqs) aws-sqs)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.aws-sqs.yml" ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.aws-sqs.yml"
;; ;;
@ -52,7 +55,7 @@ function additionalComposeQueueArgs() {
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.service-bus.yml" ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.service-bus.yml"
;; ;;
*) *)
echo "Unknown Queue service value specified: '${TB_QUEUE_TYPE}'. Should be either kafka or aws-sqs or pubsub or rabbitmq or service-bus." >&2 echo "Unknown Queue service value specified: '${TB_QUEUE_TYPE}'. Should be either kafka or confluent or aws-sqs or pubsub or rabbitmq or service-bus." >&2
exit 1 exit 1
esac esac
echo $ADDITIONAL_COMPOSE_QUEUE_ARGS echo $ADDITIONAL_COMPOSE_QUEUE_ARGS

57
docker/docker-compose.confluent.yml

@ -0,0 +1,57 @@
#
# Copyright © 2016-2020 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.
#
version: '2.2'
services:
tb-js-executor:
env_file:
- queue-confluent.env
tb-core1:
env_file:
- queue-confluent.env
depends_on:
- redis
tb-core2:
env_file:
- queue-confluent.env
depends_on:
- redis
tb-rule-engine1:
env_file:
- queue-confluent.env
depends_on:
- redis
tb-rule-engine2:
env_file:
- queue-confluent.env
depends_on:
- redis
tb-mqtt-transport1:
env_file:
- queue-confluent.env
tb-mqtt-transport2:
env_file:
- queue-confluent.env
tb-http-transport1:
env_file:
- queue-confluent.env
tb-http-transport2:
env_file:
- queue-confluent.env
tb-coap-transport:
env_file:
- queue-confluent.env

18
docker/queue-confluent.env

@ -0,0 +1,18 @@
TB_QUEUE_TYPE=kafka
TB_KAFKA_SERVERS=confluent.cloud:9092
TB_QUEUE_KAFKA_REPLICATION_FACTOR=3
TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD=true
TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM=https
TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM=PLAIN
TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG=org.apache.kafka.common.security.plain.PlainLoginModule required username="CLUSTER_API_KEY" password="CLUSTER_API_SECRET";
TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL=SASL_SSL
TB_QUEUE_KAFKA_CONFLUENT_USERNAME=CLUSTER_API_KEY
TB_QUEUE_KAFKA_CONFLUENT_PASSWORD=CLUSTER_API_SECRET
TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:104857600

1
docker/queue-kafka.env

@ -1,2 +1,3 @@
TB_QUEUE_TYPE=kafka TB_QUEUE_TYPE=kafka
TB_KAFKA_SERVERS=kafka:9092 TB_KAFKA_SERVERS=kafka:9092
TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100

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

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

39
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java

@ -15,11 +15,15 @@
*/ */
package org.thingsboard.server.msa; package org.thingsboard.server.msa;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.cassandra.cql3.Json;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.config.Registry; import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder; import org.apache.http.config.RegistryBuilder;
@ -32,6 +36,7 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.SSLContexts;
import org.json.simple.JSONObject;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
@ -42,6 +47,7 @@ import org.thingsboard.rest.client.RestClient;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse; import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
@ -52,6 +58,7 @@ import java.net.URI;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Random; import java.util.Random;
@Slf4j @Slf4j
@ -95,6 +102,17 @@ public abstract class AbstractContainerTest {
} }
}; };
protected Device createGatewayDevice() throws JsonProcessingException {
String isGateway = "{\"gateway\":true}";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode additionalInfo = objectMapper.readTree(isGateway);
Device gatewayDeviceTemplate = new Device();
gatewayDeviceTemplate.setName("mqtt_gateway");
gatewayDeviceTemplate.setType("gateway");
gatewayDeviceTemplate.setAdditionalInfo(additionalInfo);
return restClient.saveDevice(gatewayDeviceTemplate);
}
protected Device createDevice(String name) { protected Device createDevice(String name) {
return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT");
} }
@ -140,6 +158,27 @@ public abstract class AbstractContainerTest {
return expectedValue.equals(list.get(1)); return expectedValue.equals(list.get(1));
} }
protected JsonObject createGatewayConnectPayload(String deviceName){
JsonObject payload = new JsonObject();
payload.addProperty("device", deviceName);
return payload;
}
protected JsonObject createGatewayPayload(String deviceName, long ts){
JsonObject payload = new JsonObject();
payload.add(deviceName, createGatewayTelemetryArray(ts));
return payload;
}
protected JsonArray createGatewayTelemetryArray(long ts){
JsonArray telemetryArray = new JsonArray();
if (ts > 0)
telemetryArray.add(createPayload(ts));
else
telemetryArray.add(createPayload());
return telemetryArray;
}
protected JsonObject createPayload(long ts) { protected JsonObject createPayload(long ts) {
JsonObject values = createPayload(); JsonObject values = createPayload();
JsonObject payload = new JsonObject(); JsonObject payload = new JsonObject();

382
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java

@ -0,0 +1,382 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.connectivity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.checkerframework.checker.units.qual.A;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.thingsboard.mqtt.MqttClient;
import org.thingsboard.mqtt.MqttClientConfig;
import org.thingsboard.mqtt.MqttHandler;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.mapper.AttributesResponse;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
@Slf4j
public class MqttGatewayClientTest extends AbstractContainerTest {
Device gatewayDevice;
MqttClient mqttClient;
Device createdDevice;
MqttMessageListener listener;
@Before
public void createGateway() throws Exception {
restClient.login("tenant@thingsboard.org", "tenant");
this.gatewayDevice = createGatewayDevice();
Optional<DeviceCredentials> gatewayDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(gatewayDevice.getId());
Assert.assertTrue(gatewayDeviceCredentials.isPresent());
this.listener = new MqttMessageListener();
this.mqttClient = getMqttClient(gatewayDeviceCredentials.get(), listener);
this.createdDevice = createDeviceThroughGateway(mqttClient, gatewayDevice);
}
@After
public void removeGateway() throws Exception {
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.gatewayDevice.getId());
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.createdDevice.getId());
this.listener = null;
this.mqttClient = null;
this.createdDevice = null;
}
@Test
public void telemetryUpload() throws Exception {
WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
mqttClient.publish("v1/gateway/telemetry", Unpooled.wrappedBuffer(createGatewayPayload(createdDevice.getName(), -1).toString().getBytes())).get();
WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
log.info("Received telemetry: {}", actualLatestTelemetry);
wsClient.closeBlocking();
Assert.assertEquals(4, actualLatestTelemetry.getData().size());
Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"),
actualLatestTelemetry.getLatestValues().keySet());
Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString()));
Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1"));
Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0)));
Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73)));
}
@Test
public void telemetryUploadWithTs() throws Exception {
long ts = 1451649600512L;
restClient.login("tenant@thingsboard.org", "tenant");
WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
mqttClient.publish("v1/gateway/telemetry", Unpooled.wrappedBuffer(createGatewayPayload(createdDevice.getName(), ts).toString().getBytes())).get();
WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
log.info("Received telemetry: {}", actualLatestTelemetry);
wsClient.closeBlocking();
Assert.assertEquals(4, actualLatestTelemetry.getData().size());
Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues());
Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString()));
Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1"));
Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0)));
Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73)));
}
@Test
public void publishAttributeUpdateToServer() throws Exception {
Optional<DeviceCredentials> createdDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(createdDevice.getId());
Assert.assertTrue(createdDeviceCredentials.isPresent());
WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS);
JsonObject clientAttributes = new JsonObject();
clientAttributes.addProperty("attr1", "value1");
clientAttributes.addProperty("attr2", true);
clientAttributes.addProperty("attr3", 42.0);
clientAttributes.addProperty("attr4", 73);
JsonObject gatewayClientAttributes = new JsonObject();
gatewayClientAttributes.add(createdDevice.getName(), clientAttributes);
mqttClient.publish("v1/gateway/attributes", Unpooled.wrappedBuffer(gatewayClientAttributes.toString().getBytes())).get();
WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
log.info("Received attributes: {}", actualLatestTelemetry);
wsClient.closeBlocking();
Assert.assertEquals(4, actualLatestTelemetry.getData().size());
Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"),
actualLatestTelemetry.getLatestValues().keySet());
Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1"));
Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString()));
Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0)));
Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73)));
}
@Test
public void requestAttributeValuesFromServer() throws Exception {
WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS);
// Add a new client attribute
JsonObject clientAttributes = new JsonObject();
String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8);
clientAttributes.addProperty("clientAttr", clientAttributeValue);
JsonObject gatewayClientAttributes = new JsonObject();
gatewayClientAttributes.add(createdDevice.getName(), clientAttributes);
mqttClient.publish("v1/gateway/attributes", Unpooled.wrappedBuffer(gatewayClientAttributes.toString().getBytes())).get();
WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
log.info("Received ws telemetry: {}", actualLatestTelemetry);
wsClient.closeBlocking();
Assert.assertEquals(1, actualLatestTelemetry.getData().size());
Assert.assertEquals(Sets.newHashSet("clientAttr"),
actualLatestTelemetry.getLatestValues().keySet());
Assert.assertTrue(verify(actualLatestTelemetry, "clientAttr", clientAttributeValue));
// Add a new shared attribute
JsonObject sharedAttributes = new JsonObject();
String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,
createdDevice.getId());
Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful());
MqttEvent sharedAttributeEvent = listener.getEvents().poll(10, TimeUnit.SECONDS);
// Catch attribute update event
Assert.assertNotNull(sharedAttributeEvent);
Assert.assertEquals("v1/gateway/attributes", sharedAttributeEvent.getTopic());
// Subscribe to attributes response
mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get();
// Wait until subscription is processed
TimeUnit.SECONDS.sleep(3);
checkAttribute(true, clientAttributeValue);
checkAttribute(false, sharedAttributeValue);
}
@Test
public void subscribeToAttributeUpdatesFromServer() throws Exception {
mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get();
// Wait until subscription is processed
TimeUnit.SECONDS.sleep(3);
String sharedAttributeName = "sharedAttr";
// Add a new shared attribute
JsonObject sharedAttributes = new JsonObject();
String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue);
JsonObject gatewaySharedAttributeValue = new JsonObject();
gatewaySharedAttributeValue.addProperty("device", createdDevice.getName());
gatewaySharedAttributeValue.add("data", sharedAttributes);
ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,
createdDevice.getId());
Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful());
MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS);
Assert.assertEquals(sharedAttributeValue,
mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText());
// Update the shared attribute value
JsonObject updatedSharedAttributes = new JsonObject();
String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue);
JsonObject gatewayUpdatedSharedAttributeValue = new JsonObject();
gatewayUpdatedSharedAttributeValue.addProperty("device", createdDevice.getName());
gatewayUpdatedSharedAttributeValue.add("data", updatedSharedAttributes);
ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class,
createdDevice.getId());
Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful());
event = listener.getEvents().poll(10, TimeUnit.SECONDS);
Assert.assertEquals(updatedSharedAttributeValue,
mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText());
}
@Test
public void serverSideRpc() throws Exception {
String gatewayRpcTopic = "v1/gateway/rpc";
mqttClient.on(gatewayRpcTopic, listener, MqttQoS.AT_LEAST_ONCE).get();
// Wait until subscription is processed
TimeUnit.SECONDS.sleep(3);
// Send an RPC from the server
JsonObject serverRpcPayload = new JsonObject();
serverRpcPayload.addProperty("method", "getValue");
serverRpcPayload.addProperty("params", true);
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
ListenableFuture<ResponseEntity> future = service.submit(() -> {
try {
return restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}",
mapper.readTree(serverRpcPayload.toString()), String.class,
createdDevice.getId());
} catch (IOException e) {
return ResponseEntity.badRequest().build();
}
});
// Wait for RPC call from the server and send the response
MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS);
Assert.assertNotNull(requestFromServer);
Assert.assertNotNull(requestFromServer.getMessage());
JsonObject requestFromServerJson = new JsonParser().parse(requestFromServer.getMessage()).getAsJsonObject();
Assert.assertEquals(createdDevice.getName(), requestFromServerJson.get("device").getAsString());
JsonObject requestFromServerData = requestFromServerJson.get("data").getAsJsonObject();
Assert.assertEquals("getValue", requestFromServerData.get("method").getAsString());
Assert.assertTrue(requestFromServerData.get("params").getAsBoolean());
int requestId = requestFromServerData.get("id").getAsInt();
JsonObject clientResponse = new JsonObject();
clientResponse.addProperty("response", "someResponse");
JsonObject gatewayResponse = new JsonObject();
gatewayResponse.addProperty("device", createdDevice.getName());
gatewayResponse.addProperty("id", requestId);
gatewayResponse.add("data", clientResponse);
// Send a response to the server's RPC request
mqttClient.publish(gatewayRpcTopic, Unpooled.wrappedBuffer(gatewayResponse.toString().getBytes())).get();
ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS);
Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful());
Assert.assertEquals(clientResponse.toString(), serverResponse.getBody());
}
private void checkAttribute(boolean client, String expectedValue) throws Exception{
JsonObject gatewayAttributesRequest = new JsonObject();
int messageId = new Random().nextInt(100);
gatewayAttributesRequest.addProperty("id", messageId);
gatewayAttributesRequest.addProperty("device", createdDevice.getName());
gatewayAttributesRequest.addProperty("client", client);
String attributeName;
if (client)
attributeName = "clientAttr";
else
attributeName = "sharedAttr";
gatewayAttributesRequest.addProperty("key", attributeName);
log.info(gatewayAttributesRequest.toString());
mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(gatewayAttributesRequest.toString().getBytes())).get();
MqttEvent clientAttributeEvent = listener.getEvents().poll(10, TimeUnit.SECONDS);
Assert.assertNotNull(clientAttributeEvent);
JsonObject responseMessage = new JsonParser().parse(Objects.requireNonNull(clientAttributeEvent).getMessage()).getAsJsonObject();
Assert.assertEquals(messageId, responseMessage.get("id").getAsInt());
Assert.assertEquals(createdDevice.getName(), responseMessage.get("device").getAsString());
Assert.assertEquals(3, responseMessage.entrySet().size());
Assert.assertEquals(expectedValue, responseMessage.get("value").getAsString());
}
private Device createDeviceThroughGateway(MqttClient mqttClient, Device gatewayDevice) throws Exception {
String deviceName = "mqtt_device";
mqttClient.publish("v1/gateway/connect", Unpooled.wrappedBuffer(createGatewayConnectPayload(deviceName).toString().getBytes())).get();
TimeUnit.SECONDS.sleep(3);
List<EntityRelation> relations = restClient.findByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON);
Assert.assertEquals(1, relations.size());
EntityId createdEntityId = relations.get(0).getTo();
DeviceId createdDeviceId = new DeviceId(createdEntityId.getId());
Optional<Device> createdDevice = restClient.getDeviceById(createdDeviceId);
Assert.assertTrue(createdDevice.isPresent());
return createdDevice.get();
}
private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException {
MqttClientConfig clientConfig = new MqttClientConfig();
clientConfig.setClientId("MQTT client from test");
clientConfig.setUsername(deviceCredentials.getCredentialsId());
MqttClient mqttClient = MqttClient.create(clientConfig, listener);
mqttClient.connect("localhost", 1883).get();
return mqttClient;
}
@Data
private class MqttMessageListener implements MqttHandler {
private final BlockingQueue<MqttEvent> events;
private MqttMessageListener() {
events = new ArrayBlockingQueue<>(100);
}
@Override
public void onMessage(String topic, ByteBuf message) {
log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic);
events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8)));
}
}
@Data
private class MqttEvent {
private final String topic;
private final String message;
}
}

6
msa/js-executor/config/custom-environment-variables.yml

@ -26,6 +26,12 @@ kafka:
servers: "TB_KAFKA_SERVERS" servers: "TB_KAFKA_SERVERS"
replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR" replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR"
topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES"
use_confluent_cloud: "TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD"
confluent:
sasl:
mechanism: "TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM"
username: "TB_QUEUE_KAFKA_CONFLUENT_USERNAME"
password: "TB_QUEUE_KAFKA_CONFLUENT_PASSWORD"
pubsub: pubsub:
project_id: "TB_QUEUE_PUBSUB_PROJECT_ID" project_id: "TB_QUEUE_PUBSUB_PROJECT_ID"

6
msa/js-executor/config/default.yml

@ -25,7 +25,11 @@ kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers
servers: "localhost:9092" servers: "localhost:9092"
replication_factor: "1" replication_factor: "1"
topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600" topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100"
use_confluent_cloud: false
confluent:
sasl:
mechanism: "PLAIN"
pubsub: pubsub:
queue_properties: "ackDeadlineInSec:30;messageRetentionInSec:604800" queue_properties: "ackDeadlineInSec:30;messageRetentionInSec:604800"

23
msa/js-executor/package-lock.json

@ -1872,12 +1872,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -1892,17 +1894,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -2019,7 +2024,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -2031,6 +2037,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -2045,6 +2052,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -2156,7 +2164,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -2168,6 +2177,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -2289,6 +2299,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",

2
msa/js-executor/package.json

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

2
msa/js-executor/pom.xml

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

38
msa/js-executor/queue/kafkaTemplate.js

@ -34,7 +34,7 @@ function KafkaProducer() {
this.send = async (responseTopic, scriptId, rawResponse, headers) => { this.send = async (responseTopic, scriptId, rawResponse, headers) => {
if (!topics.includes(responseTopic)) { if (!topics.includes(responseTopic)) {
let createResponseTopicResult = await createTopic(responseTopic); let createResponseTopicResult = await createTopic(responseTopic, 1);
topics.push(responseTopic); topics.push(responseTopic);
if (createResponseTopicResult) { if (createResponseTopicResult) {
logger.info('Created new topic: %s', requestTopic); logger.info('Created new topic: %s', requestTopic);
@ -61,22 +61,45 @@ function KafkaProducer() {
const kafkaBootstrapServers = config.get('kafka.bootstrap.servers'); const kafkaBootstrapServers = config.get('kafka.bootstrap.servers');
const requestTopic = config.get('request_topic'); const requestTopic = config.get('request_topic');
const useConfluent = config.get('kafka.use_confluent_cloud');
logger.info('Kafka Bootstrap Servers: %s', kafkaBootstrapServers); logger.info('Kafka Bootstrap Servers: %s', kafkaBootstrapServers);
logger.info('Kafka Requests Topic: %s', requestTopic); logger.info('Kafka Requests Topic: %s', requestTopic);
kafkaClient = new Kafka({ let kafkaConfig = {
brokers: kafkaBootstrapServers.split(','), brokers: kafkaBootstrapServers.split(','),
logLevel: logLevel.INFO, logLevel: logLevel.INFO,
logCreator: KafkaJsWinstonLogCreator logCreator: KafkaJsWinstonLogCreator
}); };
if (useConfluent) {
kafkaConfig['sasl'] = {
mechanism: config.get('kafka.confluent.sasl.mechanism'),
username: config.get('kafka.confluent.username'),
password: config.get('kafka.confluent.password')
};
kafkaConfig['ssl'] = true;
}
kafkaClient = new Kafka(kafkaConfig);
parseTopicProperties(); parseTopicProperties();
kafkaAdmin = kafkaClient.admin(); kafkaAdmin = kafkaClient.admin();
await kafkaAdmin.connect(); await kafkaAdmin.connect();
let createRequestTopicResult = await createTopic(requestTopic); let partitions = 1;
for (let i = 0; i < configEntries.length; i++) {
let param = configEntries[i];
if (param.name === 'partitions') {
partitions = param.value;
configEntries.splice(i, 1);
break;
}
}
let createRequestTopicResult = await createTopic(requestTopic, partitions);
if (createRequestTopicResult) { if (createRequestTopicResult) {
logger.info('Created new topic: %s', requestTopic); logger.info('Created new topic: %s', requestTopic);
@ -109,10 +132,11 @@ function KafkaProducer() {
} }
})(); })();
function createTopic(topic) { function createTopic(topic, partitions) {
return kafkaAdmin.createTopics({ return kafkaAdmin.createTopics({
topics: [{ topics: [{
topic: topic, topic: topic,
numPartitions: partitions,
replicationFactor: replicationFactor, replicationFactor: replicationFactor,
configEntries: configEntries configEntries: configEntries
}] }]

2
msa/pom.xml

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

2
msa/tb-node/pom.xml

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

2
msa/tb/pom.xml

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

2
msa/transport/coap/pom.xml

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

2
msa/transport/http/pom.xml

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

2
msa/transport/mqtt/pom.xml

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

2
msa/transport/pom.xml

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

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-web-ui", "name": "thingsboard-web-ui",
"private": true, "private": true,
"version": "2.5.3", "version": "2.5.5",
"description": "ThingsBoard Web UI Microservice", "description": "ThingsBoard Web UI Microservice",
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",

2
msa/web-ui/pom.xml

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

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

Loading…
Cancel
Save