Browse Source

merge with develop/3.4

pull/6757/head
YevhenBondarenko 4 years ago
parent
commit
c78017ecb2
  1. 1
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  2. 27
      application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
  3. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  4. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java
  6. 48
      application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
  7. 14
      application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
  8. 10
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
  10. 8
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java
  11. 11
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
  12. 22
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  13. 11
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java
  14. 7
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java
  15. 5
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java
  16. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java
  17. 8
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java
  18. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java
  19. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java
  20. 21
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  21. 134
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  22. 2
      application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java
  23. 1
      application/src/main/resources/thingsboard.yml
  24. 2
      common/cache/src/main/java/org/thingsboard/server/cache/SimpleTbCacheValueWrapper.java
  25. 27
      common/cluster-api/src/main/proto/queue.proto
  26. 6
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  27. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataDiff.java
  28. 5
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java
  29. 6
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java
  30. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionedEntityInfo.java
  31. 8
      common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java
  32. 68
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java
  33. 13
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java
  34. 8
      docker/.gitignore
  35. 1
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  36. 1
      ui-ngx/src/app/shared/models/vc.models.ts

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

@ -138,6 +138,7 @@ public class ControllerConstants {
protected static final String ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the entity version name.";
protected static final String VERSION_ID_PARAM_DESCRIPTION = "Version id, for example fd82625bdd7d6131cf8027b44ee967012ecaf990. Represents commit hash.";
protected static final String BRANCH_PARAM_DESCRIPTION = "The name of the working branch, for example 'master'";
protected static final String MARKDOWN_CODE_BLOCK_START = "```json\n";
protected static final String MARKDOWN_CODE_BLOCK_END = "\n```";

27
application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java

@ -58,6 +58,9 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.BRANCH_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
@ -230,8 +233,11 @@ public class EntitiesVersionControlController extends BaseController {
MARKDOWN_CODE_BLOCK_END +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/version/{entityType}/{externalEntityUuid}", params = {"branch", "pageSize", "page"})
public DeferredResult<PageData<EntityVersion>> listEntityVersions(@PathVariable EntityType entityType,
public DeferredResult<PageData<EntityVersion>> listEntityVersions(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = "A string value representing external entity id. This is `externalId` property of an entity, or otherwise if not set - simply id of this entity.")
@PathVariable UUID externalEntityUuid,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION)
@RequestParam String branch,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ -256,7 +262,9 @@ public class EntitiesVersionControlController extends BaseController {
"The response structure is the same as for `listEntityVersions` API method." +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/version/{entityType}", params = {"branch", "pageSize", "page"})
public DeferredResult<PageData<EntityVersion>> listEntityTypeVersions(@PathVariable EntityType entityType,
public DeferredResult<PageData<EntityVersion>> listEntityTypeVersions(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true)
@RequestParam String branch,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ -279,7 +287,8 @@ public class EntitiesVersionControlController extends BaseController {
"The response format is the same as for `listEntityVersions` API method." +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/version", params = {"branch", "pageSize", "page"})
public DeferredResult<PageData<EntityVersion>> listVersions(@RequestParam String branch,
public DeferredResult<PageData<EntityVersion>> listVersions(@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true)
@RequestParam String branch,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -302,9 +311,11 @@ public class EntitiesVersionControlController extends BaseController {
"Entities order will be the same as in the repository." +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/entity/{entityType}/{versionId}", params = {"branch"})
public DeferredResult<List<VersionedEntityInfo>> listEntitiesAtVersion(@PathVariable EntityType entityType,
public DeferredResult<List<VersionedEntityInfo>> listEntitiesAtVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@PathVariable String versionId,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true)
@RequestParam String branch) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), branch, versionId, entityType));
@ -318,6 +329,7 @@ public class EntitiesVersionControlController extends BaseController {
@GetMapping(value = "/entity/{versionId}", params = {"branch"})
public DeferredResult<List<VersionedEntityInfo>> listAllEntitiesAtVersion(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@PathVariable String versionId,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true)
@RequestParam String branch) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, versionId));
@ -332,7 +344,9 @@ public class EntitiesVersionControlController extends BaseController {
@GetMapping("/info/{versionId}/{entityType}/{externalEntityUuid}")
public DeferredResult<EntityDataInfo> getEntityDataInfo(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@PathVariable String versionId,
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = "A string value representing external entity id", required = true)
@PathVariable UUID externalEntityUuid) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
@ -344,8 +358,11 @@ public class EntitiesVersionControlController extends BaseController {
"Entity data structure is the same as stored in a repository. " +
TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/diff/{entityType}/{internalEntityUuid}", params = {"branch", "versionId"})
public DeferredResult<EntityDataDiff> compareEntityDataToVersion(@PathVariable EntityType entityType,
public DeferredResult<EntityDataDiff> compareEntityDataToVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable EntityType entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@PathVariable UUID internalEntityUuid,
@ApiParam(value = BRANCH_PARAM_DESCRIPTION)
@RequestParam String branch,
@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
@RequestParam String versionId) throws Exception {

4
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -256,8 +256,8 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
}
@Override
public void notifyCreateOrUpdateRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user,
ActionType actionType, Object... additionalInfo) {
public void notifyRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user,
ActionType actionType, Object... additionalInfo) {
logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, additionalInfo);
logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, additionalInfo);
try {

4
application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java

@ -107,6 +107,6 @@ public interface TbNotificationEntityService {
ActionType actionType, boolean sendNotifyMsgToEdge,
Exception e, Object... additionalInfo);
void notifyCreateOrUpdateRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user,
ActionType actionType, Object... additionalInfo);
void notifyRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user,
ActionType actionType, Object... additionalInfo);
}

4
application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java

