From 769428c42e2099f4e64b784d0d46faa29f8d5adc Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 09:51:18 +0200 Subject: [PATCH 1/9] Fix edge import --- ui/src/app/import-export/import-export.service.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 17457ae8d6..dc0fbf1ea7 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -614,6 +614,16 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, } ); return deferred.promise; + case types.entityType.edge: + openImportDialogCSV($event, entityType, 'edge.import', 'edge.edge-file').then( + function success() { + deferred.resolve(); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; } } From 720c89f1097394ba4c2bd3f362a6e3f6bb60cd7c Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 09:53:16 +0200 Subject: [PATCH 2/9] Fix edge import locale --- ui/src/app/locale/locale.constant-en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 5ec07cf6cf..e97afff89f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -765,6 +765,7 @@ "edge": { "edge": "Edge", "edges": "Edges", + "edge-file": "Edge file", "management": "Edge management", "no-edges-matching": "No edges matching '{{entity}}' were found.", "add": "Add Edge", From 6cdb54970435156281e3cdd74195aa08e0977401 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 11:06:39 +0200 Subject: [PATCH 3/9] Added ConstraintViolationException for saveEdge --- .../server/dao/edge/EdgeServiceImpl.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 07059da06e..e23c19be19 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -25,6 +25,7 @@ import org.apache.http.HttpHost; import org.apache.http.conn.ssl.DefaultHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.Cache; @@ -172,6 +173,21 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic public Edge saveEdge(Edge edge) { log.trace("Executing saveEdge [{}]", edge); edgeValidator.validate(edge, Edge::getTenantId); + Edge savedEdge; + if (!sqlDatabaseUsed) { + savedEdge = edgeDao.save(edge.getTenantId(), edge); + } else { + try { + savedEdge = edgeDao.save(edge.getTenantId(), edge); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("edge_name_unq_key")) { + throw new DataValidationException("Edge with such name already exists!"); + } else { + throw t; + } + } + } return edgeDao.save(edge.getTenantId(), edge); } From 1178e5779a139cd6566f432779c205cb84c3f35f Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 11:15:37 +0200 Subject: [PATCH 4/9] Fix for ConstraintViolationException for saveEdge --- .../java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index e23c19be19..db0da5016e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -188,7 +188,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic } } } - return edgeDao.save(edge.getTenantId(), edge); + return savedEdge; } @Override From cb0201e87a27a805feab273e9950784f7b79e28c Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 13:22:24 +0200 Subject: [PATCH 5/9] Delete UI filtering for getEdge assets/entity-views/devices --- ui/src/app/api/asset.service.js | 8 +------- ui/src/app/api/device.service.js | 8 +------- ui/src/app/api/entity-view.service.js | 8 +------- ui/src/app/asset/asset.controller.js | 4 ++-- ui/src/app/device/device.controller.js | 4 ++-- ui/src/app/entity-view/entity-view.controller.js | 4 ++-- 6 files changed, 9 insertions(+), 27 deletions(-) diff --git a/ui/src/app/api/asset.service.js b/ui/src/app/api/asset.service.js index 86d06670c9..090682acc3 100644 --- a/ui/src/app/api/asset.service.js +++ b/ui/src/app/api/asset.service.js @@ -322,19 +322,13 @@ function AssetService($http, $q, $filter, customerService, userService) { return deferred.promise; } - function getEdgeAssets(edgeId, pageLink, config, type) { + function getEdgeAssets(edgeId, pageLink, config) { var deferred = $q.defer(); var url = '/api/edge/' + edgeId + '/assets?limit=' + pageLink.limit; if (angular.isDefined(pageLink.idOffset)) { url += '&offset=' + pageLink.idOffset; } $http.get(url, config).then(function success(response) { - if (pageLink.textSearch) { - response.data.data = $filter('filter')(response.data.data, {name: pageLink.textSearch}); - } - if (angular.isDefined(type) && type.length) { - response.data.data = $filter('filter')(response.data.data, {type: type}); - } deferred.resolve(response.data); }, function fail() { deferred.reject(); diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js index 9cc4412e86..8d6d7cd585 100644 --- a/ui/src/app/api/device.service.js +++ b/ui/src/app/api/device.service.js @@ -384,19 +384,13 @@ function DeviceService($http, $q, $window, $filter, userService, attributeServic return deferred.promise; } - function getEdgeDevices(edgeId, pageLink, config, type) { + function getEdgeDevices(edgeId, pageLink, config) { var deferred = $q.defer(); var url = '/api/edge/' + edgeId + '/devices?limit=' + pageLink.limit; if (angular.isDefined(pageLink.idOffset)) { url += '&offset=' + pageLink.idOffset; } $http.get(url, config).then(function success(response) { - if (pageLink.textSearch) { - response.data.data = $filter('filter')(response.data.data, {name: pageLink.textSearch}); - } - if (angular.isDefined(type) && type.length) { - response.data.data = $filter('filter')(response.data.data, {type: type}); - } deferred.resolve(response.data); }, function fail() { deferred.reject(); diff --git a/ui/src/app/api/entity-view.service.js b/ui/src/app/api/entity-view.service.js index cef08001cd..434f41dda4 100644 --- a/ui/src/app/api/entity-view.service.js +++ b/ui/src/app/api/entity-view.service.js @@ -245,19 +245,13 @@ function EntityViewService($http, $q, $window, $filter, userService, attributeSe return deferred.promise; } - function getEdgeEntityViews(edgeId, pageLink, config, type) { + function getEdgeEntityViews(edgeId, pageLink, config) { var deferred = $q.defer(); var url = '/api/edge/' + edgeId + '/entityViews?limit=' + pageLink.limit; if (angular.isDefined(pageLink.idOffset)) { url += '&offset=' + pageLink.idOffset; } $http.get(url, config).then(function success(response) { - if (pageLink.textSearch) { - response.data.data = $filter('filter')(response.data.data, {name: pageLink.textSearch}); - } - if (angular.isDefined(type) && type.length) { - response.data.data = $filter('filter')(response.data.data, {type: type}); - } deferred.resolve(response.data); }, function fail() { deferred.reject(); diff --git a/ui/src/app/asset/asset.controller.js b/ui/src/app/asset/asset.controller.js index 105ef17dbd..1be789b233 100644 --- a/ui/src/app/asset/asset.controller.js +++ b/ui/src/app/asset/asset.controller.js @@ -324,8 +324,8 @@ export function AssetController($rootScope, userService, assetService, customerS vm.assetGridConfig.addItemActions = []; } else if (vm.assetsScope === 'edge') { - fetchAssetsFunction = function (pageLink, assetType) { - return assetService.getEdgeAssets(edgeId, pageLink, null, assetType); + fetchAssetsFunction = function (pageLink) { + return assetService.getEdgeAssets(edgeId, pageLink, null); }; deleteAssetFunction = function (assetId) { return assetService.unassignAssetFromEdge(edgeId, assetId); diff --git a/ui/src/app/device/device.controller.js b/ui/src/app/device/device.controller.js index e15dd14678..a3e24f0574 100644 --- a/ui/src/app/device/device.controller.js +++ b/ui/src/app/device/device.controller.js @@ -357,8 +357,8 @@ export function DeviceController($rootScope, userService, deviceService, custome vm.deviceGridConfig.addItemActions = []; } else if (vm.devicesScope === 'edge') { - fetchDevicesFunction = function (pageLink, deviceType) { - return deviceService.getEdgeDevices(edgeId, pageLink, null, deviceType); + fetchDevicesFunction = function (pageLink) { + return deviceService.getEdgeDevices(edgeId, pageLink, null); }; deleteDeviceFunction = function (deviceId) { return deviceService.unassignDeviceFromEdge(edgeId, deviceId); diff --git a/ui/src/app/entity-view/entity-view.controller.js b/ui/src/app/entity-view/entity-view.controller.js index d1ce45dc96..c81f95b0c0 100644 --- a/ui/src/app/entity-view/entity-view.controller.js +++ b/ui/src/app/entity-view/entity-view.controller.js @@ -285,8 +285,8 @@ export function EntityViewController($rootScope, userService, entityViewService, vm.entityViewGridConfig.addItemAction = {}; } } else if (vm.entityViewsScope === 'edge') { - fetchEntityViewsFunction = function (pageLink, entityViewType) { - return entityViewService.getEdgeEntityViews(edgeId, pageLink, null, entityViewType); + fetchEntityViewsFunction = function (pageLink) { + return entityViewService.getEdgeEntityViews(edgeId, pageLink, null); }; deleteEntityViewFunction = function (entityViewId) { return entityViewService.unassignEntityViewFromEdge(edgeId, entityViewId); From 1463de7c019cf1812f1a9b57a9f2ad8cd76672fa Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 13:25:27 +0200 Subject: [PATCH 6/9] Delete UI filtering for getEdgeDashboards --- ui/src/app/api/dashboard.service.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js index 9d59e24ebc..0011a81b1e 100644 --- a/ui/src/app/api/dashboard.service.js +++ b/ui/src/app/api/dashboard.service.js @@ -302,9 +302,6 @@ function DashboardService($rootScope, $http, $q, $location, $filter) { } $http.get(url, config).then(function success(response) { response.data = prepareDashboards(response.data); - if (pageLink.textSearch) { - response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch}); - } deferred.resolve(response.data); }, function fail() { deferred.reject(); From 00caacf6c2bc4f91ba10ac228c146d02c89b508e Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 13:29:17 +0200 Subject: [PATCH 7/9] Delete UI filtering for getEdgeRuleChains --- ui/src/app/api/rule-chain.service.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index af9cfe436a..ba91e9e748 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -321,9 +321,6 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co url += '&offset=' + pageLink.idOffset; } $http.get(url, config).then(function success(response) { - if (pageLink.textSearch) { - response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch}); - } deferred.resolve(response.data); }, function fail() { deferred.reject(); From 05771cd900427f4bbedac9feb8b8d83a7d152084 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Tue, 15 Dec 2020 15:29:10 +0200 Subject: [PATCH 8/9] getEdge, getTenantEdges, getCustomerEdges refactored --- ui/src/app/api/edge.service.js | 34 +++++++++++++------ ui/src/app/api/entity.service.js | 11 +++--- ui/src/app/edge/edge.controller.js | 4 +-- ui/src/app/rulechain/rulechains.controller.js | 2 +- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/ui/src/app/api/edge.service.js b/ui/src/app/api/edge.service.js index 528dea960c..8cd7aa81c1 100644 --- a/ui/src/app/api/edge.service.js +++ b/ui/src/app/api/edge.service.js @@ -30,6 +30,7 @@ function EdgeService($http, $q, customerService) { getTenantEdges: getTenantEdges, getCustomerEdges: getCustomerEdges, assignEdgeToCustomer: assignEdgeToCustomer, + findByQuery: findByQuery, unassignEdgeFromCustomer: unassignEdgeFromCustomer, makeEdgePublic: makeEdgePublic, setRootRuleChain: setRootRuleChain, @@ -61,13 +62,7 @@ function EdgeService($http, $q, customerService) { function getEdgesByIds(edgeIds, config) { var deferred = $q.defer(); - var ids = ''; - for (var i=0;i0) { - ids += ','; - } - ids += edgeIds[i]; - } + var ids = edgeIds.join(','); var url = '/api/edges?edgeIds=' + ids; $http.get(url, config).then(function success(response) { var entities = response.data; @@ -85,9 +80,13 @@ function EdgeService($http, $q, customerService) { return deferred.promise; } - function getEdge(edgeId, config) { + function getEdge(edgeId, ignoreErrors, config) { var deferred = $q.defer(); var url = '/api/edge/' + edgeId; + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail(response) { @@ -129,7 +128,7 @@ function EdgeService($http, $q, customerService) { return deferred.promise; } - function getTenantEdges(pageLink, applyCustomersInfo, config, type) { + function getTenantEdges(pageLink, applyCustomersInfo, type, config) { var deferred = $q.defer(); var url = '/api/tenant/edges?limit=' + pageLink.limit; if (angular.isDefined(pageLink.textSearch)) { @@ -164,7 +163,7 @@ function EdgeService($http, $q, customerService) { return deferred.promise; } - function getCustomerEdges(customerId, pageLink, applyCustomersInfo, config, type) { + function getCustomerEdges(customerId, pageLink, applyCustomersInfo, type, config) { var deferred = $q.defer(); var url = '/api/customer/' + customerId + '/edges?limit=' + pageLink.limit; if (angular.isDefined(pageLink.textSearch)) { @@ -200,6 +199,21 @@ function EdgeService($http, $q, customerService) { return deferred.promise; } + function findByQuery(query, ignoreErrors, config) { + var deferred = $q.defer(); + var url = '/api/edges'; + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); + $http.post(url, query, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + function assignEdgeToCustomer(customerId, edgeId) { var deferred = $q.defer(); var url = '/api/customer/' + customerId + '/edge/' + edgeId; diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index ed479ae44d..32fac7b5db 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -78,7 +78,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device $log.error('Get Alarm Entity is not implemented!'); break; case types.entityType.edge: - promise = edgeService.getEdge(entityId, config); + promise = edgeService.getEdge(entityId, true, config); break; } return promise; @@ -164,7 +164,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device break; case types.entityType.edge: promise = getEntitiesByIdsPromise( - (id) => edgeService.getEdge(id, config), entityIds); + (id) => edgeService.getEdge(id, true, config), entityIds); break; } return promise; @@ -294,9 +294,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device break; case types.entityType.edge: if (user.authority === 'CUSTOMER_USER') { - promise = edgeService.getCustomerEdges(customerId, pageLink, false, config, subType); + promise = edgeService.getCustomerEdges(customerId, pageLink, false, subType, config); } else { - promise = edgeService.getTenantEdges(pageLink, false, config, subType); + promise = edgeService.getTenantEdges(pageLink, false, subType, config); } break; } @@ -960,9 +960,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device break; case types.entityType.entityView: case types.entityType.edge: - entityFieldKeys.push(types.entityField.name.keyName); - entityFieldKeys.push(types.entityField.type.keyName); - break; case types.entityType.device: case types.entityType.asset: entityFieldKeys.push(types.entityField.name.keyName); diff --git a/ui/src/app/edge/edge.controller.js b/ui/src/app/edge/edge.controller.js index 76b78960a1..0c9d42c584 100644 --- a/ui/src/app/edge/edge.controller.js +++ b/ui/src/app/edge/edge.controller.js @@ -161,7 +161,7 @@ export function EdgeController($rootScope, userService, edgeService, customerSer if (vm.edgesScope === 'tenant') { fetchEdgesFunction = function (pageLink, edgeType) { - return edgeService.getTenantEdges(pageLink, true, null, edgeType); + return edgeService.getTenantEdges(pageLink, true, edgeType, null); }; deleteEdgeFunction = function (edgeId) { return edgeService.deleteEdge(edgeId); @@ -337,7 +337,7 @@ export function EdgeController($rootScope, userService, edgeService, customerSer } else if (vm.edgesScope === 'customer' || vm.edgesScope === 'customer_user') { fetchEdgesFunction = function (pageLink, edgeType) { - return edgeService.getCustomerEdges(customerId, pageLink, true, null, edgeType); + return edgeService.getCustomerEdges(customerId, pageLink, true, edgeType, null); }; deleteEdgeFunction = function (edgeId) { return edgeService.unassignEdgeFromCustomer(edgeId); diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index 6e3908b0a7..65eb62abbe 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -107,7 +107,7 @@ export default function RuleChainsController(ruleChainService, userService, impo var deleteRuleChainFunction = null; if (edgeId) { - edgeService.getEdge(edgeId).then( + edgeService.getEdge(edgeId, true, null).then( function success(edge) { vm.edge = edge; } From 59c55fb2c59c4005c0a6152aba3dba3b9d4e9854 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 15 Dec 2020 18:32:35 +0200 Subject: [PATCH 9/9] For devices from edge new id generated every time --- .../service/edge/rpc/EdgeGrpcSession.java | 16 ++- .../rpc/constructor/DeviceMsgConstructor.java | 5 +- .../edge/rpc/processor/BaseProcessor.java | 4 + .../edge/rpc/processor/DeviceProcessor.java | 99 +++++++++++-------- .../thingsboard/server/edge/BaseEdgeTest.java | 56 ++++++++++- .../common/data/edge/EdgeEventActionType.java | 2 +- common/edge-api/src/main/proto/edge.proto | 3 +- .../thingsboard/rest/client/RestClient.java | 19 +++- 8 files changed, 150 insertions(+), 54 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index f4d2a6a3b2..fe44d51d2f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc; import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -335,8 +336,8 @@ public final class EdgeGrpcSession implements Closeable { case CREDENTIALS_REQUEST: downlinkMsg = processCredentialsRequestMessage(edgeEvent); break; - case ENTITY_EXISTS_REQUEST: - downlinkMsg = processEntityExistsRequestMessage(edgeEvent); + case ENTITY_MERGE_REQUEST: + downlinkMsg = processEntityMergeRequestMessage(edgeEvent); break; case RPC_CALL: downlinkMsg = processRpcCallMsg(edgeEvent); @@ -352,13 +353,18 @@ public final class EdgeGrpcSession implements Closeable { return result; } - private DownlinkMsg processEntityExistsRequestMessage(EdgeEvent edgeEvent) { + private DownlinkMsg processEntityMergeRequestMessage(EdgeEvent edgeEvent) { DownlinkMsg downlinkMsg = null; if (EdgeEventType.DEVICE.equals(edgeEvent.getType())) { DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); Device device = ctx.getDeviceService().findDeviceById(edge.getTenantId(), deviceId); CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(device); - DeviceUpdateMsg d = ctx.getDeviceMsgConstructor().constructDeviceUpdatedMsg(UpdateMsgType.DEVICE_CONFLICT_RPC_MESSAGE, device, customerId); + String conflictName = null; + if(edgeEvent.getBody() != null) { + conflictName = edgeEvent.getBody().get("conflictName").asText(); + } + DeviceUpdateMsg d = ctx.getDeviceMsgConstructor() + .constructDeviceUpdatedMsg(UpdateMsgType.ENTITY_MERGE_RPC_MESSAGE, device, customerId, conflictName); downlinkMsg = DownlinkMsg.newBuilder() .addAllDeviceUpdateMsg(Collections.singletonList(d)) .build(); @@ -497,7 +503,7 @@ public final class EdgeGrpcSession implements Closeable { if (device != null) { CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(device); DeviceUpdateMsg deviceUpdateMsg = - ctx.getDeviceMsgConstructor().constructDeviceUpdatedMsg(msgType, device, customerId); + ctx.getDeviceMsgConstructor().constructDeviceUpdatedMsg(msgType, device, customerId, null); downlinkMsg = DownlinkMsg.newBuilder() .addAllDeviceUpdateMsg(Collections.singletonList(deviceUpdateMsg)) .build(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java index 1f5b360d5b..ad52779a4b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java @@ -38,7 +38,7 @@ public class DeviceMsgConstructor { protected static final ObjectMapper mapper = new ObjectMapper(); - public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device, CustomerId customerId) { + public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device, CustomerId customerId, String conflictName) { DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(device.getId().getId().getMostSignificantBits()) @@ -55,6 +55,9 @@ public class DeviceMsgConstructor { if (device.getAdditionalInfo() != null) { builder.setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); } + if (conflictName != null) { + builder.setConflictName(conflictName); + } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseProcessor.java index 2bdcd136ce..d1236f610d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseProcessor.java @@ -37,6 +37,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeEventService; +import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.user.UserService; @@ -64,6 +65,9 @@ public abstract class BaseProcessor { @Autowired protected EntityViewService entityViewService; + @Autowired + protected EdgeService edgeService; + @Autowired protected CustomerService customerService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java index e503bed6b8..9aab6ec124 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java @@ -17,12 +17,15 @@ package org.thingsboard.server.service.edge.rpc.processor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.DataConstants; @@ -52,6 +55,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; +import java.util.List; import java.util.UUID; import java.util.concurrent.locks.ReentrantLock; @@ -64,36 +68,62 @@ public class DeviceProcessor extends BaseProcessor { public ListenableFuture onDeviceUpdate(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { log.trace("[{}] onDeviceUpdate [{}] from edge [{}]", tenantId, deviceUpdateMsg, edge.getName()); - DeviceId edgeDeviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); switch (deviceUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: String deviceName = deviceUpdateMsg.getName(); Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); if (device != null) { - log.info("[{}] Device with name '{}' already exists on the cloud. Updating id of device entity on the edge", tenantId, deviceName); - if (!device.getId().equals(edgeDeviceId)) { - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_EXISTS_REQUEST, device.getId(), null); - } + ListenableFuture> future = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId()); + SettableFuture futureToSet = SettableFuture.create(); + Futures.addCallback(future, new FutureCallback>() { + @Override + public void onSuccess(@Nullable List edgeIds) { + boolean update = false; + if (edgeIds != null && !edgeIds.isEmpty()) { + if (edgeIds.contains(edge.getId())) { + update = true; + } + } + Device device; + if (update) { + log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + + "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); + updateDevice(tenantId, edge, deviceUpdateMsg); + } else { + log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." + + "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg); + String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15); + device = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); + ObjectNode body = mapper.createObjectNode(); + body.put("conflictName", deviceName); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, device.getId(), body); + deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); + } + futureToSet.set(null); + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}] Failed to get related edge ids by device id [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId(), t); + futureToSet.setException(t); + } + }, dbCallbackExecutorService); + return futureToSet; } else { - Device deviceById = deviceService.findDeviceById(edge.getTenantId(), edgeDeviceId); - if (deviceById != null) { - log.info("[{}] Device ID [{}] already used by other device on the cloud. Creating new device and replacing device entity on the edge", tenantId, edgeDeviceId.getId()); - device = createDevice(tenantId, edge, deviceUpdateMsg); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_EXISTS_REQUEST, device.getId(), null); - } else { - device = createDevice(tenantId, edge, deviceUpdateMsg); - } + log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); + device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, device.getId(), null); + deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } - // TODO: voba - assign device only in case device is not assigned yet. Missing functionality to check this relation prior assignment - deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); break; case ENTITY_UPDATED_RPC_MESSAGE: updateDevice(tenantId, edge, deviceUpdateMsg); break; case ENTITY_DELETED_RPC_MESSAGE: - Device deviceToDelete = deviceService.findDeviceById(tenantId, edgeDeviceId); + DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); + Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId); if (deviceToDelete != null) { - deviceService.unassignDeviceFromEdge(tenantId, edgeDeviceId, edge.getId()); + deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); } break; case UNRECOGNIZED: @@ -103,7 +133,6 @@ public class DeviceProcessor extends BaseProcessor { return Futures.immediateFuture(null); } - public ListenableFuture onDeviceCredentialsUpdate(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg); DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB())); @@ -131,36 +160,34 @@ public class DeviceProcessor extends BaseProcessor { private void updateDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); Device device = deviceService.findDeviceById(tenantId, deviceId); - device.setName(deviceUpdateMsg.getName()); - device.setType(deviceUpdateMsg.getType()); - device.setLabel(deviceUpdateMsg.getLabel()); - device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); - deviceService.saveDevice(device); - - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); + if (device != null) { + device.setName(deviceUpdateMsg.getName()); + device.setType(deviceUpdateMsg.getType()); + device.setLabel(deviceUpdateMsg.getLabel()); + device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); + deviceService.saveDevice(device); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); + } else { + log.warn("[{}] can't find device [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId()); + } } - private Device createDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { + private Device createDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg, String deviceName) { Device device; try { deviceCreationLock.lock(); log.debug("[{}] Creating device entity [{}] from edge [{}]", tenantId, deviceUpdateMsg, edge.getName()); - DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); device = new Device(); device.setTenantId(edge.getTenantId()); device.setCustomerId(edge.getCustomerId()); - device.setId(deviceId); - device.setName(deviceUpdateMsg.getName()); + device.setName(deviceName); device.setType(deviceUpdateMsg.getType()); device.setLabel(deviceUpdateMsg.getLabel()); device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); device = deviceService.saveDevice(device); - createDeviceCredentials(device); createRelationFromEdge(tenantId, edge.getId(), device.getId()); deviceStateService.onDeviceAdded(device); pushDeviceCreatedEventToRuleEngine(tenantId, edge, device); - - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); } finally { deviceCreationLock.unlock(); } @@ -176,14 +203,6 @@ public class DeviceProcessor extends BaseProcessor { relationService.saveRelation(tenantId, relation); } - private void createDeviceCredentials(Device device) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setDeviceId(device.getId()); - deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(20)); - deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); - } - private void pushDeviceCreatedEventToRuleEngine(TenantId tenantId, Edge edge, Device device) { try { DeviceId deviceId = device.getId(); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 776493a176..380d9bafaa 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -26,6 +26,7 @@ import com.google.protobuf.AbstractMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -48,6 +49,7 @@ 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.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -964,6 +966,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testSendMessagesToCloud() throws Exception { log.info("Sending messages to cloud"); sendDevice(); + sendDeviceWithNameThatAlreadyExistsOnCloud(); sendRelationRequest(); sendAlarm(); sendTelemetry(); @@ -992,17 +995,66 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); edgeImitator.expectResponsesAmount(1); - + edgeImitator.expectMessageAmount(1); testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + edgeImitator.waitForResponses(); + edgeImitator.waitForMessages(); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals("Edge Device 2", latestDeviceUpdateMsg.getName()); + + UUID newDeviceId = new UUID(latestDeviceUpdateMsg.getIdMSB(), latestDeviceUpdateMsg.getIdLSB()); - Device device = doGet("/api/device/" + uuid.toString(), Device.class); + Device device = doGet("/api/device/" + newDeviceId, Device.class); Assert.assertNotNull(device); Assert.assertEquals("Edge Device 2", device.getName()); } + private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception { + String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15); + Device deviceOnCloud = saveDevice(deviceOnCloudName); + + UUID uuid = UUIDs.timeBased(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + deviceUpdateMsgBuilder.setName(deviceOnCloudName); + deviceUpdateMsgBuilder.setType("test"); + deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder); + uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + edgeImitator.waitForResponses(); + edgeImitator.waitForMessages(); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName()); + Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName()); + + UUID newDeviceId = new UUID(latestDeviceUpdateMsg.getIdMSB(), latestDeviceUpdateMsg.getIdLSB()); + + Assert.assertNotEquals(deviceOnCloud.getId().getId(), newDeviceId); + + Device device = doGet("/api/device/" + newDeviceId, Device.class); + Assert.assertNotNull(device); + Assert.assertNotEquals(deviceOnCloudName, device.getName()); + } + private void sendRelationRequest() throws Exception { Device device = findDeviceByName("Edge Device 1"); Asset asset = findAssetByName("Edge Asset 1"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java index b8b386ac1a..427ef8a46b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java @@ -34,5 +34,5 @@ public enum EdgeEventActionType { ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, CREDENTIALS_REQUEST, - ENTITY_EXISTS_REQUEST + ENTITY_MERGE_REQUEST } \ No newline at end of file diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index d14215d848..bd23f4868d 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -97,7 +97,7 @@ enum UpdateMsgType { ENTITY_DELETED_RPC_MESSAGE = 2; ALARM_ACK_RPC_MESSAGE = 3; ALARM_CLEAR_RPC_MESSAGE = 4; - DEVICE_CONFLICT_RPC_MESSAGE = 5; + ENTITY_MERGE_RPC_MESSAGE = 5; } message EntityDataProto { @@ -182,6 +182,7 @@ message DeviceUpdateMsg { string type = 7; string label = 8; string additionalInfo = 9; + string conflictName = 10; } message DeviceCredentialsUpdateMsg { diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index ee62c0808e..9529a67052 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -87,6 +87,7 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.model.SecuritySettings; @@ -97,6 +98,7 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle; import java.io.Closeable; import java.io.IOException; import java.net.URI; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -626,22 +628,31 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public List getComponentDescriptorsByType(ComponentType componentType) { + return getComponentDescriptorsByType(componentType, RuleChainType.CORE); + } + + public List getComponentDescriptorsByType(ComponentType componentType, RuleChainType ruleChainType) { return restTemplate.exchange( - baseURL + "/api/components?componentType={componentType}", + baseURL + "/api/components/" + componentType.name() + "/?ruleChainType={ruleChainType}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - componentType).getBody(); + ruleChainType).getBody(); } public List getComponentDescriptorsByTypes(List componentTypes) { + return getComponentDescriptorsByTypes(componentTypes, RuleChainType.CORE); + } + + public List getComponentDescriptorsByTypes(List componentTypes, RuleChainType ruleChainType) { return restTemplate.exchange( - baseURL + "/api/components?componentTypes={componentTypes}", + baseURL + "/api/components?componentTypes={componentTypes}&ruleChainType={ruleChainType}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - listEnumToString(componentTypes)) + listEnumToString(componentTypes), + ruleChainType) .getBody(); }