Browse Source

Merge branch 'develop/3.5.2' into feature/basic-widget-config

feature/basic-widget-config
Igor Kulikov 3 years ago
parent
commit
72db906fef
  1. 1
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 45
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  3. 3
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  4. 3
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
  5. 26
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  6. 20
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  7. 1
      application/src/main/java/org/thingsboard/server/controller/QueueController.java
  8. 34
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  10. 48
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  11. 96
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
  12. 73
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java
  13. 77
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java
  14. 69
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProfileProcessor.java
  15. 77
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java
  16. 85
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java
  17. 14
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java
  18. 102
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProfileProcessor.java
  19. 30
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
  20. 74
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java
  21. 76
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java
  22. 93
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java
  23. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  24. 43
      application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java
  25. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java
  26. 13
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  27. 4
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  28. 128
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  29. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  30. 13
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
  31. 7
      application/src/main/resources/thingsboard.yml
  32. 21
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  33. 248
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  34. 17
      application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java
  35. 120
      application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java
  36. 57
      application/src/test/java/org/thingsboard/server/edge/AssetProfileEdgeTest.java
  37. 80
      application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java
  38. 71
      application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
  39. 154
      application/src/test/java/org/thingsboard/server/edge/EntityViewEdgeTest.java
  40. 149
      application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
  41. 70
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  42. 16
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  43. 12
      application/src/test/java/org/thingsboard/server/service/notification/TestNotificationSettingsService.java
  44. 2
      application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java
  45. 2
      application/src/test/resources/application-test.properties
  46. 2
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorCtx.java
  47. 10
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java
  48. 6
      common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java
  49. 6
      common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueConsumer.java
  50. 1
      common/cluster-api/src/main/proto/queue.proto
  51. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java
  52. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  53. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  54. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java
  55. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  56. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java
  57. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  58. 82
      common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/UserNotificationSettings.java
  59. 2
      common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java
  60. 15
      common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java
  61. 2
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java
  62. 10
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  63. 4
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java
  64. 7
      common/edge-api/src/main/proto/edge.proto
  65. 2
      common/queue/src/main/java/org/thingsboard/server/queue/RuleEngineTbQueueAdminFactory.java
  66. 7
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusAdmin.java
  67. 29
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java
  68. 14
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java
  69. 133
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  70. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java
  71. 5
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbServiceInfoProvider.java
  72. 2
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfo.java
  73. 1
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfoService.java
  74. 6
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java
  75. 7
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  76. 5
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  77. 16
      common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java
  78. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java
  79. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  80. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java
  81. 2
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubAdmin.java
  82. 9
      common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqAdmin.java
  83. 8
      common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java
  84. 4
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsAdmin.java
  85. 8
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java
  86. 14
      common/queue/src/main/java/org/thingsboard/server/queue/util/PropertyUtils.java
  87. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java
  88. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java
  89. 16
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  90. 18
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  91. 13
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  92. 16
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  93. 16
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  94. 64
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  95. 2
      dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java
  96. 11
      dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java
  97. 2
      msa/vc-executor/src/main/java/org/thingsboard/server/vc/service/VersionControlTenantRoutingInfoService.java
  98. 1
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  99. 2
      pom.xml
  100. 118
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java

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

@ -246,6 +246,7 @@ public class ActorSystemContext {
private RuleNodeStateService ruleNodeStateService;
@Autowired
@Getter
private PartitionService partitionService;
@Autowired

45
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -46,6 +46,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Slf4j
@ -129,8 +130,11 @@ public class AppActor extends ContextAwareActor {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
for (Tenant tenant : tenantIterator) {
log.debug("[{}] Creating tenant actor", tenant.getId());
getOrCreateTenantActor(tenant.getId());
log.debug("[{}] Tenant actor created.", tenant.getId());
getOrCreateTenantActor(tenant.getId()).ifPresentOrElse(tenantActor -> {
log.debug("[{}] Tenant actor created.", tenant.getId());
}, () -> {
log.debug("[{}] Skipped actor creation", tenant.getId());
});
}
}
log.info("Main system actor started.");
@ -143,11 +147,9 @@ public class AppActor extends ContextAwareActor {
if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) {
msg.getMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!"));
} else {
if (!deletedTenants.contains(msg.getTenantId())) {
getOrCreateTenantActor(msg.getTenantId()).tell(msg);
} else {
msg.getMsg().getCallback().onSuccess();
}
getOrCreateTenantActor(msg.getTenantId()).ifPresentOrElse(actor -> {
actor.tell(msg);
}, () -> msg.getMsg().getCallback().onSuccess());
}
}
@ -164,12 +166,13 @@ public class AppActor extends ContextAwareActor {
log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg);
deletedTenants.add(tenantId);
ctx.stop(new TbEntityActorId(tenantId));
} else {
target = getOrCreateTenantActor(msg.getTenantId());
return;
}
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
target = getOrCreateTenantActor(msg.getTenantId()).orElseGet(() -> {
log.debug("Ignoring component lifecycle msg for tenant {} because it is not managed by this service", msg.getTenantId());
return null;
});
}
if (target != null) {
target.tellWithHighPriority(msg);
@ -179,24 +182,28 @@ public class AppActor extends ContextAwareActor {
}
private void onToDeviceActorMsg(TenantAwareMsg msg, boolean priority) {
if (!deletedTenants.contains(msg.getTenantId())) {
TbActorRef tenantActor = getOrCreateTenantActor(msg.getTenantId());
getOrCreateTenantActor(msg.getTenantId()).ifPresentOrElse(tenantActor -> {
if (priority) {
tenantActor.tellWithHighPriority(msg);
} else {
tenantActor.tell(msg);
}
} else {
}, () -> {
if (msg instanceof TransportToDeviceActorMsgWrapper) {
((TransportToDeviceActorMsgWrapper) msg).getCallback().onSuccess();
}
}
});
}
private TbActorRef getOrCreateTenantActor(TenantId tenantId) {
return ctx.getOrCreateChildActor(new TbEntityActorId(tenantId),
private Optional<TbActorRef> getOrCreateTenantActor(TenantId tenantId) {
if (deletedTenants.contains(tenantId)) {
return Optional.empty();
}
return Optional.ofNullable(ctx.getOrCreateChildActor(new TbEntityActorId(tenantId),
() -> DefaultActorService.TENANT_DISPATCHER_NAME,
() -> new TenantActor.ActorCreator(systemContext, tenantId));
() -> new TenantActor.ActorCreator(systemContext, tenantId),
() -> systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE) ||
systemContext.getPartitionService().isManagedByCurrentService(tenantId)));
}
private void onToEdgeSessionMsg(EdgeSessionMsg msg) {
@ -204,7 +211,7 @@ public class AppActor extends ContextAwareActor {
if (ModelConstants.SYSTEM_TENANT.equals(msg.getTenantId())) {
log.warn("Message has system tenant id: {}", msg);
} else {
target = getOrCreateTenantActor(msg.getTenantId());
target = getOrCreateTenantActor(msg.getTenantId()).orElse(null);
}
if (target != null) {
target.tellWithHighPriority(msg);

3
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -167,7 +167,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private TbActorRef createRuleNodeActor(TbActorCtx ctx, RuleNode ruleNode) {
return ctx.getOrCreateChildActor(new TbEntityActorId(ruleNode.getId()),
() -> DefaultActorService.RULE_DISPATCHER_NAME,
() -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleChainName, ruleNode.getId()));
() -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleChainName, ruleNode.getId()),
() -> true);
}
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {

3
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java

@ -94,7 +94,8 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
} else {
return new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain);
}
});
},
() -> true);
}
protected TbActorRef getEntityActorRef(EntityId entityId) {

26
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -32,7 +32,6 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
@ -82,21 +81,21 @@ public class TenantActor extends RuleChainManagerActor {
cantFindTenant = true;
log.info("[{}] Started tenant actor for missing tenant.", tenantId);
} else {
TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId());
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
if (isRuleEngine) {
try {
if (getApiUsageState().isReExecEnabled()) {
log.debug("[{}] Going to init rule chains", tenantId);
initRuleChains();
} else {
log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
if (systemContext.getPartitionService().isManagedByCurrentService(tenantId)) {
try {
if (getApiUsageState().isReExecEnabled()) {
log.debug("[{}] Going to init rule chains", tenantId);
initRuleChains();
} else {
log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
}
} catch (Exception e) {
log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e);
cantFindTenant = true;
}
} catch (Exception e) {
log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e);
cantFindTenant = true;
}
}
log.debug("[{}] Tenant actor started.", tenantId);
@ -270,7 +269,8 @@ public class TenantActor extends RuleChainManagerActor {
private TbActorRef getOrCreateDeviceActor(DeviceId deviceId) {
return ctx.getOrCreateChildActor(new TbEntityActorId(deviceId),
() -> DefaultActorService.DEVICE_DISPATCHER_NAME,
() -> new DeviceActorCreator(systemContext, tenantId, deviceId));
() -> new DeviceActorCreator(systemContext, tenantId, deviceId),
() -> true);
}
private void onToEdgeSessionMsg(EdgeSessionMsg msg) {

20
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
@ -297,7 +298,7 @@ public class NotificationController extends BaseController {
if (targetType == NotificationTargetType.PLATFORM_USERS) {
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(),
(PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize, 0, null,
new SortOrder("createdTime", SortOrder.Direction.DESC)));
SortOrder.BY_CREATED_TIME_DESC));
recipientsCount = (int) recipients.getTotalElements();
recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
} else {
@ -431,10 +432,23 @@ public class NotificationController extends BaseController {
notes = "Returns the list of delivery methods that are properly configured and are allowed to be used for sending notifications." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/deliveryMethods")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ);
return notificationCenter.getAvailableDeliveryMethods(user.getTenantId());
}
@PostMapping("/notification/settings/user")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public UserNotificationSettings saveUserNotificationSettings(@RequestBody @Valid UserNotificationSettings settings,
@AuthenticationPrincipal SecurityUser user) {
return notificationSettingsService.saveUserNotificationSettings(user.getTenantId(), user.getId(), settings);
}
@GetMapping("/notification/settings/user")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public UserNotificationSettings getUserNotificationSettings(@AuthenticationPrincipal SecurityUser user) {
return notificationSettingsService.getUserNotificationSettings(user.getTenantId(), user.getId(), true);
}
}

1
application/src/main/java/org/thingsboard/server/controller/QueueController.java

@ -126,7 +126,6 @@ public class QueueController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/queues", params = {"serviceType"}, method = RequestMethod.POST)
@ResponseBody
public Queue saveQueue(@ApiParam(value = "A JSON value representing the queue.")
@RequestBody Queue queue,
@ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true)

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

@ -39,12 +39,16 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
import org.thingsboard.server.gen.edge.v1.ConnectRequestMsg;
import org.thingsboard.server.gen.edge.v1.ConnectResponseCode;
import org.thingsboard.server.gen.edge.v1.ConnectResponseMsg;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
@ -53,6 +57,7 @@ import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg;
import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
@ -151,8 +156,8 @@ public final class EdgeGrpcSession implements Closeable {
}
if (connected) {
if (requestMsg.getMsgType().equals(RequestMsgType.SYNC_REQUEST_RPC_MESSAGE)) {
if (requestMsg.hasSyncRequestMsg() && requestMsg.getSyncRequestMsg().getSyncRequired()) {
boolean fullSync = true;
if (requestMsg.hasSyncRequestMsg()) {
boolean fullSync = false;
if (requestMsg.getSyncRequestMsg().hasFullSync()) {
fullSync = requestMsg.getSyncRequestMsg().getFullSync();
}
@ -654,6 +659,11 @@ public final class EdgeGrpcSession implements Closeable {
result.addAll(ctx.getTelemetryProcessor().processTelemetryMsg(edge.getTenantId(), entityData));
}
}
if (uplinkMsg.getDeviceProfileUpdateMsgCount() > 0) {
for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : uplinkMsg.getDeviceProfileUpdateMsgList()) {
result.add(ctx.getDeviceProfileProcessor().processDeviceProfileMsgFromEdge(edge.getTenantId(), edge, deviceProfileUpdateMsg));
}
}
if (uplinkMsg.getDeviceUpdateMsgCount() > 0) {
for (DeviceUpdateMsg deviceUpdateMsg : uplinkMsg.getDeviceUpdateMsgList()) {
result.add(ctx.getDeviceProcessor().processDeviceMsgFromEdge(edge.getTenantId(), edge, deviceUpdateMsg));
@ -664,16 +674,36 @@ public final class EdgeGrpcSession implements Closeable {
result.add(ctx.getDeviceProcessor().processDeviceCredentialsMsg(edge.getTenantId(), deviceCredentialsUpdateMsg));
}
}
if (uplinkMsg.getAssetProfileUpdateMsgCount() > 0) {
for (AssetProfileUpdateMsg assetProfileUpdateMsg : uplinkMsg.getAssetProfileUpdateMsgList()) {
result.add(ctx.getAssetProfileProcessor().processAssetProfileMsgFromEdge(edge.getTenantId(), edge, assetProfileUpdateMsg));
}
}
if (uplinkMsg.getAssetUpdateMsgCount() > 0) {
for (AssetUpdateMsg assetUpdateMsg : uplinkMsg.getAssetUpdateMsgList()) {
result.add(ctx.getAssetProcessor().processAssetMsgFromEdge(edge.getTenantId(), edge, assetUpdateMsg));
}
}
if (uplinkMsg.getAlarmUpdateMsgCount() > 0) {
for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) {
result.add(ctx.getAlarmProcessor().processAlarmMsg(edge.getTenantId(), alarmUpdateMsg));
}
}
if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) {
for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) {
result.add(ctx.getEntityViewProcessor().processEntityViewMsgFromEdge(edge.getTenantId(), edge, entityViewUpdateMsg));
}
}
if (uplinkMsg.getRelationUpdateMsgCount() > 0) {
for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) {
result.add(ctx.getRelationProcessor().processRelationMsg(edge.getTenantId(), relationUpdateMsg));
}
}
if (uplinkMsg.getDashboardUpdateMsgCount() > 0) {
for (DashboardUpdateMsg dashboardUpdateMsg : uplinkMsg.getDashboardUpdateMsgList()) {
result.add(ctx.getDashboardProcessor().processDashboardMsgFromEdge(edge.getTenantId(), edge, dashboardUpdateMsg));
}
}
if (uplinkMsg.getRuleChainMetadataRequestMsgCount() > 0) {
for (RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg : uplinkMsg.getRuleChainMetadataRequestMsgList()) {
result.add(ctx.getEdgeRequestsService().processRuleChainMetadataRequestMsg(edge.getTenantId(), edge, ruleChainMetadataRequestMsg));

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

@ -51,8 +51,6 @@ public class EdgeSyncCursor {
fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService()));
fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new AssetProfilesEdgeEventFetcher(ctx.getAssetProfileService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId());
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId()));
@ -61,6 +59,8 @@ public class EdgeSyncCursor {
fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));
}
}
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new AssetProfilesEdgeEventFetcher(ctx.getAssetProfileService()));
fetchers.add(new DevicesEdgeEventFetcher(ctx.getDeviceService()));
fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService()));
fetchers.add(new EntityViewsEdgeEventFetcher(ctx.getEntityViewService()));

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

@ -23,9 +23,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -43,8 +48,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
@ -106,6 +114,7 @@ import java.util.concurrent.locks.ReentrantLock;
public abstract class BaseEdgeProcessor {
protected static final Lock deviceCreationLock = new ReentrantLock();
protected static final Lock assetCreationLock = new ReentrantLock();
protected static final int DEFAULT_PAGE_SIZE = 100;
@ -197,6 +206,21 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected DataValidator<Device> deviceValidator;
@Autowired
protected DataValidator<DeviceProfile> deviceProfileValidator;
@Autowired
protected DataValidator<Asset> assetValidator;
@Autowired
protected DataValidator<AssetProfile> assetProfileValidator;
@Autowired
protected DataValidator<Dashboard> dashboardValidator;
@Autowired
protected DataValidator<EntityView> entityViewValidator;
@Autowired
protected EdgeMsgConstructor edgeMsgConstructor;
@ -520,4 +544,28 @@ public abstract class BaseEdgeProcessor {
return false;
}
}
protected void createRelationFromEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId) {
EntityRelation relation = new EntityRelation();
relation.setFrom(edgeId);
relation.setTo(entityId);
relation.setTypeGroup(RelationTypeGroup.COMMON);
relation.setType(EntityRelation.EDGE_TYPE);
relationService.saveRelation(tenantId, relation);
}
protected TbMsgMetaData getActionTbMsgMetaData(Edge edge, CustomerId customerId) {
TbMsgMetaData metaData = getTbMsgMetaData(edge);
if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString());
}
return metaData;
}
protected TbMsgMetaData getTbMsgMetaData(Edge edge) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("edgeId", edge.getId().toString());
metaData.putValue("edgeName", edge.getName());
return metaData;
}
}

96
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java

@ -15,23 +15,113 @@
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Component
@Slf4j
@TbCoreComponent
public class AssetEdgeProcessor extends BaseEdgeProcessor {
public class AssetEdgeProcessor extends BaseAssetProcessor {
public ListenableFuture<Void> processAssetMsgFromEdge(TenantId tenantId, Edge edge, AssetUpdateMsg assetUpdateMsg) {
log.trace("[{}] executing processAssetMsgFromEdge [{}] from edge [{}]", tenantId, assetUpdateMsg, edge.getName());
AssetId assetId = new AssetId(new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB()));
try {
edgeSynchronizationManager.getSync().set(true);
switch (assetUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
saveOrUpdateAsset(tenantId, assetId, assetUpdateMsg, edge);
return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE:
Asset assetToDelete = assetService.findAssetById(tenantId, assetId);
if (assetToDelete != null) {
assetService.unassignAssetFromEdge(tenantId, assetId, edge.getId());
}
return Futures.immediateFuture(null);
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(assetUpdateMsg.getMsgType());
}
} catch (DataValidationException e) {
if (e.getMessage().contains("limit reached")) {
log.warn("[{}] Number of allowed asset violated {}", tenantId, assetUpdateMsg, e);
return Futures.immediateFuture(null);
} else {
return Futures.immediateFailedFuture(e);
}
} finally {
edgeSynchronizationManager.getSync().remove();
}
}
private void saveOrUpdateAsset(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg, Edge edge) {
CustomerId customerId = safeGetCustomerId(assetUpdateMsg.getCustomerIdMSB(), assetUpdateMsg.getCustomerIdLSB());
Pair<Boolean, Boolean> resultPair = super.saveOrUpdateAsset(tenantId, assetId, assetUpdateMsg, customerId);
Boolean created = resultPair.getFirst();
if (created) {
createRelationFromEdge(tenantId, edge.getId(), assetId);
pushAssetCreatedEventToRuleEngine(tenantId, edge, assetId);
assetService.assignAssetToEdge(tenantId, assetId, edge.getId());
}
Boolean assetNameUpdated = resultPair.getSecond();
if (assetNameUpdated) {
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET, EdgeEventActionType.UPDATED, assetId, null);
}
}
private void pushAssetCreatedEventToRuleEngine(TenantId tenantId, Edge edge, AssetId assetId) {
try {
Asset asset = assetService.findAssetById(tenantId, assetId);
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(asset);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, assetId, asset.getCustomerId(),
getActionTbMsgMetaData(edge, asset.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, assetId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", asset);
}
@Override
public void onFailure(Throwable t) {
log.warn("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", asset, t);
}
});
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push asset action to rule engine: {}", assetId, DataConstants.ENTITY_CREATED, e);
}
}
public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent) {
AssetId assetId = new AssetId(edgeEvent.getEntityId());
@ -43,7 +133,7 @@ public class AssetEdgeProcessor extends BaseEdgeProcessor {
case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER:
Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId);
if (asset != null) {
if (asset != null && !BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
AssetUpdateMsg assetUpdateMsg =
assetMsgConstructor.constructAssetUpdatedMsg(msgType, asset);

73
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java

@ -15,22 +15,91 @@
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Component
@Slf4j
@TbCoreComponent
public class AssetProfileEdgeProcessor extends BaseEdgeProcessor {
public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor {
public ListenableFuture<Void> processAssetProfileMsgFromEdge(TenantId tenantId, Edge edge, AssetProfileUpdateMsg assetProfileUpdateMsg) {
log.trace("[{}] executing processAssetProfileMsgFromEdge [{}] from edge [{}]", tenantId, assetProfileUpdateMsg, edge.getName());
AssetProfileId assetProfileId = new AssetProfileId(new UUID(assetProfileUpdateMsg.getIdMSB(), assetProfileUpdateMsg.getIdLSB()));
try {
edgeSynchronizationManager.getSync().set(true);
switch (assetProfileUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
saveOrUpdateAssetProfile(tenantId, assetProfileId, assetProfileUpdateMsg, edge);
return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE:
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(assetProfileUpdateMsg.getMsgType());
}
} catch (DataValidationException e) {
log.warn("Failed to process AssetProfileUpdateMsg from Edge [{}]", assetProfileUpdateMsg, e);
return Futures.immediateFailedFuture(e);
} finally {
edgeSynchronizationManager.getSync().remove();
}
}
private void saveOrUpdateAssetProfile(TenantId tenantId, AssetProfileId assetProfileId, AssetProfileUpdateMsg assetProfileUpdateMsg, Edge edge) {
boolean created = super.saveOrUpdateAssetProfile(tenantId, assetProfileId, assetProfileUpdateMsg);
if (created) {
createRelationFromEdge(tenantId, edge.getId(), assetProfileId);
pushAssetProfileCreatedEventToRuleEngine(tenantId, edge, assetProfileId);
}
}
private void pushAssetProfileCreatedEventToRuleEngine(TenantId tenantId, Edge edge, AssetProfileId assetProfileId) {
try {
AssetProfile assetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfileId);
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(assetProfile);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, assetProfileId, getTbMsgMetaData(edge),
TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, assetProfileId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", assetProfile);
}
@Override
public void onFailure(Throwable t) {
log.warn("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", assetProfile, t);
}
});
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push asset profile action to rule engine: {}", assetProfileId, DataConstants.ENTITY_CREATED, e);
}
}
public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent) {
AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId());

77
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java

@ -0,0 +1,77 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Slf4j
public abstract class BaseAssetProcessor extends BaseEdgeProcessor {
protected Pair<Boolean, Boolean> saveOrUpdateAsset(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg, CustomerId customerId) {
boolean created = false;
boolean assetNameUpdated = false;
assetCreationLock.lock();
try {
Asset asset = assetService.findAssetById(tenantId, assetId);
String assetName = assetUpdateMsg.getName();
if (asset == null) {
created = true;
asset = new Asset();
asset.setTenantId(tenantId);
asset.setCreatedTime(Uuids.unixTimestamp(assetId.getId()));
}
Asset assetByName = assetService.findAssetByTenantIdAndName(tenantId, assetName);
if (assetByName != null && !assetByName.getId().equals(assetId)) {
assetName = assetName + "_" + StringUtils.randomAlphanumeric(15);
log.warn("Asset with name {} already exists. Renaming asset name to {}",
assetUpdateMsg.getName(), assetName);
assetNameUpdated = true;
}
asset.setName(assetName);
asset.setType(assetUpdateMsg.getType());
asset.setLabel(assetUpdateMsg.hasLabel() ? assetUpdateMsg.getLabel() : null);
asset.setAdditionalInfo(assetUpdateMsg.hasAdditionalInfo()
? JacksonUtil.toJsonNode(assetUpdateMsg.getAdditionalInfo()) : null);
UUID assetProfileUUID = safeGetUUID(assetUpdateMsg.getAssetProfileIdMSB(), assetUpdateMsg.getAssetProfileIdLSB());
asset.setAssetProfileId(assetProfileUUID != null ? new AssetProfileId(assetProfileUUID) : null);
asset.setCustomerId(customerId);
assetValidator.validate(asset, Asset::getTenantId);
if (created) {
asset.setId(assetId);
}
assetService.saveAsset(asset, false);
} finally {
assetCreationLock.unlock();
}
return Pair.of(created, assetNameUpdated);
}
}

69
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProfileProcessor.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@Slf4j
public class BaseAssetProfileProcessor extends BaseEdgeProcessor {
protected boolean saveOrUpdateAssetProfile(TenantId tenantId, AssetProfileId assetProfileId, AssetProfileUpdateMsg assetProfileUpdateMsg) {
boolean created = false;
assetCreationLock.lock();
try {
AssetProfile assetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfileId);
String assetProfileName = assetProfileUpdateMsg.getName();
if (assetProfile == null) {
created = true;
assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfile.setCreatedTime(Uuids.unixTimestamp(assetProfileId.getId()));
}
assetProfile.setName(assetProfileName);
assetProfile.setDefault(assetProfileUpdateMsg.getDefault());
assetProfile.setDefaultQueueName(assetProfileUpdateMsg.hasDefaultQueueName() ? assetProfileUpdateMsg.getDefaultQueueName() : null);
assetProfile.setDescription(assetProfileUpdateMsg.hasDescription() ? assetProfileUpdateMsg.getDescription() : null);
assetProfile.setImage(assetProfileUpdateMsg.hasImage()
? new String(assetProfileUpdateMsg.getImage().toByteArray(), StandardCharsets.UTF_8) : null);
UUID defaultRuleChainUUID = safeGetUUID(assetProfileUpdateMsg.getDefaultRuleChainIdMSB(), assetProfileUpdateMsg.getDefaultRuleChainIdLSB());
assetProfile.setDefaultRuleChainId(defaultRuleChainUUID != null ? new RuleChainId(defaultRuleChainUUID) : null);
UUID defaultDashboardUUID = safeGetUUID(assetProfileUpdateMsg.getDefaultDashboardIdMSB(), assetProfileUpdateMsg.getDefaultDashboardIdLSB());
assetProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : null);
assetProfileValidator.validate(assetProfile, AssetProfile::getTenantId);
if (created) {
assetProfile.setId(assetProfileId);
}
assetProfileService.saveAssetProfile(assetProfile, false);
} finally {
assetCreationLock.unlock();
}
return created;
}
}

77
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java

@ -0,0 +1,77 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.dashboard;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.Set;
@Slf4j
public abstract class BaseDashboardProcessor extends BaseEdgeProcessor {
protected boolean saveOrUpdateDashboard(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg, CustomerId customerId) {
boolean created = false;
Dashboard dashboard = dashboardService.findDashboardById(tenantId, dashboardId);
if (dashboard == null) {
created = true;
dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
dashboard.setCreatedTime(Uuids.unixTimestamp(dashboardId.getId()));
}
dashboard.setTitle(dashboardUpdateMsg.getTitle());
dashboard.setConfiguration(JacksonUtil.toJsonNode(dashboardUpdateMsg.getConfiguration()));
Set<ShortCustomerInfo> assignedCustomers = null;
if (dashboardUpdateMsg.hasAssignedCustomers()) {
assignedCustomers = JacksonUtil.fromString(dashboardUpdateMsg.getAssignedCustomers(), new TypeReference<>() {
});
dashboard.setAssignedCustomers(assignedCustomers);
}
dashboardValidator.validate(dashboard, Dashboard::getTenantId);
if (created) {
dashboard.setId(dashboardId);
}
Dashboard savedDashboard = dashboardService.saveDashboard(dashboard, false);
if (assignedCustomers != null && !assignedCustomers.isEmpty()) {
for (ShortCustomerInfo assignedCustomer : assignedCustomers) {
if (assignedCustomer.getCustomerId().equals(customerId)) {
dashboardService.assignDashboardToCustomer(tenantId, dashboardId, assignedCustomer.getCustomerId());
}
}
} else {
unassignCustomersFromDashboard(tenantId, savedDashboard);
}
return created;
}
private void unassignCustomersFromDashboard(TenantId tenantId, Dashboard dashboard) {
if (dashboard.getAssignedCustomers() != null && !dashboard.getAssignedCustomers().isEmpty()) {
for (ShortCustomerInfo assignedCustomer : dashboard.getAssignedCustomers()) {
dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), assignedCustomer.getCustomerId());
}
}
}
}

85
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java

@ -15,22 +15,103 @@
*/
package org.thingsboard.server.service.edge.rpc.processor.dashboard;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Component
@Slf4j
@TbCoreComponent
public class DashboardEdgeProcessor extends BaseEdgeProcessor {
public class DashboardEdgeProcessor extends BaseDashboardProcessor {
public ListenableFuture<Void> processDashboardMsgFromEdge(TenantId tenantId, Edge edge, DashboardUpdateMsg dashboardUpdateMsg) {
log.trace("[{}] executing processDashboardMsgFromEdge [{}] from edge [{}]", tenantId, dashboardUpdateMsg, edge.getName());
DashboardId dashboardId = new DashboardId(new UUID(dashboardUpdateMsg.getIdMSB(), dashboardUpdateMsg.getIdLSB()));
try {
edgeSynchronizationManager.getSync().set(true);
switch (dashboardUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
saveOrUpdateDashboard(tenantId, dashboardId, dashboardUpdateMsg, edge);
return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE:
Dashboard dashboardToDelete = dashboardService.findDashboardById(tenantId, dashboardId);
if (dashboardToDelete != null) {
dashboardService.unassignDashboardFromEdge(tenantId, dashboardId, edge.getId());
}
return Futures.immediateFuture(null);
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(dashboardUpdateMsg.getMsgType());
}
} catch (DataValidationException e) {
if (e.getMessage().contains("limit reached")) {
log.warn("[{}] Number of allowed dashboard violated {}", tenantId, dashboardUpdateMsg, e);
return Futures.immediateFuture(null);
} else {
return Futures.immediateFailedFuture(e);
}
} finally {
edgeSynchronizationManager.getSync().remove();
}
}
private void saveOrUpdateDashboard(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg, Edge edge) {
CustomerId customerId = safeGetCustomerId(dashboardUpdateMsg.getCustomerIdMSB(), dashboardUpdateMsg.getCustomerIdLSB());
boolean created = super.saveOrUpdateDashboard(tenantId, dashboardId, dashboardUpdateMsg, customerId);
if (created) {
createRelationFromEdge(tenantId, edge.getId(), dashboardId);
pushDashboardCreatedEventToRuleEngine(tenantId, edge, dashboardId);
dashboardService.assignDashboardToEdge(tenantId, dashboardId, edge.getId());
}
}
private void pushDashboardCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DashboardId dashboardId) {
try {
Dashboard dashboard = dashboardService.findDashboardById(tenantId, dashboardId);
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(dashboard);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, dashboardId, null,
getActionTbMsgMetaData(edge, null), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, dashboardId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", dashboard);
}
@Override
public void onFailure(Throwable t) {
log.warn("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", dashboard, t);
}
});
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push dashboard action to rule engine: {}", dashboardId, DataConstants.ENTITY_CREATED, e);
}
}
public DownlinkMsg convertDashboardEventToDownlink(EdgeEvent edgeEvent) {
DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId());

14
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java

@ -57,13 +57,13 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor {
device = new Device();
device.setTenantId(tenantId);
device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId()));
Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
if (deviceByName != null) {
deviceName = deviceName + "_" + StringUtils.randomAlphabetic(15);
log.warn("Device with name {} already exists. Renaming device name to {}",
deviceUpdateMsg.getName(), deviceName);
deviceNameUpdated = true;
}
}
Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
if (deviceByName != null && !deviceByName.getId().equals(deviceId)) {
deviceName = deviceName + "_" + StringUtils.randomAlphabetic(15);
log.warn("Device with name {} already exists. Renaming device name to {}",
deviceUpdateMsg.getName(), deviceName);
deviceNameUpdated = true;
}
device.setName(deviceName);
device.setType(deviceUpdateMsg.getType());

102
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProfileProcessor.java

@ -0,0 +1,102 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.device;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.UUID;
@Slf4j
public class BaseDeviceProfileProcessor extends BaseEdgeProcessor {
@Autowired
private DataDecodingEncodingService dataDecodingEncodingService;
protected boolean saveOrUpdateDeviceProfile(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) {
boolean created = false;
deviceCreationLock.lock();
try {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
if (deviceProfile == null) {
created = true;
deviceProfile = new DeviceProfile();
deviceProfile.setTenantId(tenantId);
deviceProfile.setCreatedTime(Uuids.unixTimestamp(deviceProfileId.getId()));
}
deviceProfile.setName(deviceProfileUpdateMsg.getName());
deviceProfile.setDescription(deviceProfileUpdateMsg.hasDescription() ? deviceProfileUpdateMsg.getDescription() : null);
deviceProfile.setDefault(deviceProfileUpdateMsg.getDefault());
deviceProfile.setType(DeviceProfileType.valueOf(deviceProfileUpdateMsg.getType()));
deviceProfile.setTransportType(deviceProfileUpdateMsg.hasTransportType()
? DeviceTransportType.valueOf(deviceProfileUpdateMsg.getTransportType()) : DeviceTransportType.DEFAULT);
deviceProfile.setImage(deviceProfileUpdateMsg.hasImage()
? new String(deviceProfileUpdateMsg.getImage().toByteArray(), StandardCharsets.UTF_8) : null);
deviceProfile.setProvisionType(deviceProfileUpdateMsg.hasProvisionType()
? DeviceProfileProvisionType.valueOf(deviceProfileUpdateMsg.getProvisionType()) : DeviceProfileProvisionType.DISABLED);
deviceProfile.setProvisionDeviceKey(deviceProfileUpdateMsg.hasProvisionDeviceKey()
? deviceProfileUpdateMsg.getProvisionDeviceKey() : null);
deviceProfile.setDefaultQueueName(deviceProfileUpdateMsg.getDefaultQueueName());
Optional<DeviceProfileData> profileDataOpt =
dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray());
deviceProfile.setProfileData(profileDataOpt.orElse(null));
UUID defaultRuleChainUUID = safeGetUUID(deviceProfileUpdateMsg.getDefaultRuleChainIdMSB(), deviceProfileUpdateMsg.getDefaultRuleChainIdLSB());
deviceProfile.setDefaultRuleChainId(defaultRuleChainUUID != null ? new RuleChainId(defaultRuleChainUUID) : null);
UUID defaultDashboardUUID = safeGetUUID(deviceProfileUpdateMsg.getDefaultDashboardIdMSB(), deviceProfileUpdateMsg.getDefaultDashboardIdLSB());
deviceProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : null);
String defaultQueueName = StringUtils.isNotBlank(deviceProfileUpdateMsg.getDefaultQueueName())
? deviceProfileUpdateMsg.getDefaultQueueName() : null;
deviceProfile.setDefaultQueueName(defaultQueueName);
UUID firmwareUUID = safeGetUUID(deviceProfileUpdateMsg.getFirmwareIdMSB(), deviceProfileUpdateMsg.getFirmwareIdLSB());
deviceProfile.setFirmwareId(firmwareUUID != null ? new OtaPackageId(firmwareUUID) : null);
UUID softwareUUID = safeGetUUID(deviceProfileUpdateMsg.getSoftwareIdMSB(), deviceProfileUpdateMsg.getSoftwareIdLSB());
deviceProfile.setSoftwareId(softwareUUID != null ? new OtaPackageId(softwareUUID) : null);
deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
if (created) {
deviceProfile.setId(deviceProfileId);
}
deviceProfileService.saveDeviceProfile(deviceProfile, false);
} finally {
deviceCreationLock.unlock();
}
return created;
}
}

30
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java

@ -35,12 +35,8 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.TbMsg;
@ -114,15 +110,6 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor {
}
}
private void createRelationFromEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId) {
EntityRelation relation = new EntityRelation();
relation.setFrom(edgeId);
relation.setTo(entityId);
relation.setTypeGroup(RelationTypeGroup.COMMON);
relation.setType(EntityRelation.EDGE_TYPE);
relationService.saveRelation(tenantId, relation);
}
private void pushDeviceCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DeviceId deviceId) {
try {
Device device = deviceService.findDeviceById(tenantId, deviceId);
@ -137,7 +124,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor {
@Override
public void onFailure(Throwable t) {
log.debug("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", device, t);
log.warn("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", device, t);
}
});
} catch (JsonProcessingException | IllegalArgumentException e) {
@ -145,21 +132,6 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor {
}
}
private TbMsgMetaData getActionTbMsgMetaData(Edge edge, CustomerId customerId) {
TbMsgMetaData metaData = getTbMsgMetaData(edge);
if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString());
}
return metaData;
}
private TbMsgMetaData getTbMsgMetaData(Edge edge) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("edgeId", edge.getId().toString());
metaData.putValue("edgeName", edge.getName());
return metaData;
}
public ListenableFuture<Void> processDeviceRpcCallFromEdge(TenantId tenantId, Edge edge, DeviceRpcCallMsg deviceRpcCallMsg) {
log.trace("[{}] processDeviceRpcCallFromEdge [{}]", tenantId, deviceRpcCallMsg);
if (deviceRpcCallMsg.hasResponseMsg()) {

74
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java

@ -15,22 +15,92 @@
*/
package org.thingsboard.server.service.edge.rpc.processor.device;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Component
@Slf4j
@TbCoreComponent
public class DeviceProfileEdgeProcessor extends BaseEdgeProcessor {
public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor {
public ListenableFuture<Void> processDeviceProfileMsgFromEdge(TenantId tenantId, Edge edge, DeviceProfileUpdateMsg deviceProfileUpdateMsg) {
log.trace("[{}] executing processDeviceProfileMsgFromEdge [{}] from edge [{}]", tenantId, deviceProfileUpdateMsg, edge.getName());
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileUpdateMsg.getIdMSB(), deviceProfileUpdateMsg.getIdLSB()));
try {
edgeSynchronizationManager.getSync().set(true);
switch (deviceProfileUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
saveOrUpdateDeviceProfile(tenantId, deviceProfileId, deviceProfileUpdateMsg, edge);
return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE:
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(deviceProfileUpdateMsg.getMsgType());
}
} catch (DataValidationException e) {
log.warn("Failed to process DeviceProfileUpdateMsg from Edge [{}]", deviceProfileUpdateMsg, e);
return Futures.immediateFailedFuture(e);
} finally {
edgeSynchronizationManager.getSync().remove();
}
}
private void saveOrUpdateDeviceProfile(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg, Edge edge) {
boolean created = super.saveOrUpdateDeviceProfile(tenantId, deviceProfileId, deviceProfileUpdateMsg);
if (created) {
createRelationFromEdge(tenantId, edge.getId(), deviceProfileId);
pushDeviceProfileCreatedEventToRuleEngine(tenantId, edge, deviceProfileId);
}
}
private void pushDeviceProfileCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DeviceProfileId deviceProfileId) {
try {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(deviceProfile);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceProfileId, getTbMsgMetaData(edge),
TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, deviceProfileId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", deviceProfile);
}
@Override
public void onFailure(Throwable t) {
log.warn("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", deviceProfile, t);
}
});
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push device profile action to rule engine: {}", deviceProfileId, DataConstants.ENTITY_CREATED, e);
}
}
public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent) {
DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());

76
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java