@ -42,7 +42,7 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl
public void save(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user) throws ThingsboardException {
try {
relationService.saveRelation(tenantId, relation);
notificationEntityService.notifyCreateOrUpdateRelation(tenantId, customerId,
notificationEntityService.notifyRelation(tenantId, customerId,
relation, user, ActionType.RELATION_ADD_OR_UPDATE, relation);
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, relation.getFrom(), null, customerId,
@ -60,7 +60,7 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
notificationEntityService.notifyCreateOrUpdateRelation(tenantId, customerId,
notificationEntityService.notifyRelation(tenantId, customerId,
relation, user, ActionType.RELATION_DELETED, relation);
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, relation.getFrom(), null, customerId,

48
application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java

@ -94,36 +94,32 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
@Override
public void updateEntityViewAttributes(TenantId tenantId, EntityView savedEntityView, EntityView oldEntityView, User user) throws ThingsboardException {
try {
List<ListenableFuture<?>> futures = new ArrayList<>();
List<ListenableFuture<?>> futures = new ArrayList<>();
if (oldEntityView != null) {
if (oldEntityView.getKeys() != null && oldEntityView.getKeys().getAttributes() != null) {
futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.CLIENT_SCOPE, oldEntityView.getKeys().getAttributes().getCs(), user));
futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SERVER_SCOPE, oldEntityView.getKeys().getAttributes().getSs(), user));
futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SHARED_SCOPE, oldEntityView.getKeys().getAttributes().getSh(), user));
}
List<String> tsKeys = oldEntityView.getKeys() != null && oldEntityView.getKeys().getTimeseries() != null ?
oldEntityView.getKeys().getTimeseries() : Collections.emptyList();
futures.add(deleteLatestFromEntityView(oldEntityView, tsKeys, user));
if (oldEntityView != null) {
if (oldEntityView.getKeys() != null && oldEntityView.getKeys().getAttributes() != null) {
futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.CLIENT_SCOPE, oldEntityView.getKeys().getAttributes().getCs(), user));
futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SERVER_SCOPE, oldEntityView.getKeys().getAttributes().getSs(), user));
futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SHARED_SCOPE, oldEntityView.getKeys().getAttributes().getSh(), user));
}
if (savedEntityView.getKeys() != null) {
if (savedEntityView.getKeys().getAttributes() != null) {
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), user));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), user));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), user));
}
futures.add(copyLatestFromEntityToEntityView(tenantId, savedEntityView));
List<String> tsKeys = oldEntityView.getKeys() != null && oldEntityView.getKeys().getTimeseries() != null ?
oldEntityView.getKeys().getTimeseries() : Collections.emptyList();
futures.add(deleteLatestFromEntityView(oldEntityView, tsKeys, user));
}
if (savedEntityView.getKeys() != null) {
if (savedEntityView.getKeys().getAttributes() != null) {
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), user));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), user));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), user));
}
for (ListenableFuture<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to copy attributes to entity view", e);
}
futures.add(copyLatestFromEntityToEntityView(tenantId, savedEntityView));
}
for (ListenableFuture<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to copy attributes to entity view", e);
}
} catch (Exception e) {
throw e;
}
}

14
application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java

@ -67,14 +67,10 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
@Override
public void delete(Tenant tenant) throws Exception {
try {
TenantId tenantId = tenant.getId();
tenantService.deleteTenant(tenantId);
tenantProfileCache.evict(tenantId);
notificationEntityService.notifyDeleteTenant(tenant);
versionControlService.deleteVersionControlSettings(tenantId).get(1, TimeUnit.MINUTES);
} catch (Exception e) {
throw e;
}
TenantId tenantId = tenant.getId();
tenantService.deleteTenant(tenantId);
tenantProfileCache.evict(tenantId);
notificationEntityService.notifyDeleteTenant(tenant);
versionControlService.deleteVersionControlSettings(tenantId).get(1, TimeUnit.MINUTES);
}
}

10
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -32,8 +32,8 @@ import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.apiusage.RateLimitService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService;
import org.thingsboard.server.service.sync.ie.exporting.impl.DefaultEntityExportService;
@ -57,9 +57,9 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
private final Map<EntityType, EntityExportService<?, ?, ?>> exportServices = new HashMap<>();
private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>();
private final EntityActionService entityActionService;
private final RelationService relationService;
private final RateLimitService rateLimitService;
private final TbNotificationEntityService entityNotificationService;
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of(
EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN,
@ -109,10 +109,8 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
relationService.saveRelations(ctx.getTenantId(), new ArrayList<>(ctx.getRelations()));
for (EntityRelation relation : ctx.getRelations()) {
entityActionService.logEntityAction(ctx.getUser(), relation.getFrom(), null, null,
ActionType.RELATION_ADD_OR_UPDATE, null, relation);
entityActionService.logEntityAction(ctx.getUser(), relation.getTo(), null, null,
ActionType.RELATION_ADD_OR_UPDATE, null, relation);
entityNotificationService.notifyRelation(ctx.getTenantId(), null,
relation, ctx.getUser(), ActionType.RELATION_ADD_OR_UPDATE, relation);
}
}

2
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java

@ -41,8 +41,6 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
return (D) new EntityExportData<E>();
}
;
public abstract Set<EntityType> getSupportedEntityTypes();
protected void replaceUuidsRecursively(EntitiesExportCtx<?> ctx, JsonNode node, Set<String> skipFieldsSet) {

8
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java

@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.sync.ie.AttributeExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
@ -55,7 +55,7 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
@Lazy
protected ExportableEntitiesService exportableEntitiesService;
@Autowired
private RelationService relationService;
private RelationDao relationDao;
@Autowired
private AttributesService attributesService;
@ -99,10 +99,10 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
private List<EntityRelation> exportRelations(EntitiesExportCtx<?> ctx, E entity) throws ThingsboardException {
List<EntityRelation> relations = new ArrayList<>();
List<EntityRelation> inboundRelations = relationService.findByTo(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON);
List<EntityRelation> inboundRelations = relationDao.findAllByTo(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON);
relations.addAll(inboundRelations);
List<EntityRelation> outboundRelations = relationService.findByFrom(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON);
List<EntityRelation> outboundRelations = relationDao.findAllByFrom(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON);
relations.addAll(outboundRelations);
return relations;
}

11
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java

@ -18,10 +18,7 @@ package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
@ -52,14 +49,6 @@ public class AssetImportService extends BaseEntityImportService<AssetId, Asset,
return assetService.saveAsset(asset);
}
@Override
protected void onEntitySaved(User user, Asset savedAsset, Asset oldAsset) throws ThingsboardException {
super.onEntitySaved(user, savedAsset, oldAsset);
if (oldAsset != null) {
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedAsset.getId(), EdgeEventActionType.UPDATED);
}
}
@Override
protected Asset deepCopy(Asset asset) {
return new Asset(asset);

22
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -28,7 +28,6 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -49,6 +48,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.sync.ie.AttributeExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
@ -77,6 +77,8 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
@Autowired
private RelationService relationService;
@Autowired
private RelationDao relationDao;
@Autowired
private TelemetrySubscriptionService tsSubService;
@Autowired
protected EntityActionService entityActionService;
@ -201,19 +203,18 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
if (importResult.getOldEntity() != null) {
List<EntityRelation> existingRelations = new ArrayList<>();
existingRelations.addAll(relationService.findByTo(tenantId, entity.getId(), RelationTypeGroup.COMMON));
existingRelations.addAll(relationService.findByFrom(tenantId, entity.getId(), RelationTypeGroup.COMMON));
existingRelations.addAll(relationDao.findAllByTo(tenantId, entity.getId(), RelationTypeGroup.COMMON));
existingRelations.addAll(relationDao.findAllByFrom(tenantId, entity.getId(), RelationTypeGroup.COMMON));
// dao is used here instead of service to avoid getting cached values, because relationService.deleteRelation will evict value from cache only after transaction is committed
for (EntityRelation existingRelation : existingRelations) {
EntityRelation relation = relationsMap.get(existingRelation);
if (relation == null) {
importResult.setUpdatedRelatedEntities(true);
relationService.deleteRelation(tenantId, existingRelation);
relationService.deleteRelation(ctx.getTenantId(), existingRelation.getFrom(), existingRelation.getTo(), existingRelation.getType(), existingRelation.getTypeGroup());
importResult.addSendEventsCallback(() -> {
entityActionService.logEntityAction(ctx.getUser(), existingRelation.getFrom(), null, null,
ActionType.RELATION_DELETED, null, existingRelation);
entityActionService.logEntityAction(ctx.getUser(), existingRelation.getTo(), null, null,
ActionType.RELATION_DELETED, null, existingRelation);
entityNotificationService.notifyRelation(tenantId, null,
existingRelation, ctx.getUser(), ActionType.RELATION_DELETED, existingRelation);
});
} else if (Objects.equal(relation.getAdditionalInfo(), existingRelation.getAdditionalInfo())) {
relationsMap.remove(relation);
@ -267,9 +268,8 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
}
protected void onEntitySaved(User user, E savedEntity, E oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity,
savedEntity instanceof HasCustomerId ? ((HasCustomerId) savedEntity).getCustomerId() : user.getCustomerId(),
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
entityNotificationService.notifyCreateOrUpdateEntity(user.getTenantId(), savedEntity.getId(), savedEntity,
null, oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, user);
}

11
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java

@ -19,9 +19,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
@ -68,14 +65,6 @@ public class CustomerImportService extends BaseEntityImportService<CustomerId, C
return new Customer(customer);
}
@Override
protected void onEntitySaved(User user, Customer savedCustomer, Customer oldCustomer) throws ThingsboardException {
super.onEntitySaved(user, savedCustomer, oldCustomer);
if (oldCustomer != null) {
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedCustomer.getId(), EdgeEventActionType.UPDATED);
}
}
@Override
public EntityType getEntityType() {
return EntityType.CUSTOMER;

7
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java

@ -118,11 +118,8 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
}
@Override
protected void onEntitySaved(User user, Dashboard savedDashboard, Dashboard oldDashboard) throws ThingsboardException {
super.onEntitySaved(user, savedDashboard, oldDashboard);
if (oldDashboard != null) {
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedDashboard.getId(), EdgeEventActionType.UPDATED);
}
protected boolean compare(EntitiesImportCtx ctx, EntityExportData<Dashboard> exportData, Dashboard prepared, Dashboard existing) {
return super.compare(ctx, exportData, prepared, existing) || !prepared.getConfiguration().equals(existing.getConfiguration());
}
@Override

5
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java

@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -93,8 +94,8 @@ public class DeviceImportService extends BaseEntityImportService<DeviceId, Devic
@Override
protected void onEntitySaved(User user, Device savedDevice, Device oldDevice) throws ThingsboardException {
super.onEntitySaved(user, savedDevice, oldDevice);
clusterService.onDeviceUpdated(savedDevice, oldDevice);
entityNotificationService.notifyCreateOrUpdateDevice(user.getTenantId(), savedDevice.getId(), savedDevice.getCustomerId(),
savedDevice, oldDevice, oldDevice == null ? ActionType.ADDED : ActionType.UPDATED, user);
}
@Override

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java

@ -20,7 +20,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
@ -52,6 +51,7 @@ public class DeviceProfileImportService extends BaseEntityImportService<DevicePr
deviceProfile.setDefaultDashboardId(idProvider.getInternalId(deviceProfile.getDefaultDashboardId()));
deviceProfile.setFirmwareId(getOldEntityField(old, DeviceProfile::getFirmwareId));
deviceProfile.setSoftwareId(getOldEntityField(old, DeviceProfile::getSoftwareId));
deviceProfile.setDefaultQueueId(getOldEntityField(old, DeviceProfile::getDefaultQueueId));
return deviceProfile;
}
@ -62,15 +62,13 @@ public class DeviceProfileImportService extends BaseEntityImportService<DevicePr
@Override
protected void onEntitySaved(User user, DeviceProfile savedDeviceProfile, DeviceProfile oldDeviceProfile) throws ThingsboardException {
super.onEntitySaved(user, savedDeviceProfile, oldDeviceProfile);
clusterService.onDeviceProfileChange(savedDeviceProfile, null);
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedDeviceProfile.getId(),
oldDeviceProfile == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedDeviceProfile.getId(),
oldDeviceProfile == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
otaPackageStateService.update(savedDeviceProfile,
oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getFirmwareId(), savedDeviceProfile.getFirmwareId()),
oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getSoftwareId(), savedDeviceProfile.getSoftwareId()));
super.onEntitySaved(user, savedDeviceProfile, oldDeviceProfile);
}
@Override

