Commit 2fb756ba5b897eb78a568b1b684f9945a05b2f38
Merge branch 'master' of https://github.com/thingsboard/thingsboard
Showing
113 changed files
with
2603 additions
and
540 deletions
@@ -2,6 +2,7 @@ before_install: | @@ -2,6 +2,7 @@ before_install: | ||
2 | - sudo rm -f /etc/mavenrc | 2 | - sudo rm -f /etc/mavenrc |
3 | - export M2_HOME=/usr/local/maven | 3 | - export M2_HOME=/usr/local/maven |
4 | - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" | 4 | - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" |
5 | + - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false | ||
5 | jdk: | 6 | jdk: |
6 | - oraclejdk8 | 7 | - oraclejdk8 |
7 | language: java | 8 | language: java |
@@ -61,6 +61,7 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; | @@ -61,6 +61,7 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; | ||
61 | import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; | 61 | import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
62 | import org.thingsboard.server.service.component.ComponentDiscoveryService; | 62 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
63 | import org.thingsboard.server.service.encoding.DataDecodingEncodingService; | 63 | import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
64 | +import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService; | ||
64 | import org.thingsboard.server.service.executors.DbCallbackExecutorService; | 65 | import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
65 | import org.thingsboard.server.service.executors.ExternalCallExecutorService; | 66 | import org.thingsboard.server.service.executors.ExternalCallExecutorService; |
66 | import org.thingsboard.server.service.mail.MailExecutorService; | 67 | import org.thingsboard.server.service.mail.MailExecutorService; |
@@ -188,6 +189,10 @@ public class ActorSystemContext { | @@ -188,6 +189,10 @@ public class ActorSystemContext { | ||
188 | 189 | ||
189 | @Autowired | 190 | @Autowired |
190 | @Getter | 191 | @Getter |
192 | + private ClusterRpcCallbackExecutorService clusterRpcCallbackExecutor; | ||
193 | + | ||
194 | + @Autowired | ||
195 | + @Getter | ||
191 | private DbCallbackExecutorService dbCallbackExecutor; | 196 | private DbCallbackExecutorService dbCallbackExecutor; |
192 | 197 | ||
193 | @Autowired | 198 | @Autowired |
@@ -25,15 +25,21 @@ import akka.actor.Terminated; | @@ -25,15 +25,21 @@ import akka.actor.Terminated; | ||
25 | import akka.event.Logging; | 25 | import akka.event.Logging; |
26 | import akka.event.LoggingAdapter; | 26 | import akka.event.LoggingAdapter; |
27 | import akka.japi.Function; | 27 | import akka.japi.Function; |
28 | +import com.google.common.collect.BiMap; | ||
29 | +import com.google.common.collect.HashBiMap; | ||
30 | +import lombok.extern.slf4j.Slf4j; | ||
28 | import org.thingsboard.server.actors.ActorSystemContext; | 31 | import org.thingsboard.server.actors.ActorSystemContext; |
29 | import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; | 32 | import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; |
30 | import org.thingsboard.server.actors.service.ContextBasedCreator; | 33 | import org.thingsboard.server.actors.service.ContextBasedCreator; |
31 | import org.thingsboard.server.actors.service.DefaultActorService; | 34 | import org.thingsboard.server.actors.service.DefaultActorService; |
32 | import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager; | 35 | import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager; |
33 | import org.thingsboard.server.actors.tenant.TenantActor; | 36 | import org.thingsboard.server.actors.tenant.TenantActor; |
37 | +import org.thingsboard.server.common.data.EntityType; | ||
34 | import org.thingsboard.server.common.data.Tenant; | 38 | import org.thingsboard.server.common.data.Tenant; |
35 | import org.thingsboard.server.common.data.id.TenantId; | 39 | import org.thingsboard.server.common.data.id.TenantId; |
36 | import org.thingsboard.server.common.data.page.PageDataIterable; | 40 | import org.thingsboard.server.common.data.page.PageDataIterable; |
41 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | ||
42 | +import org.thingsboard.server.common.msg.MsgType; | ||
37 | import org.thingsboard.server.common.msg.TbActorMsg; | 43 | import org.thingsboard.server.common.msg.TbActorMsg; |
38 | import org.thingsboard.server.common.msg.aware.TenantAwareMsg; | 44 | import org.thingsboard.server.common.msg.aware.TenantAwareMsg; |
39 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; | 45 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
@@ -50,16 +56,15 @@ import java.util.Optional; | @@ -50,16 +56,15 @@ import java.util.Optional; | ||
50 | 56 | ||
51 | public class AppActor extends RuleChainManagerActor { | 57 | public class AppActor extends RuleChainManagerActor { |
52 | 58 | ||
53 | - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); | ||
54 | - | ||
55 | - public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); | 59 | + private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); |
56 | private final TenantService tenantService; | 60 | private final TenantService tenantService; |
57 | - private final Map<TenantId, ActorRef> tenantActors; | 61 | + private final BiMap<TenantId, ActorRef> tenantActors; |
62 | + private boolean ruleChainsInitialized; | ||
58 | 63 | ||
59 | private AppActor(ActorSystemContext systemContext) { | 64 | private AppActor(ActorSystemContext systemContext) { |
60 | super(systemContext, new SystemRuleChainManager(systemContext)); | 65 | super(systemContext, new SystemRuleChainManager(systemContext)); |
61 | this.tenantService = systemContext.getTenantService(); | 66 | this.tenantService = systemContext.getTenantService(); |
62 | - this.tenantActors = new HashMap<>(); | 67 | + this.tenantActors = HashBiMap.create(); |
63 | } | 68 | } |
64 | 69 | ||
65 | @Override | 70 | @Override |
@@ -69,28 +74,20 @@ public class AppActor extends RuleChainManagerActor { | @@ -69,28 +74,20 @@ public class AppActor extends RuleChainManagerActor { | ||
69 | 74 | ||
70 | @Override | 75 | @Override |
71 | public void preStart() { | 76 | public void preStart() { |
72 | - logger.info("Starting main system actor."); | ||
73 | - try { | ||
74 | - initRuleChains(); | ||
75 | - | ||
76 | - if (systemContext.isTenantComponentsInitEnabled()) { | ||
77 | - PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT); | ||
78 | - for (Tenant tenant : tenantIterator) { | ||
79 | - logger.debug("[{}] Creating tenant actor", tenant.getId()); | ||
80 | - getOrCreateTenantActor(tenant.getId()); | ||
81 | - logger.debug("Tenant actor created."); | ||
82 | - } | ||
83 | - } | ||
84 | - | ||
85 | - logger.info("Main system actor started."); | ||
86 | - } catch (Exception e) { | ||
87 | - logger.error(e, "Unknown failure"); | ||
88 | - } | ||
89 | } | 77 | } |
90 | 78 | ||
91 | @Override | 79 | @Override |
92 | protected boolean process(TbActorMsg msg) { | 80 | protected boolean process(TbActorMsg msg) { |
81 | + if (!ruleChainsInitialized) { | ||
82 | + initRuleChainsAndTenantActors(); | ||
83 | + ruleChainsInitialized = true; | ||
84 | + if (msg.getMsgType() != MsgType.APP_INIT_MSG) { | ||
85 | + log.warn("Rule Chains initialized by unexpected message: {}", msg); | ||
86 | + } | ||
87 | + } | ||
93 | switch (msg.getMsgType()) { | 88 | switch (msg.getMsgType()) { |
89 | + case APP_INIT_MSG: | ||
90 | + break; | ||
94 | case SEND_TO_CLUSTER_MSG: | 91 | case SEND_TO_CLUSTER_MSG: |
95 | onPossibleClusterMsg((SendToClusterMsg) msg); | 92 | onPossibleClusterMsg((SendToClusterMsg) msg); |
96 | break; | 93 | break; |
@@ -118,6 +115,24 @@ public class AppActor extends RuleChainManagerActor { | @@ -118,6 +115,24 @@ public class AppActor extends RuleChainManagerActor { | ||
118 | return true; | 115 | return true; |
119 | } | 116 | } |
120 | 117 | ||
118 | + private void initRuleChainsAndTenantActors() { | ||
119 | + log.info("Starting main system actor."); | ||
120 | + try { | ||
121 | + initRuleChains(); | ||
122 | + if (systemContext.isTenantComponentsInitEnabled()) { | ||
123 | + PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT); | ||
124 | + for (Tenant tenant : tenantIterator) { | ||
125 | + log.debug("[{}] Creating tenant actor", tenant.getId()); | ||
126 | + getOrCreateTenantActor(tenant.getId()); | ||
127 | + log.debug("Tenant actor created."); | ||
128 | + } | ||
129 | + } | ||
130 | + log.info("Main system actor started."); | ||
131 | + } catch (Exception e) { | ||
132 | + log.warn("Unknown failure", e); | ||
133 | + } | ||
134 | + } | ||
135 | + | ||
121 | private void onPossibleClusterMsg(SendToClusterMsg msg) { | 136 | private void onPossibleClusterMsg(SendToClusterMsg msg) { |
122 | Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId()); | 137 | Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId()); |
123 | if (address.isPresent()) { | 138 | if (address.isPresent()) { |
@@ -130,7 +145,8 @@ public class AppActor extends RuleChainManagerActor { | @@ -130,7 +145,8 @@ public class AppActor extends RuleChainManagerActor { | ||
130 | 145 | ||
131 | private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { | 146 | private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { |
132 | if (SYSTEM_TENANT.equals(msg.getTenantId())) { | 147 | if (SYSTEM_TENANT.equals(msg.getTenantId())) { |
133 | - //TODO: ashvayka handle this. | 148 | +// this may be a notification about system entities created. |
149 | +// log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet: {}", SYSTEM_TENANT, msg); | ||
134 | } else { | 150 | } else { |
135 | getOrCreateTenantActor(msg.getTenantId()).tell(msg, self()); | 151 | getOrCreateTenantActor(msg.getTenantId()).tell(msg, self()); |
136 | } | 152 | } |
@@ -143,16 +159,26 @@ public class AppActor extends RuleChainManagerActor { | @@ -143,16 +159,26 @@ public class AppActor extends RuleChainManagerActor { | ||
143 | } | 159 | } |
144 | 160 | ||
145 | private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { | 161 | private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
146 | - ActorRef target; | 162 | + ActorRef target = null; |
147 | if (SYSTEM_TENANT.equals(msg.getTenantId())) { | 163 | if (SYSTEM_TENANT.equals(msg.getTenantId())) { |
148 | target = getEntityActorRef(msg.getEntityId()); | 164 | target = getEntityActorRef(msg.getEntityId()); |
149 | } else { | 165 | } else { |
150 | - target = getOrCreateTenantActor(msg.getTenantId()); | 166 | + if (msg.getEntityId().getEntityType() == EntityType.TENANT |
167 | + && msg.getEvent() == ComponentLifecycleEvent.DELETED) { | ||
168 | + log.debug("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); | ||
169 | + ActorRef tenantActor = tenantActors.remove(new TenantId(msg.getEntityId().getId())); | ||
170 | + if (tenantActor != null) { | ||
171 | + log.debug("[{}] Deleting tenant actor: {}", msg.getTenantId(), tenantActor); | ||
172 | + context().stop(tenantActor); | ||
173 | + } | ||
174 | + } else { | ||
175 | + target = getOrCreateTenantActor(msg.getTenantId()); | ||
176 | + } | ||
151 | } | 177 | } |
152 | if (target != null) { | 178 | if (target != null) { |
153 | target.tell(msg, ActorRef.noSender()); | 179 | target.tell(msg, ActorRef.noSender()); |
154 | } else { | 180 | } else { |
155 | - logger.debug("Invalid component lifecycle msg: {}", msg); | 181 | + log.debug("[{}] Invalid component lifecycle msg: {}", msg.getTenantId(), msg); |
156 | } | 182 | } |
157 | } | 183 | } |
158 | 184 | ||
@@ -161,14 +187,24 @@ public class AppActor extends RuleChainManagerActor { | @@ -161,14 +187,24 @@ public class AppActor extends RuleChainManagerActor { | ||
161 | } | 187 | } |
162 | 188 | ||
163 | private ActorRef getOrCreateTenantActor(TenantId tenantId) { | 189 | private ActorRef getOrCreateTenantActor(TenantId tenantId) { |
164 | - return tenantActors.computeIfAbsent(tenantId, k -> context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) | ||
165 | - .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString())); | 190 | + return tenantActors.computeIfAbsent(tenantId, k -> { |
191 | + log.debug("[{}] Creating tenant actor.", tenantId); | ||
192 | + ActorRef tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) | ||
193 | + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString()); | ||
194 | + context().watch(tenantActor); | ||
195 | + log.debug("[{}] Created tenant actor: {}.", tenantId, tenantActor); | ||
196 | + return tenantActor; | ||
197 | + }); | ||
166 | } | 198 | } |
167 | 199 | ||
168 | - private void processTermination(Terminated message) { | 200 | + @Override |
201 | + protected void processTermination(Terminated message) { | ||
169 | ActorRef terminated = message.actor(); | 202 | ActorRef terminated = message.actor(); |
170 | if (terminated instanceof LocalActorRef) { | 203 | if (terminated instanceof LocalActorRef) { |
171 | - logger.debug("Removed actor: {}", terminated); | 204 | + boolean removed = tenantActors.inverse().remove(terminated) != null; |
205 | + if (removed) { | ||
206 | + log.debug("[{}] Removed actor:", terminated); | ||
207 | + } | ||
172 | } else { | 208 | } else { |
173 | throw new IllegalStateException("Remote actors are not supported!"); | 209 | throw new IllegalStateException("Remote actors are not supported!"); |
174 | } | 210 | } |
@@ -182,20 +218,17 @@ public class AppActor extends RuleChainManagerActor { | @@ -182,20 +218,17 @@ public class AppActor extends RuleChainManagerActor { | ||
182 | } | 218 | } |
183 | 219 | ||
184 | @Override | 220 | @Override |
185 | - public AppActor create() throws Exception { | 221 | + public AppActor create() { |
186 | return new AppActor(context); | 222 | return new AppActor(context); |
187 | } | 223 | } |
188 | } | 224 | } |
189 | 225 | ||
190 | - private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, Directive>() { | ||
191 | - @Override | ||
192 | - public Directive apply(Throwable t) { | ||
193 | - logger.error(t, "Unknown failure"); | ||
194 | - if (t instanceof RuntimeException) { | ||
195 | - return SupervisorStrategy.restart(); | ||
196 | - } else { | ||
197 | - return SupervisorStrategy.stop(); | ||
198 | - } | 226 | + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> { |
227 | + log.warn("Unknown failure", t); | ||
228 | + if (t instanceof RuntimeException) { | ||
229 | + return SupervisorStrategy.restart(); | ||
230 | + } else { | ||
231 | + return SupervisorStrategy.stop(); | ||
199 | } | 232 | } |
200 | }); | 233 | }); |
201 | } | 234 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.actors.app; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.msg.MsgType; | ||
19 | +import org.thingsboard.server.common.msg.TbActorMsg; | ||
20 | + | ||
21 | +public class AppInitMsg implements TbActorMsg { | ||
22 | + | ||
23 | + @Override | ||
24 | + public MsgType getMsgType() { | ||
25 | + return MsgType.APP_INIT_MSG; | ||
26 | + } | ||
27 | +} |
@@ -15,17 +15,13 @@ | @@ -15,17 +15,13 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.actors.device; | 16 | package org.thingsboard.server.actors.device; |
17 | 17 | ||
18 | -import akka.event.Logging; | ||
19 | -import akka.event.LoggingAdapter; | ||
20 | import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; | 18 | import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; |
21 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; | 19 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; |
22 | import org.thingsboard.server.actors.ActorSystemContext; | 20 | import org.thingsboard.server.actors.ActorSystemContext; |
23 | import org.thingsboard.server.actors.service.ContextAwareActor; | 21 | import org.thingsboard.server.actors.service.ContextAwareActor; |
24 | -import org.thingsboard.server.actors.service.ContextBasedCreator; | ||
25 | import org.thingsboard.server.common.data.id.DeviceId; | 22 | import org.thingsboard.server.common.data.id.DeviceId; |
26 | import org.thingsboard.server.common.data.id.TenantId; | 23 | import org.thingsboard.server.common.data.id.TenantId; |
27 | import org.thingsboard.server.common.msg.TbActorMsg; | 24 | import org.thingsboard.server.common.msg.TbActorMsg; |
28 | -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; | ||
29 | import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; | 25 | import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; |
30 | import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; | 26 | import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; |
31 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; | 27 | import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; |
@@ -34,23 +30,21 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra | @@ -34,23 +30,21 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra | ||
34 | 30 | ||
35 | public class DeviceActor extends ContextAwareActor { | 31 | public class DeviceActor extends ContextAwareActor { |
36 | 32 | ||
37 | - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); | ||
38 | - | ||
39 | private final DeviceActorMessageProcessor processor; | 33 | private final DeviceActorMessageProcessor processor; |
40 | 34 | ||
41 | - private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { | 35 | + DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { |
42 | super(systemContext); | 36 | super(systemContext); |
43 | - this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId); | 37 | + this.processor = new DeviceActorMessageProcessor(systemContext, tenantId, deviceId); |
44 | } | 38 | } |
45 | 39 | ||
46 | @Override | 40 | @Override |
47 | public void preStart() { | 41 | public void preStart() { |
48 | - logger.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId); | 42 | + log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId); |
49 | try { | 43 | try { |
50 | processor.initSessionTimeout(context()); | 44 | processor.initSessionTimeout(context()); |
51 | - logger.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId); | 45 | + log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId); |
52 | } catch (Exception e) { | 46 | } catch (Exception e) { |
53 | - logger.error(e, "[{}][{}] Unknown failure", processor.tenantId, processor.deviceId); | 47 | + log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e); |
54 | } | 48 | } |
55 | } | 49 | } |
56 | 50 | ||
@@ -90,22 +84,4 @@ public class DeviceActor extends ContextAwareActor { | @@ -90,22 +84,4 @@ public class DeviceActor extends ContextAwareActor { | ||
90 | return true; | 84 | return true; |
91 | } | 85 | } |
92 | 86 | ||
93 | - public static class ActorCreator extends ContextBasedCreator<DeviceActor> { | ||
94 | - private static final long serialVersionUID = 1L; | ||
95 | - | ||
96 | - private final TenantId tenantId; | ||
97 | - private final DeviceId deviceId; | ||
98 | - | ||
99 | - public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { | ||
100 | - super(context); | ||
101 | - this.tenantId = tenantId; | ||
102 | - this.deviceId = deviceId; | ||
103 | - } | ||
104 | - | ||
105 | - @Override | ||
106 | - public DeviceActor create() throws Exception { | ||
107 | - return new DeviceActor(context, tenantId, deviceId); | ||
108 | - } | ||
109 | - } | ||
110 | - | ||
111 | } | 87 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.actors.device; | ||
17 | + | ||
18 | +import org.thingsboard.server.actors.ActorSystemContext; | ||
19 | +import org.thingsboard.server.actors.service.ContextBasedCreator; | ||
20 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
21 | +import org.thingsboard.server.common.data.id.TenantId; | ||
22 | + | ||
23 | +public class DeviceActorCreator extends ContextBasedCreator<DeviceActor> { | ||
24 | + private static final long serialVersionUID = 1L; | ||
25 | + | ||
26 | + private final TenantId tenantId; | ||
27 | + private final DeviceId deviceId; | ||
28 | + | ||
29 | + public DeviceActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { | ||
30 | + super(context); | ||
31 | + this.tenantId = tenantId; | ||
32 | + this.deviceId = deviceId; | ||
33 | + } | ||
34 | + | ||
35 | + @Override | ||
36 | + public DeviceActor create() { | ||
37 | + return new DeviceActor(context, tenantId, deviceId); | ||
38 | + } | ||
39 | +} |
@@ -24,6 +24,8 @@ import com.google.common.util.concurrent.ListenableFuture; | @@ -24,6 +24,8 @@ import com.google.common.util.concurrent.ListenableFuture; | ||
24 | import com.google.gson.Gson; | 24 | import com.google.gson.Gson; |
25 | import com.google.gson.JsonObject; | 25 | import com.google.gson.JsonObject; |
26 | import com.google.gson.JsonParser; | 26 | import com.google.gson.JsonParser; |
27 | +import com.google.protobuf.InvalidProtocolBufferException; | ||
28 | +import lombok.extern.slf4j.Slf4j; | ||
27 | import org.thingsboard.rule.engine.api.RpcError; | 29 | import org.thingsboard.rule.engine.api.RpcError; |
28 | import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; | 30 | import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; |
29 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; | 31 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; |
@@ -88,6 +90,7 @@ import java.util.stream.Collectors; | @@ -88,6 +90,7 @@ import java.util.stream.Collectors; | ||
88 | /** | 90 | /** |
89 | * @author Andrew Shvayka | 91 | * @author Andrew Shvayka |
90 | */ | 92 | */ |
93 | +@Slf4j | ||
91 | class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | 94 | class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
92 | 95 | ||
93 | final TenantId tenantId; | 96 | final TenantId tenantId; |
@@ -106,8 +109,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -106,8 +109,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
106 | private String deviceType; | 109 | private String deviceType; |
107 | private TbMsgMetaData defaultMetaData; | 110 | private TbMsgMetaData defaultMetaData; |
108 | 111 | ||
109 | - DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) { | ||
110 | - super(systemContext, logger); | 112 | + DeviceActorMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { |
113 | + super(systemContext); | ||
111 | this.tenantId = tenantId; | 114 | this.tenantId = tenantId; |
112 | this.deviceId = deviceId; | 115 | this.deviceId = deviceId; |
113 | this.sessions = new LinkedHashMap<>(); | 116 | this.sessions = new LinkedHashMap<>(); |
@@ -136,30 +139,30 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -136,30 +139,30 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
136 | 139 | ||
137 | long timeout = request.getExpirationTime() - System.currentTimeMillis(); | 140 | long timeout = request.getExpirationTime() - System.currentTimeMillis(); |
138 | if (timeout <= 0) { | 141 | if (timeout <= 0) { |
139 | - logger.debug("[{}][{}] Ignoring message due to exp time reached", deviceId, request.getId(), request.getExpirationTime()); | 142 | + log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime()); |
140 | return; | 143 | return; |
141 | } | 144 | } |
142 | 145 | ||
143 | boolean sent = rpcSubscriptions.size() > 0; | 146 | boolean sent = rpcSubscriptions.size() > 0; |
144 | Set<UUID> syncSessionSet = new HashSet<>(); | 147 | Set<UUID> syncSessionSet = new HashSet<>(); |
145 | - rpcSubscriptions.entrySet().forEach(sub -> { | ||
146 | - sendToTransport(rpcRequest, sub.getKey(), sub.getValue().getNodeId()); | ||
147 | - if (TransportProtos.SessionType.SYNC == sub.getValue().getType()) { | ||
148 | - syncSessionSet.add(sub.getKey()); | 148 | + rpcSubscriptions.forEach((key, value) -> { |
149 | + sendToTransport(rpcRequest, key, value.getNodeId()); | ||
150 | + if (TransportProtos.SessionType.SYNC == value.getType()) { | ||
151 | + syncSessionSet.add(key); | ||
149 | } | 152 | } |
150 | }); | 153 | }); |
151 | syncSessionSet.forEach(rpcSubscriptions::remove); | 154 | syncSessionSet.forEach(rpcSubscriptions::remove); |
152 | 155 | ||
153 | if (request.isOneway() && sent) { | 156 | if (request.isOneway() && sent) { |
154 | - logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); | 157 | + log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); |
155 | systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); | 158 | systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); |
156 | } else { | 159 | } else { |
157 | registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); | 160 | registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); |
158 | } | 161 | } |
159 | if (sent) { | 162 | if (sent) { |
160 | - logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); | 163 | + log.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); |
161 | } else { | 164 | } else { |
162 | - logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); | 165 | + log.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); |
163 | } | 166 | } |
164 | } | 167 | } |
165 | 168 | ||
@@ -172,7 +175,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -172,7 +175,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
172 | void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) { | 175 | void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) { |
173 | ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); | 176 | ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); |
174 | if (requestMd != null) { | 177 | if (requestMd != null) { |
175 | - logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); | 178 | + log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); |
176 | systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), | 179 | systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), |
177 | null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); | 180 | null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); |
178 | } | 181 | } |
@@ -181,13 +184,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -181,13 +184,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
181 | private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) { | 184 | private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) { |
182 | TransportProtos.SessionType sessionType = getSessionType(sessionId); | 185 | TransportProtos.SessionType sessionType = getSessionType(sessionId); |
183 | if (!toDeviceRpcPendingMap.isEmpty()) { | 186 | if (!toDeviceRpcPendingMap.isEmpty()) { |
184 | - logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); | 187 | + log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); |
185 | if (sessionType == TransportProtos.SessionType.SYNC) { | 188 | if (sessionType == TransportProtos.SessionType.SYNC) { |
186 | - logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); | 189 | + log.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); |
187 | rpcSubscriptions.remove(sessionId); | 190 | rpcSubscriptions.remove(sessionId); |
188 | } | 191 | } |
189 | } else { | 192 | } else { |
190 | - logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); | 193 | + log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); |
191 | } | 194 | } |
192 | Set<Integer> sentOneWayIds = new HashSet<>(); | 195 | Set<Integer> sentOneWayIds = new HashSet<>(); |
193 | if (sessionType == TransportProtos.SessionType.ASYNC) { | 196 | if (sessionType == TransportProtos.SessionType.ASYNC) { |
@@ -335,7 +338,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -335,7 +338,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
335 | void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) { | 338 | void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) { |
336 | ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId()); | 339 | ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId()); |
337 | if (data != null) { | 340 | if (data != null) { |
338 | - logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId()); | 341 | + log.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId()); |
339 | sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() | 342 | sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() |
340 | .setRequestId(msg.getId()).setError("timeout").build() | 343 | .setRequestId(msg.getId()).setError("timeout").build() |
341 | , data.getSessionId(), data.getNodeId()); | 344 | , data.getSessionId(), data.getNodeId()); |
@@ -346,9 +349,12 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -346,9 +349,12 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
346 | int requestId = msg.getMsg().getRequestId(); | 349 | int requestId = msg.getMsg().getRequestId(); |
347 | ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(requestId); | 350 | ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(requestId); |
348 | if (data != null) { | 351 | if (data != null) { |
352 | + log.debug("[{}] Pushing reply to [{}][{}]!", deviceId, data.getNodeId(), data.getSessionId()); | ||
349 | sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() | 353 | sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() |
350 | .setRequestId(requestId).setPayload(msg.getMsg().getData()).build() | 354 | .setRequestId(requestId).setPayload(msg.getMsg().getData()).build() |
351 | , data.getSessionId(), data.getNodeId()); | 355 | , data.getSessionId(), data.getNodeId()); |
356 | + } else { | ||
357 | + log.debug("[{}][{}] Pending RPC request to server not found!", deviceId, requestId); | ||
352 | } | 358 | } |
353 | } | 359 | } |
354 | 360 | ||
@@ -380,7 +386,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -380,7 +386,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
380 | hasNotificationData = true; | 386 | hasNotificationData = true; |
381 | } | 387 | } |
382 | } else { | 388 | } else { |
383 | - logger.debug("[{}] No public server side attributes changed!", deviceId); | 389 | + log.debug("[{}] No public server side attributes changed!", deviceId); |
384 | } | 390 | } |
385 | } | 391 | } |
386 | } | 392 | } |
@@ -391,27 +397,27 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -391,27 +397,27 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
391 | }); | 397 | }); |
392 | } | 398 | } |
393 | } else { | 399 | } else { |
394 | - logger.debug("[{}] No registered attributes subscriptions to process!", deviceId); | 400 | + log.debug("[{}] No registered attributes subscriptions to process!", deviceId); |
395 | } | 401 | } |
396 | } | 402 | } |
397 | 403 | ||
398 | private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) { | 404 | private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) { |
399 | UUID sessionId = getSessionId(sessionInfo); | 405 | UUID sessionId = getSessionId(sessionInfo); |
400 | - logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); | 406 | + log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); |
401 | ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); | 407 | ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); |
402 | boolean success = requestMd != null; | 408 | boolean success = requestMd != null; |
403 | if (success) { | 409 | if (success) { |
404 | systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), | 410 | systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), |
405 | responseMsg.getPayload(), null)); | 411 | responseMsg.getPayload(), null)); |
406 | } else { | 412 | } else { |
407 | - logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); | 413 | + log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); |
408 | } | 414 | } |
409 | } | 415 | } |
410 | 416 | ||
411 | private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) { | 417 | private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) { |
412 | UUID sessionId = getSessionId(sessionInfo); | 418 | UUID sessionId = getSessionId(sessionInfo); |
413 | if (subscribeCmd.getUnsubscribe()) { | 419 | if (subscribeCmd.getUnsubscribe()) { |
414 | - logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); | 420 | + log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); |
415 | attributeSubscriptions.remove(sessionId); | 421 | attributeSubscriptions.remove(sessionId); |
416 | } else { | 422 | } else { |
417 | SessionInfoMetaData sessionMD = sessions.get(sessionId); | 423 | SessionInfoMetaData sessionMD = sessions.get(sessionId); |
@@ -419,7 +425,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -419,7 +425,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
419 | sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId())); | 425 | sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId())); |
420 | } | 426 | } |
421 | sessionMD.setSubscribedToAttributes(true); | 427 | sessionMD.setSubscribedToAttributes(true); |
422 | - logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); | 428 | + log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); |
423 | attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo()); | 429 | attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo()); |
424 | dumpSessions(); | 430 | dumpSessions(); |
425 | } | 431 | } |
@@ -432,7 +438,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -432,7 +438,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
432 | private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) { | 438 | private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) { |
433 | UUID sessionId = getSessionId(sessionInfo); | 439 | UUID sessionId = getSessionId(sessionInfo); |
434 | if (subscribeCmd.getUnsubscribe()) { | 440 | if (subscribeCmd.getUnsubscribe()) { |
435 | - logger.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); | 441 | + log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); |
436 | rpcSubscriptions.remove(sessionId); | 442 | rpcSubscriptions.remove(sessionId); |
437 | } else { | 443 | } else { |
438 | SessionInfoMetaData sessionMD = sessions.get(sessionId); | 444 | SessionInfoMetaData sessionMD = sessions.get(sessionId); |
@@ -440,7 +446,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -440,7 +446,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
440 | sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId())); | 446 | sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId())); |
441 | } | 447 | } |
442 | sessionMD.setSubscribedToRPC(true); | 448 | sessionMD.setSubscribedToRPC(true); |
443 | - logger.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); | 449 | + log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); |
444 | rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo()); | 450 | rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo()); |
445 | sendPendingRequests(context, sessionId, sessionInfo); | 451 | sendPendingRequests(context, sessionId, sessionInfo); |
446 | dumpSessions(); | 452 | dumpSessions(); |
@@ -451,10 +457,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -451,10 +457,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
451 | UUID sessionId = getSessionId(sessionInfo); | 457 | UUID sessionId = getSessionId(sessionInfo); |
452 | if (msg.getEvent() == SessionEvent.OPEN) { | 458 | if (msg.getEvent() == SessionEvent.OPEN) { |
453 | if (sessions.containsKey(sessionId)) { | 459 | if (sessions.containsKey(sessionId)) { |
454 | - logger.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); | 460 | + log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); |
455 | return; | 461 | return; |
456 | } | 462 | } |
457 | - logger.debug("[{}] Processing new session [{}]", deviceId, sessionId); | 463 | + log.debug("[{}] Processing new session [{}]", deviceId, sessionId); |
458 | if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { | 464 | if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { |
459 | UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); | 465 | UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); |
460 | if (sessionIdToRemove != null) { | 466 | if (sessionIdToRemove != null) { |
@@ -467,7 +473,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -467,7 +473,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
467 | } | 473 | } |
468 | dumpSessions(); | 474 | dumpSessions(); |
469 | } else if (msg.getEvent() == SessionEvent.CLOSED) { | 475 | } else if (msg.getEvent() == SessionEvent.CLOSED) { |
470 | - logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); | 476 | + log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); |
471 | sessions.remove(sessionId); | 477 | sessions.remove(sessionId); |
472 | attributeSubscriptions.remove(sessionId); | 478 | attributeSubscriptions.remove(sessionId); |
473 | rpcSubscriptions.remove(sessionId); | 479 | rpcSubscriptions.remove(sessionId); |
@@ -478,19 +484,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -478,19 +484,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
478 | } | 484 | } |
479 | } | 485 | } |
480 | 486 | ||
481 | - private void handleSessionActivity(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto subscriptionInfo) { | ||
482 | - UUID sessionId = getSessionId(sessionInfo); | ||
483 | - SessionInfoMetaData sessionMD = sessions.get(sessionId); | ||
484 | - if (sessionMD != null) { | ||
485 | - sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime()); | ||
486 | - sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription()); | ||
487 | - sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription()); | ||
488 | - if (subscriptionInfo.getAttributeSubscription()) { | ||
489 | - attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | ||
490 | - } | ||
491 | - if (subscriptionInfo.getRpcSubscription()) { | ||
492 | - rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | ||
493 | - } | 487 | + private void handleSessionActivity(ActorContext context, SessionInfoProto sessionInfoProto, TransportProtos.SubscriptionInfoProto subscriptionInfo) { |
488 | + UUID sessionId = getSessionId(sessionInfoProto); | ||
489 | + SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId, | ||
490 | + id -> new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfoProto.getNodeId()), 0L)); | ||
491 | + | ||
492 | + sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime()); | ||
493 | + sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription()); | ||
494 | + sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription()); | ||
495 | + if (subscriptionInfo.getAttributeSubscription()) { | ||
496 | + attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | ||
497 | + } | ||
498 | + if (subscriptionInfo.getRpcSubscription()) { | ||
499 | + rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); | ||
494 | } | 500 | } |
495 | dumpSessions(); | 501 | dumpSessions(); |
496 | } | 502 | } |
@@ -623,10 +629,16 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -623,10 +629,16 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
623 | } | 629 | } |
624 | 630 | ||
625 | private void restoreSessions() { | 631 | private void restoreSessions() { |
626 | - logger.debug("[{}] Restoring sessions from cache", deviceId); | ||
627 | - TransportProtos.DeviceSessionsCacheEntry sessionsDump = systemContext.getDeviceSessionCacheService().get(deviceId); | ||
628 | - if (sessionsDump.getSerializedSize() == 0) { | ||
629 | - logger.debug("[{}] No session information found", deviceId); | 632 | + log.debug("[{}] Restoring sessions from cache", deviceId); |
633 | + TransportProtos.DeviceSessionsCacheEntry sessionsDump = null; | ||
634 | + try { | ||
635 | + sessionsDump = TransportProtos.DeviceSessionsCacheEntry.parseFrom(systemContext.getDeviceSessionCacheService().get(deviceId)); | ||
636 | + } catch (InvalidProtocolBufferException e) { | ||
637 | + log.warn("[{}] Failed to decode device sessions from cache", deviceId); | ||
638 | + return; | ||
639 | + } | ||
640 | + if (sessionsDump.getSessionsCount() == 0) { | ||
641 | + log.debug("[{}] No session information found", deviceId); | ||
630 | return; | 642 | return; |
631 | } | 643 | } |
632 | for (TransportProtos.SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) { | 644 | for (TransportProtos.SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) { |
@@ -644,13 +656,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -644,13 +656,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
644 | rpcSubscriptions.put(sessionId, sessionInfo); | 656 | rpcSubscriptions.put(sessionId, sessionInfo); |
645 | sessionMD.setSubscribedToRPC(true); | 657 | sessionMD.setSubscribedToRPC(true); |
646 | } | 658 | } |
647 | - logger.debug("[{}] Restored session: {}", deviceId, sessionMD); | 659 | + log.debug("[{}] Restored session: {}", deviceId, sessionMD); |
648 | } | 660 | } |
649 | - logger.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); | 661 | + log.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); |
650 | } | 662 | } |
651 | 663 | ||
652 | private void dumpSessions() { | 664 | private void dumpSessions() { |
653 | - logger.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); | 665 | + log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); |
654 | List<TransportProtos.SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size()); | 666 | List<TransportProtos.SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size()); |
655 | sessions.forEach((uuid, sessionMD) -> { | 667 | sessions.forEach((uuid, sessionMD) -> { |
656 | if (sessionMD.getSessionInfo().getType() == TransportProtos.SessionType.SYNC) { | 668 | if (sessionMD.getSessionInfo().getType() == TransportProtos.SessionType.SYNC) { |
@@ -668,11 +680,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -668,11 +680,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
668 | sessionsList.add(TransportProtos.SessionSubscriptionInfoProto.newBuilder() | 680 | sessionsList.add(TransportProtos.SessionSubscriptionInfoProto.newBuilder() |
669 | .setSessionInfo(sessionInfoProto) | 681 | .setSessionInfo(sessionInfoProto) |
670 | .setSubscriptionInfo(subscriptionInfoProto).build()); | 682 | .setSubscriptionInfo(subscriptionInfoProto).build()); |
671 | - logger.debug("[{}] Dumping session: {}", deviceId, sessionMD); | 683 | + log.debug("[{}] Dumping session: {}", deviceId, sessionMD); |
672 | }); | 684 | }); |
673 | systemContext.getDeviceSessionCacheService() | 685 | systemContext.getDeviceSessionCacheService() |
674 | .put(deviceId, TransportProtos.DeviceSessionsCacheEntry.newBuilder() | 686 | .put(deviceId, TransportProtos.DeviceSessionsCacheEntry.newBuilder() |
675 | - .addAllSessions(sessionsList).build()); | 687 | + .addAllSessions(sessionsList).build().toByteArray()); |
676 | } | 688 | } |
677 | 689 | ||
678 | void initSessionTimeout(ActorContext context) { | 690 | void initSessionTimeout(ActorContext context) { |
@@ -17,10 +17,12 @@ package org.thingsboard.server.actors.rpc; | @@ -17,10 +17,12 @@ package org.thingsboard.server.actors.rpc; | ||
17 | 17 | ||
18 | import akka.actor.ActorRef; | 18 | import akka.actor.ActorRef; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | +import org.thingsboard.server.actors.ActorSystemContext; | ||
20 | import org.thingsboard.server.actors.service.ActorService; | 21 | import org.thingsboard.server.actors.service.ActorService; |
21 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; | 22 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
22 | import org.thingsboard.server.service.cluster.rpc.GrpcSession; | 23 | import org.thingsboard.server.service.cluster.rpc.GrpcSession; |
23 | import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; | 24 | import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; |
25 | +import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService; | ||
24 | 26 | ||
25 | /** | 27 | /** |
26 | * @author Andrew Shvayka | 28 | * @author Andrew Shvayka |
@@ -28,19 +30,21 @@ import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; | @@ -28,19 +30,21 @@ import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; | ||
28 | @Slf4j | 30 | @Slf4j |
29 | public class BasicRpcSessionListener implements GrpcSessionListener { | 31 | public class BasicRpcSessionListener implements GrpcSessionListener { |
30 | 32 | ||
33 | + private final ClusterRpcCallbackExecutorService callbackExecutorService; | ||
31 | private final ActorService service; | 34 | private final ActorService service; |
32 | private final ActorRef manager; | 35 | private final ActorRef manager; |
33 | private final ActorRef self; | 36 | private final ActorRef self; |
34 | 37 | ||
35 | - BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) { | ||
36 | - this.service = service; | 38 | + BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) { |
39 | + this.service = context.getActorService(); | ||
40 | + this.callbackExecutorService = context.getClusterRpcCallbackExecutor(); | ||
37 | this.manager = manager; | 41 | this.manager = manager; |
38 | this.self = self; | 42 | this.self = self; |
39 | } | 43 | } |
40 | 44 | ||
41 | @Override | 45 | @Override |
42 | public void onConnected(GrpcSession session) { | 46 | public void onConnected(GrpcSession session) { |
43 | - log.info("{} session started -> {}", getType(session), session.getRemoteServer()); | 47 | + log.info("[{}][{}] session started", session.getRemoteServer(), getType(session)); |
44 | if (!session.isClient()) { | 48 | if (!session.isClient()) { |
45 | manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self); | 49 | manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self); |
46 | } | 50 | } |
@@ -48,21 +52,25 @@ public class BasicRpcSessionListener implements GrpcSessionListener { | @@ -48,21 +52,25 @@ public class BasicRpcSessionListener implements GrpcSessionListener { | ||
48 | 52 | ||
49 | @Override | 53 | @Override |
50 | public void onDisconnected(GrpcSession session) { | 54 | public void onDisconnected(GrpcSession session) { |
51 | - log.info("{} session closed -> {}", getType(session), session.getRemoteServer()); | 55 | + log.info("[{}][{}] session closed", session.getRemoteServer(), getType(session)); |
52 | manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self); | 56 | manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self); |
53 | } | 57 | } |
54 | 58 | ||
55 | @Override | 59 | @Override |
56 | public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) { | 60 | public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) { |
57 | - log.trace("{} Service [{}] received session actor msg {}", getType(session), | ||
58 | - session.getRemoteServer(), | ||
59 | - clusterMessage); | ||
60 | - service.onReceivedMsg(session.getRemoteServer(), clusterMessage); | 61 | + log.trace("Received session actor msg from [{}][{}]: {}", session.getRemoteServer(), getType(session), clusterMessage); |
62 | + callbackExecutorService.execute(() -> { | ||
63 | + try { | ||
64 | + service.onReceivedMsg(session.getRemoteServer(), clusterMessage); | ||
65 | + } catch (Exception e) { | ||
66 | + log.debug("[{}][{}] Failed to process cluster message: {}", session.getRemoteServer(), getType(session), clusterMessage, e); | ||
67 | + } | ||
68 | + }); | ||
61 | } | 69 | } |
62 | 70 | ||
63 | @Override | 71 | @Override |
64 | public void onError(GrpcSession session, Throwable t) { | 72 | public void onError(GrpcSession session, Throwable t) { |
65 | - log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t); | 73 | + log.warn("[{}][{}] session got error -> {}", session.getRemoteServer(), getType(session), t); |
66 | manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self); | 74 | manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self); |
67 | session.close(); | 75 | session.close(); |
68 | } | 76 | } |
@@ -16,9 +16,12 @@ | @@ -16,9 +16,12 @@ | ||
16 | package org.thingsboard.server.actors.rpc; | 16 | package org.thingsboard.server.actors.rpc; |
17 | 17 | ||
18 | import akka.actor.ActorRef; | 18 | import akka.actor.ActorRef; |
19 | +import akka.actor.OneForOneStrategy; | ||
19 | import akka.actor.Props; | 20 | import akka.actor.Props; |
21 | +import akka.actor.SupervisorStrategy; | ||
20 | import akka.event.Logging; | 22 | import akka.event.Logging; |
21 | import akka.event.LoggingAdapter; | 23 | import akka.event.LoggingAdapter; |
24 | +import lombok.extern.slf4j.Slf4j; | ||
22 | import org.thingsboard.server.actors.ActorSystemContext; | 25 | import org.thingsboard.server.actors.ActorSystemContext; |
23 | import org.thingsboard.server.actors.service.ContextAwareActor; | 26 | import org.thingsboard.server.actors.service.ContextAwareActor; |
24 | import org.thingsboard.server.actors.service.ContextBasedCreator; | 27 | import org.thingsboard.server.actors.service.ContextBasedCreator; |
@@ -29,6 +32,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; | @@ -29,6 +32,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; | ||
29 | import org.thingsboard.server.common.msg.cluster.ServerType; | 32 | import org.thingsboard.server.common.msg.cluster.ServerType; |
30 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; | 33 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
31 | import org.thingsboard.server.service.cluster.discovery.ServerInstance; | 34 | import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
35 | +import scala.concurrent.duration.Duration; | ||
32 | 36 | ||
33 | import java.util.*; | 37 | import java.util.*; |
34 | 38 | ||
@@ -37,15 +41,11 @@ import java.util.*; | @@ -37,15 +41,11 @@ import java.util.*; | ||
37 | */ | 41 | */ |
38 | public class RpcManagerActor extends ContextAwareActor { | 42 | public class RpcManagerActor extends ContextAwareActor { |
39 | 43 | ||
40 | - private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); | ||
41 | - | ||
42 | private final Map<ServerAddress, SessionActorInfo> sessionActors; | 44 | private final Map<ServerAddress, SessionActorInfo> sessionActors; |
43 | - | ||
44 | private final Map<ServerAddress, Queue<ClusterAPIProtos.ClusterMessage>> pendingMsgs; | 45 | private final Map<ServerAddress, Queue<ClusterAPIProtos.ClusterMessage>> pendingMsgs; |
45 | - | ||
46 | private final ServerAddress instance; | 46 | private final ServerAddress instance; |
47 | 47 | ||
48 | - RpcManagerActor(ActorSystemContext systemContext) { | 48 | + private RpcManagerActor(ActorSystemContext systemContext) { |
49 | super(systemContext); | 49 | super(systemContext); |
50 | this.sessionActors = new HashMap<>(); | 50 | this.sessionActors = new HashMap<>(); |
51 | this.pendingMsgs = new HashMap<>(); | 51 | this.pendingMsgs = new HashMap<>(); |
@@ -64,7 +64,7 @@ public class RpcManagerActor extends ContextAwareActor { | @@ -64,7 +64,7 @@ public class RpcManagerActor extends ContextAwareActor { | ||
64 | } | 64 | } |
65 | 65 | ||
66 | @Override | 66 | @Override |
67 | - public void onReceive(Object msg) throws Exception { | 67 | + public void onReceive(Object msg) { |
68 | if (msg instanceof ClusterAPIProtos.ClusterMessage) { | 68 | if (msg instanceof ClusterAPIProtos.ClusterMessage) { |
69 | onMsg((ClusterAPIProtos.ClusterMessage) msg); | 69 | onMsg((ClusterAPIProtos.ClusterMessage) msg); |
70 | } else if (msg instanceof RpcBroadcastMsg) { | 70 | } else if (msg instanceof RpcBroadcastMsg) { |
@@ -116,7 +116,7 @@ public class RpcManagerActor extends ContextAwareActor { | @@ -116,7 +116,7 @@ public class RpcManagerActor extends ContextAwareActor { | ||
116 | queue.add(msg); | 116 | queue.add(msg); |
117 | } | 117 | } |
118 | } else { | 118 | } else { |
119 | - logger.warning("Cluster msg doesn't have server address [{}]", msg); | 119 | + log.warn("Cluster msg doesn't have server address [{}]", msg); |
120 | } | 120 | } |
121 | } | 121 | } |
122 | 122 | ||
@@ -164,6 +164,7 @@ public class RpcManagerActor extends ContextAwareActor { | @@ -164,6 +164,7 @@ public class RpcManagerActor extends ContextAwareActor { | ||
164 | log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect); | 164 | log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect); |
165 | SessionActorInfo sessionRef = sessionActors.get(remoteAddress); | 165 | SessionActorInfo sessionRef = sessionActors.get(remoteAddress); |
166 | if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) { | 166 | if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) { |
167 | + context().stop(sessionRef.actor); | ||
167 | sessionActors.remove(remoteAddress); | 168 | sessionActors.remove(remoteAddress); |
168 | pendingMsgs.remove(remoteAddress); | 169 | pendingMsgs.remove(remoteAddress); |
169 | if (reconnect) { | 170 | if (reconnect) { |
@@ -173,9 +174,13 @@ public class RpcManagerActor extends ContextAwareActor { | @@ -173,9 +174,13 @@ public class RpcManagerActor extends ContextAwareActor { | ||
173 | } | 174 | } |
174 | 175 | ||
175 | private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) { | 176 | private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) { |
176 | - ActorRef actorRef = createSessionActor(msg); | ||
177 | if (msg.getRemoteAddress() != null) { | 177 | if (msg.getRemoteAddress() != null) { |
178 | - register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); | 178 | + if (!sessionActors.containsKey(msg.getRemoteAddress())) { |
179 | + ActorRef actorRef = createSessionActor(msg); | ||
180 | + register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); | ||
181 | + } | ||
182 | + } else { | ||
183 | + createSessionActor(msg); | ||
179 | } | 184 | } |
180 | } | 185 | } |
181 | 186 | ||
@@ -194,7 +199,8 @@ public class RpcManagerActor extends ContextAwareActor { | @@ -194,7 +199,8 @@ public class RpcManagerActor extends ContextAwareActor { | ||
194 | private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) { | 199 | private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) { |
195 | log.info("[{}] Creating session actor.", msg.getMsgUid()); | 200 | log.info("[{}] Creating session actor.", msg.getMsgUid()); |
196 | ActorRef actor = context().actorOf( | 201 | ActorRef actor = context().actorOf( |
197 | - Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); | 202 | + Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())) |
203 | + .withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); | ||
198 | actor.tell(msg, context().self()); | 204 | actor.tell(msg, context().self()); |
199 | return actor; | 205 | return actor; |
200 | } | 206 | } |
@@ -207,8 +213,18 @@ public class RpcManagerActor extends ContextAwareActor { | @@ -207,8 +213,18 @@ public class RpcManagerActor extends ContextAwareActor { | ||
207 | } | 213 | } |
208 | 214 | ||
209 | @Override | 215 | @Override |
210 | - public RpcManagerActor create() throws Exception { | 216 | + public RpcManagerActor create() { |
211 | return new RpcManagerActor(context); | 217 | return new RpcManagerActor(context); |
212 | } | 218 | } |
213 | } | 219 | } |
220 | + | ||
221 | + @Override | ||
222 | + public SupervisorStrategy supervisorStrategy() { | ||
223 | + return strategy; | ||
224 | + } | ||
225 | + | ||
226 | + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> { | ||
227 | + log.warn("Unknown failure", t); | ||
228 | + return SupervisorStrategy.resume(); | ||
229 | + }); | ||
214 | } | 230 | } |
@@ -15,12 +15,10 @@ | @@ -15,12 +15,10 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.actors.rpc; | 16 | package org.thingsboard.server.actors.rpc; |
17 | 17 | ||
18 | -import akka.event.Logging; | ||
19 | -import akka.event.LoggingAdapter; | ||
20 | -import io.grpc.Channel; | ||
21 | import io.grpc.ManagedChannel; | 18 | import io.grpc.ManagedChannel; |
22 | import io.grpc.ManagedChannelBuilder; | 19 | import io.grpc.ManagedChannelBuilder; |
23 | import io.grpc.stub.StreamObserver; | 20 | import io.grpc.stub.StreamObserver; |
21 | +import lombok.extern.slf4j.Slf4j; | ||
24 | import org.thingsboard.server.actors.ActorSystemContext; | 22 | import org.thingsboard.server.actors.ActorSystemContext; |
25 | import org.thingsboard.server.actors.service.ContextAwareActor; | 23 | import org.thingsboard.server.actors.service.ContextAwareActor; |
26 | import org.thingsboard.server.actors.service.ContextBasedCreator; | 24 | import org.thingsboard.server.actors.service.ContextBasedCreator; |
@@ -38,15 +36,15 @@ import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CO | @@ -38,15 +36,15 @@ import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CO | ||
38 | /** | 36 | /** |
39 | * @author Andrew Shvayka | 37 | * @author Andrew Shvayka |
40 | */ | 38 | */ |
39 | +@Slf4j | ||
41 | public class RpcSessionActor extends ContextAwareActor { | 40 | public class RpcSessionActor extends ContextAwareActor { |
42 | 41 | ||
43 | - private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); | ||
44 | 42 | ||
45 | private final UUID sessionId; | 43 | private final UUID sessionId; |
46 | private GrpcSession session; | 44 | private GrpcSession session; |
47 | private GrpcSessionListener listener; | 45 | private GrpcSessionListener listener; |
48 | 46 | ||
49 | - public RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { | 47 | + private RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { |
50 | super(systemContext); | 48 | super(systemContext); |
51 | this.sessionId = sessionId; | 49 | this.sessionId = sessionId; |
52 | } | 50 | } |
@@ -58,7 +56,7 @@ public class RpcSessionActor extends ContextAwareActor { | @@ -58,7 +56,7 @@ public class RpcSessionActor extends ContextAwareActor { | ||
58 | } | 56 | } |
59 | 57 | ||
60 | @Override | 58 | @Override |
61 | - public void onReceive(Object msg) throws Exception { | 59 | + public void onReceive(Object msg) { |
62 | if (msg instanceof ClusterAPIProtos.ClusterMessage) { | 60 | if (msg instanceof ClusterAPIProtos.ClusterMessage) { |
63 | tell((ClusterAPIProtos.ClusterMessage) msg); | 61 | tell((ClusterAPIProtos.ClusterMessage) msg); |
64 | } else if (msg instanceof RpcSessionCreateRequestMsg) { | 62 | } else if (msg instanceof RpcSessionCreateRequestMsg) { |
@@ -67,19 +65,29 @@ public class RpcSessionActor extends ContextAwareActor { | @@ -67,19 +65,29 @@ public class RpcSessionActor extends ContextAwareActor { | ||
67 | } | 65 | } |
68 | 66 | ||
69 | private void tell(ClusterAPIProtos.ClusterMessage msg) { | 67 | private void tell(ClusterAPIProtos.ClusterMessage msg) { |
70 | - session.sendMsg(msg); | 68 | + if (session != null) { |
69 | + session.sendMsg(msg); | ||
70 | + } else { | ||
71 | + log.trace("Failed to send message due to missing session!"); | ||
72 | + } | ||
71 | } | 73 | } |
72 | 74 | ||
73 | @Override | 75 | @Override |
74 | public void postStop() { | 76 | public void postStop() { |
75 | - log.info("Closing session -> {}", session.getRemoteServer()); | ||
76 | - session.close(); | 77 | + if (session != null) { |
78 | + log.info("Closing session -> {}", session.getRemoteServer()); | ||
79 | + try { | ||
80 | + session.close(); | ||
81 | + } catch (RuntimeException e) { | ||
82 | + log.trace("Failed to close session!", e); | ||
83 | + } | ||
84 | + } | ||
77 | } | 85 | } |
78 | 86 | ||
79 | private void initSession(RpcSessionCreateRequestMsg msg) { | 87 | private void initSession(RpcSessionCreateRequestMsg msg) { |
80 | log.info("[{}] Initializing session", context().self()); | 88 | log.info("[{}] Initializing session", context().self()); |
81 | ServerAddress remoteServer = msg.getRemoteAddress(); | 89 | ServerAddress remoteServer = msg.getRemoteAddress(); |
82 | - listener = new BasicRpcSessionListener(systemContext.getActorService(), context().parent(), context().self()); | 90 | + listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self()); |
83 | if (msg.getRemoteAddress() == null) { | 91 | if (msg.getRemoteAddress() == null) { |
84 | // Server session | 92 | // Server session |
85 | session = new GrpcSession(listener); | 93 | session = new GrpcSession(listener); |
@@ -113,7 +121,7 @@ public class RpcSessionActor extends ContextAwareActor { | @@ -113,7 +121,7 @@ public class RpcSessionActor extends ContextAwareActor { | ||
113 | } | 121 | } |
114 | 122 | ||
115 | @Override | 123 | @Override |
116 | - public RpcSessionActor create() throws Exception { | 124 | + public RpcSessionActor create() { |
117 | return new RpcSessionActor(context, sessionId); | 125 | return new RpcSessionActor(context, sessionId); |
118 | } | 126 | } |
119 | } | 127 | } |
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.actors.ruleChain; | 16 | package org.thingsboard.server.actors.ruleChain; |
17 | 17 | ||
18 | +import akka.actor.ActorInitializationException; | ||
18 | import akka.actor.OneForOneStrategy; | 19 | import akka.actor.OneForOneStrategy; |
19 | import akka.actor.SupervisorStrategy; | 20 | import akka.actor.SupervisorStrategy; |
20 | import org.thingsboard.server.actors.ActorSystemContext; | 21 | import org.thingsboard.server.actors.ActorSystemContext; |
@@ -33,7 +34,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe | @@ -33,7 +34,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe | ||
33 | private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) { | 34 | private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) { |
34 | super(systemContext, tenantId, ruleChainId); | 35 | super(systemContext, tenantId, ruleChainId); |
35 | setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext, | 36 | setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext, |
36 | - logger, context().parent(), context().self())); | 37 | + context().parent(), context().self())); |
37 | } | 38 | } |
38 | 39 | ||
39 | @Override | 40 | @Override |
@@ -79,7 +80,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe | @@ -79,7 +80,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe | ||
79 | } | 80 | } |
80 | 81 | ||
81 | @Override | 82 | @Override |
82 | - public RuleChainActor create() throws Exception { | 83 | + public RuleChainActor create() { |
83 | return new RuleChainActor(context, tenantId, ruleChainId); | 84 | return new RuleChainActor(context, tenantId, ruleChainId); |
84 | } | 85 | } |
85 | } | 86 | } |
@@ -23,6 +23,7 @@ import com.datastax.driver.core.utils.UUIDs; | @@ -23,6 +23,7 @@ import com.datastax.driver.core.utils.UUIDs; | ||
23 | 23 | ||
24 | import java.util.Optional; | 24 | import java.util.Optional; |
25 | 25 | ||
26 | +import lombok.extern.slf4j.Slf4j; | ||
26 | import org.thingsboard.server.actors.ActorSystemContext; | 27 | import org.thingsboard.server.actors.ActorSystemContext; |
27 | import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; | 28 | import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; |
28 | import org.thingsboard.server.actors.service.DefaultActorService; | 29 | import org.thingsboard.server.actors.service.DefaultActorService; |
@@ -55,6 +56,7 @@ import java.util.stream.Collectors; | @@ -55,6 +56,7 @@ import java.util.stream.Collectors; | ||
55 | /** | 56 | /** |
56 | * @author Andrew Shvayka | 57 | * @author Andrew Shvayka |
57 | */ | 58 | */ |
59 | +@Slf4j | ||
58 | public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> { | 60 | public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> { |
59 | 61 | ||
60 | private static final long DEFAULT_CLUSTER_PARTITION = 0L; | 62 | private static final long DEFAULT_CLUSTER_PARTITION = 0L; |
@@ -67,24 +69,34 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -67,24 +69,34 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
67 | private RuleNodeId firstId; | 69 | private RuleNodeId firstId; |
68 | private RuleNodeCtx firstNode; | 70 | private RuleNodeCtx firstNode; |
69 | private boolean started; | 71 | private boolean started; |
72 | + private String ruleChainName; | ||
70 | 73 | ||
71 | RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext | 74 | RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext |
72 | - , LoggingAdapter logger, ActorRef parent, ActorRef self) { | ||
73 | - super(systemContext, logger, tenantId, ruleChainId); | 75 | + , ActorRef parent, ActorRef self) { |
76 | + super(systemContext, tenantId, ruleChainId); | ||
74 | this.parent = parent; | 77 | this.parent = parent; |
75 | this.self = self; | 78 | this.self = self; |
76 | this.nodeActors = new HashMap<>(); | 79 | this.nodeActors = new HashMap<>(); |
77 | this.nodeRoutes = new HashMap<>(); | 80 | this.nodeRoutes = new HashMap<>(); |
78 | this.service = systemContext.getRuleChainService(); | 81 | this.service = systemContext.getRuleChainService(); |
82 | + this.ruleChainName = ruleChainId.toString(); | ||
79 | } | 83 | } |
80 | 84 | ||
81 | @Override | 85 | @Override |
82 | - public void start(ActorContext context) throws Exception { | 86 | + public String getComponentName() { |
87 | + return null; | ||
88 | + } | ||
89 | + | ||
90 | + @Override | ||
91 | + public void start(ActorContext context) { | ||
83 | if (!started) { | 92 | if (!started) { |
84 | RuleChain ruleChain = service.findRuleChainById(entityId); | 93 | RuleChain ruleChain = service.findRuleChainById(entityId); |
94 | + ruleChainName = ruleChain.getName(); | ||
85 | List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId); | 95 | List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId); |
96 | + log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); | ||
86 | // Creating and starting the actors; | 97 | // Creating and starting the actors; |
87 | for (RuleNode ruleNode : ruleNodeList) { | 98 | for (RuleNode ruleNode : ruleNodeList) { |
99 | + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
88 | ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); | 100 | ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); |
89 | nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); | 101 | nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); |
90 | } | 102 | } |
@@ -96,16 +108,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -96,16 +108,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
96 | } | 108 | } |
97 | 109 | ||
98 | @Override | 110 | @Override |
99 | - public void onUpdate(ActorContext context) throws Exception { | 111 | + public void onUpdate(ActorContext context) { |
100 | RuleChain ruleChain = service.findRuleChainById(entityId); | 112 | RuleChain ruleChain = service.findRuleChainById(entityId); |
113 | + ruleChainName = ruleChain.getName(); | ||
101 | List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId); | 114 | List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId); |
102 | - | 115 | + log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); |
103 | for (RuleNode ruleNode : ruleNodeList) { | 116 | for (RuleNode ruleNode : ruleNodeList) { |
104 | RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); | 117 | RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); |
105 | if (existing == null) { | 118 | if (existing == null) { |
119 | + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
106 | ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); | 120 | ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); |
107 | nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); | 121 | nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); |
108 | } else { | 122 | } else { |
123 | + log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
109 | existing.setSelf(ruleNode); | 124 | existing.setSelf(ruleNode); |
110 | existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); | 125 | existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); |
111 | } | 126 | } |
@@ -114,6 +129,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -114,6 +129,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
114 | Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); | 129 | Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); |
115 | List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); | 130 | List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); |
116 | removedRules.forEach(ruleNodeId -> { | 131 | removedRules.forEach(ruleNodeId -> { |
132 | + log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId); | ||
117 | RuleNodeCtx removed = nodeActors.remove(ruleNodeId); | 133 | RuleNodeCtx removed = nodeActors.remove(ruleNodeId); |
118 | removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); | 134 | removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); |
119 | }); | 135 | }); |
@@ -122,7 +138,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -122,7 +138,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
122 | } | 138 | } |
123 | 139 | ||
124 | @Override | 140 | @Override |
125 | - public void stop(ActorContext context) throws Exception { | 141 | + public void stop(ActorContext context) { |
142 | + log.trace("[{}][{}] Stopping rule chain with {} nodes", tenantId, entityId, nodeActors.size()); | ||
126 | nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop); | 143 | nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop); |
127 | nodeActors.clear(); | 144 | nodeActors.clear(); |
128 | nodeRoutes.clear(); | 145 | nodeRoutes.clear(); |
@@ -131,7 +148,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -131,7 +148,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
131 | } | 148 | } |
132 | 149 | ||
133 | @Override | 150 | @Override |
134 | - public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { | 151 | + public void onClusterEventMsg(ClusterEventMsg msg) { |
135 | 152 | ||
136 | } | 153 | } |
137 | 154 | ||
@@ -148,10 +165,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -148,10 +165,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
148 | // Populating the routes map; | 165 | // Populating the routes map; |
149 | for (RuleNode ruleNode : ruleNodeList) { | 166 | for (RuleNode ruleNode : ruleNodeList) { |
150 | List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId()); | 167 | List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId()); |
168 | + log.trace("[{}][{}][{}] Processing rule node relations [{}]", tenantId, entityId, ruleNode.getId(), relations.size()); | ||
151 | if (relations.size() == 0) { | 169 | if (relations.size() == 0) { |
152 | nodeRoutes.put(ruleNode.getId(), Collections.emptyList()); | 170 | nodeRoutes.put(ruleNode.getId(), Collections.emptyList()); |
153 | } else { | 171 | } else { |
154 | for (EntityRelation relation : relations) { | 172 | for (EntityRelation relation : relations) { |
173 | + log.trace("[{}][{}][{}] Processing rule node relation [{}]", tenantId, entityId, ruleNode.getId(), relation.getTo()); | ||
155 | if (relation.getTo().getEntityType() == EntityType.RULE_NODE) { | 174 | if (relation.getTo().getEntityType() == EntityType.RULE_NODE) { |
156 | RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId())); | 175 | RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId())); |
157 | if (ruleNodeCtx == null) { | 176 | if (ruleNodeCtx == null) { |
@@ -165,13 +184,15 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -165,13 +184,15 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
165 | } | 184 | } |
166 | 185 | ||
167 | firstId = ruleChain.getFirstRuleNodeId(); | 186 | firstId = ruleChain.getFirstRuleNodeId(); |
168 | - firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId()); | 187 | + firstNode = nodeActors.get(firstId); |
169 | state = ComponentLifecycleState.ACTIVE; | 188 | state = ComponentLifecycleState.ACTIVE; |
170 | } | 189 | } |
171 | 190 | ||
172 | void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) { | 191 | void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) { |
192 | + log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, envelope.getTbMsg().getId(), envelope.getTbMsg()); | ||
173 | checkActive(); | 193 | checkActive(); |
174 | if (firstNode != null) { | 194 | if (firstNode != null) { |
195 | + log.trace("[{}][{}] Pushing message to first rule node", entityId, firstId); | ||
175 | pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), ""); | 196 | pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), ""); |
176 | } | 197 | } |
177 | } | 198 | } |
@@ -216,7 +237,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -216,7 +237,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
216 | 237 | ||
217 | private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) { | 238 | private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) { |
218 | TbMsg msg = envelope.getMsg(); | 239 | TbMsg msg = envelope.getMsg(); |
219 | - logger.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator()); | 240 | + log.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator()); |
220 | envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId); | 241 | envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId); |
221 | systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope)); | 242 | systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope)); |
222 | } | 243 | } |
@@ -230,17 +251,20 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -230,17 +251,20 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
230 | int relationsCount = relations.size(); | 251 | int relationsCount = relations.size(); |
231 | EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId(); | 252 | EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId(); |
232 | if (relationsCount == 0) { | 253 | if (relationsCount == 0) { |
254 | + log.trace("[{}][{}][{}] No outbound relations to process", tenantId, entityId, msg.getId()); | ||
233 | if (ackId != null) { | 255 | if (ackId != null) { |
234 | // TODO: Ack this message in Kafka | 256 | // TODO: Ack this message in Kafka |
235 | // queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition()); | 257 | // queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition()); |
236 | } | 258 | } |
237 | } else if (relationsCount == 1) { | 259 | } else if (relationsCount == 1) { |
238 | for (RuleNodeRelation relation : relations) { | 260 | for (RuleNodeRelation relation : relations) { |
261 | + log.trace("[{}][{}][{}] Pushing message to single target: [{}]", tenantId, entityId, msg.getId(), relation.getOut()); | ||
239 | pushToTarget(msg, relation.getOut(), relation.getType()); | 262 | pushToTarget(msg, relation.getOut(), relation.getType()); |
240 | } | 263 | } |
241 | } else { | 264 | } else { |
242 | for (RuleNodeRelation relation : relations) { | 265 | for (RuleNodeRelation relation : relations) { |
243 | EntityId target = relation.getOut(); | 266 | EntityId target = relation.getOut(); |
267 | + log.trace("[{}][{}][{}] Pushing message to multiple targets: [{}]", tenantId, entityId, msg.getId(), relation.getOut()); | ||
244 | switch (target.getEntityType()) { | 268 | switch (target.getEntityType()) { |
245 | case RULE_NODE: | 269 | case RULE_NODE: |
246 | enqueueAndForwardMsgCopyToNode(msg, target, relation.getType()); | 270 | enqueueAndForwardMsgCopyToNode(msg, target, relation.getType()); |
@@ -32,7 +32,7 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa | @@ -32,7 +32,7 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa | ||
32 | super(systemContext, tenantId, ruleNodeId); | 32 | super(systemContext, tenantId, ruleNodeId); |
33 | this.ruleChainId = ruleChainId; | 33 | this.ruleChainId = ruleChainId; |
34 | setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext, | 34 | setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext, |
35 | - logger, context().parent(), context().self())); | 35 | + context().parent(), context().self())); |
36 | } | 36 | } |
37 | 37 | ||
38 | @Override | 38 | @Override |
@@ -60,7 +60,9 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa | @@ -60,7 +60,9 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa | ||
60 | } | 60 | } |
61 | 61 | ||
62 | private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) { | 62 | private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) { |
63 | - logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); | 63 | + if (log.isDebugEnabled()) { |
64 | + log.debug("[{}][{}][{}] Going to process rule msg: {}", ruleChainId, id, processor.getComponentName(), msg.getMsg()); | ||
65 | + } | ||
64 | try { | 66 | try { |
65 | processor.onRuleToSelfMsg(msg); | 67 | processor.onRuleToSelfMsg(msg); |
66 | increaseMessagesProcessedCount(); | 68 | increaseMessagesProcessedCount(); |
@@ -70,7 +72,9 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa | @@ -70,7 +72,9 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa | ||
70 | } | 72 | } |
71 | 73 | ||
72 | private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) { | 74 | private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) { |
73 | - logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); | 75 | + if (log.isDebugEnabled()) { |
76 | + log.debug("[{}][{}][{}] Going to process rule msg: {}", ruleChainId, id, processor.getComponentName(), msg.getMsg()); | ||
77 | + } | ||
74 | try { | 78 | try { |
75 | processor.onRuleChainToRuleNodeMsg(msg); | 79 | processor.onRuleChainToRuleNodeMsg(msg); |
76 | increaseMessagesProcessedCount(); | 80 | increaseMessagesProcessedCount(); |
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
@@ -44,8 +44,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | @@ -44,8 +44,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | ||
44 | private TbContext defaultCtx; | 44 | private TbContext defaultCtx; |
45 | 45 | ||
46 | RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext | 46 | RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext |
47 | - , LoggingAdapter logger, ActorRef parent, ActorRef self) { | ||
48 | - super(systemContext, logger, tenantId, ruleNodeId); | 47 | + , ActorRef parent, ActorRef self) { |
48 | + super(systemContext, tenantId, ruleNodeId); | ||
49 | this.parent = parent; | 49 | this.parent = parent; |
50 | this.self = self; | 50 | this.self = self; |
51 | this.service = systemContext.getRuleChainService(); | 51 | this.service = systemContext.getRuleChainService(); |
@@ -75,7 +75,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | @@ -75,7 +75,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | ||
75 | } | 75 | } |
76 | 76 | ||
77 | @Override | 77 | @Override |
78 | - public void stop(ActorContext context) throws Exception { | 78 | + public void stop(ActorContext context) { |
79 | if (tbNode != null) { | 79 | if (tbNode != null) { |
80 | tbNode.destroy(); | 80 | tbNode.destroy(); |
81 | } | 81 | } |
@@ -83,7 +83,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | @@ -83,7 +83,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | ||
83 | } | 83 | } |
84 | 84 | ||
85 | @Override | 85 | @Override |
86 | - public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { | 86 | + public void onClusterEventMsg(ClusterEventMsg msg) { |
87 | 87 | ||
88 | } | 88 | } |
89 | 89 | ||
@@ -111,6 +111,11 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | @@ -111,6 +111,11 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | ||
111 | } | 111 | } |
112 | } | 112 | } |
113 | 113 | ||
114 | + @Override | ||
115 | + public String getComponentName() { | ||
116 | + return ruleNode.getName(); | ||
117 | + } | ||
118 | + | ||
114 | private TbNode initComponent(RuleNode ruleNode) throws Exception { | 119 | private TbNode initComponent(RuleNode ruleNode) throws Exception { |
115 | Class<?> componentClazz = Class.forName(ruleNode.getType()); | 120 | Class<?> componentClazz = Class.forName(ruleNode.getType()); |
116 | TbNode tbNode = (TbNode) (componentClazz.newInstance()); | 121 | TbNode tbNode = (TbNode) (componentClazz.newInstance()); |
@@ -18,6 +18,7 @@ package org.thingsboard.server.actors.service; | @@ -18,6 +18,7 @@ package org.thingsboard.server.actors.service; | ||
18 | import akka.actor.ActorRef; | 18 | import akka.actor.ActorRef; |
19 | import akka.event.Logging; | 19 | import akka.event.Logging; |
20 | import akka.event.LoggingAdapter; | 20 | import akka.event.LoggingAdapter; |
21 | +import lombok.extern.slf4j.Slf4j; | ||
21 | import org.thingsboard.server.actors.ActorSystemContext; | 22 | import org.thingsboard.server.actors.ActorSystemContext; |
22 | import org.thingsboard.server.actors.shared.ComponentMsgProcessor; | 23 | import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
23 | import org.thingsboard.server.actors.stats.StatsPersistMsg; | 24 | import org.thingsboard.server.actors.stats.StatsPersistMsg; |
@@ -32,8 +33,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; | @@ -32,8 +33,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; | ||
32 | */ | 33 | */ |
33 | public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor { | 34 | public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor { |
34 | 35 | ||
35 | - protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); | ||
36 | - | ||
37 | private long lastPersistedErrorTs = 0L; | 36 | private long lastPersistedErrorTs = 0L; |
38 | protected final TenantId tenantId; | 37 | protected final TenantId tenantId; |
39 | protected final T id; | 38 | protected final T id; |
@@ -54,13 +53,14 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | @@ -54,13 +53,14 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | ||
54 | @Override | 53 | @Override |
55 | public void preStart() { | 54 | public void preStart() { |
56 | try { | 55 | try { |
56 | + log.debug("[{}][{}][{}] Starting processor.", tenantId, id, id.getEntityType()); | ||
57 | processor.start(context()); | 57 | processor.start(context()); |
58 | logLifecycleEvent(ComponentLifecycleEvent.STARTED); | 58 | logLifecycleEvent(ComponentLifecycleEvent.STARTED); |
59 | if (systemContext.isStatisticsEnabled()) { | 59 | if (systemContext.isStatisticsEnabled()) { |
60 | scheduleStatsPersistTick(); | 60 | scheduleStatsPersistTick(); |
61 | } | 61 | } |
62 | } catch (Exception e) { | 62 | } catch (Exception e) { |
63 | - logger.warning("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e); | 63 | + log.warn("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e); |
64 | logAndPersist("OnStart", e, true); | 64 | logAndPersist("OnStart", e, true); |
65 | logLifecycleEvent(ComponentLifecycleEvent.STARTED, e); | 65 | logLifecycleEvent(ComponentLifecycleEvent.STARTED, e); |
66 | } | 66 | } |
@@ -70,7 +70,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | @@ -70,7 +70,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | ||
70 | try { | 70 | try { |
71 | processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency()); | 71 | processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency()); |
72 | } catch (Exception e) { | 72 | } catch (Exception e) { |
73 | - logger.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage()); | 73 | + log.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage()); |
74 | logAndPersist("onScheduleStatsPersistMsg", e); | 74 | logAndPersist("onScheduleStatsPersistMsg", e); |
75 | } | 75 | } |
76 | } | 76 | } |
@@ -78,16 +78,18 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | @@ -78,16 +78,18 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | ||
78 | @Override | 78 | @Override |
79 | public void postStop() { | 79 | public void postStop() { |
80 | try { | 80 | try { |
81 | + log.debug("[{}][{}] Stopping processor.", tenantId, id, id.getEntityType()); | ||
81 | processor.stop(context()); | 82 | processor.stop(context()); |
82 | logLifecycleEvent(ComponentLifecycleEvent.STOPPED); | 83 | logLifecycleEvent(ComponentLifecycleEvent.STOPPED); |
83 | } catch (Exception e) { | 84 | } catch (Exception e) { |
84 | - logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage()); | 85 | + log.warn("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage()); |
85 | logAndPersist("OnStop", e, true); | 86 | logAndPersist("OnStop", e, true); |
86 | logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e); | 87 | logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e); |
87 | } | 88 | } |
88 | } | 89 | } |
89 | 90 | ||
90 | protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { | 91 | protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
92 | + log.debug("[{}][{}][{}] onComponentLifecycleMsg: [{}]", tenantId, id, id.getEntityType(), msg.getEvent()); | ||
91 | try { | 93 | try { |
92 | switch (msg.getEvent()) { | 94 | switch (msg.getEvent()) { |
93 | case CREATED: | 95 | case CREATED: |
@@ -148,9 +150,9 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | @@ -148,9 +150,9 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP | ||
148 | private void logAndPersist(String method, Exception e, boolean critical) { | 150 | private void logAndPersist(String method, Exception e, boolean critical) { |
149 | errorsOccurred++; | 151 | errorsOccurred++; |
150 | if (critical) { | 152 | if (critical) { |
151 | - logger.warning("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e); | 153 | + log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e); |
152 | } else { | 154 | } else { |
153 | - logger.debug("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e); | 155 | + log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e); |
154 | } | 156 | } |
155 | long ts = System.currentTimeMillis(); | 157 | long ts = System.currentTimeMillis(); |
156 | if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) { | 158 | if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) { |
@@ -15,14 +15,17 @@ | @@ -15,14 +15,17 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.actors.service; | 16 | package org.thingsboard.server.actors.service; |
17 | 17 | ||
18 | +import akka.actor.Terminated; | ||
18 | import akka.actor.UntypedActor; | 19 | import akka.actor.UntypedActor; |
19 | -import akka.event.Logging; | ||
20 | -import akka.event.LoggingAdapter; | 20 | +import org.slf4j.Logger; |
21 | +import org.slf4j.LoggerFactory; | ||
21 | import org.thingsboard.server.actors.ActorSystemContext; | 22 | import org.thingsboard.server.actors.ActorSystemContext; |
22 | import org.thingsboard.server.common.msg.TbActorMsg; | 23 | import org.thingsboard.server.common.msg.TbActorMsg; |
23 | 24 | ||
25 | + | ||
24 | public abstract class ContextAwareActor extends UntypedActor { | 26 | public abstract class ContextAwareActor extends UntypedActor { |
25 | - protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); | 27 | + |
28 | + protected final Logger log = LoggerFactory.getLogger(getClass()); | ||
26 | 29 | ||
27 | public static final int ENTITY_PACK_LIMIT = 1024; | 30 | public static final int ENTITY_PACK_LIMIT = 1024; |
28 | 31 | ||
@@ -34,22 +37,27 @@ public abstract class ContextAwareActor extends UntypedActor { | @@ -34,22 +37,27 @@ public abstract class ContextAwareActor extends UntypedActor { | ||
34 | } | 37 | } |
35 | 38 | ||
36 | @Override | 39 | @Override |
37 | - public void onReceive(Object msg) throws Exception { | ||
38 | - if (logger.isDebugEnabled()) { | ||
39 | - logger.debug("Processing msg: {}", msg); | 40 | + public void onReceive(Object msg) { |
41 | + if (log.isDebugEnabled()) { | ||
42 | + log.debug("Processing msg: {}", msg); | ||
40 | } | 43 | } |
41 | if (msg instanceof TbActorMsg) { | 44 | if (msg instanceof TbActorMsg) { |
42 | try { | 45 | try { |
43 | if (!process((TbActorMsg) msg)) { | 46 | if (!process((TbActorMsg) msg)) { |
44 | - logger.warning("Unknown message: {}!", msg); | 47 | + log.warn("Unknown message: {}!", msg); |
45 | } | 48 | } |
46 | } catch (Exception e) { | 49 | } catch (Exception e) { |
47 | throw e; | 50 | throw e; |
48 | } | 51 | } |
52 | + } else if (msg instanceof Terminated) { | ||
53 | + processTermination((Terminated) msg); | ||
49 | } else { | 54 | } else { |
50 | - logger.warning("Unknown message: {}!", msg); | 55 | + log.warn("Unknown message: {}!", msg); |
51 | } | 56 | } |
52 | } | 57 | } |
53 | 58 | ||
59 | + protected void processTermination(Terminated msg) { | ||
60 | + } | ||
61 | + | ||
54 | protected abstract boolean process(TbActorMsg msg); | 62 | protected abstract boolean process(TbActorMsg msg); |
55 | } | 63 | } |
@@ -22,11 +22,14 @@ import akka.actor.Terminated; | @@ -22,11 +22,14 @@ import akka.actor.Terminated; | ||
22 | import com.google.protobuf.ByteString; | 22 | import com.google.protobuf.ByteString; |
23 | import lombok.extern.slf4j.Slf4j; | 23 | import lombok.extern.slf4j.Slf4j; |
24 | import org.springframework.beans.factory.annotation.Autowired; | 24 | import org.springframework.beans.factory.annotation.Autowired; |
25 | +import org.springframework.boot.context.event.ApplicationReadyEvent; | ||
26 | +import org.springframework.context.event.EventListener; | ||
25 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
26 | import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; | 28 | import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; |
27 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; | 29 | import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; |
28 | import org.thingsboard.server.actors.ActorSystemContext; | 30 | import org.thingsboard.server.actors.ActorSystemContext; |
29 | import org.thingsboard.server.actors.app.AppActor; | 31 | import org.thingsboard.server.actors.app.AppActor; |
32 | +import org.thingsboard.server.actors.app.AppInitMsg; | ||
30 | import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; | 33 | import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
31 | import org.thingsboard.server.actors.rpc.RpcManagerActor; | 34 | import org.thingsboard.server.actors.rpc.RpcManagerActor; |
32 | import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; | 35 | import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
@@ -54,6 +57,12 @@ import scala.concurrent.duration.Duration; | @@ -54,6 +57,12 @@ import scala.concurrent.duration.Duration; | ||
54 | import javax.annotation.PostConstruct; | 57 | import javax.annotation.PostConstruct; |
55 | import javax.annotation.PreDestroy; | 58 | import javax.annotation.PreDestroy; |
56 | 59 | ||
60 | +import java.util.Arrays; | ||
61 | +import java.util.UUID; | ||
62 | +import java.util.concurrent.Executors; | ||
63 | +import java.util.concurrent.ScheduledExecutorService; | ||
64 | +import java.util.concurrent.TimeUnit; | ||
65 | + | ||
57 | import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE; | 66 | import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE; |
58 | 67 | ||
59 | @Service | 68 | @Service |
@@ -86,6 +95,8 @@ public class DefaultActorService implements ActorService { | @@ -86,6 +95,8 @@ public class DefaultActorService implements ActorService { | ||
86 | 95 | ||
87 | private ActorRef rpcManagerActor; | 96 | private ActorRef rpcManagerActor; |
88 | 97 | ||
98 | + private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); | ||
99 | + | ||
89 | @PostConstruct | 100 | @PostConstruct |
90 | public void initActorSystem() { | 101 | public void initActorSystem() { |
91 | log.info("Initializing Actor system. {}", actorContext.getRuleChainService()); | 102 | log.info("Initializing Actor system. {}", actorContext.getRuleChainService()); |
@@ -106,6 +117,12 @@ public class DefaultActorService implements ActorService { | @@ -106,6 +117,12 @@ public class DefaultActorService implements ActorService { | ||
106 | log.info("Actor system initialized."); | 117 | log.info("Actor system initialized."); |
107 | } | 118 | } |
108 | 119 | ||
120 | + @EventListener(ApplicationReadyEvent.class) | ||
121 | + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { | ||
122 | + log.info("Received application ready event. Sending application init message to actor system"); | ||
123 | + appActor.tell(new AppInitMsg(), ActorRef.noSender()); | ||
124 | + } | ||
125 | + | ||
109 | @PreDestroy | 126 | @PreDestroy |
110 | public void stopActorSystem() { | 127 | public void stopActorSystem() { |
111 | Future<Terminated> status = system.terminate(); | 128 | Future<Terminated> status = system.terminate(); |
application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
@@ -22,22 +22,22 @@ import akka.event.LoggingAdapter; | @@ -22,22 +22,22 @@ import akka.event.LoggingAdapter; | ||
22 | import com.fasterxml.jackson.databind.ObjectMapper; | 22 | import com.fasterxml.jackson.databind.ObjectMapper; |
23 | import lombok.AllArgsConstructor; | 23 | import lombok.AllArgsConstructor; |
24 | import lombok.Data; | 24 | import lombok.Data; |
25 | +import lombok.extern.slf4j.Slf4j; | ||
25 | import org.thingsboard.server.actors.ActorSystemContext; | 26 | import org.thingsboard.server.actors.ActorSystemContext; |
26 | import scala.concurrent.ExecutionContextExecutor; | 27 | import scala.concurrent.ExecutionContextExecutor; |
27 | import scala.concurrent.duration.Duration; | 28 | import scala.concurrent.duration.Duration; |
28 | 29 | ||
29 | import java.util.concurrent.TimeUnit; | 30 | import java.util.concurrent.TimeUnit; |
30 | 31 | ||
32 | +@Slf4j | ||
31 | public abstract class AbstractContextAwareMsgProcessor { | 33 | public abstract class AbstractContextAwareMsgProcessor { |
32 | 34 | ||
33 | protected final ActorSystemContext systemContext; | 35 | protected final ActorSystemContext systemContext; |
34 | - protected final LoggingAdapter logger; | ||
35 | protected final ObjectMapper mapper = new ObjectMapper(); | 36 | protected final ObjectMapper mapper = new ObjectMapper(); |
36 | 37 | ||
37 | - protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger) { | 38 | + protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext) { |
38 | super(); | 39 | super(); |
39 | this.systemContext = systemContext; | 40 | this.systemContext = systemContext; |
40 | - this.logger = logger; | ||
41 | } | 41 | } |
42 | 42 | ||
43 | private Scheduler getScheduler() { | 43 | private Scheduler getScheduler() { |
@@ -53,7 +53,7 @@ public abstract class AbstractContextAwareMsgProcessor { | @@ -53,7 +53,7 @@ public abstract class AbstractContextAwareMsgProcessor { | ||
53 | } | 53 | } |
54 | 54 | ||
55 | private void schedulePeriodicMsgWithDelay(Object msg, long delayInMs, long periodInMs, ActorRef target) { | 55 | private void schedulePeriodicMsgWithDelay(Object msg, long delayInMs, long periodInMs, ActorRef target) { |
56 | - logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs); | 56 | + log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs); |
57 | getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); | 57 | getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); |
58 | } | 58 | } |
59 | 59 | ||
@@ -62,7 +62,7 @@ public abstract class AbstractContextAwareMsgProcessor { | @@ -62,7 +62,7 @@ public abstract class AbstractContextAwareMsgProcessor { | ||
62 | } | 62 | } |
63 | 63 | ||
64 | private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) { | 64 | private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) { |
65 | - logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); | 65 | + log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); |
66 | getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); | 66 | getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); |
67 | } | 67 | } |
68 | 68 |
@@ -19,6 +19,7 @@ import akka.actor.ActorContext; | @@ -19,6 +19,7 @@ import akka.actor.ActorContext; | ||
19 | import akka.event.LoggingAdapter; | 19 | import akka.event.LoggingAdapter; |
20 | import com.google.common.util.concurrent.FutureCallback; | 20 | import com.google.common.util.concurrent.FutureCallback; |
21 | import com.google.common.util.concurrent.Futures; | 21 | import com.google.common.util.concurrent.Futures; |
22 | +import lombok.extern.slf4j.Slf4j; | ||
22 | import org.thingsboard.server.actors.ActorSystemContext; | 23 | import org.thingsboard.server.actors.ActorSystemContext; |
23 | import org.thingsboard.server.actors.stats.StatsPersistTick; | 24 | import org.thingsboard.server.actors.stats.StatsPersistTick; |
24 | import org.thingsboard.server.common.data.id.EntityId; | 25 | import org.thingsboard.server.common.data.id.EntityId; |
@@ -30,18 +31,21 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; | @@ -30,18 +31,21 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; | ||
30 | import javax.annotation.Nullable; | 31 | import javax.annotation.Nullable; |
31 | import java.util.function.Consumer; | 32 | import java.util.function.Consumer; |
32 | 33 | ||
34 | +@Slf4j | ||
33 | public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor { | 35 | public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor { |
34 | 36 | ||
35 | protected final TenantId tenantId; | 37 | protected final TenantId tenantId; |
36 | protected final T entityId; | 38 | protected final T entityId; |
37 | protected ComponentLifecycleState state; | 39 | protected ComponentLifecycleState state; |
38 | 40 | ||
39 | - protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { | ||
40 | - super(systemContext, logger); | 41 | + protected ComponentMsgProcessor(ActorSystemContext systemContext, TenantId tenantId, T id) { |
42 | + super(systemContext); | ||
41 | this.tenantId = tenantId; | 43 | this.tenantId = tenantId; |
42 | this.entityId = id; | 44 | this.entityId = id; |
43 | } | 45 | } |
44 | 46 | ||
47 | + public abstract String getComponentName(); | ||
48 | + | ||
45 | public abstract void start(ActorContext context) throws Exception; | 49 | public abstract void start(ActorContext context) throws Exception; |
46 | 50 | ||
47 | public abstract void stop(ActorContext context) throws Exception; | 51 | public abstract void stop(ActorContext context) throws Exception; |
@@ -79,7 +83,7 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract | @@ -79,7 +83,7 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract | ||
79 | 83 | ||
80 | protected void checkActive() { | 84 | protected void checkActive() { |
81 | if (state != ComponentLifecycleState.ACTIVE) { | 85 | if (state != ComponentLifecycleState.ACTIVE) { |
82 | - logger.warning("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId); | 86 | + log.warn("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId); |
83 | throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId); | 87 | throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId); |
84 | } | 88 | } |
85 | } | 89 | } |
@@ -20,6 +20,8 @@ import akka.actor.ActorRef; | @@ -20,6 +20,8 @@ import akka.actor.ActorRef; | ||
20 | import akka.actor.Props; | 20 | import akka.actor.Props; |
21 | import akka.actor.UntypedActor; | 21 | import akka.actor.UntypedActor; |
22 | import akka.japi.Creator; | 22 | import akka.japi.Creator; |
23 | +import com.google.common.collect.BiMap; | ||
24 | +import com.google.common.collect.HashBiMap; | ||
23 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
24 | import org.thingsboard.server.actors.ActorSystemContext; | 26 | import org.thingsboard.server.actors.ActorSystemContext; |
25 | import org.thingsboard.server.actors.service.ContextAwareActor; | 27 | import org.thingsboard.server.actors.service.ContextAwareActor; |
@@ -39,11 +41,11 @@ import java.util.Map; | @@ -39,11 +41,11 @@ import java.util.Map; | ||
39 | public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> { | 41 | public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> { |
40 | 42 | ||
41 | protected final ActorSystemContext systemContext; | 43 | protected final ActorSystemContext systemContext; |
42 | - protected final Map<T, ActorRef> actors; | 44 | + protected final BiMap<T, ActorRef> actors; |
43 | 45 | ||
44 | public EntityActorsManager(ActorSystemContext systemContext) { | 46 | public EntityActorsManager(ActorSystemContext systemContext) { |
45 | this.systemContext = systemContext; | 47 | this.systemContext = systemContext; |
46 | - this.actors = new HashMap<>(); | 48 | + this.actors = HashBiMap.create(); |
47 | } | 49 | } |
48 | 50 | ||
49 | protected abstract TenantId getTenantId(); | 51 | protected abstract TenantId getTenantId(); |
@@ -65,7 +67,8 @@ public abstract class EntityActorsManager<T extends EntityId, A extends UntypedA | @@ -65,7 +67,8 @@ public abstract class EntityActorsManager<T extends EntityId, A extends UntypedA | ||
65 | } | 67 | } |
66 | } | 68 | } |
67 | 69 | ||
68 | - public void visit(M entity, ActorRef actorRef) {} | 70 | + public void visit(M entity, ActorRef actorRef) { |
71 | + } | ||
69 | 72 | ||
70 | public ActorRef getOrCreateActor(ActorContext context, T entityId) { | 73 | public ActorRef getOrCreateActor(ActorContext context, T entityId) { |
71 | return actors.computeIfAbsent(entityId, eId -> | 74 | return actors.computeIfAbsent(entityId, eId -> |
@@ -15,10 +15,9 @@ | @@ -15,10 +15,9 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.actors.stats; | 16 | package org.thingsboard.server.actors.stats; |
17 | 17 | ||
18 | -import akka.event.Logging; | ||
19 | -import akka.event.LoggingAdapter; | ||
20 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
21 | import com.fasterxml.jackson.databind.ObjectMapper; | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | +import lombok.extern.slf4j.Slf4j; | ||
22 | import org.thingsboard.server.actors.ActorSystemContext; | 21 | import org.thingsboard.server.actors.ActorSystemContext; |
23 | import org.thingsboard.server.actors.service.ContextAwareActor; | 22 | import org.thingsboard.server.actors.service.ContextAwareActor; |
24 | import org.thingsboard.server.actors.service.ContextBasedCreator; | 23 | import org.thingsboard.server.actors.service.ContextBasedCreator; |
@@ -27,9 +26,9 @@ import org.thingsboard.server.common.data.Event; | @@ -27,9 +26,9 @@ import org.thingsboard.server.common.data.Event; | ||
27 | import org.thingsboard.server.common.msg.TbActorMsg; | 26 | import org.thingsboard.server.common.msg.TbActorMsg; |
28 | import org.thingsboard.server.common.msg.cluster.ServerAddress; | 27 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
29 | 28 | ||
29 | +@Slf4j | ||
30 | public class StatsActor extends ContextAwareActor { | 30 | public class StatsActor extends ContextAwareActor { |
31 | 31 | ||
32 | - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); | ||
33 | private final ObjectMapper mapper = new ObjectMapper(); | 32 | private final ObjectMapper mapper = new ObjectMapper(); |
34 | 33 | ||
35 | public StatsActor(ActorSystemContext context) { | 34 | public StatsActor(ActorSystemContext context) { |
@@ -43,13 +42,13 @@ public class StatsActor extends ContextAwareActor { | @@ -43,13 +42,13 @@ public class StatsActor extends ContextAwareActor { | ||
43 | } | 42 | } |
44 | 43 | ||
45 | @Override | 44 | @Override |
46 | - public void onReceive(Object msg) throws Exception { | ||
47 | - logger.debug("Received message: {}", msg); | 45 | + public void onReceive(Object msg) { |
46 | + log.debug("Received message: {}", msg); | ||
48 | if (msg instanceof StatsPersistMsg) { | 47 | if (msg instanceof StatsPersistMsg) { |
49 | try { | 48 | try { |
50 | onStatsPersistMsg((StatsPersistMsg) msg); | 49 | onStatsPersistMsg((StatsPersistMsg) msg); |
51 | } catch (Exception e) { | 50 | } catch (Exception e) { |
52 | - logger.warning("Failed to persist statistics: {}", msg, e); | 51 | + log.warn("Failed to persist statistics: {}", msg, e); |
53 | } | 52 | } |
54 | } | 53 | } |
55 | } | 54 | } |
@@ -75,7 +74,7 @@ public class StatsActor extends ContextAwareActor { | @@ -75,7 +74,7 @@ public class StatsActor extends ContextAwareActor { | ||
75 | } | 74 | } |
76 | 75 | ||
77 | @Override | 76 | @Override |
78 | - public StatsActor create() throws Exception { | 77 | + public StatsActor create() { |
79 | return new StatsActor(context); | 78 | return new StatsActor(context); |
80 | } | 79 | } |
81 | } | 80 | } |
@@ -17,15 +17,19 @@ package org.thingsboard.server.actors.tenant; | @@ -17,15 +17,19 @@ package org.thingsboard.server.actors.tenant; | ||
17 | 17 | ||
18 | import akka.actor.ActorInitializationException; | 18 | import akka.actor.ActorInitializationException; |
19 | import akka.actor.ActorRef; | 19 | import akka.actor.ActorRef; |
20 | +import akka.actor.LocalActorRef; | ||
20 | import akka.actor.OneForOneStrategy; | 21 | import akka.actor.OneForOneStrategy; |
21 | import akka.actor.Props; | 22 | import akka.actor.Props; |
22 | import akka.actor.SupervisorStrategy; | 23 | import akka.actor.SupervisorStrategy; |
24 | +import akka.actor.Terminated; | ||
23 | import akka.japi.Function; | 25 | import akka.japi.Function; |
26 | +import com.google.common.collect.BiMap; | ||
27 | +import com.google.common.collect.HashBiMap; | ||
28 | +import lombok.extern.slf4j.Slf4j; | ||
24 | import org.thingsboard.server.actors.ActorSystemContext; | 29 | import org.thingsboard.server.actors.ActorSystemContext; |
25 | -import org.thingsboard.server.actors.device.DeviceActor; | 30 | +import org.thingsboard.server.actors.device.DeviceActorCreator; |
26 | import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; | 31 | import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; |
27 | import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; | 32 | import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; |
28 | -import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg; | ||
29 | import org.thingsboard.server.actors.service.ContextBasedCreator; | 33 | import org.thingsboard.server.actors.service.ContextBasedCreator; |
30 | import org.thingsboard.server.actors.service.DefaultActorService; | 34 | import org.thingsboard.server.actors.service.DefaultActorService; |
31 | import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager; | 35 | import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager; |
@@ -33,6 +37,7 @@ import org.thingsboard.server.common.data.EntityType; | @@ -33,6 +37,7 @@ import org.thingsboard.server.common.data.EntityType; | ||
33 | import org.thingsboard.server.common.data.id.DeviceId; | 37 | import org.thingsboard.server.common.data.id.DeviceId; |
34 | import org.thingsboard.server.common.data.id.RuleChainId; | 38 | import org.thingsboard.server.common.data.id.RuleChainId; |
35 | import org.thingsboard.server.common.data.id.TenantId; | 39 | import org.thingsboard.server.common.data.id.TenantId; |
40 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | ||
36 | import org.thingsboard.server.common.data.rule.RuleChain; | 41 | import org.thingsboard.server.common.data.rule.RuleChain; |
37 | import org.thingsboard.server.common.msg.TbActorMsg; | 42 | import org.thingsboard.server.common.msg.TbActorMsg; |
38 | import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; | 43 | import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; |
@@ -47,15 +52,14 @@ import java.util.Map; | @@ -47,15 +52,14 @@ import java.util.Map; | ||
47 | public class TenantActor extends RuleChainManagerActor { | 52 | public class TenantActor extends RuleChainManagerActor { |
48 | 53 | ||
49 | private final TenantId tenantId; | 54 | private final TenantId tenantId; |
50 | - private final Map<DeviceId, ActorRef> deviceActors; | 55 | + private final BiMap<DeviceId, ActorRef> deviceActors; |
51 | 56 | ||
52 | private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { | 57 | private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { |
53 | super(systemContext, new TenantRuleChainManager(systemContext, tenantId)); | 58 | super(systemContext, new TenantRuleChainManager(systemContext, tenantId)); |
54 | this.tenantId = tenantId; | 59 | this.tenantId = tenantId; |
55 | - this.deviceActors = new HashMap<>(); | 60 | + this.deviceActors = HashBiMap.create(); |
56 | } | 61 | } |
57 | 62 | ||
58 | - | ||
59 | @Override | 63 | @Override |
60 | public SupervisorStrategy supervisorStrategy() { | 64 | public SupervisorStrategy supervisorStrategy() { |
61 | return strategy; | 65 | return strategy; |
@@ -63,16 +67,21 @@ public class TenantActor extends RuleChainManagerActor { | @@ -63,16 +67,21 @@ public class TenantActor extends RuleChainManagerActor { | ||
63 | 67 | ||
64 | @Override | 68 | @Override |
65 | public void preStart() { | 69 | public void preStart() { |
66 | - logger.info("[{}] Starting tenant actor.", tenantId); | 70 | + log.info("[{}] Starting tenant actor.", tenantId); |
67 | try { | 71 | try { |
68 | initRuleChains(); | 72 | initRuleChains(); |
69 | - logger.info("[{}] Tenant actor started.", tenantId); | 73 | + log.info("[{}] Tenant actor started.", tenantId); |
70 | } catch (Exception e) { | 74 | } catch (Exception e) { |
71 | - logger.error(e, "[{}] Unknown failure", tenantId); | 75 | + log.warn("[{}] Unknown failure", tenantId, e); |
72 | } | 76 | } |
73 | } | 77 | } |
74 | 78 | ||
75 | @Override | 79 | @Override |
80 | + public void postStop() { | ||
81 | + log.info("[{}] Stopping tenant actor.", tenantId); | ||
82 | + } | ||
83 | + | ||
84 | + @Override | ||
76 | protected boolean process(TbActorMsg msg) { | 85 | protected boolean process(TbActorMsg msg) { |
77 | switch (msg.getMsgType()) { | 86 | switch (msg.getMsgType()) { |
78 | case CLUSTER_EVENT_MSG: | 87 | case CLUSTER_EVENT_MSG: |
@@ -105,22 +114,20 @@ public class TenantActor extends RuleChainManagerActor { | @@ -105,22 +114,20 @@ public class TenantActor extends RuleChainManagerActor { | ||
105 | return true; | 114 | return true; |
106 | } | 115 | } |
107 | 116 | ||
108 | - @Override | ||
109 | - protected void broadcast(Object msg) { | ||
110 | - super.broadcast(msg); | ||
111 | -// deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); | ||
112 | - } | ||
113 | - | ||
114 | private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { | 117 | private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { |
115 | - if (ruleChainManager.getRootChainActor()!=null) | ||
116 | - ruleChainManager.getRootChainActor().tell(msg, self()); | ||
117 | - else logger.info("[{}] No Root Chain", msg); | 118 | + if (ruleChainManager.getRootChainActor() != null) { |
119 | + ruleChainManager.getRootChainActor().tell(msg, self()); | ||
120 | + } else { | ||
121 | + log.info("[{}] No Root Chain: {}", tenantId, msg); | ||
122 | + } | ||
118 | } | 123 | } |
119 | 124 | ||
120 | private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) { | 125 | private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) { |
121 | - if (ruleChainManager.getRootChainActor()!=null) | ||
122 | - ruleChainManager.getRootChainActor().tell(msg, self()); | ||
123 | - else logger.info("[{}] No Root Chain", msg); | 126 | + if (ruleChainManager.getRootChainActor() != null) { |
127 | + ruleChainManager.getRootChainActor().tell(msg, self()); | ||
128 | + } else { | ||
129 | + log.info("[{}] No Root Chain: {}", tenantId, msg); | ||
130 | + } | ||
124 | } | 131 | } |
125 | 132 | ||
126 | private void onRuleChainMsg(RuleChainAwareMsg msg) { | 133 | private void onRuleChainMsg(RuleChainAwareMsg msg) { |
@@ -141,13 +148,35 @@ public class TenantActor extends RuleChainManagerActor { | @@ -141,13 +148,35 @@ public class TenantActor extends RuleChainManagerActor { | ||
141 | } | 148 | } |
142 | target.tell(msg, ActorRef.noSender()); | 149 | target.tell(msg, ActorRef.noSender()); |
143 | } else { | 150 | } else { |
144 | - logger.debug("Invalid component lifecycle msg: {}", msg); | 151 | + log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg); |
145 | } | 152 | } |
146 | } | 153 | } |
147 | 154 | ||
148 | private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { | 155 | private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { |
149 | - return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId)) | ||
150 | - .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString())); | 156 | + return deviceActors.computeIfAbsent(deviceId, k -> { |
157 | + log.debug("[{}][{}] Creating device actor.", tenantId, deviceId); | ||
158 | + ActorRef deviceActor = context().actorOf(Props.create(new DeviceActorCreator(systemContext, tenantId, deviceId)) | ||
159 | + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME) | ||
160 | + , deviceId.toString()); | ||
161 | + context().watch(deviceActor); | ||
162 | + log.debug("[{}][{}] Created device actor: {}.", tenantId, deviceId, deviceActor); | ||
163 | + return deviceActor; | ||
164 | + }); | ||
165 | + } | ||
166 | + | ||
167 | + @Override | ||
168 | + protected void processTermination(Terminated message) { | ||
169 | + ActorRef terminated = message.actor(); | ||
170 | + if (terminated instanceof LocalActorRef) { | ||
171 | + boolean removed = deviceActors.inverse().remove(terminated) != null; | ||
172 | + if (removed) { | ||
173 | + log.debug("[{}] Removed actor:", terminated); | ||
174 | + } else { | ||
175 | + log.warn("[{}] Removed actor was not found in the device map!"); | ||
176 | + } | ||
177 | + } else { | ||
178 | + throw new IllegalStateException("Remote actors are not supported!"); | ||
179 | + } | ||
151 | } | 180 | } |
152 | 181 | ||
153 | public static class ActorCreator extends ContextBasedCreator<TenantActor> { | 182 | public static class ActorCreator extends ContextBasedCreator<TenantActor> { |
@@ -161,7 +190,7 @@ public class TenantActor extends RuleChainManagerActor { | @@ -161,7 +190,7 @@ public class TenantActor extends RuleChainManagerActor { | ||
161 | } | 190 | } |
162 | 191 | ||
163 | @Override | 192 | @Override |
164 | - public TenantActor create() throws Exception { | 193 | + public TenantActor create() { |
165 | return new TenantActor(context, tenantId); | 194 | return new TenantActor(context, tenantId); |
166 | } | 195 | } |
167 | } | 196 | } |
@@ -169,8 +198,8 @@ public class TenantActor extends RuleChainManagerActor { | @@ -169,8 +198,8 @@ public class TenantActor extends RuleChainManagerActor { | ||
169 | private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() { | 198 | private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() { |
170 | @Override | 199 | @Override |
171 | public SupervisorStrategy.Directive apply(Throwable t) { | 200 | public SupervisorStrategy.Directive apply(Throwable t) { |
172 | - logger.error(t, "Unknown failure"); | ||
173 | - if(t instanceof ActorInitializationException){ | 201 | + log.warn("[{}] Unknown failure", tenantId, t); |
202 | + if (t instanceof ActorInitializationException) { | ||
174 | return SupervisorStrategy.stop(); | 203 | return SupervisorStrategy.stop(); |
175 | } else { | 204 | } else { |
176 | return SupervisorStrategy.resume(); | 205 | return SupervisorStrategy.resume(); |
@@ -19,9 +19,11 @@ import com.datastax.driver.core.utils.UUIDs; | @@ -19,9 +19,11 @@ import com.datastax.driver.core.utils.UUIDs; | ||
19 | import com.fasterxml.jackson.databind.ObjectMapper; | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | import com.fasterxml.jackson.databind.node.ArrayNode; | 20 | import com.fasterxml.jackson.databind.node.ArrayNode; |
21 | import com.fasterxml.jackson.databind.node.ObjectNode; | 21 | import com.fasterxml.jackson.databind.node.ObjectNode; |
22 | +import lombok.Getter; | ||
22 | import lombok.extern.slf4j.Slf4j; | 23 | import lombok.extern.slf4j.Slf4j; |
23 | import org.apache.commons.lang3.StringUtils; | 24 | import org.apache.commons.lang3.StringUtils; |
24 | import org.springframework.beans.factory.annotation.Autowired; | 25 | import org.springframework.beans.factory.annotation.Autowired; |
26 | +import org.springframework.beans.factory.annotation.Value; | ||
25 | import org.springframework.security.core.Authentication; | 27 | import org.springframework.security.core.Authentication; |
26 | import org.springframework.security.core.context.SecurityContextHolder; | 28 | import org.springframework.security.core.context.SecurityContextHolder; |
27 | import org.springframework.web.bind.annotation.ExceptionHandler; | 29 | import org.springframework.web.bind.annotation.ExceptionHandler; |
@@ -152,6 +154,11 @@ public abstract class BaseController { | @@ -152,6 +154,11 @@ public abstract class BaseController { | ||
152 | @Autowired | 154 | @Autowired |
153 | protected AttributesService attributesService; | 155 | protected AttributesService attributesService; |
154 | 156 | ||
157 | + @Value("${server.log_controller_error_stack_trace}") | ||
158 | + @Getter | ||
159 | + private boolean logControllerErrorStackTrace; | ||
160 | + | ||
161 | + | ||
155 | @ExceptionHandler(ThingsboardException.class) | 162 | @ExceptionHandler(ThingsboardException.class) |
156 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { | 163 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { |
157 | errorResponseHandler.handle(ex, response); | 164 | errorResponseHandler.handle(ex, response); |
@@ -162,7 +169,7 @@ public abstract class BaseController { | @@ -162,7 +169,7 @@ public abstract class BaseController { | ||
162 | } | 169 | } |
163 | 170 | ||
164 | private ThingsboardException handleException(Exception exception, boolean logException) { | 171 | private ThingsboardException handleException(Exception exception, boolean logException) { |
165 | - if (logException) { | 172 | + if (logException && logControllerErrorStackTrace) { |
166 | log.error("Error [{}]", exception.getMessage(), exception); | 173 | log.error("Error [{}]", exception.getMessage(), exception); |
167 | } | 174 | } |
168 | 175 |
@@ -52,7 +52,6 @@ public class DashboardController extends BaseController { | @@ -52,7 +52,6 @@ public class DashboardController extends BaseController { | ||
52 | public static final String DASHBOARD_ID = "dashboardId"; | 52 | public static final String DASHBOARD_ID = "dashboardId"; |
53 | 53 | ||
54 | @Value("${dashboard.max_datapoints_limit}") | 54 | @Value("${dashboard.max_datapoints_limit}") |
55 | - @Getter | ||
56 | private long maxDatapointsLimit; | 55 | private long maxDatapointsLimit; |
57 | 56 | ||
58 | 57 |
@@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.RequestParam; | @@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.RequestParam; | ||
29 | import org.springframework.web.bind.annotation.ResponseBody; | 29 | import org.springframework.web.bind.annotation.ResponseBody; |
30 | import org.springframework.web.bind.annotation.ResponseStatus; | 30 | import org.springframework.web.bind.annotation.ResponseStatus; |
31 | import org.springframework.web.bind.annotation.RestController; | 31 | import org.springframework.web.bind.annotation.RestController; |
32 | -import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; | ||
33 | import org.thingsboard.server.common.data.Customer; | 32 | import org.thingsboard.server.common.data.Customer; |
34 | import org.thingsboard.server.common.data.DataConstants; | 33 | import org.thingsboard.server.common.data.DataConstants; |
35 | import org.thingsboard.server.common.data.EntitySubtype; | 34 | import org.thingsboard.server.common.data.EntitySubtype; |
@@ -39,7 +38,6 @@ import org.thingsboard.server.common.data.audit.ActionType; | @@ -39,7 +38,6 @@ import org.thingsboard.server.common.data.audit.ActionType; | ||
39 | import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; | 38 | import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; |
40 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 39 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
41 | import org.thingsboard.server.common.data.id.CustomerId; | 40 | import org.thingsboard.server.common.data.id.CustomerId; |
42 | -import org.thingsboard.server.common.data.id.DeviceId; | ||
43 | import org.thingsboard.server.common.data.id.EntityId; | 41 | import org.thingsboard.server.common.data.id.EntityId; |
44 | import org.thingsboard.server.common.data.id.EntityViewId; | 42 | import org.thingsboard.server.common.data.id.EntityViewId; |
45 | import org.thingsboard.server.common.data.id.TenantId; | 43 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -47,7 +45,6 @@ import org.thingsboard.server.common.data.id.UUIDBased; | @@ -47,7 +45,6 @@ import org.thingsboard.server.common.data.id.UUIDBased; | ||
47 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; | 45 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
48 | import org.thingsboard.server.common.data.page.TextPageData; | 46 | import org.thingsboard.server.common.data.page.TextPageData; |
49 | import org.thingsboard.server.common.data.page.TextPageLink; | 47 | import org.thingsboard.server.common.data.page.TextPageLink; |
50 | -import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; | ||
51 | import org.thingsboard.server.dao.exception.IncorrectParameterException; | 48 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
52 | import org.thingsboard.server.dao.model.ModelConstants; | 49 | import org.thingsboard.server.dao.model.ModelConstants; |
53 | import org.thingsboard.server.service.security.model.SecurityUser; | 50 | import org.thingsboard.server.service.security.model.SecurityUser; |
@@ -174,7 +171,7 @@ public class EntityViewController extends BaseController { | @@ -174,7 +171,7 @@ public class EntityViewController extends BaseController { | ||
174 | EntityView entityView = checkEntityViewId(entityViewId); | 171 | EntityView entityView = checkEntityViewId(entityViewId); |
175 | entityViewService.deleteEntityView(entityViewId); | 172 | entityViewService.deleteEntityView(entityViewId); |
176 | logEntityAction(entityViewId, entityView, entityView.getCustomerId(), | 173 | logEntityAction(entityViewId, entityView, entityView.getCustomerId(), |
177 | - ActionType.DELETED,null, strEntityViewId); | 174 | + ActionType.DELETED, null, strEntityViewId); |
178 | } catch (Exception e) { | 175 | } catch (Exception e) { |
179 | logEntityAction(emptyId(EntityType.ENTITY_VIEW), | 176 | logEntityAction(emptyId(EntityType.ENTITY_VIEW), |
180 | null, | 177 | null, |
@@ -185,10 +182,23 @@ public class EntityViewController extends BaseController { | @@ -185,10 +182,23 @@ public class EntityViewController extends BaseController { | ||
185 | } | 182 | } |
186 | 183 | ||
187 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") | 184 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
185 | + @RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET) | ||
186 | + @ResponseBody | ||
187 | + public EntityView getTenantEntityView( | ||
188 | + @RequestParam String entityViewName) throws ThingsboardException { | ||
189 | + try { | ||
190 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
191 | + return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName)); | ||
192 | + } catch (Exception e) { | ||
193 | + throw handleException(e); | ||
194 | + } | ||
195 | + } | ||
196 | + | ||
197 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
188 | @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) | 198 | @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) |
189 | @ResponseBody | 199 | @ResponseBody |
190 | public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId, | 200 | public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId, |
191 | - @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | 201 | + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { |
192 | checkParameter(CUSTOMER_ID, strCustomerId); | 202 | checkParameter(CUSTOMER_ID, strCustomerId); |
193 | checkParameter(ENTITY_VIEW_ID, strEntityViewId); | 203 | checkParameter(ENTITY_VIEW_ID, strEntityViewId); |
194 | try { | 204 | try { |
@@ -49,9 +49,11 @@ import org.thingsboard.server.common.data.kv.Aggregation; | @@ -49,9 +49,11 @@ import org.thingsboard.server.common.data.kv.Aggregation; | ||
49 | import org.thingsboard.server.common.data.kv.AttributeKey; | 49 | import org.thingsboard.server.common.data.kv.AttributeKey; |
50 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; | 50 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
51 | import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; | 51 | import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
52 | +import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery; | ||
52 | import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; | 53 | import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
53 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; | 54 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
54 | import org.thingsboard.server.common.data.kv.BooleanDataEntry; | 55 | import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
56 | +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; | ||
55 | import org.thingsboard.server.common.data.kv.DoubleDataEntry; | 57 | import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
56 | import org.thingsboard.server.common.data.kv.KvEntry; | 58 | import org.thingsboard.server.common.data.kv.KvEntry; |
57 | import org.thingsboard.server.common.data.kv.LongDataEntry; | 59 | import org.thingsboard.server.common.data.kv.LongDataEntry; |
@@ -60,12 +62,10 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; | @@ -60,12 +62,10 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; | ||
60 | import org.thingsboard.server.common.data.kv.TsKvEntry; | 62 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
61 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; | 63 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
62 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; | 64 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
63 | -import org.thingsboard.server.dao.attributes.AttributesService; | ||
64 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 65 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
65 | import org.thingsboard.server.service.security.AccessValidator; | 66 | import org.thingsboard.server.service.security.AccessValidator; |
66 | import org.thingsboard.server.service.security.model.SecurityUser; | 67 | import org.thingsboard.server.service.security.model.SecurityUser; |
67 | import org.thingsboard.server.service.telemetry.AttributeData; | 68 | import org.thingsboard.server.service.telemetry.AttributeData; |
68 | -import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; | ||
69 | import org.thingsboard.server.service.telemetry.TsData; | 69 | import org.thingsboard.server.service.telemetry.TsData; |
70 | import org.thingsboard.server.service.telemetry.exception.InvalidParametersException; | 70 | import org.thingsboard.server.service.telemetry.exception.InvalidParametersException; |
71 | import org.thingsboard.server.service.telemetry.exception.UncheckedApiException; | 71 | import org.thingsboard.server.service.telemetry.exception.UncheckedApiException; |
@@ -250,6 +250,60 @@ public class TelemetryController extends BaseController { | @@ -250,6 +250,60 @@ public class TelemetryController extends BaseController { | ||
250 | } | 250 | } |
251 | 251 | ||
252 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | 252 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
253 | + @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE) | ||
254 | + @ResponseBody | ||
255 | + public DeferredResult<ResponseEntity> deleteEntityTimeseries(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr, | ||
256 | + @RequestParam(name = "keys") String keysStr, | ||
257 | + @RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys, | ||
258 | + @RequestParam(name = "startTs", required = false) Long startTs, | ||
259 | + @RequestParam(name = "endTs", required = false) Long endTs, | ||
260 | + @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { | ||
261 | + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); | ||
262 | + return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted); | ||
263 | + } | ||
264 | + | ||
265 | + private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys, | ||
266 | + Long startTs, Long endTs, boolean rewriteLatestIfDeleted) throws ThingsboardException { | ||
267 | + List<String> keys = toKeysList(keysStr); | ||
268 | + if (keys.isEmpty()) { | ||
269 | + return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST); | ||
270 | + } | ||
271 | + SecurityUser user = getCurrentUser(); | ||
272 | + | ||
273 | + long deleteFromTs; | ||
274 | + long deleteToTs; | ||
275 | + if (deleteAllDataForKeys) { | ||
276 | + deleteFromTs = 0L; | ||
277 | + deleteToTs = System.currentTimeMillis(); | ||
278 | + } else { | ||
279 | + deleteFromTs = startTs; | ||
280 | + deleteToTs = endTs; | ||
281 | + } | ||
282 | + | ||
283 | + return accessValidator.validateEntityAndCallback(user, entityIdStr, (result, entityId) -> { | ||
284 | + List<DeleteTsKvQuery> deleteTsKvQueries = new ArrayList<>(); | ||
285 | + for (String key : keys) { | ||
286 | + deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted)); | ||
287 | + } | ||
288 | + | ||
289 | + ListenableFuture<List<Void>> future = tsService.remove(entityId, deleteTsKvQueries); | ||
290 | + Futures.addCallback(future, new FutureCallback<List<Void>>() { | ||
291 | + @Override | ||
292 | + public void onSuccess(@Nullable List<Void> tmp) { | ||
293 | + logTimeseriesDeleted(user, entityId, keys, null); | ||
294 | + result.setResult(new ResponseEntity<>(HttpStatus.OK)); | ||
295 | + } | ||
296 | + | ||
297 | + @Override | ||
298 | + public void onFailure(Throwable t) { | ||
299 | + logTimeseriesDeleted(user, entityId, keys, t); | ||
300 | + result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); | ||
301 | + } | ||
302 | + }, executor); | ||
303 | + }); | ||
304 | + } | ||
305 | + | ||
306 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | ||
253 | @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE) | 307 | @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE) |
254 | @ResponseBody | 308 | @ResponseBody |
255 | public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("deviceId") String deviceIdStr, | 309 | public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("deviceId") String deviceIdStr, |
@@ -506,6 +560,15 @@ public class TelemetryController extends BaseController { | @@ -506,6 +560,15 @@ public class TelemetryController extends BaseController { | ||
506 | }; | 560 | }; |
507 | } | 561 | } |
508 | 562 | ||
563 | + private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List<String> keys, Throwable e) { | ||
564 | + try { | ||
565 | + logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.TIMESERIES_DELETED, toException(e), | ||
566 | + keys); | ||
567 | + } catch (ThingsboardException te) { | ||
568 | + log.warn("Failed to log timeseries delete", te); | ||
569 | + } | ||
570 | + } | ||
571 | + | ||
509 | private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) { | 572 | private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) { |
510 | try { | 573 | try { |
511 | logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e), | 574 | logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e), |
@@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; | @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; | ||
32 | import org.thingsboard.server.common.data.id.TenantId; | 32 | import org.thingsboard.server.common.data.id.TenantId; |
33 | import org.thingsboard.server.common.data.page.TextPageData; | 33 | import org.thingsboard.server.common.data.page.TextPageData; |
34 | import org.thingsboard.server.common.data.page.TextPageLink; | 34 | import org.thingsboard.server.common.data.page.TextPageLink; |
35 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | ||
35 | import org.thingsboard.server.dao.tenant.TenantService; | 36 | import org.thingsboard.server.dao.tenant.TenantService; |
36 | import org.thingsboard.server.service.install.InstallScripts; | 37 | import org.thingsboard.server.service.install.InstallScripts; |
37 | 38 | ||
@@ -84,6 +85,8 @@ public class TenantController extends BaseController { | @@ -84,6 +85,8 @@ public class TenantController extends BaseController { | ||
84 | try { | 85 | try { |
85 | TenantId tenantId = new TenantId(toUUID(strTenantId)); | 86 | TenantId tenantId = new TenantId(toUUID(strTenantId)); |
86 | tenantService.deleteTenant(tenantId); | 87 | tenantService.deleteTenant(tenantId); |
88 | + | ||
89 | + actorService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); | ||
87 | } catch (Exception e) { | 90 | } catch (Exception e) { |
88 | throw handleException(e); | 91 | throw handleException(e); |
89 | } | 92 | } |
@@ -21,6 +21,7 @@ import org.apache.commons.lang3.SerializationException; | @@ -21,6 +21,7 @@ import org.apache.commons.lang3.SerializationException; | ||
21 | import org.apache.commons.lang3.SerializationUtils; | 21 | import org.apache.commons.lang3.SerializationUtils; |
22 | import org.apache.curator.framework.CuratorFramework; | 22 | import org.apache.curator.framework.CuratorFramework; |
23 | import org.apache.curator.framework.CuratorFrameworkFactory; | 23 | import org.apache.curator.framework.CuratorFrameworkFactory; |
24 | +import org.apache.curator.framework.imps.CuratorFrameworkState; | ||
24 | import org.apache.curator.framework.recipes.cache.ChildData; | 25 | import org.apache.curator.framework.recipes.cache.ChildData; |
25 | import org.apache.curator.framework.recipes.cache.PathChildrenCache; | 26 | import org.apache.curator.framework.recipes.cache.PathChildrenCache; |
26 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; | 27 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; |
@@ -30,12 +31,14 @@ import org.apache.curator.framework.state.ConnectionStateListener; | @@ -30,12 +31,14 @@ import org.apache.curator.framework.state.ConnectionStateListener; | ||
30 | import org.apache.curator.retry.RetryForever; | 31 | import org.apache.curator.retry.RetryForever; |
31 | import org.apache.curator.utils.CloseableUtils; | 32 | import org.apache.curator.utils.CloseableUtils; |
32 | import org.apache.zookeeper.CreateMode; | 33 | import org.apache.zookeeper.CreateMode; |
34 | +import org.apache.zookeeper.KeeperException; | ||
33 | import org.springframework.beans.factory.annotation.Autowired; | 35 | import org.springframework.beans.factory.annotation.Autowired; |
34 | import org.springframework.beans.factory.annotation.Value; | 36 | import org.springframework.beans.factory.annotation.Value; |
35 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 37 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
36 | import org.springframework.boot.context.event.ApplicationReadyEvent; | 38 | import org.springframework.boot.context.event.ApplicationReadyEvent; |
37 | import org.springframework.context.ApplicationListener; | 39 | import org.springframework.context.ApplicationListener; |
38 | import org.springframework.context.annotation.Lazy; | 40 | import org.springframework.context.annotation.Lazy; |
41 | +import org.springframework.context.event.EventListener; | ||
39 | import org.springframework.stereotype.Service; | 42 | import org.springframework.stereotype.Service; |
40 | import org.springframework.util.Assert; | 43 | import org.springframework.util.Assert; |
41 | import org.thingsboard.server.actors.service.ActorService; | 44 | import org.thingsboard.server.actors.service.ActorService; |
@@ -51,13 +54,15 @@ import java.util.List; | @@ -51,13 +54,15 @@ import java.util.List; | ||
51 | import java.util.NoSuchElementException; | 54 | import java.util.NoSuchElementException; |
52 | import java.util.stream.Collectors; | 55 | import java.util.stream.Collectors; |
53 | 56 | ||
57 | +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; | ||
58 | + | ||
54 | /** | 59 | /** |
55 | * @author Andrew Shvayka | 60 | * @author Andrew Shvayka |
56 | */ | 61 | */ |
57 | @Service | 62 | @Service |
58 | @ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false) | 63 | @ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false) |
59 | @Slf4j | 64 | @Slf4j |
60 | -public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener, ApplicationListener<ApplicationReadyEvent> { | 65 | +public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener { |
61 | 66 | ||
62 | @Value("${zk.url}") | 67 | @Value("${zk.url}") |
63 | private String zkUrl; | 68 | private String zkUrl; |
@@ -95,6 +100,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -95,6 +100,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
95 | private PathChildrenCache cache; | 100 | private PathChildrenCache cache; |
96 | private String nodePath; | 101 | private String nodePath; |
97 | 102 | ||
103 | + private volatile boolean stopped = false; | ||
104 | + | ||
98 | @PostConstruct | 105 | @PostConstruct |
99 | public void init() { | 106 | public void init() { |
100 | log.info("Initializing..."); | 107 | log.info("Initializing..."); |
@@ -115,6 +122,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -115,6 +122,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
115 | cache.start(); | 122 | cache.start(); |
116 | } catch (Exception e) { | 123 | } catch (Exception e) { |
117 | log.error("Failed to connect to ZK: {}", e.getMessage(), e); | 124 | log.error("Failed to connect to ZK: {}", e.getMessage(), e); |
125 | + CloseableUtils.closeQuietly(cache); | ||
118 | CloseableUtils.closeQuietly(client); | 126 | CloseableUtils.closeQuietly(client); |
119 | throw new RuntimeException(e); | 127 | throw new RuntimeException(e); |
120 | } | 128 | } |
@@ -122,25 +130,50 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -122,25 +130,50 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
122 | 130 | ||
123 | @PreDestroy | 131 | @PreDestroy |
124 | public void destroy() { | 132 | public void destroy() { |
133 | + stopped = true; | ||
125 | unpublishCurrentServer(); | 134 | unpublishCurrentServer(); |
135 | + CloseableUtils.closeQuietly(cache); | ||
126 | CloseableUtils.closeQuietly(client); | 136 | CloseableUtils.closeQuietly(client); |
127 | log.info("Stopped discovery service"); | 137 | log.info("Stopped discovery service"); |
128 | } | 138 | } |
129 | 139 | ||
130 | @Override | 140 | @Override |
131 | - public void publishCurrentServer() { | 141 | + public synchronized void publishCurrentServer() { |
142 | + ServerInstance self = this.serverInstance.getSelf(); | ||
143 | + if (currentServerExists()) { | ||
144 | + log.info("[{}:{}] ZK node for current instance already exists, NOT created new one: {}", self.getHost(), self.getPort(), nodePath); | ||
145 | + } else { | ||
146 | + try { | ||
147 | + log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); | ||
148 | + nodePath = client.create() | ||
149 | + .creatingParentsIfNeeded() | ||
150 | + .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress())); | ||
151 | + log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); | ||
152 | + client.getConnectionStateListenable().addListener(checkReconnect(self)); | ||
153 | + } catch (Exception e) { | ||
154 | + log.error("Failed to create ZK node", e); | ||
155 | + throw new RuntimeException(e); | ||
156 | + } | ||
157 | + } | ||
158 | + } | ||
159 | + | ||
160 | + private boolean currentServerExists() { | ||
161 | + if (nodePath == null) { | ||
162 | + return false; | ||
163 | + } | ||
132 | try { | 164 | try { |
133 | ServerInstance self = this.serverInstance.getSelf(); | 165 | ServerInstance self = this.serverInstance.getSelf(); |
134 | - log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); | ||
135 | - nodePath = client.create() | ||
136 | - .creatingParentsIfNeeded() | ||
137 | - .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress())); | ||
138 | - log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); | ||
139 | - client.getConnectionStateListenable().addListener(checkReconnect(self)); | 166 | + ServerAddress registeredServerAdress = null; |
167 | + registeredServerAdress = SerializationUtils.deserialize(client.getData().forPath(nodePath)); | ||
168 | + if (self.getServerAddress() != null && self.getServerAddress().equals(registeredServerAdress)) { | ||
169 | + return true; | ||
170 | + } | ||
171 | + } catch (KeeperException.NoNodeException e) { | ||
172 | + log.info("ZK node does not exist: {}", nodePath); | ||
140 | } catch (Exception e) { | 173 | } catch (Exception e) { |
141 | - log.error("Failed to create ZK node", e); | ||
142 | - throw new RuntimeException(e); | 174 | + log.error("Couldn't check if ZK node exists", e); |
143 | } | 175 | } |
176 | + return false; | ||
144 | } | 177 | } |
145 | 178 | ||
146 | private ConnectionStateListener checkReconnect(ServerInstance self) { | 179 | private ConnectionStateListener checkReconnect(ServerInstance self) { |
@@ -200,8 +233,17 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -200,8 +233,17 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
200 | .collect(Collectors.toList()); | 233 | .collect(Collectors.toList()); |
201 | } | 234 | } |
202 | 235 | ||
203 | - @Override | 236 | + @EventListener(ApplicationReadyEvent.class) |
204 | public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { | 237 | public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { |
238 | + log.info("Received application ready event. Starting current ZK node."); | ||
239 | + if (stopped) { | ||
240 | + log.debug("Ignoring application ready event. Service is stopped."); | ||
241 | + return; | ||
242 | + } | ||
243 | + if (client.getState() != CuratorFrameworkState.STARTED) { | ||
244 | + log.debug("Ignoring application ready event, ZK client is not started, ZK client state [{}]", client.getState()); | ||
245 | + return; | ||
246 | + } | ||
205 | publishCurrentServer(); | 247 | publishCurrentServer(); |
206 | getOtherServers().forEach( | 248 | getOtherServers().forEach( |
207 | server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort()) | 249 | server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort()) |
@@ -210,6 +252,14 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -210,6 +252,14 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
210 | 252 | ||
211 | @Override | 253 | @Override |
212 | public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { | 254 | public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { |
255 | + if (stopped) { | ||
256 | + log.debug("Ignoring {}. Service is stopped.", pathChildrenCacheEvent); | ||
257 | + return; | ||
258 | + } | ||
259 | + if (client.getState() != CuratorFrameworkState.STARTED) { | ||
260 | + log.debug("Ignoring {}, ZK client is not started, ZK client state [{}]", pathChildrenCacheEvent, client.getState()); | ||
261 | + return; | ||
262 | + } | ||
213 | ChildData data = pathChildrenCacheEvent.getData(); | 263 | ChildData data = pathChildrenCacheEvent.getData(); |
214 | if (data == null) { | 264 | if (data == null) { |
215 | log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent); | 265 | log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent); |
@@ -218,6 +268,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -218,6 +268,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
218 | log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent); | 268 | log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent); |
219 | return; | 269 | return; |
220 | } else if (nodePath != null && nodePath.equals(data.getPath())) { | 270 | } else if (nodePath != null && nodePath.equals(data.getPath())) { |
271 | + if (pathChildrenCacheEvent.getType() == CHILD_REMOVED) { | ||
272 | + log.info("ZK node for current instance is somehow deleted."); | ||
273 | + publishCurrentServer(); | ||
274 | + } | ||
221 | log.debug("Ignoring event about current server {}", pathChildrenCacheEvent); | 275 | log.debug("Ignoring event about current server {}", pathChildrenCacheEvent); |
222 | return; | 276 | return; |
223 | } | 277 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.service.executors; | ||
17 | + | ||
18 | +import org.springframework.beans.factory.annotation.Value; | ||
19 | +import org.springframework.stereotype.Component; | ||
20 | + | ||
21 | +@Component | ||
22 | +public class ClusterRpcCallbackExecutorService extends AbstractListeningExecutor { | ||
23 | + | ||
24 | + @Value("${actors.cluster.grpc_callback_thread_pool_size}") | ||
25 | + private int grpcCallbackExecutorThreadPoolSize; | ||
26 | + | ||
27 | + @Override | ||
28 | + protected int getThreadPollSize() { | ||
29 | + return grpcCallbackExecutorThreadPoolSize; | ||
30 | + } | ||
31 | + | ||
32 | +} |
@@ -71,7 +71,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -71,7 +71,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
71 | private int maxErrors; | 71 | private int maxErrors; |
72 | 72 | ||
73 | private TbKafkaRequestTemplate<JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> kafkaTemplate; | 73 | private TbKafkaRequestTemplate<JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> kafkaTemplate; |
74 | - protected Map<UUID, String> scriptIdToBodysMap = new ConcurrentHashMap<>(); | 74 | + private Map<UUID, String> scriptIdToBodysMap = new ConcurrentHashMap<>(); |
75 | 75 | ||
76 | @PostConstruct | 76 | @PostConstruct |
77 | public void init() { | 77 | public void init() { |
@@ -100,7 +100,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -100,7 +100,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
100 | responseBuilder.settings(kafkaSettings); | 100 | responseBuilder.settings(kafkaSettings); |
101 | responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId()); | 101 | responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId()); |
102 | responseBuilder.clientId("js-" + nodeIdProvider.getNodeId()); | 102 | responseBuilder.clientId("js-" + nodeIdProvider.getNodeId()); |
103 | - responseBuilder.groupId("rule-engine-node"); | 103 | + responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId()); |
104 | responseBuilder.autoCommit(true); | 104 | responseBuilder.autoCommit(true); |
105 | responseBuilder.autoCommitIntervalMs(autoCommitInterval); | 105 | responseBuilder.autoCommitIntervalMs(autoCommitInterval); |
106 | responseBuilder.decoder(new RemoteJsResponseDecoder()); | 106 | responseBuilder.decoder(new RemoteJsResponseDecoder()); |
@@ -136,6 +136,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | @@ -136,6 +136,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { | ||
136 | .setCompileRequest(jsRequest) | 136 | .setCompileRequest(jsRequest) |
137 | .build(); | 137 | .build(); |
138 | 138 | ||
139 | + log.trace("Post compile request for scriptId [{}]", scriptId); | ||
139 | ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); | 140 | ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); |
140 | return Futures.transform(future, response -> { | 141 | return Futures.transform(future, response -> { |
141 | JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); | 142 | JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); |
application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
@@ -22,6 +22,7 @@ import io.jsonwebtoken.Jwts; | @@ -22,6 +22,7 @@ import io.jsonwebtoken.Jwts; | ||
22 | import io.jsonwebtoken.MalformedJwtException; | 22 | import io.jsonwebtoken.MalformedJwtException; |
23 | import io.jsonwebtoken.SignatureException; | 23 | import io.jsonwebtoken.SignatureException; |
24 | import io.jsonwebtoken.UnsupportedJwtException; | 24 | import io.jsonwebtoken.UnsupportedJwtException; |
25 | +import lombok.extern.slf4j.Slf4j; | ||
25 | import org.slf4j.Logger; | 26 | import org.slf4j.Logger; |
26 | import org.slf4j.LoggerFactory; | 27 | import org.slf4j.LoggerFactory; |
27 | import org.springframework.security.authentication.BadCredentialsException; | 28 | import org.springframework.security.authentication.BadCredentialsException; |
@@ -29,12 +30,11 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio | @@ -29,12 +30,11 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio | ||
29 | 30 | ||
30 | import java.io.Serializable; | 31 | import java.io.Serializable; |
31 | 32 | ||
33 | +@Slf4j | ||
32 | public class RawAccessJwtToken implements JwtToken, Serializable { | 34 | public class RawAccessJwtToken implements JwtToken, Serializable { |
33 | 35 | ||
34 | private static final long serialVersionUID = -797397445703066079L; | 36 | private static final long serialVersionUID = -797397445703066079L; |
35 | 37 | ||
36 | - private static Logger logger = LoggerFactory.getLogger(RawAccessJwtToken.class); | ||
37 | - | ||
38 | private String token; | 38 | private String token; |
39 | 39 | ||
40 | public RawAccessJwtToken(String token) { | 40 | public RawAccessJwtToken(String token) { |
@@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable { | @@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable { | ||
52 | try { | 52 | try { |
53 | return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); | 53 | return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); |
54 | } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { | 54 | } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { |
55 | - logger.error("Invalid JWT Token", ex); | 55 | + log.error("Invalid JWT Token", ex); |
56 | throw new BadCredentialsException("Invalid JWT token: ", ex); | 56 | throw new BadCredentialsException("Invalid JWT token: ", ex); |
57 | } catch (ExpiredJwtException expiredEx) { | 57 | } catch (ExpiredJwtException expiredEx) { |
58 | - logger.info("JWT Token is expired", expiredEx); | 58 | + log.info("JWT Token is expired", expiredEx); |
59 | throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); | 59 | throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); |
60 | } | 60 | } |
61 | } | 61 | } |
@@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; | @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; | ||
22 | import org.thingsboard.server.common.data.id.DeviceId; | 22 | import org.thingsboard.server.common.data.id.DeviceId; |
23 | import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; | 23 | import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; |
24 | 24 | ||
25 | -import java.util.ArrayList; | ||
26 | import java.util.Collections; | 25 | import java.util.Collections; |
26 | +import java.util.UUID; | ||
27 | 27 | ||
28 | import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; | 28 | import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; |
29 | 29 | ||
@@ -35,16 +35,23 @@ import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; | @@ -35,16 +35,23 @@ import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; | ||
35 | public class DefaultDeviceSessionCacheService implements DeviceSessionCacheService { | 35 | public class DefaultDeviceSessionCacheService implements DeviceSessionCacheService { |
36 | 36 | ||
37 | @Override | 37 | @Override |
38 | - @Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId") | ||
39 | - public DeviceSessionsCacheEntry get(DeviceId deviceId) { | 38 | + @Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId.toString()") |
39 | + public byte[] get(DeviceId deviceId) { | ||
40 | log.debug("[{}] Fetching session data from cache", deviceId); | 40 | log.debug("[{}] Fetching session data from cache", deviceId); |
41 | - return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build(); | 41 | + return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build().toByteArray(); |
42 | } | 42 | } |
43 | 43 | ||
44 | @Override | 44 | @Override |
45 | - @CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId") | ||
46 | - public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) { | ||
47 | - log.debug("[{}] Pushing session data from cache: {}", deviceId, sessions); | 45 | + @CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId.toString()") |
46 | + public byte[] put(DeviceId deviceId, byte[] sessions) { | ||
47 | + log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions); | ||
48 | return sessions; | 48 | return sessions; |
49 | } | 49 | } |
50 | + | ||
51 | + public static void main (String[] args){ | ||
52 | + UUID uuid = UUID.fromString("d5db434e-9cd2-4903-8b3b-421b2d93664d"); | ||
53 | + System.out.println(uuid.getMostSignificantBits()); | ||
54 | + System.out.println(uuid.getLeastSignificantBits()); | ||
55 | + } | ||
56 | + | ||
50 | } | 57 | } |
@@ -23,8 +23,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheE | @@ -23,8 +23,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheE | ||
23 | */ | 23 | */ |
24 | public interface DeviceSessionCacheService { | 24 | public interface DeviceSessionCacheService { |
25 | 25 | ||
26 | - DeviceSessionsCacheEntry get(DeviceId deviceId); | 26 | + byte[] get(DeviceId deviceId); |
27 | 27 | ||
28 | - DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions); | 28 | + byte[] put(DeviceId deviceId, byte[] sessions); |
29 | 29 | ||
30 | } | 30 | } |
@@ -35,23 +35,11 @@ import org.thingsboard.server.common.data.id.EntityId; | @@ -35,23 +35,11 @@ import org.thingsboard.server.common.data.id.EntityId; | ||
35 | import org.thingsboard.server.common.data.id.EntityIdFactory; | 35 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
36 | import org.thingsboard.server.common.data.id.EntityViewId; | 36 | import org.thingsboard.server.common.data.id.EntityViewId; |
37 | import org.thingsboard.server.common.data.id.TenantId; | 37 | import org.thingsboard.server.common.data.id.TenantId; |
38 | -import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
39 | -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; | ||
40 | -import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; | ||
41 | -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; | ||
42 | -import org.thingsboard.server.common.data.kv.BooleanDataEntry; | ||
43 | -import org.thingsboard.server.common.data.kv.DataType; | ||
44 | -import org.thingsboard.server.common.data.kv.DoubleDataEntry; | ||
45 | -import org.thingsboard.server.common.data.kv.KvEntry; | ||
46 | -import org.thingsboard.server.common.data.kv.LongDataEntry; | ||
47 | -import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | ||
48 | -import org.thingsboard.server.common.data.kv.StringDataEntry; | ||
49 | -import org.thingsboard.server.common.data.kv.TsKvEntry; | 38 | +import org.thingsboard.server.common.data.kv.*; |
50 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; | 39 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
51 | import org.thingsboard.server.common.msg.cluster.ServerAddress; | 40 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
52 | import org.thingsboard.server.dao.attributes.AttributesService; | 41 | import org.thingsboard.server.dao.attributes.AttributesService; |
53 | import org.thingsboard.server.dao.entityview.EntityViewService; | 42 | import org.thingsboard.server.dao.entityview.EntityViewService; |
54 | -import org.thingsboard.server.dao.model.ModelConstants; | ||
55 | import org.thingsboard.server.dao.timeseries.TimeseriesService; | 43 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
56 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; | 44 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
57 | import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; | 45 | import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
@@ -68,7 +56,6 @@ import javax.annotation.PostConstruct; | @@ -68,7 +56,6 @@ import javax.annotation.PostConstruct; | ||
68 | import javax.annotation.PreDestroy; | 56 | import javax.annotation.PreDestroy; |
69 | import java.util.ArrayList; | 57 | import java.util.ArrayList; |
70 | import java.util.Collections; | 58 | import java.util.Collections; |
71 | -import java.util.HashMap; | ||
72 | import java.util.HashSet; | 59 | import java.util.HashSet; |
73 | import java.util.Iterator; | 60 | import java.util.Iterator; |
74 | import java.util.List; | 61 | import java.util.List; |
@@ -339,9 +326,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | @@ -339,9 +326,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | ||
339 | Set<Subscription> subscriptions = e.getValue(); | 326 | Set<Subscription> subscriptions = e.getValue(); |
340 | Optional<ServerAddress> newAddressOptional = routingService.resolveById(e.getKey()); | 327 | Optional<ServerAddress> newAddressOptional = routingService.resolveById(e.getKey()); |
341 | if (newAddressOptional.isPresent()) { | 328 | if (newAddressOptional.isPresent()) { |
342 | - newAddressOptional.ifPresent(serverAddress -> checkSubsciptionsNewAddress(serverAddress, subscriptions)); | 329 | + newAddressOptional.ifPresent(serverAddress -> checkSubscriptionsNewAddress(serverAddress, subscriptions)); |
343 | } else { | 330 | } else { |
344 | - checkSubsciptionsPrevAddress(subscriptions); | 331 | + checkSubscriptionsPrevAddress(subscriptions); |
345 | } | 332 | } |
346 | if (subscriptions.size() == 0) { | 333 | if (subscriptions.size() == 0) { |
347 | log.trace("[{}] No more subscriptions for this device on current server.", e.getKey()); | 334 | log.trace("[{}] No more subscriptions for this device on current server.", e.getKey()); |
@@ -350,7 +337,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | @@ -350,7 +337,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | ||
350 | } | 337 | } |
351 | } | 338 | } |
352 | 339 | ||
353 | - private void checkSubsciptionsNewAddress(ServerAddress newAddress, Set<Subscription> subscriptions) { | 340 | + private void checkSubscriptionsNewAddress(ServerAddress newAddress, Set<Subscription> subscriptions) { |
354 | Iterator<Subscription> subscriptionIterator = subscriptions.iterator(); | 341 | Iterator<Subscription> subscriptionIterator = subscriptions.iterator(); |
355 | while (subscriptionIterator.hasNext()) { | 342 | while (subscriptionIterator.hasNext()) { |
356 | Subscription s = subscriptionIterator.next(); | 343 | Subscription s = subscriptionIterator.next(); |
@@ -368,7 +355,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | @@ -368,7 +355,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | ||
368 | } | 355 | } |
369 | } | 356 | } |
370 | 357 | ||
371 | - private void checkSubsciptionsPrevAddress(Set<Subscription> subscriptions) { | 358 | + private void checkSubscriptionsPrevAddress(Set<Subscription> subscriptions) { |
372 | for (Subscription s : subscriptions) { | 359 | for (Subscription s : subscriptions) { |
373 | if (s.isLocal() && s.getServer() != null) { | 360 | if (s.isLocal() && s.getServer() != null) { |
374 | log.trace("[{}] Local subscription is no longer handled on remote server address [{}]", s.getWsSessionId(), s.getServer()); | 361 | log.trace("[{}] Local subscription is no longer handled on remote server address [{}]", s.getWsSessionId(), s.getServer()); |
@@ -381,7 +368,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | @@ -381,7 +368,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | ||
381 | 368 | ||
382 | private void addRemoteWsSubscription(ServerAddress address, String sessionId, Subscription subscription) { | 369 | private void addRemoteWsSubscription(ServerAddress address, String sessionId, Subscription subscription) { |
383 | EntityId entityId = subscription.getEntityId(); | 370 | EntityId entityId = subscription.getEntityId(); |
384 | - log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address); | 371 | + log.trace("[{}] Registering remote subscription [{}] for entity [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address); |
385 | registerSubscription(sessionId, entityId, subscription); | 372 | registerSubscription(sessionId, entityId, subscription); |
386 | if (subscription.getType() == TelemetryFeature.ATTRIBUTES) { | 373 | if (subscription.getType() == TelemetryFeature.ATTRIBUTES) { |
387 | final Map<String, Long> keyStates = subscription.getKeyStates(); | 374 | final Map<String, Long> keyStates = subscription.getKeyStates(); |
@@ -401,17 +388,22 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | @@ -401,17 +388,22 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio | ||
401 | long curTs = System.currentTimeMillis(); | 388 | long curTs = System.currentTimeMillis(); |
402 | List<ReadTsKvQuery> queries = new ArrayList<>(); | 389 | List<ReadTsKvQuery> queries = new ArrayList<>(); |
403 | subscription.getKeyStates().entrySet().forEach(e -> { | 390 | subscription.getKeyStates().entrySet().forEach(e -> { |
404 | - queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs)); | 391 | + if (curTs > e.getValue()) { |
392 | + queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs, 0, 1000, Aggregation.NONE)); | ||
393 | + } else { | ||
394 | + log.debug("[{}] Invalid subscription [{}], entityId [{}] curTs [{}]", sessionId, subscription, entityId, curTs); | ||
395 | + } | ||
405 | }); | 396 | }); |
406 | - | ||
407 | - DonAsynchron.withCallback(tsService.findAll(entityId, queries), | ||
408 | - missedUpdates -> { | ||
409 | - if (missedUpdates != null && !missedUpdates.isEmpty()) { | ||
410 | - tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); | ||
411 | - } | ||
412 | - }, | ||
413 | - e -> log.error("Failed to fetch missed updates.", e), | ||
414 | - tsCallBackExecutor); | 397 | + if (!queries.isEmpty()) { |
398 | + DonAsynchron.withCallback(tsService.findAll(entityId, queries), | ||
399 | + missedUpdates -> { | ||
400 | + if (missedUpdates != null && !missedUpdates.isEmpty()) { | ||
401 | + tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); | ||
402 | + } | ||
403 | + }, | ||
404 | + e -> log.error("Failed to fetch missed updates.", e), | ||
405 | + tsCallBackExecutor); | ||
406 | + } | ||
415 | } | 407 | } |
416 | } | 408 | } |
417 | 409 |
@@ -29,6 +29,9 @@ import org.apache.kafka.clients.producer.RecordMetadata; | @@ -29,6 +29,9 @@ import org.apache.kafka.clients.producer.RecordMetadata; | ||
29 | import org.springframework.beans.factory.annotation.Autowired; | 29 | import org.springframework.beans.factory.annotation.Autowired; |
30 | import org.springframework.beans.factory.annotation.Value; | 30 | import org.springframework.beans.factory.annotation.Value; |
31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
32 | +import org.springframework.boot.context.event.ApplicationReadyEvent; | ||
33 | +import org.springframework.context.event.ContextRefreshedEvent; | ||
34 | +import org.springframework.context.event.EventListener; | ||
32 | import org.springframework.stereotype.Service; | 35 | import org.springframework.stereotype.Service; |
33 | import org.thingsboard.server.actors.ActorSystemContext; | 36 | import org.thingsboard.server.actors.ActorSystemContext; |
34 | import org.thingsboard.server.actors.service.ActorService; | 37 | import org.thingsboard.server.actors.service.ActorService; |
@@ -127,7 +130,11 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ | @@ -127,7 +130,11 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ | ||
127 | 130 | ||
128 | ruleEngineConsumer = ruleEngineConsumerBuilder.build(); | 131 | ruleEngineConsumer = ruleEngineConsumerBuilder.build(); |
129 | ruleEngineConsumer.subscribe(); | 132 | ruleEngineConsumer.subscribe(); |
133 | + } | ||
130 | 134 | ||
135 | + @EventListener(ApplicationReadyEvent.class) | ||
136 | + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { | ||
137 | + log.info("Received application ready event. Starting polling for events."); | ||
131 | LocalBucketBuilder builder = Bucket4j.builder(); | 138 | LocalBucketBuilder builder = Bucket4j.builder(); |
132 | builder.addLimit(Bandwidth.simple(pollRecordsPerSecond, Duration.ofSeconds(1))); | 139 | builder.addLimit(Bandwidth.simple(pollRecordsPerSecond, Duration.ofSeconds(1))); |
133 | builder.addLimit(Bandwidth.simple(pollRecordsPerMinute, Duration.ofMinutes(1))); | 140 | builder.addLimit(Bandwidth.simple(pollRecordsPerMinute, Duration.ofMinutes(1))); |
@@ -149,6 +156,7 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ | @@ -149,6 +156,7 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ | ||
149 | records.forEach(record -> { | 156 | records.forEach(record -> { |
150 | try { | 157 | try { |
151 | ToRuleEngineMsg toRuleEngineMsg = ruleEngineConsumer.decode(record); | 158 | ToRuleEngineMsg toRuleEngineMsg = ruleEngineConsumer.decode(record); |
159 | + log.trace("Forwarding message to rule engine {}", toRuleEngineMsg); | ||
152 | if (toRuleEngineMsg.hasToDeviceActorMsg()) { | 160 | if (toRuleEngineMsg.hasToDeviceActorMsg()) { |
153 | forwardToDeviceActor(toRuleEngineMsg.getToDeviceActorMsg()); | 161 | forwardToDeviceActor(toRuleEngineMsg.getToDeviceActorMsg()); |
154 | } | 162 | } |
@@ -175,18 +183,21 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ | @@ -175,18 +183,21 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ | ||
175 | 183 | ||
176 | @Override | 184 | @Override |
177 | public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) { | 185 | public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) { |
178 | - notificationsProducer.send(notificationsTopic + "." + nodeId, | ||
179 | - new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()).toString(), | ||
180 | - ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build() | ||
181 | - , new QueueCallbackAdaptor(onSuccess, onFailure)); | 186 | + String topic = notificationsTopic + "." + nodeId; |
187 | + UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()); | ||
188 | + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build(); | ||
189 | + log.trace("[{}][{}] Pushing session data to topic: {}", topic, sessionId, transportMsg); | ||
190 | + notificationsProducer.send(topic, sessionId.toString(), transportMsg, new QueueCallbackAdaptor(onSuccess, onFailure)); | ||
182 | } | 191 | } |
183 | 192 | ||
184 | private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) { | 193 | private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) { |
185 | TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); | 194 | TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); |
186 | Optional<ServerAddress> address = routingService.resolveById(wrapper.getDeviceId()); | 195 | Optional<ServerAddress> address = routingService.resolveById(wrapper.getDeviceId()); |
187 | if (address.isPresent()) { | 196 | if (address.isPresent()) { |
197 | + log.trace("[{}] Pushing message to remote server: {}", address.get(), toDeviceActorMsg); | ||
188 | rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper)); | 198 | rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper)); |
189 | } else { | 199 | } else { |
200 | + log.trace("Pushing message to local server: {}", toDeviceActorMsg); | ||
190 | actorContext.getAppActor().tell(wrapper, ActorRef.noSender()); | 201 | actorContext.getAppActor().tell(wrapper, ActorRef.noSender()); |
191 | } | 202 | } |
192 | } | 203 | } |
@@ -19,6 +19,8 @@ import lombok.extern.slf4j.Slf4j; | @@ -19,6 +19,8 @@ import lombok.extern.slf4j.Slf4j; | ||
19 | import org.springframework.beans.factory.annotation.Autowired; | 19 | import org.springframework.beans.factory.annotation.Autowired; |
20 | import org.springframework.beans.factory.annotation.Value; | 20 | import org.springframework.beans.factory.annotation.Value; |
21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
22 | +import org.springframework.boot.context.event.ApplicationReadyEvent; | ||
23 | +import org.springframework.context.event.EventListener; | ||
22 | import org.springframework.stereotype.Component; | 24 | import org.springframework.stereotype.Component; |
23 | import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; | 25 | import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; |
24 | import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; | 26 | import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; |
@@ -93,6 +95,11 @@ public class RemoteTransportApiService { | @@ -93,6 +95,11 @@ public class RemoteTransportApiService { | ||
93 | builder.executor(transportCallbackExecutor); | 95 | builder.executor(transportCallbackExecutor); |
94 | builder.handler(transportApiService); | 96 | builder.handler(transportApiService); |
95 | transportApiTemplate = builder.build(); | 97 | transportApiTemplate = builder.build(); |
98 | + } | ||
99 | + | ||
100 | + @EventListener(ApplicationReadyEvent.class) | ||
101 | + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { | ||
102 | + log.info("Received application ready event. Starting polling for events."); | ||
96 | transportApiTemplate.init(); | 103 | transportApiTemplate.init(); |
97 | } | 104 | } |
98 | 105 |
@@ -19,7 +19,7 @@ akka { | @@ -19,7 +19,7 @@ akka { | ||
19 | # JVM shutdown, System.exit(-1), in case of a fatal error, | 19 | # JVM shutdown, System.exit(-1), in case of a fatal error, |
20 | # such as OutOfMemoryError | 20 | # such as OutOfMemoryError |
21 | jvm-exit-on-fatal-error = off | 21 | jvm-exit-on-fatal-error = off |
22 | - loglevel = "DEBUG" | 22 | + loglevel = "INFO" |
23 | loggers = ["akka.event.slf4j.Slf4jLogger"] | 23 | loggers = ["akka.event.slf4j.Slf4jLogger"] |
24 | } | 24 | } |
25 | 25 |
@@ -31,6 +31,7 @@ server: | @@ -31,6 +31,7 @@ server: | ||
31 | key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}" | 31 | key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}" |
32 | # Alias that identifies the key in the key store | 32 | # Alias that identifies the key in the key store |
33 | key-alias: "${SSL_KEY_ALIAS:tomcat}" | 33 | key-alias: "${SSL_KEY_ALIAS:tomcat}" |
34 | + log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:true}" | ||
34 | 35 | ||
35 | # Zookeeper connection parameters. Used for service discovery. | 36 | # Zookeeper connection parameters. Used for service discovery. |
36 | zk: | 37 | zk: |
@@ -63,7 +64,7 @@ cluster: | @@ -63,7 +64,7 @@ cluster: | ||
63 | 64 | ||
64 | # Plugins configuration parameters | 65 | # Plugins configuration parameters |
65 | plugins: | 66 | plugins: |
66 | - # Comma seperated package list used during classpath scanning for plugins | 67 | + # Comma separated package list used during classpath scanning for plugins |
67 | scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}" | 68 | scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}" |
68 | 69 | ||
69 | # Security parameters | 70 | # Security parameters |
@@ -83,6 +84,7 @@ dashboard: | @@ -83,6 +84,7 @@ dashboard: | ||
83 | max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" | 84 | max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" |
84 | 85 | ||
85 | database: | 86 | database: |
87 | + ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records | ||
86 | entities: | 88 | entities: |
87 | type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql | 89 | type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql |
88 | ts: | 90 | ts: |
@@ -105,7 +107,7 @@ cassandra: | @@ -105,7 +107,7 @@ cassandra: | ||
105 | metrics: "${CASSANDRA_DISABLE_METRICS:true}" | 107 | metrics: "${CASSANDRA_DISABLE_METRICS:true}" |
106 | # NONE SNAPPY LZ4 | 108 | # NONE SNAPPY LZ4 |
107 | compression: "${CASSANDRA_COMPRESSION:none}" | 109 | compression: "${CASSANDRA_COMPRESSION:none}" |
108 | - # Specify cassandra claster initialization timeout (if no hosts available during startup) | 110 | + # Specify cassandra cluster initialization timeout in milliseconds (if no hosts available during startup) |
109 | init_timeout_ms: "${CASSANDRA_CLUSTER_INIT_TIMEOUT_MS:300000}" | 111 | init_timeout_ms: "${CASSANDRA_CLUSTER_INIT_TIMEOUT_MS:300000}" |
110 | # Specify cassandra claster initialization retry interval (if no hosts available during startup) | 112 | # Specify cassandra claster initialization retry interval (if no hosts available during startup) |
111 | init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}" | 113 | init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}" |
@@ -151,6 +153,8 @@ sql: | @@ -151,6 +153,8 @@ sql: | ||
151 | 153 | ||
152 | # Actor system parameters | 154 | # Actor system parameters |
153 | actors: | 155 | actors: |
156 | + cluster: | ||
157 | + grpc_callback_thread_pool_size: "${ACTORS_CLUSTER_GRPC_CALLBACK_THREAD_POOL_SIZE:10}" | ||
154 | tenant: | 158 | tenant: |
155 | create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" | 159 | create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" |
156 | session: | 160 | session: |
@@ -306,7 +310,7 @@ audit_log: | @@ -306,7 +310,7 @@ audit_log: | ||
306 | "user": "${AUDIT_LOG_MASK_USER:W}" | 310 | "user": "${AUDIT_LOG_MASK_USER:W}" |
307 | "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" | 311 | "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" |
308 | "alarm": "${AUDIT_LOG_MASK_ALARM:W}" | 312 | "alarm": "${AUDIT_LOG_MASK_ALARM:W}" |
309 | - "entity_view": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" | 313 | + "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}" |
310 | sink: | 314 | sink: |
311 | # Type of external sink. possible options: none, elasticsearch | 315 | # Type of external sink. possible options: none, elasticsearch |
312 | type: "${AUDIT_LOG_SINK_TYPE:none}" | 316 | type: "${AUDIT_LOG_SINK_TYPE:none}" |
@@ -320,7 +324,7 @@ audit_log: | @@ -320,7 +324,7 @@ audit_log: | ||
320 | date_format: "${AUDIT_LOG_SINK_DATE_FORMAT:YYYY.MM.DD}" | 324 | date_format: "${AUDIT_LOG_SINK_DATE_FORMAT:YYYY.MM.DD}" |
321 | scheme_name: "${AUDIT_LOG_SINK_SCHEME_NAME:http}" # http or https | 325 | scheme_name: "${AUDIT_LOG_SINK_SCHEME_NAME:http}" # http or https |
322 | host: "${AUDIT_LOG_SINK_HOST:localhost}" | 326 | host: "${AUDIT_LOG_SINK_HOST:localhost}" |
323 | - port: "${AUDIT_LOG_SINK_POST:9200}" | 327 | + port: "${AUDIT_LOG_SINK_PORT:9200}" |
324 | user_name: "${AUDIT_LOG_SINK_USER_NAME:}" | 328 | user_name: "${AUDIT_LOG_SINK_USER_NAME:}" |
325 | password: "${AUDIT_LOG_SINK_PASSWORD:}" | 329 | password: "${AUDIT_LOG_SINK_PASSWORD:}" |
326 | 330 | ||
@@ -340,7 +344,7 @@ kafka: | @@ -340,7 +344,7 @@ kafka: | ||
340 | requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" | 344 | requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" |
341 | responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" | 345 | responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" |
342 | max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" | 346 | max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" |
343 | - request_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" | 347 | + max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" |
344 | request_poll_interval: "${TB_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}" | 348 | request_poll_interval: "${TB_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}" |
345 | request_auto_commit_interval: "${TB_TRANSPORT_REQUEST_AUTO_COMMIT_INTERVAL_MS:100}" | 349 | request_auto_commit_interval: "${TB_TRANSPORT_REQUEST_AUTO_COMMIT_INTERVAL_MS:100}" |
346 | rule_engine: | 350 | rule_engine: |
@@ -367,7 +371,7 @@ js: | @@ -367,7 +371,7 @@ js: | ||
367 | # JS Eval request topic | 371 | # JS Eval request topic |
368 | request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js.eval.requests}" | 372 | request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js.eval.requests}" |
369 | # JS Eval responses topic prefix that is combined with node id | 373 | # JS Eval responses topic prefix that is combined with node id |
370 | - response_topic_prefix: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js.eval.responses}" | 374 | + response_topic_prefix: "${REMOTE_JS_EVAL_RESPONSE_TOPIC:js.eval.responses}" |
371 | # JS Eval max pending requests | 375 | # JS Eval max pending requests |
372 | max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" | 376 | max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" |
373 | # JS Eval max request timeout | 377 | # JS Eval max request timeout |
@@ -405,6 +409,9 @@ transport: | @@ -405,6 +409,9 @@ transport: | ||
405 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" | 409 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" |
406 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" | 410 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" |
407 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" | 411 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" |
412 | + json: | ||
413 | + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON | ||
414 | + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" | ||
408 | # Local HTTP transport parameters | 415 | # Local HTTP transport parameters |
409 | http: | 416 | http: |
410 | enabled: "${HTTP_ENABLED:true}" | 417 | enabled: "${HTTP_ENABLED:true}" |
@@ -9,7 +9,7 @@ | @@ -9,7 +9,7 @@ | ||
9 | 9 | ||
10 | <logger name="org.thingsboard.server" level="WARN"/> | 10 | <logger name="org.thingsboard.server" level="WARN"/> |
11 | <logger name="org.springframework" level="WARN"/> | 11 | <logger name="org.springframework" level="WARN"/> |
12 | - <logger name="org.springframework.boot.test" level="DEBUG"/> | 12 | + <logger name="org.springframework.boot.test" level="WARN"/> |
13 | <logger name="org.apache.cassandra" level="WARN"/> | 13 | <logger name="org.apache.cassandra" level="WARN"/> |
14 | <logger name="org.cassandraunit" level="INFO"/> | 14 | <logger name="org.cassandraunit" level="INFO"/> |
15 | 15 |
@@ -24,6 +24,7 @@ public enum ActionType { | @@ -24,6 +24,7 @@ public enum ActionType { | ||
24 | UPDATED(false), // log entity | 24 | UPDATED(false), // log entity |
25 | ATTRIBUTES_UPDATED(false), // log attributes/values | 25 | ATTRIBUTES_UPDATED(false), // log attributes/values |
26 | ATTRIBUTES_DELETED(false), // log attributes | 26 | ATTRIBUTES_DELETED(false), // log attributes |
27 | + TIMESERIES_DELETED(false), // log timeseries | ||
27 | RPC_CALL(false), // log method and params | 28 | RPC_CALL(false), // log method and params |
28 | CREDENTIALS_UPDATED(false), // log new credentials | 29 | CREDENTIALS_UPDATED(false), // log new credentials |
29 | ASSIGNED_TO_CUSTOMER(false), // log customer name | 30 | ASSIGNED_TO_CUSTOMER(false), // log customer name |
@@ -32,11 +33,11 @@ public enum ActionType { | @@ -32,11 +33,11 @@ public enum ActionType { | ||
32 | SUSPENDED(false), // log string id | 33 | SUSPENDED(false), // log string id |
33 | CREDENTIALS_READ(true), // log device id | 34 | CREDENTIALS_READ(true), // log device id |
34 | ATTRIBUTES_READ(true), // log attributes | 35 | ATTRIBUTES_READ(true), // log attributes |
35 | - RELATION_ADD_OR_UPDATE (false), | ||
36 | - RELATION_DELETED (false), | ||
37 | - RELATIONS_DELETED (false), | ||
38 | - ALARM_ACK (false), | ||
39 | - ALARM_CLEAR (false); | 36 | + RELATION_ADD_OR_UPDATE(false), |
37 | + RELATION_DELETED(false), | ||
38 | + RELATIONS_DELETED(false), | ||
39 | + ALARM_ACK(false), | ||
40 | + ALARM_CLEAR(false); | ||
40 | 41 | ||
41 | private final boolean isRead; | 42 | private final boolean isRead; |
42 | 43 |
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.objects; | @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.objects; | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | import lombok.NoArgsConstructor; | 19 | import lombok.NoArgsConstructor; |
20 | 20 | ||
21 | +import java.io.Serializable; | ||
21 | import java.util.ArrayList; | 22 | import java.util.ArrayList; |
22 | import java.util.List; | 23 | import java.util.List; |
23 | 24 | ||
@@ -26,7 +27,7 @@ import java.util.List; | @@ -26,7 +27,7 @@ import java.util.List; | ||
26 | */ | 27 | */ |
27 | @Data | 28 | @Data |
28 | @NoArgsConstructor | 29 | @NoArgsConstructor |
29 | -public class AttributesEntityView { | 30 | +public class AttributesEntityView implements Serializable { |
30 | 31 | ||
31 | private List<String> cs = new ArrayList<>(); | 32 | private List<String> cs = new ArrayList<>(); |
32 | private List<String> ss = new ArrayList<>(); | 33 | private List<String> ss = new ArrayList<>(); |
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.objects; | @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.objects; | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | import lombok.NoArgsConstructor; | 19 | import lombok.NoArgsConstructor; |
20 | 20 | ||
21 | +import java.io.Serializable; | ||
21 | import java.util.ArrayList; | 22 | import java.util.ArrayList; |
22 | import java.util.List; | 23 | import java.util.List; |
23 | 24 | ||
@@ -26,7 +27,7 @@ import java.util.List; | @@ -26,7 +27,7 @@ import java.util.List; | ||
26 | */ | 27 | */ |
27 | @Data | 28 | @Data |
28 | @NoArgsConstructor | 29 | @NoArgsConstructor |
29 | -public class TelemetryEntityView { | 30 | +public class TelemetryEntityView implements Serializable { |
30 | 31 | ||
31 | private List<String> timeseries; | 32 | private List<String> timeseries; |
32 | private AttributesEntityView attributes; | 33 | private AttributesEntityView attributes; |
@@ -77,9 +77,10 @@ public class TBKafkaProducerTemplate<T> { | @@ -77,9 +77,10 @@ public class TBKafkaProducerTemplate<T> { | ||
77 | result.all().get(); | 77 | result.all().get(); |
78 | } catch (Exception e) { | 78 | } catch (Exception e) { |
79 | if ((e instanceof TopicExistsException) || (e.getCause() != null && e.getCause() instanceof TopicExistsException)) { | 79 | if ((e instanceof TopicExistsException) || (e.getCause() != null && e.getCause() instanceof TopicExistsException)) { |
80 | - log.trace("[{}] Topic already exists: ", defaultTopic); | 80 | + log.trace("[{}] Topic already exists.", defaultTopic); |
81 | } else { | 81 | } else { |
82 | - log.trace("[{}] Failed to create topic: {}", defaultTopic, e.getMessage(), e); | 82 | + log.info("[{}] Failed to create topic: {}", defaultTopic, e.getMessage(), e); |
83 | + throw new RuntimeException(e); | ||
83 | } | 84 | } |
84 | } | 85 | } |
85 | //Maybe this should not be cached, but we don't plan to change size of partitions | 86 | //Maybe this should not be cached, but we don't plan to change size of partitions |
@@ -23,6 +23,9 @@ import lombok.extern.slf4j.Slf4j; | @@ -23,6 +23,9 @@ import lombok.extern.slf4j.Slf4j; | ||
23 | import org.apache.kafka.clients.admin.CreateTopicsResult; | 23 | import org.apache.kafka.clients.admin.CreateTopicsResult; |
24 | import org.apache.kafka.clients.admin.NewTopic; | 24 | import org.apache.kafka.clients.admin.NewTopic; |
25 | import org.apache.kafka.clients.consumer.ConsumerRecords; | 25 | import org.apache.kafka.clients.consumer.ConsumerRecords; |
26 | +import org.apache.kafka.clients.producer.Callback; | ||
27 | +import org.apache.kafka.clients.producer.RecordMetadata; | ||
28 | +import org.apache.kafka.common.errors.TopicExistsException; | ||
26 | import org.apache.kafka.common.header.Header; | 29 | import org.apache.kafka.common.header.Header; |
27 | import org.apache.kafka.common.header.internals.RecordHeader; | 30 | import org.apache.kafka.common.header.internals.RecordHeader; |
28 | 31 | ||
@@ -83,7 +86,13 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | @@ -83,7 +86,13 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | ||
83 | CreateTopicsResult result = admin.createTopic(new NewTopic(responseTemplate.getTopic(), 1, (short) 1)); | 86 | CreateTopicsResult result = admin.createTopic(new NewTopic(responseTemplate.getTopic(), 1, (short) 1)); |
84 | result.all().get(); | 87 | result.all().get(); |
85 | } catch (Exception e) { | 88 | } catch (Exception e) { |
86 | - log.trace("Failed to create topic: {}", e.getMessage(), e); | 89 | + if ((e instanceof TopicExistsException) || (e.getCause() != null && e.getCause() instanceof TopicExistsException)) { |
90 | + log.trace("[{}] Topic already exists. ", responseTemplate.getTopic()); | ||
91 | + } else { | ||
92 | + log.info("[{}] Failed to create topic: {}", responseTemplate.getTopic(), e.getMessage(), e); | ||
93 | + throw new RuntimeException(e); | ||
94 | + } | ||
95 | + | ||
87 | } | 96 | } |
88 | this.requestTemplate.init(); | 97 | this.requestTemplate.init(); |
89 | tickTs = System.currentTimeMillis(); | 98 | tickTs = System.currentTimeMillis(); |
@@ -92,7 +101,11 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | @@ -92,7 +101,11 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | ||
92 | long nextCleanupMs = 0L; | 101 | long nextCleanupMs = 0L; |
93 | while (!stopped) { | 102 | while (!stopped) { |
94 | ConsumerRecords<String, byte[]> responses = responseTemplate.poll(Duration.ofMillis(pollInterval)); | 103 | ConsumerRecords<String, byte[]> responses = responseTemplate.poll(Duration.ofMillis(pollInterval)); |
104 | + if (responses.count() > 0) { | ||
105 | + log.trace("Polling responses completed, consumer records count [{}]", responses.count()); | ||
106 | + } | ||
95 | responses.forEach(response -> { | 107 | responses.forEach(response -> { |
108 | + log.trace("Received response to Kafka Template request: {}", response); | ||
96 | Header requestIdHeader = response.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER); | 109 | Header requestIdHeader = response.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER); |
97 | Response decodedResponse = null; | 110 | Response decodedResponse = null; |
98 | UUID requestId = null; | 111 | UUID requestId = null; |
@@ -109,6 +122,7 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | @@ -109,6 +122,7 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | ||
109 | if (requestId == null) { | 122 | if (requestId == null) { |
110 | log.error("[{}] Missing requestId in header and body", response); | 123 | log.error("[{}] Missing requestId in header and body", response); |
111 | } else { | 124 | } else { |
125 | + log.trace("[{}] Response received", requestId); | ||
112 | ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId); | 126 | ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId); |
113 | if (expectedResponse == null) { | 127 | if (expectedResponse == null) { |
114 | log.trace("[{}] Invalid or stale request", requestId); | 128 | log.trace("[{}] Invalid or stale request", requestId); |
@@ -132,6 +146,7 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | @@ -132,6 +146,7 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | ||
132 | if (kv.getValue().expTime < tickTs) { | 146 | if (kv.getValue().expTime < tickTs) { |
133 | ResponseMetaData<Response> staleRequest = pendingRequests.remove(kv.getKey()); | 147 | ResponseMetaData<Response> staleRequest = pendingRequests.remove(kv.getKey()); |
134 | if (staleRequest != null) { | 148 | if (staleRequest != null) { |
149 | + log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", kv.getKey(), staleRequest.expTime, tickTs); | ||
135 | staleRequest.future.setException(new TimeoutException()); | 150 | staleRequest.future.setException(new TimeoutException()); |
136 | } | 151 | } |
137 | } | 152 | } |
@@ -158,9 +173,17 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | @@ -158,9 +173,17 @@ public class TbKafkaRequestTemplate<Request, Response> extends AbstractTbKafkaTe | ||
158 | headers.add(new RecordHeader(TbKafkaSettings.REQUEST_ID_HEADER, uuidToBytes(requestId))); | 173 | headers.add(new RecordHeader(TbKafkaSettings.REQUEST_ID_HEADER, uuidToBytes(requestId))); |
159 | headers.add(new RecordHeader(TbKafkaSettings.RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic()))); | 174 | headers.add(new RecordHeader(TbKafkaSettings.RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic()))); |
160 | SettableFuture<Response> future = SettableFuture.create(); | 175 | SettableFuture<Response> future = SettableFuture.create(); |
161 | - pendingRequests.putIfAbsent(requestId, new ResponseMetaData<>(tickTs + maxRequestTimeout, future)); | 176 | + ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); |
177 | + pendingRequests.putIfAbsent(requestId, responseMetaData); | ||
162 | request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId); | 178 | request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId); |
163 | - requestTemplate.send(key, request, headers, null); | 179 | + log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, key, responseMetaData.expTime); |
180 | + requestTemplate.send(key, request, headers, (metadata, exception) -> { | ||
181 | + if (exception != null) { | ||
182 | + log.trace("[{}] Failed to post the request", requestId, exception); | ||
183 | + } else { | ||
184 | + log.trace("[{}] Posted the request", requestId, metadata); | ||
185 | + } | ||
186 | + }); | ||
164 | return future; | 187 | return future; |
165 | } | 188 | } |
166 | 189 |
@@ -18,6 +18,7 @@ package org.thingsboard.server.kafka; | @@ -18,6 +18,7 @@ package org.thingsboard.server.kafka; | ||
18 | import lombok.Builder; | 18 | import lombok.Builder; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | import org.apache.kafka.clients.consumer.ConsumerRecords; | 20 | import org.apache.kafka.clients.consumer.ConsumerRecords; |
21 | +import org.apache.kafka.common.errors.InterruptException; | ||
21 | import org.apache.kafka.common.header.Header; | 22 | import org.apache.kafka.common.header.Header; |
22 | import org.apache.kafka.common.header.internals.RecordHeader; | 23 | import org.apache.kafka.common.header.internals.RecordHeader; |
23 | 24 | ||
@@ -127,6 +128,10 @@ public class TbKafkaResponseTemplate<Request, Response> extends AbstractTbKafkaT | @@ -127,6 +128,10 @@ public class TbKafkaResponseTemplate<Request, Response> extends AbstractTbKafkaT | ||
127 | log.warn("[{}] Failed to process the request: {}", requestId, request, e); | 128 | log.warn("[{}] Failed to process the request: {}", requestId, request, e); |
128 | } | 129 | } |
129 | }); | 130 | }); |
131 | + } catch (InterruptException ie) { | ||
132 | + if (!stopped) { | ||
133 | + log.warn("Fetching data from kafka was interrupted.", ie); | ||
134 | + } | ||
130 | } catch (Throwable e) { | 135 | } catch (Throwable e) { |
131 | log.warn("Failed to obtain messages from queue.", e); | 136 | log.warn("Failed to obtain messages from queue.", e); |
132 | try { | 137 | try { |
@@ -141,7 +141,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -141,7 +141,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
141 | processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg); | 141 | processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg); |
142 | break; | 142 | break; |
143 | case PINGREQ: | 143 | case PINGREQ: |
144 | - if (checkConnected(ctx)) { | 144 | + if (checkConnected(ctx, msg)) { |
145 | ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); | 145 | ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); |
146 | transportService.reportActivity(sessionInfo); | 146 | transportService.reportActivity(sessionInfo); |
147 | if (gatewaySessionHandler != null) { | 147 | if (gatewaySessionHandler != null) { |
@@ -150,7 +150,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -150,7 +150,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
150 | } | 150 | } |
151 | break; | 151 | break; |
152 | case DISCONNECT: | 152 | case DISCONNECT: |
153 | - if (checkConnected(ctx)) { | 153 | + if (checkConnected(ctx, msg)) { |
154 | processDisconnect(ctx); | 154 | processDisconnect(ctx); |
155 | } | 155 | } |
156 | break; | 156 | break; |
@@ -161,12 +161,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -161,12 +161,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
161 | } | 161 | } |
162 | 162 | ||
163 | private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) { | 163 | private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) { |
164 | - if (!checkConnected(ctx)) { | 164 | + if (!checkConnected(ctx, mqttMsg)) { |
165 | return; | 165 | return; |
166 | } | 166 | } |
167 | String topicName = mqttMsg.variableHeader().topicName(); | 167 | String topicName = mqttMsg.variableHeader().topicName(); |
168 | int msgId = mqttMsg.variableHeader().packetId(); | 168 | int msgId = mqttMsg.variableHeader().packetId(); |
169 | - log.trace("[{}] Processing publish msg [{}][{}]!", sessionId, topicName, msgId); | 169 | + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); |
170 | 170 | ||
171 | if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { | 171 | if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { |
172 | if (gatewaySessionHandler != null) { | 172 | if (gatewaySessionHandler != null) { |
@@ -248,7 +248,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -248,7 +248,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
248 | } | 248 | } |
249 | 249 | ||
250 | private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { | 250 | private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { |
251 | - if (!checkConnected(ctx)) { | 251 | + if (!checkConnected(ctx, mqttMsg)) { |
252 | return; | 252 | return; |
253 | } | 253 | } |
254 | log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); | 254 | log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); |
@@ -293,7 +293,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -293,7 +293,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
293 | } | 293 | } |
294 | 294 | ||
295 | private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { | 295 | private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { |
296 | - if (!checkConnected(ctx)) { | 296 | + if (!checkConnected(ctx, mqttMsg)) { |
297 | return; | 297 | return; |
298 | } | 298 | } |
299 | log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); | 299 | log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); |
@@ -336,6 +336,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -336,6 +336,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
336 | 336 | ||
337 | private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { | 337 | private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { |
338 | String userName = msg.payload().userName(); | 338 | String userName = msg.payload().userName(); |
339 | + log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); | ||
339 | if (StringUtils.isEmpty(userName)) { | 340 | if (StringUtils.isEmpty(userName)) { |
340 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)); | 341 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)); |
341 | ctx.close(); | 342 | ctx.close(); |
@@ -444,11 +445,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -444,11 +445,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
444 | return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); | 445 | return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); |
445 | } | 446 | } |
446 | 447 | ||
447 | - private boolean checkConnected(ChannelHandlerContext ctx) { | 448 | + private boolean checkConnected(ChannelHandlerContext ctx, MqttMessage msg) { |
448 | if (deviceSessionCtx.isConnected()) { | 449 | if (deviceSessionCtx.isConnected()) { |
449 | return true; | 450 | return true; |
450 | } else { | 451 | } else { |
451 | - log.info("[{}] Closing current session due to invalid msg order [{}][{}]", sessionId); | 452 | + log.info("[{}] Closing current session due to invalid msg order: {}", sessionId, msg); |
452 | ctx.close(); | 453 | ctx.close(); |
453 | return false; | 454 | return false; |
454 | } | 455 | } |
@@ -496,6 +497,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -496,6 +497,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
496 | transportService.registerAsyncSession(sessionInfo, this); | 497 | transportService.registerAsyncSession(sessionInfo, this); |
497 | checkGatewaySession(); | 498 | checkGatewaySession(); |
498 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); | 499 | ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); |
500 | + log.info("[{}] Client connected!", sessionId); | ||
499 | } | 501 | } |
500 | } | 502 | } |
501 | 503 |
@@ -119,10 +119,10 @@ public class GatewaySessionHandler { | @@ -119,10 +119,10 @@ public class GatewaySessionHandler { | ||
119 | GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), mqttQoSMap); | 119 | GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), mqttQoSMap); |
120 | if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) { | 120 | if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) { |
121 | SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo(); | 121 | SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo(); |
122 | + transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx); | ||
122 | transportService.process(deviceSessionInfo, AbstractTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); | 123 | transportService.process(deviceSessionInfo, AbstractTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); |
123 | transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null); | 124 | transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null); |
124 | transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null); | 125 | transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null); |
125 | - transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx); | ||
126 | } | 126 | } |
127 | future.set(devices.get(deviceName)); | 127 | future.set(devices.get(deviceName)); |
128 | } | 128 | } |
@@ -43,7 +43,7 @@ public interface TransportService { | @@ -43,7 +43,7 @@ public interface TransportService { | ||
43 | void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg msg, | 43 | void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg msg, |
44 | TransportServiceCallback<TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg> callback); | 44 | TransportServiceCallback<TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg> callback); |
45 | 45 | ||
46 | - boolean checkLimits(SessionInfoProto sessionInfo, TransportServiceCallback<Void> callback); | 46 | + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback); |
47 | 47 | ||
48 | void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback); | 48 | void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback); |
49 | 49 |
@@ -22,6 +22,7 @@ import com.google.gson.JsonObject; | @@ -22,6 +22,7 @@ import com.google.gson.JsonObject; | ||
22 | import com.google.gson.JsonParser; | 22 | import com.google.gson.JsonParser; |
23 | import com.google.gson.JsonPrimitive; | 23 | import com.google.gson.JsonPrimitive; |
24 | import com.google.gson.JsonSyntaxException; | 24 | import com.google.gson.JsonSyntaxException; |
25 | +import org.apache.commons.lang3.math.NumberUtils; | ||
25 | import org.springframework.util.StringUtils; | 26 | import org.springframework.util.StringUtils; |
26 | import org.thingsboard.server.common.data.kv.AttributeKey; | 27 | import org.thingsboard.server.common.data.kv.AttributeKey; |
27 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; | 28 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
@@ -58,6 +59,8 @@ public class JsonConverter { | @@ -58,6 +59,8 @@ public class JsonConverter { | ||
58 | private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; | 59 | private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; |
59 | private static final String DEVICE_PROPERTY = "device"; | 60 | private static final String DEVICE_PROPERTY = "device"; |
60 | 61 | ||
62 | + private static boolean isTypeCastEnabled = true; | ||
63 | + | ||
61 | public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonObject) throws JsonSyntaxException { | 64 | public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonObject) throws JsonSyntaxException { |
62 | long systemTs = System.currentTimeMillis(); | 65 | long systemTs = System.currentTimeMillis(); |
63 | PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder(); | 66 | PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder(); |
@@ -128,24 +131,22 @@ public class JsonConverter { | @@ -128,24 +131,22 @@ public class JsonConverter { | ||
128 | if (element.isJsonPrimitive()) { | 131 | if (element.isJsonPrimitive()) { |
129 | JsonPrimitive value = element.getAsJsonPrimitive(); | 132 | JsonPrimitive value = element.getAsJsonPrimitive(); |
130 | if (value.isString()) { | 133 | if (value.isString()) { |
131 | - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) | ||
132 | - .setStringV(value.getAsString()).build()); | 134 | + if(isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { |
135 | + try { | ||
136 | + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); | ||
137 | + } catch (RuntimeException th) { | ||
138 | + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) | ||
139 | + .setStringV(value.getAsString()).build()); | ||
140 | + } | ||
141 | + } else { | ||
142 | + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) | ||
143 | + .setStringV(value.getAsString()).build()); | ||
144 | + } | ||
133 | } else if (value.isBoolean()) { | 145 | } else if (value.isBoolean()) { |
134 | result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) | 146 | result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) |
135 | .setBoolV(value.getAsBoolean()).build()); | 147 | .setBoolV(value.getAsBoolean()).build()); |
136 | } else if (value.isNumber()) { | 148 | } else if (value.isNumber()) { |
137 | - if (value.getAsString().contains(".")) { | ||
138 | - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.DOUBLE_V) | ||
139 | - .setDoubleV(value.getAsDouble()).build()); | ||
140 | - } else { | ||
141 | - try { | ||
142 | - long longValue = Long.parseLong(value.getAsString()); | ||
143 | - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.LONG_V) | ||
144 | - .setLongV(longValue).build()); | ||
145 | - } catch (NumberFormatException e) { | ||
146 | - throw new JsonSyntaxException("Big integer values are not supported!"); | ||
147 | - } | ||
148 | - } | 149 | + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); |
149 | } else { | 150 | } else { |
150 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); | 151 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); |
151 | } | 152 | } |
@@ -156,6 +157,24 @@ public class JsonConverter { | @@ -156,6 +157,24 @@ public class JsonConverter { | ||
156 | return result; | 157 | return result; |
157 | } | 158 | } |
158 | 159 | ||
160 | + private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { | ||
161 | + if (value.getAsString().contains(".")) { | ||
162 | + return KeyValueProto.newBuilder() | ||
163 | + .setKey(key) | ||
164 | + .setType(KeyValueType.DOUBLE_V) | ||
165 | + .setDoubleV(value.getAsDouble()) | ||
166 | + .build(); | ||
167 | + } else { | ||
168 | + try { | ||
169 | + long longValue = Long.parseLong(value.getAsString()); | ||
170 | + return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V) | ||
171 | + .setLongV(longValue).build(); | ||
172 | + } catch (NumberFormatException e) { | ||
173 | + throw new JsonSyntaxException("Big integer values are not supported!"); | ||
174 | + } | ||
175 | + } | ||
176 | + } | ||
177 | + | ||
159 | public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { | 178 | public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { |
160 | JsonObject object = json.getAsJsonObject(); | 179 | JsonObject object = json.getAsJsonObject(); |
161 | return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build(); | 180 | return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build(); |
@@ -370,7 +389,15 @@ public class JsonConverter { | @@ -370,7 +389,15 @@ public class JsonConverter { | ||
370 | if (element.isJsonPrimitive()) { | 389 | if (element.isJsonPrimitive()) { |
371 | JsonPrimitive value = element.getAsJsonPrimitive(); | 390 | JsonPrimitive value = element.getAsJsonPrimitive(); |
372 | if (value.isString()) { | 391 | if (value.isString()) { |
373 | - result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); | 392 | + if(isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { |
393 | + try { | ||
394 | + parseNumericValue(result, valueEntry, value); | ||
395 | + } catch (RuntimeException th) { | ||
396 | + result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); | ||
397 | + } | ||
398 | + } else { | ||
399 | + result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); | ||
400 | + } | ||
374 | } else if (value.isBoolean()) { | 401 | } else if (value.isBoolean()) { |
375 | result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); | 402 | result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); |
376 | } else if (value.isNumber()) { | 403 | } else if (value.isNumber()) { |
@@ -426,5 +453,7 @@ public class JsonConverter { | @@ -426,5 +453,7 @@ public class JsonConverter { | ||
426 | } | 453 | } |
427 | } | 454 | } |
428 | 455 | ||
429 | - | 456 | + public static void setTypeCastEnabled(boolean enabled) { |
457 | + isTypeCastEnabled = enabled; | ||
458 | + } | ||
430 | } | 459 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.common.transport.adaptor; | ||
17 | + | ||
18 | +import lombok.extern.slf4j.Slf4j; | ||
19 | +import org.springframework.beans.factory.annotation.Value; | ||
20 | +import org.springframework.context.annotation.Configuration; | ||
21 | + | ||
22 | +@Configuration | ||
23 | +@Slf4j | ||
24 | +public class JsonConverterConfig { | ||
25 | + | ||
26 | + @Value("${transport.json.type_cast_enabled:true}") | ||
27 | + public void setIsJsonTypeCastEnabled(boolean jsonTypeCastEnabled) { | ||
28 | + JsonConverter.setTypeCastEnabled(jsonTypeCastEnabled); | ||
29 | + log.info("JSON type cast enabled = {}", jsonTypeCastEnabled); | ||
30 | + } | ||
31 | +} |
@@ -68,7 +68,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -68,7 +68,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
68 | 68 | ||
69 | @Override | 69 | @Override |
70 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback<Void> callback) { | 70 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback<Void> callback) { |
71 | - if (checkLimits(sessionInfo, callback)) { | 71 | + if (checkLimits(sessionInfo, msg, callback)) { |
72 | reportActivityInternal(sessionInfo); | 72 | reportActivityInternal(sessionInfo); |
73 | doProcess(sessionInfo, msg, callback); | 73 | doProcess(sessionInfo, msg, callback); |
74 | } | 74 | } |
@@ -76,7 +76,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -76,7 +76,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
76 | 76 | ||
77 | @Override | 77 | @Override |
78 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { | 78 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { |
79 | - if (checkLimits(sessionInfo, callback)) { | 79 | + if (checkLimits(sessionInfo, msg, callback)) { |
80 | reportActivityInternal(sessionInfo); | 80 | reportActivityInternal(sessionInfo); |
81 | doProcess(sessionInfo, msg, callback); | 81 | doProcess(sessionInfo, msg, callback); |
82 | } | 82 | } |
@@ -84,7 +84,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -84,7 +84,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
84 | 84 | ||
85 | @Override | 85 | @Override |
86 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { | 86 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { |
87 | - if (checkLimits(sessionInfo, callback)) { | 87 | + if (checkLimits(sessionInfo, msg, callback)) { |
88 | reportActivityInternal(sessionInfo); | 88 | reportActivityInternal(sessionInfo); |
89 | doProcess(sessionInfo, msg, callback); | 89 | doProcess(sessionInfo, msg, callback); |
90 | } | 90 | } |
@@ -92,7 +92,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -92,7 +92,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
92 | 92 | ||
93 | @Override | 93 | @Override |
94 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) { | 94 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) { |
95 | - if (checkLimits(sessionInfo, callback)) { | 95 | + if (checkLimits(sessionInfo, msg, callback)) { |
96 | reportActivityInternal(sessionInfo); | 96 | reportActivityInternal(sessionInfo); |
97 | doProcess(sessionInfo, msg, callback); | 97 | doProcess(sessionInfo, msg, callback); |
98 | } | 98 | } |
@@ -100,7 +100,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -100,7 +100,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
100 | 100 | ||
101 | @Override | 101 | @Override |
102 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) { | 102 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) { |
103 | - if (checkLimits(sessionInfo, callback)) { | 103 | + if (checkLimits(sessionInfo, msg, callback)) { |
104 | SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); | 104 | SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); |
105 | sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); | 105 | sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); |
106 | doProcess(sessionInfo, msg, callback); | 106 | doProcess(sessionInfo, msg, callback); |
@@ -109,7 +109,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -109,7 +109,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
109 | 109 | ||
110 | @Override | 110 | @Override |
111 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) { | 111 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) { |
112 | - if (checkLimits(sessionInfo, callback)) { | 112 | + if (checkLimits(sessionInfo, msg, callback)) { |
113 | SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); | 113 | SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); |
114 | sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); | 114 | sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); |
115 | doProcess(sessionInfo, msg, callback); | 115 | doProcess(sessionInfo, msg, callback); |
@@ -118,7 +118,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -118,7 +118,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
118 | 118 | ||
119 | @Override | 119 | @Override |
120 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) { | 120 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) { |
121 | - if (checkLimits(sessionInfo, callback)) { | 121 | + if (checkLimits(sessionInfo, msg, callback)) { |
122 | reportActivityInternal(sessionInfo); | 122 | reportActivityInternal(sessionInfo); |
123 | doProcess(sessionInfo, msg, callback); | 123 | doProcess(sessionInfo, msg, callback); |
124 | } | 124 | } |
@@ -126,7 +126,7 @@ public abstract class AbstractTransportService implements TransportService { | @@ -126,7 +126,7 @@ public abstract class AbstractTransportService implements TransportService { | ||
126 | 126 | ||
127 | @Override | 127 | @Override |
128 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) { | 128 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) { |
129 | - if (checkLimits(sessionInfo, callback)) { | 129 | + if (checkLimits(sessionInfo, msg, callback)) { |
130 | reportActivityInternal(sessionInfo); | 130 | reportActivityInternal(sessionInfo); |
131 | doProcess(sessionInfo, msg, callback); | 131 | doProcess(sessionInfo, msg, callback); |
132 | } | 132 | } |
@@ -196,7 +196,10 @@ public abstract class AbstractTransportService implements TransportService { | @@ -196,7 +196,10 @@ public abstract class AbstractTransportService implements TransportService { | ||
196 | } | 196 | } |
197 | 197 | ||
198 | @Override | 198 | @Override |
199 | - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, TransportServiceCallback<Void> callback) { | 199 | + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) { |
200 | + if (log.isTraceEnabled()) { | ||
201 | + log.trace("[{}] Processing msg: {}", toId(sessionInfo), msg); | ||
202 | + } | ||
200 | if (!rateLimitEnabled) { | 203 | if (!rateLimitEnabled) { |
201 | return true; | 204 | return true; |
202 | } | 205 | } |
@@ -206,6 +209,9 @@ public abstract class AbstractTransportService implements TransportService { | @@ -206,6 +209,9 @@ public abstract class AbstractTransportService implements TransportService { | ||
206 | if (callback != null) { | 209 | if (callback != null) { |
207 | callback.onError(new TbRateLimitsException(EntityType.TENANT)); | 210 | callback.onError(new TbRateLimitsException(EntityType.TENANT)); |
208 | } | 211 | } |
212 | + if (log.isTraceEnabled()) { | ||
213 | + log.trace("[{}][{}] Tenant level rate limit detected: {}", toId(sessionInfo), tenantId, msg); | ||
214 | + } | ||
209 | return false; | 215 | return false; |
210 | } | 216 | } |
211 | DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); | 217 | DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); |
@@ -214,8 +220,12 @@ public abstract class AbstractTransportService implements TransportService { | @@ -214,8 +220,12 @@ public abstract class AbstractTransportService implements TransportService { | ||
214 | if (callback != null) { | 220 | if (callback != null) { |
215 | callback.onError(new TbRateLimitsException(EntityType.DEVICE)); | 221 | callback.onError(new TbRateLimitsException(EntityType.DEVICE)); |
216 | } | 222 | } |
223 | + if (log.isTraceEnabled()) { | ||
224 | + log.trace("[{}][{}] Device level rate limit detected: {}", toId(sessionInfo), deviceId, msg); | ||
225 | + } | ||
217 | return false; | 226 | return false; |
218 | } | 227 | } |
228 | + | ||
219 | return true; | 229 | return true; |
220 | } | 230 | } |
221 | 231 | ||
@@ -250,11 +260,11 @@ public abstract class AbstractTransportService implements TransportService { | @@ -250,11 +260,11 @@ public abstract class AbstractTransportService implements TransportService { | ||
250 | } | 260 | } |
251 | } | 261 | } |
252 | 262 | ||
253 | - private UUID toId(TransportProtos.SessionInfoProto sessionInfo) { | 263 | + protected UUID toId(TransportProtos.SessionInfoProto sessionInfo) { |
254 | return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); | 264 | return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); |
255 | } | 265 | } |
256 | 266 | ||
257 | - String getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { | 267 | + protected String getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { |
258 | return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()).toString(); | 268 | return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()).toString(); |
259 | } | 269 | } |
260 | 270 |
@@ -197,6 +197,7 @@ public class RemoteTransportService extends AbstractTransportService { | @@ -197,6 +197,7 @@ public class RemoteTransportService extends AbstractTransportService { | ||
197 | 197 | ||
198 | @Override | 198 | @Override |
199 | public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) { | 199 | public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) { |
200 | + log.trace("Processing msg: {}", msg); | ||
200 | AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getToken(), | 201 | AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getToken(), |
201 | TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()), | 202 | TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()), |
202 | response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); | 203 | response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); |
@@ -204,6 +205,7 @@ public class RemoteTransportService extends AbstractTransportService { | @@ -204,6 +205,7 @@ public class RemoteTransportService extends AbstractTransportService { | ||
204 | 205 | ||
205 | @Override | 206 | @Override |
206 | public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) { | 207 | public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) { |
208 | + log.trace("Processing msg: {}", msg); | ||
207 | AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getHash(), | 209 | AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getHash(), |
208 | TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()), | 210 | TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()), |
209 | response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); | 211 | response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); |
@@ -211,6 +213,7 @@ public class RemoteTransportService extends AbstractTransportService { | @@ -211,6 +213,7 @@ public class RemoteTransportService extends AbstractTransportService { | ||
211 | 213 | ||
212 | @Override | 214 | @Override |
213 | public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponseMsg> callback) { | 215 | public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponseMsg> callback) { |
216 | + log.trace("Processing msg: {}", msg); | ||
214 | AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getDeviceName(), | 217 | AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getDeviceName(), |
215 | TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()), | 218 | TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()), |
216 | response -> callback.onSuccess(response.getGetOrCreateDeviceResponseMsg()), callback::onError, transportCallbackExecutor); | 219 | response -> callback.onSuccess(response.getGetOrCreateDeviceResponseMsg()), callback::onError, transportCallbackExecutor); |
@@ -218,6 +221,9 @@ public class RemoteTransportService extends AbstractTransportService { | @@ -218,6 +221,9 @@ public class RemoteTransportService extends AbstractTransportService { | ||
218 | 221 | ||
219 | @Override | 222 | @Override |
220 | public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) { | 223 | public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) { |
224 | + if (log.isTraceEnabled()) { | ||
225 | + log.trace("[{}] Processing msg: {}", toId(sessionInfo), msg); | ||
226 | + } | ||
221 | ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( | 227 | ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( |
222 | TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) | 228 | TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) |
223 | .setSubscriptionInfo(msg).build() | 229 | .setSubscriptionInfo(msg).build() |
@@ -45,7 +45,6 @@ public abstract class DeviceAwareSessionContext implements SessionContext { | @@ -45,7 +45,6 @@ public abstract class DeviceAwareSessionContext implements SessionContext { | ||
45 | this.deviceId = new DeviceId(new UUID(deviceInfo.getDeviceIdMSB(), deviceInfo.getDeviceIdLSB())); | 45 | this.deviceId = new DeviceId(new UUID(deviceInfo.getDeviceIdMSB(), deviceInfo.getDeviceIdLSB())); |
46 | } | 46 | } |
47 | 47 | ||
48 | - | ||
49 | public boolean isConnected() { | 48 | public boolean isConnected() { |
50 | return deviceInfo != null; | 49 | return deviceInfo != null; |
51 | } | 50 | } |
@@ -43,6 +43,8 @@ public interface EntityViewService { | @@ -43,6 +43,8 @@ public interface EntityViewService { | ||
43 | 43 | ||
44 | EntityView findEntityViewById(EntityViewId entityViewId); | 44 | EntityView findEntityViewById(EntityViewId entityViewId); |
45 | 45 | ||
46 | + EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name); | ||
47 | + | ||
46 | TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); | 48 | TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); |
47 | 49 | ||
48 | TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type); | 50 | TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type); |
@@ -29,8 +29,6 @@ import org.springframework.cache.annotation.Cacheable; | @@ -29,8 +29,6 @@ import org.springframework.cache.annotation.Cacheable; | ||
29 | import org.springframework.cache.annotation.Caching; | 29 | import org.springframework.cache.annotation.Caching; |
30 | import org.springframework.stereotype.Service; | 30 | import org.springframework.stereotype.Service; |
31 | import org.thingsboard.server.common.data.Customer; | 31 | import org.thingsboard.server.common.data.Customer; |
32 | -import org.thingsboard.server.common.data.DataConstants; | ||
33 | -import org.thingsboard.server.common.data.Device; | ||
34 | import org.thingsboard.server.common.data.EntitySubtype; | 32 | import org.thingsboard.server.common.data.EntitySubtype; |
35 | import org.thingsboard.server.common.data.EntityType; | 33 | import org.thingsboard.server.common.data.EntityType; |
36 | import org.thingsboard.server.common.data.EntityView; | 34 | import org.thingsboard.server.common.data.EntityView; |
@@ -40,12 +38,10 @@ import org.thingsboard.server.common.data.id.CustomerId; | @@ -40,12 +38,10 @@ import org.thingsboard.server.common.data.id.CustomerId; | ||
40 | import org.thingsboard.server.common.data.id.EntityId; | 38 | import org.thingsboard.server.common.data.id.EntityId; |
41 | import org.thingsboard.server.common.data.id.EntityViewId; | 39 | import org.thingsboard.server.common.data.id.EntityViewId; |
42 | import org.thingsboard.server.common.data.id.TenantId; | 40 | import org.thingsboard.server.common.data.id.TenantId; |
43 | -import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
44 | import org.thingsboard.server.common.data.page.TextPageData; | 41 | import org.thingsboard.server.common.data.page.TextPageData; |
45 | import org.thingsboard.server.common.data.page.TextPageLink; | 42 | import org.thingsboard.server.common.data.page.TextPageLink; |
46 | import org.thingsboard.server.common.data.relation.EntityRelation; | 43 | import org.thingsboard.server.common.data.relation.EntityRelation; |
47 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; | 44 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
48 | -import org.thingsboard.server.dao.attributes.AttributesService; | ||
49 | import org.thingsboard.server.dao.customer.CustomerDao; | 45 | import org.thingsboard.server.dao.customer.CustomerDao; |
50 | import org.thingsboard.server.dao.entity.AbstractEntityService; | 46 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
51 | import org.thingsboard.server.dao.exception.DataValidationException; | 47 | import org.thingsboard.server.dao.exception.DataValidationException; |
@@ -56,15 +52,13 @@ import org.thingsboard.server.dao.tenant.TenantDao; | @@ -56,15 +52,13 @@ import org.thingsboard.server.dao.tenant.TenantDao; | ||
56 | import javax.annotation.Nullable; | 52 | import javax.annotation.Nullable; |
57 | import java.util.ArrayList; | 53 | import java.util.ArrayList; |
58 | import java.util.Arrays; | 54 | import java.util.Arrays; |
59 | -import java.util.Collection; | ||
60 | import java.util.Collections; | 55 | import java.util.Collections; |
61 | import java.util.Comparator; | 56 | import java.util.Comparator; |
62 | import java.util.List; | 57 | import java.util.List; |
63 | -import java.util.concurrent.ExecutionException; | 58 | +import java.util.Optional; |
64 | import java.util.stream.Collectors; | 59 | import java.util.stream.Collectors; |
65 | 60 | ||
66 | import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; | 61 | import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; |
67 | -import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE; | ||
68 | import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; | 62 | import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; |
69 | import static org.thingsboard.server.dao.service.Validator.validateId; | 63 | import static org.thingsboard.server.dao.service.Validator.validateId; |
70 | import static org.thingsboard.server.dao.service.Validator.validatePageLink; | 64 | import static org.thingsboard.server.dao.service.Validator.validatePageLink; |
@@ -96,6 +90,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti | @@ -96,6 +90,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti | ||
96 | 90 | ||
97 | @Caching(evict = { | 91 | @Caching(evict = { |
98 | @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"), | 92 | @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"), |
93 | + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.name}"), | ||
99 | @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")}) | 94 | @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")}) |
100 | @Override | 95 | @Override |
101 | public EntityView saveEntityView(EntityView entityView) { | 96 | public EntityView saveEntityView(EntityView entityView) { |
@@ -137,6 +132,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti | @@ -137,6 +132,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti | ||
137 | return entityViewDao.findById(entityViewId.getId()); | 132 | return entityViewDao.findById(entityViewId.getId()); |
138 | } | 133 | } |
139 | 134 | ||
135 | + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #name}") | ||
136 | + @Override | ||
137 | + public EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name) { | ||
138 | + log.trace("Executing findEntityViewByTenantIdAndName [{}][{}]", tenantId, name); | ||
139 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
140 | + Optional<EntityView> entityViewOpt = entityViewDao.findEntityViewByTenantIdAndName(tenantId.getId(), name); | ||
141 | + return entityViewOpt.orElse(null); | ||
142 | + } | ||
143 | + | ||
140 | @Override | 144 | @Override |
141 | public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) { | 145 | public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) { |
142 | log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); | 146 | log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); |
@@ -255,6 +259,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti | @@ -255,6 +259,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti | ||
255 | deleteEntityRelations(entityViewId); | 259 | deleteEntityRelations(entityViewId); |
256 | EntityView entityView = entityViewDao.findById(entityViewId.getId()); | 260 | EntityView entityView = entityViewDao.findById(entityViewId.getId()); |
257 | cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); | 261 | cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); |
262 | + cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getName())); | ||
258 | entityViewDao.removeById(entityViewId.getId()); | 263 | entityViewDao.removeById(entityViewId.getId()); |
259 | } | 264 | } |
260 | 265 |
@@ -20,14 +20,29 @@ import lombok.Data; | @@ -20,14 +20,29 @@ import lombok.Data; | ||
20 | import lombok.NoArgsConstructor; | 20 | import lombok.NoArgsConstructor; |
21 | import org.thingsboard.server.common.data.EntityType; | 21 | import org.thingsboard.server.common.data.EntityType; |
22 | 22 | ||
23 | +import javax.persistence.Column; | ||
24 | +import javax.persistence.Embeddable; | ||
25 | +import javax.persistence.EnumType; | ||
26 | +import javax.persistence.Enumerated; | ||
23 | import java.io.Serializable; | 27 | import java.io.Serializable; |
24 | 28 | ||
29 | +import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_KEY_COLUMN; | ||
30 | +import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_TYPE_COLUMN; | ||
31 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; | ||
32 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; | ||
33 | + | ||
25 | @Data | 34 | @Data |
26 | @AllArgsConstructor | 35 | @AllArgsConstructor |
27 | @NoArgsConstructor | 36 | @NoArgsConstructor |
37 | +@Embeddable | ||
28 | public class AttributeKvCompositeKey implements Serializable { | 38 | public class AttributeKvCompositeKey implements Serializable { |
39 | + @Enumerated(EnumType.STRING) | ||
40 | + @Column(name = ENTITY_TYPE_COLUMN) | ||
29 | private EntityType entityType; | 41 | private EntityType entityType; |
42 | + @Column(name = ENTITY_ID_COLUMN) | ||
30 | private String entityId; | 43 | private String entityId; |
44 | + @Column(name = ATTRIBUTE_TYPE_COLUMN) | ||
31 | private String attributeType; | 45 | private String attributeType; |
46 | + @Column(name = ATTRIBUTE_KEY_COLUMN) | ||
32 | private String attributeKey; | 47 | private String attributeKey; |
33 | } | 48 | } |
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; | @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; | ||
27 | import org.thingsboard.server.dao.model.ToData; | 27 | import org.thingsboard.server.dao.model.ToData; |
28 | 28 | ||
29 | import javax.persistence.Column; | 29 | import javax.persistence.Column; |
30 | +import javax.persistence.EmbeddedId; | ||
30 | import javax.persistence.Entity; | 31 | import javax.persistence.Entity; |
31 | import javax.persistence.EnumType; | 32 | import javax.persistence.EnumType; |
32 | import javax.persistence.Enumerated; | 33 | import javax.persistence.Enumerated; |
@@ -48,25 +49,10 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM | @@ -48,25 +49,10 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM | ||
48 | @Data | 49 | @Data |
49 | @Entity | 50 | @Entity |
50 | @Table(name = "attribute_kv") | 51 | @Table(name = "attribute_kv") |
51 | -@IdClass(AttributeKvCompositeKey.class) | ||
52 | public class AttributeKvEntity implements ToData<AttributeKvEntry>, Serializable { | 52 | public class AttributeKvEntity implements ToData<AttributeKvEntry>, Serializable { |
53 | 53 | ||
54 | - @Id | ||
55 | - @Enumerated(EnumType.STRING) | ||
56 | - @Column(name = ENTITY_TYPE_COLUMN) | ||
57 | - private EntityType entityType; | ||
58 | - | ||
59 | - @Id | ||
60 | - @Column(name = ENTITY_ID_COLUMN) | ||
61 | - private String entityId; | ||
62 | - | ||
63 | - @Id | ||
64 | - @Column(name = ATTRIBUTE_TYPE_COLUMN) | ||
65 | - private String attributeType; | ||
66 | - | ||
67 | - @Id | ||
68 | - @Column(name = ATTRIBUTE_KEY_COLUMN) | ||
69 | - private String attributeKey; | 54 | + @EmbeddedId |
55 | + private AttributeKvCompositeKey id; | ||
70 | 56 | ||
71 | @Column(name = BOOLEAN_VALUE_COLUMN) | 57 | @Column(name = BOOLEAN_VALUE_COLUMN) |
72 | private Boolean booleanValue; | 58 | private Boolean booleanValue; |
@@ -87,13 +73,13 @@ public class AttributeKvEntity implements ToData<AttributeKvEntry>, Serializable | @@ -87,13 +73,13 @@ public class AttributeKvEntity implements ToData<AttributeKvEntry>, Serializable | ||
87 | public AttributeKvEntry toData() { | 73 | public AttributeKvEntry toData() { |
88 | KvEntry kvEntry = null; | 74 | KvEntry kvEntry = null; |
89 | if (strValue != null) { | 75 | if (strValue != null) { |
90 | - kvEntry = new StringDataEntry(attributeKey, strValue); | 76 | + kvEntry = new StringDataEntry(id.getAttributeKey(), strValue); |
91 | } else if (booleanValue != null) { | 77 | } else if (booleanValue != null) { |
92 | - kvEntry = new BooleanDataEntry(attributeKey, booleanValue); | 78 | + kvEntry = new BooleanDataEntry(id.getAttributeKey(), booleanValue); |
93 | } else if (doubleValue != null) { | 79 | } else if (doubleValue != null) { |
94 | - kvEntry = new DoubleDataEntry(attributeKey, doubleValue); | 80 | + kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); |
95 | } else if (longValue != null) { | 81 | } else if (longValue != null) { |
96 | - kvEntry = new LongDataEntry(attributeKey, longValue); | 82 | + kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); |
97 | } | 83 | } |
98 | return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); | 84 | return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); |
99 | } | 85 | } |
@@ -15,7 +15,9 @@ | @@ -15,7 +15,9 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.dao.sql.attributes; | 16 | package org.thingsboard.server.dao.sql.attributes; |
17 | 17 | ||
18 | +import org.springframework.data.jpa.repository.Query; | ||
18 | import org.springframework.data.repository.CrudRepository; | 19 | import org.springframework.data.repository.CrudRepository; |
20 | +import org.springframework.data.repository.query.Param; | ||
19 | import org.thingsboard.server.common.data.EntityType; | 21 | import org.thingsboard.server.common.data.EntityType; |
20 | import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; | 22 | import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; |
21 | import org.thingsboard.server.dao.model.sql.AttributeKvEntity; | 23 | import org.thingsboard.server.dao.model.sql.AttributeKvEntity; |
@@ -26,8 +28,11 @@ import java.util.List; | @@ -26,8 +28,11 @@ import java.util.List; | ||
26 | @SqlDao | 28 | @SqlDao |
27 | public interface AttributeKvRepository extends CrudRepository<AttributeKvEntity, AttributeKvCompositeKey> { | 29 | public interface AttributeKvRepository extends CrudRepository<AttributeKvEntity, AttributeKvCompositeKey> { |
28 | 30 | ||
29 | - List<AttributeKvEntity> findAllByEntityTypeAndEntityIdAndAttributeType(EntityType entityType, | ||
30 | - String entityId, | ||
31 | - String attributeType); | 31 | + @Query("SELECT a FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + |
32 | + "AND a.id.entityId = :entityId " + | ||
33 | + "AND a.id.attributeType = :attributeType") | ||
34 | + List<AttributeKvEntity> findAllByEntityTypeAndEntityIdAndAttributeType(@Param("entityType") EntityType entityType, | ||
35 | + @Param("entityId") String entityId, | ||
36 | + @Param("attributeType") String attributeType); | ||
32 | } | 37 | } |
33 | 38 |
@@ -79,10 +79,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | @@ -79,10 +79,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | ||
79 | @Override | 79 | @Override |
80 | public ListenableFuture<Void> save(EntityId entityId, String attributeType, AttributeKvEntry attribute) { | 80 | public ListenableFuture<Void> save(EntityId entityId, String attributeType, AttributeKvEntry attribute) { |
81 | AttributeKvEntity entity = new AttributeKvEntity(); | 81 | AttributeKvEntity entity = new AttributeKvEntity(); |
82 | - entity.setEntityType(entityId.getEntityType()); | ||
83 | - entity.setEntityId(fromTimeUUID(entityId.getId())); | ||
84 | - entity.setAttributeType(attributeType); | ||
85 | - entity.setAttributeKey(attribute.getKey()); | 82 | + entity.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, attribute.getKey())); |
86 | entity.setLastUpdateTs(attribute.getLastUpdateTs()); | 83 | entity.setLastUpdateTs(attribute.getLastUpdateTs()); |
87 | entity.setStrValue(attribute.getStrValue().orElse(null)); | 84 | entity.setStrValue(attribute.getStrValue().orElse(null)); |
88 | entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); | 85 | entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); |
@@ -100,10 +97,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | @@ -100,10 +97,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | ||
100 | .stream() | 97 | .stream() |
101 | .map(key -> { | 98 | .map(key -> { |
102 | AttributeKvEntity entityToDelete = new AttributeKvEntity(); | 99 | AttributeKvEntity entityToDelete = new AttributeKvEntity(); |
103 | - entityToDelete.setEntityType(entityId.getEntityType()); | ||
104 | - entityToDelete.setEntityId(fromTimeUUID(entityId.getId())); | ||
105 | - entityToDelete.setAttributeType(attributeType); | ||
106 | - entityToDelete.setAttributeKey(key); | 100 | + entityToDelete.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, key)); |
107 | return entityToDelete; | 101 | return entityToDelete; |
108 | }).collect(Collectors.toList()); | 102 | }).collect(Collectors.toList()); |
109 | 103 |
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries; | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries; | ||
17 | 17 | ||
18 | import com.google.common.base.Function; | 18 | import com.google.common.base.Function; |
19 | import com.google.common.collect.Lists; | 19 | import com.google.common.collect.Lists; |
20 | +import com.google.common.util.concurrent.FutureCallback; | ||
20 | import com.google.common.util.concurrent.Futures; | 21 | import com.google.common.util.concurrent.Futures; |
21 | import com.google.common.util.concurrent.ListenableFuture; | 22 | import com.google.common.util.concurrent.ListenableFuture; |
22 | import com.google.common.util.concurrent.ListeningExecutorService; | 23 | import com.google.common.util.concurrent.ListeningExecutorService; |
@@ -31,6 +32,7 @@ import org.springframework.stereotype.Component; | @@ -31,6 +32,7 @@ import org.springframework.stereotype.Component; | ||
31 | import org.thingsboard.server.common.data.UUIDConverter; | 32 | import org.thingsboard.server.common.data.UUIDConverter; |
32 | import org.thingsboard.server.common.data.id.EntityId; | 33 | import org.thingsboard.server.common.data.id.EntityId; |
33 | import org.thingsboard.server.common.data.kv.Aggregation; | 34 | import org.thingsboard.server.common.data.kv.Aggregation; |
35 | +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; | ||
34 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; | 36 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
35 | import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; | 37 | import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; |
36 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | 38 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
@@ -41,9 +43,9 @@ import org.thingsboard.server.dao.model.sql.TsKvEntity; | @@ -41,9 +43,9 @@ import org.thingsboard.server.dao.model.sql.TsKvEntity; | ||
41 | import org.thingsboard.server.dao.model.sql.TsKvLatestCompositeKey; | 43 | import org.thingsboard.server.dao.model.sql.TsKvLatestCompositeKey; |
42 | import org.thingsboard.server.dao.model.sql.TsKvLatestEntity; | 44 | import org.thingsboard.server.dao.model.sql.TsKvLatestEntity; |
43 | import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; | 45 | import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; |
46 | +import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; | ||
44 | import org.thingsboard.server.dao.timeseries.TimeseriesDao; | 47 | import org.thingsboard.server.dao.timeseries.TimeseriesDao; |
45 | import org.thingsboard.server.dao.timeseries.TsInsertExecutorType; | 48 | import org.thingsboard.server.dao.timeseries.TsInsertExecutorType; |
46 | -import org.thingsboard.server.dao.util.SqlDao; | ||
47 | import org.thingsboard.server.dao.util.SqlTsDao; | 49 | import org.thingsboard.server.dao.util.SqlTsDao; |
48 | 50 | ||
49 | import javax.annotation.Nullable; | 51 | import javax.annotation.Nullable; |
@@ -53,6 +55,7 @@ import java.util.ArrayList; | @@ -53,6 +55,7 @@ import java.util.ArrayList; | ||
53 | import java.util.List; | 55 | import java.util.List; |
54 | import java.util.Optional; | 56 | import java.util.Optional; |
55 | import java.util.concurrent.CompletableFuture; | 57 | import java.util.concurrent.CompletableFuture; |
58 | +import java.util.concurrent.ExecutionException; | ||
56 | import java.util.concurrent.Executors; | 59 | import java.util.concurrent.Executors; |
57 | import java.util.stream.Collectors; | 60 | import java.util.stream.Collectors; |
58 | 61 | ||
@@ -64,6 +67,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | @@ -64,6 +67,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | ||
64 | @SqlTsDao | 67 | @SqlTsDao |
65 | public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { | 68 | public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { |
66 | 69 | ||
70 | + private static final String DESC_ORDER = "DESC"; | ||
71 | + | ||
67 | @Value("${sql.ts_inserts_executor_type}") | 72 | @Value("${sql.ts_inserts_executor_type}") |
68 | private String insertExecutorType; | 73 | private String insertExecutorType; |
69 | 74 | ||
@@ -326,14 +331,72 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | @@ -326,14 +331,72 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | ||
326 | 331 | ||
327 | @Override | 332 | @Override |
328 | public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) { | 333 | public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) { |
329 | - TsKvLatestEntity latestEntity = new TsKvLatestEntity(); | ||
330 | - latestEntity.setEntityType(entityId.getEntityType()); | ||
331 | - latestEntity.setEntityId(fromTimeUUID(entityId.getId())); | ||
332 | - latestEntity.setKey(query.getKey()); | ||
333 | - return service.submit(() -> { | ||
334 | - tsKvLatestRepository.delete(latestEntity); | ||
335 | - return null; | 334 | + ListenableFuture<TsKvEntry> latestFuture = findLatest(entityId, query.getKey()); |
335 | + | ||
336 | + ListenableFuture<Boolean> booleanFuture = Futures.transform(latestFuture, tsKvEntry -> { | ||
337 | + long ts = tsKvEntry.getTs(); | ||
338 | + return ts > query.getStartTs() && ts <= query.getEndTs(); | ||
339 | + }, service); | ||
340 | + | ||
341 | + ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { | ||
342 | + if (isRemove) { | ||
343 | + TsKvLatestEntity latestEntity = new TsKvLatestEntity(); | ||
344 | + latestEntity.setEntityType(entityId.getEntityType()); | ||
345 | + latestEntity.setEntityId(fromTimeUUID(entityId.getId())); | ||
346 | + latestEntity.setKey(query.getKey()); | ||
347 | + return service.submit(() -> { | ||
348 | + tsKvLatestRepository.delete(latestEntity); | ||
349 | + return null; | ||
350 | + }); | ||
351 | + } | ||
352 | + return Futures.immediateFuture(null); | ||
353 | + }, service); | ||
354 | + | ||
355 | + final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>(); | ||
356 | + Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() { | ||
357 | + @Override | ||
358 | + public void onSuccess(@Nullable Void result) { | ||
359 | + if (query.getRewriteLatestIfDeleted()) { | ||
360 | + ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { | ||
361 | + if (isRemove) { | ||
362 | + return getNewLatestEntryFuture(entityId, query); | ||
363 | + } | ||
364 | + return Futures.immediateFuture(null); | ||
365 | + }, service); | ||
366 | + | ||
367 | + try { | ||
368 | + resultFuture.set(savedLatestFuture.get()); | ||
369 | + } catch (InterruptedException | ExecutionException e) { | ||
370 | + log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e); | ||
371 | + } | ||
372 | + } else { | ||
373 | + resultFuture.set(null); | ||
374 | + } | ||
375 | + } | ||
376 | + | ||
377 | + @Override | ||
378 | + public void onFailure(Throwable t) { | ||
379 | + log.warn("[{}] Failed to process remove of the latest value", entityId, t); | ||
380 | + } | ||
336 | }); | 381 | }); |
382 | + return resultFuture; | ||
383 | + } | ||
384 | + | ||
385 | + private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) { | ||
386 | + long startTs = 0; | ||
387 | + long endTs = query.getStartTs() - 1; | ||
388 | + ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1, | ||
389 | + Aggregation.NONE, DESC_ORDER); | ||
390 | + ListenableFuture<List<TsKvEntry>> future = findAllAsync(entityId, findNewLatestQuery); | ||
391 | + | ||
392 | + return Futures.transformAsync(future, entryList -> { | ||
393 | + if (entryList.size() == 1) { | ||
394 | + return saveLatest(entityId, entryList.get(0)); | ||
395 | + } else { | ||
396 | + log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey()); | ||
397 | + } | ||
398 | + return Futures.immediateFuture(null); | ||
399 | + }, service); | ||
337 | } | 400 | } |
338 | 401 | ||
339 | @Override | 402 | @Override |
@@ -47,7 +47,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -47,7 +47,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
47 | @Modifying | 47 | @Modifying |
48 | @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + | 48 | @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + |
49 | "AND tskv.entityType = :entityType AND tskv.key = :entityKey " + | 49 | "AND tskv.entityType = :entityType AND tskv.key = :entityKey " + |
50 | - "AND tskv.ts > :startTs AND tskv.ts < :endTs") | 50 | + "AND tskv.ts > :startTs AND tskv.ts <= :endTs") |
51 | void delete(@Param("entityId") String entityId, | 51 | void delete(@Param("entityId") String entityId, |
52 | @Param("entityType") EntityType entityType, | 52 | @Param("entityType") EntityType entityType, |
53 | @Param("entityKey") String key, | 53 | @Param("entityKey") String key, |
@@ -20,11 +20,13 @@ import com.google.common.util.concurrent.Futures; | @@ -20,11 +20,13 @@ import com.google.common.util.concurrent.Futures; | ||
20 | import com.google.common.util.concurrent.ListenableFuture; | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
22 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | +import org.springframework.beans.factory.annotation.Value; | ||
23 | import org.springframework.stereotype.Service; | 24 | import org.springframework.stereotype.Service; |
24 | import org.thingsboard.server.common.data.EntityType; | 25 | import org.thingsboard.server.common.data.EntityType; |
25 | import org.thingsboard.server.common.data.EntityView; | 26 | import org.thingsboard.server.common.data.EntityView; |
26 | import org.thingsboard.server.common.data.id.EntityId; | 27 | import org.thingsboard.server.common.data.id.EntityId; |
27 | import org.thingsboard.server.common.data.id.EntityViewId; | 28 | import org.thingsboard.server.common.data.id.EntityViewId; |
29 | +import org.thingsboard.server.common.data.kv.Aggregation; | ||
28 | import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; | 30 | import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
29 | import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; | 31 | import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; |
30 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | 32 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
@@ -47,8 +49,11 @@ import static org.apache.commons.lang3.StringUtils.isBlank; | @@ -47,8 +49,11 @@ import static org.apache.commons.lang3.StringUtils.isBlank; | ||
47 | @Slf4j | 49 | @Slf4j |
48 | public class BaseTimeseriesService implements TimeseriesService { | 50 | public class BaseTimeseriesService implements TimeseriesService { |
49 | 51 | ||
50 | - public static final int INSERTS_PER_ENTRY = 3; | ||
51 | - public static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; | 52 | + private static final int INSERTS_PER_ENTRY = 3; |
53 | + private static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; | ||
54 | + | ||
55 | + @Value("${database.ts_max_intervals}") | ||
56 | + private long maxTsIntervals; | ||
52 | 57 | ||
53 | @Autowired | 58 | @Autowired |
54 | private TimeseriesDao timeseriesDao; | 59 | private TimeseriesDao timeseriesDao; |
@@ -59,7 +64,7 @@ public class BaseTimeseriesService implements TimeseriesService { | @@ -59,7 +64,7 @@ public class BaseTimeseriesService implements TimeseriesService { | ||
59 | @Override | 64 | @Override |
60 | public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) { | 65 | public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) { |
61 | validate(entityId); | 66 | validate(entityId); |
62 | - queries.forEach(BaseTimeseriesService::validate); | 67 | + queries.forEach(this::validate); |
63 | if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { | 68 | if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { |
64 | EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); | 69 | EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); |
65 | List<ReadTsKvQuery> filteredQueries = | 70 | List<ReadTsKvQuery> filteredQueries = |
@@ -189,7 +194,7 @@ public class BaseTimeseriesService implements TimeseriesService { | @@ -189,7 +194,7 @@ public class BaseTimeseriesService implements TimeseriesService { | ||
189 | Validator.validateEntityId(entityId, "Incorrect entityId " + entityId); | 194 | Validator.validateEntityId(entityId, "Incorrect entityId " + entityId); |
190 | } | 195 | } |
191 | 196 | ||
192 | - private static void validate(ReadTsKvQuery query) { | 197 | + private void validate(ReadTsKvQuery query) { |
193 | if (query == null) { | 198 | if (query == null) { |
194 | throw new IncorrectParameterException("ReadTsKvQuery can't be null"); | 199 | throw new IncorrectParameterException("ReadTsKvQuery can't be null"); |
195 | } else if (isBlank(query.getKey())) { | 200 | } else if (isBlank(query.getKey())) { |
@@ -197,6 +202,14 @@ public class BaseTimeseriesService implements TimeseriesService { | @@ -197,6 +202,14 @@ public class BaseTimeseriesService implements TimeseriesService { | ||
197 | } else if (query.getAggregation() == null) { | 202 | } else if (query.getAggregation() == null) { |
198 | throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty"); | 203 | throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty"); |
199 | } | 204 | } |
205 | + if(!Aggregation.NONE.equals(query.getAggregation())) { | ||
206 | + long step = Math.max(query.getInterval(), 1000); | ||
207 | + long intervalCounts = (query.getEndTs() - query.getStartTs()) / step; | ||
208 | + if (intervalCounts > maxTsIntervals || intervalCounts < 0) { | ||
209 | + throw new IncorrectParameterException("Incorrect TsKvQuery. Number of intervals is to high - " + intervalCounts + ". " + | ||
210 | + "Please increase 'interval' parameter for your query or reduce the time range of the query."); | ||
211 | + } | ||
212 | + } | ||
200 | } | 213 | } |
201 | 214 | ||
202 | private static void validate(DeleteTsKvQuery query) { | 215 | private static void validate(DeleteTsKvQuery query) { |
@@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; | @@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; | ||
48 | import org.thingsboard.server.common.data.kv.TsKvEntry; | 48 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
49 | import org.thingsboard.server.dao.model.ModelConstants; | 49 | import org.thingsboard.server.dao.model.ModelConstants; |
50 | import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao; | 50 | import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao; |
51 | -import org.thingsboard.server.dao.util.NoSqlDao; | ||
52 | import org.thingsboard.server.dao.util.NoSqlTsDao; | 51 | import org.thingsboard.server.dao.util.NoSqlTsDao; |
53 | 52 | ||
54 | import javax.annotation.Nullable; | 53 | import javax.annotation.Nullable; |
@@ -62,6 +61,7 @@ import java.util.Arrays; | @@ -62,6 +61,7 @@ import java.util.Arrays; | ||
62 | import java.util.Collections; | 61 | import java.util.Collections; |
63 | import java.util.List; | 62 | import java.util.List; |
64 | import java.util.Optional; | 63 | import java.util.Optional; |
64 | +import java.util.concurrent.ExecutionException; | ||
65 | import java.util.stream.Collectors; | 65 | import java.util.stream.Collectors; |
66 | 66 | ||
67 | import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | 67 | import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; |
@@ -434,14 +434,14 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem | @@ -434,14 +434,14 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem | ||
434 | public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) { | 434 | public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) { |
435 | ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey()); | 435 | ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey()); |
436 | 436 | ||
437 | - ListenableFuture<Boolean> booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> { | 437 | + ListenableFuture<Boolean> booleanFuture = Futures.transform(latestEntryFuture, latestEntry -> { |
438 | long ts = latestEntry.getTs(); | 438 | long ts = latestEntry.getTs(); |
439 | - if (ts >= query.getStartTs() && ts <= query.getEndTs()) { | ||
440 | - return Futures.immediateFuture(true); | 439 | + if (ts > query.getStartTs() && ts <= query.getEndTs()) { |
440 | + return true; | ||
441 | } else { | 441 | } else { |
442 | log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey()); | 442 | log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey()); |
443 | } | 443 | } |
444 | - return Futures.immediateFuture(false); | 444 | + return false; |
445 | }, readResultsProcessingExecutor); | 445 | }, readResultsProcessingExecutor); |
446 | 446 | ||
447 | ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { | 447 | ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { |
@@ -451,18 +451,34 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem | @@ -451,18 +451,34 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem | ||
451 | return Futures.immediateFuture(null); | 451 | return Futures.immediateFuture(null); |
452 | }, readResultsProcessingExecutor); | 452 | }, readResultsProcessingExecutor); |
453 | 453 | ||
454 | - if (query.getRewriteLatestIfDeleted()) { | ||
455 | - ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { | ||
456 | - if (isRemove) { | ||
457 | - return getNewLatestEntryFuture(entityId, query); | 454 | + final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>(); |
455 | + Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() { | ||
456 | + @Override | ||
457 | + public void onSuccess(@Nullable Void result) { | ||
458 | + if (query.getRewriteLatestIfDeleted()) { | ||
459 | + ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { | ||
460 | + if (isRemove) { | ||
461 | + return getNewLatestEntryFuture(entityId, query); | ||
462 | + } | ||
463 | + return Futures.immediateFuture(null); | ||
464 | + }, readResultsProcessingExecutor); | ||
465 | + | ||
466 | + try { | ||
467 | + resultFuture.set(savedLatestFuture.get()); | ||
468 | + } catch (InterruptedException | ExecutionException e) { | ||
469 | + log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e); | ||
470 | + } | ||
471 | + } else { | ||
472 | + resultFuture.set(null); | ||
458 | } | 473 | } |
459 | - return Futures.immediateFuture(null); | ||
460 | - }, readResultsProcessingExecutor); | 474 | + } |
461 | 475 | ||
462 | - return Futures.transformAsync(Futures.allAsList(Arrays.asList(savedLatestFuture, removedLatestFuture)), | ||
463 | - list -> Futures.immediateFuture(null), readResultsProcessingExecutor); | ||
464 | - } | ||
465 | - return removedLatestFuture; | 476 | + @Override |
477 | + public void onFailure(Throwable t) { | ||
478 | + log.warn("[{}] Failed to process remove of the latest value", entityId, t); | ||
479 | + } | ||
480 | + }); | ||
481 | + return resultFuture; | ||
466 | } | 482 | } |
467 | 483 | ||
468 | private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) { | 484 | private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) { |
@@ -152,7 +152,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { | @@ -152,7 +152,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { | ||
152 | } | 152 | } |
153 | 153 | ||
154 | @Test | 154 | @Test |
155 | - public void testDeleteDeviceTsData() throws Exception { | 155 | + public void testDeleteDeviceTsDataWithoutOverwritingLatest() throws Exception { |
156 | DeviceId deviceId = new DeviceId(UUIDs.timeBased()); | 156 | DeviceId deviceId = new DeviceId(UUIDs.timeBased()); |
157 | 157 | ||
158 | saveEntries(deviceId, 10000); | 158 | saveEntries(deviceId, 10000); |
@@ -172,6 +172,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { | @@ -172,6 +172,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { | ||
172 | } | 172 | } |
173 | 173 | ||
174 | @Test | 174 | @Test |
175 | + public void testDeleteDeviceTsDataWithOverwritingLatest() throws Exception { | ||
176 | + DeviceId deviceId = new DeviceId(UUIDs.timeBased()); | ||
177 | + | ||
178 | + saveEntries(deviceId, 10000); | ||
179 | + saveEntries(deviceId, 20000); | ||
180 | + saveEntries(deviceId, 30000); | ||
181 | + saveEntries(deviceId, 40000); | ||
182 | + | ||
183 | + tsService.remove(deviceId, Collections.singletonList( | ||
184 | + new BaseDeleteTsKvQuery(STRING_KEY, 25000, 45000, true))).get(); | ||
185 | + | ||
186 | + List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList( | ||
187 | + new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get(); | ||
188 | + Assert.assertEquals(2, list.size()); | ||
189 | + | ||
190 | + List<TsKvEntry> latest = tsService.findLatest(deviceId, Collections.singletonList(STRING_KEY)).get(); | ||
191 | + Assert.assertEquals(20000, latest.get(0).getTs()); | ||
192 | + } | ||
193 | + | ||
194 | + @Test | ||
175 | public void testFindDeviceTsData() throws Exception { | 195 | public void testFindDeviceTsData() throws Exception { |
176 | DeviceId deviceId = new DeviceId(UUIDs.timeBased()); | 196 | DeviceId deviceId = new DeviceId(UUIDs.timeBased()); |
177 | List<TsKvEntry> entries = new ArrayList<>(); | 197 | List<TsKvEntry> entries = new ArrayList<>(); |
@@ -15,4 +15,4 @@ TB_VERSION=latest | @@ -15,4 +15,4 @@ TB_VERSION=latest | ||
15 | 15 | ||
16 | DATABASE=postgres | 16 | DATABASE=postgres |
17 | 17 | ||
18 | -KAFKA_TOPICS="js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1" | 18 | +LOAD_BALANCER_NAME=haproxy-certbot |
docker/check-dirs.sh
deleted
100755 → 0
1 | -#!/bin/bash | ||
2 | -# | ||
3 | -# Copyright © 2016-2018 The Thingsboard Authors | ||
4 | -# | ||
5 | -# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | -# you may not use this file except in compliance with the License. | ||
7 | -# You may obtain a copy of the License at | ||
8 | -# | ||
9 | -# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | -# | ||
11 | -# Unless required by applicable law or agreed to in writing, software | ||
12 | -# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | -# See the License for the specific language governing permissions and | ||
15 | -# limitations under the License. | ||
16 | -# | ||
17 | - | ||
18 | -dirsArray=("./haproxy/certs.d" "./haproxy/letsencrypt" "./tb-node/postgres" "./tb-node/cassandra" "./tb-node/log/tb1" "./tb-node/log/tb2") | ||
19 | - | ||
20 | -for dir in ${dirsArray[@]} | ||
21 | -do | ||
22 | - if [ ! -d "$dir" ]; then | ||
23 | - echo creating dir $dir | ||
24 | - mkdir -p $dir | ||
25 | - fi | ||
26 | -done |
docker/docker-compose.postgres.volumes.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2018 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 | + | ||
17 | +version: '2.2' | ||
18 | + | ||
19 | +services: | ||
20 | + postgres: | ||
21 | + volumes: | ||
22 | + - postgres-db-volume:/var/lib/postgresql/data | ||
23 | + tb1: | ||
24 | + volumes: | ||
25 | + - tb-log-volume:/var/log/thingsboard | ||
26 | + tb2: | ||
27 | + volumes: | ||
28 | + - tb-log-volume:/var/log/thingsboard | ||
29 | + tb-coap-transport: | ||
30 | + volumes: | ||
31 | + - tb-coap-transport-log-volume:/var/log/tb-coap-transport | ||
32 | + tb-http-transport1: | ||
33 | + volumes: | ||
34 | + - tb-http-transport-log-volume:/var/log/tb-http-transport | ||
35 | + tb-http-transport2: | ||
36 | + volumes: | ||
37 | + - tb-http-transport-log-volume:/var/log/tb-http-transport | ||
38 | + tb-mqtt-transport1: | ||
39 | + volumes: | ||
40 | + - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport | ||
41 | + tb-mqtt-transport2: | ||
42 | + volumes: | ||
43 | + - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport | ||
44 | + | ||
45 | +volumes: | ||
46 | + postgres-db-volume: | ||
47 | + external: true | ||
48 | + name: ${POSTGRES_DATA_VOLUME} | ||
49 | + tb-log-volume: | ||
50 | + external: true | ||
51 | + name: ${TB_LOG_VOLUME} | ||
52 | + tb-coap-transport-log-volume: | ||
53 | + external: true | ||
54 | + name: ${TB_COAP_TRANSPORT_LOG_VOLUME} | ||
55 | + tb-http-transport-log-volume: | ||
56 | + external: true | ||
57 | + name: ${TB_HTTP_TRANSPORT_LOG_VOLUME} | ||
58 | + tb-mqtt-transport-log-volume: | ||
59 | + external: true | ||
60 | + name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} |
@@ -64,6 +64,7 @@ services: | @@ -64,6 +64,7 @@ services: | ||
64 | depends_on: | 64 | depends_on: |
65 | - kafka | 65 | - kafka |
66 | - redis | 66 | - redis |
67 | + - tb-js-executor | ||
67 | tb2: | 68 | tb2: |
68 | restart: always | 69 | restart: always |
69 | image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}" | 70 | image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}" |
@@ -84,13 +85,19 @@ services: | @@ -84,13 +85,19 @@ services: | ||
84 | depends_on: | 85 | depends_on: |
85 | - kafka | 86 | - kafka |
86 | - redis | 87 | - redis |
88 | + - tb-js-executor | ||
87 | tb-mqtt-transport1: | 89 | tb-mqtt-transport1: |
88 | restart: always | 90 | restart: always |
89 | image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" | 91 | image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" |
90 | ports: | 92 | ports: |
91 | - "1883" | 93 | - "1883" |
94 | + environment: | ||
95 | + TB_HOST: tb-mqtt-transport1 | ||
92 | env_file: | 96 | env_file: |
93 | - tb-mqtt-transport.env | 97 | - tb-mqtt-transport.env |
98 | + volumes: | ||
99 | + - ./tb-transports/mqtt/conf:/config | ||
100 | + - ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport | ||
94 | depends_on: | 101 | depends_on: |
95 | - kafka | 102 | - kafka |
96 | tb-mqtt-transport2: | 103 | tb-mqtt-transport2: |
@@ -98,8 +105,13 @@ services: | @@ -98,8 +105,13 @@ services: | ||
98 | image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" | 105 | image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" |
99 | ports: | 106 | ports: |
100 | - "1883" | 107 | - "1883" |
108 | + environment: | ||
109 | + TB_HOST: tb-mqtt-transport2 | ||
101 | env_file: | 110 | env_file: |
102 | - tb-mqtt-transport.env | 111 | - tb-mqtt-transport.env |
112 | + volumes: | ||
113 | + - ./tb-transports/mqtt/conf:/config | ||
114 | + - ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport | ||
103 | depends_on: | 115 | depends_on: |
104 | - kafka | 116 | - kafka |
105 | tb-http-transport1: | 117 | tb-http-transport1: |
@@ -107,8 +119,13 @@ services: | @@ -107,8 +119,13 @@ services: | ||
107 | image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" | 119 | image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" |
108 | ports: | 120 | ports: |
109 | - "8081" | 121 | - "8081" |
122 | + environment: | ||
123 | + TB_HOST: tb-http-transport1 | ||
110 | env_file: | 124 | env_file: |
111 | - tb-http-transport.env | 125 | - tb-http-transport.env |
126 | + volumes: | ||
127 | + - ./tb-transports/http/conf:/config | ||
128 | + - ./tb-transports/http/log:/var/log/tb-http-transport | ||
112 | depends_on: | 129 | depends_on: |
113 | - kafka | 130 | - kafka |
114 | tb-http-transport2: | 131 | tb-http-transport2: |
@@ -116,8 +133,13 @@ services: | @@ -116,8 +133,13 @@ services: | ||
116 | image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" | 133 | image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" |
117 | ports: | 134 | ports: |
118 | - "8081" | 135 | - "8081" |
136 | + environment: | ||
137 | + TB_HOST: tb-http-transport2 | ||
119 | env_file: | 138 | env_file: |
120 | - tb-http-transport.env | 139 | - tb-http-transport.env |
140 | + volumes: | ||
141 | + - ./tb-transports/http/conf:/config | ||
142 | + - ./tb-transports/http/log:/var/log/tb-http-transport | ||
121 | depends_on: | 143 | depends_on: |
122 | - kafka | 144 | - kafka |
123 | tb-coap-transport: | 145 | tb-coap-transport: |
@@ -125,8 +147,13 @@ services: | @@ -125,8 +147,13 @@ services: | ||
125 | image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" | 147 | image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" |
126 | ports: | 148 | ports: |
127 | - "5683:5683/udp" | 149 | - "5683:5683/udp" |
150 | + environment: | ||
151 | + TB_HOST: tb-coap-transport | ||
128 | env_file: | 152 | env_file: |
129 | - tb-coap-transport.env | 153 | - tb-coap-transport.env |
154 | + volumes: | ||
155 | + - ./tb-transports/coap/conf:/config | ||
156 | + - ./tb-transports/coap/log:/var/log/tb-coap-transport | ||
130 | depends_on: | 157 | depends_on: |
131 | - kafka | 158 | - kafka |
132 | tb-web-ui1: | 159 | tb-web-ui1: |
@@ -145,7 +172,7 @@ services: | @@ -145,7 +172,7 @@ services: | ||
145 | - tb-web-ui.env | 172 | - tb-web-ui.env |
146 | haproxy: | 173 | haproxy: |
147 | restart: always | 174 | restart: always |
148 | - container_name: haproxy-certbot | 175 | + container_name: "${LOAD_BALANCER_NAME}" |
149 | image: xalauc/haproxy-certbot:1.7.9 | 176 | image: xalauc/haproxy-certbot:1.7.9 |
150 | volumes: | 177 | volumes: |
151 | - ./haproxy/config:/config | 178 | - ./haproxy/config:/config |
@@ -153,7 +180,6 @@ services: | @@ -153,7 +180,6 @@ services: | ||
153 | - ./haproxy/certs.d:/usr/local/etc/haproxy/certs.d | 180 | - ./haproxy/certs.d:/usr/local/etc/haproxy/certs.d |
154 | ports: | 181 | ports: |
155 | - "80:80" | 182 | - "80:80" |
156 | - - "8080" | ||
157 | - "443:443" | 183 | - "443:443" |
158 | - "1883:1883" | 184 | - "1883:1883" |
159 | - "9999:9999" | 185 | - "9999:9999" |
@@ -163,7 +189,6 @@ services: | @@ -163,7 +189,6 @@ services: | ||
163 | HTTP_PORT: 80 | 189 | HTTP_PORT: 80 |
164 | HTTPS_PORT: 443 | 190 | HTTPS_PORT: 443 |
165 | MQTT_PORT: 1883 | 191 | MQTT_PORT: 1883 |
166 | - TB_API_PORT: 8080 | ||
167 | FORCE_HTTPS_REDIRECT: "false" | 192 | FORCE_HTTPS_REDIRECT: "false" |
168 | links: | 193 | links: |
169 | - tb1 | 194 | - tb1 |
@@ -56,13 +56,20 @@ frontend http-in | @@ -56,13 +56,20 @@ frontend http-in | ||
56 | 56 | ||
57 | reqadd X-Forwarded-Proto:\ http | 57 | reqadd X-Forwarded-Proto:\ http |
58 | 58 | ||
59 | + acl acl_static path_beg /static/ /index.html | ||
60 | + acl acl_static path / | ||
61 | + acl acl_static_rulenode path_beg /static/rulenode/ | ||
62 | + | ||
59 | acl transport_http_acl path_beg /api/v1/ | 63 | acl transport_http_acl path_beg /api/v1/ |
60 | acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ | 64 | acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ |
65 | + | ||
61 | redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true } | 66 | redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true } |
67 | + | ||
62 | use_backend letsencrypt_http if letsencrypt_http_acl | 68 | use_backend letsencrypt_http if letsencrypt_http_acl |
63 | use_backend tb-http-backend if transport_http_acl | 69 | use_backend tb-http-backend if transport_http_acl |
70 | + use_backend tb-web-backend if acl_static !acl_static_rulenode | ||
64 | 71 | ||
65 | - default_backend tb-web-backend | 72 | + default_backend tb-api-backend |
66 | 73 | ||
67 | frontend https_in | 74 | frontend https_in |
68 | bind *:${HTTPS_PORT} ssl crt /usr/local/etc/haproxy/default.pem crt /usr/local/etc/haproxy/certs.d ciphers ECDHE-RSA-AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM | 75 | bind *:${HTTPS_PORT} ssl crt /usr/local/etc/haproxy/default.pem crt /usr/local/etc/haproxy/certs.d ciphers ECDHE-RSA-AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM |
@@ -72,14 +79,15 @@ frontend https_in | @@ -72,14 +79,15 @@ frontend https_in | ||
72 | reqadd X-Forwarded-Proto:\ https | 79 | reqadd X-Forwarded-Proto:\ https |
73 | 80 | ||
74 | acl transport_http_acl path_beg /api/v1/ | 81 | acl transport_http_acl path_beg /api/v1/ |
75 | - use_backend tb-http-backend if transport_http_acl | ||
76 | 82 | ||
77 | - default_backend tb-web-backend | 83 | + acl acl_static path_beg /static/ /index.html |
84 | + acl acl_static path / | ||
85 | + acl acl_static_rulenode path_beg /static/rulenode/ | ||
78 | 86 | ||
79 | -frontend http-api-in | ||
80 | - bind *:${TB_API_PORT} | 87 | + use_backend tb-http-backend if transport_http_acl |
88 | + use_backend tb-web-backend if acl_static !acl_static_rulenode | ||
81 | 89 | ||
82 | - default_backend tb-api-backend | 90 | + default_backend tb-api-backend |
83 | 91 | ||
84 | backend letsencrypt_http | 92 | backend letsencrypt_http |
85 | server letsencrypt_http_srv 127.0.0.1:8080 | 93 | server letsencrypt_http_srv 127.0.0.1:8080 |
@@ -4,7 +4,7 @@ KAFKA_LISTENERS=INSIDE://:9093,OUTSIDE://:9092 | @@ -4,7 +4,7 @@ KAFKA_LISTENERS=INSIDE://:9093,OUTSIDE://:9092 | ||
4 | KAFKA_ADVERTISED_LISTENERS=INSIDE://:9093,OUTSIDE://kafka:9092 | 4 | KAFKA_ADVERTISED_LISTENERS=INSIDE://:9093,OUTSIDE://kafka:9092 |
5 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT | 5 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT |
6 | KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE | 6 | KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE |
7 | -KAFKA_CREATE_TOPICS=${KAFKA_TOPICS} | 7 | +KAFKA_CREATE_TOPICS=js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1 |
8 | KAFKA_AUTO_CREATE_TOPICS_ENABLE=false | 8 | KAFKA_AUTO_CREATE_TOPICS_ENABLE=false |
9 | KAFKA_LOG_RETENTION_BYTES=1073741824 | 9 | KAFKA_LOG_RETENTION_BYTES=1073741824 |
10 | KAFKA_LOG_SEGMENT_BYTES=268435456 | 10 | KAFKA_LOG_SEGMENT_BYTES=268435456 |
@@ -24,7 +24,7 @@ | @@ -24,7 +24,7 @@ | ||
24 | <file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file> | 24 | <file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file> |
25 | <rollingPolicy | 25 | <rollingPolicy |
26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | 26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
27 | - <fileNamePattern>/var/log/thingsboard/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | 27 | + <fileNamePattern>/var/log/thingsboard/${TB_HOST}/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
28 | <maxFileSize>100MB</maxFileSize> | 28 | <maxFileSize>100MB</maxFileSize> |
29 | <maxHistory>30</maxHistory> | 29 | <maxHistory>30</maxHistory> |
30 | <totalSizeCap>3GB</totalSizeCap> | 30 | <totalSizeCap>3GB</totalSizeCap> |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | # | 15 | # |
16 | 16 | ||
17 | export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" | 17 | export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" |
18 | -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | 18 | +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" |
19 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | 19 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" |
20 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | 20 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
21 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | 21 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" |
docker/tb-transports/coap/conf/logback.xml
renamed from
msa/transport/coap/docker/logback.xml
@@ -21,10 +21,10 @@ | @@ -21,10 +21,10 @@ | ||
21 | 21 | ||
22 | <appender name="fileLogAppender" | 22 | <appender name="fileLogAppender" |
23 | class="ch.qos.logback.core.rolling.RollingFileAppender"> | 23 | class="ch.qos.logback.core.rolling.RollingFileAppender"> |
24 | - <file>/var/log/${pkg.name}/${pkg.name}.log</file> | 24 | + <file>/var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.log</file> |
25 | <rollingPolicy | 25 | <rollingPolicy |
26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | 26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
27 | - <fileNamePattern>/var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | 27 | + <fileNamePattern>/var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
28 | <maxFileSize>100MB</maxFileSize> | 28 | <maxFileSize>100MB</maxFileSize> |
29 | <maxHistory>30</maxHistory> | 29 | <maxHistory>30</maxHistory> |
30 | <totalSizeCap>3GB</totalSizeCap> | 30 | <totalSizeCap>3GB</totalSizeCap> |
docker/tb-transports/coap/conf/tb-coap-transport.conf
renamed from
msa/transport/coap/docker/tb-coap-transport.conf
@@ -14,10 +14,10 @@ | @@ -14,10 +14,10 @@ | ||
14 | # limitations under the License. | 14 | # limitations under the License. |
15 | # | 15 | # |
16 | 16 | ||
17 | -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | 17 | +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" |
18 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | 18 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" |
19 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | 19 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
20 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | 20 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" |
21 | export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | 21 | export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" |
22 | -export LOG_FILENAME=${pkg.name}.out | ||
23 | -export LOADER_PATH=${pkg.installFolder}/conf | 22 | +export LOG_FILENAME=tb-coap-transport.out |
23 | +export LOADER_PATH=/usr/share/tb-coap-transport/conf |
docker/tb-transports/http/conf/logback.xml
renamed from
msa/transport/http/docker/logback.xml
@@ -21,10 +21,10 @@ | @@ -21,10 +21,10 @@ | ||
21 | 21 | ||
22 | <appender name="fileLogAppender" | 22 | <appender name="fileLogAppender" |
23 | class="ch.qos.logback.core.rolling.RollingFileAppender"> | 23 | class="ch.qos.logback.core.rolling.RollingFileAppender"> |
24 | - <file>/var/log/${pkg.name}/${pkg.name}.log</file> | 24 | + <file>/var/log/tb-http-transport/${TB_HOST}/tb-http-transport.log</file> |
25 | <rollingPolicy | 25 | <rollingPolicy |
26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | 26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
27 | - <fileNamePattern>/var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | 27 | + <fileNamePattern>/var/log/tb-http-transport/${TB_HOST}/tb-http-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
28 | <maxFileSize>100MB</maxFileSize> | 28 | <maxFileSize>100MB</maxFileSize> |
29 | <maxHistory>30</maxHistory> | 29 | <maxHistory>30</maxHistory> |
30 | <totalSizeCap>3GB</totalSizeCap> | 30 | <totalSizeCap>3GB</totalSizeCap> |
docker/tb-transports/http/conf/tb-http-transport.conf
renamed from
msa/transport/http/docker/tb-http-transport.conf
@@ -14,10 +14,10 @@ | @@ -14,10 +14,10 @@ | ||
14 | # limitations under the License. | 14 | # limitations under the License. |
15 | # | 15 | # |
16 | 16 | ||
17 | -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | 17 | +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" |
18 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | 18 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" |
19 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | 19 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
20 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | 20 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" |
21 | export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | 21 | export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" |
22 | -export LOG_FILENAME=${pkg.name}.out | ||
23 | -export LOADER_PATH=${pkg.installFolder}/conf | 22 | +export LOG_FILENAME=tb-http-transport.out |
23 | +export LOADER_PATH=/usr/share/tb-http-transport/conf |
docker/tb-transports/mqtt/conf/logback.xml
renamed from
msa/transport/mqtt/docker/logback.xml
@@ -21,10 +21,10 @@ | @@ -21,10 +21,10 @@ | ||
21 | 21 | ||
22 | <appender name="fileLogAppender" | 22 | <appender name="fileLogAppender" |
23 | class="ch.qos.logback.core.rolling.RollingFileAppender"> | 23 | class="ch.qos.logback.core.rolling.RollingFileAppender"> |
24 | - <file>/var/log/${pkg.name}/${pkg.name}.log</file> | 24 | + <file>/var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.log</file> |
25 | <rollingPolicy | 25 | <rollingPolicy |
26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | 26 | class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
27 | - <fileNamePattern>/var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | 27 | + <fileNamePattern>/var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
28 | <maxFileSize>100MB</maxFileSize> | 28 | <maxFileSize>100MB</maxFileSize> |
29 | <maxHistory>30</maxHistory> | 29 | <maxHistory>30</maxHistory> |
30 | <totalSizeCap>3GB</totalSizeCap> | 30 | <totalSizeCap>3GB</totalSizeCap> |
docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf
renamed from
msa/transport/mqtt/docker/tb-mqtt-transport.conf
@@ -14,10 +14,10 @@ | @@ -14,10 +14,10 @@ | ||
14 | # limitations under the License. | 14 | # limitations under the License. |
15 | # | 15 | # |
16 | 16 | ||
17 | -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | 17 | +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" |
18 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | 18 | export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" |
19 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | 19 | export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
20 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | 20 | export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" |
21 | export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | 21 | export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" |
22 | -export LOG_FILENAME=${pkg.name}.out | ||
23 | -export LOADER_PATH=${pkg.installFolder}/conf | 22 | +export LOG_FILENAME=tb-mqtt-transport.out |
23 | +export LOADER_PATH=/usr/share/tb-mqtt-transport/conf |
1 | 1 | ||
2 | HTTP_BIND_ADDRESS=0.0.0.0 | 2 | HTTP_BIND_ADDRESS=0.0.0.0 |
3 | HTTP_BIND_PORT=8080 | 3 | HTTP_BIND_PORT=8080 |
4 | -TB_HOST=haproxy | ||
5 | -TB_PORT=8080 | 4 | +TB_ENABLE_PROXY=false |
6 | LOGGER_LEVEL=info | 5 | LOGGER_LEVEL=info |
7 | LOG_FOLDER=logs | 6 | LOG_FOLDER=logs |
8 | LOGGER_FILENAME=tb-web-ui-%DATE%.log | 7 | LOGGER_FILENAME=tb-web-ui-%DATE%.log |
msa/black-box-tests/README.md
0 → 100644
1 | + | ||
2 | +## Black box tests execution | ||
3 | +To run the black box tests with using Docker, the local Docker images of Thingsboard's microservices should be built. <br /> | ||
4 | +- Build the local Docker images in the directory with the Thingsboard's main [pom.xml](./../../pom.xml): | ||
5 | + | ||
6 | + mvn clean install -Ddockerfile.skip=false | ||
7 | +- Verify that the new local images were built: | ||
8 | + | ||
9 | + docker image ls | ||
10 | +As result, in REPOSITORY column, next images should be present: | ||
11 | + | ||
12 | + thingsboard/tb-coap-transport | ||
13 | + thingsboard/tb-http-transport | ||
14 | + thingsboard/tb-mqtt-transport | ||
15 | + thingsboard/tb-node | ||
16 | + thingsboard/tb-web-ui | ||
17 | + thingsboard/tb-js-executor | ||
18 | + | ||
19 | +- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory: | ||
20 | + | ||
21 | + mvn clean install -DblackBoxTests.skip=false | ||
22 | + | ||
23 | + |
msa/black-box-tests/pom.xml
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2018 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
20 | + <modelVersion>4.0.0</modelVersion> | ||
21 | + | ||
22 | + <parent> | ||
23 | + <groupId>org.thingsboard</groupId> | ||
24 | + <version>2.2.0-SNAPSHOT</version> | ||
25 | + <artifactId>msa</artifactId> | ||
26 | + </parent> | ||
27 | + <groupId>org.thingsboard.msa</groupId> | ||
28 | + <artifactId>black-box-tests</artifactId> | ||
29 | + | ||
30 | + <name>ThingsBoard Black Box Tests</name> | ||
31 | + <url>https://thingsboard.io</url> | ||
32 | + <description>Project for ThingsBoard black box testing with using Docker</description> | ||
33 | + | ||
34 | + <properties> | ||
35 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
36 | + <main.dir>${basedir}/../..</main.dir> | ||
37 | + <blackBoxTests.skip>true</blackBoxTests.skip> | ||
38 | + <testcontainers.version>1.9.1</testcontainers.version> | ||
39 | + <zeroturnaround.version>1.10</zeroturnaround.version> | ||
40 | + <java-websocket.version>1.3.9</java-websocket.version> | ||
41 | + <httpclient.version>4.5.6</httpclient.version> | ||
42 | + </properties> | ||
43 | + | ||
44 | + <dependencies> | ||
45 | + <dependency> | ||
46 | + <groupId>org.testcontainers</groupId> | ||
47 | + <artifactId>testcontainers</artifactId> | ||
48 | + <version>${testcontainers.version}</version> | ||
49 | + </dependency> | ||
50 | + <dependency> | ||
51 | + <groupId>org.zeroturnaround</groupId> | ||
52 | + <artifactId>zt-exec</artifactId> | ||
53 | + <version>${zeroturnaround.version}</version> | ||
54 | + </dependency> | ||
55 | + <dependency> | ||
56 | + <groupId>org.java-websocket</groupId> | ||
57 | + <artifactId>Java-WebSocket</artifactId> | ||
58 | + <version>${java-websocket.version}</version> | ||
59 | + </dependency> | ||
60 | + <dependency> | ||
61 | + <groupId>org.apache.httpcomponents</groupId> | ||
62 | + <artifactId>httpclient</artifactId> | ||
63 | + <version>${httpclient.version}</version> | ||
64 | + </dependency> | ||
65 | + <dependency> | ||
66 | + <groupId>io.takari.junit</groupId> | ||
67 | + <artifactId>takari-cpsuite</artifactId> | ||
68 | + </dependency> | ||
69 | + <dependency> | ||
70 | + <groupId>ch.qos.logback</groupId> | ||
71 | + <artifactId>logback-classic</artifactId> | ||
72 | + </dependency> | ||
73 | + <dependency> | ||
74 | + <groupId>com.google.code.gson</groupId> | ||
75 | + <artifactId>gson</artifactId> | ||
76 | + </dependency> | ||
77 | + <dependency> | ||
78 | + <groupId>org.apache.commons</groupId> | ||
79 | + <artifactId>commons-lang3</artifactId> | ||
80 | + </dependency> | ||
81 | + <dependency> | ||
82 | + <groupId>com.google.guava</groupId> | ||
83 | + <artifactId>guava</artifactId> | ||
84 | + </dependency> | ||
85 | + <dependency> | ||
86 | + <groupId>org.thingsboard</groupId> | ||
87 | + <artifactId>netty-mqtt</artifactId> | ||
88 | + </dependency> | ||
89 | + <dependency> | ||
90 | + <groupId>org.thingsboard</groupId> | ||
91 | + <artifactId>tools</artifactId> | ||
92 | + </dependency> | ||
93 | + </dependencies> | ||
94 | + | ||
95 | + <build> | ||
96 | + <plugins> | ||
97 | + <plugin> | ||
98 | + <groupId>org.apache.maven.plugins</groupId> | ||
99 | + <artifactId>maven-surefire-plugin</artifactId> | ||
100 | + <configuration> | ||
101 | + <includes> | ||
102 | + <include>**/*TestSuite.java</include> | ||
103 | + </includes> | ||
104 | + <skipTests>${blackBoxTests.skip}</skipTests> | ||
105 | + </configuration> | ||
106 | + </plugin> | ||
107 | + </plugins> | ||
108 | + </build> | ||
109 | + | ||
110 | +</project> |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
19 | +import com.google.common.collect.ImmutableMap; | ||
20 | +import com.google.gson.JsonArray; | ||
21 | +import com.google.gson.JsonObject; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.apache.commons.lang3.RandomStringUtils; | ||
24 | +import org.apache.http.config.Registry; | ||
25 | +import org.apache.http.config.RegistryBuilder; | ||
26 | +import org.apache.http.conn.socket.ConnectionSocketFactory; | ||
27 | +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | ||
28 | +import org.apache.http.conn.ssl.TrustStrategy; | ||
29 | +import org.apache.http.conn.ssl.X509HostnameVerifier; | ||
30 | +import org.apache.http.impl.client.CloseableHttpClient; | ||
31 | +import org.apache.http.impl.client.HttpClients; | ||
32 | +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; | ||
33 | +import org.apache.http.ssl.SSLContextBuilder; | ||
34 | +import org.apache.http.ssl.SSLContexts; | ||
35 | +import org.junit.*; | ||
36 | +import org.junit.rules.TestRule; | ||
37 | +import org.junit.rules.TestWatcher; | ||
38 | +import org.junit.runner.Description; | ||
39 | +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; | ||
40 | +import org.thingsboard.client.tools.RestClient; | ||
41 | +import org.thingsboard.server.common.data.Device; | ||
42 | +import org.thingsboard.server.common.data.EntityType; | ||
43 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
44 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
45 | + | ||
46 | +import javax.net.ssl.*; | ||
47 | +import java.net.URI; | ||
48 | +import java.security.cert.X509Certificate; | ||
49 | +import java.util.List; | ||
50 | +import java.util.Map; | ||
51 | +import java.util.Random; | ||
52 | + | ||
53 | +@Slf4j | ||
54 | +public abstract class AbstractContainerTest { | ||
55 | + protected static final String HTTPS_URL = "https://localhost"; | ||
56 | + protected static final String WSS_URL = "wss://localhost"; | ||
57 | + protected static RestClient restClient; | ||
58 | + protected ObjectMapper mapper = new ObjectMapper(); | ||
59 | + | ||
60 | + @BeforeClass | ||
61 | + public static void before() throws Exception { | ||
62 | + restClient = new RestClient(HTTPS_URL); | ||
63 | + restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert()); | ||
64 | + } | ||
65 | + | ||
66 | + @Rule | ||
67 | + public TestRule watcher = new TestWatcher() { | ||
68 | + protected void starting(Description description) { | ||
69 | + log.info("================================================="); | ||
70 | + log.info("STARTING TEST: {}" , description.getMethodName()); | ||
71 | + log.info("================================================="); | ||
72 | + } | ||
73 | + | ||
74 | + /** | ||
75 | + * Invoked when a test succeeds | ||
76 | + */ | ||
77 | + protected void succeeded(Description description) { | ||
78 | + log.info("================================================="); | ||
79 | + log.info("SUCCEEDED TEST: {}" , description.getMethodName()); | ||
80 | + log.info("================================================="); | ||
81 | + } | ||
82 | + | ||
83 | + /** | ||
84 | + * Invoked when a test fails | ||
85 | + */ | ||
86 | + protected void failed(Throwable e, Description description) { | ||
87 | + log.info("================================================="); | ||
88 | + log.info("FAILED TEST: {}" , description.getMethodName(), e); | ||
89 | + log.info("================================================="); | ||
90 | + } | ||
91 | + }; | ||
92 | + | ||
93 | + protected Device createDevice(String name) { | ||
94 | + return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); | ||
95 | + } | ||
96 | + | ||
97 | + protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception { | ||
98 | + WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); | ||
99 | + SSLContextBuilder builder = SSLContexts.custom(); | ||
100 | + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); | ||
101 | + wsClient.setSocket(builder.build().getSocketFactory().createSocket()); | ||
102 | + wsClient.connectBlocking(); | ||
103 | + | ||
104 | + JsonObject cmdsObject = new JsonObject(); | ||
105 | + cmdsObject.addProperty("entityType", EntityType.DEVICE.name()); | ||
106 | + cmdsObject.addProperty("entityId", deviceId.toString()); | ||
107 | + cmdsObject.addProperty("scope", scope); | ||
108 | + cmdsObject.addProperty("cmdId", new Random().nextInt(100)); | ||
109 | + | ||
110 | + JsonArray cmd = new JsonArray(); | ||
111 | + cmd.add(cmdsObject); | ||
112 | + JsonObject wsRequest = new JsonObject(); | ||
113 | + wsRequest.add(property.toString(), cmd); | ||
114 | + wsClient.send(wsRequest.toString()); | ||
115 | + wsClient.waitForFirstReply(); | ||
116 | + return wsClient; | ||
117 | + } | ||
118 | + | ||
119 | + protected Map<String, Long> getExpectedLatestValues(long ts) { | ||
120 | + return ImmutableMap.<String, Long>builder() | ||
121 | + .put("booleanKey", ts) | ||
122 | + .put("stringKey", ts) | ||
123 | + .put("doubleKey", ts) | ||
124 | + .put("longKey", ts) | ||
125 | + .build(); | ||
126 | + } | ||
127 | + | ||
128 | + protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, Long expectedTs, String expectedValue) { | ||
129 | + List<Object> list = wsTelemetryResponse.getDataValuesByKey(key); | ||
130 | + return expectedTs.equals(list.get(0)) && expectedValue.equals(list.get(1)); | ||
131 | + } | ||
132 | + | ||
133 | + protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, String expectedValue) { | ||
134 | + List<Object> list = wsTelemetryResponse.getDataValuesByKey(key); | ||
135 | + return expectedValue.equals(list.get(1)); | ||
136 | + } | ||
137 | + | ||
138 | + protected JsonObject createPayload(long ts) { | ||
139 | + JsonObject values = createPayload(); | ||
140 | + JsonObject payload = new JsonObject(); | ||
141 | + payload.addProperty("ts", ts); | ||
142 | + payload.add("values", values); | ||
143 | + return payload; | ||
144 | + } | ||
145 | + | ||
146 | + protected JsonObject createPayload() { | ||
147 | + JsonObject values = new JsonObject(); | ||
148 | + values.addProperty("stringKey", "value1"); | ||
149 | + values.addProperty("booleanKey", true); | ||
150 | + values.addProperty("doubleKey", 42.0); | ||
151 | + values.addProperty("longKey", 73L); | ||
152 | + | ||
153 | + return values; | ||
154 | + } | ||
155 | + | ||
156 | + protected enum CmdsType { | ||
157 | + TS_SUB_CMDS("tsSubCmds"), | ||
158 | + HISTORY_CMDS("historyCmds"), | ||
159 | + ATTR_SUB_CMDS("attrSubCmds"); | ||
160 | + | ||
161 | + private final String text; | ||
162 | + | ||
163 | + CmdsType(final String text) { | ||
164 | + this.text = text; | ||
165 | + } | ||
166 | + | ||
167 | + @Override | ||
168 | + public String toString() { | ||
169 | + return text; | ||
170 | + } | ||
171 | + } | ||
172 | + | ||
173 | + private static HttpComponentsClientHttpRequestFactory getRequestFactoryForSelfSignedCert() throws Exception { | ||
174 | + SSLContextBuilder builder = SSLContexts.custom(); | ||
175 | + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); | ||
176 | + SSLContext sslContext = builder.build(); | ||
177 | + SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() { | ||
178 | + @Override | ||
179 | + public void verify(String host, SSLSocket ssl) { | ||
180 | + } | ||
181 | + | ||
182 | + @Override | ||
183 | + public void verify(String host, X509Certificate cert) { | ||
184 | + } | ||
185 | + | ||
186 | + @Override | ||
187 | + public void verify(String host, String[] cns, String[] subjectAlts) { | ||
188 | + } | ||
189 | + | ||
190 | + @Override | ||
191 | + public boolean verify(String s, SSLSession sslSession) { | ||
192 | + return true; | ||
193 | + } | ||
194 | + }); | ||
195 | + | ||
196 | + Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder | ||
197 | + .<ConnectionSocketFactory>create() | ||
198 | + .register("https", sslSelfSigned) | ||
199 | + .build(); | ||
200 | + | ||
201 | + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry); | ||
202 | + CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); | ||
203 | + return new HttpComponentsClientHttpRequestFactory(httpClient); | ||
204 | + } | ||
205 | + | ||
206 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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; | ||
17 | + | ||
18 | +import org.junit.ClassRule; | ||
19 | +import org.junit.extensions.cpsuite.ClasspathSuite; | ||
20 | +import org.junit.rules.ExternalResource; | ||
21 | +import org.junit.runner.RunWith; | ||
22 | +import org.testcontainers.containers.DockerComposeContainer; | ||
23 | +import org.testcontainers.containers.wait.strategy.Wait; | ||
24 | +import org.testcontainers.utility.Base58; | ||
25 | + | ||
26 | +import java.io.File; | ||
27 | +import java.time.Duration; | ||
28 | +import java.util.Arrays; | ||
29 | +import java.util.HashMap; | ||
30 | +import java.util.List; | ||
31 | +import java.util.Map; | ||
32 | + | ||
33 | +@RunWith(ClasspathSuite.class) | ||
34 | +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) | ||
35 | +public class ContainerTestSuite { | ||
36 | + | ||
37 | + private static DockerComposeContainer testContainer; | ||
38 | + | ||
39 | + @ClassRule | ||
40 | + public static ThingsBoardDbInstaller installTb = new ThingsBoardDbInstaller(); | ||
41 | + | ||
42 | + @ClassRule | ||
43 | + public static DockerComposeContainer getTestContainer() { | ||
44 | + if (testContainer == null) { | ||
45 | + testContainer = new DockerComposeContainer( | ||
46 | + new File("./../../docker/docker-compose.yml"), | ||
47 | + new File("./../../docker/docker-compose.postgres.yml"), | ||
48 | + new File("./../../docker/docker-compose.postgres.volumes.yml")) | ||
49 | + .withPull(false) | ||
50 | + .withLocalCompose(true) | ||
51 | + .withTailChildContainers(true) | ||
52 | + .withEnv(installTb.getEnv()) | ||
53 | + .withEnv("LOAD_BALANCER_NAME", "") | ||
54 | + .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(120))); | ||
55 | + } | ||
56 | + return testContainer; | ||
57 | + } | ||
58 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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 | + | ||
17 | +package org.thingsboard.server.msa; | ||
18 | + | ||
19 | + | ||
20 | +import com.google.common.base.Splitter; | ||
21 | +import com.google.common.collect.Maps; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.apache.commons.lang3.SystemUtils; | ||
24 | +import org.testcontainers.containers.ContainerLaunchException; | ||
25 | +import org.testcontainers.utility.CommandLine; | ||
26 | +import org.zeroturnaround.exec.InvalidExitValueException; | ||
27 | +import org.zeroturnaround.exec.ProcessExecutor; | ||
28 | +import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; | ||
29 | + | ||
30 | +import java.io.File; | ||
31 | +import java.util.HashMap; | ||
32 | +import java.util.List; | ||
33 | +import java.util.Map; | ||
34 | +import java.util.Objects; | ||
35 | +import java.util.stream.Stream; | ||
36 | + | ||
37 | +import static com.google.common.base.Preconditions.checkArgument; | ||
38 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
39 | +import static java.util.stream.Collectors.joining; | ||
40 | + | ||
41 | +@Slf4j | ||
42 | +public class DockerComposeExecutor { | ||
43 | + | ||
44 | + String ENV_PROJECT_NAME = "COMPOSE_PROJECT_NAME"; | ||
45 | + String ENV_COMPOSE_FILE = "COMPOSE_FILE"; | ||
46 | + | ||
47 | + private static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker-compose.exe" : "docker-compose"; | ||
48 | + private static final String DOCKER_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker.exe" : "docker"; | ||
49 | + | ||
50 | + private final List<File> composeFiles; | ||
51 | + private final String identifier; | ||
52 | + private String cmd = ""; | ||
53 | + private Map<String, String> env = new HashMap<>(); | ||
54 | + | ||
55 | + public DockerComposeExecutor(List<File> composeFiles, String identifier) { | ||
56 | + validateFileList(composeFiles); | ||
57 | + this.composeFiles = composeFiles; | ||
58 | + this.identifier = identifier; | ||
59 | + } | ||
60 | + | ||
61 | + public DockerComposeExecutor withCommand(String cmd) { | ||
62 | + this.cmd = cmd; | ||
63 | + return this; | ||
64 | + } | ||
65 | + | ||
66 | + public DockerComposeExecutor withEnv(Map<String, String> env) { | ||
67 | + this.env = env; | ||
68 | + return this; | ||
69 | + } | ||
70 | + | ||
71 | + public void invokeCompose() { | ||
72 | + // bail out early | ||
73 | + if (!CommandLine.executableExists(COMPOSE_EXECUTABLE)) { | ||
74 | + throw new ContainerLaunchException("Local Docker Compose not found. Is " + COMPOSE_EXECUTABLE + " on the PATH?"); | ||
75 | + } | ||
76 | + final Map<String, String> environment = Maps.newHashMap(env); | ||
77 | + environment.put(ENV_PROJECT_NAME, identifier); | ||
78 | + final Stream<String> absoluteDockerComposeFilePaths = composeFiles.stream().map(File::getAbsolutePath).map(Objects::toString); | ||
79 | + final String composeFileEnvVariableValue = absoluteDockerComposeFilePaths.collect(joining(File.pathSeparator + "")); | ||
80 | + log.debug("Set env COMPOSE_FILE={}", composeFileEnvVariableValue); | ||
81 | + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile(); | ||
82 | + environment.put(ENV_COMPOSE_FILE, composeFileEnvVariableValue); | ||
83 | + log.info("Local Docker Compose is running command: {}", cmd); | ||
84 | + final List<String> command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(COMPOSE_EXECUTABLE + " " + cmd); | ||
85 | + try { | ||
86 | + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).environment(environment).directory(pwd).exitValueNormal().executeNoTimeout(); | ||
87 | + log.info("Docker Compose has finished running"); | ||
88 | + } catch (InvalidExitValueException e) { | ||
89 | + throw new ContainerLaunchException("Local Docker Compose exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd); | ||
90 | + } catch (Exception e) { | ||
91 | + throw new ContainerLaunchException("Error running local Docker Compose command: " + cmd, e); | ||
92 | + } | ||
93 | + } | ||
94 | + | ||
95 | + public void invokeDocker() { | ||
96 | + // bail out early | ||
97 | + if (!CommandLine.executableExists(DOCKER_EXECUTABLE)) { | ||
98 | + throw new ContainerLaunchException("Local Docker not found. Is " + DOCKER_EXECUTABLE + " on the PATH?"); | ||
99 | + } | ||
100 | + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile(); | ||
101 | + log.info("Local Docker is running command: {}", cmd); | ||
102 | + final List<String> command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(DOCKER_EXECUTABLE + " " + cmd); | ||
103 | + try { | ||
104 | + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).directory(pwd).exitValueNormal().executeNoTimeout(); | ||
105 | + log.info("Docker has finished running"); | ||
106 | + } catch (InvalidExitValueException e) { | ||
107 | + throw new ContainerLaunchException("Local Docker exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd); | ||
108 | + } catch (Exception e) { | ||
109 | + throw new ContainerLaunchException("Error running local Docker command: " + cmd, e); | ||
110 | + } | ||
111 | + } | ||
112 | + | ||
113 | + void validateFileList(List<File> composeFiles) { | ||
114 | + checkNotNull(composeFiles); | ||
115 | + checkArgument(!composeFiles.isEmpty(), "No docker compose file have been provided"); | ||
116 | + } | ||
117 | + | ||
118 | + | ||
119 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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; | ||
17 | + | ||
18 | +import org.junit.rules.ExternalResource; | ||
19 | +import org.testcontainers.utility.Base58; | ||
20 | + | ||
21 | +import java.io.File; | ||
22 | +import java.util.Arrays; | ||
23 | +import java.util.HashMap; | ||
24 | +import java.util.List; | ||
25 | +import java.util.Map; | ||
26 | + | ||
27 | +public class ThingsBoardDbInstaller extends ExternalResource { | ||
28 | + | ||
29 | + private final static String POSTGRES_DATA_VOLUME = "tb-postgres-test-data-volume"; | ||
30 | + private final static String TB_LOG_VOLUME = "tb-log-test-volume"; | ||
31 | + private final static String TB_COAP_TRANSPORT_LOG_VOLUME = "tb-coap-transport-log-test-volume"; | ||
32 | + private final static String TB_HTTP_TRANSPORT_LOG_VOLUME = "tb-http-transport-log-test-volume"; | ||
33 | + private final static String TB_MQTT_TRANSPORT_LOG_VOLUME = "tb-mqtt-transport-log-test-volume"; | ||
34 | + | ||
35 | + private final DockerComposeExecutor dockerCompose; | ||
36 | + | ||
37 | + private final String postgresDataVolume; | ||
38 | + private final String tbLogVolume; | ||
39 | + private final String tbCoapTransportLogVolume; | ||
40 | + private final String tbHttpTransportLogVolume; | ||
41 | + private final String tbMqttTransportLogVolume; | ||
42 | + private final Map<String, String> env; | ||
43 | + | ||
44 | + public ThingsBoardDbInstaller() { | ||
45 | + List<File> composeFiles = Arrays.asList(new File("./../../docker/docker-compose.yml"), | ||
46 | + new File("./../../docker/docker-compose.postgres.yml"), | ||
47 | + new File("./../../docker/docker-compose.postgres.volumes.yml")); | ||
48 | + | ||
49 | + String identifier = Base58.randomString(6).toLowerCase(); | ||
50 | + String project = identifier + Base58.randomString(6).toLowerCase(); | ||
51 | + | ||
52 | + postgresDataVolume = project + "_" + POSTGRES_DATA_VOLUME; | ||
53 | + tbLogVolume = project + "_" + TB_LOG_VOLUME; | ||
54 | + tbCoapTransportLogVolume = project + "_" + TB_COAP_TRANSPORT_LOG_VOLUME; | ||
55 | + tbHttpTransportLogVolume = project + "_" + TB_HTTP_TRANSPORT_LOG_VOLUME; | ||
56 | + tbMqttTransportLogVolume = project + "_" + TB_MQTT_TRANSPORT_LOG_VOLUME; | ||
57 | + | ||
58 | + dockerCompose = new DockerComposeExecutor(composeFiles, project); | ||
59 | + | ||
60 | + env = new HashMap<>(); | ||
61 | + env.put("POSTGRES_DATA_VOLUME", postgresDataVolume); | ||
62 | + env.put("TB_LOG_VOLUME", tbLogVolume); | ||
63 | + env.put("TB_COAP_TRANSPORT_LOG_VOLUME", tbCoapTransportLogVolume); | ||
64 | + env.put("TB_HTTP_TRANSPORT_LOG_VOLUME", tbHttpTransportLogVolume); | ||
65 | + env.put("TB_MQTT_TRANSPORT_LOG_VOLUME", tbMqttTransportLogVolume); | ||
66 | + dockerCompose.withEnv(env); | ||
67 | + } | ||
68 | + | ||
69 | + public Map<String, String> getEnv() { | ||
70 | + return env; | ||
71 | + } | ||
72 | + | ||
73 | + @Override | ||
74 | + protected void before() throws Throwable { | ||
75 | + try { | ||
76 | + | ||
77 | + dockerCompose.withCommand("volume create " + postgresDataVolume); | ||
78 | + dockerCompose.invokeDocker(); | ||
79 | + | ||
80 | + dockerCompose.withCommand("volume create " + tbLogVolume); | ||
81 | + dockerCompose.invokeDocker(); | ||
82 | + | ||
83 | + dockerCompose.withCommand("volume create " + tbCoapTransportLogVolume); | ||
84 | + dockerCompose.invokeDocker(); | ||
85 | + | ||
86 | + dockerCompose.withCommand("volume create " + tbHttpTransportLogVolume); | ||
87 | + dockerCompose.invokeDocker(); | ||
88 | + | ||
89 | + dockerCompose.withCommand("volume create " + tbMqttTransportLogVolume); | ||
90 | + dockerCompose.invokeDocker(); | ||
91 | + | ||
92 | + dockerCompose.withCommand("up -d redis postgres"); | ||
93 | + dockerCompose.invokeCompose(); | ||
94 | + | ||
95 | + dockerCompose.withCommand("run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=true tb1"); | ||
96 | + dockerCompose.invokeCompose(); | ||
97 | + | ||
98 | + } finally { | ||
99 | + try { | ||
100 | + dockerCompose.withCommand("down -v"); | ||
101 | + dockerCompose.invokeCompose(); | ||
102 | + } catch (Exception e) {} | ||
103 | + } | ||
104 | + } | ||
105 | + | ||
106 | + @Override | ||
107 | + protected void after() { | ||
108 | + copyLogs(tbLogVolume, "./target/tb-logs/"); | ||
109 | + copyLogs(tbCoapTransportLogVolume, "./target/tb-coap-transport-logs/"); | ||
110 | + copyLogs(tbHttpTransportLogVolume, "./target/tb-http-transport-logs/"); | ||
111 | + copyLogs(tbMqttTransportLogVolume, "./target/tb-mqtt-transport-logs/"); | ||
112 | + | ||
113 | + dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume + | ||
114 | + " " + tbCoapTransportLogVolume + " " + tbHttpTransportLogVolume + " " + tbMqttTransportLogVolume); | ||
115 | + dockerCompose.invokeDocker(); | ||
116 | + } | ||
117 | + | ||
118 | + private void copyLogs(String volumeName, String targetDir) { | ||
119 | + File tbLogsDir = new File(targetDir); | ||
120 | + tbLogsDir.mkdirs(); | ||
121 | + | ||
122 | + dockerCompose.withCommand("run -d --rm --name tb-logs-container -v " + volumeName + ":/root alpine tail -f /dev/null"); | ||
123 | + dockerCompose.invokeDocker(); | ||
124 | + | ||
125 | + dockerCompose.withCommand("cp tb-logs-container:/root/. "+tbLogsDir.getAbsolutePath()); | ||
126 | + dockerCompose.invokeDocker(); | ||
127 | + | ||
128 | + dockerCompose.withCommand("rm -f tb-logs-container"); | ||
129 | + dockerCompose.invokeDocker(); | ||
130 | + } | ||
131 | + | ||
132 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2018 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; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
19 | +import lombok.extern.slf4j.Slf4j; | ||
20 | +import org.java_websocket.client.WebSocketClient; | ||
21 | +import org.java_websocket.handshake.ServerHandshake; | ||
22 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
23 | + | ||
24 | +import java.io.IOException; | ||
25 | +import java.net.URI; | ||
26 | +import java.util.concurrent.CountDownLatch; | ||
27 | +import java.util.concurrent.TimeUnit; | ||
28 | + | ||
29 | +@Slf4j | ||
30 | +public class WsClient extends WebSocketClient { | ||
31 | + private static final ObjectMapper mapper = new ObjectMapper(); | ||
32 | + private WsTelemetryResponse message; | ||
33 | + | ||
34 | + private volatile boolean firstReplyReceived; | ||
35 | + private CountDownLatch firstReply = new CountDownLatch(1); | ||
36 | + private CountDownLatch latch = new CountDownLatch(1); | ||
37 | + | ||
38 | + WsClient(URI serverUri) { | ||
39 | + super(serverUri); | ||
40 | + } | ||
41 | + | ||
42 | + @Override | ||
43 | + public void onOpen(ServerHandshake serverHandshake) { | ||
44 | + } | ||
45 | + | ||
46 | + @Override | ||
47 | + public void onMessage(String message) { | ||
48 | + if (!firstReplyReceived) { | ||
49 | + firstReplyReceived = true; | ||
50 | + firstReply.countDown(); | ||
51 | + } else { | ||
52 | + try { | ||
53 | + WsTelemetryResponse response = mapper.readValue(message, WsTelemetryResponse.class); | ||
54 | + if (!response.getData().isEmpty()) { | ||
55 | + this.message = response; | ||
56 | + latch.countDown(); | ||
57 | + } | ||
58 | + } catch (IOException e) { | ||
59 | + log.error("ws message can't be read"); | ||
60 | + } | ||
61 | + } | ||
62 | + } | ||
63 | + | ||
64 | + @Override | ||
65 | + public void onClose(int code, String reason, boolean remote) { | ||
66 | + log.info("ws is closed, due to [{}]", reason); | ||
67 | + } | ||
68 | + | ||
69 | + @Override | ||
70 | + public void onError(Exception ex) { | ||
71 | + ex.printStackTrace(); | ||
72 | + } | ||
73 | + | ||
74 | + public WsTelemetryResponse getLastMessage() { | ||
75 | + try { | ||
76 | + latch.await(10, TimeUnit.SECONDS); | ||
77 | + return this.message; | ||
78 | + } catch (InterruptedException e) { | ||
79 | + log.error("Timeout, ws message wasn't received"); | ||
80 | + } | ||
81 | + return null; | ||
82 | + } | ||
83 | + | ||
84 | + void waitForFirstReply() { | ||
85 | + try { | ||
86 | + firstReply.await(10, TimeUnit.SECONDS); | ||
87 | + } catch (InterruptedException e) { | ||
88 | + log.error("Timeout, ws message wasn't received"); | ||
89 | + throw new RuntimeException(e); | ||
90 | + } | ||
91 | + } | ||
92 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.connectivity; | ||
17 | + | ||
18 | +import com.google.common.collect.Sets; | ||
19 | +import org.junit.Assert; | ||
20 | +import org.junit.Test; | ||
21 | +import org.springframework.http.ResponseEntity; | ||
22 | +import org.thingsboard.server.common.data.Device; | ||
23 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
24 | +import org.thingsboard.server.msa.AbstractContainerTest; | ||
25 | +import org.thingsboard.server.msa.WsClient; | ||
26 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
27 | + | ||
28 | +public class HttpClientTest extends AbstractContainerTest { | ||
29 | + | ||
30 | + @Test | ||
31 | + public void telemetryUpload() throws Exception { | ||
32 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
33 | + | ||
34 | + Device device = createDevice("http_"); | ||
35 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
36 | + | ||
37 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); | ||
38 | + ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate() | ||
39 | + .postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry", | ||
40 | + mapper.readTree(createPayload().toString()), | ||
41 | + ResponseEntity.class, | ||
42 | + deviceCredentials.getCredentialsId()); | ||
43 | + Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful()); | ||
44 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
45 | + wsClient.closeBlocking(); | ||
46 | + | ||
47 | + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), | ||
48 | + actualLatestTelemetry.getLatestValues().keySet()); | ||
49 | + | ||
50 | + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); | ||
51 | + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); | ||
52 | + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); | ||
53 | + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); | ||
54 | + | ||
55 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
56 | + } | ||
57 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.connectivity; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.google.common.collect.Sets; | ||
20 | +import com.google.common.util.concurrent.ListenableFuture; | ||
21 | +import com.google.common.util.concurrent.ListeningExecutorService; | ||
22 | +import com.google.common.util.concurrent.MoreExecutors; | ||
23 | +import com.google.gson.JsonObject; | ||
24 | +import io.netty.buffer.ByteBuf; | ||
25 | +import io.netty.buffer.Unpooled; | ||
26 | +import io.netty.handler.codec.mqtt.MqttQoS; | ||
27 | +import lombok.Data; | ||
28 | +import lombok.extern.slf4j.Slf4j; | ||
29 | +import org.apache.commons.lang3.RandomStringUtils; | ||
30 | +import org.junit.*; | ||
31 | +import org.junit.rules.TestRule; | ||
32 | +import org.junit.rules.TestWatcher; | ||
33 | +import org.junit.runner.Description; | ||
34 | +import org.springframework.core.ParameterizedTypeReference; | ||
35 | +import org.springframework.http.HttpMethod; | ||
36 | +import org.springframework.http.ResponseEntity; | ||
37 | +import org.thingsboard.mqtt.MqttClient; | ||
38 | +import org.thingsboard.mqtt.MqttClientConfig; | ||
39 | +import org.thingsboard.mqtt.MqttHandler; | ||
40 | +import org.thingsboard.server.common.data.Device; | ||
41 | +import org.thingsboard.server.common.data.id.RuleChainId; | ||
42 | +import org.thingsboard.server.common.data.page.TextPageData; | ||
43 | +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; | ||
44 | +import org.thingsboard.server.common.data.rule.RuleChain; | ||
45 | +import org.thingsboard.server.common.data.rule.RuleChainMetaData; | ||
46 | +import org.thingsboard.server.common.data.rule.RuleNode; | ||
47 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
48 | +import org.thingsboard.server.msa.AbstractContainerTest; | ||
49 | +import org.thingsboard.server.msa.WsClient; | ||
50 | +import org.thingsboard.server.msa.mapper.AttributesResponse; | ||
51 | +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; | ||
52 | + | ||
53 | +import java.io.IOException; | ||
54 | +import java.nio.charset.StandardCharsets; | ||
55 | +import java.util.*; | ||
56 | +import java.util.concurrent.*; | ||
57 | + | ||
58 | +@Slf4j | ||
59 | +public class MqttClientTest extends AbstractContainerTest { | ||
60 | + | ||
61 | + @Test | ||
62 | + public void telemetryUpload() throws Exception { | ||
63 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
64 | + Device device = createDevice("mqtt_"); | ||
65 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
66 | + | ||
67 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); | ||
68 | + MqttClient mqttClient = getMqttClient(deviceCredentials, null); | ||
69 | + mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes())); | ||
70 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
71 | + log.info("Received telemetry: {}", actualLatestTelemetry); | ||
72 | + wsClient.closeBlocking(); | ||
73 | + | ||
74 | + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); | ||
75 | + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), | ||
76 | + actualLatestTelemetry.getLatestValues().keySet()); | ||
77 | + | ||
78 | + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); | ||
79 | + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); | ||
80 | + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); | ||
81 | + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); | ||
82 | + | ||
83 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
84 | + } | ||
85 | + | ||
86 | + @Test | ||
87 | + public void telemetryUploadWithTs() throws Exception { | ||
88 | + long ts = 1451649600512L; | ||
89 | + | ||
90 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
91 | + Device device = createDevice("mqtt_"); | ||
92 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
93 | + | ||
94 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); | ||
95 | + MqttClient mqttClient = getMqttClient(deviceCredentials, null); | ||
96 | + mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes())); | ||
97 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
98 | + log.info("Received telemetry: {}", actualLatestTelemetry); | ||
99 | + wsClient.closeBlocking(); | ||
100 | + | ||
101 | + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); | ||
102 | + Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); | ||
103 | + | ||
104 | + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString())); | ||
105 | + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1")); | ||
106 | + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0))); | ||
107 | + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73))); | ||
108 | + | ||
109 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
110 | + } | ||
111 | + | ||
112 | + @Test | ||
113 | + public void publishAttributeUpdateToServer() throws Exception { | ||
114 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
115 | + Device device = createDevice("mqtt_"); | ||
116 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
117 | + | ||
118 | + WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); | ||
119 | + MqttMessageListener listener = new MqttMessageListener(); | ||
120 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
121 | + JsonObject clientAttributes = new JsonObject(); | ||
122 | + clientAttributes.addProperty("attr1", "value1"); | ||
123 | + clientAttributes.addProperty("attr2", true); | ||
124 | + clientAttributes.addProperty("attr3", 42.0); | ||
125 | + clientAttributes.addProperty("attr4", 73); | ||
126 | + mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); | ||
127 | + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); | ||
128 | + log.info("Received telemetry: {}", actualLatestTelemetry); | ||
129 | + wsClient.closeBlocking(); | ||
130 | + | ||
131 | + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); | ||
132 | + Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"), | ||
133 | + actualLatestTelemetry.getLatestValues().keySet()); | ||
134 | + | ||
135 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1")); | ||
136 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString())); | ||
137 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0))); | ||
138 | + Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73))); | ||
139 | + | ||
140 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
141 | + } | ||
142 | + | ||
143 | + @Test | ||
144 | + public void requestAttributeValuesFromServer() throws Exception { | ||
145 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
146 | + Device device = createDevice("mqtt_"); | ||
147 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
148 | + | ||
149 | + MqttMessageListener listener = new MqttMessageListener(); | ||
150 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
151 | + | ||
152 | + // Add a new client attribute | ||
153 | + JsonObject clientAttributes = new JsonObject(); | ||
154 | + String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
155 | + clientAttributes.addProperty("clientAttr", clientAttributeValue); | ||
156 | + mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); | ||
157 | + | ||
158 | + // Add a new shared attribute | ||
159 | + JsonObject sharedAttributes = new JsonObject(); | ||
160 | + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
161 | + sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); | ||
162 | + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() | ||
163 | + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", | ||
164 | + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, | ||
165 | + device.getId()); | ||
166 | + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); | ||
167 | + | ||
168 | + // Subscribe to attributes response | ||
169 | + mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE); | ||
170 | + // Request attributes | ||
171 | + JsonObject request = new JsonObject(); | ||
172 | + request.addProperty("clientKeys", "clientAttr"); | ||
173 | + request.addProperty("sharedKeys", "sharedAttr"); | ||
174 | + mqttClient.publish("v1/devices/me/attributes/request/" + new Random().nextInt(100), Unpooled.wrappedBuffer(request.toString().getBytes())); | ||
175 | + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
176 | + AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class); | ||
177 | + log.info("Received telemetry: {}", attributes); | ||
178 | + | ||
179 | + Assert.assertEquals(1, attributes.getClient().size()); | ||
180 | + Assert.assertEquals(clientAttributeValue, attributes.getClient().get("clientAttr")); | ||
181 | + | ||
182 | + Assert.assertEquals(1, attributes.getShared().size()); | ||
183 | + Assert.assertEquals(sharedAttributeValue, attributes.getShared().get("sharedAttr")); | ||
184 | + | ||
185 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
186 | + } | ||
187 | + | ||
188 | + @Test | ||
189 | + public void subscribeToAttributeUpdatesFromServer() throws Exception { | ||
190 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
191 | + Device device = createDevice("mqtt_"); | ||
192 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
193 | + | ||
194 | + MqttMessageListener listener = new MqttMessageListener(); | ||
195 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
196 | + mqttClient.on("v1/devices/me/attributes", listener, MqttQoS.AT_LEAST_ONCE); | ||
197 | + | ||
198 | + String sharedAttributeName = "sharedAttr"; | ||
199 | + | ||
200 | + // Add a new shared attribute | ||
201 | + JsonObject sharedAttributes = new JsonObject(); | ||
202 | + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
203 | + sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); | ||
204 | + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() | ||
205 | + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", | ||
206 | + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, | ||
207 | + device.getId()); | ||
208 | + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); | ||
209 | + | ||
210 | + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
211 | + Assert.assertEquals(sharedAttributeValue, | ||
212 | + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); | ||
213 | + | ||
214 | + // Update the shared attribute value | ||
215 | + JsonObject updatedSharedAttributes = new JsonObject(); | ||
216 | + String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); | ||
217 | + updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); | ||
218 | + ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate() | ||
219 | + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", | ||
220 | + mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class, | ||
221 | + device.getId()); | ||
222 | + Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); | ||
223 | + | ||
224 | + event = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
225 | + Assert.assertEquals(updatedSharedAttributeValue, | ||
226 | + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); | ||
227 | + | ||
228 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
229 | + } | ||
230 | + | ||
231 | + @Test | ||
232 | + public void serverSideRpc() throws Exception { | ||
233 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
234 | + Device device = createDevice("mqtt_"); | ||
235 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
236 | + | ||
237 | + MqttMessageListener listener = new MqttMessageListener(); | ||
238 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
239 | + mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE); | ||
240 | + | ||
241 | + // Send an RPC from the server | ||
242 | + JsonObject serverRpcPayload = new JsonObject(); | ||
243 | + serverRpcPayload.addProperty("method", "getValue"); | ||
244 | + serverRpcPayload.addProperty("params", true); | ||
245 | + ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | ||
246 | + ListenableFuture<ResponseEntity> future = service.submit(() -> { | ||
247 | + try { | ||
248 | + return restClient.getRestTemplate() | ||
249 | + .postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", | ||
250 | + mapper.readTree(serverRpcPayload.toString()), String.class, | ||
251 | + device.getId()); | ||
252 | + } catch (IOException e) { | ||
253 | + return ResponseEntity.badRequest().build(); | ||
254 | + } | ||
255 | + }); | ||
256 | + | ||
257 | + // Wait for RPC call from the server and send the response | ||
258 | + MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS); | ||
259 | + | ||
260 | + Assert.assertEquals("{\"method\":\"getValue\",\"params\":true}", Objects.requireNonNull(requestFromServer).getMessage()); | ||
261 | + | ||
262 | + Integer requestId = Integer.valueOf(Objects.requireNonNull(requestFromServer).getTopic().substring("v1/devices/me/rpc/request/".length())); | ||
263 | + JsonObject clientResponse = new JsonObject(); | ||
264 | + clientResponse.addProperty("response", "someResponse"); | ||
265 | + // Send a response to the server's RPC request | ||
266 | + mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes())); | ||
267 | + | ||
268 | + ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS); | ||
269 | + Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); | ||
270 | + Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); | ||
271 | + | ||
272 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
273 | + } | ||
274 | + | ||
275 | + @Test | ||
276 | + public void clientSideRpc() throws Exception { | ||
277 | + restClient.login("tenant@thingsboard.org", "tenant"); | ||
278 | + Device device = createDevice("mqtt_"); | ||
279 | + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); | ||
280 | + | ||
281 | + MqttMessageListener listener = new MqttMessageListener(); | ||
282 | + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); | ||
283 | + mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE); | ||
284 | + | ||
285 | + // Get the default rule chain id to make it root again after test finished | ||
286 | + RuleChainId defaultRuleChainId = getDefaultRuleChainId(); | ||
287 | + | ||
288 | + // Create a new root rule chain | ||
289 | + RuleChainId ruleChainId = createRootRuleChainForRpcResponse(); | ||
290 | + | ||
291 | + TimeUnit.SECONDS.sleep(3); | ||
292 | + // Send the request to the server | ||
293 | + JsonObject clientRequest = new JsonObject(); | ||
294 | + clientRequest.addProperty("method", "getResponse"); | ||
295 | + clientRequest.addProperty("params", true); | ||
296 | + Integer requestId = 42; | ||
297 | + mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes())); | ||
298 | + | ||
299 | + // Check the response from the server | ||
300 | + TimeUnit.SECONDS.sleep(1); | ||
301 | + MqttEvent responseFromServer = listener.getEvents().poll(1, TimeUnit.SECONDS); | ||
302 | + Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length())); | ||
303 | + Assert.assertEquals(requestId, responseId); | ||
304 | + Assert.assertEquals("requestReceived", mapper.readTree(responseFromServer.getMessage()).get("response").asText()); | ||
305 | + | ||
306 | + // Make the default rule chain a root again | ||
307 | + ResponseEntity<RuleChain> rootRuleChainResponse = restClient.getRestTemplate() | ||
308 | + .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", | ||
309 | + null, | ||
310 | + RuleChain.class, | ||
311 | + defaultRuleChainId); | ||
312 | + Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); | ||
313 | + | ||
314 | + // Delete the created rule chain | ||
315 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/ruleChain/{ruleChainId}", ruleChainId); | ||
316 | + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); | ||
317 | + } | ||
318 | + | ||
319 | + private RuleChainId createRootRuleChainForRpcResponse() throws Exception { | ||
320 | + RuleChain newRuleChain = new RuleChain(); | ||
321 | + newRuleChain.setName("testRuleChain"); | ||
322 | + ResponseEntity<RuleChain> ruleChainResponse = restClient.getRestTemplate() | ||
323 | + .postForEntity(HTTPS_URL + "/api/ruleChain", | ||
324 | + newRuleChain, | ||
325 | + RuleChain.class); | ||
326 | + Assert.assertTrue(ruleChainResponse.getStatusCode().is2xxSuccessful()); | ||
327 | + RuleChain ruleChain = ruleChainResponse.getBody(); | ||
328 | + | ||
329 | + JsonNode configuration = mapper.readTree(this.getClass().getClassLoader().getResourceAsStream("RpcResponseRuleChainMetadata.json")); | ||
330 | + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); | ||
331 | + ruleChainMetaData.setRuleChainId(ruleChain.getId()); | ||
332 | + ruleChainMetaData.setFirstNodeIndex(configuration.get("firstNodeIndex").asInt()); | ||
333 | + ruleChainMetaData.setNodes(Arrays.asList(mapper.treeToValue(configuration.get("nodes"), RuleNode[].class))); | ||
334 | + ruleChainMetaData.setConnections(Arrays.asList(mapper.treeToValue(configuration.get("connections"), NodeConnectionInfo[].class))); | ||
335 | + | ||
336 | + ResponseEntity<RuleChainMetaData> ruleChainMetadataResponse = restClient.getRestTemplate() | ||
337 | + .postForEntity(HTTPS_URL + "/api/ruleChain/metadata", | ||
338 | + ruleChainMetaData, | ||
339 | + RuleChainMetaData.class); | ||
340 | + Assert.assertTrue(ruleChainMetadataResponse.getStatusCode().is2xxSuccessful()); | ||
341 | + | ||
342 | + // Set a new rule chain as root | ||
343 | + ResponseEntity<RuleChain> rootRuleChainResponse = restClient.getRestTemplate() | ||
344 | + .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", | ||
345 | + null, | ||
346 | + RuleChain.class, | ||
347 | + ruleChain.getId()); | ||
348 | + Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); | ||
349 | + | ||
350 | + return ruleChain.getId(); | ||
351 | + } | ||
352 | + | ||
353 | + private RuleChainId getDefaultRuleChainId() { | ||
354 | + ResponseEntity<TextPageData<RuleChain>> ruleChains = restClient.getRestTemplate().exchange( | ||
355 | + HTTPS_URL + "/api/ruleChains?limit=40&textSearch=", | ||
356 | + HttpMethod.GET, | ||
357 | + null, | ||
358 | + new ParameterizedTypeReference<TextPageData<RuleChain>>() { | ||
359 | + }); | ||
360 | + | ||
361 | + Optional<RuleChain> defaultRuleChain = ruleChains.getBody().getData() | ||
362 | + .stream() | ||
363 | + .filter(RuleChain::isRoot) | ||
364 | + .findFirst(); | ||
365 | + if (!defaultRuleChain.isPresent()) { | ||
366 | + Assert.fail("Root rule chain wasn't found"); | ||
367 | + } | ||
368 | + return defaultRuleChain.get().getId(); | ||
369 | + } | ||
370 | + | ||
371 | + private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException { | ||
372 | + MqttClientConfig clientConfig = new MqttClientConfig(); | ||
373 | + clientConfig.setClientId("MQTT client from test"); | ||
374 | + clientConfig.setUsername(deviceCredentials.getCredentialsId()); | ||
375 | + MqttClient mqttClient = MqttClient.create(clientConfig, listener); | ||
376 | + mqttClient.connect("localhost", 1883).get(); | ||
377 | + return mqttClient; | ||
378 | + } | ||
379 | + | ||
380 | + @Data | ||
381 | + private class MqttMessageListener implements MqttHandler { | ||
382 | + private final BlockingQueue<MqttEvent> events; | ||
383 | + | ||
384 | + private MqttMessageListener() { | ||
385 | + events = new ArrayBlockingQueue<>(100); | ||
386 | + } | ||
387 | + | ||
388 | + @Override | ||
389 | + public void onMessage(String topic, ByteBuf message) { | ||
390 | + log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic); | ||
391 | + events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8))); | ||
392 | + } | ||
393 | + } | ||
394 | + | ||
395 | + @Data | ||
396 | + private class MqttEvent { | ||
397 | + private final String topic; | ||
398 | + private final String message; | ||
399 | + } | ||
400 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.mapper; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.util.Map; | ||
21 | + | ||
22 | +@Data | ||
23 | +public class AttributesResponse { | ||
24 | + private Map<String, Object> client; | ||
25 | + private Map<String, Object> shared; | ||
26 | +} |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2018 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.mapper; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.io.Serializable; | ||
21 | +import java.util.Collection; | ||
22 | +import java.util.List; | ||
23 | +import java.util.Map; | ||
24 | +import java.util.stream.Collectors; | ||
25 | + | ||
26 | +@Data | ||
27 | +public class WsTelemetryResponse implements Serializable { | ||
28 | + private int subscriptionId; | ||
29 | + private int errorCode; | ||
30 | + private String errorMsg; | ||
31 | + private Map<String, List<List<Object>>> data; | ||
32 | + private Map<String, Object> latestValues; | ||
33 | + | ||
34 | + public List<Object> getDataValuesByKey(String key) { | ||
35 | + return data.entrySet().stream() | ||
36 | + .filter(e -> e.getKey().equals(key)) | ||
37 | + .flatMap(e -> e.getValue().stream().flatMap(Collection::stream)) | ||
38 | + .collect(Collectors.toList()); | ||
39 | + } | ||
40 | +} |
1 | +{ | ||
2 | + "firstNodeIndex": 0, | ||
3 | + "nodes": [ | ||
4 | + { | ||
5 | + "additionalInfo": { | ||
6 | + "layoutX": 325, | ||
7 | + "layoutY": 150 | ||
8 | + }, | ||
9 | + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", | ||
10 | + "name": "msgTypeSwitch", | ||
11 | + "debugMode": true, | ||
12 | + "configuration": { | ||
13 | + "version": 0 | ||
14 | + } | ||
15 | + }, | ||
16 | + { | ||
17 | + "additionalInfo": { | ||
18 | + "layoutX": 60, | ||
19 | + "layoutY": 300 | ||
20 | + }, | ||
21 | + "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", | ||
22 | + "name": "formResponse", | ||
23 | + "debugMode": true, | ||
24 | + "configuration": { | ||
25 | + "jsScript": "if (msg.method == \"getResponse\") {\n return {msg: {\"response\": \"requestReceived\"}, metadata: metadata, msgType: msgType};\n}\n\nreturn {msg: msg, metadata: metadata, msgType: msgType};" | ||
26 | + } | ||
27 | + }, | ||
28 | + { | ||
29 | + "additionalInfo": { | ||
30 | + "layoutX": 450, | ||
31 | + "layoutY": 300 | ||
32 | + }, | ||
33 | + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode", | ||
34 | + "name": "rpcReply", | ||
35 | + "debugMode": true, | ||
36 | + "configuration": { | ||
37 | + "requestIdMetaDataAttribute": "requestId" | ||
38 | + } | ||
39 | + } | ||
40 | + ], | ||
41 | + "connections": [ | ||
42 | + { | ||
43 | + "fromIndex": 0, | ||
44 | + "toIndex": 1, | ||
45 | + "type": "RPC Request from Device" | ||
46 | + }, | ||
47 | + { | ||
48 | + "fromIndex": 1, | ||
49 | + "toIndex": 2, | ||
50 | + "type": "Success" | ||
51 | + }, | ||
52 | + { | ||
53 | + "fromIndex": 1, | ||
54 | + "toIndex": 2, | ||
55 | + "type": "Failure" | ||
56 | + } | ||
57 | + ], | ||
58 | + "ruleChainConnections": null | ||
59 | +} |
@@ -45,6 +45,24 @@ var kafkaClient; | @@ -45,6 +45,24 @@ var kafkaClient; | ||
45 | kafkaRequestTopic | 45 | kafkaRequestTopic |
46 | ); | 46 | ); |
47 | 47 | ||
48 | + consumer.on('error', (err) => { | ||
49 | + logger.error('Unexpected kafka consumer error: %s', err.message); | ||
50 | + logger.error(err.stack); | ||
51 | + }); | ||
52 | + | ||
53 | + consumer.on('offsetOutOfRange', (err) => { | ||
54 | + logger.error('Offset out of range error: %s', err.message); | ||
55 | + logger.error(err.stack); | ||
56 | + }); | ||
57 | + | ||
58 | + consumer.on('rebalancing', () => { | ||
59 | + logger.info('Rebalancing event received.'); | ||
60 | + }) | ||
61 | + | ||
62 | + consumer.on('rebalanced', () => { | ||
63 | + logger.info('Rebalanced event received.'); | ||
64 | + }); | ||
65 | + | ||
48 | var producer = new Producer(kafkaClient); | 66 | var producer = new Producer(kafkaClient); |
49 | producer.on('error', (err) => { | 67 | producer.on('error', (err) => { |
50 | logger.error('Unexpected kafka producer error: %s', err.message); | 68 | logger.error('Unexpected kafka producer error: %s', err.message); |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 18 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
19 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | 19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
20 | <modelVersion>4.0.0</modelVersion> | 20 | <modelVersion>4.0.0</modelVersion> |
21 | <parent> | 21 | <parent> |
22 | <groupId>org.thingsboard</groupId> | 22 | <groupId>org.thingsboard</groupId> |
@@ -41,6 +41,7 @@ | @@ -41,6 +41,7 @@ | ||
41 | <module>web-ui</module> | 41 | <module>web-ui</module> |
42 | <module>tb-node</module> | 42 | <module>tb-node</module> |
43 | <module>transport</module> | 43 | <module>transport</module> |
44 | + <module>black-box-tests</module> | ||
44 | </modules> | 45 | </modules> |
45 | 46 | ||
46 | <build> | 47 | <build> |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | FROM openjdk:8-jdk | 17 | FROM openjdk:8-jdk |
18 | 18 | ||
19 | -COPY logback.xml ${pkg.name}.conf start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ | 19 | +COPY start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ |
20 | 20 | ||
21 | RUN chmod a+x /tmp/*.sh \ | 21 | RUN chmod a+x /tmp/*.sh \ |
22 | && mv /tmp/start-tb-coap-transport.sh /usr/bin | 22 | && mv /tmp/start-tb-coap-transport.sh /usr/bin |
@@ -25,7 +25,4 @@ RUN dpkg -i /tmp/${pkg.name}.deb | @@ -25,7 +25,4 @@ RUN dpkg -i /tmp/${pkg.name}.deb | ||
25 | 25 | ||
26 | RUN update-rc.d ${pkg.name} disable | 26 | RUN update-rc.d ${pkg.name} disable |
27 | 27 | ||
28 | -RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \ | ||
29 | - && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf | ||
30 | - | ||
31 | CMD ["start-tb-coap-transport.sh"] | 28 | CMD ["start-tb-coap-transport.sh"] |
@@ -15,15 +15,17 @@ | @@ -15,15 +15,17 @@ | ||
15 | # limitations under the License. | 15 | # limitations under the License. |
16 | # | 16 | # |
17 | 17 | ||
18 | -CONF_FOLDER="${pkg.installFolder}/conf" | 18 | +CONF_FOLDER="/config" |
19 | jarfile=${pkg.installFolder}/bin/${pkg.name}.jar | 19 | jarfile=${pkg.installFolder}/bin/${pkg.name}.jar |
20 | configfile=${pkg.name}.conf | 20 | configfile=${pkg.name}.conf |
21 | 21 | ||
22 | source "${CONF_FOLDER}/${configfile}" | 22 | source "${CONF_FOLDER}/${configfile}" |
23 | 23 | ||
24 | +export LOADER_PATH=/config,${LOADER_PATH} | ||
25 | + | ||
24 | echo "Starting '${project.name}' ..." | 26 | echo "Starting '${project.name}' ..." |
25 | 27 | ||
26 | exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \ | 28 | exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \ |
27 | -Dspring.jpa.hibernate.ddl-auto=none \ | 29 | -Dspring.jpa.hibernate.ddl-auto=none \ |
28 | - -Dlogging.config=${CONF_FOLDER}/logback.xml \ | 30 | + -Dlogging.config=/config/logback.xml \ |
29 | org.springframework.boot.loader.PropertiesLauncher | 31 | org.springframework.boot.loader.PropertiesLauncher |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | FROM openjdk:8-jdk | 17 | FROM openjdk:8-jdk |
18 | 18 | ||
19 | -COPY logback.xml ${pkg.name}.conf start-tb-http-transport.sh ${pkg.name}.deb /tmp/ | 19 | +COPY start-tb-http-transport.sh ${pkg.name}.deb /tmp/ |
20 | 20 | ||
21 | RUN chmod a+x /tmp/*.sh \ | 21 | RUN chmod a+x /tmp/*.sh \ |
22 | && mv /tmp/start-tb-http-transport.sh /usr/bin | 22 | && mv /tmp/start-tb-http-transport.sh /usr/bin |
@@ -25,7 +25,4 @@ RUN dpkg -i /tmp/${pkg.name}.deb | @@ -25,7 +25,4 @@ RUN dpkg -i /tmp/${pkg.name}.deb | ||
25 | 25 | ||
26 | RUN update-rc.d ${pkg.name} disable | 26 | RUN update-rc.d ${pkg.name} disable |
27 | 27 | ||
28 | -RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \ | ||
29 | - && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf | ||
30 | - | ||
31 | CMD ["start-tb-http-transport.sh"] | 28 | CMD ["start-tb-http-transport.sh"] |
@@ -15,15 +15,17 @@ | @@ -15,15 +15,17 @@ | ||
15 | # limitations under the License. | 15 | # limitations under the License. |
16 | # | 16 | # |
17 | 17 | ||
18 | -CONF_FOLDER="${pkg.installFolder}/conf" | 18 | +CONF_FOLDER="/config" |
19 | jarfile=${pkg.installFolder}/bin/${pkg.name}.jar | 19 | jarfile=${pkg.installFolder}/bin/${pkg.name}.jar |
20 | configfile=${pkg.name}.conf | 20 | configfile=${pkg.name}.conf |
21 | 21 | ||
22 | source "${CONF_FOLDER}/${configfile}" | 22 | source "${CONF_FOLDER}/${configfile}" |
23 | 23 | ||
24 | +export LOADER_PATH=/config,${LOADER_PATH} | ||
25 | + | ||
24 | echo "Starting '${project.name}' ..." | 26 | echo "Starting '${project.name}' ..." |
25 | 27 | ||
26 | exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \ | 28 | exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \ |
27 | -Dspring.jpa.hibernate.ddl-auto=none \ | 29 | -Dspring.jpa.hibernate.ddl-auto=none \ |
28 | - -Dlogging.config=${CONF_FOLDER}/logback.xml \ | 30 | + -Dlogging.config=/config/logback.xml \ |
29 | org.springframework.boot.loader.PropertiesLauncher | 31 | org.springframework.boot.loader.PropertiesLauncher |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | FROM openjdk:8-jdk | 17 | FROM openjdk:8-jdk |
18 | 18 | ||
19 | -COPY logback.xml ${pkg.name}.conf start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ | 19 | +COPY start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ |
20 | 20 | ||
21 | RUN chmod a+x /tmp/*.sh \ | 21 | RUN chmod a+x /tmp/*.sh \ |
22 | && mv /tmp/start-tb-mqtt-transport.sh /usr/bin | 22 | && mv /tmp/start-tb-mqtt-transport.sh /usr/bin |
@@ -25,7 +25,4 @@ RUN dpkg -i /tmp/${pkg.name}.deb | @@ -25,7 +25,4 @@ RUN dpkg -i /tmp/${pkg.name}.deb | ||
25 | 25 | ||
26 | RUN update-rc.d ${pkg.name} disable | 26 | RUN update-rc.d ${pkg.name} disable |
27 | 27 | ||
28 | -RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \ | ||
29 | - && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf | ||
30 | - | ||
31 | CMD ["start-tb-mqtt-transport.sh"] | 28 | CMD ["start-tb-mqtt-transport.sh"] |
@@ -15,15 +15,17 @@ | @@ -15,15 +15,17 @@ | ||
15 | # limitations under the License. | 15 | # limitations under the License. |
16 | # | 16 | # |
17 | 17 | ||
18 | -CONF_FOLDER="${pkg.installFolder}/conf" | 18 | +CONF_FOLDER="/config" |
19 | jarfile=${pkg.installFolder}/bin/${pkg.name}.jar | 19 | jarfile=${pkg.installFolder}/bin/${pkg.name}.jar |
20 | configfile=${pkg.name}.conf | 20 | configfile=${pkg.name}.conf |
21 | 21 | ||
22 | source "${CONF_FOLDER}/${configfile}" | 22 | source "${CONF_FOLDER}/${configfile}" |
23 | 23 | ||
24 | +export LOADER_PATH=/config,${LOADER_PATH} | ||
25 | + | ||
24 | echo "Starting '${project.name}' ..." | 26 | echo "Starting '${project.name}' ..." |
25 | 27 | ||
26 | exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \ | 28 | exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \ |
27 | -Dspring.jpa.hibernate.ddl-auto=none \ | 29 | -Dspring.jpa.hibernate.ddl-auto=none \ |
28 | - -Dlogging.config=${CONF_FOLDER}/logback.xml \ | 30 | + -Dlogging.config=/config/logback.xml \ |
29 | org.springframework.boot.loader.PropertiesLauncher | 31 | org.springframework.boot.loader.PropertiesLauncher |
@@ -20,6 +20,7 @@ server: | @@ -20,6 +20,7 @@ server: | ||
20 | # Server bind port | 20 | # Server bind port |
21 | port: "HTTP_BIND_PORT" | 21 | port: "HTTP_BIND_PORT" |
22 | thingsboard: | 22 | thingsboard: |
23 | + enableProxy: "TB_ENABLE_PROXY" | ||
23 | # ThingsBoard node host | 24 | # ThingsBoard node host |
24 | host: "TB_HOST" | 25 | host: "TB_HOST" |
25 | # ThingsBoard node port | 26 | # ThingsBoard node port |
@@ -20,6 +20,7 @@ server: | @@ -20,6 +20,7 @@ server: | ||
20 | # Server bind port | 20 | # Server bind port |
21 | port: "8090" | 21 | port: "8090" |
22 | thingsboard: | 22 | thingsboard: |
23 | + enableProxy: "false" | ||
23 | # ThingsBoard node host | 24 | # ThingsBoard node host |
24 | host: "localhost" | 25 | host: "localhost" |
25 | # ThingsBoard node port | 26 | # ThingsBoard node port |
@@ -31,14 +31,19 @@ var server; | @@ -31,14 +31,19 @@ var server; | ||
31 | const bindAddress = config.get('server.address'); | 31 | const bindAddress = config.get('server.address'); |
32 | const bindPort = config.get('server.port'); | 32 | const bindPort = config.get('server.port'); |
33 | 33 | ||
34 | + const thingsboardEnableProxy = config.get('thingsboard.enableProxy'); | ||
35 | + | ||
34 | const thingsboardHost = config.get('thingsboard.host'); | 36 | const thingsboardHost = config.get('thingsboard.host'); |
35 | const thingsboardPort = config.get('thingsboard.port'); | 37 | const thingsboardPort = config.get('thingsboard.port'); |
36 | 38 | ||
37 | logger.info('Bind address: %s', bindAddress); | 39 | logger.info('Bind address: %s', bindAddress); |
38 | logger.info('Bind port: %s', bindPort); | 40 | logger.info('Bind port: %s', bindPort); |
41 | + logger.info('ThingsBoard Enable Proxy: %s', thingsboardEnableProxy); | ||
39 | logger.info('ThingsBoard host: %s', thingsboardHost); | 42 | logger.info('ThingsBoard host: %s', thingsboardHost); |
40 | logger.info('ThingsBoard port: %s', thingsboardPort); | 43 | logger.info('ThingsBoard port: %s', thingsboardPort); |
41 | 44 | ||
45 | + const useApiProxy = thingsboardEnableProxy === "true"; | ||
46 | + | ||
42 | var webDir = path.join(__dirname, 'web'); | 47 | var webDir = path.join(__dirname, 'web'); |
43 | 48 | ||
44 | if (typeof process.env.WEB_FOLDER === 'string') { | 49 | if (typeof process.env.WEB_FOLDER === 'string') { |
@@ -49,47 +54,51 @@ var server; | @@ -49,47 +54,51 @@ var server; | ||
49 | const app = express(); | 54 | const app = express(); |
50 | server = http.createServer(app); | 55 | server = http.createServer(app); |
51 | 56 | ||
52 | - const apiProxy = httpProxy.createProxyServer({ | ||
53 | - target: { | ||
54 | - host: thingsboardHost, | ||
55 | - port: thingsboardPort | ||
56 | - } | ||
57 | - }); | ||
58 | - | ||
59 | - apiProxy.on('error', function (err, req, res) { | ||
60 | - logger.warn('API proxy error: %s', err.message); | ||
61 | - res.writeHead(500); | ||
62 | - if (err.code && err.code === 'ECONNREFUSED') { | ||
63 | - res.end('Unable to connect to ThingsBoard server.'); | ||
64 | - } else { | ||
65 | - res.end('Thingsboard server connection error: ' + err.code ? err.code : ''); | ||
66 | - } | ||
67 | - }); | ||
68 | - | ||
69 | - const root = path.join(webDir, 'public'); | ||
70 | - | ||
71 | - const staticDir = path.join(root, 'static'); | 57 | + if (useApiProxy) { |
58 | + const apiProxy = httpProxy.createProxyServer({ | ||
59 | + target: { | ||
60 | + host: thingsboardHost, | ||
61 | + port: thingsboardPort | ||
62 | + } | ||
63 | + }); | ||
64 | + | ||
65 | + apiProxy.on('error', function (err, req, res) { | ||
66 | + logger.warn('API proxy error: %s', err.message); | ||
67 | + res.writeHead(500); | ||
68 | + if (err.code && err.code === 'ECONNREFUSED') { | ||
69 | + res.end('Unable to connect to ThingsBoard server.'); | ||
70 | + } else { | ||
71 | + res.end('Thingsboard server connection error: ' + err.code ? err.code : ''); | ||
72 | + } | ||
73 | + }); | ||
74 | + } | ||
72 | 75 | ||
73 | - app.all('/api/*', (req, res) => { | ||
74 | - logger.debug(req.method + ' ' + req.originalUrl); | ||
75 | - apiProxy.web(req, res); | ||
76 | - }); | 76 | + if (useApiProxy) { |
77 | + app.all('/api/*', (req, res) => { | ||
78 | + logger.debug(req.method + ' ' + req.originalUrl); | ||
79 | + apiProxy.web(req, res); | ||
80 | + }); | ||
77 | 81 | ||
78 | - app.all('/static/rulenode/*', (req, res) => { | ||
79 | - apiProxy.web(req, res); | ||
80 | - }); | 82 | + app.all('/static/rulenode/*', (req, res) => { |
83 | + apiProxy.web(req, res); | ||
84 | + }); | ||
85 | + } | ||
81 | 86 | ||
82 | app.use(historyApiFallback()); | 87 | app.use(historyApiFallback()); |
83 | 88 | ||
84 | - app.use('/static', express.static(staticDir)); | 89 | + const root = path.join(webDir, 'public'); |
85 | 90 | ||
86 | - app.get('*', (req, res) => { | ||
87 | - apiProxy.web(req, res); | ||
88 | - }); | 91 | + app.use(express.static(root)); |
89 | 92 | ||
90 | - server.on('upgrade', (req, socket, head) => { | ||
91 | - apiProxy.ws(req, socket, head); | ||
92 | - }); | 93 | + if (useApiProxy) { |
94 | + app.get('*', (req, res) => { | ||
95 | + apiProxy.web(req, res); | ||
96 | + }); | ||
97 | + | ||
98 | + server.on('upgrade', (req, socket, head) => { | ||
99 | + apiProxy.ws(req, socket, head); | ||
100 | + }); | ||
101 | + } | ||
93 | 102 | ||
94 | server.listen(bindPort, bindAddress, (error) => { | 103 | server.listen(bindPort, bindAddress, (error) => { |
95 | if (error) { | 104 | if (error) { |
@@ -118,16 +118,16 @@ public class TbRestApiCallNode implements TbNode { | @@ -118,16 +118,16 @@ public class TbRestApiCallNode implements TbNode { | ||
118 | } | 118 | } |
119 | 119 | ||
120 | private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) { | 120 | private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) { |
121 | - TbMsgMetaData metaData = new TbMsgMetaData(); | 121 | + TbMsgMetaData metaData = origMsg.getMetaData(); |
122 | metaData.putValue(STATUS, response.getStatusCode().name()); | 122 | metaData.putValue(STATUS, response.getStatusCode().name()); |
123 | metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); | 123 | metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); |
124 | metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); | 124 | metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); |
125 | - response.getHeaders().toSingleValueMap().forEach((k,v) -> metaData.putValue(k,v) ); | 125 | + response.getHeaders().toSingleValueMap().forEach(metaData::putValue); |
126 | return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); | 126 | return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); |
127 | } | 127 | } |
128 | 128 | ||
129 | private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) { | 129 | private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) { |
130 | - TbMsgMetaData metaData = origMsg.getMetaData().copy(); | 130 | + TbMsgMetaData metaData = origMsg.getMetaData(); |
131 | metaData.putValue(STATUS, response.getStatusCode().name()); | 131 | metaData.putValue(STATUS, response.getStatusCode().name()); |
132 | metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); | 132 | metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); |
133 | metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); | 133 | metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); |
@@ -136,7 +136,7 @@ public class TbRestApiCallNode implements TbNode { | @@ -136,7 +136,7 @@ public class TbRestApiCallNode implements TbNode { | ||
136 | } | 136 | } |
137 | 137 | ||
138 | private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { | 138 | private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { |
139 | - TbMsgMetaData metaData = origMsg.getMetaData().copy(); | 139 | + TbMsgMetaData metaData = origMsg.getMetaData(); |
140 | metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); | 140 | metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); |
141 | if (e instanceof HttpClientErrorException) { | 141 | if (e instanceof HttpClientErrorException) { |
142 | HttpClientErrorException httpClientErrorException = (HttpClientErrorException)e; | 142 | HttpClientErrorException httpClientErrorException = (HttpClientErrorException)e; |
@@ -30,6 +30,9 @@ transport: | @@ -30,6 +30,9 @@ transport: | ||
30 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" | 30 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" |
31 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" | 31 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" |
32 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" | 32 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" |
33 | + json: | ||
34 | + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON | ||
35 | + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" | ||
33 | 36 | ||
34 | kafka: | 37 | kafka: |
35 | enabled: true | 38 | enabled: true |
@@ -31,6 +31,9 @@ transport: | @@ -31,6 +31,9 @@ transport: | ||
31 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" | 31 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" |
32 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" | 32 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" |
33 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" | 33 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" |
34 | + json: | ||
35 | + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON | ||
36 | + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" | ||
34 | 37 | ||
35 | kafka: | 38 | kafka: |
36 | enabled: true | 39 | enabled: true |
@@ -50,6 +50,9 @@ transport: | @@ -50,6 +50,9 @@ transport: | ||
50 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" | 50 | enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" |
51 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" | 51 | tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" |
52 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" | 52 | device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" |
53 | + json: | ||
54 | + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON | ||
55 | + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" | ||
53 | 56 | ||
54 | kafka: | 57 | kafka: |
55 | enabled: true | 58 | enabled: true |