@ -0,0 +1,76 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.entityview;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.EdgeEntityType;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Slf4j
public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor {
protected Pair<Boolean, Boolean> saveOrUpdateEntityView(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg, CustomerId customerId) {
boolean created = false;
boolean entityViewNameUpdated = false;
EntityView entityView = entityViewService.findEntityViewById(tenantId, entityViewId);
String entityViewName = entityViewUpdateMsg.getName();
if (entityView == null) {
created = true;
entityView = new EntityView();
entityView.setTenantId(tenantId);
entityView.setCreatedTime(Uuids.unixTimestamp(entityViewId.getId()));
}
EntityView entityViewByName = entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName);
if (entityViewByName != null && !entityViewByName.getId().equals(entityViewId)) {
entityViewName = entityViewName + "_" + StringUtils.randomAlphanumeric(15);
log.warn("Entity view with name {} already exists. Renaming entity view name to {}",
entityViewUpdateMsg.getName(), entityViewName);
entityViewNameUpdated = true;
}
entityView.setName(entityViewName);
entityView.setType(entityViewUpdateMsg.getType());
entityView.setCustomerId(customerId);
entityView.setAdditionalInfo(entityViewUpdateMsg.hasAdditionalInfo() ?
JacksonUtil.toJsonNode(entityViewUpdateMsg.getAdditionalInfo()) : null);
UUID entityIdUUID = safeGetUUID(entityViewUpdateMsg.getEntityIdMSB(), entityViewUpdateMsg.getEntityIdLSB());
if (EdgeEntityType.DEVICE.equals(entityViewUpdateMsg.getEntityType())) {
entityView.setEntityId(entityIdUUID != null ? new DeviceId(entityIdUUID) : null);
} else if (EdgeEntityType.ASSET.equals(entityViewUpdateMsg.getEntityType())) {
entityView.setEntityId(entityIdUUID != null ? new AssetId(entityIdUUID) : null);
}
entityViewValidator.validate(entityView, EntityView::getTenantId);
if (created) {
entityView.setId(entityViewId);
}
entityViewService.saveEntityView(entityView, false);
return Pair.of(created, entityViewNameUpdated);
}
}

93
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java

@ -15,22 +15,111 @@
*/
package org.thingsboard.server.service.edge.rpc.processor.entityview;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@Component
@Slf4j
@TbCoreComponent
public class EntityViewEdgeProcessor extends BaseEdgeProcessor {
public class EntityViewEdgeProcessor extends BaseEntityViewProcessor {
public ListenableFuture<Void> processEntityViewMsgFromEdge(TenantId tenantId, Edge edge, EntityViewUpdateMsg entityViewUpdateMsg) {
log.trace("[{}] executing processEntityViewMsgFromEdge [{}] from edge [{}]", tenantId, entityViewUpdateMsg, edge.getName());
EntityViewId entityViewId = new EntityViewId(new UUID(entityViewUpdateMsg.getIdMSB(), entityViewUpdateMsg.getIdLSB()));
try {
edgeSynchronizationManager.getSync().set(true);
switch (entityViewUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
saveOrUpdateEntityView(tenantId, entityViewId, entityViewUpdateMsg, edge);
return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE:
EntityView entityViewToDelete = entityViewService.findEntityViewById(tenantId, entityViewId);
if (entityViewToDelete != null) {
entityViewService.unassignEntityViewFromEdge(tenantId, entityViewId, edge.getId());
}
return Futures.immediateFuture(null);
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(entityViewUpdateMsg.getMsgType());
}
} catch (DataValidationException e) {
if (e.getMessage().contains("limit reached")) {
log.warn("[{}] Number of allowed entity views violated {}", tenantId, entityViewUpdateMsg, e);
return Futures.immediateFuture(null);
} else {
return Futures.immediateFailedFuture(e);
}
} finally {
edgeSynchronizationManager.getSync().remove();
}
}
private void saveOrUpdateEntityView(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg, Edge edge) {
CustomerId customerId = safeGetCustomerId(entityViewUpdateMsg.getCustomerIdMSB(), entityViewUpdateMsg.getCustomerIdLSB());
Pair<Boolean, Boolean> resultPair = super.saveOrUpdateEntityView(tenantId, entityViewId, entityViewUpdateMsg, customerId);
Boolean created = resultPair.getFirst();
if (created) {
createRelationFromEdge(tenantId, edge.getId(), entityViewId);
pushAssetCreatedEventToRuleEngine(tenantId, edge, entityViewId);
entityViewService.assignEntityViewToEdge(tenantId, entityViewId, edge.getId());
}
Boolean assetNameUpdated = resultPair.getSecond();
if (assetNameUpdated) {
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ENTITY_VIEW, EdgeEventActionType.UPDATED, entityViewId, null);
}
}
private void pushAssetCreatedEventToRuleEngine(TenantId tenantId, Edge edge, EntityViewId entityViewId) {
try {
EntityView entityView = entityViewService.findEntityViewById(tenantId, entityViewId);
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(entityView);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, entityViewId, entityView.getCustomerId(),
getActionTbMsgMetaData(edge, entityView.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, entityViewId, tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", entityView);
}
@Override
public void onFailure(Throwable t) {
log.warn("Failed to send ENTITY_CREATED EVENT to rule engine [{}]", entityView, t);
}
});
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push entity view action to rule engine: {}", entityViewId, DataConstants.ENTITY_CREATED, e);
}
}
public DownlinkMsg convertEntityViewEventToDownlink(EdgeEvent edgeEvent) {
EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId());

1
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink;

43
application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.service.entitiy.queue;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfi
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -35,20 +34,17 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@TbCoreComponent
@AllArgsConstructor
@RequiredArgsConstructor
public class DefaultTbQueueService extends AbstractTbEntityService implements TbQueueService {
private static final long DELETE_DELAY = 30;
private final QueueService queueService;
private final TbClusterService tbClusterService;
private final TbQueueAdmin tbQueueAdmin;
private final SchedulerComponent scheduler;
@Override
public Queue saveQueue(Queue queue) {
@ -90,7 +86,9 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb
private void onQueueCreated(Queue queue) {
for (int i = 0; i < queue.getPartitions(); i++) {
tbQueueAdmin.createTopicIfNotExists(
new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName(),
queue.getCustomProperties()
);
}
tbClusterService.onQueueChange(queue);
@ -105,21 +103,15 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb
log.info("Added [{}] new partitions to [{}] queue", currentPartitions - oldPartitions, queue.getName());
for (int i = oldPartitions; i < currentPartitions; i++) {
tbQueueAdmin.createTopicIfNotExists(
new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName(),
queue.getCustomProperties()
);
}
tbClusterService.onQueueChange(queue);
} else {
log.info("Removed [{}] partitions from [{}] queue", oldPartitions - currentPartitions, queue.getName());
tbClusterService.onQueueChange(queue);
scheduler.schedule(() -> {
for (int i = currentPartitions; i < oldPartitions; i++) {
String fullTopicName = new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName();
log.info("Removed partition [{}]", fullTopicName);
tbQueueAdmin.deleteTopic(
fullTopicName);
}
}, DELETE_DELAY, TimeUnit.SECONDS);
// TODO: move all the messages left in old partitions and delete topics
}
} else if (!oldQueue.equals(queue)) {
tbClusterService.onQueueChange(queue);
@ -128,20 +120,7 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb
private void onQueueDeleted(Queue queue) {
tbClusterService.onQueueDelete(queue);
// queueStatsService.deleteQueueStatsByQueueId(tenantId, queueId);
scheduler.schedule(() -> {
for (int i = 0; i < queue.getPartitions(); i++) {
String fullTopicName = new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName();
log.info("Deleting queue [{}]", fullTopicName);
try {
tbQueueAdmin.deleteTopic(fullTopicName);
} catch (Exception e) {
log.error("Failed to delete queue [{}]", fullTopicName);
}
}
}, DELETE_DELAY, TimeUnit.SECONDS);
}
@Override
@ -193,6 +172,10 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb
}
}
if (log.isDebugEnabled()) {
log.debug("[{}] Handling profile queue config update: creating queues {}, updating {}, deleting {}. Affected tenants: {}",
newTenantProfile.getUuidId(), toCreate, toUpdate, toRemove, tenantIds);
}
tenantIds.forEach(tenantId -> {
toCreate.forEach(key -> saveQueue(new Queue(tenantId, newQueues.get(key))));

8
application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java

@ -44,16 +44,14 @@ public class DefaultTbTenantProfileService extends AbstractTbEntityService imple
@Override
public TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) throws ThingsboardException {
TenantProfile savedTenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(tenantId, tenantProfile));
if (oldTenantProfile != null && savedTenantProfile.isIsolatedTbRuleEngine()) {
List<TenantId> tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId());
tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile);
}
tenantProfileCache.put(savedTenantProfile);
tbClusterService.onTenantProfileChange(savedTenantProfile, null);
tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, savedTenantProfile.getId(),
tenantProfile.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
List<TenantId> tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId());
tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile);
return savedTenantProfile;
}

13
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -237,7 +238,17 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private void processForRecipient(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient, NotificationProcessingContext ctx) throws Exception {
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
throw new AlreadySentException();
} else {
ctx.getStats().reportProcessed(deliveryMethod, recipient.getId());
}
if (recipient instanceof User) {
UserNotificationSettings settings = notificationSettingsService.getUserNotificationSettings(ctx.getTenantId(), ((User) recipient).getId(), false);
if (!settings.isEnabled(ctx.getNotificationType(), deliveryMethod)) {
throw new RuntimeException("User disabled " + deliveryMethod.getName() + " notifications of this type");
}
}
NotificationChannel notificationChannel = channels.get(deliveryMethod);
DeliveryMethodNotificationTemplate processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient);
@ -251,7 +262,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationTemplate().getNotificationType())
.type(ctx.getNotificationType())
.subject(processedTemplate.getSubject())
.text(processedTemplate.getBody())
.additionalConfig(processedTemplate.getAdditionalConfig())

4
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
@ -52,6 +53,8 @@ public class NotificationProcessingContext {
private final Set<NotificationDeliveryMethod> deliveryMethods;
@Getter
private final NotificationTemplate notificationTemplate;
@Getter
private final NotificationType notificationType;
private final Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
@Getter
@ -65,6 +68,7 @@ public class NotificationProcessingContext {
this.deliveryMethods = deliveryMethods;
this.settings = settings;
this.notificationTemplate = template;
this.notificationType = template.getNotificationType();
this.templates = new EnumMap<>(NotificationDeliveryMethod.class);
this.stats = new NotificationRequestStats();
init();

128
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -23,11 +23,14 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
@ -42,12 +45,14 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
@ -98,6 +103,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
private boolean statsEnabled;
@Value("${queue.rule-engine.prometheus-stats.enabled:false}")
boolean prometheusStatsEnabled;
@Value("${queue.rule-engine.topic-deletion-delay:30}")
private int topicDeletionDelayInSec;
private final StatsFactory statsFactory;
private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory;
@ -107,7 +114,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
private final TbRuleEngineDeviceRpcService tbDeviceRpcService;
private final TbServiceInfoProvider serviceInfoProvider;
private final QueueService queueService;
// private final TenantId tenantId;
private final TbQueueProducerProvider producerProvider;
private final TbQueueAdmin queueAdmin;
private final ConcurrentMap<QueueKey, TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> consumers = new ConcurrentHashMap<>();
private final ConcurrentMap<QueueKey, Queue> consumerConfigurations = new ConcurrentHashMap<>();
private final ConcurrentMap<QueueKey, TbRuleEngineConsumerStats> consumerStats = new ConcurrentHashMap<>();
@ -128,7 +136,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher,
TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
TbServiceInfoProvider serviceInfoProvider, QueueService queueService,
TbQueueProducerProvider producerProvider, TbQueueAdmin queueAdmin) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
@ -138,6 +147,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
this.statsFactory = statsFactory;
this.serviceInfoProvider = serviceInfoProvider;
this.queueService = queueService;
this.producerProvider = producerProvider;
this.queueAdmin = queueAdmin;
}
@PostConstruct
@ -145,14 +156,16 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
super.init("tb-rule-engine-consumer", "tb-rule-engine-notifications-consumer");
List<Queue> queues = queueService.findAllQueues();
for (Queue configuration : queues) {
initConsumer(configuration);
if (partitionService.isManagedByCurrentService(configuration.getTenantId())) {
initConsumer(configuration);
}
}
}
private void initConsumer(Queue configuration) {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, configuration);
consumerConfigurations.putIfAbsent(queueKey, configuration);
consumerStats.putIfAbsent(queueKey, new TbRuleEngineConsumerStats(configuration.getName(), statsFactory));
consumerStats.putIfAbsent(queueKey, new TbRuleEngineConsumerStats(configuration, statsFactory));
if (!configuration.isConsumerPerPartition()) {
consumers.computeIfAbsent(queueKey, queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration));
} else {
@ -172,7 +185,12 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
if (event.getServiceType().equals(getServiceType())) {
String serviceQueue = event.getQueueKey().getQueueName();
log.info("[{}] Subscribing to partitions: {}", serviceQueue, event.getPartitions());
if (!consumerConfigurations.get(event.getQueueKey()).isConsumerPerPartition()) {
Queue configuration = consumerConfigurations.get(event.getQueueKey());
if (configuration == null) {
log.warn("Received invalid partition change event for {} that is not managed by this service", event.getQueueKey());
return;
}
if (!configuration.isConsumerPerPartition()) {
consumers.get(event.getQueueKey()).subscribe(event.getPartitions());
} else {
log.info("[{}] Subscribing consumer per partition: {}", serviceQueue, event.getPartitions());
@ -230,7 +248,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
launchConsumer(consumer, consumerConfigurations.get(queueKey), consumerStats.get(queueKey), "" + queueKey + "-" + tpi.getPartition().orElse(-999999));
consumer.subscribe(Collections.singleton(tpi));
});
} finally {
tbTopicWithConsumerPerPartition.getLock().unlock();
}
@ -278,9 +295,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
void consumerLoop(TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer, org.thingsboard.server.common.data.queue.Queue configuration, TbRuleEngineConsumerStats stats, String threadSuffix) {
updateCurrentThreadName(threadSuffix);
while (!stopped && !consumer.isStopped()) {
while (!stopped && !consumer.isStopped() && !consumer.isQueueDeleted()) {
try {
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = consumer.poll(pollDuration);
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = consumer.poll(configuration.getPollInterval());
if (msgs.isEmpty()) {
continue;
}
@ -328,6 +345,10 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
}
}
}
if (consumer.isQueueDeleted()) {
processQueueDeletion(configuration, consumer);
}
log.info("TB Rule Engine Consumer stopped.");
}
@ -425,32 +446,34 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
private void updateQueue(TransportProtos.QueueUpdateMsg queueUpdateMsg) {
log.info("Received queue update msg: [{}]", queueUpdateMsg);
String queueName = queueUpdateMsg.getQueueName();
TenantId tenantId = new TenantId(new UUID(queueUpdateMsg.getTenantIdMSB(), queueUpdateMsg.getTenantIdLSB()));
QueueId queueId = new QueueId(new UUID(queueUpdateMsg.getQueueIdMSB(), queueUpdateMsg.getQueueIdLSB()));
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueUpdateMsg.getQueueName(), tenantId);
Queue queue = queueService.findQueueById(tenantId, queueId);
Queue oldQueue = consumerConfigurations.remove(queueKey);
if (oldQueue != null) {
if (oldQueue.isConsumerPerPartition()) {
TbTopicWithConsumerPerPartition consumerPerPartition = topicsConsumerPerPartition.remove(queueKey);
ReentrantLock lock = consumerPerPartition.getLock();
try {
lock.lock();
consumerPerPartition.getConsumers().values().forEach(TbQueueConsumer::unsubscribe);
} finally {
lock.unlock();
if (partitionService.isManagedByCurrentService(tenantId)) {
QueueId queueId = new QueueId(new UUID(queueUpdateMsg.getQueueIdMSB(), queueUpdateMsg.getQueueIdLSB()));
String queueName = queueUpdateMsg.getQueueName();
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueName, tenantId);
Queue queue = queueService.findQueueById(tenantId, queueId);
Queue oldQueue = consumerConfigurations.remove(queueKey);
if (oldQueue != null) {
if (oldQueue.isConsumerPerPartition()) {
TbTopicWithConsumerPerPartition consumerPerPartition = topicsConsumerPerPartition.remove(queueKey);
ReentrantLock lock = consumerPerPartition.getLock();
try {
lock.lock();
consumerPerPartition.getConsumers().values().forEach(TbQueueConsumer::unsubscribe);
} finally {
lock.unlock();
}
} else {
TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer = consumers.remove(queueKey);
consumer.unsubscribe();
}
} else {
TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer = consumers.remove(queueKey);
consumer.unsubscribe();
}
}
initConsumer(queue);
initConsumer(queue);
if (!queue.isConsumerPerPartition()) {
launchConsumer(consumers.get(queueKey), consumerConfigurations.get(queueKey), consumerStats.get(queueKey), queueName);
if (!queue.isConsumerPerPartition()) {
launchConsumer(consumers.get(queueKey), consumerConfigurations.get(queueKey), consumerStats.get(queueKey), queueName);
}
}
partitionService.updateQueue(queueUpdateMsg);
@ -462,22 +485,22 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TenantId tenantId = new TenantId(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB()));
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId);
partitionService.removeQueue(queueDeleteMsg);
Queue queue = consumerConfigurations.remove(queueKey);
if (queue != null) {
if (queue.isConsumerPerPartition()) {
TbTopicWithConsumerPerPartition tbTopicWithConsumerPerPartition = topicsConsumerPerPartition.remove(queueKey);
if (tbTopicWithConsumerPerPartition != null) {
tbTopicWithConsumerPerPartition.getConsumers().values().forEach(TbQueueConsumer::unsubscribe);
tbTopicWithConsumerPerPartition.getConsumers().values().forEach(TbQueueConsumer::onQueueDelete);
tbTopicWithConsumerPerPartition.getConsumers().clear();
}
} else {
TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer = consumers.remove(queueKey);
if (consumer != null) {
consumer.unsubscribe();
consumer.onQueueDelete();
}
}
}
partitionService.removeQueue(queueDeleteMsg);
}
private void forwardToRuleEngineActor(String queueName, TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) {
@ -496,6 +519,47 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
actorContext.tell(msg);
}
private void processQueueDeletion(Queue queue, TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer) {
long finishTs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(topicDeletionDelayInSec);
try {
int n = 0;
while (System.currentTimeMillis() <= finishTs) {
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = consumer.poll(queue.getPollInterval());
if (msgs.isEmpty()) {
continue;
}
for (TbProtoQueueMsg<ToRuleEngineMsg> msg : msgs) {
try {
MsgProtos.TbMsgProto tbMsgProto = MsgProtos.TbMsgProto.parseFrom(msg.getValue().getTbMsg().toByteArray());
EntityId originator = EntityIdFactory.getByTypeAndUuid(tbMsgProto.getEntityType(), new UUID(tbMsgProto.getEntityIdMSB(), tbMsgProto.getEntityIdLSB()));
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue.getName(), TenantId.SYS_TENANT_ID, originator);
producerProvider.getRuleEngineMsgProducer().send(tpi, msg, null);
n++;
} catch (Throwable e) {
log.debug("Failed to move message to system {}: {}", consumer.getTopic(), msg, e);
}
}
consumer.commit();
}
if (n > 0) {
log.info("Moved {} messages from {} to system {}", n, consumer.getFullTopicNames(), consumer.getTopic());
}
consumer.unsubscribe();
for (String topic : consumer.getFullTopicNames()) {
try {
queueAdmin.deleteTopic(topic);
log.info("Deleted topic {}", topic);
} catch (Exception e) {
log.error("Failed to delete topic {} after unsubscribing", topic, e);
}
}
} catch (Exception e) {
log.error("Failed to process deletion of {} ({})", consumer.getTopic(), queue.getTenantId(), e);
}
}
@Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}")
public void printStats() {
if (statsEnabled) {

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

@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ -31,12 +30,9 @@ import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core' || '${service.type:null}'=='tb-rule-engine'")
public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService {
private final TenantService tenantService;
private final TbTenantProfileCache tenantProfileCache;
public DefaultTenantRoutingInfoService(TenantService tenantService, TbTenantProfileCache tenantProfileCache) {
this.tenantService = tenantService;
public DefaultTenantRoutingInfoService(TbTenantProfileCache tenantProfileCache) {
this.tenantProfileCache = tenantProfileCache;
}
@ -44,7 +40,7 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
if (tenantProfile != null) {
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine());
return new TenantRoutingInfo(tenantId, tenantProfile.getId(), tenantProfile.isIsolatedTbRuleEngine());
} else {
throw new TenantNotFoundException(tenantId);
}

13
application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.queue;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
@ -63,9 +64,11 @@ public class TbRuleEngineConsumerStats {
private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>();
private final String queueName;
private final TenantId tenantId;
public TbRuleEngineConsumerStats(String queueName, StatsFactory statsFactory) {
this.queueName = queueName;
public TbRuleEngineConsumerStats(Queue queue, StatsFactory statsFactory) {
this.queueName = queue.getName();
this.tenantId = queue.getTenantId();
this.statsFactory = statsFactory;
String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName;
@ -156,7 +159,11 @@ public class TbRuleEngineConsumerStats {
counters.forEach(counter -> {
stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
});
log.info("[{}] Stats: {}", queueName, stats);
if (tenantId.isSysTenantId()) {
log.info("[{}] Stats: {}", queueName, stats);
} else {
log.info("[{}][{}] Stats: {}", queueName, tenantId, stats);
}
}
}

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

@ -1091,6 +1091,7 @@ queue:
fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
request.timeout.ms: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
session.timeout.ms: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms
auto_offset_reset: "${TB_QUEUE_KAFKA_AUTO_OFFSET_RESET:earliest}" # earliest, latest or none
use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
confluent:
ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
@ -1287,6 +1288,8 @@ queue:
failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries.
# After a queue is deleted (or profile's isolation option was disabled), Rule Engine will continue reading related topics during this period, before deleting the actual topics
topic-deletion-delay: "${TB_QUEUE_RULE_ENGINE_TOPIC_DELETION_DELAY_SEC:30}"
transport:
# For high priority notifications that require minimum latency and processing time
notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
@ -1300,6 +1303,10 @@ service:
type: "${TB_SERVICE_TYPE:monolith}" # monolith or tb-core or tb-rule-engine
# Unique id for this service (autogenerated if empty)
id: "${TB_SERVICE_ID:}"
rule_engine:
# Comma-separated list of tenant profiles ids assigned to this Rule Engine.
# This Rule Engine will only be responsible for tenants with these profiles (in case 'isolation' option is enabled in profile).
assigned_tenant_profiles: "${TB_RULE_ENGINE_ASSIGNED_TENANT_PROFILES:}"
metrics:
# Enable/disable actuator metrics.

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

@ -1029,13 +1029,20 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return (DeviceActorMessageProcessor) ReflectionTestUtils.getField(actor, "processor");
}
protected void updateDefaultTenantProfile(Consumer<DefaultTenantProfileConfiguration> updater) throws ThingsboardException {
TenantProfile tenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID);
TenantProfileData profileData = tenantProfile.getProfileData();
DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) profileData.getConfiguration();
updater.accept(profileConfiguration);
tenantProfile.setProfileData(profileData);
tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, null);
protected void updateDefaultTenantProfileConfig(Consumer<DefaultTenantProfileConfiguration> updater) throws ThingsboardException {
updateDefaultTenantProfile(tenantProfile -> {
TenantProfileData profileData = tenantProfile.getProfileData();
DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) profileData.getConfiguration();
updater.accept(profileConfiguration);
tenantProfile.setProfileData(profileData);
});
}
protected void updateDefaultTenantProfile(Consumer<TenantProfile> updater) throws ThingsboardException {
TenantProfile oldTenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID);
TenantProfile tenantProfile = JacksonUtil.clone(oldTenantProfile);
updater.accept(tenantProfile);
tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, oldTenantProfile);
}
}

