Showing
7 changed files
with
757 additions
and
2 deletions
... | ... | @@ -94,6 +94,11 @@ |
94 | 94 | <groupId>org.thingsboard</groupId> |
95 | 95 | <artifactId>rest-client</artifactId> |
96 | 96 | </dependency> |
97 | + <dependency> | |
98 | + <groupId>org.thingsboard.common</groupId> | |
99 | + <artifactId>edge-api</artifactId> | |
100 | + <scope>test</scope> | |
101 | + </dependency> | |
97 | 102 | </dependencies> |
98 | 103 | |
99 | 104 | <build> | ... | ... |
... | ... | @@ -31,7 +31,7 @@ import java.util.List; |
31 | 31 | import java.util.Map; |
32 | 32 | |
33 | 33 | @RunWith(ClasspathSuite.class) |
34 | -@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) | |
34 | +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*EdgeTest"}) | |
35 | 35 | public class ContainerTestSuite { |
36 | 36 | |
37 | 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 | +} | |
\ No newline at end of file | ... | ... |