8
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java

@ -22,15 +22,14 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@ -65,9 +64,8 @@ public class EntityViewImportService extends BaseEntityImportService<EntityViewI
protected void onEntitySaved(User user, EntityView savedEntityView, EntityView oldEntityView) throws ThingsboardException {
tbEntityViewService.updateEntityViewAttributes(user.getTenantId(), savedEntityView, oldEntityView, user);
super.onEntitySaved(user, savedEntityView, oldEntityView);
if (oldEntityView != null) {
entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedEntityView.getId(), EdgeEventActionType.UPDATED);
}
clusterService.broadcastEntityStateChangeEvent(savedEntityView.getTenantId(), savedEntityView.getId(),
oldEntityView == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
}
@Override

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.RuleChainId;
@ -119,14 +120,15 @@ public class RuleChainImportService extends BaseEntityImportService<RuleChainId,
RuleChainMetaData newMD = exportData.getMetaData();
RuleChainMetaData existingMD = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), prepared.getId());
existingMD.setRuleChainId(null);
different = newMD.equals(existingMD);
different = !newMD.equals(existingMD);
}
return different;
}
@Override
protected void onEntitySaved(User user, RuleChain savedRuleChain, RuleChain oldRuleChain) throws ThingsboardException {
super.onEntitySaved(user, savedRuleChain, oldRuleChain);
entityActionService.logEntityAction(user, savedRuleChain.getId(), savedRuleChain, null,
oldRuleChain == null ? ActionType.ADDED : ActionType.UPDATED, null);
if (savedRuleChain.getType() == RuleChainType.CORE) {
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedRuleChain.getId(),
oldRuleChain == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java

@ -86,9 +86,13 @@ public class WidgetsBundleImportService extends BaseEntityImportService<WidgetsB
return savedWidgetsBundle;
}
@Override
protected boolean compare(EntitiesImportCtx ctx, WidgetsBundleExportData exportData, WidgetsBundle prepared, WidgetsBundle existing) {
return true;
}
@Override
protected void onEntitySaved(User user, WidgetsBundle savedWidgetsBundle, WidgetsBundle oldWidgetsBundle) throws ThingsboardException {
super.onEntitySaved(user, savedWidgetsBundle, oldWidgetsBundle);
entityNotificationService.notifySendMsgToEdgeService(user.getTenantId(), savedWidgetsBundle.getId(),
oldWidgetsBundle == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
}

21
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -99,7 +99,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import static com.google.common.util.concurrent.Futures.transform;
import static com.google.common.util.concurrent.Futures.transformAsync;
@Service
@TbCoreComponent
@ -174,9 +173,11 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
private <T> T getStatus(User user, UUID requestId, Function<VersionControlTaskCacheEntry, T> getter) throws ThingsboardException {
var cacheEntry = taskCache.get(requestId);
if (cacheEntry == null || cacheEntry.get() == null) {
log.debug("[{}] No cache record: {}", requestId, cacheEntry);
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
} else {
var entry = cacheEntry.get();
log.debug("[{}] Cache get: {}", requestId, entry);
var result = getter.apply(entry);
if (result == null) {
throw new ThingsboardException(ThingsboardErrorCode.BAD_REQUEST_PARAMS);
@ -362,6 +363,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
.build();
}
@SneakyThrows
@SuppressWarnings({"rawtypes", "unchecked"})
private void importEntities(EntitiesImportCtx ctx, EntityType entityType) {
int limit = 100;
@ -373,9 +375,9 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
log.debug("[{}] Loading {} entities pack ({})", ctx.getTenantId(), entityType, entityDataList.size());
for (EntityExportData entityData : entityDataList) {
EntityExportData reimportBackup = JacksonUtil.clone(entityData);
log.debug("[{}] Loading {} entities", ctx.getTenantId(), entityType);
EntityImportResult<?> importResult;
try {
importResult = exportImportService.importEntity(ctx, entityData);
@ -391,6 +393,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>())
.add(importResult.getSavedEntity().getId());
}
log.debug("Imported {} pack for tenant {}", entityType, ctx.getTenantId());
offset += limit;
} while (entityDataList.size() == limit);
}
@ -456,18 +459,20 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
EntityId externalId = ((ExportableEntity<EntityId>) entity).getExternalId();
if (externalId == null) externalId = entityId;
return transformAsync(gitServiceQueue.getEntity(user.getTenantId(), versionId, externalId),
return transform(gitServiceQueue.getEntity(user.getTenantId(), versionId, externalId),
otherVersion -> {
SimpleEntitiesExportCtx ctx = new SimpleEntitiesExportCtx(user, null, null, EntityExportSettings.builder()
.exportRelations(otherVersion.hasRelations())
.exportAttributes(otherVersion.hasAttributes())
.exportCredentials(otherVersion.hasCredentials())
.build());
EntityExportData<?> currentVersion = exportImportService.exportEntity(ctx, entityId);
return transform(gitServiceQueue.getContentsDiff(user.getTenantId(),
JacksonUtil.toPrettyString(currentVersion.sort()),
JacksonUtil.toPrettyString(otherVersion.sort())),
rawDiff -> new EntityDataDiff(currentVersion, otherVersion, rawDiff), MoreExecutors.directExecutor());
EntityExportData<?> currentVersion;
try {
currentVersion = exportImportService.exportEntity(ctx, entityId);
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
return new EntityDataDiff(currentVersion.sort(), otherVersion.sort());
}, MoreExecutors.directExecutor());
}

134
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -15,7 +15,10 @@
*/
package org.thingsboard.server.service.sync.vc;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import lombok.SneakyThrows;
@ -23,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
@ -71,12 +75,16 @@ import org.thingsboard.server.service.sync.vc.data.VersionsDiffGitRequest;
import org.thingsboard.server.service.sync.vc.data.VoidGitRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -93,9 +101,12 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
private final SchedulerComponent scheduler;
private final Map<UUID, PendingGitRequest<?>> pendingRequestMap = new HashMap<>();
private final Map<UUID, Map<String, String[]>> chunkedMsgs = new ConcurrentHashMap<>();
@Value("${queue.vc.request-timeout:60000}")
private int requestTimeout;
@Value("${queue.vc.msg-chunk-size:500000}")
private int msgChunkSize;
public DefaultGitVersionControlQueueService(TbServiceInfoProvider serviceInfoProvider, TbClusterService clusterService,
DataDecodingEncodingService encodingService,
@ -119,20 +130,35 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
return future;
}
@SuppressWarnings("UnstableApiUsage")
@Override
public ListenableFuture<Void> addToCommit(CommitGitRequest commit, EntityExportData<ExportableEntity<EntityId>> entityData) {
SettableFuture<Void> future = SettableFuture.create();
String path = getRelativePath(entityData.getEntityType(), entityData.getExternalId());
String entityDataJson = JacksonUtil.toPrettyString(entityData.sort());
registerAndSend(commit, builder -> builder.setCommitRequest(
buildCommitRequest(commit).setAddMsg(
TransportProtos.AddMsg.newBuilder()
.setRelativePath(path).setEntityDataJson(entityDataJson).build()
).build()
).build(), wrap(future, null));
return future;
Iterable<String> entityDataChunks = StringUtils.split(entityDataJson, msgChunkSize);
String chunkedMsgId = UUID.randomUUID().toString();
int chunksCount = Iterables.size(entityDataChunks);
AtomicInteger chunkIndex = new AtomicInteger();
List<ListenableFuture<Void>> futures = new ArrayList<>();
entityDataChunks.forEach(chunk -> {
SettableFuture<Void> chunkFuture = SettableFuture.create();
log.trace("[{}] sending chunk {} for 'addToCommit'", chunkedMsgId, chunkIndex.get());
registerAndSend(commit, builder -> builder.setCommitRequest(
buildCommitRequest(commit).setAddMsg(
TransportProtos.AddMsg.newBuilder()
.setRelativePath(path).setEntityDataJsonChunk(chunk)
.setChunkedMsgId(chunkedMsgId).setChunkIndex(chunkIndex.getAndIncrement())
.setChunksCount(chunksCount).build()
).build()
).build(), wrap(chunkFuture, null));
futures.add(chunkFuture);
});
return Futures.transform(Futures.allAsList(futures), r -> {
log.trace("[{}] sent all chunks for 'addToCommit'", chunkedMsgId);
return null;
}, MoreExecutors.directExecutor());
}
@Override
@ -221,7 +247,6 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@Override
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId, EntityType entityType) {
return listEntitiesAtVersion(tenantId, ListEntitiesRequestMsg.newBuilder()
.setBranchName(branch)
.setVersionId(versionId)
.setEntityType(entityType.name())
.build());
@ -230,7 +255,6 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@Override
public ListenableFuture<List<VersionedEntityInfo>> listEntitiesAtVersion(TenantId tenantId, String branch, String versionId) {
return listEntitiesAtVersion(tenantId, ListEntitiesRequestMsg.newBuilder()
.setBranchName(branch)
.setVersionId(versionId)
.build());
}
@ -257,18 +281,11 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
.build()));
}
@Override
public ListenableFuture<String> getContentsDiff(TenantId tenantId, String content1, String content2) {
ContentsDiffGitRequest request = new ContentsDiffGitRequest(tenantId, content1, content2);
return sendRequest(request, builder -> builder.setContentsDiffRequest(TransportProtos.ContentsDiffRequestMsg.newBuilder()
.setContent1(content1)
.setContent2(content2)));
}
@Override
@SuppressWarnings("rawtypes")
public ListenableFuture<EntityExportData> getEntity(TenantId tenantId, String versionId, EntityId entityId) {
EntityContentGitRequest request = new EntityContentGitRequest(tenantId, versionId, entityId);
chunkedMsgs.put(request.getRequestId(), new HashMap<>());
registerAndSend(request, builder -> builder.setEntityContentRequest(EntityContentRequestMsg.newBuilder()
.setVersionId(versionId)
.setEntityType(entityId.getEntityType().name())
@ -290,9 +307,9 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
var requestBody = enrichFunction.apply(newRequestProto(request, settings));
log.trace("[{}][{}] PUSHING request: {}", request.getTenantId(), request.getRequestId(), requestBody);
clusterService.pushMsgToVersionControl(request.getTenantId(), requestBody, callback);
request.setTimeoutTask(scheduler.schedule(() -> {
processTimeout(request.getRequestId());
}, requestTimeout, TimeUnit.MILLISECONDS));
if (request.getTimeoutTask() == null) {
request.setTimeoutTask(scheduler.schedule(() -> processTimeout(request.getRequestId()), requestTimeout, TimeUnit.MILLISECONDS));
}
} else {
throw new RuntimeException("Future is already done!");
}
@ -310,7 +327,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@SuppressWarnings("rawtypes")
public ListenableFuture<List<EntityExportData>> getEntities(TenantId tenantId, String versionId, EntityType entityType, int offset, int limit) {
EntitiesContentGitRequest request = new EntitiesContentGitRequest(tenantId, versionId, entityType);
chunkedMsgs.put(request.getRequestId(), new HashMap<>());
registerAndSend(request, builder -> builder.setEntitiesContentRequest(EntitiesContentRequestMsg.newBuilder()
.setVersionId(versionId)
.setEntityType(entityType.name())
@ -356,15 +373,15 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@Override
public void processResponse(VersionControlResponseMsg vcResponseMsg) {
UUID requestId = new UUID(vcResponseMsg.getRequestIdMSB(), vcResponseMsg.getRequestIdLSB());
PendingGitRequest<?> request = pendingRequestMap.remove(requestId);
PendingGitRequest<?> request = pendingRequestMap.get(requestId);
if (request == null) {
log.debug("[{}] received stale response: {}", requestId, vcResponseMsg);
return;
} else {
log.debug("[{}] processing response: {}", requestId, vcResponseMsg);
request.getTimeoutTask().cancel(true);
}
var future = request.getFuture();
boolean completed = true;
if (!StringUtils.isEmpty(vcResponseMsg.getError())) {
future.setException(new RuntimeException(vcResponseMsg.getError()));
} else {
@ -392,12 +409,28 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
var listVersionsResponse = vcResponseMsg.getListVersionsResponse();
((ListVersionsGitRequest) request).getFuture().set(toPageData(listVersionsResponse));
} else if (vcResponseMsg.hasEntityContentResponse()) {
var data = vcResponseMsg.getEntityContentResponse().getData();
((EntityContentGitRequest) request).getFuture().set(toData(data));
TransportProtos.EntityContentResponseMsg responseMsg = vcResponseMsg.getEntityContentResponse();
log.trace("[{}] received chunk {} for 'getEntity'", responseMsg.getChunkedMsgId(), responseMsg.getChunkIndex());
var joined = joinChunks(requestId, responseMsg, 1);
if (joined.isPresent()) {
log.trace("[{}] collected all chunks for 'getEntity'", responseMsg.getChunkedMsgId());
((EntityContentGitRequest) request).getFuture().set(joined.get().get(0));
} else {
completed = false;
}
} else if (vcResponseMsg.hasEntitiesContentResponse()) {
var dataList = vcResponseMsg.getEntitiesContentResponse().getDataList();
((EntitiesContentGitRequest) request).getFuture()
.set(dataList.stream().map(this::toData).collect(Collectors.toList()));
TransportProtos.EntitiesContentResponseMsg responseMsg = vcResponseMsg.getEntitiesContentResponse();
TransportProtos.EntityContentResponseMsg item = responseMsg.getItem();
if (responseMsg.getItemsCount() > 0) {
var joined = joinChunks(requestId, item, responseMsg.getItemsCount());
if (joined.isPresent()) {
((EntitiesContentGitRequest) request).getFuture().set(joined.get());
} else {
completed = false;
}
} else {
((EntitiesContentGitRequest) request).getFuture().set(Collections.emptyList());
}
} else if (vcResponseMsg.hasVersionsDiffResponse()) {
TransportProtos.VersionsDiffResponseMsg diffResponse = vcResponseMsg.getVersionsDiffResponse();
List<EntityVersionsDiff> entityVersionsDiffList = diffResponse.getDiffList().stream()
@ -412,21 +445,50 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
.build())
.collect(Collectors.toList());
((VersionsDiffGitRequest) request).getFuture().set(entityVersionsDiffList);
} else if (vcResponseMsg.hasContentsDiffResponse()) {
String diff = vcResponseMsg.getContentsDiffResponse().getDiff();
((ContentsDiffGitRequest) request).getFuture().set(diff);
}
}
if (completed) {
removePendingRequest(requestId);
}
}
@SuppressWarnings("rawtypes")
private Optional<List<EntityExportData>> joinChunks(UUID requestId, TransportProtos.EntityContentResponseMsg responseMsg, int expectedMsgCount) {
var chunksMap = chunkedMsgs.get(requestId);
if (chunksMap == null) {
return Optional.empty();
}
String[] msgChunks = chunksMap.computeIfAbsent(responseMsg.getChunkedMsgId(), id -> new String[responseMsg.getChunksCount()]);
msgChunks[responseMsg.getChunkIndex()] = responseMsg.getData();
if (chunksMap.size() == expectedMsgCount && chunksMap.values().stream()
.allMatch(chunks -> CollectionsUtil.countNonNull(chunks) == chunks.length)) {
return Optional.of(chunksMap.values().stream()
.map(chunks -> String.join("", chunks))
.map(this::toData)
.collect(Collectors.toList()));
} else {
return Optional.empty();
}
}
private void processTimeout(UUID requestId) {
PendingGitRequest<?> pendingRequest = pendingRequestMap.remove(requestId);
PendingGitRequest<?> pendingRequest = removePendingRequest(requestId);
if (pendingRequest != null) {
log.debug("[{}] request timed out ({} ms}", requestId, requestTimeout);
pendingRequest.getFuture().setException(new TimeoutException("Request timed out"));
}
}
private PendingGitRequest<?> removePendingRequest(UUID requestId) {
PendingGitRequest<?> pendingRequest = pendingRequestMap.remove(requestId);
if (pendingRequest != null && pendingRequest.getTimeoutTask() != null) {
pendingRequest.getTimeoutTask().cancel(true);
pendingRequest.setTimeoutTask(null);
}
chunkedMsgs.remove(requestId);
return pendingRequest;
}
private PageData<EntityVersion> toPageData(TransportProtos.ListVersionsResponseMsg listVersionsResponse) {
var listVersions = listVersionsResponse.getVersionsList().stream().map(this::getEntityVersion).collect(Collectors.toList());
return new PageData<>(listVersions, listVersionsResponse.getTotalPages(), listVersionsResponse.getTotalElements(), listVersionsResponse.getHasNext());
@ -450,6 +512,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
return JacksonUtil.fromString(data, EntityExportData.class);
}
//The future will be completed when the corresponding result arrives from kafka
private static <T> TbQueueCallback wrap(SettableFuture<T> future) {
return new TbQueueCallback() {
@Override
@ -463,7 +526,8 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
};
}
private static <T> TbQueueCallback wrap(SettableFuture<T> future, T value) {
//The future will be completed when the request is successfully sent to kafka
private <T> TbQueueCallback wrap(SettableFuture<T> future, T value) {
return new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {

2
application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java

@ -64,8 +64,6 @@ public interface GitVersionControlQueueService {
ListenableFuture<List<EntityVersionsDiff>> getVersionsDiff(TenantId tenantId, EntityType entityType, EntityId externalId, String versionId1, String versionId2);
ListenableFuture<String> getContentsDiff(TenantId tenantId, String rawEntityData1, String rawEntityData2);
ListenableFuture<Void> initRepository(TenantId tenantId, RepositorySettings settings);
ListenableFuture<Void> testRepository(TenantId tenantId, RepositorySettings settings);

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

@ -1027,6 +1027,7 @@ queue:
poll-interval: "${TB_QUEUE_VC_INTERVAL_MS:25}"
pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:60000}"
request-timeout: "${TB_QUEUE_VC_REQUEST_TIMEOUT:60000}"
msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:500000}"
js:
# JS Eval request topic
request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}"

2
common/cache/src/main/java/org/thingsboard/server/cache/SimpleTbCacheValueWrapper.java

@ -17,8 +17,10 @@ package org.thingsboard.server.cache;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.springframework.cache.Cache;
@ToString
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class SimpleTbCacheValueWrapper<T> implements TbCacheValueWrapper<T> {

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

@ -715,7 +715,10 @@ message PrepareMsg {
message AddMsg {
string relativePath = 1;
string entityDataJson = 2;
string entityDataJsonChunk = 2;
string chunkedMsgId = 3;
int32 chunkIndex = 4;
int32 chunksCount = 5;
}
message DeleteMsg {
@ -755,9 +758,8 @@ message ListVersionsResponseMsg {
}
message ListEntitiesRequestMsg {
string branchName = 1;
string versionId = 2;
string entityType = 3;
string versionId = 1;
string entityType = 2;
}
message VersionedEntityInfoProto {
@ -791,6 +793,9 @@ message EntityContentRequestMsg {
message EntityContentResponseMsg {
string data = 1;
string chunkedMsgId = 2;
int32 chunkIndex = 3;
int32 chunksCount = 4;
}
message EntitiesContentRequestMsg {
@ -801,7 +806,8 @@ message EntitiesContentRequestMsg {
}
message EntitiesContentResponseMsg {
repeated string data = 1;
EntityContentResponseMsg item = 1;
int32 itemsCount = 2;
}
message VersionsDiffRequestMsg {
@ -823,15 +829,6 @@ message EntityVersionsDiff {
string rawDiff = 6;
}
message ContentsDiffRequestMsg {
string content1 = 1;
string content2 = 2;
}
message ContentsDiffResponseMsg {
string diff = 1;
}
message GenericRepositoryRequestMsg {}
message GenericRepositoryResponseMsg {}
@ -853,7 +850,6 @@ message ToVersionControlServiceMsg {
EntityContentRequestMsg entityContentRequest = 14;
EntitiesContentRequestMsg entitiesContentRequest = 15;
VersionsDiffRequestMsg versionsDiffRequest = 16;
ContentsDiffRequestMsg contentsDiffRequest = 17;
}
message VersionControlResponseMsg {
@ -868,7 +864,6 @@ message VersionControlResponseMsg {
EntityContentResponseMsg entityContentResponse = 9;
EntitiesContentResponseMsg entitiesContentResponse = 10;
VersionsDiffResponseMsg versionsDiffResponse = 11;
ContentsDiffResponseMsg contentsDiffResponse = 12;
}
/**

6
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data;
import com.google.common.base.Splitter;
import static org.apache.commons.lang3.StringUtils.repeat;
public class StringUtils {
@ -92,4 +94,8 @@ public class StringUtils {
return input.substring(0, startIndexInclusive) + obfuscatedPart + input.substring(endIndexExclusive);
}
public static Iterable<String> split(String value, int maxPartSize) {
return Splitter.fixedLength(maxPartSize).split(value);
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataDiff.java

@ -24,5 +24,4 @@ import org.thingsboard.server.common.data.sync.ie.EntityExportData;
public class EntityDataDiff {
private EntityExportData<?> currentVersion;
private EntityExportData<?> otherVersion;
private String rawDiff;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java

@ -20,12 +20,15 @@ import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class EntityLoadError {
public class EntityLoadError implements Serializable {
private static final long serialVersionUID = 7538450180582109391L;
private String type;
private EntityId source;

6
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java

@ -21,11 +21,15 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EntityTypeLoadResult {
public class EntityTypeLoadResult implements Serializable {
private static final long serialVersionUID = -8428039809651395241L;
private EntityType entityType;
private int created;
private int updated;

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionedEntityInfo.java

@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.id.EntityId;
@NoArgsConstructor
public class VersionedEntityInfo {
private EntityId externalId;
// etc..
public VersionedEntityInfo(EntityId externalId) {
this.externalId = externalId;

8
common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java

@ -39,4 +39,12 @@ public class CollectionsUtil {
return isNotEmpty(collection) && collection.contains(element);
}
public static <T> int countNonNull(T[] array) {
int count = 0;
for (T t : array) {
if (t != null) count++;
}
return count;
}
}

68
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.sync.vc;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -28,6 +29,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
@ -89,6 +91,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
@ -123,6 +126,8 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
private long packProcessingTimeout;
@Value("${vc.git.io_pool_size:3}")
private int ioPoolSize;
@Value("${queue.vc.msg-chunk-size:500000}")
private int msgChunkSize;
//We need to manually manage the threads since tasks for particular tenant need to be processed sequentially.
private final List<ListeningExecutorService> ioThreads = new ArrayList<>();
@ -252,8 +257,6 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
handleEntitiesContentRequest(ctx, msg.getEntitiesContentRequest());
} else if (msg.hasVersionsDiffRequest()) {
handleVersionsDiffRequest(ctx, msg.getVersionsDiffRequest());
} else if (msg.hasContentsDiffRequest()) {
handleContentsDiffRequest(ctx, msg.getContentsDiffRequest());
}
}
}
@ -270,19 +273,49 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
String path = getRelativePath(entityType, null);
var ids = vcService.listEntitiesAtVersion(ctx.getTenantId(), request.getVersionId(), path)
.stream().skip(request.getOffset()).limit(request.getLimit()).collect(Collectors.toList());
var response = EntitiesContentResponseMsg.newBuilder();
for (VersionedEntityInfo info : ids) {
var data = vcService.getFileContentAtCommit(ctx.getTenantId(),
getRelativePath(info.getExternalId().getEntityType(), info.getExternalId().getId().toString()), request.getVersionId());
response.addData(data);
if (!ids.isEmpty()) {
for (VersionedEntityInfo info : ids) {
var data = vcService.getFileContentAtCommit(ctx.getTenantId(),
getRelativePath(info.getExternalId().getEntityType(), info.getExternalId().getId().toString()), request.getVersionId());
Iterable<String> dataChunks = StringUtils.split(data, msgChunkSize);
String chunkedMsgId = UUID.randomUUID().toString();
int chunksCount = Iterables.size(dataChunks);
AtomicInteger chunkIndex = new AtomicInteger();
dataChunks.forEach(chunk -> {
EntitiesContentResponseMsg.Builder response = EntitiesContentResponseMsg.newBuilder()
.setItemsCount(ids.size())
.setItem(EntityContentResponseMsg.newBuilder()
.setData(chunk)
.setChunkedMsgId(chunkedMsgId)
.setChunksCount(chunksCount)
.setChunkIndex(chunkIndex.getAndIncrement())
.build());
reply(ctx, Optional.empty(), builder -> builder.setEntitiesContentResponse(response));
});
}
} else {
reply(ctx, Optional.empty(), builder -> builder.setEntitiesContentResponse(
EntitiesContentResponseMsg.newBuilder()
.setItemsCount(0)));
}
reply(ctx, Optional.empty(), builder -> builder.setEntitiesContentResponse(response));
}
private void handleEntityContentRequest(VersionControlRequestCtx ctx, EntityContentRequestMsg request) throws IOException {
String path = getRelativePath(EntityType.valueOf(request.getEntityType()), new UUID(request.getEntityIdMSB(), request.getEntityIdLSB()).toString());
String data = vcService.getFileContentAtCommit(ctx.getTenantId(), path, request.getVersionId());
reply(ctx, Optional.empty(), builder -> builder.setEntityContentResponse(EntityContentResponseMsg.newBuilder().setData(data)));
Iterable<String> dataChunks = StringUtils.split(data, msgChunkSize);
String chunkedMsgId = UUID.randomUUID().toString();
int chunksCount = Iterables.size(dataChunks);
AtomicInteger chunkIndex = new AtomicInteger();
dataChunks.forEach(chunk -> {
log.trace("[{}] sending chunk {} for 'getEntity'", chunkedMsgId, chunkIndex.get());
reply(ctx, Optional.empty(), builder -> builder.setEntityContentResponse(EntityContentResponseMsg.newBuilder()
.setData(chunk).setChunkedMsgId(chunkedMsgId).setChunksCount(chunksCount)
.setChunkIndex(chunkIndex.getAndIncrement())));
});
}
private void handleListVersions(VersionControlRequestCtx ctx, ListVersionsRequestMsg request) throws Exception {
@ -359,12 +392,6 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
.addAllDiff(diffList)));
}
private void handleContentsDiffRequest(VersionControlRequestCtx ctx, TransportProtos.ContentsDiffRequestMsg request) throws IOException {
String diff = vcService.getContentsDiff(ctx.getTenantId(), request.getContent1(), request.getContent2());
reply(ctx, builder -> builder.setContentsDiffResponse(TransportProtos.ContentsDiffResponseMsg.newBuilder()
.setDiff(diff)));
}
private void handleCommitRequest(VersionControlRequestCtx ctx, CommitRequestMsg request) throws Exception {
var tenantId = ctx.getTenantId();
UUID txId = UUID.fromString(request.getTxId());
@ -416,7 +443,16 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
}
private void addToCommit(VersionControlRequestCtx ctx, PendingCommit commit, AddMsg addMsg) throws IOException {
vcService.add(commit, addMsg.getRelativePath(), addMsg.getEntityDataJson());
log.trace("[{}] received chunk {} for 'addToCommit'", addMsg.getChunkedMsgId(), addMsg.getChunkIndex());
Map<String, String[]> chunkedMsgs = commit.getChunkedMsgs();
String[] msgChunks = chunkedMsgs.computeIfAbsent(addMsg.getChunkedMsgId(), id -> new String[addMsg.getChunksCount()]);
msgChunks[addMsg.getChunkIndex()] = addMsg.getEntityDataJsonChunk();
if (CollectionsUtil.countNonNull(msgChunks) == msgChunks.length) {
log.trace("[{}] collected all chunks for 'addToCommit'", addMsg.getChunkedMsgId());
String entityDataJson = String.join("", msgChunks);
chunkedMsgs.remove(addMsg.getChunkedMsgId());
vcService.add(commit, addMsg.getRelativePath(), entityDataJson);
}
}
private void doAbortCurrentCommit(TenantId tenantId, PendingCommit current) {

13
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java

@ -18,7 +18,9 @@ package org.thingsboard.server.service.sync.vc;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Data
public class PendingCommit {
@ -31,9 +33,10 @@ public class PendingCommit {
private String versionName;
private String authorName;
private String authorEmail;
private Map<String, String[]> chunkedMsgs;
public PendingCommit(TenantId tenantId, String nodeId, UUID txId, String branch, String versionName, String authorName, String authorEmail) {
this.tenantId = tenantId;
this.nodeId = nodeId;
@ -44,4 +47,12 @@ public class PendingCommit {
this.authorEmail = authorEmail;
this.workingBranch = txId.toString();
}
public Map<String, String[]> getChunkedMsgs() {
if (chunkedMsgs == null) {
chunkedMsgs = new ConcurrentHashMap<>();
}
return chunkedMsgs;
}
}

8
docker/.gitignore

@ -6,4 +6,12 @@ tb-node/postgres/**
tb-node/cassandra/**
tb-transports/*/log
tb-vc-executor/log/**
tb-node/redis-cluster-data-0/**
tb-node/redis-cluster-data-1/**
tb-node/redis-cluster-data-2/**
tb-node/redis-cluster-data-3/**
tb-node/redis-cluster-data-4/**
tb-node/redis-cluster-data-5/**
tb-node/redis-data/**
!.env

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

@ -168,6 +168,7 @@ queue:
partitions: "${TB_QUEUE_VC_PARTITIONS:10}"
poll-interval: "${TB_QUEUE_VC_INTERVAL_MS:25}"
pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:60000}"
msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:500000}"
vc:
# Pool size for handling export tasks

1
ui-ngx/src/app/shared/models/vc.models.ts

@ -228,7 +228,6 @@ export interface RuleChainExportData extends EntityExportData<RuleChain> {
export interface EntityDataDiff {
currentVersion: EntityExportData<any>;
otherVersion: EntityExportData<any>;
rawDiff: string;
}
export function entityExportDataToJsonString(data: EntityExportData<any>): string {

Loading…
Cancel
Save