248
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -27,15 +27,20 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -50,24 +55,47 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
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.queue.TbQueueAdmin;
import org.thingsboard.server.queue.discovery.PartitionService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.DataConstants.MAIN_QUEUE_NAME;
import static org.thingsboard.server.common.data.DataConstants.MAIN_QUEUE_TOPIC;
@TestPropertySource(properties = {
"js.evaluator=mock",
"queue.rule-engine.topic-deletion-delay=10"
})
@Slf4j
@DaoSqlTest
@ -80,6 +108,13 @@ public class TenantControllerTest extends AbstractControllerTest {
ListeningExecutorService executor;
@SpyBean
private PartitionService partitionService;
@SpyBean
private ActorSystemContext actorContext;
@SpyBean
private TbQueueAdmin queueAdmin;
@Before
public void setUp() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
@ -87,6 +122,12 @@ public class TenantControllerTest extends AbstractControllerTest {
@After
public void tearDown() throws Exception {
loginSysAdmin();
for (Queue queue : doGetTypedWithPageLink("/api/queues?serviceType=TB_RULE_ENGINE&", new TypeReference<PageData<Queue>>() {}, new PageLink(100)).getData()) {
if (!queue.getName().equals(MAIN_QUEUE_NAME)) {
doDelete("/api/queues/" + queue.getId()).andExpect(status().isOk());
}
}
executor.shutdownNow();
}
@ -430,7 +471,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration());
tenantProfile.setProfileData(tenantProfileData);
tenantProfile.setIsolatedTbRuleEngine(true);
addQueueConfig(tenantProfile, DataConstants.MAIN_QUEUE_NAME);
addQueueConfig(tenantProfile, MAIN_QUEUE_NAME);
addQueueConfig(tenantProfile, "Test");
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
@ -459,7 +500,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfileData2.setConfiguration(new DefaultTenantProfileConfiguration());
tenantProfile2.setProfileData(tenantProfileData2);
tenantProfile2.setIsolatedTbRuleEngine(true);
addQueueConfig(tenantProfile2, DataConstants.MAIN_QUEUE_NAME);
addQueueConfig(tenantProfile2, MAIN_QUEUE_NAME);
addQueueConfig(tenantProfile2, "Test");
addQueueConfig(tenantProfile2, "Test2");
tenantProfile2 = doPost("/api/tenantProfile", tenantProfile2, TenantProfile.class);
@ -520,10 +561,201 @@ public class TenantControllerTest extends AbstractControllerTest {
doDelete("/api/tenant/" + tenant.getId().getId().toString()).andExpect(status().isOk());
}
@Test
public void testUpdateTenantProfileToIsolated() throws Exception {
loginSysAdmin();
doPost("/api/queues?serviceType=TB_RULE_ENGINE", new Queue(TenantId.SYS_TENANT_ID, getQueueConfig(DataConstants.HP_QUEUE_NAME, DataConstants.HP_QUEUE_TOPIC))).andExpect(status().isOk());
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Test profile");
TenantProfileData tenantProfileData = new TenantProfileData();
tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration());
tenantProfile.setProfileData(tenantProfileData);
tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId;
loginDifferentTenant();
DeviceProfile hpQueueProfile = createDeviceProfile("HighPriority profile");
hpQueueProfile.setDefaultQueueName(DataConstants.HP_QUEUE_NAME);
hpQueueProfile = doPost("/api/deviceProfile", hpQueueProfile, DeviceProfile.class);
Device hpQueueDevice = createDevice("HP", hpQueueProfile.getName(), "HP");
DeviceProfile mainQueueProfile = createDeviceProfile("Main profile");
mainQueueProfile.setDefaultQueueName(MAIN_QUEUE_NAME);
mainQueueProfile = doPost("/api/deviceProfile", mainQueueProfile, DeviceProfile.class);
Device mainQueueDevice = createDevice("Main", mainQueueProfile.getName(), "Main");
verifyUsedQueueAndMessage(DataConstants.HP_QUEUE_NAME, tenantId, hpQueueDevice.getId(), DataConstants.ATTRIBUTES_UPDATED, () -> {
doPost("/api/plugins/telemetry/DEVICE/" + hpQueueDevice.getId() + "/attributes/SERVER_SCOPE", "{\"test\":123}", String.class);
}, usedTpi -> {
assertThat(usedTpi.getTopic()).isEqualTo(DataConstants.HP_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(TenantId.SYS_TENANT_ID);
});
verifyUsedQueueAndMessage(MAIN_QUEUE_NAME, tenantId, mainQueueDevice.getId(), DataConstants.ATTRIBUTES_UPDATED, () -> {
doPost("/api/plugins/telemetry/DEVICE/" + mainQueueDevice.getId() + "/attributes/SERVER_SCOPE", "{\"test\":123}", String.class);
}, usedTpi -> {
assertThat(usedTpi.getTopic()).isEqualTo(MAIN_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(TenantId.SYS_TENANT_ID);
});
loginSysAdmin();
tenantProfile.setIsolatedTbRuleEngine(true);
tenantProfile.getProfileData().setQueueConfiguration(List.of(
getQueueConfig(MAIN_QUEUE_NAME, MAIN_QUEUE_TOPIC)
));
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
loginDifferentTenant();
verifyUsedQueueAndMessage(MAIN_QUEUE_NAME, tenantId, mainQueueDevice.getId(), DataConstants.ATTRIBUTES_UPDATED, () -> {
doPost("/api/plugins/telemetry/DEVICE/" + mainQueueDevice.getId() + "/attributes/SERVER_SCOPE", "{\"test\":123}", String.class);
}, usedTpi -> {
assertThat(usedTpi.getTopic()).isEqualTo(MAIN_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(tenantId);
});
verifyUsedQueueAndMessage(DataConstants.HP_QUEUE_NAME, tenantId, hpQueueDevice.getId(), DataConstants.ATTRIBUTES_UPDATED, () -> {
doPost("/api/plugins/telemetry/DEVICE/" + hpQueueDevice.getId() + "/attributes/SERVER_SCOPE", "{\"test\":123}", String.class);
}, usedTpi -> {
assertThat(usedTpi.getTopic()).isEqualTo(DataConstants.HP_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(TenantId.SYS_TENANT_ID);
});
loginSysAdmin();
tenantProfile.setIsolatedTbRuleEngine(true);
tenantProfile.getProfileData().setQueueConfiguration(List.of(
getQueueConfig(MAIN_QUEUE_NAME, MAIN_QUEUE_TOPIC),
getQueueConfig(DataConstants.HP_QUEUE_NAME, DataConstants.HP_QUEUE_TOPIC)
));
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
loginDifferentTenant();
verifyUsedQueueAndMessage(DataConstants.HP_QUEUE_NAME, tenantId, hpQueueDevice.getId(), DataConstants.ATTRIBUTES_UPDATED, () -> {
doPost("/api/plugins/telemetry/DEVICE/" + hpQueueDevice.getId() + "/attributes/SERVER_SCOPE", "{\"test\":123}", String.class);
}, usedTpi -> {
assertThat(usedTpi.getTopic()).isEqualTo(DataConstants.HP_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(tenantId);
});
verifyUsedQueueAndMessage(MAIN_QUEUE_NAME, tenantId, mainQueueDevice.getId(), DataConstants.ATTRIBUTES_UPDATED, () -> {
doPost("/api/plugins/telemetry/DEVICE/" + mainQueueDevice.getId() + "/attributes/SERVER_SCOPE", "{\"test\":123}", String.class);
}, usedTpi -> {
assertThat(usedTpi.getTopic()).isEqualTo(MAIN_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(tenantId);
});
}
@Test
public void testIsolatedQueueDeletion() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Test profile");
TenantProfileData tenantProfileData = new TenantProfileData();
tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration());
tenantProfile.setProfileData(tenantProfileData);
tenantProfile.setIsolatedTbRuleEngine(true);
addQueueConfig(tenantProfile, MAIN_QUEUE_NAME);
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId;
await().atMost(10, TimeUnit.SECONDS)
.until(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId);
return !tpi.getTenantId().get().isSysTenantId();
});
TopicPartitionInfo tpi = new TopicPartitionInfo(MAIN_QUEUE_TOPIC, tenantId, 0, false);
String isolatedTopic = tpi.getFullTopicName();
TbMsg expectedMsg = publishTbMsg(tenantId, tpi);
awaitTbMsg(tbMsg -> tbMsg.getId().equals(expectedMsg.getId()), 10000); // to wait for consumer start
loginSysAdmin();
tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList());
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
await().atMost(10, TimeUnit.SECONDS)
.until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId)
.getTenantId().get().isSysTenantId());
Deque<UUID> submittedMsgs = new LinkedList<>();
await().atLeast(8, TimeUnit.SECONDS) // due to topic-deletion-delay
.atMost(20, TimeUnit.SECONDS)
.pollInterval(1, TimeUnit.SECONDS)
.untilAsserted(() -> {
TbMsg tbMsg = publishTbMsg(tenantId, tpi);
submittedMsgs.add(tbMsg.getId());
verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic));
});
submittedMsgs.removeLast();
for (UUID msgId : submittedMsgs) {
verify(actorContext, timeout(2000)).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(msgId);
}));
}
}
private TbMsg publishTbMsg(TenantId tenantId, TopicPartitionInfo tpi) {
TbMsg tbMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", tenantId, TbMsgMetaData.EMPTY, "{\"test\":1}");
TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setTbMsg(TbMsg.toByteString(tbMsg)).build();
tbClusterService.pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, null);
return tbMsg;
}
private void verifyUsedQueueAndMessage(String queue, TenantId tenantId, EntityId entityId, String msgType, Runnable action, Consumer<TopicPartitionInfo> tpiAssert) {
await().atMost(15, TimeUnit.SECONDS)
.untilAsserted(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId);
tpiAssert.accept(tpi);
});
action.run();
TbMsg tbMsg = awaitTbMsg(msg -> msg.getOriginator().equals(entityId)
&& msg.getType().equals(msgType), 10000);
assertThat(tbMsg.getQueueName()).isEqualTo(queue);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId);
tpiAssert.accept(tpi);
}
protected TbMsg awaitTbMsg(Predicate<TbMsg> predicate, int timeoutMillis) {
AtomicReference<TbMsg> tbMsgCaptor = new AtomicReference<>();
verify(actorContext, timeout(timeoutMillis).atLeastOnce()).tell(argThat(actorMsg -> {
if (!(actorMsg instanceof QueueToRuleEngineMsg)) {
return false;
}
TbMsg tbMsg = ((QueueToRuleEngineMsg) actorMsg).getMsg();
if (predicate.test(tbMsg)) {
tbMsgCaptor.set(tbMsg);
return true;
}
return false;
}));
return tbMsgCaptor.get();
}
private void addQueueConfig(TenantProfile tenantProfile, String queueName) {
TenantProfileQueueConfiguration queueConfiguration = getQueueConfig(queueName, "tb_rule_engine." + queueName.toLowerCase());
TenantProfileData profileData = tenantProfile.getProfileData();
List<TenantProfileQueueConfiguration> configs = profileData.getQueueConfiguration();
if (configs == null) {
configs = new ArrayList<>();
}
configs.add(queueConfiguration);
profileData.setQueueConfiguration(configs);
tenantProfile.setProfileData(profileData);
}
private TenantProfileQueueConfiguration getQueueConfig(String queueName, String topic) {
TenantProfileQueueConfiguration queueConfiguration = new TenantProfileQueueConfiguration();
queueConfiguration.setName(queueName);
queueConfiguration.setTopic("tb_rule_engine." + queueName.toLowerCase());
queueConfiguration.setTopic(topic);
queueConfiguration.setPollInterval(25);
queueConfiguration.setPartitions(1 + new Random().nextInt(99));
queueConfiguration.setConsumerPerPartition(true);
@ -539,15 +771,7 @@ public class TenantControllerTest extends AbstractControllerTest {
processingStrategy.setPauseBetweenRetries(3);
processingStrategy.setMaxPauseBetweenRetries(3);
queueConfiguration.setProcessingStrategy(processingStrategy);
TenantProfileData profileData = tenantProfile.getProfileData();
List<TenantProfileQueueConfiguration> configs = profileData.getQueueConfiguration();
if (configs == null) {
configs = new ArrayList<>();
}
configs.add(queueConfiguration);
profileData.setQueueConfiguration(configs);
tenantProfile.setProfileData(profileData);
return queueConfiguration;
}
private List<Queue> getQueuesFromConfig(List<TenantProfileQueueConfiguration> queueConfiguration, List<Queue> queues) {

17
application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java

@ -167,23 +167,6 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventNeverTenantProfile();
}
@Test
public void testSaveSameTenantProfileWithDifferentIsolatedTbRuleEngine() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
savedTenantProfile.setIsolatedTbRuleEngine(true);
addMainQueueConfig(savedTenantProfile);
Mockito.reset(tbClusterService);
doPost("/api/tenantProfile", savedTenantProfile)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't update isolatedTbRuleEngine property")));
testBroadcastEntityStateChangeEventNeverTenantProfile();
}
@Test
public void testDeleteTenantProfileWithExistingTenant() throws Exception {
loginSysAdmin();

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

@ -15,19 +15,29 @@
*/
package org.thingsboard.server.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -154,5 +164,115 @@ public class AssetEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB());
}
@Test
public void testSendAssetToCloud() throws Exception {
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AssetUpdateMsg.Builder assetUpdateMsgBuilder = AssetUpdateMsg.newBuilder();
assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetUpdateMsgBuilder.setName("Asset Edge 2");
assetUpdateMsgBuilder.setType("test");
assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder);
uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
Assert.assertTrue(latestResponseMsg.getSuccess());
Asset asset = doGet("/api/asset/" + uuid, Asset.class);
Assert.assertNotNull(asset);
Assert.assertEquals("Asset Edge 2", asset.getName());
}
@Test
public void testSendAssetToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
String assetOnCloudName = StringUtils.randomAlphanumeric(15);
Asset assetOnCloud = saveAsset(assetOnCloudName);
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AssetUpdateMsg.Builder assetUpdateMsgBuilder = AssetUpdateMsg.newBuilder();
assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetUpdateMsgBuilder.setName(assetOnCloudName);
assetUpdateMsgBuilder.setType("test");
assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder);
uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<AssetUpdateMsg> assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class);
Assert.assertTrue(assetUpdateMsgOpt.isPresent());
AssetUpdateMsg latestAssetUpdateMsg = assetUpdateMsgOpt.get();
Assert.assertNotEquals(assetOnCloudName, latestAssetUpdateMsg.getName());
UUID newAssetId = new UUID(latestAssetUpdateMsg.getIdMSB(), latestAssetUpdateMsg.getIdLSB());
Assert.assertNotEquals(assetOnCloud.getUuidId(), newAssetId);
Asset asset = doGet("/api/asset/" + newAssetId, Asset.class);
Assert.assertNotNull(asset);
Assert.assertNotEquals(assetOnCloudName, asset.getName());
}
@Test
public void testSendDeleteAssetOnEdgeToCloud() throws Exception {
Asset savedAsset = saveAssetOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
AssetUpdateMsg.Builder assetDeleteMsgBuilder = AssetUpdateMsg.newBuilder();
assetDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
assetDeleteMsgBuilder.setIdMSB(savedAsset.getUuidId().getMostSignificantBits());
assetDeleteMsgBuilder.setIdLSB(savedAsset.getUuidId().getLeastSignificantBits());
testAutoGeneratedCodeByProtobuf(assetDeleteMsgBuilder);
upLinkMsgBuilder.addAssetUpdateMsg(assetDeleteMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(upLinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
AssetInfo assetInfo = doGet("/api/asset/info/" + savedAsset.getUuidId(), AssetInfo.class);
Assert.assertNotNull(assetInfo);
List<AssetInfo> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?",
new TypeReference<PageData<AssetInfo>>() {
}, new PageLink(100)).getData();
Assert.assertFalse(edgeAssets.contains(assetInfo));
}
private Asset saveAssetOnCloudAndVerifyDeliveryToEdge() throws Exception {
// create asset and assign to edge
Asset savedAsset = saveAsset(StringUtils.randomAlphanumeric(15));
edgeImitator.expectMessageAmount(1); // asset message
doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<AssetUpdateMsg> assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class);
Assert.assertTrue(assetUpdateMsgOpt.isPresent());
AssetUpdateMsg assetUpdateMsg = assetUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
Assert.assertEquals(savedAsset.getUuidId().getMostSignificantBits(), assetUpdateMsg.getIdMSB());
Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB());
return savedAsset;
}
}

57
application/src/test/java/org/thingsboard/server/edge/AssetProfileEdgeTest.java

