diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index bcf3425f53..db83fd700a 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -588,7 +588,7 @@ transport:
# Edges parameters
edges:
rpc:
- enabled: "${EDGES_RPC_ENABLED:false}"
+ enabled: "${EDGES_RPC_ENABLED:true}"
port: "${EDGES_RPC_PORT:7070}"
ssl:
# Enable/disable SSL support
diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml
index d24f38bc36..1602d24185 100644
--- a/msa/black-box-tests/pom.xml
+++ b/msa/black-box-tests/pom.xml
@@ -94,6 +94,11 @@
org.thingsboard
rest-client
+
+ org.thingsboard.common
+ edge-api
+ test
+
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java
index 647c8878b7..5a798a8cc8 100644
--- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java
@@ -31,7 +31,7 @@ import java.util.List;
import java.util.Map;
@RunWith(ClasspathSuite.class)
-@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"})
+@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*EdgeTest"})
public class ContainerTestSuite {
private static DockerComposeContainer testContainer;
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeImitator.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeImitator.java
new file mode 100644
index 0000000000..823d180261
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeImitator.java
@@ -0,0 +1,156 @@
+/**
+ * Copyright © 2016-2020 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.edge;
+
+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.MoreExecutors;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.thingsboard.edge.rpc.EdgeGrpcClient;
+import org.thingsboard.edge.rpc.EdgeRpcClient;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
+import org.thingsboard.server.gen.edge.AssetUpdateMsg;
+import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
+import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
+import org.thingsboard.server.gen.edge.DownlinkMsg;
+import org.thingsboard.server.gen.edge.DownlinkResponseMsg;
+import org.thingsboard.server.gen.edge.EdgeConfiguration;
+import org.thingsboard.server.gen.edge.EntityDataProto;
+import org.thingsboard.server.gen.edge.RelationUpdateMsg;
+import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
+import org.thingsboard.server.gen.edge.UplinkResponseMsg;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+public class EdgeImitator {
+
+ private String routingKey;
+ private String routingSecret;
+
+ private EdgeRpcClient edgeRpcClient;
+
+ @Getter
+ private EdgeStorage storage;
+
+
+ public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException {
+ edgeRpcClient = new EdgeGrpcClient();
+ storage = new EdgeStorage();
+ this.routingKey = routingKey;
+ this.routingSecret = routingSecret;
+ setEdgeCredentials("rpcHost", host);
+ setEdgeCredentials("rpcPort", port);
+ }
+
+ private void setEdgeCredentials(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
+ Field fieldToSet = edgeRpcClient.getClass().getDeclaredField(fieldName);
+ fieldToSet.setAccessible(true);
+ fieldToSet.set(edgeRpcClient, value);
+ fieldToSet.setAccessible(false);
+ }
+
+ public void connect() {
+ edgeRpcClient.connect(routingKey, routingSecret,
+ this::onUplinkResponse,
+ this::onEdgeUpdate,
+ this::onDownlink,
+ this::onError);
+ }
+
+ public void disconnect() throws InterruptedException {
+ edgeRpcClient.disconnect();
+ }
+
+ private void onUplinkResponse(UplinkResponseMsg msg) {
+ log.info("onUplinkResponse: {}", msg);
+ }
+
+ private void onEdgeUpdate(EdgeConfiguration edgeConfiguration) {
+ storage.setConfiguration(edgeConfiguration);
+ }
+
+ private void onDownlink(DownlinkMsg downlinkMsg) {
+ ListenableFuture> future = processDownlinkMsg(downlinkMsg);
+ Futures.addCallback(future, new FutureCallback>() {
+ @Override
+ public void onSuccess(@Nullable List result) {
+ DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder().setSuccess(true).build();
+ edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder().setSuccess(false).setErrorMsg(t.getMessage()).build();
+ edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg);
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ private void onError(Exception e) {
+ log.error("Error during Edge lifecycle: ", e);
+ }
+
+ private ListenableFuture> processDownlinkMsg(DownlinkMsg downlinkMsg) {
+ List> result = new ArrayList<>();
+ if (downlinkMsg.getDeviceUpdateMsgList() != null && !downlinkMsg.getDeviceUpdateMsgList().isEmpty()) {
+ for (DeviceUpdateMsg deviceUpdateMsg: downlinkMsg.getDeviceUpdateMsgList()) {
+ result.add(storage.processEntity(deviceUpdateMsg.getMsgType(), EdgeEventType.DEVICE, new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())));
+ }
+ }
+ if (downlinkMsg.getAssetUpdateMsgList() != null && !downlinkMsg.getAssetUpdateMsgList().isEmpty()) {
+ for (AssetUpdateMsg assetUpdateMsg: downlinkMsg.getAssetUpdateMsgList()) {
+ result.add(storage.processEntity(assetUpdateMsg.getMsgType(), EdgeEventType.ASSET, new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB())));
+ }
+ }
+ if (downlinkMsg.getRuleChainUpdateMsgList() != null && !downlinkMsg.getRuleChainUpdateMsgList().isEmpty()) {
+ for (RuleChainUpdateMsg ruleChainUpdateMsg: downlinkMsg.getRuleChainUpdateMsgList()) {
+ result.add(storage.processEntity(ruleChainUpdateMsg.getMsgType(), EdgeEventType.RULE_CHAIN, new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB())));
+ }
+ }
+ if (downlinkMsg.getDashboardUpdateMsgList() != null && !downlinkMsg.getDashboardUpdateMsgList().isEmpty()) {
+ for (DashboardUpdateMsg dashboardUpdateMsg: downlinkMsg.getDashboardUpdateMsgList()) {
+ result.add(storage.processEntity(dashboardUpdateMsg.getMsgType(), EdgeEventType.DASHBOARD, new UUID(dashboardUpdateMsg.getIdMSB(), dashboardUpdateMsg.getIdLSB())));
+ }
+ }
+ if (downlinkMsg.getRelationUpdateMsgList() != null && !downlinkMsg.getRelationUpdateMsgList().isEmpty()) {
+ for (RelationUpdateMsg relationUpdateMsg: downlinkMsg.getRelationUpdateMsgList()) {
+ result.add(storage.processRelation(relationUpdateMsg));
+ }
+ }
+ if (downlinkMsg.getAlarmUpdateMsgList() != null && !downlinkMsg.getAlarmUpdateMsgList().isEmpty()) {
+ for (AlarmUpdateMsg alarmUpdateMsg: downlinkMsg.getAlarmUpdateMsgList()) {
+ result.add(storage.processAlarm(alarmUpdateMsg));
+ }
+ }
+ if (downlinkMsg.getEntityDataList() != null && !downlinkMsg.getEntityDataList().isEmpty()) {
+ for (EntityDataProto entityDataProto: downlinkMsg.getEntityDataList()) {
+ if (entityDataProto.hasPostTelemetryMsg()) {
+ result.add(storage.processTelemetry(new UUID(entityDataProto.getEntityIdMSB(), entityDataProto.getEntityIdLSB()), entityDataProto.getPostTelemetryMsg()));
+ }
+ }
+ }
+ return Futures.allAsList(result);
+ }
+
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeStorage.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeStorage.java
new file mode 100644
index 0000000000..0bad7473cf
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeStorage.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright © 2016-2020 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.edge;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
+import org.thingsboard.server.gen.edge.EdgeConfiguration;
+import org.thingsboard.server.gen.edge.RelationUpdateMsg;
+import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.transport.TransportProtos;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Getter
+@Setter
+public class EdgeStorage {
+
+ private EdgeConfiguration configuration;
+
+ private Map entities;
+ private Map alarms;
+ private List relations;
+ private Map latestTelemetry;
+
+
+ public EdgeStorage() {
+ entities = new HashMap<>();
+ alarms = new HashMap<>();
+ relations = new ArrayList<>();
+ latestTelemetry = new HashMap<>();
+ }
+
+ public ListenableFuture processEntity(UpdateMsgType msgType, EdgeEventType type, UUID uuid) {
+ switch (msgType) {
+ case ENTITY_CREATED_RPC_MESSAGE:
+ case ENTITY_UPDATED_RPC_MESSAGE:
+ entities.put(uuid, type);
+ break;
+ case ENTITY_DELETED_RPC_MESSAGE:
+ entities.remove(uuid);
+ break;
+ }
+ return Futures.immediateFuture(null);
+ }
+
+ public ListenableFuture processRelation(RelationUpdateMsg relationMsg) {
+ EntityRelation relation = new EntityRelation();
+ relation.setType(relationMsg.getType());
+ relation.setTypeGroup(RelationTypeGroup.valueOf(relationMsg.getTypeGroup()));
+ relation.setTo(EntityIdFactory.getByTypeAndUuid(relationMsg.getToEntityType(), new UUID(relationMsg.getToIdMSB(), relationMsg.getToIdLSB())));
+ relation.setFrom(EntityIdFactory.getByTypeAndUuid(relationMsg.getFromEntityType(), new UUID(relationMsg.getFromIdMSB(), relationMsg.getFromIdLSB())));
+ switch (relationMsg.getMsgType()) {
+ case ENTITY_CREATED_RPC_MESSAGE:
+ case ENTITY_UPDATED_RPC_MESSAGE:
+ relations.add(relation);
+ break;
+ case ENTITY_DELETED_RPC_MESSAGE:
+ relations.remove(relation);
+ break;
+ }
+ return Futures.immediateFuture(null);
+ }
+
+ public ListenableFuture processAlarm(AlarmUpdateMsg alarmMsg) {
+ switch (alarmMsg.getMsgType()) {
+ case ENTITY_CREATED_RPC_MESSAGE:
+ case ENTITY_UPDATED_RPC_MESSAGE:
+ case ALARM_ACK_RPC_MESSAGE:
+ case ALARM_CLEAR_RPC_MESSAGE:
+ alarms.put(alarmMsg.getType(), AlarmStatus.valueOf(alarmMsg.getStatus()));
+ break;
+ case ENTITY_DELETED_RPC_MESSAGE:
+ alarms.remove(alarmMsg.getName());
+ break;
+ }
+ return Futures.immediateFuture(null);
+ }
+
+ public ListenableFuture processTelemetry(UUID uuid, TransportProtos.PostTelemetryMsg telemetryMsg) {
+ latestTelemetry.put(uuid, telemetryMsg);
+ return Futures.immediateFuture(null);
+ }
+
+ public Set getEntitiesByType(EdgeEventType type) {
+ Map filtered = entities.entrySet().stream()
+ .filter(entry -> entry.getValue().equals(type))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ return filtered.keySet();
+ }
+
+}
diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeTest.java
new file mode 100644
index 0000000000..a1f8c8e374
--- /dev/null
+++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/edge/EdgeTest.java
@@ -0,0 +1,341 @@
+/**
+ * Copyright © 2016-2020 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.msa.edge;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.*;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
+import org.thingsboard.server.common.data.alarm.AlarmSeverity;
+import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.rule.RuleChainMetaData;
+import org.thingsboard.server.common.data.rule.RuleChainType;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.gen.edge.EdgeConfiguration;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.msa.AbstractContainerTest;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+@Slf4j
+public class EdgeTest extends AbstractContainerTest {
+
+ private static EdgeImitator edgeImitator;
+
+ @BeforeClass
+ public static void init() throws NoSuchFieldException, IllegalAccessException, InterruptedException, IOException {
+ restClient.login("tenant@thingsboard.org", "tenant");
+ installation();
+ edgeImitator = new EdgeImitator("localhost", 7070, "routing", "secret");
+ edgeImitator.connect();
+ Thread.sleep(10000);
+ }
+
+ @Test
+ public void testReceivedData() {
+ Edge edge = restClient.getTenantEdge("Edge1").get();
+
+ EdgeConfiguration configuration = edgeImitator.getStorage().getConfiguration();
+ Assert.assertNotNull(configuration);
+
+ Map entities = edgeImitator.getStorage().getEntities();
+ Assert.assertFalse(entities.isEmpty());
+
+ Set devices = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DEVICE);
+ Assert.assertEquals(1, devices.size());
+ for (Device device: restClient.getEdgeDevices(edge.getId(), new TextPageLink(1)).getData()) {
+ Assert.assertTrue(devices.contains(device.getUuidId()));
+ }
+
+ Set ruleChains = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.RULE_CHAIN);
+ Assert.assertEquals(1, ruleChains.size());
+ for (RuleChain ruleChain: restClient.getEdgeRuleChains(edge.getId(), new TimePageLink(1)).getData()) {
+ Assert.assertTrue(ruleChains.contains(ruleChain.getUuidId()));
+ }
+
+ Set assets = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.ASSET);
+ Assert.assertEquals(1, assets.size());
+ for (Asset asset: restClient.getEdgeAssets(edge.getId(), new TextPageLink(1)).getData()) {
+ Assert.assertTrue(assets.contains(asset.getUuidId()));
+ }
+ }
+
+ @Test
+ public void testDevices() throws Exception {
+ Edge edge = restClient.getTenantEdge("Edge1").get();
+
+ Device device = new Device();
+ device.setName("Edge Device 2");
+ device.setType("test");
+ Device savedDevice = restClient.saveDevice(device);
+ restClient.assignDeviceToEdge(edge.getId(), savedDevice.getId());
+
+ Thread.sleep(1000);
+ Assert.assertTrue(restClient.getEdgeDevices(edge.getId(), new TextPageLink(2)).getData().contains(savedDevice));
+ Set devices = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DEVICE);
+ Assert.assertEquals(2, devices.size());
+ Assert.assertTrue(devices.contains(savedDevice.getUuidId()));
+
+ restClient.unassignDeviceFromEdge(edge.getId(), savedDevice.getId());
+ Thread.sleep(1000);
+ Assert.assertFalse(restClient.getEdgeDevices(edge.getId(), new TextPageLink(2)).getData().contains(savedDevice));
+ devices = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DEVICE);
+ Assert.assertEquals(1, devices.size());
+ Assert.assertFalse(devices.contains(savedDevice.getUuidId()));
+
+ restClient.deleteDevice(savedDevice.getId());
+ }
+
+ @Test
+ public void testAssets() throws Exception {
+ Edge edge = restClient.getTenantEdge("Edge1").get();
+
+ Asset asset = new Asset();
+ asset.setName("Edge Asset 2");
+ asset.setType("test");
+ Asset savedAsset = restClient.saveAsset(asset);
+ restClient.assignAssetToEdge(edge.getId(), savedAsset.getId());
+
+ Thread.sleep(1000);
+ Assert.assertTrue(restClient.getEdgeAssets(edge.getId(), new TextPageLink(2)).getData().contains(savedAsset));
+ Set assets = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.ASSET);
+ Assert.assertEquals(2, assets.size());
+ Assert.assertTrue(assets.contains(savedAsset.getUuidId()));
+
+ restClient.unassignAssetFromEdge(edge.getId(), savedAsset.getId());
+ Thread.sleep(1000);
+ Assert.assertFalse(restClient.getEdgeAssets(edge.getId(), new TextPageLink(2)).getData().contains(savedAsset));
+ assets = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.ASSET);
+ Assert.assertEquals(1, assets.size());
+ Assert.assertFalse(assets.contains(savedAsset.getUuidId()));
+
+ restClient.deleteAsset(savedAsset.getId());
+ }
+
+ @Test
+ public void testRuleChains() throws Exception {
+ Edge edge = restClient.getTenantEdge("Edge1").get();
+
+ RuleChain ruleChain = new RuleChain();
+ ruleChain.setName("Edge Test Rule Chain");
+ ruleChain.setType(RuleChainType.EDGE);
+ RuleChain savedRuleChain = restClient.saveRuleChain(ruleChain);
+ restClient.assignRuleChainToEdge(edge.getId(), savedRuleChain.getId());
+
+ Thread.sleep(1000);
+ Assert.assertTrue(restClient.getEdgeRuleChains(edge.getId(), new TimePageLink(2)).getData().contains(savedRuleChain));
+ Set ruleChains = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.RULE_CHAIN);
+ Assert.assertEquals(2, ruleChains.size());
+ Assert.assertTrue(ruleChains.contains(savedRuleChain.getUuidId()));
+
+ restClient.unassignRuleChainFromEdge(edge.getId(), savedRuleChain.getId());
+ Thread.sleep(1000);
+ Assert.assertFalse(restClient.getEdgeRuleChains(edge.getId(), new TimePageLink(2)).getData().contains(savedRuleChain));
+ ruleChains = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.RULE_CHAIN);
+ Assert.assertEquals(1, ruleChains.size());
+ Assert.assertFalse(ruleChains.contains(savedRuleChain.getUuidId()));
+
+ restClient.deleteRuleChain(savedRuleChain.getId());
+
+ }
+
+ @Test
+ public void testDashboards() throws Exception {
+ Edge edge = restClient.getTenantEdge("Edge1").get();
+
+ Dashboard dashboard = new Dashboard();
+ dashboard.setTitle("Edge Test Dashboard");
+ Dashboard savedDashboard = restClient.saveDashboard(dashboard);
+ restClient.assignDashboardToEdge(edge.getId(), savedDashboard.getId());
+
+ Thread.sleep(1000);
+ Assert.assertTrue(restClient.getEdgeDashboards(edge.getId(), new TimePageLink(2)).getData().stream().allMatch(dashboardInfo -> dashboardInfo.getUuidId().equals(savedDashboard.getUuidId())));
+ Set dashboards = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DASHBOARD);
+ Assert.assertEquals(1, dashboards.size());
+ Assert.assertTrue(dashboards.contains(savedDashboard.getUuidId()));
+
+ restClient.unassignDashboardFromEdge(edge.getId(), savedDashboard.getId());
+ Thread.sleep(1000);
+ Assert.assertFalse(restClient.getEdgeDashboards(edge.getId(), new TimePageLink(2)).getData().stream().anyMatch(dashboardInfo -> dashboardInfo.getUuidId().equals(savedDashboard.getUuidId())));
+ dashboards = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DASHBOARD);
+ Assert.assertEquals(0, dashboards.size());
+ Assert.assertFalse(dashboards.contains(savedDashboard.getUuidId()));
+
+ restClient.deleteDashboard(savedDashboard.getId());
+
+ }
+
+ @Test
+ public void testRelations() throws InterruptedException {
+ Device device = restClient.getTenantDevice("Edge Device 1").get();
+ Asset asset = restClient.getTenantAsset("Edge Asset 1").get();
+
+ EntityRelation relation = new EntityRelation();
+ relation.setType("test");
+ relation.setFrom(device.getId());
+ relation.setTo(asset.getId());
+ relation.setTypeGroup(RelationTypeGroup.COMMON);
+ restClient.saveRelation(relation);
+
+ Thread.sleep(1000);
+ List relations = edgeImitator.getStorage().getRelations();
+ Assert.assertEquals(1, relations.size());
+ Assert.assertTrue(relations.contains(relation));
+ restClient.deleteRelation(relation.getFrom(), relation.getType(), relation.getTypeGroup(), relation.getTo());
+
+ Thread.sleep(1000);
+ relations = edgeImitator.getStorage().getRelations();
+ Assert.assertEquals(0, relations.size());
+ Assert.assertFalse(relations.contains(relation));
+ }
+
+ @Test
+ public void testAlarms() throws Exception {
+ Device device = restClient.getTenantDevice("Edge Device 1").get();
+ Alarm alarm = new Alarm();
+ alarm.setOriginator(device.getId());
+ alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
+ alarm.setType("alarm");
+ alarm.setSeverity(AlarmSeverity.CRITICAL);
+
+ Alarm savedAlarm = restClient.saveAlarm(alarm);
+ AlarmInfo alarmInfo = restClient.getAlarmInfoById(savedAlarm.getId()).get();
+ Thread.sleep(1000);
+
+ Assert.assertEquals(1, edgeImitator.getStorage().getAlarms().size());
+ Assert.assertTrue(edgeImitator.getStorage().getAlarms().containsKey(alarmInfo.getType()));
+ Assert.assertEquals(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()), alarmInfo.getStatus());
+ restClient.ackAlarm(savedAlarm.getId());
+
+ Thread.sleep(1000);
+ alarmInfo = restClient.getAlarmInfoById(savedAlarm.getId()).get();
+ Assert.assertTrue(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()).isAck());
+ Assert.assertEquals(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()), alarmInfo.getStatus());
+ restClient.clearAlarm(savedAlarm.getId());
+
+ Thread.sleep(1000);
+ alarmInfo = restClient.getAlarmInfoById(savedAlarm.getId()).get();
+ Assert.assertTrue(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()).isAck());
+ Assert.assertTrue(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()).isCleared());
+ Assert.assertEquals(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()), alarmInfo.getStatus());
+
+ restClient.deleteAlarm(savedAlarm.getId());
+
+ }
+
+ @Ignore
+ @Test
+ public void testTelemetry() throws Exception {
+ Device device = restClient.getTenantDevice("Edge Device 1").get();
+ DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get();
+ ResponseEntity response = restClient.getRestTemplate()
+ .postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry",
+ "{'test': 25}",
+ ResponseEntity.class,
+ deviceCredentials.getCredentialsId());
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Thread.sleep(1000);
+ List keys = restClient.getTimeseriesKeys(device.getId());
+ List latestTimeseries = restClient.getLatestTimeseries(device.getId(), keys);
+ Assert.assertEquals(1, latestTimeseries.size());
+ TsKvEntry tsKvEntry = latestTimeseries.get(0);
+ Map telemetry = edgeImitator.getStorage().getLatestTelemetry();
+ Assert.assertEquals(1, telemetry.size());
+ Assert.assertTrue(telemetry.containsKey(device.getUuidId()));
+ TransportProtos.PostTelemetryMsg telemetryMsg = telemetry.get(device.getUuidId());
+ Assert.assertEquals(1, telemetryMsg.getTsKvListCount());
+ TransportProtos.TsKvListProto tsKv = telemetryMsg.getTsKvListList().get(0);
+ Assert.assertEquals(tsKvEntry.getTs(), tsKv.getTs());
+ Assert.assertEquals(1, tsKv.getKvCount());
+ TransportProtos.KeyValueProto keyValue = tsKv.getKvList().get(0);
+ Assert.assertEquals(tsKvEntry.getKey(), keyValue.getKey());
+ Assert.assertEquals(tsKvEntry.getValueAsString(), Long.toString(keyValue.getLongV()));
+ }
+
+ @AfterClass
+ public static void destroy() throws InterruptedException {
+ uninstallation();
+ edgeImitator.disconnect();
+ }
+
+ private static void installation() throws IOException {
+ Edge edge = new Edge();
+ edge.setName("Edge1");
+ edge.setType("test");
+ edge.setRoutingKey("routing");
+ edge.setSecret("secret");
+ Edge savedEdge = restClient.saveEdge(edge);
+
+ Device device = new Device();
+ device.setName("Edge Device 1");
+ device.setType("test");
+ Device savedDevice = restClient.saveDevice(device);
+ restClient.assignDeviceToEdge(savedEdge.getId(), savedDevice.getId());
+
+ Asset asset = new Asset();
+ asset.setName("Edge Asset 1");
+ asset.setType("test");
+ Asset savedAsset = restClient.saveAsset(asset);
+ restClient.assignAssetToEdge(savedEdge.getId(), savedAsset.getId());
+
+ ObjectMapper mapper = new ObjectMapper();
+ Class edgeTestClass = EdgeTest.class;
+ JsonNode configuration = mapper.readTree(edgeTestClass.getClassLoader().getResourceAsStream("RootRuleChain.json"));
+ RuleChain ruleChain = mapper.treeToValue(configuration.get("ruleChain"), RuleChain.class);
+ RuleChainMetaData ruleChainMetaData = mapper.treeToValue(configuration.get("metadata"), RuleChainMetaData.class);
+ RuleChain savedRuleChain = restClient.saveRuleChain(ruleChain);
+ ruleChainMetaData.setRuleChainId(savedRuleChain.getId());
+ restClient.saveRuleChainMetaData(ruleChainMetaData);
+ restClient.setRootRuleChain(savedRuleChain.getId());
+ }
+
+ private static void uninstallation() {
+ Device device = restClient.getTenantDevice("Edge Device 1").get();
+ restClient.deleteDevice(device.getId());
+
+ Asset asset = restClient.getTenantAsset("Edge Asset 1").get();
+ restClient.deleteAsset(asset.getId());
+
+ Edge edge = restClient.getTenantEdge("Edge1").get();
+ restClient.deleteEdge(edge.getId());
+
+ List ruleChains = restClient.getRuleChains(new TextPageLink(3)).getData();
+ RuleChain oldRoot = ruleChains.stream().filter(ruleChain -> ruleChain.getName().equals("Root Rule Chain")).findAny().get();
+ RuleChain newRoot = ruleChains.stream().filter(ruleChain -> ruleChain.getName().equals("Test Root Rule Chain")).findAny().get();
+ restClient.setRootRuleChain(oldRoot.getId());
+ restClient.deleteRuleChain(newRoot.getId());
+ }
+
+}
diff --git a/msa/black-box-tests/src/test/resources/RootRuleChain.json b/msa/black-box-tests/src/test/resources/RootRuleChain.json
new file mode 100644
index 0000000000..56324e72f5
--- /dev/null
+++ b/msa/black-box-tests/src/test/resources/RootRuleChain.json
@@ -0,0 +1,133 @@
+{
+ "ruleChain": {
+ "additionalInfo": null,
+ "name": "Test Root Rule Chain",
+ "type": "CORE",
+ "firstRuleNodeId": null,
+ "root": false,
+ "debugMode": false,
+ "configuration": null
+ },
+ "metadata": {
+ "firstNodeIndex": 4,
+ "nodes": [
+ {
+ "additionalInfo": {
+ "layoutX": 1117,
+ "layoutY": 156
+ },
+ "type": "org.thingsboard.rule.engine.edge.TbMsgPushToEdgeNode",
+ "name": "Push to edge",
+ "debugMode": false,
+ "configuration": {
+ "version": 0
+ }
+ },
+ {
+ "additionalInfo": {
+ "layoutX": 825,
+ "layoutY": 407
+ },
+ "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
+ "name": "RPC Call Request",
+ "debugMode": false,
+ "configuration": {
+ "timeoutInSeconds": 60
+ }
+ },
+ {
+ "additionalInfo": {
+ "layoutX": 826,
+ "layoutY": 327
+ },
+ "type": "org.thingsboard.rule.engine.action.TbLogNode",
+ "name": "Log Other",
+ "debugMode": false,
+ "configuration": {
+ "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
+ }
+ },
+ {
+ "additionalInfo": {
+ "layoutX": 827,
+ "layoutY": 244
+ },
+ "type": "org.thingsboard.rule.engine.action.TbLogNode",
+ "name": "Log RPC from Device",
+ "debugMode": false,
+ "configuration": {
+ "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
+ }
+ },
+ {
+ "additionalInfo": {
+ "layoutX": 347,
+ "layoutY": 149
+ },
+ "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
+ "name": "Message Type Switch",
+ "debugMode": false,
+ "configuration": {
+ "version": 0
+ }
+ },
+ {
+ "additionalInfo": {
+ "layoutX": 821,
+ "layoutY": 72
+ },
+ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
+ "name": "Save Client Attributes",
+ "debugMode": false,
+ "configuration": {
+ "scope": "CLIENT_SCOPE"
+ }
+ },
+ {
+ "additionalInfo": {
+ "layoutX": 824,
+ "layoutY": 156
+ },
+ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
+ "name": "Save Timeseries",
+ "debugMode": false,
+ "configuration": {
+ "defaultTTL": 0
+ }
+ }
+ ],
+ "connections": [
+ {
+ "fromIndex": 4,
+ "toIndex": 1,
+ "type": "RPC Request to Device"
+ },
+ {
+ "fromIndex": 4,
+ "toIndex": 3,
+ "type": "RPC Request from Device"
+ },
+ {
+ "fromIndex": 4,
+ "toIndex": 6,
+ "type": "Post telemetry"
+ },
+ {
+ "fromIndex": 4,
+ "toIndex": 5,
+ "type": "Post attributes"
+ },
+ {
+ "fromIndex": 4,
+ "toIndex": 2,
+ "type": "Other"
+ },
+ {
+ "fromIndex": 6,
+ "toIndex": 0,
+ "type": "Success"
+ }
+ ],
+ "ruleChainConnections": null
+ }
+}
\ No newline at end of file