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