Commit a6ae072bc9223a16b22751245058debc41a23014

Authored by Bohdan Smetaniuk
1 parent a1db669a

edge services code coverage

@@ -588,7 +588,7 @@ transport: @@ -588,7 +588,7 @@ transport:
588 # Edges parameters 588 # Edges parameters
589 edges: 589 edges:
590 rpc: 590 rpc:
591 - enabled: "${EDGES_RPC_ENABLED:false}" 591 + enabled: "${EDGES_RPC_ENABLED:true}"
592 port: "${EDGES_RPC_PORT:7070}" 592 port: "${EDGES_RPC_PORT:7070}"
593 ssl: 593 ssl:
594 # Enable/disable SSL support 594 # Enable/disable SSL support
@@ -94,6 +94,11 @@ @@ -94,6 +94,11 @@
94 <groupId>org.thingsboard</groupId> 94 <groupId>org.thingsboard</groupId>
95 <artifactId>rest-client</artifactId> 95 <artifactId>rest-client</artifactId>
96 </dependency> 96 </dependency>
  97 + <dependency>
  98 + <groupId>org.thingsboard.common</groupId>
  99 + <artifactId>edge-api</artifactId>
  100 + <scope>test</scope>
  101 + </dependency>
97 </dependencies> 102 </dependencies>
98 103
99 <build> 104 <build>
@@ -31,7 +31,7 @@ import java.util.List; @@ -31,7 +31,7 @@ import java.util.List;
31 import java.util.Map; 31 import java.util.Map;
32 32
33 @RunWith(ClasspathSuite.class) 33 @RunWith(ClasspathSuite.class)
34 -@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) 34 +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*EdgeTest"})
35 public class ContainerTestSuite { 35 public class ContainerTestSuite {
36 36
37 private static DockerComposeContainer testContainer; 37 private static DockerComposeContainer testContainer;
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.msa.edge;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import com.google.common.util.concurrent.MoreExecutors;
  22 +import lombok.Getter;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.checkerframework.checker.nullness.qual.Nullable;
  25 +import org.thingsboard.edge.rpc.EdgeGrpcClient;
  26 +import org.thingsboard.edge.rpc.EdgeRpcClient;
  27 +import org.thingsboard.server.common.data.edge.EdgeEventType;
  28 +import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
  29 +import org.thingsboard.server.gen.edge.AssetUpdateMsg;
  30 +import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
  31 +import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
  32 +import org.thingsboard.server.gen.edge.DownlinkMsg;
  33 +import org.thingsboard.server.gen.edge.DownlinkResponseMsg;
  34 +import org.thingsboard.server.gen.edge.EdgeConfiguration;
  35 +import org.thingsboard.server.gen.edge.EntityDataProto;
  36 +import org.thingsboard.server.gen.edge.RelationUpdateMsg;
  37 +import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
  38 +import org.thingsboard.server.gen.edge.UplinkResponseMsg;
  39 +
  40 +import java.lang.reflect.Field;
  41 +import java.util.ArrayList;
  42 +import java.util.List;
  43 +import java.util.UUID;
  44 +
  45 +@Slf4j
  46 +public class EdgeImitator {
  47 +
  48 + private String routingKey;
  49 + private String routingSecret;
  50 +
  51 + private EdgeRpcClient edgeRpcClient;
  52 +
  53 + @Getter
  54 + private EdgeStorage storage;
  55 +
  56 +
  57 + public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException {
  58 + edgeRpcClient = new EdgeGrpcClient();
  59 + storage = new EdgeStorage();
  60 + this.routingKey = routingKey;
  61 + this.routingSecret = routingSecret;
  62 + setEdgeCredentials("rpcHost", host);
  63 + setEdgeCredentials("rpcPort", port);
  64 + }
  65 +
  66 + private void setEdgeCredentials(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
  67 + Field fieldToSet = edgeRpcClient.getClass().getDeclaredField(fieldName);
  68 + fieldToSet.setAccessible(true);
  69 + fieldToSet.set(edgeRpcClient, value);
  70 + fieldToSet.setAccessible(false);
  71 + }
  72 +
  73 + public void connect() {
  74 + edgeRpcClient.connect(routingKey, routingSecret,
  75 + this::onUplinkResponse,
  76 + this::onEdgeUpdate,
  77 + this::onDownlink,
  78 + this::onError);
  79 + }
  80 +
  81 + public void disconnect() throws InterruptedException {
  82 + edgeRpcClient.disconnect();
  83 + }
  84 +
  85 + private void onUplinkResponse(UplinkResponseMsg msg) {
  86 + log.info("onUplinkResponse: {}", msg);
  87 + }
  88 +
  89 + private void onEdgeUpdate(EdgeConfiguration edgeConfiguration) {
  90 + storage.setConfiguration(edgeConfiguration);
  91 + }
  92 +
  93 + private void onDownlink(DownlinkMsg downlinkMsg) {
  94 + ListenableFuture<List<Void>> future = processDownlinkMsg(downlinkMsg);
  95 + Futures.addCallback(future, new FutureCallback<List<Void>>() {
  96 + @Override
  97 + public void onSuccess(@Nullable List<Void> result) {
  98 + DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder().setSuccess(true).build();
  99 + edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg);
  100 + }
  101 +
  102 + @Override
  103 + public void onFailure(Throwable t) {
  104 + DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder().setSuccess(false).setErrorMsg(t.getMessage()).build();
  105 + edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg);
  106 + }
  107 + }, MoreExecutors.directExecutor());
  108 + }
  109 +
  110 + private void onError(Exception e) {
  111 + log.error("Error during Edge lifecycle: ", e);
  112 + }
  113 +
  114 + private ListenableFuture<List<Void>> processDownlinkMsg(DownlinkMsg downlinkMsg) {
  115 + List<ListenableFuture<Void>> result = new ArrayList<>();
  116 + if (downlinkMsg.getDeviceUpdateMsgList() != null && !downlinkMsg.getDeviceUpdateMsgList().isEmpty()) {
  117 + for (DeviceUpdateMsg deviceUpdateMsg: downlinkMsg.getDeviceUpdateMsgList()) {
  118 + result.add(storage.processEntity(deviceUpdateMsg.getMsgType(), EdgeEventType.DEVICE, new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())));
  119 + }
  120 + }
  121 + if (downlinkMsg.getAssetUpdateMsgList() != null && !downlinkMsg.getAssetUpdateMsgList().isEmpty()) {
  122 + for (AssetUpdateMsg assetUpdateMsg: downlinkMsg.getAssetUpdateMsgList()) {
  123 + result.add(storage.processEntity(assetUpdateMsg.getMsgType(), EdgeEventType.ASSET, new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB())));
  124 + }
  125 + }
  126 + if (downlinkMsg.getRuleChainUpdateMsgList() != null && !downlinkMsg.getRuleChainUpdateMsgList().isEmpty()) {
  127 + for (RuleChainUpdateMsg ruleChainUpdateMsg: downlinkMsg.getRuleChainUpdateMsgList()) {
  128 + result.add(storage.processEntity(ruleChainUpdateMsg.getMsgType(), EdgeEventType.RULE_CHAIN, new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB())));
  129 + }
  130 + }
  131 + if (downlinkMsg.getDashboardUpdateMsgList() != null && !downlinkMsg.getDashboardUpdateMsgList().isEmpty()) {
  132 + for (DashboardUpdateMsg dashboardUpdateMsg: downlinkMsg.getDashboardUpdateMsgList()) {
  133 + result.add(storage.processEntity(dashboardUpdateMsg.getMsgType(), EdgeEventType.DASHBOARD, new UUID(dashboardUpdateMsg.getIdMSB(), dashboardUpdateMsg.getIdLSB())));
  134 + }
  135 + }
  136 + if (downlinkMsg.getRelationUpdateMsgList() != null && !downlinkMsg.getRelationUpdateMsgList().isEmpty()) {
  137 + for (RelationUpdateMsg relationUpdateMsg: downlinkMsg.getRelationUpdateMsgList()) {
  138 + result.add(storage.processRelation(relationUpdateMsg));
  139 + }
  140 + }
  141 + if (downlinkMsg.getAlarmUpdateMsgList() != null && !downlinkMsg.getAlarmUpdateMsgList().isEmpty()) {
  142 + for (AlarmUpdateMsg alarmUpdateMsg: downlinkMsg.getAlarmUpdateMsgList()) {
  143 + result.add(storage.processAlarm(alarmUpdateMsg));
  144 + }
  145 + }
  146 + if (downlinkMsg.getEntityDataList() != null && !downlinkMsg.getEntityDataList().isEmpty()) {
  147 + for (EntityDataProto entityDataProto: downlinkMsg.getEntityDataList()) {
  148 + if (entityDataProto.hasPostTelemetryMsg()) {
  149 + result.add(storage.processTelemetry(new UUID(entityDataProto.getEntityIdMSB(), entityDataProto.getEntityIdLSB()), entityDataProto.getPostTelemetryMsg()));
  150 + }
  151 + }
  152 + }
  153 + return Futures.allAsList(result);
  154 + }
  155 +
  156 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.msa.edge;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import lombok.Getter;
  21 +import lombok.Setter;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  24 +import org.thingsboard.server.common.data.edge.EdgeEventType;
  25 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  26 +import org.thingsboard.server.common.data.relation.EntityRelation;
  27 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  28 +import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
  29 +import org.thingsboard.server.gen.edge.EdgeConfiguration;
  30 +import org.thingsboard.server.gen.edge.RelationUpdateMsg;
  31 +import org.thingsboard.server.gen.edge.UpdateMsgType;
  32 +import org.thingsboard.server.gen.transport.TransportProtos;
  33 +
  34 +import java.util.ArrayList;
  35 +import java.util.HashMap;
  36 +import java.util.List;
  37 +import java.util.Map;
  38 +import java.util.Set;
  39 +import java.util.UUID;
  40 +import java.util.stream.Collectors;
  41 +
  42 +@Slf4j
  43 +@Getter
  44 +@Setter
  45 +public class EdgeStorage {
  46 +
  47 + private EdgeConfiguration configuration;
  48 +
  49 + private Map<UUID, EdgeEventType> entities;
  50 + private Map<String, AlarmStatus> alarms;
  51 + private List<EntityRelation> relations;
  52 + private Map<UUID, TransportProtos.PostTelemetryMsg> latestTelemetry;
  53 +
  54 +
  55 + public EdgeStorage() {
  56 + entities = new HashMap<>();
  57 + alarms = new HashMap<>();
  58 + relations = new ArrayList<>();
  59 + latestTelemetry = new HashMap<>();
  60 + }
  61 +
  62 + public ListenableFuture<Void> processEntity(UpdateMsgType msgType, EdgeEventType type, UUID uuid) {
  63 + switch (msgType) {
  64 + case ENTITY_CREATED_RPC_MESSAGE:
  65 + case ENTITY_UPDATED_RPC_MESSAGE:
  66 + entities.put(uuid, type);
  67 + break;
  68 + case ENTITY_DELETED_RPC_MESSAGE:
  69 + entities.remove(uuid);
  70 + break;
  71 + }
  72 + return Futures.immediateFuture(null);
  73 + }
  74 +
  75 + public ListenableFuture<Void> processRelation(RelationUpdateMsg relationMsg) {
  76 + EntityRelation relation = new EntityRelation();
  77 + relation.setType(relationMsg.getType());
  78 + relation.setTypeGroup(RelationTypeGroup.valueOf(relationMsg.getTypeGroup()));
  79 + relation.setTo(EntityIdFactory.getByTypeAndUuid(relationMsg.getToEntityType(), new UUID(relationMsg.getToIdMSB(), relationMsg.getToIdLSB())));
  80 + relation.setFrom(EntityIdFactory.getByTypeAndUuid(relationMsg.getFromEntityType(), new UUID(relationMsg.getFromIdMSB(), relationMsg.getFromIdLSB())));
  81 + switch (relationMsg.getMsgType()) {
  82 + case ENTITY_CREATED_RPC_MESSAGE:
  83 + case ENTITY_UPDATED_RPC_MESSAGE:
  84 + relations.add(relation);
  85 + break;
  86 + case ENTITY_DELETED_RPC_MESSAGE:
  87 + relations.remove(relation);
  88 + break;
  89 + }
  90 + return Futures.immediateFuture(null);
  91 + }
  92 +
  93 + public ListenableFuture<Void> processAlarm(AlarmUpdateMsg alarmMsg) {
  94 + switch (alarmMsg.getMsgType()) {
  95 + case ENTITY_CREATED_RPC_MESSAGE:
  96 + case ENTITY_UPDATED_RPC_MESSAGE:
  97 + case ALARM_ACK_RPC_MESSAGE:
  98 + case ALARM_CLEAR_RPC_MESSAGE:
  99 + alarms.put(alarmMsg.getType(), AlarmStatus.valueOf(alarmMsg.getStatus()));
  100 + break;
  101 + case ENTITY_DELETED_RPC_MESSAGE:
  102 + alarms.remove(alarmMsg.getName());
  103 + break;
  104 + }
  105 + return Futures.immediateFuture(null);
  106 + }
  107 +
  108 + public ListenableFuture<Void> processTelemetry(UUID uuid, TransportProtos.PostTelemetryMsg telemetryMsg) {
  109 + latestTelemetry.put(uuid, telemetryMsg);
  110 + return Futures.immediateFuture(null);
  111 + }
  112 +
  113 + public Set<UUID> getEntitiesByType(EdgeEventType type) {
  114 + Map<UUID, EdgeEventType> filtered = entities.entrySet().stream()
  115 + .filter(entry -> entry.getValue().equals(type))
  116 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  117 + return filtered.keySet();
  118 + }
  119 +
  120 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.msa.edge;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.junit.*;
  22 +import org.springframework.http.HttpStatus;
  23 +import org.springframework.http.ResponseEntity;
  24 +import org.thingsboard.server.common.data.Dashboard;
  25 +import org.thingsboard.server.common.data.Device;
  26 +import org.thingsboard.server.common.data.alarm.Alarm;
  27 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
  28 +import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  29 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  30 +import org.thingsboard.server.common.data.asset.Asset;
  31 +import org.thingsboard.server.common.data.edge.Edge;
  32 +import org.thingsboard.server.common.data.edge.EdgeEventType;
  33 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  34 +import org.thingsboard.server.common.data.page.TextPageLink;
  35 +import org.thingsboard.server.common.data.page.TimePageLink;
  36 +import org.thingsboard.server.common.data.relation.EntityRelation;
  37 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  38 +import org.thingsboard.server.common.data.rule.RuleChain;
  39 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  40 +import org.thingsboard.server.common.data.rule.RuleChainType;
  41 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  42 +import org.thingsboard.server.gen.edge.EdgeConfiguration;
  43 +import org.thingsboard.server.gen.transport.TransportProtos;
  44 +import org.thingsboard.server.msa.AbstractContainerTest;
  45 +
  46 +import java.io.IOException;
  47 +import java.util.List;
  48 +import java.util.Map;
  49 +import java.util.Set;
  50 +import java.util.UUID;
  51 +
  52 +@Slf4j
  53 +public class EdgeTest extends AbstractContainerTest {
  54 +
  55 + private static EdgeImitator edgeImitator;
  56 +
  57 + @BeforeClass
  58 + public static void init() throws NoSuchFieldException, IllegalAccessException, InterruptedException, IOException {
  59 + restClient.login("tenant@thingsboard.org", "tenant");
  60 + installation();
  61 + edgeImitator = new EdgeImitator("localhost", 7070, "routing", "secret");
  62 + edgeImitator.connect();
  63 + Thread.sleep(10000);
  64 + }
  65 +
  66 + @Test
  67 + public void testReceivedData() {
  68 + Edge edge = restClient.getTenantEdge("Edge1").get();
  69 +
  70 + EdgeConfiguration configuration = edgeImitator.getStorage().getConfiguration();
  71 + Assert.assertNotNull(configuration);
  72 +
  73 + Map<UUID, EdgeEventType> entities = edgeImitator.getStorage().getEntities();
  74 + Assert.assertFalse(entities.isEmpty());
  75 +
  76 + Set<UUID> devices = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DEVICE);
  77 + Assert.assertEquals(1, devices.size());
  78 + for (Device device: restClient.getEdgeDevices(edge.getId(), new TextPageLink(1)).getData()) {
  79 + Assert.assertTrue(devices.contains(device.getUuidId()));
  80 + }
  81 +
  82 + Set<UUID> ruleChains = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.RULE_CHAIN);
  83 + Assert.assertEquals(1, ruleChains.size());
  84 + for (RuleChain ruleChain: restClient.getEdgeRuleChains(edge.getId(), new TimePageLink(1)).getData()) {
  85 + Assert.assertTrue(ruleChains.contains(ruleChain.getUuidId()));
  86 + }
  87 +
  88 + Set<UUID> assets = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.ASSET);
  89 + Assert.assertEquals(1, assets.size());
  90 + for (Asset asset: restClient.getEdgeAssets(edge.getId(), new TextPageLink(1)).getData()) {
  91 + Assert.assertTrue(assets.contains(asset.getUuidId()));
  92 + }
  93 + }
  94 +
  95 + @Test
  96 + public void testDevices() throws Exception {
  97 + Edge edge = restClient.getTenantEdge("Edge1").get();
  98 +
  99 + Device device = new Device();
  100 + device.setName("Edge Device 2");
  101 + device.setType("test");
  102 + Device savedDevice = restClient.saveDevice(device);
  103 + restClient.assignDeviceToEdge(edge.getId(), savedDevice.getId());
  104 +
  105 + Thread.sleep(1000);
  106 + Assert.assertTrue(restClient.getEdgeDevices(edge.getId(), new TextPageLink(2)).getData().contains(savedDevice));
  107 + Set<UUID> devices = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DEVICE);
  108 + Assert.assertEquals(2, devices.size());
  109 + Assert.assertTrue(devices.contains(savedDevice.getUuidId()));
  110 +
  111 + restClient.unassignDeviceFromEdge(edge.getId(), savedDevice.getId());
  112 + Thread.sleep(1000);
  113 + Assert.assertFalse(restClient.getEdgeDevices(edge.getId(), new TextPageLink(2)).getData().contains(savedDevice));
  114 + devices = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DEVICE);
  115 + Assert.assertEquals(1, devices.size());
  116 + Assert.assertFalse(devices.contains(savedDevice.getUuidId()));
  117 +
  118 + restClient.deleteDevice(savedDevice.getId());
  119 + }
  120 +
  121 + @Test
  122 + public void testAssets() throws Exception {
  123 + Edge edge = restClient.getTenantEdge("Edge1").get();
  124 +
  125 + Asset asset = new Asset();
  126 + asset.setName("Edge Asset 2");
  127 + asset.setType("test");
  128 + Asset savedAsset = restClient.saveAsset(asset);
  129 + restClient.assignAssetToEdge(edge.getId(), savedAsset.getId());
  130 +
  131 + Thread.sleep(1000);
  132 + Assert.assertTrue(restClient.getEdgeAssets(edge.getId(), new TextPageLink(2)).getData().contains(savedAsset));
  133 + Set<UUID> assets = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.ASSET);
  134 + Assert.assertEquals(2, assets.size());
  135 + Assert.assertTrue(assets.contains(savedAsset.getUuidId()));
  136 +
  137 + restClient.unassignAssetFromEdge(edge.getId(), savedAsset.getId());
  138 + Thread.sleep(1000);
  139 + Assert.assertFalse(restClient.getEdgeAssets(edge.getId(), new TextPageLink(2)).getData().contains(savedAsset));
  140 + assets = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.ASSET);
  141 + Assert.assertEquals(1, assets.size());
  142 + Assert.assertFalse(assets.contains(savedAsset.getUuidId()));
  143 +
  144 + restClient.deleteAsset(savedAsset.getId());
  145 + }
  146 +
  147 + @Test
  148 + public void testRuleChains() throws Exception {
  149 + Edge edge = restClient.getTenantEdge("Edge1").get();
  150 +
  151 + RuleChain ruleChain = new RuleChain();
  152 + ruleChain.setName("Edge Test Rule Chain");
  153 + ruleChain.setType(RuleChainType.EDGE);
  154 + RuleChain savedRuleChain = restClient.saveRuleChain(ruleChain);
  155 + restClient.assignRuleChainToEdge(edge.getId(), savedRuleChain.getId());
  156 +
  157 + Thread.sleep(1000);
  158 + Assert.assertTrue(restClient.getEdgeRuleChains(edge.getId(), new TimePageLink(2)).getData().contains(savedRuleChain));
  159 + Set<UUID> ruleChains = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.RULE_CHAIN);
  160 + Assert.assertEquals(2, ruleChains.size());
  161 + Assert.assertTrue(ruleChains.contains(savedRuleChain.getUuidId()));
  162 +
  163 + restClient.unassignRuleChainFromEdge(edge.getId(), savedRuleChain.getId());
  164 + Thread.sleep(1000);
  165 + Assert.assertFalse(restClient.getEdgeRuleChains(edge.getId(), new TimePageLink(2)).getData().contains(savedRuleChain));
  166 + ruleChains = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.RULE_CHAIN);
  167 + Assert.assertEquals(1, ruleChains.size());
  168 + Assert.assertFalse(ruleChains.contains(savedRuleChain.getUuidId()));
  169 +
  170 + restClient.deleteRuleChain(savedRuleChain.getId());
  171 +
  172 + }
  173 +
  174 + @Test
  175 + public void testDashboards() throws Exception {
  176 + Edge edge = restClient.getTenantEdge("Edge1").get();
  177 +
  178 + Dashboard dashboard = new Dashboard();
  179 + dashboard.setTitle("Edge Test Dashboard");
  180 + Dashboard savedDashboard = restClient.saveDashboard(dashboard);
  181 + restClient.assignDashboardToEdge(edge.getId(), savedDashboard.getId());
  182 +
  183 + Thread.sleep(1000);
  184 + Assert.assertTrue(restClient.getEdgeDashboards(edge.getId(), new TimePageLink(2)).getData().stream().allMatch(dashboardInfo -> dashboardInfo.getUuidId().equals(savedDashboard.getUuidId())));
  185 + Set<UUID> dashboards = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DASHBOARD);
  186 + Assert.assertEquals(1, dashboards.size());
  187 + Assert.assertTrue(dashboards.contains(savedDashboard.getUuidId()));
  188 +
  189 + restClient.unassignDashboardFromEdge(edge.getId(), savedDashboard.getId());
  190 + Thread.sleep(1000);
  191 + Assert.assertFalse(restClient.getEdgeDashboards(edge.getId(), new TimePageLink(2)).getData().stream().anyMatch(dashboardInfo -> dashboardInfo.getUuidId().equals(savedDashboard.getUuidId())));
  192 + dashboards = edgeImitator.getStorage().getEntitiesByType(EdgeEventType.DASHBOARD);
  193 + Assert.assertEquals(0, dashboards.size());
  194 + Assert.assertFalse(dashboards.contains(savedDashboard.getUuidId()));
  195 +
  196 + restClient.deleteDashboard(savedDashboard.getId());
  197 +
  198 + }
  199 +
  200 + @Test
  201 + public void testRelations() throws InterruptedException {
  202 + Device device = restClient.getTenantDevice("Edge Device 1").get();
  203 + Asset asset = restClient.getTenantAsset("Edge Asset 1").get();
  204 +
  205 + EntityRelation relation = new EntityRelation();
  206 + relation.setType("test");
  207 + relation.setFrom(device.getId());
  208 + relation.setTo(asset.getId());
  209 + relation.setTypeGroup(RelationTypeGroup.COMMON);
  210 + restClient.saveRelation(relation);
  211 +
  212 + Thread.sleep(1000);
  213 + List<EntityRelation> relations = edgeImitator.getStorage().getRelations();
  214 + Assert.assertEquals(1, relations.size());
  215 + Assert.assertTrue(relations.contains(relation));
  216 + restClient.deleteRelation(relation.getFrom(), relation.getType(), relation.getTypeGroup(), relation.getTo());
  217 +
  218 + Thread.sleep(1000);
  219 + relations = edgeImitator.getStorage().getRelations();
  220 + Assert.assertEquals(0, relations.size());
  221 + Assert.assertFalse(relations.contains(relation));
  222 + }
  223 +
  224 + @Test
  225 + public void testAlarms() throws Exception {
  226 + Device device = restClient.getTenantDevice("Edge Device 1").get();
  227 + Alarm alarm = new Alarm();
  228 + alarm.setOriginator(device.getId());
  229 + alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
  230 + alarm.setType("alarm");
  231 + alarm.setSeverity(AlarmSeverity.CRITICAL);
  232 +
  233 + Alarm savedAlarm = restClient.saveAlarm(alarm);
  234 + AlarmInfo alarmInfo = restClient.getAlarmInfoById(savedAlarm.getId()).get();
  235 + Thread.sleep(1000);
  236 +
  237 + Assert.assertEquals(1, edgeImitator.getStorage().getAlarms().size());
  238 + Assert.assertTrue(edgeImitator.getStorage().getAlarms().containsKey(alarmInfo.getType()));
  239 + Assert.assertEquals(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()), alarmInfo.getStatus());
  240 + restClient.ackAlarm(savedAlarm.getId());
  241 +
  242 + Thread.sleep(1000);
  243 + alarmInfo = restClient.getAlarmInfoById(savedAlarm.getId()).get();
  244 + Assert.assertTrue(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()).isAck());
  245 + Assert.assertEquals(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()), alarmInfo.getStatus());
  246 + restClient.clearAlarm(savedAlarm.getId());
  247 +
  248 + Thread.sleep(1000);
  249 + alarmInfo = restClient.getAlarmInfoById(savedAlarm.getId()).get();
  250 + Assert.assertTrue(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()).isAck());
  251 + Assert.assertTrue(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()).isCleared());
  252 + Assert.assertEquals(edgeImitator.getStorage().getAlarms().get(alarmInfo.getType()), alarmInfo.getStatus());
  253 +
  254 + restClient.deleteAlarm(savedAlarm.getId());
  255 +
  256 + }
  257 +
  258 + @Ignore
  259 + @Test
  260 + public void testTelemetry() throws Exception {
  261 + Device device = restClient.getTenantDevice("Edge Device 1").get();
  262 + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get();
  263 + ResponseEntity response = restClient.getRestTemplate()
  264 + .postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry",
  265 + "{'test': 25}",
  266 + ResponseEntity.class,
  267 + deviceCredentials.getCredentialsId());
  268 + Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
  269 + Thread.sleep(1000);
  270 + List<String> keys = restClient.getTimeseriesKeys(device.getId());
  271 + List<TsKvEntry> latestTimeseries = restClient.getLatestTimeseries(device.getId(), keys);
  272 + Assert.assertEquals(1, latestTimeseries.size());
  273 + TsKvEntry tsKvEntry = latestTimeseries.get(0);
  274 + Map<UUID, TransportProtos.PostTelemetryMsg> telemetry = edgeImitator.getStorage().getLatestTelemetry();
  275 + Assert.assertEquals(1, telemetry.size());
  276 + Assert.assertTrue(telemetry.containsKey(device.getUuidId()));
  277 + TransportProtos.PostTelemetryMsg telemetryMsg = telemetry.get(device.getUuidId());
  278 + Assert.assertEquals(1, telemetryMsg.getTsKvListCount());
  279 + TransportProtos.TsKvListProto tsKv = telemetryMsg.getTsKvListList().get(0);
  280 + Assert.assertEquals(tsKvEntry.getTs(), tsKv.getTs());
  281 + Assert.assertEquals(1, tsKv.getKvCount());
  282 + TransportProtos.KeyValueProto keyValue = tsKv.getKvList().get(0);
  283 + Assert.assertEquals(tsKvEntry.getKey(), keyValue.getKey());
  284 + Assert.assertEquals(tsKvEntry.getValueAsString(), Long.toString(keyValue.getLongV()));
  285 + }
  286 +
  287 + @AfterClass
  288 + public static void destroy() throws InterruptedException {
  289 + uninstallation();
  290 + edgeImitator.disconnect();
  291 + }
  292 +
  293 + private static void installation() throws IOException {
  294 + Edge edge = new Edge();
  295 + edge.setName("Edge1");
  296 + edge.setType("test");
  297 + edge.setRoutingKey("routing");
  298 + edge.setSecret("secret");
  299 + Edge savedEdge = restClient.saveEdge(edge);
  300 +
  301 + Device device = new Device();
  302 + device.setName("Edge Device 1");
  303 + device.setType("test");
  304 + Device savedDevice = restClient.saveDevice(device);
  305 + restClient.assignDeviceToEdge(savedEdge.getId(), savedDevice.getId());
  306 +
  307 + Asset asset = new Asset();
  308 + asset.setName("Edge Asset 1");
  309 + asset.setType("test");
  310 + Asset savedAsset = restClient.saveAsset(asset);
  311 + restClient.assignAssetToEdge(savedEdge.getId(), savedAsset.getId());
  312 +
  313 + ObjectMapper mapper = new ObjectMapper();
  314 + Class edgeTestClass = EdgeTest.class;
  315 + JsonNode configuration = mapper.readTree(edgeTestClass.getClassLoader().getResourceAsStream("RootRuleChain.json"));
  316 + RuleChain ruleChain = mapper.treeToValue(configuration.get("ruleChain"), RuleChain.class);
  317 + RuleChainMetaData ruleChainMetaData = mapper.treeToValue(configuration.get("metadata"), RuleChainMetaData.class);
  318 + RuleChain savedRuleChain = restClient.saveRuleChain(ruleChain);
  319 + ruleChainMetaData.setRuleChainId(savedRuleChain.getId());
  320 + restClient.saveRuleChainMetaData(ruleChainMetaData);
  321 + restClient.setRootRuleChain(savedRuleChain.getId());
  322 + }
  323 +
  324 + private static void uninstallation() {
  325 + Device device = restClient.getTenantDevice("Edge Device 1").get();
  326 + restClient.deleteDevice(device.getId());
  327 +
  328 + Asset asset = restClient.getTenantAsset("Edge Asset 1").get();
  329 + restClient.deleteAsset(asset.getId());
  330 +
  331 + Edge edge = restClient.getTenantEdge("Edge1").get();
  332 + restClient.deleteEdge(edge.getId());
  333 +
  334 + List<RuleChain> ruleChains = restClient.getRuleChains(new TextPageLink(3)).getData();
  335 + RuleChain oldRoot = ruleChains.stream().filter(ruleChain -> ruleChain.getName().equals("Root Rule Chain")).findAny().get();
  336 + RuleChain newRoot = ruleChains.stream().filter(ruleChain -> ruleChain.getName().equals("Test Root Rule Chain")).findAny().get();
  337 + restClient.setRootRuleChain(oldRoot.getId());
  338 + restClient.deleteRuleChain(newRoot.getId());
  339 + }
  340 +
  341 +}
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Test Root Rule Chain",
  5 + "type": "CORE",
  6 + "firstRuleNodeId": null,
  7 + "root": false,
  8 + "debugMode": false,
  9 + "configuration": null
  10 + },
  11 + "metadata": {
  12 + "firstNodeIndex": 4,
  13 + "nodes": [
  14 + {
  15 + "additionalInfo": {
  16 + "layoutX": 1117,
  17 + "layoutY": 156
  18 + },
  19 + "type": "org.thingsboard.rule.engine.edge.TbMsgPushToEdgeNode",
  20 + "name": "Push to edge",
  21 + "debugMode": false,
  22 + "configuration": {
  23 + "version": 0
  24 + }
  25 + },
  26 + {
  27 + "additionalInfo": {
  28 + "layoutX": 825,
  29 + "layoutY": 407
  30 + },
  31 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  32 + "name": "RPC Call Request",
  33 + "debugMode": false,
  34 + "configuration": {
  35 + "timeoutInSeconds": 60
  36 + }
  37 + },
  38 + {
  39 + "additionalInfo": {
  40 + "layoutX": 826,
  41 + "layoutY": 327
  42 + },
  43 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  44 + "name": "Log Other",
  45 + "debugMode": false,
  46 + "configuration": {
  47 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  48 + }
  49 + },
  50 + {
  51 + "additionalInfo": {
  52 + "layoutX": 827,
  53 + "layoutY": 244
  54 + },
  55 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  56 + "name": "Log RPC from Device",
  57 + "debugMode": false,
  58 + "configuration": {
  59 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  60 + }
  61 + },
  62 + {
  63 + "additionalInfo": {
  64 + "layoutX": 347,
  65 + "layoutY": 149
  66 + },
  67 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  68 + "name": "Message Type Switch",
  69 + "debugMode": false,
  70 + "configuration": {
  71 + "version": 0
  72 + }
  73 + },
  74 + {
  75 + "additionalInfo": {
  76 + "layoutX": 821,
  77 + "layoutY": 72
  78 + },
  79 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  80 + "name": "Save Client Attributes",
  81 + "debugMode": false,
  82 + "configuration": {
  83 + "scope": "CLIENT_SCOPE"
  84 + }
  85 + },
  86 + {
  87 + "additionalInfo": {
  88 + "layoutX": 824,
  89 + "layoutY": 156
  90 + },
  91 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  92 + "name": "Save Timeseries",
  93 + "debugMode": false,
  94 + "configuration": {
  95 + "defaultTTL": 0
  96 + }
  97 + }
  98 + ],
  99 + "connections": [
  100 + {
  101 + "fromIndex": 4,
  102 + "toIndex": 1,
  103 + "type": "RPC Request to Device"
  104 + },
  105 + {
  106 + "fromIndex": 4,
  107 + "toIndex": 3,
  108 + "type": "RPC Request from Device"
  109 + },
  110 + {
  111 + "fromIndex": 4,
  112 + "toIndex": 6,
  113 + "type": "Post telemetry"
  114 + },
  115 + {
  116 + "fromIndex": 4,
  117 + "toIndex": 5,
  118 + "type": "Post attributes"
  119 + },
  120 + {
  121 + "fromIndex": 4,
  122 + "toIndex": 2,
  123 + "type": "Other"
  124 + },
  125 + {
  126 + "fromIndex": 6,
  127 + "toIndex": 0,
  128 + "type": "Success"
  129 + }
  130 + ],
  131 + "ruleChainConnections": null
  132 + }
  133 +}