Showing
7 changed files
with
757 additions
and
2 deletions
@@ -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 | +} |