committed by
GitHub
492 changed files with 17777 additions and 8009 deletions
@ -1,83 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSession; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; |
|||
import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Slf4j |
|||
public class BasicRpcSessionListener implements GrpcSessionListener { |
|||
|
|||
private final ClusterRpcCallbackExecutorService callbackExecutorService; |
|||
private final ActorService service; |
|||
private final ActorRef manager; |
|||
private final ActorRef self; |
|||
|
|||
BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) { |
|||
this.service = context.getActorService(); |
|||
this.callbackExecutorService = context.getClusterRpcCallbackExecutor(); |
|||
this.manager = manager; |
|||
this.self = self; |
|||
} |
|||
|
|||
@Override |
|||
public void onConnected(GrpcSession session) { |
|||
log.info("[{}][{}] session started", session.getRemoteServer(), getType(session)); |
|||
if (!session.isClient()) { |
|||
manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDisconnected(GrpcSession session) { |
|||
log.info("[{}][{}] session closed", session.getRemoteServer(), getType(session)); |
|||
manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self); |
|||
} |
|||
|
|||
@Override |
|||
public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) { |
|||
log.trace("Received session actor msg from [{}][{}]: {}", session.getRemoteServer(), getType(session), clusterMessage); |
|||
callbackExecutorService.execute(() -> { |
|||
try { |
|||
service.onReceivedMsg(session.getRemoteServer(), clusterMessage); |
|||
} catch (Exception e) { |
|||
log.debug("[{}][{}] Failed to process cluster message: {}", session.getRemoteServer(), getType(session), clusterMessage, e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(GrpcSession session, Throwable t) { |
|||
log.warn("[{}][{}] session got error -> {}", session.getRemoteServer(), getType(session), t); |
|||
manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self); |
|||
session.close(); |
|||
} |
|||
|
|||
private static String getType(GrpcSession session) { |
|||
return session.isClient() ? "Client" : "Server"; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -1,230 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.actor.OneForOneStrategy; |
|||
import akka.actor.Props; |
|||
import akka.actor.SupervisorStrategy; |
|||
import akka.event.Logging; |
|||
import akka.event.LoggingAdapter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ServerType; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
import scala.concurrent.duration.Duration; |
|||
|
|||
import java.util.*; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class RpcManagerActor extends ContextAwareActor { |
|||
|
|||
private final Map<ServerAddress, SessionActorInfo> sessionActors; |
|||
private final Map<ServerAddress, Queue<ClusterAPIProtos.ClusterMessage>> pendingMsgs; |
|||
private final ServerAddress instance; |
|||
|
|||
private RpcManagerActor(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
this.sessionActors = new HashMap<>(); |
|||
this.pendingMsgs = new HashMap<>(); |
|||
this.instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress(); |
|||
|
|||
systemContext.getDiscoveryService().getOtherServers().stream() |
|||
.filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0) |
|||
.forEach(otherServer -> onCreateSessionRequest( |
|||
new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null))); |
|||
} |
|||
|
|||
@Override |
|||
protected boolean process(TbActorMsg msg) { |
|||
//TODO Move everything here, to work with TbActorMsg
|
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) { |
|||
if (msg instanceof ClusterAPIProtos.ClusterMessage) { |
|||
onMsg((ClusterAPIProtos.ClusterMessage) msg); |
|||
} else if (msg instanceof RpcBroadcastMsg) { |
|||
onMsg((RpcBroadcastMsg) msg); |
|||
} else if (msg instanceof RpcSessionCreateRequestMsg) { |
|||
onCreateSessionRequest((RpcSessionCreateRequestMsg) msg); |
|||
} else if (msg instanceof RpcSessionConnectedMsg) { |
|||
onSessionConnected((RpcSessionConnectedMsg) msg); |
|||
} else if (msg instanceof RpcSessionDisconnectedMsg) { |
|||
onSessionDisconnected((RpcSessionDisconnectedMsg) msg); |
|||
} else if (msg instanceof RpcSessionClosedMsg) { |
|||
onSessionClosed((RpcSessionClosedMsg) msg); |
|||
} else if (msg instanceof ClusterEventMsg) { |
|||
onClusterEvent((ClusterEventMsg) msg); |
|||
} |
|||
} |
|||
|
|||
private void onMsg(RpcBroadcastMsg msg) { |
|||
log.debug("Forwarding msg to session actors {}", msg); |
|||
sessionActors.keySet().forEach(address -> { |
|||
ClusterAPIProtos.ClusterMessage msgWithServerAddress = msg.getMsg() |
|||
.toBuilder() |
|||
.setServerAddress(ClusterAPIProtos.ServerAddress |
|||
.newBuilder() |
|||
.setHost(address.getHost()) |
|||
.setPort(address.getPort()) |
|||
.build()) |
|||
.build(); |
|||
onMsg(msgWithServerAddress); |
|||
}); |
|||
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg())); |
|||
} |
|||
|
|||
private void onMsg(ClusterAPIProtos.ClusterMessage msg) { |
|||
if (msg.hasServerAddress()) { |
|||
ServerAddress address = new ServerAddress(msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE); |
|||
SessionActorInfo session = sessionActors.get(address); |
|||
if (session != null) { |
|||
log.debug("{} Forwarding msg to session actor: {}", address, msg); |
|||
session.getActor().tell(msg, ActorRef.noSender()); |
|||
} else { |
|||
log.debug("{} Storing msg to pending queue: {}", address, msg); |
|||
Queue<ClusterAPIProtos.ClusterMessage> queue = pendingMsgs.get(address); |
|||
if (queue == null) { |
|||
queue = new LinkedList<>(); |
|||
pendingMsgs.put(new ServerAddress( |
|||
msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE), queue); |
|||
} |
|||
queue.add(msg); |
|||
} |
|||
} else { |
|||
log.warn("Cluster msg doesn't have server address [{}]", msg); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void postStop() { |
|||
sessionActors.clear(); |
|||
pendingMsgs.clear(); |
|||
} |
|||
|
|||
private void onClusterEvent(ClusterEventMsg msg) { |
|||
ServerAddress server = msg.getServerAddress(); |
|||
if (server.compareTo(instance) > 0) { |
|||
if (msg.isAdded()) { |
|||
onCreateSessionRequest(new RpcSessionCreateRequestMsg(UUID.randomUUID(), server, null)); |
|||
} else { |
|||
onSessionClose(false, server); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void onSessionConnected(RpcSessionConnectedMsg msg) { |
|||
register(msg.getRemoteAddress(), msg.getId(), context().sender()); |
|||
} |
|||
|
|||
private void onSessionDisconnected(RpcSessionDisconnectedMsg msg) { |
|||
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress()); |
|||
onSessionClose(reconnect, msg.getRemoteAddress()); |
|||
} |
|||
|
|||
private void onSessionClosed(RpcSessionClosedMsg msg) { |
|||
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress()); |
|||
onSessionClose(reconnect, msg.getRemoteAddress()); |
|||
} |
|||
|
|||
private boolean isRegistered(ServerAddress address) { |
|||
for (ServerInstance server : systemContext.getDiscoveryService().getOtherServers()) { |
|||
if (server.getServerAddress().equals(address)) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) { |
|||
log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect); |
|||
SessionActorInfo sessionRef = sessionActors.get(remoteAddress); |
|||
if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) { |
|||
context().stop(sessionRef.actor); |
|||
sessionActors.remove(remoteAddress); |
|||
pendingMsgs.remove(remoteAddress); |
|||
if (reconnect) { |
|||
onCreateSessionRequest(new RpcSessionCreateRequestMsg(sessionRef.sessionId, remoteAddress, null)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) { |
|||
if (msg.getRemoteAddress() != null) { |
|||
if (!sessionActors.containsKey(msg.getRemoteAddress())) { |
|||
ActorRef actorRef = createSessionActor(msg); |
|||
register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); |
|||
} |
|||
} else { |
|||
createSessionActor(msg); |
|||
} |
|||
} |
|||
|
|||
private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) { |
|||
sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender)); |
|||
log.info("[{}][{}] Registering session actor.", remoteAddress, uuid); |
|||
Queue<ClusterAPIProtos.ClusterMessage> data = pendingMsgs.remove(remoteAddress); |
|||
if (data != null) { |
|||
log.info("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size()); |
|||
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(msg), ActorRef.noSender())); |
|||
} else { |
|||
log.info("[{}][{}] No pending messages to forward.", remoteAddress, uuid); |
|||
} |
|||
} |
|||
|
|||
private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) { |
|||
log.info("[{}] Creating session actor.", msg.getMsgUid()); |
|||
ActorRef actor = context().actorOf( |
|||
Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())) |
|||
.withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); |
|||
actor.tell(msg, context().self()); |
|||
return actor; |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<RpcManagerActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public ActorCreator(ActorSystemContext context) { |
|||
super(context); |
|||
} |
|||
|
|||
@Override |
|||
public RpcManagerActor create() { |
|||
return new RpcManagerActor(context); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public SupervisorStrategy supervisorStrategy() { |
|||
return strategy; |
|||
} |
|||
|
|||
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> { |
|||
log.warn("Unknown failure", t); |
|||
return SupervisorStrategy.resume(); |
|||
}); |
|||
} |
|||
@ -1,135 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import io.grpc.ManagedChannel; |
|||
import io.grpc.ManagedChannelBuilder; |
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSession; |
|||
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CONNECT_RPC_MESSAGE; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Slf4j |
|||
public class RpcSessionActor extends ContextAwareActor { |
|||
|
|||
|
|||
private final UUID sessionId; |
|||
private GrpcSession session; |
|||
private GrpcSessionListener listener; |
|||
|
|||
private RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { |
|||
super(systemContext); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
@Override |
|||
protected boolean process(TbActorMsg msg) { |
|||
//TODO Move everything here, to work with TbActorMsg
|
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public void onReceive(Object msg) { |
|||
if (msg instanceof ClusterAPIProtos.ClusterMessage) { |
|||
tell((ClusterAPIProtos.ClusterMessage) msg); |
|||
} else if (msg instanceof RpcSessionCreateRequestMsg) { |
|||
initSession((RpcSessionCreateRequestMsg) msg); |
|||
} |
|||
} |
|||
|
|||
private void tell(ClusterAPIProtos.ClusterMessage msg) { |
|||
if (session != null) { |
|||
session.sendMsg(msg); |
|||
} else { |
|||
log.trace("Failed to send message due to missing session!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void postStop() { |
|||
if (session != null) { |
|||
log.info("Closing session -> {}", session.getRemoteServer()); |
|||
try { |
|||
session.close(); |
|||
} catch (RuntimeException e) { |
|||
log.trace("Failed to close session!", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void initSession(RpcSessionCreateRequestMsg msg) { |
|||
log.info("[{}] Initializing session", context().self()); |
|||
ServerAddress remoteServer = msg.getRemoteAddress(); |
|||
listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self()); |
|||
if (msg.getRemoteAddress() == null) { |
|||
// Server session
|
|||
session = new GrpcSession(listener); |
|||
session.setOutputStream(msg.getResponseObserver()); |
|||
session.initInputStream(); |
|||
session.initOutputStream(); |
|||
systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream()); |
|||
} else { |
|||
// Client session
|
|||
ManagedChannel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext().build(); |
|||
session = new GrpcSession(remoteServer, listener, channel); |
|||
session.initInputStream(); |
|||
|
|||
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel); |
|||
StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream = stub.handleMsgs(session.getInputStream()); |
|||
|
|||
session.setOutputStream(outputStream); |
|||
session.initOutputStream(); |
|||
outputStream.onNext(toConnectMsg()); |
|||
} |
|||
} |
|||
|
|||
public static class ActorCreator extends ContextBasedCreator<RpcSessionActor> { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final UUID sessionId; |
|||
|
|||
public ActorCreator(ActorSystemContext context, UUID sessionId) { |
|||
super(context); |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
@Override |
|||
public RpcSessionActor create() { |
|||
return new RpcSessionActor(context, sessionId); |
|||
} |
|||
} |
|||
|
|||
private ClusterAPIProtos.ClusterMessage toConnectMsg() { |
|||
ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress(); |
|||
return ClusterAPIProtos.ClusterMessage.newBuilder().setMessageType(CONNECT_RPC_MESSAGE).setServerAddress( |
|||
ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost()) |
|||
.setPort(instance.getPort()).build()).build(); |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.rpc; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public final class RpcSessionTellMsg { |
|||
private final ClusterAPIProtos.ClusterMessage msg; |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.ruleChain; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.MsgType; |
|||
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; |
|||
import org.thingsboard.server.common.msg.aware.TenantAwareMsg; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* Created by ashvayka on 19.03.18. |
|||
*/ |
|||
@Data |
|||
final class RemoteToRuleChainTellNextMsg extends RuleNodeToRuleChainTellNextMsg implements TenantAwareMsg, RuleChainAwareMsg { |
|||
|
|||
private static final long serialVersionUID = 2459605482321657447L; |
|||
private final TenantId tenantId; |
|||
private final RuleChainId ruleChainId; |
|||
|
|||
public RemoteToRuleChainTellNextMsg(RuleNodeToRuleChainTellNextMsg original, TenantId tenantId, RuleChainId ruleChainId) { |
|||
super(original.getOriginator(), original.getRelationTypes(), original.getMsg()); |
|||
this.tenantId = tenantId; |
|||
this.ruleChainId = ruleChainId; |
|||
} |
|||
|
|||
@Override |
|||
public MsgType getMsgType() { |
|||
return MsgType.REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG; |
|||
} |
|||
|
|||
} |
|||
@ -1,87 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Props; |
|||
import akka.actor.UntypedActor; |
|||
import akka.japi.Creator; |
|||
import com.google.common.collect.BiMap; |
|||
import com.google.common.collect.HashBiMap; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.common.data.SearchTextBased; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UUIDBased; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* Created by ashvayka on 15.03.18. |
|||
*/ |
|||
@Slf4j |
|||
public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> { |
|||
|
|||
protected final ActorSystemContext systemContext; |
|||
protected final BiMap<T, ActorRef> actors; |
|||
|
|||
public EntityActorsManager(ActorSystemContext systemContext) { |
|||
this.systemContext = systemContext; |
|||
this.actors = HashBiMap.create(); |
|||
} |
|||
|
|||
protected abstract TenantId getTenantId(); |
|||
|
|||
protected abstract String getDispatcherName(); |
|||
|
|||
protected abstract Creator<A> creator(T entityId); |
|||
|
|||
protected abstract PageDataIterable.FetchFunction<M> getFetchEntitiesFunction(); |
|||
|
|||
public void init(ActorContext context) { |
|||
for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) { |
|||
T entityId = (T) entity.getId(); |
|||
log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId()); |
|||
//TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
|
|||
ActorRef actorRef = getOrCreateActor(context, entityId); |
|||
visit(entity, actorRef); |
|||
log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId()); |
|||
} |
|||
} |
|||
|
|||
public void visit(M entity, ActorRef actorRef) { |
|||
} |
|||
|
|||
public ActorRef getOrCreateActor(ActorContext context, T entityId) { |
|||
return actors.computeIfAbsent(entityId, eId -> |
|||
context.actorOf(Props.create(creator(eId)) |
|||
.withDispatcher(getDispatcherName()), eId.toString())); |
|||
} |
|||
|
|||
public void broadcast(Object msg) { |
|||
actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|||
} |
|||
|
|||
public void remove(T id) { |
|||
actors.remove(id); |
|||
} |
|||
|
|||
} |
|||
@ -1,59 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.rulechain; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.japi.Creator; |
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.ruleChain.RuleChainActor; |
|||
import org.thingsboard.server.actors.shared.EntityActorsManager; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
|
|||
/** |
|||
* Created by ashvayka on 15.03.18. |
|||
*/ |
|||
@Slf4j |
|||
public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, RuleChainActor, RuleChain> { |
|||
|
|||
protected final RuleChainService service; |
|||
@Getter |
|||
protected RuleChain rootChain; |
|||
@Getter |
|||
protected ActorRef rootChainActor; |
|||
|
|||
public RuleChainManager(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
this.service = systemContext.getRuleChainService(); |
|||
} |
|||
|
|||
@Override |
|||
public Creator<RuleChainActor> creator(RuleChainId entityId) { |
|||
return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId); |
|||
} |
|||
|
|||
@Override |
|||
public void visit(RuleChain entity, ActorRef actorRef) { |
|||
if (entity != null && entity.isRoot()) { |
|||
rootChain = entity; |
|||
rootChainActor = actorRef; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.rulechain; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.page.TextPageData; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
|
|||
import java.util.Collections; |
|||
|
|||
public class SystemRuleChainManager extends RuleChainManager { |
|||
|
|||
public SystemRuleChainManager(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
} |
|||
|
|||
@Override |
|||
protected FetchFunction<RuleChain> getFetchEntitiesFunction() { |
|||
return link -> new TextPageData<>(Collections.emptyList(), link); |
|||
} |
|||
|
|||
@Override |
|||
protected TenantId getTenantId() { |
|||
return ModelConstants.SYSTEM_TENANT; |
|||
} |
|||
|
|||
@Override |
|||
protected String getDispatcherName() { |
|||
return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME; |
|||
} |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.actors.shared.rulechain; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
|
|||
public class TenantRuleChainManager extends RuleChainManager { |
|||
|
|||
private final TenantId tenantId; |
|||
|
|||
public TenantRuleChainManager(ActorSystemContext systemContext, TenantId tenantId) { |
|||
super(systemContext); |
|||
this.tenantId = tenantId; |
|||
} |
|||
|
|||
@Override |
|||
public void init(ActorContext context) { |
|||
super.init(context); |
|||
} |
|||
|
|||
@Override |
|||
protected TenantId getTenantId() { |
|||
return tenantId; |
|||
} |
|||
|
|||
@Override |
|||
protected String getDispatcherName() { |
|||
return DefaultActorService.TENANT_RULE_DISPATCHER_NAME; |
|||
} |
|||
|
|||
@Override |
|||
protected FetchFunction<RuleChain> getFetchEntitiesFunction() { |
|||
return link -> service.findTenantRuleChains(tenantId, link); |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
public class QueueController extends BaseController { |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public List<String> getTenantQueuesByServiceType(@RequestParam String serviceType) throws ThingsboardException { |
|||
checkParameter("serviceType", serviceType); |
|||
try { |
|||
ServiceType type = ServiceType.valueOf(serviceType); |
|||
switch (type) { |
|||
case TB_RULE_ENGINE: |
|||
return Arrays.asList("HighPriority", "Main"); |
|||
default: |
|||
return Collections.emptyList(); |
|||
} |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -1,55 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.Assert; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ServerType; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
|
|||
import static org.thingsboard.server.utils.MiscUtils.missingProperty; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class CurrentServerInstanceService implements ServerInstanceService { |
|||
|
|||
@Value("${rpc.bind_host}") |
|||
private String rpcHost; |
|||
@Value("${rpc.bind_port}") |
|||
private Integer rpcPort; |
|||
|
|||
private ServerInstance self; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
Assert.hasLength(rpcHost, missingProperty("rpc.bind_host")); |
|||
Assert.notNull(rpcPort, missingProperty("rpc.bind_port")); |
|||
self = new ServerInstance(new ServerAddress(rpcHost, rpcPort, ServerType.CORE)); |
|||
log.info("Current server instance: [{};{}]", self.getHost(), self.getPort()); |
|||
} |
|||
|
|||
@Override |
|||
public ServerInstance getSelf() { |
|||
return self; |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.discovery; |
|||
|
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@ToString |
|||
@EqualsAndHashCode(exclude = {"serverInfo", "serverAddress"}) |
|||
public final class ServerInstance implements Comparable<ServerInstance> { |
|||
|
|||
@Getter |
|||
private final String host; |
|||
@Getter |
|||
private final int port; |
|||
@Getter |
|||
private final ServerAddress serverAddress; |
|||
|
|||
public ServerInstance(ServerAddress serverAddress) { |
|||
this.serverAddress = serverAddress; |
|||
this.host = serverAddress.getHost(); |
|||
this.port = serverAddress.getPort(); |
|||
} |
|||
|
|||
@Override |
|||
public int compareTo(ServerInstance o) { |
|||
return this.serverAddress.compareTo(o.serverAddress); |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.routing; |
|||
|
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ServerType; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; |
|||
|
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ClusterRoutingService extends DiscoveryServiceListener { |
|||
|
|||
ServerAddress getCurrentServer(); |
|||
|
|||
Optional<ServerAddress> resolveById(EntityId entityId); |
|||
|
|||
} |
|||
@ -1,153 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.routing; |
|||
|
|||
import com.google.common.hash.HashCode; |
|||
import com.google.common.hash.HashFunction; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.Assert; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ServerType; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
import org.thingsboard.server.utils.MiscUtils; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.Arrays; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentNavigableMap; |
|||
import java.util.concurrent.ConcurrentSkipListMap; |
|||
|
|||
/** |
|||
* Cluster service implementation based on consistent hash ring |
|||
*/ |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class ConsistentClusterRoutingService implements ClusterRoutingService { |
|||
|
|||
@Autowired |
|||
private DiscoveryService discoveryService; |
|||
|
|||
@Value("${cluster.hash_function_name}") |
|||
private String hashFunctionName; |
|||
@Value("${cluster.vitrual_nodes_size}") |
|||
private Integer virtualNodesSize; |
|||
|
|||
private ServerInstance currentServer; |
|||
|
|||
private HashFunction hashFunction; |
|||
|
|||
private ConsistentHashCircle[] circles; |
|||
private ConsistentHashCircle rootCircle; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
log.info("Initializing Cluster routing service!"); |
|||
this.hashFunction = MiscUtils.forName(hashFunctionName); |
|||
this.currentServer = discoveryService.getCurrentServer(); |
|||
this.circles = new ConsistentHashCircle[ServerType.values().length]; |
|||
for (ServerType serverType : ServerType.values()) { |
|||
circles[serverType.ordinal()] = new ConsistentHashCircle(); |
|||
} |
|||
rootCircle = circles[ServerType.CORE.ordinal()]; |
|||
addNode(discoveryService.getCurrentServer()); |
|||
for (ServerInstance instance : discoveryService.getOtherServers()) { |
|||
addNode(instance); |
|||
} |
|||
logCircle(); |
|||
log.info("Cluster routing service initialized!"); |
|||
} |
|||
|
|||
@Override |
|||
public ServerAddress getCurrentServer() { |
|||
return discoveryService.getCurrentServer().getServerAddress(); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<ServerAddress> resolveById(EntityId entityId) { |
|||
return resolveByUuid(rootCircle, entityId.getId()); |
|||
} |
|||
|
|||
private Optional<ServerAddress> resolveByUuid(ConsistentHashCircle circle, UUID uuid) { |
|||
Assert.notNull(uuid); |
|||
if (circle.isEmpty()) { |
|||
return Optional.empty(); |
|||
} |
|||
Long hash = hashFunction.newHasher().putLong(uuid.getMostSignificantBits()) |
|||
.putLong(uuid.getLeastSignificantBits()).hash().asLong(); |
|||
if (!circle.containsKey(hash)) { |
|||
ConcurrentNavigableMap<Long, ServerInstance> tailMap = |
|||
circle.tailMap(hash); |
|||
hash = tailMap.isEmpty() ? |
|||
circle.firstKey() : tailMap.firstKey(); |
|||
} |
|||
ServerInstance result = circle.get(hash); |
|||
if (!currentServer.equals(result)) { |
|||
return Optional.of(result.getServerAddress()); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onServerAdded(ServerInstance server) { |
|||
log.info("On server added event: {}", server); |
|||
addNode(server); |
|||
logCircle(); |
|||
} |
|||
|
|||
@Override |
|||
public void onServerUpdated(ServerInstance server) { |
|||
log.debug("Ignoring server onUpdate event: {}", server); |
|||
} |
|||
|
|||
@Override |
|||
public void onServerRemoved(ServerInstance server) { |
|||
log.info("On server removed event: {}", server); |
|||
removeNode(server); |
|||
logCircle(); |
|||
} |
|||
|
|||
private void addNode(ServerInstance instance) { |
|||
for (int i = 0; i < virtualNodesSize; i++) { |
|||
circles[instance.getServerAddress().getServerType().ordinal()].put(hash(instance, i).asLong(), instance); |
|||
} |
|||
} |
|||
|
|||
private void removeNode(ServerInstance instance) { |
|||
for (int i = 0; i < virtualNodesSize; i++) { |
|||
circles[instance.getServerAddress().getServerType().ordinal()].remove(hash(instance, i).asLong()); |
|||
} |
|||
} |
|||
|
|||
private HashCode hash(ServerInstance instance, int i) { |
|||
return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash(); |
|||
} |
|||
|
|||
private void logCircle() { |
|||
log.trace("Consistent Hash Circle Start"); |
|||
Arrays.asList(circles).forEach(ConsistentHashCircle::log); |
|||
log.trace("Consistent Hash Circle End"); |
|||
} |
|||
|
|||
} |
|||
@ -1,161 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import com.google.protobuf.ByteString; |
|||
import io.grpc.Server; |
|||
import io.grpc.ServerBuilder; |
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstanceService; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import java.io.IOException; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ArrayBlockingQueue; |
|||
import java.util.concurrent.BlockingQueue; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceImplBase implements ClusterRpcService { |
|||
|
|||
@Autowired |
|||
private ServerInstanceService instanceService; |
|||
|
|||
@Autowired |
|||
private DataDecodingEncodingService encodingService; |
|||
|
|||
private RpcMsgListener listener; |
|||
|
|||
private Server server; |
|||
|
|||
private ServerInstance instance; |
|||
|
|||
private ConcurrentMap<UUID, BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>>> pendingSessionMap = |
|||
new ConcurrentHashMap<>(); |
|||
|
|||
public void init(RpcMsgListener listener) { |
|||
this.listener = listener; |
|||
log.info("Initializing RPC service!"); |
|||
instance = instanceService.getSelf(); |
|||
server = ServerBuilder.forPort(instance.getPort()).addService(this).build(); |
|||
log.info("Going to start RPC server using port: {}", instance.getPort()); |
|||
try { |
|||
server.start(); |
|||
} catch (IOException e) { |
|||
log.error("Failed to start RPC server!", e); |
|||
throw new RuntimeException("Failed to start RPC server!"); |
|||
} |
|||
log.info("RPC service initialized!"); |
|||
} |
|||
|
|||
@Override |
|||
public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream) { |
|||
BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = pendingSessionMap.remove(msgUid); |
|||
if (queue != null) { |
|||
try { |
|||
queue.put(inputStream); |
|||
} catch (InterruptedException e) { |
|||
log.warn("Failed to report created session!"); |
|||
Thread.currentThread().interrupt(); |
|||
} |
|||
} else { |
|||
log.warn("Failed to lookup pending session!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public StreamObserver<ClusterAPIProtos.ClusterMessage> handleMsgs( |
|||
StreamObserver<ClusterAPIProtos.ClusterMessage> responseObserver) { |
|||
log.info("Processing new session."); |
|||
return createSession(new RpcSessionCreateRequestMsg(UUID.randomUUID(), null, responseObserver)); |
|||
} |
|||
|
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
if (server != null) { |
|||
log.info("Going to onStop RPC server"); |
|||
server.shutdownNow(); |
|||
try { |
|||
server.awaitTermination(); |
|||
log.info("RPC server stopped!"); |
|||
} catch (InterruptedException e) { |
|||
log.warn("Failed to onStop RPC server!"); |
|||
Thread.currentThread().interrupt(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void broadcast(RpcBroadcastMsg msg) { |
|||
listener.onBroadcastMsg(msg); |
|||
} |
|||
|
|||
private StreamObserver<ClusterAPIProtos.ClusterMessage> createSession(RpcSessionCreateRequestMsg msg) { |
|||
BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = new ArrayBlockingQueue<>(1); |
|||
pendingSessionMap.put(msg.getMsgUid(), queue); |
|||
listener.onRpcSessionCreateRequestMsg(msg); |
|||
try { |
|||
StreamObserver<ClusterAPIProtos.ClusterMessage> observer = queue.take(); |
|||
log.info("Processed new session."); |
|||
return observer; |
|||
} catch (Exception e) { |
|||
log.info("Failed to process session.", e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ClusterAPIProtos.ClusterMessage message) { |
|||
listener.onSendMsg(message); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, TbActorMsg actorMsg) { |
|||
listener.onSendMsg(encodingService.convertToProtoDataMessage(serverAddress, actorMsg)); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data) { |
|||
ClusterAPIProtos.ClusterMessage msg = ClusterAPIProtos.ClusterMessage |
|||
.newBuilder() |
|||
.setServerAddress(ClusterAPIProtos.ServerAddress |
|||
.newBuilder() |
|||
.setHost(serverAddress.getHost()) |
|||
.setPort(serverAddress.getPort()) |
|||
.build()) |
|||
.setMessageType(msgType) |
|||
.setPayload(ByteString.copyFrom(data)).build(); |
|||
listener.onSendMsg(msg); |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import io.grpc.stub.StreamObserver; |
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ClusterRpcService { |
|||
|
|||
void init(RpcMsgListener listener); |
|||
|
|||
void broadcast(RpcBroadcastMsg msg); |
|||
|
|||
void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream); |
|||
|
|||
void tell(ClusterAPIProtos.ClusterMessage message); |
|||
|
|||
void tell(ServerAddress serverAddress, TbActorMsg actorMsg); |
|||
|
|||
void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data); |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import io.grpc.Channel; |
|||
import io.grpc.ManagedChannel; |
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ServerType; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.io.Closeable; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
@Slf4j |
|||
public final class GrpcSession implements Closeable { |
|||
private final UUID sessionId; |
|||
private final boolean client; |
|||
private final GrpcSessionListener listener; |
|||
private final ManagedChannel channel; |
|||
private StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream; |
|||
private StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream; |
|||
|
|||
private boolean connected; |
|||
private ServerAddress remoteServer; |
|||
|
|||
public GrpcSession(GrpcSessionListener listener) { |
|||
this(null, listener, null); |
|||
} |
|||
|
|||
public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener, ManagedChannel channel) { |
|||
this.sessionId = UUID.randomUUID(); |
|||
this.listener = listener; |
|||
if (remoteServer != null) { |
|||
this.client = true; |
|||
this.connected = true; |
|||
this.remoteServer = remoteServer; |
|||
} else { |
|||
this.client = false; |
|||
} |
|||
this.channel = channel; |
|||
} |
|||
|
|||
public void initInputStream() { |
|||
this.inputStream = new StreamObserver<ClusterAPIProtos.ClusterMessage>() { |
|||
@Override |
|||
public void onNext(ClusterAPIProtos.ClusterMessage clusterMessage) { |
|||
if (!connected && clusterMessage.getMessageType() == ClusterAPIProtos.MessageType.CONNECT_RPC_MESSAGE) { |
|||
connected = true; |
|||
ServerAddress rpcAddress = new ServerAddress(clusterMessage.getServerAddress().getHost(), clusterMessage.getServerAddress().getPort(), ServerType.CORE); |
|||
remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort(), ServerType.CORE); |
|||
listener.onConnected(GrpcSession.this); |
|||
} |
|||
if (connected) { |
|||
listener.onReceiveClusterGrpcMsg(GrpcSession.this, clusterMessage); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable t) { |
|||
listener.onError(GrpcSession.this, t); |
|||
} |
|||
|
|||
@Override |
|||
public void onCompleted() { |
|||
outputStream.onCompleted(); |
|||
listener.onDisconnected(GrpcSession.this); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public void initOutputStream() { |
|||
if (client) { |
|||
listener.onConnected(GrpcSession.this); |
|||
} |
|||
} |
|||
|
|||
public void sendMsg(ClusterAPIProtos.ClusterMessage msg) { |
|||
if (connected) { |
|||
try { |
|||
outputStream.onNext(msg); |
|||
} catch (Throwable t) { |
|||
try { |
|||
outputStream.onError(t); |
|||
} catch (Throwable t2) { |
|||
} |
|||
listener.onError(GrpcSession.this, t); |
|||
} |
|||
} else { |
|||
log.warn("[{}] Failed to send message due to closed session!", sessionId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void close() { |
|||
connected = false; |
|||
try { |
|||
outputStream.onCompleted(); |
|||
} catch (IllegalStateException e) { |
|||
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage()); |
|||
} |
|||
if (channel != null) { |
|||
channel.shutdownNow(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface GrpcSessionListener { |
|||
|
|||
void onConnected(GrpcSession session); |
|||
|
|||
void onDisconnected(GrpcSession session); |
|||
|
|||
void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage); |
|||
|
|||
void onError(GrpcSession session, Throwable t); |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.cluster.rpc; |
|||
|
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
|
|||
public interface RpcMsgListener { |
|||
void onReceivedMsg(ServerAddress remoteServer, ClusterAPIProtos.ClusterMessage msg); |
|||
void onSendMsg(ClusterAPIProtos.ClusterMessage msg); |
|||
void onRpcSessionCreateRequestMsg(RpcSessionCreateRequestMsg msg); |
|||
void onBroadcastMsg(RpcBroadcastMsg msg); |
|||
} |
|||
@ -0,0 +1,204 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import com.google.protobuf.ByteString; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; |
|||
import org.thingsboard.server.queue.TbQueueCallback; |
|||
import org.thingsboard.server.queue.TbQueueProducer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
|||
|
|||
import java.util.HashSet; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class DefaultTbClusterService implements TbClusterService { |
|||
|
|||
@Value("${cluster.stats.enabled:false}") |
|||
private boolean statsEnabled; |
|||
|
|||
private final AtomicInteger toCoreMsgs = new AtomicInteger(0); |
|||
private final AtomicInteger toCoreNfs = new AtomicInteger(0); |
|||
private final AtomicInteger toRuleEngineMsgs = new AtomicInteger(0); |
|||
private final AtomicInteger toRuleEngineNfs = new AtomicInteger(0); |
|||
private final AtomicInteger toTransportNfs = new AtomicInteger(0); |
|||
|
|||
private final TbQueueProducerProvider producerProvider; |
|||
private final PartitionService partitionService; |
|||
private final DataDecodingEncodingService encodingService; |
|||
|
|||
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService) { |
|||
this.producerProvider = producerProvider; |
|||
this.partitionService = partitionService; |
|||
this.encodingService = encodingService; |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); |
|||
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), callback); |
|||
toCoreMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToCore(TopicPartitionInfo tpi, UUID msgId, ToCoreMsg msg, TbQueueCallback callback) { |
|||
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(msgId, msg), callback); |
|||
toCoreMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToCore(ToDeviceActorNotificationMsg msg, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, msg.getTenantId(), msg.getDeviceId()); |
|||
log.trace("PUSHING msg: {} to:{}", msg, tpi); |
|||
byte[] msgBytes = encodingService.encode(msg); |
|||
ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToDeviceActorNotificationMsg(ByteString.copyFrom(msgBytes)).build(); |
|||
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(msg.getDeviceId().getId(), toCoreMsg), callback); |
|||
toCoreMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToCore(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); |
|||
log.trace("PUSHING msg: {} to:{}", response, tpi); |
|||
FromDeviceRPCResponseProto.Builder builder = FromDeviceRPCResponseProto.newBuilder() |
|||
.setRequestIdMSB(response.getId().getMostSignificantBits()) |
|||
.setRequestIdLSB(response.getId().getLeastSignificantBits()) |
|||
.setError(response.getError().isPresent() ? response.getError().get().ordinal() : -1); |
|||
response.getResponse().ifPresent(builder::setResponse); |
|||
ToCoreNotificationMsg msg = ToCoreNotificationMsg.newBuilder().setFromDeviceRpcResponse(builder).build(); |
|||
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(response.getId(), msg), callback); |
|||
toCoreNfs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToRuleEngine(TopicPartitionInfo tpi, UUID msgId, ToRuleEngineMsg msg, TbQueueCallback callback) { |
|||
log.trace("PUSHING msg: {} to:{}", msg, tpi); |
|||
producerProvider.getRuleEngineMsgProducer().send(tpi, new TbProtoQueueMsg<>(msgId, msg), callback); |
|||
toRuleEngineMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { |
|||
if (tenantId.isNullUid()) { |
|||
if (entityId.getEntityType().equals(EntityType.TENANT)) { |
|||
tenantId = new TenantId(entityId.getId()); |
|||
} else { |
|||
log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg); |
|||
return; |
|||
} |
|||
} |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId); |
|||
log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); |
|||
ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder() |
|||
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) |
|||
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) |
|||
.setTbMsg(TbMsg.toByteString(tbMsg)).build(); |
|||
producerProvider.getRuleEngineMsgProducer().send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); |
|||
toRuleEngineMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToRuleEngine(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId); |
|||
log.trace("PUSHING msg: {} to:{}", response, tpi); |
|||
FromDeviceRPCResponseProto.Builder builder = FromDeviceRPCResponseProto.newBuilder() |
|||
.setRequestIdMSB(response.getId().getMostSignificantBits()) |
|||
.setRequestIdLSB(response.getId().getLeastSignificantBits()) |
|||
.setError(response.getError().isPresent() ? response.getError().get().ordinal() : -1); |
|||
response.getResponse().ifPresent(builder::setResponse); |
|||
ToRuleEngineNotificationMsg msg = ToRuleEngineNotificationMsg.newBuilder().setFromDeviceRpcResponse(builder).build(); |
|||
producerProvider.getRuleEngineNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(response.getId(), msg), callback); |
|||
toRuleEngineNfs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToTransport(String serviceId, ToTransportMsg response, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceId); |
|||
log.trace("PUSHING msg: {} to:{}", response, tpi); |
|||
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), response), callback); |
|||
toTransportNfs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { |
|||
log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state); |
|||
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state)); |
|||
} |
|||
|
|||
private void broadcast(ComponentLifecycleMsg msg) { |
|||
byte[] msgBytes = encodingService.encode(msg); |
|||
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); |
|||
Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); |
|||
if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)) { |
|||
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); |
|||
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); |
|||
for (String serviceId : tbCoreServices) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); |
|||
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setComponentLifecycleMsg(ByteString.copyFrom(msgBytes)).build(); |
|||
toCoreNfProducer.send(tpi, new TbProtoQueueMsg<>(msg.getEntityId().getId(), toCoreMsg), null); |
|||
toCoreNfs.incrementAndGet(); |
|||
} |
|||
// No need to push notifications twice
|
|||
tbRuleEngineServices.removeAll(tbCoreServices); |
|||
} |
|||
for (String serviceId : tbRuleEngineServices) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId); |
|||
ToRuleEngineNotificationMsg toRuleEngineMsg = ToRuleEngineNotificationMsg.newBuilder().setComponentLifecycleMsg(ByteString.copyFrom(msgBytes)).build(); |
|||
toRuleEngineProducer.send(tpi, new TbProtoQueueMsg<>(msg.getEntityId().getId(), toRuleEngineMsg), null); |
|||
toRuleEngineNfs.incrementAndGet(); |
|||
} |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${cluster.stats.print_interval_ms}") |
|||
public void printStats() { |
|||
if (statsEnabled) { |
|||
int toCoreMsgCnt = toCoreMsgs.getAndSet(0); |
|||
int toCoreNfsCnt = toCoreNfs.getAndSet(0); |
|||
int toRuleEngineMsgsCnt = toRuleEngineMsgs.getAndSet(0); |
|||
int toRuleEngineNfsCnt = toRuleEngineNfs.getAndSet(0); |
|||
int toTransportNfsCnt = toTransportNfs.getAndSet(0); |
|||
if (toCoreMsgCnt > 0 || toCoreNfsCnt > 0 || toRuleEngineMsgsCnt > 0 || toRuleEngineNfsCnt > 0 || toTransportNfsCnt > 0) { |
|||
log.info("To TbCore: [{}] messages [{}] notifications; To TbRuleEngine: [{}] messages [{}] notifications; To Transport: [{}] notifications", |
|||
toCoreMsgCnt, toCoreNfsCnt, toRuleEngineMsgsCnt, toRuleEngineNfsCnt, toTransportNfsCnt); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,283 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.rule.engine.api.RpcError; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.provider.TbCoreQueueFactory; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; |
|||
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
|||
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
|||
import org.thingsboard.server.service.state.DeviceStateService; |
|||
import org.thingsboard.server.service.subscription.SubscriptionManagerService; |
|||
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; |
|||
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; |
|||
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@Slf4j |
|||
public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService { |
|||
|
|||
@Value("${queue.core.poll-interval}") |
|||
private long pollDuration; |
|||
@Value("${queue.core.pack-processing-timeout}") |
|||
private long packProcessingTimeout; |
|||
@Value("${queue.core.stats.enabled:false}") |
|||
private boolean statsEnabled; |
|||
|
|||
private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer; |
|||
private final DeviceStateService stateService; |
|||
private final TbLocalSubscriptionService localSubscriptionService; |
|||
private final SubscriptionManagerService subscriptionManagerService; |
|||
private final TbCoreDeviceRpcService tbCoreDeviceRpcService; |
|||
private final TbCoreConsumerStats stats = new TbCoreConsumerStats(); |
|||
|
|||
public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext, |
|||
DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService, |
|||
SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService, |
|||
TbCoreDeviceRpcService tbCoreDeviceRpcService) { |
|||
super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); |
|||
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); |
|||
this.stateService = stateService; |
|||
this.localSubscriptionService = localSubscriptionService; |
|||
this.subscriptionManagerService = subscriptionManagerService; |
|||
this.tbCoreDeviceRpcService = tbCoreDeviceRpcService; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
super.init("tb-core-consumer", "tb-core-notifications-consumer"); |
|||
} |
|||
|
|||
@Override |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
if (partitionChangeEvent.getServiceType().equals(getServiceType())) { |
|||
log.info("Subscribing to partitions: {}", partitionChangeEvent.getPartitions()); |
|||
this.mainConsumer.subscribe(partitionChangeEvent.getPartitions()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void launchMainConsumers() { |
|||
consumersExecutor.submit(() -> { |
|||
while (!stopped) { |
|||
try { |
|||
List<TbProtoQueueMsg<ToCoreMsg>> msgs = mainConsumer.poll(pollDuration); |
|||
if (msgs.isEmpty()) { |
|||
continue; |
|||
} |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect( |
|||
Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> failedMap = new ConcurrentHashMap<>(); |
|||
CountDownLatch processingTimeoutLatch = new CountDownLatch(1); |
|||
pendingMap.forEach((id, msg) -> { |
|||
log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); |
|||
TbCallback callback = new TbPackCallback<>(id, processingTimeoutLatch, pendingMap, failedMap); |
|||
try { |
|||
ToCoreMsg toCoreMsg = msg.getValue(); |
|||
if (toCoreMsg.hasToSubscriptionMgrMsg()) { |
|||
log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); |
|||
forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); |
|||
} else if (toCoreMsg.hasToDeviceActorMsg()) { |
|||
log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); |
|||
forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); |
|||
} else if (toCoreMsg.hasDeviceStateServiceMsg()) { |
|||
log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); |
|||
forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); |
|||
} else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { |
|||
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); |
|||
if (actorMsg.isPresent()) { |
|||
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); |
|||
actorContext.tell(actorMsg.get(), ActorRef.noSender()); |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to process message: {}", id, msg, e); |
|||
callback.onFailure(e); |
|||
} |
|||
}); |
|||
if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) { |
|||
pendingMap.forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue())); |
|||
failedMap.forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); |
|||
} |
|||
mainConsumer.commit(); |
|||
} catch (Exception e) { |
|||
if (!stopped) { |
|||
log.warn("Failed to obtain messages from queue.", e); |
|||
try { |
|||
Thread.sleep(pollDuration); |
|||
} catch (InterruptedException e2) { |
|||
log.trace("Failed to wait until the server has capacity to handle new requests", e2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
log.info("TB Core Consumer stopped."); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
protected ServiceType getServiceType() { |
|||
return ServiceType.TB_CORE; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPollDuration() { |
|||
return pollDuration; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPackProcessingTimeout() { |
|||
return packProcessingTimeout; |
|||
} |
|||
|
|||
@Override |
|||
protected void handleNotification(UUID id, TbProtoQueueMsg<ToCoreNotificationMsg> msg, TbCallback callback) { |
|||
ToCoreNotificationMsg toCoreNotification = msg.getValue(); |
|||
if (toCoreNotification.hasToLocalSubscriptionServiceMsg()) { |
|||
log.trace("[{}] Forwarding message to local subscription service {}", id, toCoreNotification.getToLocalSubscriptionServiceMsg()); |
|||
forwardToLocalSubMgrService(toCoreNotification.getToLocalSubscriptionServiceMsg(), callback); |
|||
} else if (toCoreNotification.hasFromDeviceRpcResponse()) { |
|||
log.trace("[{}] Forwarding message to RPC service {}", id, toCoreNotification.getFromDeviceRpcResponse()); |
|||
forwardToCoreRpcService(toCoreNotification.getFromDeviceRpcResponse(), callback); |
|||
} else if (toCoreNotification.getComponentLifecycleMsg() != null && !toCoreNotification.getComponentLifecycleMsg().isEmpty()) { |
|||
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreNotification.getComponentLifecycleMsg().toByteArray()); |
|||
if (actorMsg.isPresent()) { |
|||
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); |
|||
actorContext.tell(actorMsg.get(), ActorRef.noSender()); |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
if (statsEnabled) { |
|||
stats.log(toCoreNotification); |
|||
} |
|||
} |
|||
|
|||
private void forwardToCoreRpcService(FromDeviceRPCResponseProto proto, TbCallback callback) { |
|||
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; |
|||
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()) |
|||
, proto.getResponse(), error); |
|||
tbCoreDeviceRpcService.processRpcResponseFromRuleEngine(response); |
|||
callback.onSuccess(); |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${queue.core.stats.print-interval-ms}") |
|||
public void printStats() { |
|||
if (statsEnabled) { |
|||
stats.printStats(); |
|||
} |
|||
} |
|||
|
|||
private void forwardToLocalSubMgrService(LocalSubscriptionServiceMsgProto msg, TbCallback callback) { |
|||
if (msg.hasSubUpdate()) { |
|||
localSubscriptionService.onSubscriptionUpdate(msg.getSubUpdate().getSessionId(), TbSubscriptionUtils.fromProto(msg.getSubUpdate()), callback); |
|||
} else { |
|||
throwNotHandled(msg, callback); |
|||
} |
|||
} |
|||
|
|||
private void forwardToSubMgrService(SubscriptionMgrMsgProto msg, TbCallback callback) { |
|||
if (msg.hasAttributeSub()) { |
|||
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getAttributeSub()), callback); |
|||
} else if (msg.hasTelemetrySub()) { |
|||
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getTelemetrySub()), callback); |
|||
} else if (msg.hasSubClose()) { |
|||
TbSubscriptionCloseProto closeProto = msg.getSubClose(); |
|||
subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback); |
|||
} else if (msg.hasTsUpdate()) { |
|||
TbTimeSeriesUpdateProto proto = msg.getTsUpdate(); |
|||
subscriptionManagerService.onTimeSeriesUpdate( |
|||
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), |
|||
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), |
|||
TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback); |
|||
} else if (msg.hasAttrUpdate()) { |
|||
TbAttributeUpdateProto proto = msg.getAttrUpdate(); |
|||
subscriptionManagerService.onAttributesUpdate( |
|||
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), |
|||
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), |
|||
proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback); |
|||
} else { |
|||
throwNotHandled(msg, callback); |
|||
} |
|||
if (statsEnabled) { |
|||
stats.log(msg); |
|||
} |
|||
} |
|||
|
|||
private void forwardToStateService(DeviceStateServiceMsgProto deviceStateServiceMsg, TbCallback callback) { |
|||
if (statsEnabled) { |
|||
stats.log(deviceStateServiceMsg); |
|||
} |
|||
stateService.onQueueMsg(deviceStateServiceMsg, callback); |
|||
} |
|||
|
|||
private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TbCallback callback) { |
|||
if (statsEnabled) { |
|||
stats.log(toDeviceActorMsg); |
|||
} |
|||
actorContext.tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback), ActorRef.noSender()); |
|||
} |
|||
|
|||
private void throwNotHandled(Object msg, TbCallback callback) { |
|||
log.warn("Message not handled: {}", msg); |
|||
callback.onFailure(new RuntimeException("Message not handled!")); |
|||
} |
|||
|
|||
@Override |
|||
protected void stopMainConsumers() { |
|||
if (mainConsumer != null) { |
|||
mainConsumer.unsubscribe(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,266 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import com.google.protobuf.ProtocolStringList; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.common.msg.queue.ServiceQueue; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.common.msg.queue.TbMsgCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; |
|||
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; |
|||
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; |
|||
import org.thingsboard.server.queue.util.TbRuleEngineComponent; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategy; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory; |
|||
import org.thingsboard.server.service.stats.RuleEngineStatisticsService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Collections; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.BiConsumer; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@TbRuleEngineComponent |
|||
@Slf4j |
|||
public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<ToRuleEngineNotificationMsg> implements TbRuleEngineConsumerService { |
|||
|
|||
@Value("${queue.rule-engine.poll-interval}") |
|||
private long pollDuration; |
|||
@Value("${queue.rule-engine.pack-processing-timeout}") |
|||
private long packProcessingTimeout; |
|||
@Value("${queue.rule-engine.stats.enabled:true}") |
|||
private boolean statsEnabled; |
|||
|
|||
private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory; |
|||
private final TbRuleEngineProcessingStrategyFactory processingStrategyFactory; |
|||
private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory; |
|||
private final TbQueueRuleEngineSettings ruleEngineSettings; |
|||
private final RuleEngineStatisticsService statisticsService; |
|||
private final ConcurrentMap<String, TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> consumers = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<String, TbRuleEngineQueueConfiguration> consumerConfigurations = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<String, TbRuleEngineConsumerStats> consumerStats = new ConcurrentHashMap<>(); |
|||
private ExecutorService submitExecutor; |
|||
|
|||
public DefaultTbRuleEngineConsumerService(TbRuleEngineProcessingStrategyFactory processingStrategyFactory, |
|||
TbRuleEngineSubmitStrategyFactory submitStrategyFactory, |
|||
TbQueueRuleEngineSettings ruleEngineSettings, |
|||
TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService, |
|||
ActorSystemContext actorContext, DataDecodingEncodingService encodingService) { |
|||
super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer()); |
|||
this.statisticsService = statisticsService; |
|||
this.ruleEngineSettings = ruleEngineSettings; |
|||
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; |
|||
this.submitStrategyFactory = submitStrategyFactory; |
|||
this.processingStrategyFactory = processingStrategyFactory; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
super.init("tb-rule-engine-consumer", "tb-rule-engine-notifications-consumer"); |
|||
for (TbRuleEngineQueueConfiguration configuration : ruleEngineSettings.getQueues()) { |
|||
consumerConfigurations.putIfAbsent(configuration.getName(), configuration); |
|||
consumers.computeIfAbsent(configuration.getName(), queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration)); |
|||
consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName())); |
|||
} |
|||
submitExecutor = Executors.newSingleThreadExecutor(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
if (submitExecutor != null) { |
|||
submitExecutor.shutdownNow(); |
|||
} |
|||
|
|||
ruleEngineSettings.getQueues().forEach(config -> consumerConfigurations.put(config.getName(), config)); |
|||
} |
|||
|
|||
@Override |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
if (partitionChangeEvent.getServiceType().equals(getServiceType())) { |
|||
ServiceQueue serviceQueue = partitionChangeEvent.getServiceQueueKey().getServiceQueue(); |
|||
log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), partitionChangeEvent.getPartitions()); |
|||
consumers.get(serviceQueue.getQueue()).subscribe(partitionChangeEvent.getPartitions()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void launchMainConsumers() { |
|||
consumers.forEach((queue, consumer) -> launchConsumer(consumer, consumerConfigurations.get(queue), consumerStats.get(queue))); |
|||
} |
|||
|
|||
@Override |
|||
protected void stopMainConsumers() { |
|||
consumers.values().forEach(TbQueueConsumer::unsubscribe); |
|||
} |
|||
|
|||
private void launchConsumer(TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer, TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats) { |
|||
consumersExecutor.execute(() -> { |
|||
while (!stopped) { |
|||
try { |
|||
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = consumer.poll(pollDuration); |
|||
if (msgs.isEmpty()) { |
|||
continue; |
|||
} |
|||
TbRuleEngineSubmitStrategy submitStrategy = submitStrategyFactory.newInstance(configuration.getName(), configuration.getSubmitStrategy()); |
|||
TbRuleEngineProcessingStrategy ackStrategy = processingStrategyFactory.newInstance(configuration.getName(), configuration.getProcessingStrategy()); |
|||
|
|||
submitStrategy.init(msgs); |
|||
|
|||
while (!stopped) { |
|||
ProcessingAttemptContext ctx = new ProcessingAttemptContext(submitStrategy); |
|||
submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> { |
|||
log.trace("[{}] Creating callback for message: {}", id, msg.getValue()); |
|||
ToRuleEngineMsg toRuleEngineMsg = msg.getValue(); |
|||
TenantId tenantId = new TenantId(new UUID(toRuleEngineMsg.getTenantIdMSB(), toRuleEngineMsg.getTenantIdLSB())); |
|||
TbMsgCallback callback = new TbMsgPackCallback(id, tenantId, ctx); |
|||
try { |
|||
if (toRuleEngineMsg.getTbMsg() != null && !toRuleEngineMsg.getTbMsg().isEmpty()) { |
|||
forwardToRuleEngineActor(tenantId, toRuleEngineMsg, callback); |
|||
} else { |
|||
callback.onSuccess(); |
|||
} |
|||
} catch (Exception e) { |
|||
callback.onFailure(new RuleEngineException(e.getMessage())); |
|||
} |
|||
})); |
|||
|
|||
boolean timeout = false; |
|||
if (!ctx.await(configuration.getPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { |
|||
timeout = true; |
|||
} |
|||
|
|||
TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(timeout, ctx); |
|||
TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result); |
|||
if (statsEnabled) { |
|||
stats.log(result, decision.isCommit()); |
|||
} |
|||
if (decision.isCommit()) { |
|||
submitStrategy.stop(); |
|||
break; |
|||
} else { |
|||
submitStrategy.update(decision.getReprocessMap()); |
|||
} |
|||
} |
|||
consumer.commit(); |
|||
} catch (Exception e) { |
|||
if (!stopped) { |
|||
log.warn("Failed to process messages from queue.", e); |
|||
try { |
|||
Thread.sleep(pollDuration); |
|||
} catch (InterruptedException e2) { |
|||
log.trace("Failed to wait until the server has capacity to handle new requests", e2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
log.info("TB Rule Engine Consumer stopped."); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
protected ServiceType getServiceType() { |
|||
return ServiceType.TB_RULE_ENGINE; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPollDuration() { |
|||
return pollDuration; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPackProcessingTimeout() { |
|||
return packProcessingTimeout; |
|||
} |
|||
|
|||
@Override |
|||
protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) throws Exception { |
|||
ToRuleEngineNotificationMsg nfMsg = msg.getValue(); |
|||
if (nfMsg.getComponentLifecycleMsg() != null && !nfMsg.getComponentLifecycleMsg().isEmpty()) { |
|||
Optional<TbActorMsg> actorMsg = encodingService.decode(nfMsg.getComponentLifecycleMsg().toByteArray()); |
|||
if (actorMsg.isPresent()) { |
|||
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); |
|||
actorContext.tell(actorMsg.get(), ActorRef.noSender()); |
|||
} |
|||
callback.onSuccess(); |
|||
} else { |
|||
callback.onSuccess(); |
|||
} |
|||
} |
|||
|
|||
private void forwardToRuleEngineActor(TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) { |
|||
TbMsg tbMsg = TbMsg.fromBytes(toRuleEngineMsg.getTbMsg().toByteArray(), callback); |
|||
QueueToRuleEngineMsg msg; |
|||
ProtocolStringList relationTypesList = toRuleEngineMsg.getRelationTypesList(); |
|||
Set<String> relationTypes = null; |
|||
if (relationTypesList != null) { |
|||
if (relationTypesList.size() == 1) { |
|||
relationTypes = Collections.singleton(relationTypesList.get(0)); |
|||
} else { |
|||
relationTypes = new HashSet<>(relationTypesList); |
|||
} |
|||
} |
|||
msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage()); |
|||
actorContext.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}") |
|||
public void printStats() { |
|||
if (statsEnabled) { |
|||
long ts = System.currentTimeMillis(); |
|||
consumerStats.forEach((queue, stats) -> { |
|||
stats.printStats(); |
|||
statisticsService.reportQueueStats(ts, stats); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
import org.thingsboard.server.queue.discovery.TenantRoutingInfo; |
|||
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core' || '${service.type:null}'=='tb-rule-engine'") |
|||
public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService { |
|||
|
|||
private final TenantService tenantService; |
|||
|
|||
public DefaultTenantRoutingInfoService(TenantService tenantService) { |
|||
this.tenantService = tenantService; |
|||
} |
|||
|
|||
@Override |
|||
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { |
|||
Tenant tenant = tenantService.findTenantById(tenantId); |
|||
if (tenant != null) { |
|||
return new TenantRoutingInfo(tenantId, tenant.isIsolatedTbCore(), tenant.isIsolatedTbRuleEngine()); |
|||
} else { |
|||
throw new RuntimeException("Tenant not found!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
public class ProcessingAttemptContext { |
|||
|
|||
private final TbRuleEngineSubmitStrategy submitStrategy; |
|||
|
|||
private final AtomicInteger pendingCount; |
|||
private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1); |
|||
@Getter |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> pendingMap; |
|||
@Getter |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> successMap = new ConcurrentHashMap<>(); |
|||
@Getter |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> failedMap = new ConcurrentHashMap<>(); |
|||
@Getter |
|||
private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>(); |
|||
|
|||
public ProcessingAttemptContext(TbRuleEngineSubmitStrategy submitStrategy) { |
|||
this.submitStrategy = submitStrategy; |
|||
this.pendingMap = submitStrategy.getPendingMap(); |
|||
this.pendingCount = new AtomicInteger(pendingMap.size()); |
|||
} |
|||
|
|||
public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { |
|||
return processingTimeoutLatch.await(packProcessingTimeout, milliseconds); |
|||
} |
|||
|
|||
public void onSuccess(UUID id) { |
|||
TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg> msg; |
|||
boolean empty = false; |
|||
msg = pendingMap.remove(id); |
|||
if (msg != null) { |
|||
empty = pendingCount.decrementAndGet() == 0; |
|||
successMap.put(id, msg); |
|||
submitStrategy.onSuccess(id); |
|||
} |
|||
if (empty) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
|
|||
public void onFailure(TenantId tenantId, UUID id, RuleEngineException e) { |
|||
TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg> msg; |
|||
boolean empty = false; |
|||
msg = pendingMap.remove(id); |
|||
if (msg != null) { |
|||
empty = pendingCount.decrementAndGet() == 0; |
|||
failedMap.put(id, msg); |
|||
exceptionsMap.putIfAbsent(tenantId, e); |
|||
} |
|||
if (empty) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; |
|||
import org.thingsboard.server.queue.TbQueueCallback; |
|||
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface TbClusterService { |
|||
|
|||
void pushMsgToCore(TopicPartitionInfo tpi, UUID msgKey, ToCoreMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushMsgToCore(ToDeviceActorNotificationMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushNotificationToCore(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); |
|||
|
|||
void pushMsgToRuleEngine(TopicPartitionInfo tpi, UUID msgId, TransportProtos.ToRuleEngineMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushNotificationToRuleEngine(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); |
|||
|
|||
void pushNotificationToTransport(String targetServiceId, ToTransportMsg response, TbQueueCallback callback); |
|||
|
|||
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); |
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import org.springframework.context.ApplicationListener; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
|
|||
public interface TbCoreConsumerService extends ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.common.msg.queue.RuleNodeException; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.common.msg.queue.TbMsgCallback; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
|
|||
@Slf4j |
|||
public class TbMsgPackCallback implements TbMsgCallback { |
|||
private final UUID id; |
|||
private final TenantId tenantId; |
|||
private final ProcessingAttemptContext ctx; |
|||
|
|||
public TbMsgPackCallback(UUID id, TenantId tenantId, ProcessingAttemptContext ctx) { |
|||
this.id = id; |
|||
this.tenantId = tenantId; |
|||
this.ctx = ctx; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess() { |
|||
log.trace("[{}] ON SUCCESS", id); |
|||
ctx.onSuccess(id); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(RuleEngineException e) { |
|||
log.trace("[{}] ON FAILURE", id, e); |
|||
ctx.onFailure(tenantId, id, e); |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
|
|||
@Slf4j |
|||
public class TbPackCallback<T> implements TbCallback { |
|||
private final CountDownLatch processingTimeoutLatch; |
|||
private final ConcurrentMap<UUID, T> ackMap; |
|||
private final ConcurrentMap<UUID, T> failedMap; |
|||
private final UUID id; |
|||
|
|||
public TbPackCallback(UUID id, |
|||
CountDownLatch processingTimeoutLatch, |
|||
ConcurrentMap<UUID, T> ackMap, |
|||
ConcurrentMap<UUID, T> failedMap) { |
|||
this.id = id; |
|||
this.processingTimeoutLatch = processingTimeoutLatch; |
|||
this.ackMap = ackMap; |
|||
this.failedMap = failedMap; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess() { |
|||
log.trace("[{}] ON SUCCESS", id); |
|||
T msg = ackMap.remove(id); |
|||
if (msg != null && ackMap.isEmpty()) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.trace("[{}] ON FAILURE", id, t); |
|||
T msg = ackMap.remove(id); |
|||
if (msg != null) { |
|||
failedMap.put(id, msg); |
|||
} |
|||
if (ackMap.isEmpty()) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import org.springframework.context.ApplicationListener; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
|
|||
public interface TbRuleEngineConsumerService extends ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class TbRuleEngineConsumerStats { |
|||
|
|||
public static final String TOTAL_MSGS = "totalMsgs"; |
|||
public static final String SUCCESSFUL_MSGS = "successfulMsgs"; |
|||
public static final String TMP_TIMEOUT = "tmpTimeout"; |
|||
public static final String TMP_FAILED = "tmpFailed"; |
|||
public static final String TIMEOUT_MSGS = "timeoutMsgs"; |
|||
public static final String FAILED_MSGS = "failedMsgs"; |
|||
public static final String SUCCESSFUL_ITERATIONS = "successfulIterations"; |
|||
public static final String FAILED_ITERATIONS = "failedIterations"; |
|||
|
|||
private final AtomicInteger totalMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger successMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpTimeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpFailedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final AtomicInteger timeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger failedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final AtomicInteger successIterationsCounter = new AtomicInteger(0); |
|||
private final AtomicInteger failedIterationsCounter = new AtomicInteger(0); |
|||
|
|||
private final Map<String, AtomicInteger> counters = new HashMap<>(); |
|||
private final ConcurrentMap<UUID, TbTenantRuleEngineStats> tenantStats = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>(); |
|||
|
|||
private final String queueName; |
|||
|
|||
public TbRuleEngineConsumerStats(String queueName) { |
|||
this.queueName = queueName; |
|||
counters.put(TOTAL_MSGS, totalMsgCounter); |
|||
counters.put(SUCCESSFUL_MSGS, successMsgCounter); |
|||
counters.put(TIMEOUT_MSGS, timeoutMsgCounter); |
|||
counters.put(FAILED_MSGS, failedMsgCounter); |
|||
|
|||
counters.put(TMP_TIMEOUT, tmpTimeoutMsgCounter); |
|||
counters.put(TMP_FAILED, tmpFailedMsgCounter); |
|||
counters.put(SUCCESSFUL_ITERATIONS, successIterationsCounter); |
|||
counters.put(FAILED_ITERATIONS, failedIterationsCounter); |
|||
} |
|||
|
|||
public void log(TbRuleEngineProcessingResult msg, boolean finalIterationForPack) { |
|||
int success = msg.getSuccessMap().size(); |
|||
int pending = msg.getPendingMap().size(); |
|||
int failed = msg.getFailedMap().size(); |
|||
totalMsgCounter.addAndGet(success + pending + failed); |
|||
successMsgCounter.addAndGet(success); |
|||
msg.getSuccessMap().values().forEach(m -> getTenantStats(m).logSuccess()); |
|||
if (finalIterationForPack) { |
|||
if (pending > 0 || failed > 0) { |
|||
timeoutMsgCounter.addAndGet(pending); |
|||
failedMsgCounter.addAndGet(failed); |
|||
if (pending > 0) { |
|||
msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTimeout()); |
|||
} |
|||
if (failed > 0) { |
|||
msg.getFailedMap().values().forEach(m -> getTenantStats(m).logFailed()); |
|||
} |
|||
failedIterationsCounter.incrementAndGet(); |
|||
} else { |
|||
successIterationsCounter.incrementAndGet(); |
|||
} |
|||
} else { |
|||
failedIterationsCounter.incrementAndGet(); |
|||
tmpTimeoutMsgCounter.addAndGet(pending); |
|||
tmpFailedMsgCounter.addAndGet(failed); |
|||
if (pending > 0) { |
|||
msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTmpTimeout()); |
|||
} |
|||
if (failed > 0) { |
|||
msg.getFailedMap().values().forEach(m -> getTenantStats(m).logTmpFailed()); |
|||
} |
|||
} |
|||
msg.getExceptionsMap().forEach(tenantExceptions::putIfAbsent); |
|||
} |
|||
|
|||
private TbTenantRuleEngineStats getTenantStats(TbProtoQueueMsg<ToRuleEngineMsg> m) { |
|||
ToRuleEngineMsg reMsg = m.getValue(); |
|||
return tenantStats.computeIfAbsent(new UUID(reMsg.getTenantIdMSB(), reMsg.getTenantIdLSB()), TbTenantRuleEngineStats::new); |
|||
} |
|||
|
|||
public void printStats() { |
|||
int total = totalMsgCounter.get(); |
|||
if (total > 0) { |
|||
StringBuilder stats = new StringBuilder(); |
|||
counters.forEach((label, value) -> { |
|||
stats.append(label).append(" = [").append(value.get()).append("] "); |
|||
}); |
|||
log.info("[{}] Stats: {}", queueName, stats); |
|||
} |
|||
} |
|||
|
|||
public void reset() { |
|||
counters.values().forEach(counter -> counter.set(0)); |
|||
tenantStats.clear(); |
|||
tenantExceptions.clear(); |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue; |
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class TbTenantRuleEngineStats { |
|||
|
|||
private final UUID tenantId; |
|||
|
|||
private final AtomicInteger totalMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger successMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpTimeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpFailedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final AtomicInteger timeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger failedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final Map<String, AtomicInteger> counters = new HashMap<>(); |
|||
|
|||
public TbTenantRuleEngineStats(UUID tenantId) { |
|||
this.tenantId = tenantId; |
|||
counters.put(TbRuleEngineConsumerStats.TOTAL_MSGS, totalMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.SUCCESSFUL_MSGS, successMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.TIMEOUT_MSGS, timeoutMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.FAILED_MSGS, failedMsgCounter); |
|||
|
|||
counters.put(TbRuleEngineConsumerStats.TMP_TIMEOUT, tmpTimeoutMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.TMP_FAILED, tmpFailedMsgCounter); |
|||
} |
|||
|
|||
public void logSuccess() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
successMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logFailed() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
failedMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logTimeout() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
timeoutMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logTmpFailed() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
tmpFailedMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logTmpTimeout() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
tmpTimeoutMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void printStats() { |
|||
int total = totalMsgCounter.get(); |
|||
if (total > 0) { |
|||
StringBuilder stats = new StringBuilder(); |
|||
counters.forEach((label, value) -> { |
|||
stats.append(label).append(" = [").append(value.get()).append("]"); |
|||
}); |
|||
log.info("[{}] Stats: {}", tenantId, stats); |
|||
} |
|||
} |
|||
|
|||
public void reset() { |
|||
counters.values().forEach(counter -> counter.set(0)); |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
|||
import org.springframework.context.ApplicationListener; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.queue.TbPackCallback; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> implements ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
protected volatile ExecutorService consumersExecutor; |
|||
protected volatile ExecutorService notificationsConsumerExecutor; |
|||
protected volatile boolean stopped = false; |
|||
|
|||
protected final ActorSystemContext actorContext; |
|||
protected final DataDecodingEncodingService encodingService; |
|||
|
|||
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer; |
|||
|
|||
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) { |
|||
this.actorContext = actorContext; |
|||
this.encodingService = encodingService; |
|||
this.nfConsumer = nfConsumer; |
|||
} |
|||
|
|||
public void init(String mainConsumerThreadName, String nfConsumerThreadName) { |
|||
this.consumersExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName(mainConsumerThreadName)); |
|||
this.notificationsConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(nfConsumerThreadName)); |
|||
} |
|||
|
|||
@EventListener(ApplicationReadyEvent.class) |
|||
public void onApplicationEvent(ApplicationReadyEvent event) { |
|||
log.info("Subscribing to notifications: {}", nfConsumer.getTopic()); |
|||
this.nfConsumer.subscribe(); |
|||
launchNotificationsConsumer(); |
|||
launchMainConsumers(); |
|||
} |
|||
|
|||
protected abstract ServiceType getServiceType(); |
|||
|
|||
protected abstract void launchMainConsumers(); |
|||
|
|||
protected abstract void stopMainConsumers(); |
|||
|
|||
protected abstract long getNotificationPollDuration(); |
|||
|
|||
protected abstract long getNotificationPackProcessingTimeout(); |
|||
|
|||
protected void launchNotificationsConsumer() { |
|||
notificationsConsumerExecutor.submit(() -> { |
|||
while (!stopped) { |
|||
try { |
|||
List<TbProtoQueueMsg<N>> msgs = nfConsumer.poll(getNotificationPollDuration()); |
|||
if (msgs.isEmpty()) { |
|||
continue; |
|||
} |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<N>> pendingMap = msgs.stream().collect( |
|||
Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<N>> failedMap = new ConcurrentHashMap<>(); |
|||
CountDownLatch processingTimeoutLatch = new CountDownLatch(1); |
|||
pendingMap.forEach((id, msg) -> { |
|||
log.trace("[{}] Creating notification callback for message: {}", id, msg.getValue()); |
|||
TbCallback callback = new TbPackCallback<>(id, processingTimeoutLatch, pendingMap, failedMap); |
|||
try { |
|||
handleNotification(id, msg, callback); |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to process notification: {}", id, msg, e); |
|||
callback.onFailure(e); |
|||
} |
|||
}); |
|||
if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { |
|||
pendingMap.forEach((id, msg) -> log.warn("[{}] Timeout to process notification: {}", id, msg.getValue())); |
|||
failedMap.forEach((id, msg) -> log.warn("[{}] Failed to process notification: {}", id, msg.getValue())); |
|||
} |
|||
nfConsumer.commit(); |
|||
} catch (Exception e) { |
|||
if (!stopped) { |
|||
log.warn("Failed to obtain notifications from queue.", e); |
|||
try { |
|||
Thread.sleep(getNotificationPollDuration()); |
|||
} catch (InterruptedException e2) { |
|||
log.trace("Failed to wait until the server has capacity to handle new notifications", e2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
log.info("TB Notifications Consumer stopped."); |
|||
}); |
|||
} |
|||
|
|||
protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception; |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
stopped = true; |
|||
|
|||
stopMainConsumers(); |
|||
|
|||
if (nfConsumer != null) { |
|||
nfConsumer.unsubscribe(); |
|||
} |
|||
|
|||
if (consumersExecutor != null) { |
|||
consumersExecutor.shutdownNow(); |
|||
} |
|||
if (notificationsConsumerExecutor != null) { |
|||
notificationsConsumerExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy { |
|||
|
|||
protected final String queueName; |
|||
protected List<IdMsgPair> orderedMsgList; |
|||
private volatile boolean stopped; |
|||
|
|||
public AbstractTbRuleEngineSubmitStrategy(String queueName) { |
|||
this.queueName = queueName; |
|||
} |
|||
|
|||
protected abstract void doOnSuccess(UUID id); |
|||
|
|||
@Override |
|||
public void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs) { |
|||
orderedMsgList = msgs.stream().map(msg -> new IdMsgPair(UUID.randomUUID(), msg)).collect(Collectors.toList()); |
|||
} |
|||
|
|||
@Override |
|||
public ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> getPendingMap() { |
|||
return orderedMsgList.stream().collect(Collectors.toConcurrentMap(pair -> pair.uuid, pair -> pair.msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { |
|||
List<IdMsgPair> newOrderedMsgList = new ArrayList<>(reprocessMap.size()); |
|||
for (IdMsgPair pair : orderedMsgList) { |
|||
if (reprocessMap.containsKey(pair.uuid)) { |
|||
newOrderedMsgList.add(pair); |
|||
} |
|||
} |
|||
orderedMsgList = newOrderedMsgList; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(UUID id) { |
|||
if (!stopped) { |
|||
doOnSuccess(id); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void stop() { |
|||
stopped = true; |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.LinkedHashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.function.BiConsumer; |
|||
|
|||
@Slf4j |
|||
public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { |
|||
|
|||
private final int batchSize; |
|||
private final AtomicInteger packIdx = new AtomicInteger(0); |
|||
private final Map<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> pendingPack = new LinkedHashMap<>(); |
|||
private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer; |
|||
|
|||
public BatchTbRuleEngineSubmitStrategy(String queueName, int batchSize) { |
|||
super(queueName); |
|||
this.batchSize = batchSize; |
|||
} |
|||
|
|||
@Override |
|||
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { |
|||
this.msgConsumer = msgConsumer; |
|||
submitNext(); |
|||
} |
|||
|
|||
@Override |
|||
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { |
|||
super.update(reprocessMap); |
|||
packIdx.set(0); |
|||
} |
|||
|
|||
@Override |
|||
protected void doOnSuccess(UUID id) { |
|||
boolean endOfPendingPack; |
|||
synchronized (pendingPack) { |
|||
TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg> msg = pendingPack.remove(id); |
|||
endOfPendingPack = msg != null && pendingPack.isEmpty(); |
|||
} |
|||
if (endOfPendingPack) { |
|||
packIdx.incrementAndGet(); |
|||
submitNext(); |
|||
} |
|||
} |
|||
|
|||
private void submitNext() { |
|||
int listSize = orderedMsgList.size(); |
|||
int startIdx = Math.min(packIdx.get() * batchSize, listSize); |
|||
int endIdx = Math.min(startIdx + batchSize, listSize); |
|||
synchronized (pendingPack) { |
|||
pendingPack.clear(); |
|||
for (int i = startIdx; i < endIdx; i++) { |
|||
IdMsgPair pair = orderedMsgList.get(i); |
|||
pendingPack.put(pair.uuid, pair.msg); |
|||
} |
|||
} |
|||
int submitSize = pendingPack.size(); |
|||
if (log.isInfoEnabled() && submitSize > 0) { |
|||
log.info("[{}] submitting [{}] messages to rule engine", queueName, submitSize); |
|||
} |
|||
pendingPack.forEach(msgConsumer); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.function.BiConsumer; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public class BurstTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { |
|||
|
|||
public BurstTbRuleEngineSubmitStrategy(String queueName) { |
|||
super(queueName); |
|||
} |
|||
|
|||
@Override |
|||
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { |
|||
if (log.isInfoEnabled()) { |
|||
log.info("[{}] submitting [{}] messages to rule engine", queueName, orderedMsgList.size()); |
|||
} |
|||
orderedMsgList.forEach(pair -> msgConsumer.accept(pair.uuid, pair.msg)); |
|||
} |
|||
|
|||
@Override |
|||
protected void doOnSuccess(UUID id) { |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.gen.MsgProtos; |
|||
import org.thingsboard.server.common.msg.queue.TbMsgCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.Queue; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.function.BiConsumer; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { |
|||
|
|||
private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer; |
|||
private volatile ConcurrentMap<UUID, EntityId> msgToEntityIdMap = new ConcurrentHashMap<>(); |
|||
private volatile ConcurrentMap<EntityId, Queue<IdMsgPair>> entityIdToListMap = new ConcurrentHashMap<>(); |
|||
|
|||
public SequentialByEntityIdTbRuleEngineSubmitStrategy(String queueName) { |
|||
super(queueName); |
|||
} |
|||
|
|||
@Override |
|||
public void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs) { |
|||
super.init(msgs); |
|||
initMaps(); |
|||
} |
|||
|
|||
@Override |
|||
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { |
|||
this.msgConsumer = msgConsumer; |
|||
entityIdToListMap.forEach((entityId, queue) -> { |
|||
IdMsgPair msg = queue.peek(); |
|||
if (msg != null) { |
|||
msgConsumer.accept(msg.uuid, msg.msg); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { |
|||
super.update(reprocessMap); |
|||
initMaps(); |
|||
} |
|||
|
|||
@Override |
|||
protected void doOnSuccess(UUID id) { |
|||
EntityId entityId = msgToEntityIdMap.get(id); |
|||
if (entityId != null) { |
|||
Queue<IdMsgPair> queue = entityIdToListMap.get(entityId); |
|||
if (queue != null) { |
|||
IdMsgPair next = null; |
|||
synchronized (queue) { |
|||
IdMsgPair expected = queue.peek(); |
|||
if (expected != null && expected.uuid.equals(id)) { |
|||
queue.poll(); |
|||
next = queue.peek(); |
|||
} |
|||
} |
|||
if (next != null) { |
|||
msgConsumer.accept(next.uuid, next.msg); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void initMaps() { |
|||
msgToEntityIdMap.clear(); |
|||
entityIdToListMap.clear(); |
|||
for (IdMsgPair pair : orderedMsgList) { |
|||
EntityId entityId = getEntityId(pair.msg.getValue()); |
|||
if (entityId != null) { |
|||
msgToEntityIdMap.put(pair.uuid, entityId); |
|||
entityIdToListMap.computeIfAbsent(entityId, id -> new LinkedList<>()).add(pair); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected abstract EntityId getEntityId(TransportProtos.ToRuleEngineMsg msg); |
|||
|
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.msg.gen.MsgProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public class SequentialByOriginatorIdTbRuleEngineSubmitStrategy extends SequentialByEntityIdTbRuleEngineSubmitStrategy { |
|||
|
|||
public SequentialByOriginatorIdTbRuleEngineSubmitStrategy(String queueName) { |
|||
super(queueName); |
|||
} |
|||
|
|||
@Override |
|||
protected EntityId getEntityId(TransportProtos.ToRuleEngineMsg msg) { |
|||
try { |
|||
MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(msg.getTbMsg()); |
|||
return EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())); |
|||
} catch (InvalidProtocolBufferException e) { |
|||
log.warn("[{}] Failed to parse TbMsg: {}", queueName, msg); |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.LinkedHashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.function.BiConsumer; |
|||
|
|||
@Slf4j |
|||
public class SequentialTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { |
|||
|
|||
private final AtomicInteger msgIdx = new AtomicInteger(0); |
|||
private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer; |
|||
private volatile UUID expectedMsgId; |
|||
|
|||
public SequentialTbRuleEngineSubmitStrategy(String queueName) { |
|||
super(queueName); |
|||
} |
|||
|
|||
@Override |
|||
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { |
|||
this.msgConsumer = msgConsumer; |
|||
msgIdx.set(0); |
|||
submitNext(); |
|||
} |
|||
|
|||
@Override |
|||
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { |
|||
super.update(reprocessMap); |
|||
} |
|||
|
|||
@Override |
|||
protected void doOnSuccess(UUID id) { |
|||
if (expectedMsgId.equals(id)) { |
|||
msgIdx.incrementAndGet(); |
|||
submitNext(); |
|||
} |
|||
} |
|||
|
|||
private void submitNext() { |
|||
int listSize = orderedMsgList.size(); |
|||
int idx = msgIdx.get(); |
|||
if (idx < listSize) { |
|||
IdMsgPair pair = orderedMsgList.get(idx); |
|||
expectedMsgId = pair.uuid; |
|||
if (log.isInfoEnabled()) { |
|||
log.info("[{}] submitting [{}] message to rule engine", queueName, pair.msg); |
|||
} |
|||
msgConsumer.accept(pair.uuid, pair.msg); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
@Data |
|||
public class TbRuleEngineProcessingDecision { |
|||
|
|||
private final boolean commit; |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> reprocessMap; |
|||
|
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.service.queue.ProcessingAttemptContext; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
public class TbRuleEngineProcessingResult { |
|||
|
|||
@Getter |
|||
private final boolean success; |
|||
@Getter |
|||
private final boolean timeout; |
|||
@Getter |
|||
private final ProcessingAttemptContext ctx; |
|||
|
|||
public TbRuleEngineProcessingResult(boolean timeout, ProcessingAttemptContext ctx) { |
|||
this.timeout = timeout; |
|||
this.ctx = ctx; |
|||
this.success = !timeout && ctx.getPendingMap().isEmpty() && ctx.getFailedMap().isEmpty(); |
|||
} |
|||
|
|||
public ConcurrentMap<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> getPendingMap() { |
|||
return ctx.getPendingMap(); |
|||
} |
|||
|
|||
public ConcurrentMap<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> getSuccessMap() { |
|||
return ctx.getSuccessMap(); |
|||
} |
|||
|
|||
public ConcurrentMap<UUID, TbProtoQueueMsg<ToRuleEngineMsg>> getFailedMap() { |
|||
return ctx.getFailedMap(); |
|||
} |
|||
|
|||
public ConcurrentMap<TenantId, RuleEngineException> getExceptionsMap() { |
|||
return ctx.getExceptionsMap(); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
public interface TbRuleEngineProcessingStrategy { |
|||
|
|||
TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result); |
|||
|
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.queue.TbMsgCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.settings.TbRuleEngineQueueAckStrategyConfiguration; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
public class TbRuleEngineProcessingStrategyFactory { |
|||
|
|||
public TbRuleEngineProcessingStrategy newInstance(String name, TbRuleEngineQueueAckStrategyConfiguration configuration) { |
|||
switch (configuration.getType()) { |
|||
case "SKIP_ALL_FAILURES": |
|||
return new SkipStrategy(name); |
|||
case "RETRY_ALL": |
|||
return new RetryStrategy(name, true, true, true, configuration); |
|||
case "RETRY_FAILED": |
|||
return new RetryStrategy(name, false, true, false, configuration); |
|||
case "RETRY_TIMED_OUT": |
|||
return new RetryStrategy(name, false, false, true, configuration); |
|||
case "RETRY_FAILED_AND_TIMED_OUT": |
|||
return new RetryStrategy(name, false, true, true, configuration); |
|||
default: |
|||
throw new RuntimeException("TbRuleEngineProcessingStrategy with type " + configuration.getType() + " is not supported!"); |
|||
} |
|||
} |
|||
|
|||
private static class RetryStrategy implements TbRuleEngineProcessingStrategy { |
|||
private final String queueName; |
|||
private final boolean retrySuccessful; |
|||
private final boolean retryFailed; |
|||
private final boolean retryTimeout; |
|||
private final int maxRetries; |
|||
private final double maxAllowedFailurePercentage; |
|||
private final long pauseBetweenRetries; |
|||
|
|||
private int initialTotalCount; |
|||
private int retryCount; |
|||
|
|||
public RetryStrategy(String queueName, boolean retrySuccessful, boolean retryFailed, boolean retryTimeout, TbRuleEngineQueueAckStrategyConfiguration configuration) { |
|||
this.queueName = queueName; |
|||
this.retrySuccessful = retrySuccessful; |
|||
this.retryFailed = retryFailed; |
|||
this.retryTimeout = retryTimeout; |
|||
this.maxRetries = configuration.getRetries(); |
|||
this.maxAllowedFailurePercentage = configuration.getFailurePercentage(); |
|||
this.pauseBetweenRetries = configuration.getPauseBetweenRetries(); |
|||
} |
|||
|
|||
@Override |
|||
public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) { |
|||
if (result.isSuccess()) { |
|||
return new TbRuleEngineProcessingDecision(true, null); |
|||
} else { |
|||
if (retryCount == 0) { |
|||
initialTotalCount = result.getPendingMap().size() + result.getFailedMap().size() + result.getSuccessMap().size(); |
|||
} |
|||
retryCount++; |
|||
double failedCount = result.getFailedMap().size() + result.getPendingMap().size(); |
|||
if (maxRetries > 0 && retryCount > maxRetries) { |
|||
log.info("[{}] Skip reprocess of the rule engine pack due to max retries", queueName); |
|||
return new TbRuleEngineProcessingDecision(true, null); |
|||
} else if (maxAllowedFailurePercentage > 0 && (failedCount / initialTotalCount) > maxAllowedFailurePercentage) { |
|||
log.info("[{}] Skip reprocess of the rule engine pack due to max allowed failure percentage", queueName); |
|||
return new TbRuleEngineProcessingDecision(true, null); |
|||
} else { |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> toReprocess = new ConcurrentHashMap<>(initialTotalCount); |
|||
if (retryFailed) { |
|||
result.getFailedMap().forEach(toReprocess::put); |
|||
} |
|||
if (retryTimeout) { |
|||
result.getPendingMap().forEach(toReprocess::put); |
|||
} |
|||
if (retrySuccessful) { |
|||
result.getSuccessMap().forEach(toReprocess::put); |
|||
} |
|||
log.info("[{}] Going to reprocess {} messages", queueName, toReprocess.size()); |
|||
if (log.isTraceEnabled()) { |
|||
toReprocess.forEach((id, msg) -> log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); |
|||
} |
|||
if (pauseBetweenRetries > 0) { |
|||
try { |
|||
Thread.sleep(TimeUnit.SECONDS.toMillis(pauseBetweenRetries)); |
|||
} catch (InterruptedException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
return new TbRuleEngineProcessingDecision(false, toReprocess); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static class SkipStrategy implements TbRuleEngineProcessingStrategy { |
|||
|
|||
private final String queueName; |
|||
|
|||
public SkipStrategy(String name) { |
|||
this.queueName = name; |
|||
} |
|||
|
|||
@Override |
|||
public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) { |
|||
if (!result.isSuccess()) { |
|||
log.info("[{}] Reprocessing skipped for {} failed and {} timeout messages", queueName, result.getFailedMap().size(), result.getPendingMap().size()); |
|||
} |
|||
if (log.isTraceEnabled()) { |
|||
result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); |
|||
} |
|||
if (log.isTraceEnabled()) { |
|||
result.getPendingMap().forEach((id, msg) -> log.trace("Timeout messages [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); |
|||
} |
|||
return new TbRuleEngineProcessingDecision(true, null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.function.BiConsumer; |
|||
|
|||
public interface TbRuleEngineSubmitStrategy { |
|||
|
|||
void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs); |
|||
|
|||
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> getPendingMap(); |
|||
|
|||
void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer); |
|||
|
|||
void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap); |
|||
|
|||
void onSuccess(UUID id); |
|||
|
|||
void stop(); |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.queue.settings.TbRuleEngineQueueSubmitStrategyConfiguration; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
public class TbRuleEngineSubmitStrategyFactory { |
|||
|
|||
public TbRuleEngineSubmitStrategy newInstance(String name, TbRuleEngineQueueSubmitStrategyConfiguration configuration) { |
|||
switch (configuration.getType()) { |
|||
case "BURST": |
|||
return new BurstTbRuleEngineSubmitStrategy(name); |
|||
case "BATCH": |
|||
return new BatchTbRuleEngineSubmitStrategy(name, configuration.getBatchSize()); |
|||
case "SEQUENTIAL_WITHIN_ORIGINATOR": |
|||
return new SequentialByOriginatorIdTbRuleEngineSubmitStrategy(name); |
|||
case "SEQUENTIAL_WITHIN_TENANT": |
|||
return new SequentialByTenantIdTbRuleEngineSubmitStrategy(name); |
|||
case "SEQUENTIAL": |
|||
return new SequentialTbRuleEngineSubmitStrategy(name); |
|||
default: |
|||
throw new RuntimeException("TbRuleEngineProcessingStrategy with type " + configuration.getType() + " is not supported!"); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,222 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.rpc; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.rule.engine.api.RpcError; |
|||
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgDataType; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; |
|||
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
|||
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Created by ashvayka on 27.03.18. |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class DefaultDeviceRpcService implements DeviceRpcService { |
|||
|
|||
private static final ObjectMapper json = new ObjectMapper(); |
|||
|
|||
@Autowired |
|||
private ClusterRoutingService routingService; |
|||
|
|||
@Autowired |
|||
private ClusterRpcService rpcService; |
|||
|
|||
@Autowired |
|||
private DeviceService deviceService; |
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private ActorService actorService; |
|||
|
|||
private ScheduledExecutorService rpcCallBackExecutor; |
|||
|
|||
private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> localToRuleEngineRpcRequests = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> localToDeviceRpcRequests = new ConcurrentHashMap<>(); |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("rpc-callback")); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (rpcCallBackExecutor != null) { |
|||
rpcCallBackExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void processRestAPIRpcRequestToRuleEngine(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) { |
|||
log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); |
|||
UUID requestId = request.getId(); |
|||
localToRuleEngineRpcRequests.put(requestId, responseConsumer); |
|||
sendRpcRequestToRuleEngine(request); |
|||
scheduleTimeout(request, requestId, localToRuleEngineRpcRequests); |
|||
} |
|||
|
|||
@Override |
|||
public void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response) { |
|||
log.trace("[{}] Received response to server-side RPC request from rule engine: [{}]", response.getId(), requestOriginAddress); |
|||
if (routingService.getCurrentServer().equals(requestOriginAddress)) { |
|||
UUID requestId = response.getId(); |
|||
Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId); |
|||
if (consumer != null) { |
|||
consumer.accept(response); |
|||
} else { |
|||
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
|||
} |
|||
} else { |
|||
ClusterAPIProtos.FromDeviceRPCResponseProto.Builder builder = ClusterAPIProtos.FromDeviceRPCResponseProto.newBuilder(); |
|||
builder.setRequestIdMSB(response.getId().getMostSignificantBits()); |
|||
builder.setRequestIdLSB(response.getId().getLeastSignificantBits()); |
|||
response.getResponse().ifPresent(builder::setResponse); |
|||
if (response.getError().isPresent()) { |
|||
builder.setError(response.getError().get().ordinal()); |
|||
} else { |
|||
builder.setError(-1); |
|||
} |
|||
rpcService.tell(requestOriginAddress, ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) { |
|||
log.trace("[{}][{}] Processing local rpc call to device actor [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); |
|||
UUID requestId = request.getId(); |
|||
localToDeviceRpcRequests.put(requestId, responseConsumer); |
|||
sendRpcRequestToDevice(request); |
|||
scheduleTimeout(request, requestId, localToDeviceRpcRequests); |
|||
} |
|||
|
|||
@Override |
|||
public void processResponseToServerSideRPCRequestFromDeviceActor(FromDeviceRpcResponse response) { |
|||
log.trace("[{}] Received response to server-side RPC request from device actor.", response.getId()); |
|||
UUID requestId = response.getId(); |
|||
Consumer<FromDeviceRpcResponse> consumer = localToDeviceRpcRequests.remove(requestId); |
|||
if (consumer != null) { |
|||
consumer.accept(response); |
|||
} else { |
|||
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void processResponseToServerSideRPCRequestFromRemoteServer(ServerAddress serverAddress, byte[] data) { |
|||
ClusterAPIProtos.FromDeviceRPCResponseProto proto; |
|||
try { |
|||
proto = ClusterAPIProtos.FromDeviceRPCResponseProto.parseFrom(data); |
|||
} catch (InvalidProtocolBufferException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; |
|||
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), proto.getResponse(), error); |
|||
processResponseToServerSideRPCRequestFromRuleEngine(routingService.getCurrentServer(), response); |
|||
} |
|||
|
|||
@Override |
|||
public void sendReplyToRpcCallFromDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) { |
|||
ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body)); |
|||
forward(deviceId, rpcMsg); |
|||
} |
|||
|
|||
private void sendRpcRequestToRuleEngine(ToDeviceRpcRequest msg) { |
|||
ObjectNode entityNode = json.createObjectNode(); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("requestUUID", msg.getId().toString()); |
|||
metaData.putValue("originHost", routingService.getCurrentServer().getHost()); |
|||
metaData.putValue("originPort", Integer.toString(routingService.getCurrentServer().getPort())); |
|||
metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime())); |
|||
metaData.putValue("oneway", Boolean.toString(msg.isOneway())); |
|||
|
|||
Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId()); |
|||
if (device != null) { |
|||
metaData.putValue("deviceName", device.getName()); |
|||
metaData.putValue("deviceType", device.getType()); |
|||
} |
|||
|
|||
entityNode.put("method", msg.getBody().getMethod()); |
|||
entityNode.put("params", msg.getBody().getParams()); |
|||
|
|||
try { |
|||
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), metaData, TbMsgDataType.JSON |
|||
, json.writeValueAsString(entityNode) |
|||
, null, null, 0L); |
|||
actorService.onMsg(new SendToClusterMsg(msg.getDeviceId(), new ServiceToRuleEngineMsg(msg.getTenantId(), tbMsg))); |
|||
} catch (JsonProcessingException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
private void sendRpcRequestToDevice(ToDeviceRpcRequest msg) { |
|||
ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(routingService.getCurrentServer(), msg); |
|||
log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg); |
|||
forward(msg.getDeviceId(), rpcMsg); |
|||
} |
|||
|
|||
private <T extends ToDeviceActorNotificationMsg> void forward(DeviceId deviceId, T msg) { |
|||
actorService.onMsg(new SendToClusterMsg(deviceId, msg)); |
|||
} |
|||
|
|||
private void scheduleTimeout(ToDeviceRpcRequest request, UUID requestId, ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> requestsMap) { |
|||
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()); |
|||
log.trace("[{}] processing the request: [{}]", this.hashCode(), requestId); |
|||
rpcCallBackExecutor.schedule(() -> { |
|||
log.trace("[{}] timeout the request: [{}]", this.hashCode(), requestId); |
|||
Consumer<FromDeviceRpcResponse> consumer = requestsMap.remove(requestId); |
|||
if (consumer != null) { |
|||
consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT)); |
|||
} |
|||
}, timeout, TimeUnit.MILLISECONDS); |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,199 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.rpc; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.rule.engine.api.RpcError; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgDataType; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.queue.TbClusterService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Created by ashvayka on 27.03.18. |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { |
|||
|
|||
private static final ObjectMapper json = new ObjectMapper(); |
|||
|
|||
private final DeviceService deviceService; |
|||
private final TbClusterService clusterService; |
|||
private final TbServiceInfoProvider serviceInfoProvider; |
|||
private final ActorSystemContext actorContext; |
|||
|
|||
private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> localToRuleEngineRpcRequests = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<UUID, ToDeviceRpcRequestActorMsg> localToDeviceRpcRequests = new ConcurrentHashMap<>(); |
|||
|
|||
private Optional<TbRuleEngineDeviceRpcService> tbRuleEngineRpcService; |
|||
private ScheduledExecutorService scheduler; |
|||
private String serviceId; |
|||
|
|||
public DefaultTbCoreDeviceRpcService(DeviceService deviceService, TbClusterService clusterService, TbServiceInfoProvider serviceInfoProvider, |
|||
ActorSystemContext actorContext) { |
|||
this.deviceService = deviceService; |
|||
this.clusterService = clusterService; |
|||
this.serviceInfoProvider = serviceInfoProvider; |
|||
this.actorContext = actorContext; |
|||
} |
|||
|
|||
@Autowired(required = false) |
|||
public void setTbRuleEngineRpcService(Optional<TbRuleEngineDeviceRpcService> tbRuleEngineRpcService) { |
|||
this.tbRuleEngineRpcService = tbRuleEngineRpcService; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("tb-core-rpc-scheduler")); |
|||
serviceId = serviceInfoProvider.getServiceId(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (scheduler != null) { |
|||
scheduler.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) { |
|||
log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); |
|||
UUID requestId = request.getId(); |
|||
localToRuleEngineRpcRequests.put(requestId, responseConsumer); |
|||
sendRpcRequestToRuleEngine(request); |
|||
scheduleToRuleEngineTimeout(request, requestId); |
|||
} |
|||
|
|||
@Override |
|||
public void processRpcResponseFromRuleEngine(FromDeviceRpcResponse response) { |
|||
log.trace("[{}] Received response to server-side RPC request from rule engine: [{}]", response.getId(), response); |
|||
UUID requestId = response.getId(); |
|||
Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId); |
|||
if (consumer != null) { |
|||
consumer.accept(response); |
|||
} else { |
|||
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void forwardRpcRequestToDeviceActor(ToDeviceRpcRequestActorMsg rpcMsg) { |
|||
ToDeviceRpcRequest request = rpcMsg.getMsg(); |
|||
log.trace("[{}][{}] Processing local rpc call to device actor [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); |
|||
UUID requestId = request.getId(); |
|||
localToDeviceRpcRequests.put(requestId, rpcMsg); |
|||
actorContext.tell(rpcMsg, ActorRef.noSender()); |
|||
scheduleToDeviceTimeout(request, requestId); |
|||
} |
|||
|
|||
@Override |
|||
public void processRpcResponseFromDeviceActor(FromDeviceRpcResponse response) { |
|||
log.trace("[{}] Received response to server-side RPC request from device actor.", response.getId()); |
|||
UUID requestId = response.getId(); |
|||
ToDeviceRpcRequestActorMsg request = localToDeviceRpcRequests.remove(requestId); |
|||
if (request != null) { |
|||
sendRpcResponseToTbRuleEngine(request.getServiceId(), response); |
|||
} else { |
|||
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
|||
} |
|||
} |
|||
|
|||
private void sendRpcResponseToTbRuleEngine(String originServiceId, FromDeviceRpcResponse response) { |
|||
if (serviceId.equals(originServiceId)) { |
|||
if (tbRuleEngineRpcService.isPresent()) { |
|||
tbRuleEngineRpcService.get().processRpcResponseFromDevice(response); |
|||
} else { |
|||
log.warn("Failed to find tbCoreRpcService for local service. Possible duplication of serviceIds."); |
|||
} |
|||
} else { |
|||
clusterService.pushNotificationToRuleEngine(originServiceId, response, null); |
|||
} |
|||
} |
|||
|
|||
private void sendRpcRequestToRuleEngine(ToDeviceRpcRequest msg) { |
|||
ObjectNode entityNode = json.createObjectNode(); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("requestUUID", msg.getId().toString()); |
|||
metaData.putValue("originServiceId", serviceId); |
|||
metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime())); |
|||
metaData.putValue("oneway", Boolean.toString(msg.isOneway())); |
|||
|
|||
Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId()); |
|||
if (device != null) { |
|||
metaData.putValue("deviceName", device.getName()); |
|||
metaData.putValue("deviceType", device.getType()); |
|||
} |
|||
|
|||
entityNode.put("method", msg.getBody().getMethod()); |
|||
entityNode.put("params", msg.getBody().getParams()); |
|||
|
|||
try { |
|||
TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); |
|||
clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); |
|||
} catch (JsonProcessingException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
private void scheduleToRuleEngineTimeout(ToDeviceRpcRequest request, UUID requestId) { |
|||
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()); |
|||
log.trace("[{}] processing to rule engine request.", requestId); |
|||
scheduler.schedule(() -> { |
|||
log.trace("[{}] timeout for processing to rule engine request.", requestId); |
|||
Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId); |
|||
if (consumer != null) { |
|||
consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT)); |
|||
} |
|||
}, timeout, TimeUnit.MILLISECONDS); |
|||
} |
|||
|
|||
private void scheduleToDeviceTimeout(ToDeviceRpcRequest request, UUID requestId) { |
|||
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()); |
|||
log.trace("[{}] processing to device request.", requestId); |
|||
scheduler.schedule(() -> { |
|||
log.trace("[{}] timeout for to device request.", requestId); |
|||
localToDeviceRpcRequests.remove(requestId); |
|||
}, timeout, TimeUnit.MILLISECONDS); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,177 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.rpc; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.rule.engine.api.RpcError; |
|||
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; |
|||
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; |
|||
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.util.TbRuleEngineComponent; |
|||
import org.thingsboard.server.service.queue.TbClusterService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Consumer; |
|||
|
|||
@Service |
|||
@TbRuleEngineComponent |
|||
@Slf4j |
|||
public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcService { |
|||
|
|||
private final PartitionService partitionService; |
|||
private final TbClusterService clusterService; |
|||
private final TbServiceInfoProvider serviceInfoProvider; |
|||
|
|||
|
|||
private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> toDeviceRpcRequests = new ConcurrentHashMap<>(); |
|||
|
|||
private Optional<TbCoreDeviceRpcService> tbCoreRpcService; |
|||
private ScheduledExecutorService scheduler; |
|||
private String serviceId; |
|||
|
|||
public DefaultTbRuleEngineRpcService(PartitionService partitionService, |
|||
TbClusterService clusterService, |
|||
TbServiceInfoProvider serviceInfoProvider) { |
|||
this.partitionService = partitionService; |
|||
this.clusterService = clusterService; |
|||
this.serviceInfoProvider = serviceInfoProvider; |
|||
} |
|||
|
|||
@Autowired(required = false) |
|||
public void setTbCoreRpcService(Optional<TbCoreDeviceRpcService> tbCoreRpcService) { |
|||
this.tbCoreRpcService = tbCoreRpcService; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("rule-engine-rpc-scheduler")); |
|||
serviceId = serviceInfoProvider.getServiceId(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (scheduler != null) { |
|||
scheduler.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void sendRpcReplyToDevice(String serviceId, UUID sessionId, int requestId, String body) { |
|||
TransportProtos.ToServerRpcResponseMsg responseMsg = TransportProtos.ToServerRpcResponseMsg.newBuilder() |
|||
.setRequestId(requestId) |
|||
.setPayload(body).build(); |
|||
TransportProtos.ToTransportMsg msg = TransportProtos.ToTransportMsg.newBuilder() |
|||
.setSessionIdMSB(sessionId.getMostSignificantBits()) |
|||
.setSessionIdLSB(sessionId.getLeastSignificantBits()) |
|||
.setToServerResponse(responseMsg) |
|||
.build(); |
|||
clusterService.pushNotificationToTransport(serviceId, msg, null); |
|||
} |
|||
|
|||
@Override |
|||
public void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) { |
|||
ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(), |
|||
src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody())); |
|||
forwardRpcRequestToDeviceActor(request, response -> { |
|||
if (src.isRestApiCall()) { |
|||
sendRpcResponseToTbCore(src.getOriginServiceId(), response); |
|||
} |
|||
consumer.accept(RuleEngineDeviceRpcResponse.builder() |
|||
.deviceId(src.getDeviceId()) |
|||
.requestId(src.getRequestId()) |
|||
.error(response.getError()) |
|||
.response(response.getResponse()) |
|||
.build()); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void processRpcResponseFromDevice(FromDeviceRpcResponse response) { |
|||
log.trace("[{}] Received response to server-side RPC request from Core RPC Service", response.getId()); |
|||
UUID requestId = response.getId(); |
|||
Consumer<FromDeviceRpcResponse> consumer = toDeviceRpcRequests.remove(requestId); |
|||
if (consumer != null) { |
|||
scheduler.submit(() -> consumer.accept(response)); |
|||
} else { |
|||
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
|||
} |
|||
} |
|||
|
|||
private void forwardRpcRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) { |
|||
log.trace("[{}][{}] Processing local rpc call to device actor [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); |
|||
UUID requestId = request.getId(); |
|||
toDeviceRpcRequests.put(requestId, responseConsumer); |
|||
sendRpcRequestToDevice(request); |
|||
scheduleTimeout(request, requestId); |
|||
} |
|||
|
|||
private void sendRpcRequestToDevice(ToDeviceRpcRequest msg) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, msg.getTenantId(), msg.getDeviceId()); |
|||
ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(serviceId, msg); |
|||
if (tpi.isMyPartition()) { |
|||
log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg); |
|||
if (tbCoreRpcService.isPresent()) { |
|||
tbCoreRpcService.get().forwardRpcRequestToDeviceActor(rpcMsg); |
|||
} else { |
|||
log.warn("Failed to find tbCoreRpcService for local service. Possible duplication of serviceIds."); |
|||
} |
|||
} else { |
|||
log.trace("[{}] Forwarding msg {} to queue actor!", msg.getDeviceId(), msg); |
|||
clusterService.pushMsgToCore(rpcMsg, null); |
|||
} |
|||
} |
|||
|
|||
private void sendRpcResponseToTbCore(String originServiceId, FromDeviceRpcResponse response) { |
|||
if (serviceId.equals(originServiceId)) { |
|||
if (tbCoreRpcService.isPresent()) { |
|||
tbCoreRpcService.get().processRpcResponseFromRuleEngine(response); |
|||
} else { |
|||
log.warn("Failed to find tbCoreRpcService for local service. Possible duplication of serviceIds."); |
|||
} |
|||
} else { |
|||
clusterService.pushNotificationToCore(originServiceId, response, null); |
|||
} |
|||
} |
|||
|
|||
private void scheduleTimeout(ToDeviceRpcRequest request, UUID requestId) { |
|||
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()); |
|||
log.trace("[{}] processing the request: [{}]", this.hashCode(), requestId); |
|||
scheduler.schedule(() -> { |
|||
log.trace("[{}] timeout the request: [{}]", this.hashCode(), requestId); |
|||
Consumer<FromDeviceRpcResponse> consumer = toDeviceRpcRequests.remove(requestId); |
|||
if (consumer != null) { |
|||
scheduler.submit(() -> consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT))); |
|||
} |
|||
}, timeout, TimeUnit.MILLISECONDS); |
|||
} |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.rpc; |
|||
|
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
|||
|
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Created by ashvayka on 16.04.18. |
|||
*/ |
|||
public interface DeviceRpcService { |
|||
|
|||
void processRestAPIRpcRequestToRuleEngine(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer); |
|||
|
|||
void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response); |
|||
|
|||
void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer); |
|||
|
|||
void processResponseToServerSideRPCRequestFromDeviceActor(FromDeviceRpcResponse response); |
|||
|
|||
void processResponseToServerSideRPCRequestFromRemoteServer(ServerAddress serverAddress, byte[] data); |
|||
|
|||
void sendReplyToRpcCallFromDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body); |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.rpc; |
|||
|
|||
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
|||
|
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Handles REST API calls that contain RPC requests to Device. |
|||
*/ |
|||
public interface TbCoreDeviceRpcService { |
|||
|
|||
/** |
|||
* Handles REST API calls that contain RPC requests to Device and pushes them to Rule Engine. |
|||
* Schedules the timeout for the RPC call based on the {@link ToDeviceRpcRequest} |
|||
* |
|||
* @param request the RPC request |
|||
* @param responseConsumer the consumer of the RPC response |
|||
*/ |
|||
void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer); |
|||
|
|||
/** |
|||
* Handles the RPC response from the Rule Engine. |
|||
* |
|||
* @param response the RPC response |
|||
*/ |
|||
void processRpcResponseFromRuleEngine(FromDeviceRpcResponse response); |
|||
|
|||
/** |
|||
* Forwards the RPC request from Rule Engine to Device Actor |
|||
* |
|||
* @param request the RPC request message |
|||
*/ |
|||
void forwardRpcRequestToDeviceActor(ToDeviceRpcRequestActorMsg request); |
|||
|
|||
/** |
|||
* Handles the RPC response from the Device Actor (Transport). |
|||
* |
|||
* @param response the RPC response |
|||
*/ |
|||
void processRpcResponseFromDeviceActor(FromDeviceRpcResponse response); |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.rpc; |
|||
|
|||
import org.thingsboard.rule.engine.api.RuleEngineRpcService; |
|||
|
|||
/** |
|||
* Created by ashvayka on 16.04.18. |
|||
*/ |
|||
public interface TbRuleEngineDeviceRpcService extends RuleEngineRpcService { |
|||
|
|||
/** |
|||
* Handles the RPC response from the Device Actor (Transport). |
|||
* |
|||
* @param response the RPC response |
|||
*/ |
|||
void processRpcResponseFromDevice(FromDeviceRpcResponse response); |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue