diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b0f0e0867f..582af82f71 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -104,6 +104,8 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.EdgeNotificationService; +import org.thingsboard.server.service.edge.rpc.EdgeGrpcService; +import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; @@ -202,12 +204,18 @@ public abstract class BaseController { @Autowired protected TbQueueProducerProvider producerProvider; - @Autowired + @Autowired(required = false) protected EdgeService edgeService; - @Autowired + @Autowired(required = false) protected EdgeNotificationService edgeNotificationService; + @Autowired(required = false) + protected SyncEdgeService syncEdgeService; + + @Autowired(required = false) + protected EdgeGrpcService edgeGrpcService; + @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 4c6be42064..be03c034e9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -46,6 +46,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.EdgeGrpcSession; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -409,6 +410,23 @@ public class EdgeController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/edge/sync", method = RequestMethod.POST) + public void syncEdge(@RequestBody EdgeId edgeId) throws ThingsboardException { + try { + edgeId = checkNotNull(edgeId); + if (isEdgesSupportEnabled()) { + EdgeGrpcSession session = edgeGrpcService.getEdgeGrpcSessionById(edgeId); + Edge edge = session.getEdge(); + syncEdgeService.sync(edge); + } else { + throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); + } + } catch (Exception e) { + throw handleException(e); + } + } + @RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST) @ResponseBody public Object checkInstance(@RequestBody Object request) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 85ea93bed3..936a805ea0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -146,6 +146,15 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, System.currentTimeMillis()); } + public EdgeGrpcSession getEdgeGrpcSessionById(EdgeId edgeId) { + EdgeGrpcSession session = sessions.get(edgeId); + if (session != null && session.isConnected()) { + return session; + } else { + throw new RuntimeException("Edge is not connected"); + } + } + private void processHandleMessages() { executor.submit(() -> { while (!Thread.interrupted()) { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index 165f8cecdb..9e1bf1b609 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -24,9 +24,11 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -34,6 +36,10 @@ import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.edge.imitator.EdgeImitator; +import org.thingsboard.server.gen.edge.AssetUpdateMsg; +import org.thingsboard.server.gen.edge.DeviceUpdateMsg; +import org.thingsboard.server.gen.edge.RuleChainUpdateMsg; import java.util.ArrayList; import java.util.Collections; @@ -641,4 +647,54 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(0, pageData.getData().size()); } + @Test + public void testSyncEdge() throws Exception { + Edge edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); + + Device device = new Device(); + device.setName("Edge Device 1"); + device.setType("test"); + Device savedDevice = doPost("/api/device", device, Device.class); + doPost("/api/edge/" + edge.getId().getId().toString() + + "/device/" + savedDevice.getId().getId().toString(), Device.class); + + Asset asset = new Asset(); + asset.setName("Edge Asset 1"); + asset.setType("test"); + Asset savedAsset = doPost("/api/asset", asset, Asset.class); + doPost("/api/edge/" + edge.getId().getId().toString() + + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + + EdgeImitator edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); + // should be 3, but 3 events from sync service + 3 from controller. will be fixed in next releases + edgeImitator.expectMessageAmount(6); + edgeImitator.connect(); + edgeImitator.waitForMessages(); + + Assert.assertEquals(6, edgeImitator.getDownlinkMsgs().size()); + Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent()); + Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent()); + Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent()); + + edgeImitator.getDownlinkMsgs().clear(); + + edgeImitator.expectMessageAmount(3); + doPost("/api/edge/sync", edge.getId()); + edgeImitator.waitForMessages(); + + Assert.assertEquals(3, edgeImitator.getDownlinkMsgs().size()); + Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent()); + Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent()); + Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent()); + + edgeImitator.disconnect(); + + doDelete("/api/device/" + savedDevice.getId().getId().toString()) + .andExpect(status().isOk()); + doDelete("/api/asset/" + savedAsset.getId().getId().toString()) + .andExpect(status().isOk()); + doDelete("/api/edge/" + edge.getId().getId().toString()) + .andExpect(status().isOk()); + } + } 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 904e662e12..ee62c0808e 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 @@ -2372,6 +2372,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } + public void syncEdge(EdgeId edgeId) { + restTemplate.postForEntity(baseURL + "/api/edge/sync", edgeId, EdgeId.class); + } + @Deprecated public Optional getAttributes(String accessToken, String clientKeys, String sharedKeys) { Map params = new HashMap<>(); diff --git a/ui/src/app/api/edge.service.js b/ui/src/app/api/edge.service.js index 7814e1d826..528dea960c 100644 --- a/ui/src/app/api/edge.service.js +++ b/ui/src/app/api/edge.service.js @@ -33,7 +33,8 @@ function EdgeService($http, $q, customerService) { unassignEdgeFromCustomer: unassignEdgeFromCustomer, makeEdgePublic: makeEdgePublic, setRootRuleChain: setRootRuleChain, - getEdgeEvents: getEdgeEvents + getEdgeEvents: getEdgeEvents, + syncEdge: syncEdge }; return service; @@ -262,4 +263,15 @@ function EdgeService($http, $q, customerService) { }); return deferred.promise; } + + function syncEdge(edgeId) { + var deferred = $q.defer(); + var url = '/api/edge/sync'; + $http.post(url, edgeId).then(function success(response) { + deferred.resolve(response); + }, function fail(response) { + deferred.reject(response.data); + }); + return deferred.promise; + } } diff --git a/ui/src/app/edge/edge-fieldset.tpl.html b/ui/src/app/edge/edge-fieldset.tpl.html index 1595b2aebd..5811969b6a 100644 --- a/ui/src/app/edge/edge-fieldset.tpl.html +++ b/ui/src/app/edge/edge-fieldset.tpl.html @@ -48,6 +48,12 @@ edge.copy-id + + + edge.sync + diff --git a/ui/src/app/edge/edge.directive.js b/ui/src/app/edge/edge.directive.js index 98f010e009..04165418e9 100644 --- a/ui/src/app/edge/edge.directive.js +++ b/ui/src/app/edge/edge.directive.js @@ -20,7 +20,7 @@ import edgeFieldsetTemplate from './edge-fieldset.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, utils, toast, types, customerService) { +export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, utils, toast, types, customerService, edgeService) { var linker = function (scope, element) { var template = $templateCache.get(edgeFieldsetTemplate); element.html(template); @@ -69,6 +69,17 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD toast.showSuccess($translate.instant('edge.id-copied-message'), 750, angular.element(element).parent().parent(), 'bottom left'); }; + scope.onEdgeSync = function (edgeId) { + edgeService.syncEdge(edgeId).then( + function success() { + toast.showSuccess($translate.instant('edge.sync-message'), 750, angular.element(element).parent().parent(), 'bottom left'); + }, + function fail(error) { + toast.showError(error); + } + ); + } + $compile(element.contents())(scope); scope.onEdgeInfoCopied = function(type) { diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index f8a1d887ff..582b7b72c2 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -756,6 +756,8 @@ "details": "Details", "copy-id": "Regelketten-ID kopieren", "id-copied-message": "Regelketten-ID wurde in die Zwischenablage kopiert", + "sync": "Sync Edge", + "sync-message": "Edge wurde synchronisiert", "permissions": "Berechtigungen", "edge-required": "Rand ist erforderlich.", "edge-type": "Randtyp", diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index c77192bffb..412280603c 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -788,6 +788,8 @@ "events": "Events", "copy-id": "Copy Edge Id", "id-copied-message": "Edge Id has been copied to clipboard", + "sync": "Sync Edge", + "sync-message": "Edge has been synchronized", "permissions": "Permissions", "edge-required": "Edge required", "edge-type": "Edge type", diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index f8f4e6bbe9..3eea48845f 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -769,6 +769,8 @@ "details": "Detalles", "copy-id": "Copiar ID de borde", "id-copied-message": "El ID de borde se ha copiado al portapapeles", + "sync": "Sinc Edge", + "sync-message": "Edge se ha sincronizado", "permissions": "Permisos", "edge-required": "Edge required", "edge-type": "Type de la bordure", diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index 4058148bbd..ffd4e05896 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -774,6 +774,8 @@ "details": "Détails de l'entité", "copy-id": "Copier borudre Id", "id-copied-message": "Id de la bordure a été copié dans le presse-papier", + "sync": "Sync Edge", + "sync-message": "Edge a été synchronisé", "permissions": "Autorisations", "edge-required": "Bordure est requise", "edge-type": "Type de la bordure",