@ -15,17 +15,22 @@
*/
package org.thingsboard.server.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ByteString;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -77,4 +82,56 @@ public class AssetProfileEdgeTest extends AbstractEdgeTest {
unAssignFromEdgeAndDeleteRuleChain(buildingsRuleChainId);
}
@Test
public void testSendAssetProfileToCloud() throws Exception {
RuleChainId ruleChainId = createEdgeRuleChainAndAssignToEdge("Asset Profile Rule Chain");
DashboardId dashboardId = createDashboardAndAssignToEdge("Asset Profile Dashboard");
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AssetProfileUpdateMsg.Builder assetProfileUpdateMsgBuilder = AssetProfileUpdateMsg.newBuilder();
assetProfileUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
assetProfileUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
assetProfileUpdateMsgBuilder.setName("Asset Profile On Edge");
assetProfileUpdateMsgBuilder.setDefault(false);
assetProfileUpdateMsgBuilder.setDefaultRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits());
assetProfileUpdateMsgBuilder.setDefaultRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits());
assetProfileUpdateMsgBuilder.setDefaultDashboardIdMSB(dashboardId.getId().getMostSignificantBits());
assetProfileUpdateMsgBuilder.setDefaultDashboardIdLSB(dashboardId.getId().getLeastSignificantBits());
assetProfileUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(assetProfileUpdateMsgBuilder);
uplinkMsgBuilder.addAssetProfileUpdateMsg(assetProfileUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
Assert.assertTrue(latestResponseMsg.getSuccess());
AssetProfile assetProfile = doGet("/api/assetProfile/" + uuid, AssetProfile.class);
Assert.assertNotNull(assetProfile);
Assert.assertEquals("Asset Profile On Edge", assetProfile.getName());
// delete profile
edgeImitator.expectMessageAmount(1);
doDelete("/api/assetProfile/" + assetProfile.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg);
AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType());
Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB());
Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB());
// cleanup
unAssignFromEdgeAndDeleteDashboard(dashboardId);
unAssignFromEdgeAndDeleteRuleChain(ruleChainId);
}
}

80
application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
@ -22,13 +23,22 @@ import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -149,4 +159,74 @@ public class DashboardEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), dashboardUpdateMsg.getIdLSB());
}
@Test
public void testSendDashboardToCloud() throws Exception {
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
DashboardUpdateMsg.Builder dashboardUpdateMsgBuilder = DashboardUpdateMsg.newBuilder();
dashboardUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
dashboardUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
dashboardUpdateMsgBuilder.setTitle("Edge Test Dashboard");
dashboardUpdateMsgBuilder.setConfiguration("");
dashboardUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(dashboardUpdateMsgBuilder);
uplinkMsgBuilder.addDashboardUpdateMsg(dashboardUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
Dashboard dashboard = doGet("/api/dashboard/" + uuid, Dashboard.class);
Assert.assertNotNull(dashboard);
Assert.assertEquals("Edge Test Dashboard", dashboard.getName());
}
@Test
public void testSendDeleteEntityViewOnEdgeToCloud() throws Exception {
Dashboard savedDashboard = saveDashboardOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
DashboardUpdateMsg.Builder dashboardDeleteMsgBuilder = DashboardUpdateMsg.newBuilder();
dashboardDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
dashboardDeleteMsgBuilder.setIdMSB(savedDashboard.getUuidId().getMostSignificantBits());
dashboardDeleteMsgBuilder.setIdLSB(savedDashboard.getUuidId().getLeastSignificantBits());
testAutoGeneratedCodeByProtobuf(dashboardDeleteMsgBuilder);
upLinkMsgBuilder.addDashboardUpdateMsg(dashboardDeleteMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(upLinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
DashboardInfo dashboardInfo = doGet("/api/dashboard/info/" + savedDashboard.getUuidId(), DashboardInfo.class);
Assert.assertNotNull(dashboardInfo);
List<DashboardInfo> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, new PageLink(100)).getData();
Assert.assertFalse(edgeAssets.contains(dashboardInfo));
}
private Dashboard saveDashboardOnCloudAndVerifyDeliveryToEdge() throws Exception {
// create dashboard and assign to edge
Dashboard dashboard = new Dashboard();
dashboard.setTitle(StringUtils.randomAlphanumeric(15));
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
edgeImitator.expectMessageAmount(1); // dashboard message
doPost("/api/edge/" + edge.getUuidId()
+ "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<DashboardUpdateMsg> dashboardUpdateMsgOpt = edgeImitator.findMessageByType(DashboardUpdateMsg.class);
Assert.assertTrue(dashboardUpdateMsgOpt.isPresent());
DashboardUpdateMsg entityViewUpdateMsg = dashboardUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType());
Assert.assertEquals(savedDashboard.getUuidId().getMostSignificantBits(), entityViewUpdateMsg.getIdMSB());
Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), entityViewUpdateMsg.getIdLSB());
return savedDashboard;
}
}

71
application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java

@ -15,19 +15,26 @@
*/
package org.thingsboard.server.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ByteString;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
@ -46,12 +53,15 @@ import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQu
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import org.thingsboard.server.transport.AbstractTransportIntegrationTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -260,6 +270,67 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
removeDeviceProfileAndDoBasicAssert(deviceProfile);
}
@Test
public void testSendDeviceProfileToCloud() throws Exception {
RuleChainId ruleChainId = createEdgeRuleChainAndAssignToEdge("Device Profile Rule Chain");
DashboardId dashboardId = createDashboardAndAssignToEdge("Device Profile Dashboard");
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
DeviceProfileUpdateMsg.Builder deviceProfileUpdateMsgBuilder = DeviceProfileUpdateMsg.newBuilder();
deviceProfileUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceProfileUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceProfileUpdateMsgBuilder.setName("Device Profile On Edge");
deviceProfileUpdateMsgBuilder.setDefault(false);
deviceProfileUpdateMsgBuilder.setType(DeviceProfileType.DEFAULT.name());
deviceProfileUpdateMsgBuilder.setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(createProfileData())));
deviceProfileUpdateMsgBuilder.setDefaultRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits());
deviceProfileUpdateMsgBuilder.setDefaultRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits());
deviceProfileUpdateMsgBuilder.setDefaultDashboardIdMSB(dashboardId.getId().getMostSignificantBits());
deviceProfileUpdateMsgBuilder.setDefaultDashboardIdLSB(dashboardId.getId().getLeastSignificantBits());
deviceProfileUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(deviceProfileUpdateMsgBuilder);
uplinkMsgBuilder.addDeviceProfileUpdateMsg(deviceProfileUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
Assert.assertTrue(latestResponseMsg.getSuccess());
AssetProfile assetProfile = doGet("/api/deviceProfile/" + uuid, AssetProfile.class);
Assert.assertNotNull(assetProfile);
Assert.assertEquals("Device Profile On Edge", assetProfile.getName());
// delete profile
edgeImitator.expectMessageAmount(1);
doDelete("/api/deviceProfile/" + assetProfile.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB());
Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB());
// cleanup
unAssignFromEdgeAndDeleteDashboard(dashboardId);
unAssignFromEdgeAndDeleteRuleChain(ruleChainId);
}
private DeviceProfileData createProfileData() {
DeviceProfileData deviceProfileData = new DeviceProfileData();
deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration("Device Secret"));
return deviceProfileData;
}
private DeviceProfile createDeviceProfileAndDoBasicAssert(String deviceProfileName, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile(deviceProfileName, deviceProfileTransportConfiguration);

154
application/src/test/java/org/thingsboard/server/edge/EntityViewEdgeTest.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Assert;
@ -22,15 +24,24 @@ import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.EdgeEntityType;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -43,11 +54,7 @@ public class EntityViewEdgeTest extends AbstractEdgeTest {
// create entity view and assign to edge
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
EntityView entityView = new EntityView();
entityView.setName("Edge EntityView 1");
entityView.setType("test");
entityView.setEntityId(device.getId());
EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class);
EntityView savedEntityView = saveEntityView("Edge EntityView 1", device.getId());
doPost("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages());
@ -102,11 +109,7 @@ public class EntityViewEdgeTest extends AbstractEdgeTest {
// create entity view #2 and assign to edge
edgeImitator.expectMessageAmount(1);
entityView = new EntityView();
entityView.setName("Edge EntityView 2");
entityView.setType("test");
entityView.setEntityId(device.getId());
savedEntityView = doPost("/api/entityView", entityView, EntityView.class);
savedEntityView = saveEntityView("Edge EntityView 2", device.getId());
doPost("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages());
@ -158,6 +161,115 @@ public class EntityViewEdgeTest extends AbstractEdgeTest {
}
@Test
public void testSendEntityViewToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
EntityViewUpdateMsg.Builder entityViewUpdateMsgBuilder = EntityViewUpdateMsg.newBuilder();
entityViewUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
entityViewUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
entityViewUpdateMsgBuilder.setName("Edge EntityView 2");
entityViewUpdateMsgBuilder.setType("test");
entityViewUpdateMsgBuilder.setEntityType(EdgeEntityType.DEVICE);
entityViewUpdateMsgBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits());
entityViewUpdateMsgBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits());
entityViewUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(entityViewUpdateMsgBuilder);
uplinkMsgBuilder.addEntityViewUpdateMsg(entityViewUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
Assert.assertTrue(latestResponseMsg.getSuccess());
EntityView entityView = doGet("/api/entityView/" + uuid, EntityView.class);
Assert.assertNotNull(entityView);
Assert.assertEquals("Edge EntityView 2", entityView.getName());
}
@Test
public void testSendEntityViewToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
String entityViewOnCloudName = StringUtils.randomAlphanumeric(15);
EntityView entityViewOnCloud = saveEntityView(entityViewOnCloudName, device.getId());
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
EntityViewUpdateMsg.Builder entityViewUpdateMsgBuilder = EntityViewUpdateMsg.newBuilder();
entityViewUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
entityViewUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
entityViewUpdateMsgBuilder.setName(entityViewOnCloudName);
entityViewUpdateMsgBuilder.setType("test");
entityViewUpdateMsgBuilder.setEntityType(EdgeEntityType.DEVICE);
entityViewUpdateMsgBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits());
entityViewUpdateMsgBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits());
entityViewUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(entityViewUpdateMsgBuilder);
uplinkMsgBuilder.addEntityViewUpdateMsg(entityViewUpdateMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<EntityViewUpdateMsg> entityViewUpdateMsgOpt = edgeImitator.findMessageByType(EntityViewUpdateMsg.class);
Assert.assertTrue(entityViewUpdateMsgOpt.isPresent());
EntityViewUpdateMsg latestEntityViewUpdateMsg = entityViewUpdateMsgOpt.get();
Assert.assertNotEquals(entityViewOnCloudName, latestEntityViewUpdateMsg.getName());
UUID newEntityViewId = new UUID(latestEntityViewUpdateMsg.getIdMSB(), latestEntityViewUpdateMsg.getIdLSB());
Assert.assertNotEquals(entityViewOnCloud.getId().getId(), newEntityViewId);
EntityView entityView = doGet("/api/entityView/" + newEntityViewId, EntityView.class);
Assert.assertNotNull(entityView);
Assert.assertNotEquals(entityViewOnCloudName, entityView.getName());
}
@Test
public void testSendDeleteEntityViewOnEdgeToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
EntityView savedEntityView = saveEntityViewOnCloudAndVerifyDeliveryToEdge(device);
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
EntityViewUpdateMsg.Builder entityViewDeleteMsgBuilder = EntityViewUpdateMsg.newBuilder();
entityViewDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
entityViewDeleteMsgBuilder.setIdMSB(savedEntityView.getUuidId().getMostSignificantBits());
entityViewDeleteMsgBuilder.setIdLSB(savedEntityView.getUuidId().getLeastSignificantBits());
testAutoGeneratedCodeByProtobuf(entityViewDeleteMsgBuilder);
upLinkMsgBuilder.addEntityViewUpdateMsg(entityViewDeleteMsgBuilder.build());
testAutoGeneratedCodeByProtobuf(upLinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
EntityViewInfo entityViewInfo = doGet("/api/entityView/info/" + savedEntityView.getUuidId(), EntityViewInfo.class);
Assert.assertNotNull(entityViewInfo);
List<EntityViewInfo> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/entityViews?",
new TypeReference<PageData<EntityViewInfo>>() {
}, new PageLink(100)).getData();
Assert.assertFalse(edgeAssets.contains(entityViewInfo));
}
private void verifyEntityViewUpdateMsg(EntityView entityView, Device device) throws InvalidProtocolBufferException {
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
@ -173,6 +285,28 @@ public class EntityViewEdgeTest extends AbstractEdgeTest {
testAutoGeneratedCodeByProtobuf(entityViewUpdateMsg);
}
private EntityView saveEntityViewOnCloudAndVerifyDeliveryToEdge(Device device) throws Exception {
// create entity view and assign to edge
EntityView savedEntityView = saveEntityView(StringUtils.randomAlphanumeric(15), device.getId());
edgeImitator.expectMessageAmount(1); // entity view message
doPost("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<EntityViewUpdateMsg> entityViewUpdateMsgOpt = edgeImitator.findMessageByType(EntityViewUpdateMsg.class);
Assert.assertTrue(entityViewUpdateMsgOpt.isPresent());
EntityViewUpdateMsg entityViewUpdateMsg = entityViewUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType());
Assert.assertEquals(savedEntityView.getUuidId().getMostSignificantBits(), entityViewUpdateMsg.getIdMSB());
Assert.assertEquals(savedEntityView.getUuidId().getLeastSignificantBits(), entityViewUpdateMsg.getIdLSB());
return savedEntityView;
}
private EntityView saveEntityView(String name, DeviceId deviceId) {
EntityView entityView = new EntityView();
entityView.setName(name);
entityView.setType("test");
entityView.setEntityId(deviceId);
return doPost("/api/entityView", entityView, EntityView.class);
}
}

149
application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.queue.discovery;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -25,24 +26,36 @@ import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.queue.Queue;
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.gen.transport.TransportProtos.ServiceInfo;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
@ -57,7 +70,7 @@ public class HashPartitionServiceTest {
private ApplicationEventPublisher applicationEventPublisher;
private QueueRoutingInfoService queueRoutingInfoService;
private String hashFunctionName = "sha256";
private String hashFunctionName = "murmur3_128";
@Before
public void setup() throws Exception {
@ -74,15 +87,15 @@ public class HashPartitionServiceTest {
ReflectionTestUtils.setField(clusterRoutingService, "vcTopic", "tb.vc");
ReflectionTestUtils.setField(clusterRoutingService, "vcPartitions", 10);
ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName);
TransportProtos.ServiceInfo currentServer = TransportProtos.ServiceInfo.newBuilder()
ServiceInfo currentServer = ServiceInfo.newBuilder()
.setServiceId("tb-core-0")
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build();
// when(queueService.resolve(Mockito.any(), Mockito.anyString())).thenAnswer(i -> i.getArguments()[1]);
// when(discoveryService.getServiceInfo()).thenReturn(currentServer);
List<TransportProtos.ServiceInfo> otherServers = new ArrayList<>();
List<ServiceInfo> otherServers = new ArrayList<>();
for (int i = 1; i < SERVER_COUNT; i++) {
otherServers.add(TransportProtos.ServiceInfo.newBuilder()
otherServers.add(ServiceInfo.newBuilder()
.setServiceId("tb-rule-" + i)
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build());
@ -122,10 +135,10 @@ public class HashPartitionServiceTest {
int queueCount = 3;
int partitionCount = 3;
List<TransportProtos.ServiceInfo> services = new ArrayList<>();
List<ServiceInfo> services = new ArrayList<>();
for (int i = 0; i < serverCount; i++) {
services.add(TransportProtos.ServiceInfo.newBuilder().setServiceId("RE-" + i).build());
services.add(ServiceInfo.newBuilder().setServiceId("RE-" + i).build());
}
long start = System.currentTimeMillis();
@ -140,7 +153,7 @@ public class HashPartitionServiceTest {
for (int queueIndex = 0; queueIndex < queueCount; queueIndex++) {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "queue" + queueIndex, tenantId);
for (int partition = 0; partition < partitionCount; partition++) {
TransportProtos.ServiceInfo serviceInfo = clusterRoutingService.resolveByPartitionIdx(services, queueKey, partition);
ServiceInfo serviceInfo = clusterRoutingService.resolveByPartitionIdx(services, queueKey, partition);
String serviceId = serviceInfo.getServiceId();
map.put(serviceId, map.get(serviceId) + 1);
}
@ -163,4 +176,124 @@ public class HashPartitionServiceTest {
Assert.assertTrue(diffPercent < maxDiffPercent);
}
@Test
public void testPartitionsAssignmentWithDedicatedServers() {
int isolatedProfilesCount = 5;
int tenantsCountPerProfile = 100;
int dedicatedServerSetsCount = 3;
int serversCountPerSet = 3;
int profilesPerSet = (int) Math.ceil((double) isolatedProfilesCount / dedicatedServerSetsCount);
List<TenantProfileId> isolatedTenantProfiles = Stream.generate(() -> new TenantProfileId(UUID.randomUUID()))
.limit(isolatedProfilesCount).collect(Collectors.toList());
Map<TenantId, TenantProfileId> tenants = new HashMap<>();
for (TenantProfileId tenantProfileId : isolatedTenantProfiles) {
for (int i = 0; i < tenantsCountPerProfile; i++) {
tenants.put(new TenantId(UUID.randomUUID()), tenantProfileId);
}
}
List<Queue> queues = new ArrayList<>();
Queue systemQueue = new Queue();
systemQueue.setTenantId(TenantId.SYS_TENANT_ID);
systemQueue.setName("Main");
systemQueue.setTopic(DataConstants.MAIN_QUEUE_TOPIC);
systemQueue.setPartitions(10);
systemQueue.setId(new QueueId(UUID.randomUUID()));
queues.add(systemQueue);
tenants.forEach((tenantId, profileId) -> {
Queue isolatedQueue = new Queue();
isolatedQueue.setTenantId(tenantId);
isolatedQueue.setName("Main");
isolatedQueue.setTopic(DataConstants.MAIN_QUEUE_TOPIC);
isolatedQueue.setPartitions(2);
isolatedQueue.setId(new QueueId(UUID.randomUUID()));
queues.add(isolatedQueue);
when(routingInfoService.getRoutingInfo(eq(tenantId))).thenReturn(new TenantRoutingInfo(tenantId, profileId, true));
});
when(queueRoutingInfoService.getAllQueuesRoutingInfo()).thenReturn(queues.stream()
.map(QueueRoutingInfo::new).collect(Collectors.toList()));
List<ServiceInfo> ruleEngines = new ArrayList<>();
Map<TenantProfileId, List<ServiceInfo>> dedicatedServers = new HashMap<>();
int serviceId = 0;
for (int i = 0; i < serversCountPerSet; i++) {
ServiceInfo commonServer = ServiceInfo.newBuilder()
.setServiceId("tb-rule-engine-" + serviceId)
.addAllServiceTypes(List.of(ServiceType.TB_RULE_ENGINE.name()))
.build();
ruleEngines.add(commonServer);
serviceId++;
}
for (int i = 0; i < dedicatedServerSetsCount; i++) {
List<TenantProfileId> assignedProfiles = ListUtils.partition(isolatedTenantProfiles, profilesPerSet).get(i);
for (int j = 0; j < serversCountPerSet; j++) {
ServiceInfo dedicatedServer = ServiceInfo.newBuilder()
.setServiceId("tb-rule-engine-" + serviceId)
.addAllServiceTypes(List.of(ServiceType.TB_RULE_ENGINE.name()))
.addAllAssignedTenantProfiles(assignedProfiles.stream().map(UUIDBased::toString).collect(Collectors.toList()))
.build();
ruleEngines.add(dedicatedServer);
serviceId++;
for (TenantProfileId assignedProfileId : assignedProfiles) {
dedicatedServers.computeIfAbsent(assignedProfileId, p -> new ArrayList<>()).add(dedicatedServer);
}
}
}
Map<QueueKey, Map<ServiceInfo, List<Integer>>> serversPartitions = new HashMap<>();
clusterRoutingService.init();
for (ServiceInfo ruleEngine : ruleEngines) {
List<ServiceInfo> other = new ArrayList<>(ruleEngines);
other.removeIf(serviceInfo -> serviceInfo.getServiceId().equals(ruleEngine.getServiceId()));
clusterRoutingService.recalculatePartitions(ruleEngine, other);
clusterRoutingService.myPartitions.forEach((queueKey, partitions) -> {
serversPartitions.computeIfAbsent(queueKey, k -> new HashMap<>()).put(ruleEngine, partitions);
});
}
assertThat(serversPartitions.keySet()).containsAll(queues.stream().map(queue -> new QueueKey(ServiceType.TB_RULE_ENGINE, queue)).collect(Collectors.toList()));
serversPartitions.forEach((queueKey, partitionsPerServer) -> {
if (queueKey.getTenantId().isSysTenantId()) {
partitionsPerServer.forEach((server, partitions) -> {
assertThat(server.getAssignedTenantProfilesCount()).as("system queues are not assigned to dedicated servers").isZero();
});
} else {
List<ServiceInfo> responsibleServers = dedicatedServers.get(tenants.get(queueKey.getTenantId()));
partitionsPerServer.forEach((server, partitions) -> {
assertThat(server.getAssignedTenantProfilesCount()).as("isolated queues are only assigned to dedicated servers").isPositive();
assertThat(responsibleServers).contains(server);
});
}
List<Integer> allPartitions = partitionsPerServer.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
assertThat(allPartitions).doesNotHaveDuplicates();
});
}
@Test
public void testIsManagedByCurrentServiceCheck() {
TenantProfileId isolatedProfileId = new TenantProfileId(UUID.randomUUID());
when(discoveryService.getAssignedTenantProfiles()).thenReturn(Set.of(isolatedProfileId.getId())); // dedicated server
TenantProfileId regularProfileId = new TenantProfileId(UUID.randomUUID());
TenantId isolatedTenantId = new TenantId(UUID.randomUUID());
when(routingInfoService.getRoutingInfo(eq(isolatedTenantId))).thenReturn(new TenantRoutingInfo(isolatedTenantId, isolatedProfileId, true));
TenantId regularTenantId = new TenantId(UUID.randomUUID());
when(routingInfoService.getRoutingInfo(eq(regularTenantId))).thenReturn(new TenantRoutingInfo(regularTenantId, regularProfileId, false));
assertThat(clusterRoutingService.isManagedByCurrentService(isolatedTenantId)).isTrue();
assertThat(clusterRoutingService.isManagedByCurrentService(regularTenantId)).isFalse();
when(discoveryService.getAssignedTenantProfiles()).thenReturn(Collections.emptySet()); // common server
assertThat(clusterRoutingService.isManagedByCurrentService(isolatedTenantId)).isTrue();
assertThat(clusterRoutingService.isManagedByCurrentService(regularTenantId)).isTrue();
}
}

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.notification;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.data.Offset;
import org.java_websocket.client.WebSocketClient;
@ -23,6 +24,7 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@ -35,6 +37,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -59,6 +62,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@ -470,6 +474,66 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
}
@Test
public void testUserNotificationSettings() throws Exception {
var entityActionNotificationPref = new UserNotificationSettings.NotificationPref();
entityActionNotificationPref.setEnabled(true);
entityActionNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.WEB, true,
NotificationDeliveryMethod.SMS, false,
NotificationDeliveryMethod.EMAIL, false
));
var entitiesLimitNotificationPref = new UserNotificationSettings.NotificationPref();
entitiesLimitNotificationPref.setEnabled(true);
entitiesLimitNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.SMS, true,
NotificationDeliveryMethod.WEB, false,
NotificationDeliveryMethod.EMAIL, false
));
var apiUsageLimitNotificationPref = new UserNotificationSettings.NotificationPref();
apiUsageLimitNotificationPref.setEnabled(false);
apiUsageLimitNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.WEB, true,
NotificationDeliveryMethod.SMS, false,
NotificationDeliveryMethod.EMAIL, false
));
UserNotificationSettings settings = new UserNotificationSettings(Map.of(
NotificationType.ENTITY_ACTION, entityActionNotificationPref,
NotificationType.ENTITIES_LIMIT, entitiesLimitNotificationPref,
NotificationType.API_USAGE_LIMIT, apiUsageLimitNotificationPref
));
doPost("/api/notification/settings/user", settings, UserNotificationSettings.class);
var entityActionNotificationTemplate = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Entity action", "Entity action", NotificationDeliveryMethod.WEB);
var entitiesLimitNotificationTemplate = createNotificationTemplate(NotificationType.ENTITIES_LIMIT, "Entities limit", "Entities limit", NotificationDeliveryMethod.WEB);
var apiUsageLimitNotificationTemplate = createNotificationTemplate(NotificationType.API_USAGE_LIMIT, "API usage limit", "API usage limit", NotificationDeliveryMethod.WEB);
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId)
.templateId(entityActionNotificationTemplate.getId())
.originatorEntityId(tenantAdminUserId)
.targets(List.of(target.getUuidId()))
.ruleId(new NotificationRuleId(UUID.randomUUID())) // to trigger user settings check
.build();
NotificationRequestStats stats = submitNotificationRequestAndWait(notificationRequest);
assertThat(stats.getErrors()).isEmpty();
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB).get()).isOne();
notificationRequest.setTemplateId(entitiesLimitNotificationTemplate.getId());
stats = submitNotificationRequestAndWait(notificationRequest);
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).matches(n -> n == null || n.get() == 0);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.WEB).values()).first().asString().contains("disabled");
notificationRequest.setTemplateId(apiUsageLimitNotificationTemplate.getId());
stats = submitNotificationRequestAndWait(notificationRequest);
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).matches(n -> n == null || n.get() == 0);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.WEB).values()).first().asString().contains("disabled");
}
@Test
public void testSlackNotifications() throws Exception {
NotificationSettings settings = new NotificationSettings();
@ -524,6 +588,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SLACK).values()).containsExactly(errorMessage);
}
private NotificationRequestStats submitNotificationRequestAndWait(NotificationRequest notificationRequest) throws Exception {
SettableFuture<NotificationRequestStats> future = SettableFuture.create();
notificationCenter.processNotificationRequest(notificationRequest.getTenantId(), notificationRequest, future::set);
return future.get(30, TimeUnit.SECONDS);
}
private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) {
assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications);
assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getType).containsOnly(DEFAULT_NOTIFICATION_TYPE);

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

@ -342,7 +342,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRuleProcessing_entitiesLimit() throws Exception {
int limit = 5;
updateDefaultTenantProfile(profileConfiguration -> {
updateDefaultTenantProfileConfig(profileConfiguration -> {
profileConfiguration.setMaxDevices(limit);
profileConfiguration.setMaxAssets(limit);
profileConfiguration.setMaxCustomers(limit);
@ -421,10 +421,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
int n = 10;
updateDefaultTenantProfile(profileConfiguration -> {
profileConfiguration.setTenantEntityExportRateLimit(n + ":600");
profileConfiguration.setCustomerServerRestLimitsConfiguration(n + ":600");
profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(n + ":600");
profileConfiguration.setTransportDeviceTelemetryMsgRateLimit(n + ":600");
profileConfiguration.getProfileConfiguration().get().setTenantEntityExportRateLimit(n + ":600");
profileConfiguration.getProfileConfiguration().get().setCustomerServerRestLimitsConfiguration(n + ":600");
profileConfiguration.getProfileConfiguration().get().setTenantNotificationRequestsPerRuleRateLimit(n + ":600");
profileConfiguration.getProfileConfiguration().get().setTransportDeviceTelemetryMsgRateLimit(n + ":600");
});
loginTenantAdmin();
NotificationRule rule = createNotificationRule(AlarmCommentNotificationRuleTriggerConfig.builder()
@ -608,7 +608,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRequestsPerRuleRateLimits() throws Exception {
int notificationRequestsLimit = 10;
updateDefaultTenantProfile(profileConfiguration -> {
updateDefaultTenantProfileConfig(profileConfiguration -> {
profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(notificationRequestsLimit + ":300");
});
@ -691,8 +691,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
int n = 5;
updateDefaultTenantProfile(profileConfiguration -> {
profileConfiguration.setTenantEntityExportRateLimit(n + ":600");
profileConfiguration.setTransportDeviceTelemetryMsgRateLimit(n + ":800");
profileConfiguration.getProfileConfiguration().get().setTenantEntityExportRateLimit(n + ":600");
profileConfiguration.getProfileConfiguration().get().setTransportDeviceTelemetryMsgRateLimit(n + ":800");
});
RateLimitsTrigger expectedTrigger = RateLimitsTrigger.builder()

12
application/src/test/java/org/thingsboard/server/service/notification/MockNotificationSettingsService.java → application/src/test/java/org/thingsboard/server/service/notification/TestNotificationSettingsService.java

@ -19,14 +19,20 @@ import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.notification.DefaultNotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserSettingsService;
@Service
@Primary
public class MockNotificationSettingsService extends DefaultNotificationSettingsService {
public class TestNotificationSettingsService extends DefaultNotificationSettingsService {
public MockNotificationSettingsService(AdminSettingsService adminSettingsService) {
super(adminSettingsService, null, null, null);
public TestNotificationSettingsService(AdminSettingsService adminSettingsService,
NotificationTargetService notificationTargetService,
NotificationTemplateService notificationTemplateService,
UserSettingsService userSettingsService) {
super(adminSettingsService, notificationTargetService, notificationTemplateService, null, userSettingsService);
}
@Override

2
application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java

@ -67,7 +67,7 @@ public class AlarmsCleanUpServiceTest extends AbstractControllerTest {
@Test
public void testAlarmsCleanUp() throws Exception {
int ttlDays = 1;
updateDefaultTenantProfile(profileConfiguration -> {
updateDefaultTenantProfileConfig(profileConfiguration -> {
profileConfiguration.setAlarmsTtlDays(ttlDays);
});

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

@ -68,3 +68,5 @@ sql.ttl.audit_logs.ttl=2592000
sql.edge_events.partition_size=168
sql.ttl.edge_events.edge_event_ttl=2592000
server.log_controller_error_stack_trace=false

2
common/actor/src/main/java/org/thingsboard/server/actors/TbActorCtx.java

@ -32,7 +32,7 @@ public interface TbActorCtx extends TbActorRef {
void stop(TbActorId target);
TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator);
TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator, Supplier<Boolean> createCondition);
void broadcastToChildren(TbActorMsg msg);

10
common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java

@ -15,7 +15,8 @@
*/
package org.thingsboard.server.actors;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.msg.MsgType;
@ -31,7 +32,8 @@ import java.util.function.Predicate;
import java.util.function.Supplier;
@Slf4j
@Data
@Getter
@RequiredArgsConstructor
public final class TbActorMailbox implements TbActorCtx {
private static final boolean HIGH_PRIORITY = true;
private static final boolean NORMAL_PRIORITY = false;
@ -212,9 +214,9 @@ public final class TbActorMailbox implements TbActorCtx {
}
@Override
public TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator) {
public TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator, Supplier<Boolean> createCondition) {
TbActorRef actorRef = system.getActor(actorId);
if (actorRef == null) {
if (actorRef == null && createCondition.get()) {
return system.createChildActor(dispatcher.get(), creator.get(), selfId);
} else {
return actorRef;

6
common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java

@ -17,7 +17,11 @@ package org.thingsboard.server.queue;
public interface TbQueueAdmin {
void createTopicIfNotExists(String topic);
default void createTopicIfNotExists(String topic) {
createTopicIfNotExists(topic, null);
}
void createTopicIfNotExists(String topic, String properties);
void destroy();

6
common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueConsumer.java

@ -36,4 +36,10 @@ public interface TbQueueConsumer<T extends TbQueueMsg> {
boolean isStopped();
void onQueueDelete();
boolean isQueueDeleted();
List<String> getFullTopicNames();
}

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

@ -28,6 +28,7 @@ message ServiceInfo {
repeated string serviceTypes = 2;
repeated string transports = 6;
SystemInfoProto systemInfo = 10;
repeated string assignedTenantProfiles = 11;
}
message SystemInfoProto {

2
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java

@ -31,6 +31,8 @@ public interface AssetProfileService extends EntityDaoService {
AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, AssetProfileId assetProfileId);
AssetProfile saveAssetProfile(AssetProfile assetProfile, boolean doValidate);
AssetProfile saveAssetProfile(AssetProfile assetProfile);
void deleteAssetProfile(TenantId tenantId, AssetProfileId assetProfileId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -41,6 +41,8 @@ public interface AssetService extends EntityDaoService {
Asset findAssetByTenantIdAndName(TenantId tenantId, String name);
Asset saveAsset(Asset asset, boolean doValidate);
Asset saveAsset(Asset asset);
Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, CustomerId customerId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -40,6 +40,8 @@ public interface DashboardService extends EntityDaoService {
ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId);
Dashboard saveDashboard(Dashboard dashboard, boolean doValidate);
Dashboard saveDashboard(Dashboard dashboard);
Dashboard assignDashboardToCustomer(TenantId tenantId, DashboardId dashboardId, CustomerId customerId);

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

@ -31,6 +31,8 @@ public interface DeviceProfileService extends EntityDaoService {
DeviceProfileInfo findDeviceProfileInfoById(TenantId tenantId, DeviceProfileId deviceProfileId);
DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile, boolean doValidate);
DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile);
void deleteDeviceProfile(TenantId tenantId, DeviceProfileId deviceProfileId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -38,6 +38,8 @@ public interface EntityViewService extends EntityDaoService {
EntityView saveEntityView(EntityView entityView);
EntityView saveEntityView(EntityView entityView, boolean doValidate);
EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, CustomerId customerId);
EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId);

6
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java

@ -16,7 +16,9 @@
package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
public interface NotificationSettingsService {
@ -24,6 +26,10 @@ public interface NotificationSettingsService {
NotificationSettings findNotificationSettings(TenantId tenantId);
UserNotificationSettings saveUserNotificationSettings(TenantId tenantId, UserId userId, UserNotificationSettings settings);
UserNotificationSettings getUserNotificationSettings(TenantId tenantId, UserId userId, boolean format);
void createDefaultNotificationConfigs(TenantId tenantId);
void updateDefaultNotificationConfigs(TenantId tenantId);

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java

@ -54,7 +54,6 @@ public class NotificationRequestStats {
public void reportSent(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet();
processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipient.getId());
}
public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, NotificationRecipient recipient) {
@ -68,6 +67,10 @@ public class NotificationRequestStats {
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage);
}
public void reportProcessed(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipientId);
}
public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
Set<Object> processedRecipients = this.processedRecipients.get(deliveryMethod);
return processedRecipients != null && processedRecipients.contains(recipientId);

82
common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/UserNotificationSettings.java

@ -0,0 +1,82 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.settings;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Data
public class UserNotificationSettings {
@NotNull
@Valid
private final Map<NotificationType, NotificationPref> prefs;
public static final UserNotificationSettings DEFAULT = new UserNotificationSettings(Collections.emptyMap());
public static final Set<NotificationDeliveryMethod> deliveryMethods = NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods();
@JsonCreator
public UserNotificationSettings(@JsonProperty("prefs") Map<NotificationType, NotificationPref> prefs) {
this.prefs = prefs;
}
public boolean isEnabled(NotificationType notificationType, NotificationDeliveryMethod deliveryMethod) {
NotificationPref pref = prefs.get(notificationType);
if (pref == null) {
return true;
}
if (!pref.isEnabled()) {
return false;
}
return pref.getEnabledDeliveryMethods().getOrDefault(deliveryMethod, true);
}
@Data
public static class NotificationPref {
private boolean enabled;
@NotNull
private Map<NotificationDeliveryMethod, Boolean> enabledDeliveryMethods;
public static NotificationPref createDefault() {
NotificationPref pref = new NotificationPref();
pref.setEnabled(true);
pref.setEnabledDeliveryMethods(deliveryMethods.stream().collect(Collectors.toMap(v -> v, v -> true)));
return pref;
}
@JsonIgnore
@AssertTrue(message = "Only email, Web and SMS delivery methods are allowed")
public boolean isValid() {
return enabledDeliveryMethods.entrySet().stream()
.allMatch(entry -> deliveryMethods.contains(entry.getKey()) && entry.getValue() != null);
}
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java

@ -36,4 +36,6 @@ public class SortOrder {
ASC, DESC
}
public static final SortOrder BY_CREATED_TIME_DESC = new SortOrder("createdTime", Direction.DESC);
}

15
common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.queue;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo;
import org.thingsboard.server.common.data.HasName;
@ -25,6 +27,8 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfi
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Optional;
@Data
public class Queue extends BaseDataWithAdditionalInfo<QueueId> implements HasName, HasTenantId {
private TenantId tenantId;
@ -60,4 +64,13 @@ public class Queue extends BaseDataWithAdditionalInfo<QueueId> implements HasNam
this.processingStrategy = queueConfiguration.getProcessingStrategy();
setAdditionalInfo(queueConfiguration.getAdditionalInfo());
}
}
@JsonIgnore
public String getCustomProperties() {
return Optional.ofNullable(getAdditionalInfo())
.map(info -> info.get("customProperties"))
.filter(JsonNode::isTextual).map(JsonNode::asText).orElse(null);
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java

@ -19,7 +19,7 @@ import lombok.Getter;
public enum UserSettingsType {
GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS, GETTING_STARTED;
GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS, GETTING_STARTED, NOTIFICATIONS;
@Getter
private final boolean reserved;

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

@ -221,17 +221,11 @@ public class EdgeGrpcClient implements EdgeRpcClient {
}
@Override
public void sendSyncRequestMsg(boolean syncRequired) {
sendSyncRequestMsg(syncRequired, true);
}
@Override
public void sendSyncRequestMsg(boolean syncRequired, boolean fullSync) {
public void sendSyncRequestMsg(boolean fullSyncRequired) {
uplinkMsgLock.lock();
try {
SyncRequestMsg syncRequestMsg = SyncRequestMsg.newBuilder()
.setSyncRequired(syncRequired)
.setFullSync(fullSync)
.setFullSync(fullSyncRequired)
.build();
this.inputStream.onNext(RequestMsg.newBuilder()
.setMsgType(RequestMsgType.SYNC_REQUEST_RPC_MESSAGE)

4
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java

@ -34,9 +34,7 @@ public interface EdgeRpcClient {
void disconnect(boolean onError) throws InterruptedException;
void sendSyncRequestMsg(boolean syncRequired);
void sendSyncRequestMsg(boolean syncRequired, boolean fullSync);
void sendSyncRequestMsg(boolean fullSyncRequired);
void sendUplinkMsg(UplinkMsg uplinkMsg);

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

@ -85,7 +85,7 @@ message ConnectResponseMsg {
}
message SyncRequestMsg {
bool syncRequired = 1;
bool syncRequired = 1; // deprecated
optional bool fullSync = 2;
}
@ -529,6 +529,11 @@ message UplinkMsg {
repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13; // deprecated
repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14;
repeated EntityViewsRequestMsg entityViewsRequestMsg = 15;
repeated AssetUpdateMsg assetUpdateMsg = 16;
repeated DashboardUpdateMsg dashboardUpdateMsg = 17;
repeated EntityViewUpdateMsg entityViewUpdateMsg = 18;
repeated AssetProfileUpdateMsg assetProfileUpdateMsg = 19;
repeated DeviceProfileUpdateMsg deviceProfileUpdateMsg = 20;
}
message UplinkResponseMsg {

2
common/queue/src/main/java/org/thingsboard/server/queue/RuleEngineTbQueueAdminFactory.java

@ -99,7 +99,7 @@ public class RuleEngineTbQueueAdminFactory {
return new TbQueueAdmin() {
@Override
public void createTopicIfNotExists(String topic) {
public void createTopicIfNotExists(String topic, String properties) {
}
@Override

7
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusAdmin.java

@ -22,6 +22,7 @@ import com.microsoft.azure.servicebus.primitives.MessagingEntityAlreadyExistsExc
import com.microsoft.azure.servicebus.primitives.ServiceBusException;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.util.PropertyUtils;
import java.io.IOException;
import java.time.Duration;
@ -60,7 +61,7 @@ public class TbServiceBusAdmin implements TbQueueAdmin {
}
@Override
public void createTopicIfNotExists(String topic) {
public void createTopicIfNotExists(String topic, String properties) {
if (queues.contains(topic)) {
return;
}
@ -68,7 +69,7 @@ public class TbServiceBusAdmin implements TbQueueAdmin {
try {
QueueDescription queueDescription = new QueueDescription(topic);
queueDescription.setRequiresDuplicateDetection(false);
setQueueConfigs(queueDescription);
setQueueConfigs(queueDescription, PropertyUtils.getProps(queueConfigs, properties));
client.createQueue(queueDescription);
queues.add(topic);
@ -107,7 +108,7 @@ public class TbServiceBusAdmin implements TbQueueAdmin {
}
}
private void setQueueConfigs(QueueDescription queueDescription) {
private void setQueueConfigs(QueueDescription queueDescription, Map<String, String> queueConfigs) {
queueConfigs.forEach((confKey, confValue) -> {
switch (confKey) {
case MAX_SIZE:

29
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java

@ -44,6 +44,7 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
protected volatile Set<TopicPartitionInfo> partitions;
protected final ReentrantLock consumerLock = new ReentrantLock(); //NonfairSync
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
protected volatile boolean queueDeleted = false;
@Getter
private final String topic;
@ -94,7 +95,7 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
partitions = subscribeQueue.poll();
}
if (!subscribed) {
List<String> topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
List<String> topicNames = getFullTopicNames();
doSubscribe(topicNames);
subscribed = true;
}
@ -103,7 +104,9 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
consumerLock.unlock();
}
if (records.isEmpty()) { return sleepAndReturnEmpty(startNanos, durationInMillis); }
if (records.isEmpty() && !isLongPollingSupported()) {
return sleepAndReturnEmpty(startNanos, durationInMillis);
}
return decodeRecords(records);
}
@ -162,7 +165,9 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
@Override
public void unsubscribe() {
log.info("unsubscribe topic and stop consumer {}", getTopic());
log.info("Unsubscribing from topics and stopping consumer for topics {}", partitions.stream()
.map(TopicPartitionInfo::getFullTopicName)
.collect(Collectors.joining(", ")));
stopped = true;
consumerLock.lock();
try {
@ -187,4 +192,22 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
abstract protected void doUnsubscribe();
@Override
public void onQueueDelete() {
queueDeleted = true;
}
public boolean isQueueDeleted() {
return queueDeleted;
}
@Override
public List<String> getFullTopicNames() {
return partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
}
protected boolean isLongPollingSupported() {
return false;
}
}

14
common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java

@ -23,6 +23,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
@ -35,6 +36,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.common.util.SystemUtil.getCpuCount;
@ -57,6 +60,10 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
@Value("${service.type:monolith}")
private String serviceType;
@Getter
@Value("${service.rule_engine.assigned_tenant_profiles:}")
private Set<UUID> assignedTenantProfiles;
@Autowired
private ApplicationContext applicationContext;
@ -78,6 +85,9 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
} else {
serviceTypes = Collections.singletonList(ServiceType.of(serviceType));
}
if (!serviceTypes.contains(ServiceType.TB_RULE_ENGINE) || assignedTenantProfiles == null) {
assignedTenantProfiles = Collections.emptySet();
}
generateNewServiceInfoWithCurrentSystemInfo();
}
@ -111,7 +121,9 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
.setServiceId(serviceId)
.addAllServiceTypes(serviceTypes.stream().map(ServiceType::name).collect(Collectors.toList()))
.setSystemInfo(getCurrentSystemInfoProto());
if (CollectionsUtil.isNotEmpty(assignedTenantProfiles)) {
builder.addAllAssignedTenantProfiles(assignedTenantProfiles.stream().map(UUID::toString).collect(Collectors.toList()));
}
return serviceInfo = builder.build();
}

133
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -24,6 +24,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -36,6 +37,7 @@ import org.thingsboard.server.queue.util.AfterStartUp;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -48,6 +50,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.MAIN_QUEUE_NAME;
@Service
@Slf4j
public class HashPartitionService implements PartitionService {
@ -68,15 +72,16 @@ public class HashPartitionService implements PartitionService {
private final TenantRoutingInfoService tenantRoutingInfoService;
private final QueueRoutingInfoService queueRoutingInfoService;
private volatile ConcurrentMap<QueueKey, List<Integer>> myPartitions = new ConcurrentHashMap<>();
protected volatile ConcurrentMap<QueueKey, List<Integer>> myPartitions = new ConcurrentHashMap<>();
private final ConcurrentMap<QueueKey, String> partitionTopicsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<QueueKey, Integer> partitionSizesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>();
private Map<String, List<ServiceInfo>> tbTransportServicesByType = new HashMap<>();
private List<ServiceInfo> currentOtherServices;
private final Map<String, List<ServiceInfo>> tbTransportServicesByType = new HashMap<>();
private final Map<TenantProfileId, List<ServiceInfo>> responsibleServices = new HashMap<>();
private HashFunction hashFunction;
@ -165,6 +170,9 @@ public class HashPartitionService implements PartitionService {
partitionTopicsMap.put(queueKey, queueUpdateMsg.getQueueTopic());
partitionSizesMap.put(queueKey, queueUpdateMsg.getPartitions());
myPartitions.remove(queueKey);
if (!tenantId.isSysTenantId()) {
tenantRoutingInfoMap.remove(tenantId);
}
}
@Override
@ -178,12 +186,38 @@ public class HashPartitionService implements PartitionService {
removeTenant(tenantId);
}
@Override
public boolean isManagedByCurrentService(TenantId tenantId) {
Set<UUID> assignedTenantProfiles = serviceInfoProvider.getAssignedTenantProfiles();
if (assignedTenantProfiles.isEmpty()) {
// TODO: refactor this for common servers
return true;
} else {
if (tenantId.isSysTenantId()) {
return false;
}
TenantProfileId profileId = tenantRoutingInfoService.getRoutingInfo(tenantId).getProfileId();
return assignedTenantProfiles.contains(profileId.getId());
}
}
@Override
public TopicPartitionInfo resolve(ServiceType serviceType, String queueName, TenantId tenantId, EntityId entityId) {
TenantId isolatedOrSystemTenantId = getIsolatedOrSystemTenantId(serviceType, tenantId);
if (queueName == null) {
queueName = MAIN_QUEUE_NAME;
}
QueueKey queueKey = new QueueKey(serviceType, queueName, isolatedOrSystemTenantId);
if (!partitionSizesMap.containsKey(queueKey)) {
queueKey = new QueueKey(serviceType, isolatedOrSystemTenantId);
if (isolatedOrSystemTenantId.isSysTenantId()) {
queueKey = new QueueKey(serviceType, TenantId.SYS_TENANT_ID);
} else {
queueKey = new QueueKey(serviceType, queueName, TenantId.SYS_TENANT_ID);
if (!MAIN_QUEUE_NAME.equals(queueName) && !partitionSizesMap.containsKey(queueKey)) {
queueKey = new QueueKey(serviceType, TenantId.SYS_TENANT_ID);
}
log.warn("Using queue {} instead of isolated {} for tenant {}", queueKey, queueName, isolatedOrSystemTenantId);
}
}
return resolve(queueKey, entityId);
}
@ -199,11 +233,12 @@ public class HashPartitionService implements PartitionService {
}
private TopicPartitionInfo resolve(QueueKey queueKey, EntityId entityId) {
int hash = hashFunction.newHasher()
.putLong(entityId.getId().getMostSignificantBits())
.putLong(entityId.getId().getLeastSignificantBits()).hash().asInt();
Integer partitionSize = partitionSizesMap.get(queueKey);
if (partitionSize == null) {
throw new IllegalStateException("Partitions info for queue " + queueKey + " is missing");
}
int hash = hash(entityId.getId());
int partition = Math.abs(hash % partitionSize);
return buildTopicPartitionInfo(queueKey, partition);
@ -212,6 +247,7 @@ public class HashPartitionService implements PartitionService {
@Override
public synchronized void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) {
tbTransportServicesByType.clear();
responsibleServices.clear();
logServiceInfo(currentService);
otherServices.forEach(this::logServiceInfo);
@ -221,6 +257,7 @@ public class HashPartitionService implements PartitionService {
addNode(queueServicesMap, other);
}
queueServicesMap.values().forEach(list -> list.sort(Comparator.comparing(ServiceInfo::getServiceId)));
responsibleServices.values().forEach(list -> list.sort(Comparator.comparing(ServiceInfo::getServiceId)));
final ConcurrentMap<QueueKey, List<Integer>> newPartitions = new ConcurrentHashMap<>();
partitionSizesMap.forEach((queueKey, size) -> {
@ -268,6 +305,9 @@ public class HashPartitionService implements PartitionService {
changes.addAll(newMap.keySet());
if (!changes.isEmpty()) {
applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes));
responsibleServices.forEach((profileId, serviceInfos) -> {
log.info("Servers responsible for tenant profile {}: {}", profileId, toServiceIds(serviceInfos));
});
}
}
@ -305,9 +345,7 @@ public class HashPartitionService implements PartitionService {
@Override
public int resolvePartitionIndex(UUID entityId, int partitions) {
int hash = hashFunction.newHasher()
.putLong(entityId.getMostSignificantBits())
.putLong(entityId.getLeastSignificantBits()).hash().asInt();
int hash = hash(entityId);
return Math.abs(hash % partitions);
}
@ -358,16 +396,9 @@ public class HashPartitionService implements PartitionService {
if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
return false;
}
TenantRoutingInfo routingInfo = tenantRoutingInfoMap.get(tenantId);
if (routingInfo == null) {
synchronized (tenantRoutingInfoMap) {
routingInfo = tenantRoutingInfoMap.get(tenantId);
if (routingInfo == null) {
routingInfo = tenantRoutingInfoService.getRoutingInfo(tenantId);
tenantRoutingInfoMap.put(tenantId, routingInfo);
}
}
}
TenantRoutingInfo routingInfo = tenantRoutingInfoMap.computeIfAbsent(tenantId, k -> {
return tenantRoutingInfoService.getRoutingInfo(tenantId);
});
if (routingInfo == null) {
throw new TenantNotFoundException(tenantId);
}
@ -396,6 +427,19 @@ public class HashPartitionService implements PartitionService {
queueServiceList.computeIfAbsent(key, k -> new ArrayList<>()).add(instance);
}
});
if (instance.getAssignedTenantProfilesCount() > 0) {
for (String profileIdStr : instance.getAssignedTenantProfilesList()) {
TenantProfileId profileId;
try {
profileId = new TenantProfileId(UUID.fromString(profileIdStr));
} catch (IllegalArgumentException e) {
log.warn("Failed to parse '{}' as tenant profile id", profileIdStr);
continue;
}
responsibleServices.computeIfAbsent(profileId, k -> new ArrayList<>()).add(instance);
}
}
} else if (ServiceType.TB_CORE.equals(serviceType) || ServiceType.TB_VC_EXECUTOR.equals(serviceType)) {
queueServiceList.computeIfAbsent(new QueueKey(serviceType), key -> new ArrayList<>()).add(instance);
}
@ -411,18 +455,51 @@ public class HashPartitionService implements PartitionService {
return null;
}
if (!ServiceType.TB_RULE_ENGINE.equals(queueKey.getType()) || TenantId.SYS_TENANT_ID.equals(queueKey.getTenantId())) {
return servers.get(partition % servers.size());
} else {
int hash = hashFunction.newHasher().putLong(queueKey.getTenantId().getId().getMostSignificantBits())
.putLong(queueKey.getTenantId().getId().getLeastSignificantBits())
TenantId tenantId = queueKey.getTenantId();
if (queueKey.getType() == ServiceType.TB_RULE_ENGINE) {
if (!responsibleServices.isEmpty()) { // if there are any dedicated servers
TenantProfileId profileId;
if (tenantId != null && !tenantId.isSysTenantId()) {
TenantRoutingInfo routingInfo = tenantRoutingInfoService.getRoutingInfo(tenantId);
profileId = routingInfo.getProfileId();
} else {
profileId = null;
}
List<ServiceInfo> responsible = responsibleServices.get(profileId);
if (responsible == null) {
// if there are no dedicated servers for this tenant profile, or for system queues,
// using the servers that are not responsible for any profile
responsible = servers.stream()
.filter(serviceInfo -> serviceInfo.getAssignedTenantProfilesCount() == 0)
.sorted(Comparator.comparing(ServiceInfo::getServiceId))
.collect(Collectors.toList());
if (profileId != null) {
log.debug("Using servers {} for profile {}", toServiceIds(responsible), profileId);
}
responsibleServices.put(profileId, responsible);
}
servers = responsible;
}
int hash = hashFunction.newHasher()
.putLong(tenantId.getId().getMostSignificantBits())
.putLong(tenantId.getId().getLeastSignificantBits())
.putString(queueKey.getQueueName(), StandardCharsets.UTF_8)
.hash().asInt();
return servers.get(Math.abs((hash + partition) % servers.size()));
} else {
return servers.get(partition % servers.size());
}
}
private int hash(UUID key) {
return hashFunction.newHasher()
.putLong(key.getMostSignificantBits())
.putLong(key.getLeastSignificantBits())
.hash().asInt();
}
public static HashFunction forName(String name) {
switch (name) {
case "murmur3_32":
@ -436,4 +513,8 @@ public class HashPartitionService implements PartitionService {
}
}
private List<String> toServiceIds(Collection<ServiceInfo> serviceInfos) {
return serviceInfos.stream().map(ServiceInfo::getServiceId).collect(Collectors.toList());
}
}

3
common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java

@ -64,4 +64,7 @@ public interface PartitionService {
void updateQueue(TransportProtos.QueueUpdateMsg queueUpdateMsg);
void removeQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg);
boolean isManagedByCurrentService(TenantId tenantId);
}

5
common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbServiceInfoProvider.java

@ -18,6 +18,9 @@ package org.thingsboard.server.queue.discovery;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import java.util.Set;
import java.util.UUID;
public interface TbServiceInfoProvider {
String getServiceId();
@ -30,4 +33,6 @@ public interface TbServiceInfoProvider {
ServiceInfo generateNewServiceInfoWithCurrentSystemInfo();
Set<UUID> getAssignedTenantProfiles();
}

2
common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfo.java

@ -17,9 +17,11 @@ package org.thingsboard.server.queue.discovery;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
@Data
public class TenantRoutingInfo {
private final TenantId tenantId;
private final TenantProfileId profileId;
private final boolean isolatedTbRuleEngine;
}

1
common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfoService.java

@ -20,4 +20,5 @@ import org.thingsboard.server.common.data.id.TenantId;
public interface TenantRoutingInfoService {
TenantRoutingInfo getRoutingInfo(TenantId tenantId);
}

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

@ -21,6 +21,7 @@ import org.apache.kafka.clients.admin.CreateTopicsResult;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.common.errors.TopicExistsException;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.util.PropertyUtils;
import java.util.Collections;
import java.util.Map;
@ -62,12 +63,12 @@ public class TbKafkaAdmin implements TbQueueAdmin {
}
@Override
public void createTopicIfNotExists(String topic) {
public void createTopicIfNotExists(String topic, String properties) {
if (topics.contains(topic)) {
return;
}
try {
NewTopic newTopic = new NewTopic(topic, numPartitions, replicationFactor).configs(topicConfigs);
NewTopic newTopic = new NewTopic(topic, numPartitions, replicationFactor).configs(PropertyUtils.getProps(topicConfigs, properties));
createTopic(newTopic).values().get(topic).get();
topics.add(topic);
} catch (ExecutionException ee) {
@ -81,7 +82,6 @@ public class TbKafkaAdmin implements TbQueueAdmin {
log.warn("[{}] Failed to create topic", topic, e);
throw new RuntimeException(e);
}
}
@Override

7
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -114,7 +114,6 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Override
protected void doUnsubscribe() {
log.info("unsubscribe topic and close consumer for topic {}", getTopic());
if (consumer != null) {
consumer.unsubscribe();
consumer.close();
@ -123,4 +122,10 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
statsService.unregisterClientGroup(groupId);
}
}
@Override
public boolean isLongPollingSupported() {
return true;
}
}

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

@ -115,6 +115,9 @@ public class TbKafkaSettings {
@Value("${queue.kafka.session.timeout.ms:10000}")
private int sessionTimeoutMs;
@Value("${queue.kafka.auto_offset_reset:earliest}")
private String autoOffsetReset;
@Value("${queue.kafka.use_confluent_cloud:false}")
private boolean useConfluent;
@ -155,6 +158,8 @@ public class TbKafkaSettings {
props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, maxPartitionFetchBytes);
props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, fetchMaxBytes);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalMs);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);

16
common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java

@ -31,6 +31,7 @@ public class InMemoryTbQueueConsumer<T extends TbQueueMsg> implements TbQueueCon
private volatile Set<TopicPartitionInfo> partitions;
private volatile boolean stopped;
private volatile boolean subscribed;
private volatile boolean queueDeleted;
public InMemoryTbQueueConsumer(InMemoryStorage storage, String topic) {
this.storage = storage;
@ -103,4 +104,19 @@ public class InMemoryTbQueueConsumer<T extends TbQueueMsg> implements TbQueueCon
return stopped;
}
@Override
public void onQueueDelete() {
queueDeleted = true;
}
@Override
public boolean isQueueDeleted() {
return queueDeleted;
}
@Override
public List<String> getFullTopicNames() {
return partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
}
}

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java

@ -74,7 +74,7 @@ public class InMemoryTbTransportQueueFactory implements TbTransportQueueFactory
templateBuilder.queueAdmin(new TbQueueAdmin() {
@Override
public void createTopicIfNotExists(String topic) {}
public void createTopicIfNotExists(String topic, String properties) {}
@Override
public void destroy() {}

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -187,7 +187,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(configuration.getTopic());
consumerBuilder.clientId("re-" + queueName + "-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet());
consumerBuilder.groupId("re-" + queueName + "-consumer");
consumerBuilder.groupId("re-" + queueName + (configuration.getTenantId().isSysTenantId() ? "" : ("-" + configuration.getTenantId())) + "-consumer");
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(ruleEngineAdmin);
consumerBuilder.statsService(consumerStatsService);

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java

@ -166,7 +166,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(configuration.getTopic());
consumerBuilder.clientId("re-" + queueName + "-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet());
consumerBuilder.groupId("re-" + queueName + "-consumer");
consumerBuilder.groupId("re-" + queueName + (configuration.getTenantId().isSysTenantId() ? "" : ("-" + configuration.getTenantId())) + "-consumer");
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(ruleEngineAdmin);
consumerBuilder.statsService(consumerStatsService);

2
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubAdmin.java

@ -103,7 +103,7 @@ public class TbPubSubAdmin implements TbQueueAdmin {
}
@Override
public void createTopicIfNotExists(String partition) {
public void createTopicIfNotExists(String partition, String properties) {
TopicName topicName = TopicName.newBuilder()
.setTopic(partition)
.setProject(pubSubSettings.getProjectId())

9
common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqAdmin.java

@ -18,9 +18,11 @@ package org.thingsboard.server.queue.rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.queue.TbQueueAdmin;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
@ -50,7 +52,12 @@ public class TbRabbitMqAdmin implements TbQueueAdmin {
}
@Override
public void createTopicIfNotExists(String topic) {
public void createTopicIfNotExists(String topic, String properties) {
Map<String, Object> arguments = this.arguments;
if (StringUtils.isNotBlank(properties)) {
arguments = new HashMap<>(arguments);
arguments.putAll(TbRabbitMqQueueArguments.getArgs(properties));
}
try {
channel.queueDeclare(topic, false, false, false, arguments);
} catch (IOException e) {

8
common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java

@ -65,7 +65,7 @@ public class TbRabbitMqQueueArguments {
vcArgs = getArgs(vcProperties);
}
private Map<String, Object> getArgs(String properties) {
public static Map<String, Object> getArgs(String properties) {
Map<String, Object> configs = new HashMap<>();
if (StringUtils.isNotEmpty(properties)) {
for (String property : properties.split(";")) {
@ -78,7 +78,7 @@ public class TbRabbitMqQueueArguments {
return configs;
}
private Object getObjectValue(String str) {
private static Object getObjectValue(String str) {
if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("false")) {
return Boolean.valueOf(str);
} else if (isNumeric(str)) {
@ -87,7 +87,7 @@ public class TbRabbitMqQueueArguments {
return str;
}
private Object getNumericValue(String str) {
private static Object getNumericValue(String str) {
if (str.contains(".")) {
return Double.valueOf(str);
} else {
@ -97,7 +97,7 @@ public class TbRabbitMqQueueArguments {
private static final Pattern PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
public boolean isNumeric(String strNum) {
private static boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}

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

@ -26,6 +26,7 @@ import com.amazonaws.services.sqs.model.CreateQueueRequest;
import com.amazonaws.services.sqs.model.GetQueueUrlResult;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.util.PropertyUtils;
import java.util.Map;
import java.util.function.Function;
@ -63,11 +64,12 @@ public class TbAwsSqsAdmin implements TbQueueAdmin {
}
@Override
public void createTopicIfNotExists(String topic) {
public void createTopicIfNotExists(String topic, String properties) {
String queueName = convertTopicToQueueName(topic);
if (queues.containsKey(queueName)) {
return;
}
Map<String, String> attributes = PropertyUtils.getProps(this.attributes, properties, TbAwsSqsQueueAttributes::toConfigs);
final CreateQueueRequest createQueueRequest = new CreateQueueRequest(queueName).withAttributes(attributes);
String queueUrl = sqsClient.createQueue(createQueueRequest).getQueueUrl();
queues.put(getQueueNameFromUrl(queueUrl), queueUrl);

8
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java

@ -76,6 +76,12 @@ public class TbAwsSqsQueueAttributes {
private Map<String, String> getConfigs(String properties) {
Map<String, String> configs = new HashMap<>(defaultAttributes);
configs.putAll(toConfigs(properties));
return configs;
}
public static Map<String, String> toConfigs(String properties) {
Map<String, String> configs = new HashMap<>();
if (StringUtils.isNotEmpty(properties)) {
for (String property : properties.split(";")) {
int delimiterPosition = property.indexOf(":");
@ -88,7 +94,7 @@ public class TbAwsSqsQueueAttributes {
return configs;
}
private void validateAttributeName(String key) {
private static void validateAttributeName(String key) {
QueueAttributeName.fromValue(key);
}
}

14
common/queue/src/main/java/org/thingsboard/server/queue/util/PropertyUtils.java

@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class PropertyUtils {
@ -37,4 +38,17 @@ public class PropertyUtils {
return configs;
}
public static Map<String, String> getProps(Map<String, String> defaultProperties, String propertiesStr) {
return getProps(defaultProperties, propertiesStr, PropertyUtils::getProps);
}
public static Map<String, String> getProps(Map<String, String> defaultProperties, String propertiesStr, Function<String, Map<String, String>> parser) {
Map<String, String> properties = defaultProperties;
if (StringUtils.isNotBlank(propertiesStr)) {
properties = new HashMap<>(properties);
properties.putAll(parser.apply(propertiesStr));
}
return properties;
}
}

1
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java

@ -82,6 +82,7 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil
if (profileOpt.isPresent()) {
TenantProfile newProfile = profileOpt.get();
log.trace("[{}] put: {}", newProfile.getId(), newProfile);
profiles.put(newProfile.getId(), newProfile);
Set<TenantId> affectedTenants = tenantProfileIds.get(newProfile.getId());
return new TenantProfileUpdateResult(newProfile, affectedTenants != null ? affectedTenants : Collections.emptySet());
} else {

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java

@ -29,7 +29,7 @@ import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ConditionalOnExpression("'${service.type:null}'=='tb-transport'")
public class TransportTenantRoutingInfoService implements TenantRoutingInfoService {
private TransportTenantProfileCache tenantProfileCache;
private final TransportTenantProfileCache tenantProfileCache;
public TransportTenantRoutingInfoService(TransportTenantProfileCache tenantProfileCache) {
this.tenantProfileCache = tenantProfileCache;
@ -38,7 +38,7 @@ public class TransportTenantRoutingInfoService implements TenantRoutingInfoServi
@Override
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
TenantProfile profile = tenantProfileCache.get(tenantId);
return new TenantRoutingInfo(tenantId, profile.isIsolatedTbRuleEngine());
return new TenantRoutingInfo(tenantId, profile.getId(), profile.isIsolatedTbRuleEngine());
}
}

16
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -111,10 +111,24 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
return toAssetProfileInfo(findAssetProfileById(tenantId, assetProfileId));
}
@Override
public AssetProfile saveAssetProfile(AssetProfile assetProfile, boolean doValidate) {
return doSaveAssetProfile(assetProfile, doValidate);
}
@Override
public AssetProfile saveAssetProfile(AssetProfile assetProfile) {
return doSaveAssetProfile(assetProfile, true);
}
private AssetProfile doSaveAssetProfile(AssetProfile assetProfile, boolean doValidate) {
log.trace("Executing saveAssetProfile [{}]", assetProfile);
AssetProfile oldAssetProfile = assetProfileValidator.validate(assetProfile, AssetProfile::getTenantId);
AssetProfile oldAssetProfile = null;
if (doValidate) {
oldAssetProfile = assetProfileValidator.validate(assetProfile, AssetProfile::getTenantId);
} else if (assetProfile.getId() != null) {
oldAssetProfile = findAssetProfileById(assetProfile.getTenantId(), assetProfile.getId());
}
AssetProfile savedAssetProfile;
try {
savedAssetProfile = assetProfileDao.saveAndFlush(assetProfile.getTenantId(), assetProfile);

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

@ -132,12 +132,26 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
.orElse(null), true);
}
@Override
public Asset saveAsset(Asset asset, boolean doValidate) {
return doSaveAsset(asset, doValidate);
}
@Override
public Asset saveAsset(Asset asset) {
return doSaveAsset(asset, true);
}
private Asset doSaveAsset(Asset asset, boolean doValidate) {
log.trace("Executing saveAsset [{}]", asset);
Asset oldAsset = assetValidator.validate(asset, Asset::getTenantId);
Asset savedAsset;
Asset oldAsset = null;
if (doValidate) {
oldAsset = assetValidator.validate(asset, Asset::getTenantId);
} else if (asset.getId() != null) {
oldAsset = findAssetById(asset.getTenantId(), asset.getId());
}
AssetCacheEvictEvent evictEvent = new AssetCacheEvictEvent(asset.getTenantId(), asset.getName(), oldAsset != null ? oldAsset.getName() : null);
Asset savedAsset;
try {
AssetProfile assetProfile;
if (asset.getAssetProfileId() == null) {

13
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -137,10 +137,21 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findByIdAsync(tenantId, dashboardId.getId());
}
@Override
public Dashboard saveDashboard(Dashboard dashboard, boolean doValidate) {
return doSaveDashboard(dashboard, doValidate);
}
@Override
public Dashboard saveDashboard(Dashboard dashboard) {
return doSaveDashboard(dashboard, true);
}
private Dashboard doSaveDashboard(Dashboard dashboard, boolean doValidate) {
log.trace("Executing saveDashboard [{}]", dashboard);
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
if (doValidate) {
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
}
try {
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));

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

@ -142,8 +142,17 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
return toDeviceProfileInfo(findDeviceProfileById(tenantId, deviceProfileId));
}
@Override
public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile, boolean doValidate) {
return doSaveDeviceProfile(deviceProfile, doValidate);
}
@Override
public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) {
return doSaveDeviceProfile(deviceProfile, true);
}
private DeviceProfile doSaveDeviceProfile(DeviceProfile deviceProfile, boolean doValidate) {
log.trace("Executing saveDeviceProfile [{}]", deviceProfile);
if (deviceProfile.getProfileData() != null && deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) {
X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration();
@ -151,7 +160,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
formatDeviceProfileCertificate(deviceProfile, x509Configuration);
}
}
DeviceProfile oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
DeviceProfile oldDeviceProfile = null;
if (doValidate) {
oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
} else if (deviceProfile.getId() != null) {
oldDeviceProfile = findDeviceProfileById(deviceProfile.getTenantId(), deviceProfile.getId());
}
DeviceProfile savedDeviceProfile;
try {
savedDeviceProfile = deviceProfileDao.saveAndFlush(deviceProfile.getTenantId(), deviceProfile);

16
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -101,10 +101,24 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
cache.evict(keys);
}
@Override
public EntityView saveEntityView(EntityView entityView, boolean doValidate) {
return doSaveEntityView(entityView, doValidate);
}
@Override
public EntityView saveEntityView(EntityView entityView) {
return doSaveEntityView(entityView, true);
}
private EntityView doSaveEntityView(EntityView entityView, boolean doValidate) {
log.trace("Executing save entity view [{}]", entityView);
EntityView old = entityViewValidator.validate(entityView, EntityView::getTenantId);
EntityView old = null;
if (doValidate) {
old = entityViewValidator.validate(entityView, EntityView::getTenantId);
} else if (entityView.getId() != null) {
old = findEntityViewById(entityView.getTenantId(), entityView.getId());
}
try {
EntityView saved = entityViewDao.save(entityView.getTenantId(), entityView);
publishEvictEvent(new EntityViewEvictEvent(saved.getTenantId(), saved.getId(), saved.getEntityId(), old != null ? old.getEntityId() : null, saved.getName(), old != null ? old.getName() : null));

64
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -25,8 +26,11 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings.NotificationPref;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AffectedTenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.AffectedUserFilter;
@ -38,20 +42,28 @@ import org.thingsboard.server.common.data.notification.targets.platform.TenantAd
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserSettingsService;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultNotificationSettingsService implements NotificationSettingsService {
private final AdminSettingsService adminSettingsService;
private final NotificationTargetService notificationTargetService;
private final NotificationTemplateService notificationTemplateService;
private final DefaultNotifications defaultNotifications;
private final UserSettingsService userSettingsService;
private static final String SETTINGS_KEY = "notifications";
@ -81,6 +93,58 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
});
}
@Override
public UserNotificationSettings saveUserNotificationSettings(TenantId tenantId, UserId userId, UserNotificationSettings settings) {
UserSettings userSettings = new UserSettings();
userSettings.setUserId(userId);
userSettings.setType(UserSettingsType.NOTIFICATIONS);
userSettings.setSettings(JacksonUtil.valueToTree(settings));
userSettingsService.saveUserSettings(tenantId, userSettings);
return formatUserNotificationSettings(settings);
}
@Override
public UserNotificationSettings getUserNotificationSettings(TenantId tenantId, UserId userId, boolean format) {
UserSettings userSettings = userSettingsService.findUserSettings(tenantId, userId, UserSettingsType.NOTIFICATIONS);
UserNotificationSettings settings = null;
if (userSettings != null) {
try {
settings = JacksonUtil.treeToValue(userSettings.getSettings(), UserNotificationSettings.class);
} catch (Exception e) {
log.warn("Failed to parse notification settings for user {}", userId, e);
}
}
if (settings == null) {
settings = UserNotificationSettings.DEFAULT;
}
if (format) {
settings = formatUserNotificationSettings(settings);
}
return settings;
}
private UserNotificationSettings formatUserNotificationSettings(UserNotificationSettings settings) {
Map<NotificationType, NotificationPref> prefs = new EnumMap<>(NotificationType.class);
if (settings != null) {
prefs.putAll(settings.getPrefs());
}
NotificationPref defaultPref = NotificationPref.createDefault();
for (NotificationType notificationType : NotificationType.values()) {
NotificationPref pref = prefs.get(notificationType);
if (pref == null) {
prefs.put(notificationType, defaultPref);
} else {
var enabledDeliveryMethods = new LinkedHashMap<>(pref.getEnabledDeliveryMethods());
// in case a new delivery method was added to the platform
UserNotificationSettings.deliveryMethods.forEach(deliveryMethod -> {
enabledDeliveryMethods.putIfAbsent(deliveryMethod, true);
});
pref.setEnabledDeliveryMethods(enabledDeliveryMethods);
}
}
return new UserNotificationSettings(prefs);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED) // so that parent transaction is not aborted on method failure
@Override
public void createDefaultNotificationConfigs(TenantId tenantId) {

2
dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java

@ -99,8 +99,6 @@ public class TenantProfileDataValidator extends DataValidator<TenantProfile> {
TenantProfile old = tenantProfileDao.findById(TenantId.SYS_TENANT_ID, tenantProfile.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing tenant profile!");
} else if (old.isIsolatedTbRuleEngine() != tenantProfile.isIsolatedTbRuleEngine()) {
throw new DataValidationException("Can't update isolatedTbRuleEngine property!");
}
return old;
}

11
dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java

@ -188,17 +188,6 @@ public class TenantProfileServiceTest extends AbstractServiceTest {
});
}
@Test
public void testSaveSameTenantProfileWithDifferentIsolatedTbRuleEngine() {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile);
savedTenantProfile.setIsolatedTbRuleEngine(true);
addMainQueueConfig(savedTenantProfile);
Assertions.assertThrows(DataValidationException.class, () -> {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile);
});
}
@Test
public void testDeleteTenantProfileWithExistingTenant() {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");

2
msa/vc-executor/src/main/java/org/thingsboard/server/vc/service/VersionControlTenantRoutingInfoService.java

@ -25,6 +25,6 @@ public class VersionControlTenantRoutingInfoService implements TenantRoutingInfo
@Override
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
//This dummy implementation is ok since Version Control service does not produce any rule engine messages.
return new TenantRoutingInfo(tenantId, false);
return new TenantRoutingInfo(tenantId, null, false);
}
}

1
msa/vc-executor/src/main/resources/tb-vc-executor.yml

@ -73,6 +73,7 @@ queue:
fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
request.timeout.ms: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
session.timeout.ms: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms
auto_offset_reset: "${TB_QUEUE_KAFKA_AUTO_OFFSET_RESET:earliest}" # earliest, latest or none
use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
confluent:
ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"

2
pom.xml

@ -78,7 +78,7 @@
<zookeeper.version>3.8.1</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version>
<tbel.version>1.0.7</tbel.version>
<tbel.version>1.0.8</tbel.version>
<lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

118
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java

@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
@ -44,9 +46,10 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -81,9 +84,8 @@ import static org.thingsboard.rule.engine.math.TbMathArgumentType.CONSTANT;
)
public class TbMathNode implements TbNode {
private static final ConcurrentMap<EntityId, Semaphore> semaphores = new ConcurrentReferenceHashMap<>();
private static final ConcurrentMap<EntityId, SemaphoreWithQueue<TbMsgTbContext>> locks = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
private final ThreadLocal<Expression> customExpression = new ThreadLocal<>();
private TbMathNodeConfiguration config;
private boolean msgBodyToJsonConversionRequired;
@ -108,51 +110,69 @@ public class TbMathNode implements TbNode {
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
var originator = msg.getOriginator();
var originatorSemaphore = semaphores.computeIfAbsent(originator, tmp -> new Semaphore(1, true));
boolean acquired = tryAcquire(originator, originatorSemaphore);
var semaphoreWithQueue = locks.computeIfAbsent(msg.getOriginator(), SemaphoreWithQueue::new);
semaphoreWithQueue.getQueue().add(new TbMsgTbContext(msg, ctx));
if (!acquired) {
ctx.tellFailure(msg, new RuntimeException("Failed to process message for originator synchronously"));
return;
}
tryProcessQueue(semaphoreWithQueue);
}
try {
var arguments = config.getArguments();
Optional<ObjectNode> msgBodyOpt = convertMsgBodyIfRequired(msg);
var argumentValues = Futures.allAsList(arguments.stream()
.map(arg -> resolveArguments(ctx, msg, msgBodyOpt, arg)).collect(Collectors.toList()));
ListenableFuture<TbMsg> resultMsgFuture = Futures.transformAsync(argumentValues, args ->
updateMsgAndDb(ctx, msg, msgBodyOpt, calculateResult(args)), ctx.getDbCallbackExecutor());
DonAsynchron.withCallback(resultMsgFuture, resultMsg -> {
try {
ctx.tellSuccess(resultMsg);
} finally {
originatorSemaphore.release();
void tryProcessQueue(SemaphoreWithQueue<TbMsgTbContext> lockAndQueue) {
final Semaphore semaphore = lockAndQueue.getSemaphore();
final Queue<TbMsgTbContext> queue = lockAndQueue.getQueue();
while (!queue.isEmpty()) {
// The semaphore have to be acquired before EACH poll and released before NEXT poll.
// Otherwise, some message will remain unprocessed in queue
if (!semaphore.tryAcquire()) {
return;
}
TbMsgTbContext tbMsgTbContext = null;
try {
tbMsgTbContext = queue.poll();
if (tbMsgTbContext == null) {
semaphore.release();
continue;
}
}, t -> {
try {
ctx.tellFailure(msg, t);
} finally {
originatorSemaphore.release();
final TbMsg msg = tbMsgTbContext.getMsg();
if (!msg.getCallback().isMsgValid()) {
log.trace("[{}] Skipping non-valid message [{}]", lockAndQueue.getEntityId(), msg);
semaphore.release();
continue;
}
}, ctx.getDbCallbackExecutor());
} catch (Throwable e) {
originatorSemaphore.release();
log.warn("[{}] Failed to process message: {}", originator, msg, e);
throw e;
//DO PROCESSING
final TbContext ctx = tbMsgTbContext.getCtx();
final ListenableFuture<TbMsg> resultMsgFuture = processMsgAsync(ctx, msg);
DonAsynchron.withCallback(resultMsgFuture, resultMsg -> {
try {
ctx.tellSuccess(resultMsg);
} finally {
lockAndQueue.getSemaphore().release();
tryProcessQueue(lockAndQueue);
}
}, t -> {
try {
ctx.tellFailure(msg, t);
} finally {
lockAndQueue.getSemaphore().release();
tryProcessQueue(lockAndQueue);
}
}, ctx.getDbCallbackExecutor());
} catch (Throwable e) {
semaphore.release();
log.warn("[{}] Failed to process message: {}", lockAndQueue.getEntityId(), tbMsgTbContext == null ? null : tbMsgTbContext.getMsg(), e);
throw e;
}
break; //submitted async exact one task. next poll will try on callback
}
}
private boolean tryAcquire(EntityId originator, Semaphore originatorSemaphore) {
boolean acquired;
try {
acquired = originatorSemaphore.tryAcquire(20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
acquired = false;
log.debug("[{}] Failed to acquire semaphore", originator, e);
}
return acquired;
ListenableFuture<TbMsg> processMsgAsync(TbContext ctx, TbMsg msg) {
var arguments = config.getArguments();
Optional<ObjectNode> msgBodyOpt = convertMsgBodyIfRequired(msg);
var argumentValues = Futures.allAsList(arguments.stream()
.map(arg -> resolveArguments(ctx, msg, msgBodyOpt, arg)).collect(Collectors.toList()));
ListenableFuture<TbMsg> resultMsgFuture = Futures.transformAsync(argumentValues, args ->
updateMsgAndDb(ctx, msg, msgBodyOpt, calculateResult(args)), ctx.getDbCallbackExecutor());
return resultMsgFuture;
}
private ListenableFuture<TbMsg> updateMsgAndDb(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, double result) {
@ -402,4 +422,20 @@ public class TbMathNode implements TbNode {
@Override
public void destroy() {
}
@Data
@RequiredArgsConstructor
static public class SemaphoreWithQueue<T> {
final EntityId entityId;
final Semaphore semaphore = new Semaphore(1);
final Queue<T> queue = new ConcurrentLinkedQueue<>();
}
@Data
@RequiredArgsConstructor
static public class TbMsgTbContext {
final TbMsg msg;
final TbContext ctx;
}
}

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

Loading…
Cancel
Save