WIP: Cluster mode refactoring
This commit is contained in:
parent
e09ef84f43
commit
d3278f05bb
@ -104,6 +104,7 @@ import org.thingsboard.server.queue.discovery.DiscoveryService;
|
||||
import org.thingsboard.server.queue.discovery.PartitionService;
|
||||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
|
||||
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
|
||||
import org.thingsboard.server.service.cf.CalculatedFieldExecutionService;
|
||||
import org.thingsboard.server.service.component.ComponentDiscoveryService;
|
||||
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
|
||||
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
|
||||
@ -507,6 +508,11 @@ public class ActorSystemContext {
|
||||
@Getter
|
||||
private EntityService entityService;
|
||||
|
||||
@Lazy
|
||||
@Autowired(required = false)
|
||||
@Getter
|
||||
private CalculatedFieldExecutionService calculatedFieldExecutionService;
|
||||
|
||||
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
|
||||
@Getter
|
||||
private long maxConcurrentSessionsPerDevice;
|
||||
|
||||
@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.TbActorMsg;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
|
||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
|
||||
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
|
||||
@ -111,6 +112,17 @@ public class AppActor extends ContextAwareActor {
|
||||
case SESSION_TIMEOUT_MSG:
|
||||
ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
|
||||
break;
|
||||
case CF_INIT_MSG:
|
||||
case CF_LINK_INIT_MSG:
|
||||
case CF_STATE_RESTORE_MSG:
|
||||
case CF_UPDATE_MSG:
|
||||
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);
|
||||
break;
|
||||
case CF_TELEMETRY_MSG:
|
||||
case CF_LINKED_TELEMETRY_MSG:
|
||||
case CF_ENTITY_UPDATE_MSG:
|
||||
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, false);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -175,6 +187,19 @@ public class AppActor extends ContextAwareActor {
|
||||
}
|
||||
}
|
||||
|
||||
private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) {
|
||||
getOrCreateTenantActor(msg.getTenantId()).ifPresentOrElse(tenantActor -> {
|
||||
if (priority) {
|
||||
tenantActor.tellWithHighPriority(msg);
|
||||
} else {
|
||||
tenantActor.tell(msg);
|
||||
}
|
||||
}, () -> {
|
||||
msg.getCallback().onSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void onToDeviceActorMsg(TenantAwareMsg msg, boolean priority) {
|
||||
getOrCreateTenantActor(msg.getTenantId()).ifPresentOrElse(tenantActor -> {
|
||||
if (priority) {
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.TbActorCtx;
|
||||
import org.thingsboard.server.actors.TbActorException;
|
||||
import org.thingsboard.server.actors.service.ContextAwareActor;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.TbActorMsg;
|
||||
|
||||
@Slf4j
|
||||
public class CalculatedFieldEntityActor extends ContextAwareActor {
|
||||
|
||||
private final CalculatedFieldEntityMessageProcessor processor;
|
||||
|
||||
CalculatedFieldEntityActor(ActorSystemContext systemContext, TenantId tenantId, EntityId entityId) {
|
||||
super(systemContext);
|
||||
this.processor = new CalculatedFieldEntityMessageProcessor(systemContext, tenantId, entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(TbActorCtx ctx) throws TbActorException {
|
||||
super.init(ctx);
|
||||
log.debug("[{}][{}] Starting CF entity actor.", processor.tenantId, processor.entityId);
|
||||
try {
|
||||
processor.init(ctx);
|
||||
log.debug("[{}][{}] CF entity actor started.", processor.tenantId, processor.entityId);
|
||||
} catch (Exception e) {
|
||||
log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.entityId, e);
|
||||
throw new TbActorException("Failed to initialize device actor", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doProcess(TbActorMsg msg) {
|
||||
switch (msg.getMsgType()) {
|
||||
case CF_STATE_RESTORE_MSG:
|
||||
processor.process((CalculatedFieldStateRestoreMsg) msg);
|
||||
break;
|
||||
case CF_ENTITY_TELEMETRY_MSG:
|
||||
processor.process((EntityCalculatedFieldTelemetryMsg) msg);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.TbActor;
|
||||
import org.thingsboard.server.actors.TbActorId;
|
||||
import org.thingsboard.server.actors.TbEntityActorId;
|
||||
import org.thingsboard.server.actors.device.DeviceActor;
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
public class CalculatedFieldEntityActorCreator extends ContextBasedCreator {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final EntityId entityId;
|
||||
|
||||
public CalculatedFieldEntityActorCreator(ActorSystemContext context, TenantId tenantId, EntityId entityId) {
|
||||
super(context);
|
||||
this.tenantId = tenantId;
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbActorId createActorId() {
|
||||
return new TbEntityActorId(entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbActor createActor() {
|
||||
return new CalculatedFieldEntityActor(context, tenantId, entityId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.TbActorCtx;
|
||||
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
|
||||
import org.thingsboard.server.common.data.AttributeScope;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.AttributeScopeProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
|
||||
import org.thingsboard.server.service.cf.CalculatedFieldExecutionService;
|
||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
|
||||
import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
|
||||
/**
|
||||
* @author Andrew Shvayka
|
||||
*/
|
||||
@Slf4j
|
||||
public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareMsgProcessor {
|
||||
// (1 for result persistence + 1 for the state persistence )
|
||||
public static final int CALLBACKS_PER_CF = 2;
|
||||
|
||||
final TenantId tenantId;
|
||||
final EntityId entityId;
|
||||
final CalculatedFieldExecutionService cfService;
|
||||
|
||||
TbActorCtx ctx;
|
||||
Map<CalculatedFieldId, CalculatedFieldState> states = new HashMap<>();
|
||||
|
||||
CalculatedFieldEntityMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, EntityId entityId) {
|
||||
super(systemContext);
|
||||
this.tenantId = tenantId;
|
||||
this.entityId = entityId;
|
||||
this.cfService = systemContext.getCalculatedFieldExecutionService();
|
||||
}
|
||||
|
||||
void init(TbActorCtx ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public void process(EntityCalculatedFieldTelemetryMsg msg) {
|
||||
var proto = msg.getProto();
|
||||
var numberOfCallbacks = CALLBACKS_PER_CF * (msg.getEntityIdFields().size() + msg.getProfileIdFields().size());
|
||||
MultipleTbCallback callback = new MultipleTbCallback(numberOfCallbacks, msg.getCallback());
|
||||
List<CalculatedFieldId> cfIdList = getCalculatedFieldIds(proto);
|
||||
Set<CalculatedFieldId> cfIdSet = new HashSet<>(cfIdList);
|
||||
for (var ctx : msg.getEntityIdFields()) {
|
||||
process(ctx, proto, cfIdSet, cfIdList, callback);
|
||||
}
|
||||
for (var ctx : msg.getProfileIdFields()) {
|
||||
process(ctx, proto, cfIdSet, cfIdList, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, Set<CalculatedFieldId> cfIds, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback) {
|
||||
if (cfIds.contains(ctx.getCfId())) {
|
||||
callback.onSuccess(CALLBACKS_PER_CF);
|
||||
} else {
|
||||
if (proto.getTsDataCount() > 0) {
|
||||
processTelemetry(ctx, proto, cfIdList, callback);
|
||||
} else if (proto.getAttrDataCount() > 0) {
|
||||
processAttributes(ctx, proto, cfIdList, callback);
|
||||
} else {
|
||||
callback.onSuccess(CALLBACKS_PER_CF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void processTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback) {
|
||||
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getTsDataList()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void processAttributes(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback) {
|
||||
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getScope(), proto.getAttrDataList()));
|
||||
}
|
||||
|
||||
private void processArgumentValuesUpdate(CalculatedFieldCtx ctx, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback,
|
||||
Map<String, ArgumentEntry> newArgValues) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
if (newArgValues.isEmpty()) {
|
||||
callback.onSuccess(CALLBACKS_PER_CF);
|
||||
}
|
||||
CalculatedFieldState state = getOrInitState(ctx);
|
||||
if (state.updateState(newArgValues)) {
|
||||
if (state.isReady()) {
|
||||
CalculatedFieldResult calculationResult = state.performCalculation(ctx).get(5, TimeUnit.SECONDS);
|
||||
cfIdList = new ArrayList<>(cfIdList);
|
||||
cfIdList.add(ctx.getCfId());
|
||||
cfService.pushMsgToRuleEngine(tenantId, entityId, calculationResult, cfIdList, callback);
|
||||
} else {
|
||||
callback.onSuccess(); // State was updated but no calculation performed;
|
||||
}
|
||||
cfService.pushStateToStorage(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), state, callback);
|
||||
} else {
|
||||
callback.onSuccess(CALLBACKS_PER_CF);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private CalculatedFieldState getOrInitState(CalculatedFieldCtx ctx) {
|
||||
CalculatedFieldState state = states.get(ctx.getCfId());
|
||||
if (state != null) {
|
||||
return state;
|
||||
} else {
|
||||
ListenableFuture<CalculatedFieldState> stateFuture = systemContext.getCalculatedFieldExecutionService().fetchStateFromDb(ctx, entityId);
|
||||
// Ugly but necessary. We do not expect to often fetch data from DB. Only once per <Entity, CalculatedField> pair lifetime.
|
||||
// This call happens while processing the CF pack from the queue consumer. So the timeout should be relatively low.
|
||||
// Alternatively, we can fetch the state outside the actor system and push separate command to create this actor,
|
||||
// but this will significantly complicate the code.
|
||||
state = stateFuture.get(1, TimeUnit.MINUTES);
|
||||
states.put(ctx.getCfId(), state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private Map<String, ArgumentEntry> mapToArguments(CalculatedFieldCtx ctx, List<TsKvProto> data) {
|
||||
Map<String, ArgumentEntry> arguments = new HashMap<>();
|
||||
var argNames = ctx.getMainEntityArguments();
|
||||
for (TsKvProto item : data) {
|
||||
ReferencedEntityKey key = new ReferencedEntityKey(item.getKv().getKey(), ArgumentType.TS_LATEST, null);
|
||||
String argName = argNames.get(key);
|
||||
if (argName != null) {
|
||||
arguments.put(argName, new SingleValueArgumentEntry(item));
|
||||
}
|
||||
key = new ReferencedEntityKey(item.getKv().getKey(), ArgumentType.TS_ROLLING, null);
|
||||
argName = argNames.get(key);
|
||||
if (argName != null) {
|
||||
arguments.put(argName, new SingleValueArgumentEntry(item));
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
private Map<String, ArgumentEntry> mapToArguments(CalculatedFieldCtx ctx, AttributeScopeProto scope, List<AttributeValueProto> attrDataList) {
|
||||
Map<String, ArgumentEntry> arguments = new HashMap<>();
|
||||
var argNames = ctx.getMainEntityArguments();
|
||||
for (AttributeValueProto item : attrDataList) {
|
||||
ReferencedEntityKey key = new ReferencedEntityKey(item.getKey(), ArgumentType.ATTRIBUTE, AttributeScope.valueOf(scope.name()));
|
||||
String argName = argNames.get(key);
|
||||
if (argName != null) {
|
||||
arguments.put(argName, new SingleValueArgumentEntry(item));
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
private static List<CalculatedFieldId> getCalculatedFieldIds(CalculatedFieldTelemetryMsgProto proto) {
|
||||
List<CalculatedFieldId> cfIds = new LinkedList<>();
|
||||
for (var cfId : proto.getPreviousCalculatedFieldsList()) {
|
||||
cfIds.add(new CalculatedFieldId(new UUID(cfId.getCalculatedFieldIdMSB(), cfId.getCalculatedFieldIdLSB())));
|
||||
}
|
||||
return cfIds;
|
||||
}
|
||||
|
||||
public void process(CalculatedFieldStateRestoreMsg msg) {
|
||||
states.put(msg.getId().cfId(), msg.getState());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||
|
||||
@Data
|
||||
public class CalculatedFieldLinkedTelemetryMsg implements ToCalculatedFieldSystemMsg {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final EntityId entityId;
|
||||
private final CalculatedFieldLinkedTelemetryMsgProto proto;
|
||||
private final TbCallback callback;
|
||||
|
||||
|
||||
@Override
|
||||
public MsgType getMsgType() {
|
||||
return MsgType.CF_LINKED_TELEMETRY_MSG;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.TbActorCtx;
|
||||
import org.thingsboard.server.actors.TbActorException;
|
||||
import org.thingsboard.server.actors.service.ContextAwareActor;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.TbActorMsg;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
|
||||
|
||||
/**
|
||||
* Created by ashvayka on 15.03.18.
|
||||
*/
|
||||
@Slf4j
|
||||
public class CalculatedFieldManagerActor extends ContextAwareActor {
|
||||
|
||||
private final CalculatedFieldManagerMessageProcessor processor;
|
||||
|
||||
public CalculatedFieldManagerActor(ActorSystemContext systemContext, TenantId tenantId) {
|
||||
super(systemContext);
|
||||
this.processor = new CalculatedFieldManagerMessageProcessor(systemContext, tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(TbActorCtx ctx) throws TbActorException {
|
||||
super.init(ctx);
|
||||
log.debug("[{}] Starting CF manager actor.", processor.tenantId);
|
||||
try {
|
||||
processor.init(ctx);
|
||||
log.debug("[{}] CF manager actor started.", processor.tenantId);
|
||||
} catch (Exception e) {
|
||||
log.warn("[{}] Unknown failure", processor.tenantId, e);
|
||||
throw new TbActorException("Failed to initialize manager actor", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doProcess(TbActorMsg msg) {
|
||||
switch (msg.getMsgType()) {
|
||||
case PARTITION_CHANGE_MSG:
|
||||
ctx.broadcastToChildren(msg, true); // TODO
|
||||
break;
|
||||
case CF_INIT_MSG:
|
||||
processor.onFieldInitMsg((CalculatedFieldInitMsg) msg);
|
||||
break;
|
||||
case CF_LINK_INIT_MSG:
|
||||
processor.onLinkInitMsg((CalculatedFieldLinkInitMsg) msg);
|
||||
break;
|
||||
case CF_STATE_RESTORE_MSG:
|
||||
processor.onStateRestoreMsg((CalculatedFieldStateRestoreMsg) msg);
|
||||
break;
|
||||
case CF_UPDATE_MSG:
|
||||
// processor.onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg);
|
||||
break;
|
||||
case CF_TELEMETRY_MSG:
|
||||
processor.onTelemetryMsg((CalculatedFieldTelemetryMsg) msg);
|
||||
break;
|
||||
case CF_LINKED_TELEMETRY_MSG:
|
||||
case CF_ENTITY_UPDATE_MSG:
|
||||
// processor.onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.TbActor;
|
||||
import org.thingsboard.server.actors.TbActorId;
|
||||
import org.thingsboard.server.actors.TbEntityActorId;
|
||||
import org.thingsboard.server.actors.TbStringActorId;
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
public class CalculatedFieldManagerActorCreator extends ContextBasedCreator {
|
||||
|
||||
private final TenantId tenantId;
|
||||
|
||||
public CalculatedFieldManagerActorCreator(ActorSystemContext context, TenantId tenantId) {
|
||||
super(context);
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbActorId createActorId() {
|
||||
return new TbStringActorId("CFM|" + tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbActor createActor() {
|
||||
return new CalculatedFieldManagerActor(context, tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.TbActorCtx;
|
||||
import org.thingsboard.server.actors.TbActorRef;
|
||||
import org.thingsboard.server.actors.TbCalculatedFieldEntityActorId;
|
||||
import org.thingsboard.server.actors.service.DefaultActorService;
|
||||
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
|
||||
import org.thingsboard.server.common.data.id.AssetId;
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
||||
import org.thingsboard.server.service.profile.TbAssetProfileCache;
|
||||
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* @author Andrew Shvayka
|
||||
*/
|
||||
@Slf4j
|
||||
public class CalculatedFieldManagerMessageProcessor extends AbstractContextAwareMsgProcessor {
|
||||
|
||||
private final Map<CalculatedFieldId, CalculatedField> calculatedFields = new HashMap<>();
|
||||
private final Map<EntityId, List<CalculatedFieldCtx>> entityIdCalculatedFields = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<EntityId, List<CalculatedFieldLink>> entityIdCalculatedFieldLinks = new ConcurrentHashMap<>();
|
||||
|
||||
private final TbAssetProfileCache assetProfileCache;
|
||||
private final TbDeviceProfileCache deviceProfileCache;
|
||||
|
||||
protected TbActorCtx ctx;
|
||||
final TenantId tenantId;
|
||||
|
||||
CalculatedFieldManagerMessageProcessor(ActorSystemContext systemContext, TenantId tenantId) {
|
||||
super(systemContext);
|
||||
this.assetProfileCache = systemContext.getAssetProfileCache();
|
||||
this.deviceProfileCache = systemContext.getDeviceProfileCache();
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
void init(TbActorCtx ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public void onFieldInitMsg(CalculatedFieldInitMsg msg) {
|
||||
var cf = msg.getCf();
|
||||
calculatedFields.put(cf.getId(), cf);
|
||||
// We use copy on write lists to safely pass the reference to another actor for the iteration.
|
||||
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
|
||||
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>())
|
||||
.add(new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService()));
|
||||
msg.getCallback().onSuccess();
|
||||
}
|
||||
|
||||
public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) {
|
||||
var link = msg.getLink();
|
||||
// We use copy on write lists to safely pass the reference to another actor for the iteration.
|
||||
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
|
||||
entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link);
|
||||
msg.getCallback().onSuccess();
|
||||
}
|
||||
|
||||
public void onStateRestoreMsg(CalculatedFieldStateRestoreMsg msg) {
|
||||
if (calculatedFields.containsKey(msg.getId().cfId())) {
|
||||
getOrCreateActor(msg.getId().entityId()).tell(msg);
|
||||
} else {
|
||||
// TODO: remove state from storage
|
||||
}
|
||||
}
|
||||
|
||||
public void onTelemetryMsg(CalculatedFieldTelemetryMsg msg) {
|
||||
EntityId entityId = msg.getEntityId();
|
||||
var proto = msg.getProto();
|
||||
// process all cfs related to entity, or it's profile;
|
||||
var entityIdFields = getCalculatedFieldsByEntityId(entityId);
|
||||
var profileIdFields = getCalculatedFieldsByEntityId(getProfileId(tenantId, entityId));
|
||||
//TODO: Transfer only 'part' of the original callback.
|
||||
getOrCreateActor(entityId).tell(new EntityCalculatedFieldTelemetryMsg(msg, entityIdFields, profileIdFields, msg.getCallback()));
|
||||
// process all links (if any);
|
||||
var links = getCalculatedFieldLinksByEntityId(entityId);
|
||||
}
|
||||
|
||||
private List<CalculatedFieldCtx> getCalculatedFieldsByEntityId(EntityId entityId) {
|
||||
if (entityId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var result = entityIdCalculatedFields.get(entityId);
|
||||
if (result == null) {
|
||||
result = Collections.emptyList();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<CalculatedFieldLink> getCalculatedFieldLinksByEntityId(EntityId entityId) {
|
||||
if (entityId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var result = entityIdCalculatedFieldLinks.get(entityId);
|
||||
if (result == null) {
|
||||
result = Collections.emptyList();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private EntityId getProfileId(TenantId tenantId, EntityId entityId) {
|
||||
return switch (entityId.getEntityType()) {
|
||||
case ASSET -> assetProfileCache.get(tenantId, (AssetId) entityId).getId();
|
||||
case DEVICE -> deviceProfileCache.get(tenantId, (DeviceId) entityId).getId();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
protected TbActorRef getOrCreateActor(EntityId entityId) {
|
||||
return ctx.getOrCreateChildActor(new TbCalculatedFieldEntityActorId(entityId),
|
||||
() -> DefaultActorService.CF_ENTITY_DISPATCHER_NAME,
|
||||
() -> new CalculatedFieldEntityActorCreator(systemContext, tenantId, entityId),
|
||||
() -> true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
|
||||
|
||||
@Data
|
||||
public class CalculatedFieldStateRestoreMsg implements ToCalculatedFieldSystemMsg {
|
||||
|
||||
private final CalculatedFieldEntityCtxId id;
|
||||
private final CalculatedFieldState state;
|
||||
|
||||
@Override
|
||||
public MsgType getMsgType() {
|
||||
return MsgType.CF_STATE_RESTORE_MSG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TenantId getTenantId() {
|
||||
return id.tenantId();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||
|
||||
@Data
|
||||
public class CalculatedFieldTelemetryMsg implements ToCalculatedFieldSystemMsg {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final EntityId entityId;
|
||||
private final CalculatedFieldTelemetryMsgProto proto;
|
||||
private final TbCallback callback;
|
||||
|
||||
|
||||
@Override
|
||||
public MsgType getMsgType() {
|
||||
return MsgType.CF_TELEMETRY_MSG;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class EntityCalculatedFieldTelemetryMsg implements ToCalculatedFieldSystemMsg {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final EntityId entityId;
|
||||
private final CalculatedFieldTelemetryMsgProto proto;
|
||||
private final List<CalculatedFieldCtx> entityIdFields;
|
||||
private final List<CalculatedFieldCtx> profileIdFields;
|
||||
private final TbCallback callback;
|
||||
|
||||
public EntityCalculatedFieldTelemetryMsg(CalculatedFieldTelemetryMsg msg,
|
||||
List<CalculatedFieldCtx> entityIdFields,
|
||||
List<CalculatedFieldCtx> profileIdFields,
|
||||
TbCallback callback) {
|
||||
this.tenantId = msg.getTenantId();
|
||||
this.entityId = msg.getEntityId();
|
||||
this.proto = msg.getProto();
|
||||
this.entityIdFields = entityIdFields;
|
||||
this.profileIdFields = profileIdFields;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MsgType getMsgType() {
|
||||
return MsgType.CF_ENTITY_TELEMETRY_MSG;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.calculatedField;
|
||||
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class MultipleTbCallback implements TbCallback {
|
||||
|
||||
private final AtomicInteger counter;
|
||||
private final TbCallback callback;
|
||||
|
||||
public MultipleTbCallback(int count, TbCallback callback) {
|
||||
this.counter = new AtomicInteger(count);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (counter.decrementAndGet() <= 0) {
|
||||
callback.onSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
public void onSuccess(int number) {
|
||||
if (counter.addAndGet(-number) <= 0) {
|
||||
callback.onSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
@ -49,6 +49,8 @@ public class DefaultActorService extends TbApplicationEventListener<PartitionCha
|
||||
public static final String TENANT_DISPATCHER_NAME = "tenant-dispatcher";
|
||||
public static final String DEVICE_DISPATCHER_NAME = "device-dispatcher";
|
||||
public static final String RULE_DISPATCHER_NAME = "rule-dispatcher";
|
||||
public static final String CF_MANAGER_DISPATCHER_NAME = "cf-manager-dispatcher";
|
||||
public static final String CF_ENTITY_DISPATCHER_NAME = "cf-entity-dispatcher";
|
||||
|
||||
@Autowired
|
||||
private ActorSystemContext actorContext;
|
||||
@ -78,6 +80,13 @@ public class DefaultActorService extends TbApplicationEventListener<PartitionCha
|
||||
@Value("${actors.system.rule_dispatcher_pool_size:8}")
|
||||
private int ruleDispatcherSize;
|
||||
|
||||
@Value("${actors.system.cfm_dispatcher_pool_size:2}")
|
||||
private int calculatedFieldManagerDispatcherSize;
|
||||
|
||||
@Value("${actors.system.cfe_dispatcher_pool_size:8}")
|
||||
private int calculatedFieldEntityDispatcherSize;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void initActorSystem() {
|
||||
log.info("Initializing actor system.");
|
||||
@ -89,6 +98,8 @@ public class DefaultActorService extends TbApplicationEventListener<PartitionCha
|
||||
system.createDispatcher(TENANT_DISPATCHER_NAME, initDispatcherExecutor(TENANT_DISPATCHER_NAME, tenantDispatcherSize));
|
||||
system.createDispatcher(DEVICE_DISPATCHER_NAME, initDispatcherExecutor(DEVICE_DISPATCHER_NAME, deviceDispatcherSize));
|
||||
system.createDispatcher(RULE_DISPATCHER_NAME, initDispatcherExecutor(RULE_DISPATCHER_NAME, ruleDispatcherSize));
|
||||
system.createDispatcher(CF_MANAGER_DISPATCHER_NAME, initDispatcherExecutor(CF_MANAGER_DISPATCHER_NAME, calculatedFieldManagerDispatcherSize));
|
||||
system.createDispatcher(CF_ENTITY_DISPATCHER_NAME, initDispatcherExecutor(CF_ENTITY_DISPATCHER_NAME, calculatedFieldEntityDispatcherSize));
|
||||
|
||||
actorContext.setActorSystem(system);
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ import org.thingsboard.server.actors.TbActorNotRegisteredException;
|
||||
import org.thingsboard.server.actors.TbActorRef;
|
||||
import org.thingsboard.server.actors.TbEntityActorId;
|
||||
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
|
||||
import org.thingsboard.server.actors.TbStringActorId;
|
||||
import org.thingsboard.server.actors.calculatedField.CalculatedFieldManagerActorCreator;
|
||||
import org.thingsboard.server.actors.device.DeviceActorCreator;
|
||||
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator;
|
||||
@ -44,6 +46,7 @@ import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.TbActorMsg;
|
||||
import org.thingsboard.server.common.msg.TbActorStopReason;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
|
||||
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
|
||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
|
||||
@ -64,8 +67,8 @@ public class TenantActor extends RuleChainManagerActor {
|
||||
private boolean isRuleEngine;
|
||||
private boolean isCore;
|
||||
private ApiUsageState apiUsageState;
|
||||
|
||||
private Set<DeviceId> deletedDevices;
|
||||
private TbActorRef cfActor;
|
||||
|
||||
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
|
||||
super(systemContext, tenantId);
|
||||
@ -95,6 +98,11 @@ public class TenantActor extends RuleChainManagerActor {
|
||||
} else {
|
||||
log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
|
||||
}
|
||||
//TODO: IM - extend API usage to have CF Exec Enabled? Not in 4.0;
|
||||
cfActor = ctx.getOrCreateChildActor(new TbStringActorId("CFM|" + tenantId),
|
||||
() -> DefaultActorService.CF_MANAGER_DISPATCHER_NAME,
|
||||
() -> new CalculatedFieldManagerActorCreator(systemContext, tenantId),
|
||||
() -> true);
|
||||
} catch (Exception e) {
|
||||
log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e);
|
||||
cantFindTenant = true;
|
||||
@ -159,12 +167,31 @@ public class TenantActor extends RuleChainManagerActor {
|
||||
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
|
||||
onRuleChainMsg((RuleChainAwareMsg) msg);
|
||||
break;
|
||||
case CF_INIT_MSG:
|
||||
case CF_LINK_INIT_MSG:
|
||||
case CF_STATE_RESTORE_MSG:
|
||||
case CF_UPDATE_MSG:
|
||||
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);
|
||||
break;
|
||||
case CF_TELEMETRY_MSG:
|
||||
case CF_LINKED_TELEMETRY_MSG:
|
||||
case CF_ENTITY_UPDATE_MSG:
|
||||
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, false);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) {
|
||||
if (priority) {
|
||||
cfActor.tellWithHighPriority(msg);
|
||||
} else {
|
||||
cfActor.tell(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMyPartition(EntityId entityId) {
|
||||
return systemContext.resolve(ServiceType.TB_CORE, tenantId, entityId).isMyPartition();
|
||||
}
|
||||
|
||||
@ -15,14 +15,22 @@
|
||||
*/
|
||||
package org.thingsboard.server.service.cf;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
|
||||
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.kv.TimeseriesSaveResult;
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldEntityUpdateMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.ComponentLifecycleMsgProto;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -30,16 +38,17 @@ public interface CalculatedFieldExecutionService {
|
||||
|
||||
/**
|
||||
* Filter CFs based on the request entity. Push to the queue if any matching CF exist;
|
||||
* @param request - telemetry save request;
|
||||
* @param request - telemetry save result;
|
||||
*
|
||||
* @param request - telemetry save request;
|
||||
* @param callback
|
||||
*/
|
||||
void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result);
|
||||
void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result, FutureCallback<Void> callback);
|
||||
|
||||
void pushRequestToQueue(AttributesSaveRequest request, List<Long> result);
|
||||
void pushRequestToQueue(AttributesSaveRequest request, List<Long> result, FutureCallback<Void> callback);
|
||||
|
||||
void onTelemetryMsg(CalculatedFieldTelemetryMsgProto msg, TbCallback callback);
|
||||
void pushStateToStorage(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback);
|
||||
|
||||
void onLinkedTelemetryMsg(CalculatedFieldLinkedTelemetryMsgProto linkedMsg, TbCallback callback);
|
||||
ListenableFuture<CalculatedFieldState> fetchStateFromDb(CalculatedFieldCtx ctx, EntityId entityId);
|
||||
|
||||
// void pushEntityUpdateMsg(TransportProtos.CalculatedFieldEntityUpdateMsgProto proto, TbCallback callback);
|
||||
|
||||
@ -53,4 +62,6 @@ public interface CalculatedFieldExecutionService {
|
||||
|
||||
void onEntityUpdateMsg(CalculatedFieldEntityUpdateMsgProto proto, TbCallback callback);
|
||||
|
||||
void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, CalculatedFieldResult calculationResult, List<CalculatedFieldId> cfIds, TbCallback callback);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.cf;
|
||||
|
||||
public interface CalculatedFieldInitService {
|
||||
}
|
||||
@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.script.api.tbel.TbelInvokeService;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.asset.Asset;
|
||||
@ -36,6 +37,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
import org.thingsboard.server.dao.cf.CalculatedFieldService;
|
||||
import org.thingsboard.server.dao.device.DeviceService;
|
||||
|
||||
@ -17,7 +17,6 @@ package org.thingsboard.server.service.cf;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.OutputType;
|
||||
import org.thingsboard.server.common.data.id.AssetId;
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
@ -63,7 +61,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
|
||||
import org.thingsboard.server.common.data.kv.TimeseriesSaveResult;
|
||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||
import org.thingsboard.server.common.data.msg.TbMsgType;
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||
import org.thingsboard.server.common.msg.queue.ServiceType;
|
||||
@ -108,12 +105,13 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
@ -167,7 +165,6 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
Math.max(4, Runtime.getRuntime().availableProcessors()), "calculated-field"));
|
||||
calculatedFieldCallbackExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(
|
||||
Math.max(4, Runtime.getRuntime().availableProcessors()), "calculated-field-callback"));
|
||||
scheduledExecutor.submit(() -> states.putAll(stateService.restoreStates()));
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@ -192,22 +189,22 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result) {
|
||||
public void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result, FutureCallback<Void> callback) {
|
||||
var tenantId = request.getTenantId();
|
||||
var entityId = request.getEntityId();
|
||||
//TODO: 1. check that request entity has calculated fields for entity or profile. If yes - push to corresponding partitions;
|
||||
//TODO: 2. check that request entity has calculated field links. If yes - push to corresponding partitions;
|
||||
//TODO: in 1 and 2 we should do the check as quick as possible. Should we also check the field/link keys?;
|
||||
checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matches(request.getEntries()), cf -> cf.linkMatches(entityId, request.getEntries()),
|
||||
() -> toCalculatedFieldTelemetryMsgProto(request, result), request.getCallback());
|
||||
() -> toCalculatedFieldTelemetryMsgProto(request, result), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushRequestToQueue(AttributesSaveRequest request, List<Long> result) {
|
||||
public void pushRequestToQueue(AttributesSaveRequest request, List<Long> result, FutureCallback<Void> callback) {
|
||||
var tenantId = request.getTenantId();
|
||||
var entityId = request.getEntityId();
|
||||
checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matches(request.getEntries(), request.getScope()), cf -> cf.linkMatches(entityId, request.getEntries(), request.getScope()),
|
||||
() -> toCalculatedFieldTelemetryMsgProto(request, result), request.getCallback());
|
||||
() -> toCalculatedFieldTelemetryMsgProto(request, result), callback);
|
||||
}
|
||||
|
||||
private void checkEntityAndPushToQueue(TenantId tenantId, EntityId entityId,
|
||||
@ -241,77 +238,84 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTelemetryMsg(CalculatedFieldTelemetryMsgProto msg, TbCallback callback) {
|
||||
|
||||
callback.onSuccess();
|
||||
public ListenableFuture<CalculatedFieldState> fetchStateFromDb(CalculatedFieldCtx ctx, EntityId entityId) {
|
||||
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
||||
for (var entry : ctx.getArguments().entrySet()) {
|
||||
var argEntityId = entry.getValue().getRefEntityId() != null ? entry.getValue().getRefEntityId() : entityId;
|
||||
var argValueFuture = fetchKvEntry(ctx.getTenantId(), argEntityId, entry.getValue());
|
||||
argFutures.put(entry.getKey(), argValueFuture);
|
||||
}
|
||||
return Futures.whenAllComplete(argFutures.values()).call(() -> {
|
||||
var result = createStateByType(ctx.getCfType());
|
||||
result.updateState(argFutures.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Entry::getKey, // Keep the key as is
|
||||
entry -> {
|
||||
try {
|
||||
// Resolve the future to get the value
|
||||
return entry.getValue().get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException("Error getting future result for key: " + entry.getKey(), e);
|
||||
}
|
||||
}
|
||||
)));
|
||||
return result;
|
||||
}, calculatedFieldCallbackExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkedTelemetryMsg(CalculatedFieldLinkedTelemetryMsgProto linkedMsg, TbCallback callback) {
|
||||
|
||||
callback.onSuccess();
|
||||
public void pushStateToStorage(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback) {
|
||||
stateService.persistState(stateId, state, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<TopicPartitionInfo, List<ListenableFuture<?>>> onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
|
||||
var result = new HashMap<TopicPartitionInfo, List<ListenableFuture<?>>>();
|
||||
PageDataIterable<CalculatedField> cfs = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFields, initFetchPackSize);
|
||||
Map<TopicPartitionInfo, List<CalculatedFieldEntityCtxId>> tpiTargetEntityMap = new HashMap<>();
|
||||
|
||||
for (CalculatedField cf : cfs) {
|
||||
|
||||
Consumer<EntityId> resolvePartition = entityId -> {
|
||||
TopicPartitionInfo tpi;
|
||||
try {
|
||||
tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, cf.getTenantId(), entityId);
|
||||
if (addedPartitions.contains(tpi) && states.keySet().stream().noneMatch(ctxId -> ctxId.cfId().equals(cf.getId()))) {
|
||||
tpiTargetEntityMap.computeIfAbsent(tpi, k -> new ArrayList<>()).add(new CalculatedFieldEntityCtxId(cf.getId(), entityId));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to resolve partition for CalculatedFieldEntityCtxId: entityId=[{}], tenantId=[{}]. Reason: {}",
|
||||
entityId, cf.getTenantId(), e.getMessage());
|
||||
}
|
||||
};
|
||||
|
||||
EntityId cfEntityId = cf.getEntityId();
|
||||
if (isProfileEntity(cfEntityId)) {
|
||||
calculatedFieldCache.getEntitiesByProfile(cf.getTenantId(), cfEntityId).forEach(resolvePartition);
|
||||
} else {
|
||||
resolvePartition.accept(cfEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
for (var entry : tpiTargetEntityMap.entrySet()) {
|
||||
for (List<CalculatedFieldEntityCtxId> partition : Lists.partition(entry.getValue(), 1000)) {
|
||||
log.info("[{}] Submit task for CalculatedFields: {}", entry.getKey(), partition.size());
|
||||
var future = calculatedFieldExecutor.submit(() -> {
|
||||
try {
|
||||
for (CalculatedFieldEntityCtxId ctxId : partition) {
|
||||
restoreState(ctxId.cfId(), ctxId.entityId());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.error("Unexpected exception while restoring CalculatedField states", t);
|
||||
throw t;
|
||||
}
|
||||
});
|
||||
result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(future);
|
||||
}
|
||||
}
|
||||
// PageDataIterable<CalculatedField> cfs = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFields, initFetchPackSize);
|
||||
// Map<TopicPartitionInfo, List<CalculatedFieldEntityCtxId>> tpiTargetEntityMap = new HashMap<>();
|
||||
//
|
||||
// for (CalculatedField cf : cfs) {
|
||||
//
|
||||
// Consumer<EntityId> resolvePartition = entityId -> {
|
||||
// TopicPartitionInfo tpi;
|
||||
// try {
|
||||
// tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, cf.getTenantId(), entityId);
|
||||
// if (addedPartitions.contains(tpi) && states.keySet().stream().noneMatch(ctxId -> ctxId.cfId().equals(cf.getId()))) {
|
||||
// tpiTargetEntityMap.computeIfAbsent(tpi, k -> new ArrayList<>()).add(new CalculatedFieldEntityCtxId(cf.getId(), entityId));
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.warn("Failed to resolve partition for CalculatedFieldEntityCtxId: entityId=[{}], tenantId=[{}]. Reason: {}",
|
||||
// entityId, cf.getTenantId(), e.getMessage());
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// EntityId cfEntityId = cf.getEntityId();
|
||||
// if (isProfileEntity(cfEntityId)) {
|
||||
// calculatedFieldCache.getEntitiesByProfile(cf.getTenantId(), cfEntityId).forEach(resolvePartition);
|
||||
// } else {
|
||||
// resolvePartition.accept(cfEntityId);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (var entry : tpiTargetEntityMap.entrySet()) {
|
||||
// for (List<CalculatedFieldEntityCtxId> partition : Lists.partition(entry.getValue(), 1000)) {
|
||||
// log.info("[{}] Submit task for CalculatedFields: {}", entry.getKey(), partition.size());
|
||||
// var future = calculatedFieldExecutor.submit(() -> {
|
||||
// try {
|
||||
// for (CalculatedFieldEntityCtxId ctxId : partition) {
|
||||
// restoreState(ctxId.cfId(), ctxId.entityId());
|
||||
// }
|
||||
// } catch (Throwable t) {
|
||||
// log.error("Unexpected exception while restoring CalculatedField states", t);
|
||||
// throw t;
|
||||
// }
|
||||
// });
|
||||
// result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(future);
|
||||
// }
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
private void restoreState(CalculatedFieldId calculatedFieldId, EntityId entityId) {
|
||||
CalculatedFieldEntityCtxId ctxId = new CalculatedFieldEntityCtxId(calculatedFieldId, entityId);
|
||||
CalculatedFieldEntityCtx restoredCtx = stateService.restoreState(ctxId);
|
||||
|
||||
if (restoredCtx != null) {
|
||||
states.put(ctxId, restoredCtx);
|
||||
log.info("Restored state for CalculatedField [{}]", calculatedFieldId);
|
||||
} else {
|
||||
log.warn("No state found for CalculatedField [{}], entity [{}].", calculatedFieldId, entityId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanupEntityOnPartitionRemoval(CalculatedFieldId entityId) {
|
||||
cleanupEntity(entityId);
|
||||
@ -491,7 +495,7 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
}
|
||||
} else {
|
||||
List<CalculatedFieldEntityCtxId> ctxIds = tpiStates.computeIfAbsent(targetEntityTpi, k -> new ArrayList<>());
|
||||
ctxIds.add(new CalculatedFieldEntityCtxId(ctx.getCfId(), targetEntity));
|
||||
ctxIds.add(new CalculatedFieldEntityCtxId(ctx.getTenantId(), ctx.getCfId(), targetEntity));
|
||||
}
|
||||
}
|
||||
|
||||
@ -525,7 +529,7 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
Map<String, ArgumentEntry> argumentValues = updatedTelemetry.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> ArgumentEntry.createSingleValueArgument(entry.getValue())));
|
||||
|
||||
updateOrInitializeState(cfCtx, entityId, argumentValues, previousCalculatedFieldIds);
|
||||
// updateOrInitializeState(cfCtx, entityId, argumentValues, previousCalculatedFieldIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -569,9 +573,6 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
|
||||
private void clearState(CalculatedFieldId calculatedFieldId, EntityId entityId) {
|
||||
log.warn("Executing clearState, calculatedFieldId=[{}], entityId=[{}]", calculatedFieldId, entityId);
|
||||
CalculatedFieldEntityCtxId ctxId = new CalculatedFieldEntityCtxId(calculatedFieldId, entityId);
|
||||
states.remove(ctxId);
|
||||
stateService.removeState(ctxId);
|
||||
}
|
||||
|
||||
private void initializeStateForEntityByProfile(EntityId entityId, EntityId profileId, TbCallback callback) {
|
||||
@ -601,7 +602,7 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<ArgumentEntry> results) {
|
||||
updateOrInitializeState(calculatedFieldCtx, entityId, argumentValues, new ArrayList<>());
|
||||
// updateOrInitializeState(calculatedFieldCtx, entityId, argumentValues, new ArrayList<>());
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@ -613,96 +614,83 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
}, calculatedFieldCallbackExecutor);
|
||||
}
|
||||
|
||||
private void updateOrInitializeState(CalculatedFieldCtx calculatedFieldCtx, EntityId entityId, Map<String, ArgumentEntry> argumentValues, List<CalculatedFieldId> previousCalculatedFieldIds) {
|
||||
CalculatedFieldId cfId = calculatedFieldCtx.getCfId();
|
||||
Map<String, ArgumentEntry> argumentsMap = new HashMap<>(argumentValues);
|
||||
// private void updateOrInitializeState(CalculatedFieldCtx calculatedFieldCtx, EntityId entityId, Map<String, ArgumentEntry> argumentValues, List<CalculatedFieldId> previousCalculatedFieldIds) {
|
||||
// CalculatedFieldId cfId = calculatedFieldCtx.getCfId();
|
||||
// Map<String, ArgumentEntry> argumentsMap = new HashMap<>(argumentValues);
|
||||
//
|
||||
// CalculatedFieldEntityCtxId entityCtxId = new CalculatedFieldEntityCtxId(cfId, entityId);
|
||||
//
|
||||
// states.compute(entityCtxId, (ctxId, ctx) -> {
|
||||
// CalculatedFieldEntityCtx calculatedFieldEntityCtx = ctx != null ? ctx : fetchCalculatedFieldEntityState(ctxId, calculatedFieldCtx.getCfType());
|
||||
//
|
||||
// CompletableFuture<Void> updateFuture = new CompletableFuture<>();
|
||||
//
|
||||
// Consumer<CalculatedFieldState> performUpdateState = (state) -> {
|
||||
// if (state.updateState(argumentsMap)) {
|
||||
// calculatedFieldEntityCtx.setState(state);
|
||||
// stateService.persistState(entityCtxId, calculatedFieldEntityCtx);
|
||||
// Map<String, ArgumentEntry> arguments = state.getArguments();
|
||||
// boolean allArgsPresent = arguments.keySet().containsAll(calculatedFieldCtx.getArguments().keySet()) &&
|
||||
// !arguments.containsValue(SingleValueArgumentEntry.EMPTY) && !arguments.containsValue(TsRollingArgumentEntry.EMPTY);
|
||||
// if (allArgsPresent) {
|
||||
// performCalculation(calculatedFieldCtx, state, entityId, previousCalculatedFieldIds);
|
||||
// }
|
||||
// log.info("Successfully updated state: calculatedFieldId=[{}], entityId=[{}]", calculatedFieldCtx.getCfId(), entityId);
|
||||
// }
|
||||
// updateFuture.complete(null);
|
||||
// };
|
||||
//
|
||||
// CalculatedFieldState state = calculatedFieldEntityCtx.getState();
|
||||
//
|
||||
// boolean allKeysPresent = argumentsMap.keySet().containsAll(calculatedFieldCtx.getArguments().keySet());
|
||||
// boolean requiresTsRollingUpdate = calculatedFieldCtx.getArguments().values().stream()
|
||||
// .anyMatch(argument -> ArgumentType.TS_ROLLING.equals(argument.getRefEntityKey().getType()) && state.getArguments().get(argument.getRefEntityKey().getKey()) == null);
|
||||
//
|
||||
// if (!allKeysPresent || requiresTsRollingUpdate) {
|
||||
// Map<String, Argument> missingArguments = calculatedFieldCtx.getArguments().entrySet().stream()
|
||||
// .filter(entry -> !argumentsMap.containsKey(entry.getKey()) || (ArgumentType.TS_ROLLING.equals(entry.getValue().getRefEntityKey().getType()) && state.getArguments().get(entry.getKey()) == null))
|
||||
// .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
//
|
||||
// fetchArguments(calculatedFieldCtx.getTenantId(), entityId, missingArguments, argumentsMap::putAll)
|
||||
// .addListener(() -> performUpdateState.accept(state),
|
||||
// calculatedFieldCallbackExecutor);
|
||||
// } else {
|
||||
// performUpdateState.accept(state);
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// updateFuture.join();
|
||||
// } catch (Exception e) {
|
||||
// log.trace("Failed to update state for ctxId [{}].", ctxId, e);
|
||||
// throw new RuntimeException("Failed to update or initialize state.", e);
|
||||
// }
|
||||
//
|
||||
// return calculatedFieldEntityCtx;
|
||||
// });
|
||||
// }
|
||||
|
||||
CalculatedFieldEntityCtxId entityCtxId = new CalculatedFieldEntityCtxId(cfId, entityId);
|
||||
|
||||
states.compute(entityCtxId, (ctxId, ctx) -> {
|
||||
CalculatedFieldEntityCtx calculatedFieldEntityCtx = ctx != null ? ctx : fetchCalculatedFieldEntityState(ctxId, calculatedFieldCtx.getCfType());
|
||||
|
||||
CompletableFuture<Void> updateFuture = new CompletableFuture<>();
|
||||
|
||||
Consumer<CalculatedFieldState> performUpdateState = (state) -> {
|
||||
if (state.updateState(argumentsMap)) {
|
||||
calculatedFieldEntityCtx.setState(state);
|
||||
stateService.persistState(entityCtxId, calculatedFieldEntityCtx);
|
||||
Map<String, ArgumentEntry> arguments = state.getArguments();
|
||||
boolean allArgsPresent = arguments.keySet().containsAll(calculatedFieldCtx.getArguments().keySet()) &&
|
||||
!arguments.containsValue(SingleValueArgumentEntry.EMPTY) && !arguments.containsValue(TsRollingArgumentEntry.EMPTY);
|
||||
if (allArgsPresent) {
|
||||
performCalculation(calculatedFieldCtx, state, entityId, previousCalculatedFieldIds);
|
||||
}
|
||||
log.info("Successfully updated state: calculatedFieldId=[{}], entityId=[{}]", calculatedFieldCtx.getCfId(), entityId);
|
||||
}
|
||||
updateFuture.complete(null);
|
||||
};
|
||||
|
||||
CalculatedFieldState state = calculatedFieldEntityCtx.getState();
|
||||
|
||||
boolean allKeysPresent = argumentsMap.keySet().containsAll(calculatedFieldCtx.getArguments().keySet());
|
||||
boolean requiresTsRollingUpdate = calculatedFieldCtx.getArguments().values().stream()
|
||||
.anyMatch(argument -> ArgumentType.TS_ROLLING.equals(argument.getRefEntityKey().getType()) && state.getArguments().get(argument.getRefEntityKey().getKey()) == null);
|
||||
|
||||
if (!allKeysPresent || requiresTsRollingUpdate) {
|
||||
Map<String, Argument> missingArguments = calculatedFieldCtx.getArguments().entrySet().stream()
|
||||
.filter(entry -> !argumentsMap.containsKey(entry.getKey()) || (ArgumentType.TS_ROLLING.equals(entry.getValue().getRefEntityKey().getType()) && state.getArguments().get(entry.getKey()) == null))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
fetchArguments(calculatedFieldCtx.getTenantId(), entityId, missingArguments, argumentsMap::putAll)
|
||||
.addListener(() -> performUpdateState.accept(state),
|
||||
calculatedFieldCallbackExecutor);
|
||||
} else {
|
||||
performUpdateState.accept(state);
|
||||
}
|
||||
|
||||
try {
|
||||
updateFuture.join();
|
||||
} catch (Exception e) {
|
||||
log.trace("Failed to update state for ctxId [{}].", ctxId, e);
|
||||
throw new RuntimeException("Failed to update or initialize state.", e);
|
||||
}
|
||||
|
||||
return calculatedFieldEntityCtx;
|
||||
});
|
||||
}
|
||||
|
||||
private void performCalculation(CalculatedFieldCtx calculatedFieldCtx, CalculatedFieldState state, EntityId entityId, List<CalculatedFieldId> previousCalculatedFieldIds) {
|
||||
ListenableFuture<CalculatedFieldResult> resultFuture = state.performCalculation(calculatedFieldCtx);
|
||||
Futures.addCallback(resultFuture, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(CalculatedFieldResult result) {
|
||||
if (result != null) {
|
||||
pushMsgToRuleEngine(calculatedFieldCtx.getTenantId(), calculatedFieldCtx.getCfId(), entityId, result, previousCalculatedFieldIds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
log.warn("[{}] Failed to perform calculation. entityId: [{}]", calculatedFieldCtx.getCfId(), entityId, t);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private void pushMsgToRuleEngine(TenantId tenantId, CalculatedFieldId calculatedFieldId, EntityId originatorId, CalculatedFieldResult calculatedFieldResult, List<CalculatedFieldId> previousCalculatedFieldIds) {
|
||||
@Override
|
||||
public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, CalculatedFieldResult calculatedFieldResult, List<CalculatedFieldId> cfIds, TbCallback callback) {
|
||||
try {
|
||||
OutputType type = calculatedFieldResult.getType();
|
||||
TbMsgType msgType = OutputType.ATTRIBUTES.equals(type) ? TbMsgType.POST_ATTRIBUTES_REQUEST : TbMsgType.POST_TELEMETRY_REQUEST;
|
||||
TbMsgMetaData md = OutputType.ATTRIBUTES.equals(type) ? new TbMsgMetaData(Map.of(SCOPE, calculatedFieldResult.getScope().name())) : TbMsgMetaData.EMPTY;
|
||||
ObjectNode payload = createJsonPayload(calculatedFieldResult);
|
||||
if (previousCalculatedFieldIds != null && previousCalculatedFieldIds.contains(calculatedFieldId)) {
|
||||
throw new IllegalArgumentException("Calculated field [" + calculatedFieldId.getId() + "] refers to itself, causing an infinite loop.");
|
||||
}
|
||||
List<CalculatedFieldId> calculatedFieldIds = previousCalculatedFieldIds != null
|
||||
? new ArrayList<>(previousCalculatedFieldIds)
|
||||
: new ArrayList<>();
|
||||
calculatedFieldIds.add(calculatedFieldId);
|
||||
TbMsg msg = TbMsg.newMsg().type(msgType).originator(originatorId).previousCalculatedFieldIds(calculatedFieldIds).metaData(md).data(JacksonUtil.writeValueAsString(payload)).build();
|
||||
clusterService.pushMsgToRuleEngine(tenantId, originatorId, msg, null);
|
||||
log.info("Pushed message to rule engine: originatorId=[{}]", originatorId);
|
||||
TbMsg msg = TbMsg.newMsg().type(msgType).originator(entityId).previousCalculatedFieldIds(cfIds).metaData(md).data(JacksonUtil.writeValueAsString(payload)).build();
|
||||
clusterService.pushMsgToRuleEngine(tenantId, entityId, msg, new TbQueueCallback() {
|
||||
@Override
|
||||
public void onSuccess(TbQueueMsgMetadata metadata) {
|
||||
callback.onSuccess();
|
||||
log.trace("[{}][{}] Pushed message to rule engine: {} ", tenantId, entityId, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.warn("[{}] Failed to push message to rule engine. CalculatedFieldResult: {}", originatorId, calculatedFieldResult, e);
|
||||
log.warn("[{}][{}] Failed to push message to rule engine. CalculatedFieldResult: {}", tenantId, entityId, calculatedFieldResult, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -781,15 +769,6 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
|
||||
return new StringDataEntry(key, defaultValue);
|
||||
}
|
||||
|
||||
private CalculatedFieldEntityCtx fetchCalculatedFieldEntityState(CalculatedFieldEntityCtxId entityCtxId, CalculatedFieldType cfType) {
|
||||
CalculatedFieldEntityCtx state = stateService.restoreState(entityCtxId);
|
||||
|
||||
if (state == null) {
|
||||
return new CalculatedFieldEntityCtx(entityCtxId, createStateByType(cfType));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private ObjectNode createJsonPayload(CalculatedFieldResult calculatedFieldResult) {
|
||||
ObjectNode payload = JacksonUtil.newObjectNode();
|
||||
Map<String, Object> resultMap = calculatedFieldResult.getResultMap();
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.cf;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.calculatedField.CalculatedFieldStateRestoreMsg;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
|
||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
|
||||
import org.thingsboard.server.dao.cf.CalculatedFieldService;
|
||||
import org.thingsboard.server.queue.util.AfterStartUp;
|
||||
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldStateService;
|
||||
|
||||
@Service
|
||||
@TbRuleEngineComponent
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultCalculatedFieldInitService implements CalculatedFieldInitService {
|
||||
|
||||
private final CalculatedFieldService calculatedFieldService;
|
||||
private final CalculatedFieldStateService stateService;
|
||||
private final ActorSystemContext actorSystemContext;
|
||||
|
||||
@Value("${calculated_fields.init_fetch_pack_size:50000}")
|
||||
@Getter
|
||||
private int initFetchPackSize;
|
||||
|
||||
@AfterStartUp(order = AfterStartUp.CF_INIT_SERVICE)
|
||||
public void initCalculatedFieldDefinitions() {
|
||||
PageDataIterable<CalculatedField> cfs = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFields, initFetchPackSize);
|
||||
cfs.forEach(cf -> actorSystemContext.tell(new CalculatedFieldInitMsg(cf.getTenantId(), cf)));
|
||||
PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFieldLinks, initFetchPackSize);
|
||||
cfls.forEach(link -> actorSystemContext.tell(new CalculatedFieldLinkInitMsg(link.getTenantId(), link)));
|
||||
//TODO: combine with the DefaultCalculatedFieldCache.
|
||||
|
||||
}
|
||||
|
||||
@AfterStartUp(order = AfterStartUp.CF_STATE_RESTORE_SERVICE)
|
||||
public void initCalculatedFieldStates() {
|
||||
stateService.restoreStates().forEach((k, v) -> actorSystemContext.tell(new CalculatedFieldStateRestoreMsg(k, v)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -17,6 +17,7 @@ package org.thingsboard.server.service.cf.ctx;
|
||||
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
public record CalculatedFieldEntityCtxId(CalculatedFieldId cfId, EntityId entityId) {
|
||||
public record CalculatedFieldEntityCtxId(TenantId tenantId, CalculatedFieldId cfId, EntityId entityId) {
|
||||
}
|
||||
|
||||
@ -15,15 +15,18 @@
|
||||
*/
|
||||
package org.thingsboard.server.service.cf.ctx;
|
||||
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface CalculatedFieldStateService {
|
||||
|
||||
Map<CalculatedFieldEntityCtxId, CalculatedFieldEntityCtx> restoreStates();
|
||||
Map<CalculatedFieldEntityCtxId, CalculatedFieldState> restoreStates();
|
||||
|
||||
CalculatedFieldEntityCtx restoreState(CalculatedFieldEntityCtxId ctxId);
|
||||
CalculatedFieldState restoreState(CalculatedFieldEntityCtxId ctxId);
|
||||
|
||||
void persistState(CalculatedFieldEntityCtxId ctxId, CalculatedFieldEntityCtx state);
|
||||
void persistState(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback);
|
||||
|
||||
void removeState(CalculatedFieldEntityCtxId ctxId);
|
||||
|
||||
|
||||
@ -66,4 +66,9 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
|
||||
return stateUpdated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
//TODO: IM
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||
import org.thingsboard.server.common.data.util.TbPair;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
@ -44,4 +44,5 @@ public interface CalculatedFieldState {
|
||||
|
||||
ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx);
|
||||
|
||||
boolean isReady();
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
import org.thingsboard.server.service.cf.RocksDBService;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtx;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||
@ -36,24 +37,25 @@ public class RocksDBStateService implements CalculatedFieldStateService {
|
||||
private final RocksDBService rocksDBService;
|
||||
|
||||
@Override
|
||||
public Map<CalculatedFieldEntityCtxId, CalculatedFieldEntityCtx> restoreStates() {
|
||||
public Map<CalculatedFieldEntityCtxId, CalculatedFieldState> restoreStates() {
|
||||
return rocksDBService.getAll().entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
entry -> JacksonUtil.fromString(entry.getKey(), CalculatedFieldEntityCtxId.class),
|
||||
entry -> JacksonUtil.fromString(entry.getValue(), CalculatedFieldEntityCtx.class)
|
||||
entry -> JacksonUtil.fromString(entry.getValue(), CalculatedFieldState.class)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculatedFieldEntityCtx restoreState(CalculatedFieldEntityCtxId ctxId) {
|
||||
public CalculatedFieldState restoreState(CalculatedFieldEntityCtxId ctxId) {
|
||||
return Optional.ofNullable(rocksDBService.get(JacksonUtil.writeValueAsString(ctxId)))
|
||||
.map(storedState -> JacksonUtil.fromString(storedState, CalculatedFieldEntityCtx.class))
|
||||
.map(storedState -> JacksonUtil.fromString(storedState, CalculatedFieldState.class))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persistState(CalculatedFieldEntityCtxId ctxId, CalculatedFieldEntityCtx state) {
|
||||
rocksDBService.put(JacksonUtil.writeValueAsString(ctxId), JacksonUtil.writeValueAsString(state));
|
||||
public void persistState(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback){
|
||||
rocksDBService.put(JacksonUtil.writeValueAsString(stateId), JacksonUtil.writeValueAsString(state));
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -21,6 +21,10 @@ import lombok.NoArgsConstructor;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||
import org.thingsboard.server.common.util.KvProtoUtil;
|
||||
import org.thingsboard.server.common.util.ProtoUtils;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@ -34,6 +38,18 @@ public class SingleValueArgumentEntry implements ArgumentEntry {
|
||||
|
||||
private Long version;
|
||||
|
||||
public SingleValueArgumentEntry(TsKvProto entry) {
|
||||
this.ts = entry.getTs();
|
||||
this.version = entry.getVersion();
|
||||
this.value = ProtoUtils.fromProto(entry).getValue();
|
||||
}
|
||||
|
||||
public SingleValueArgumentEntry(AttributeValueProto entry) {
|
||||
this.ts = entry.getLastUpdateTs();
|
||||
this.version = entry.getVersion();
|
||||
this.value = ProtoUtils.fromProto(entry).getValue();
|
||||
}
|
||||
|
||||
public SingleValueArgumentEntry(KvEntry entry) {
|
||||
if (entry instanceof TsKvEntry tsKvEntry) {
|
||||
this.ts = tsKvEntry.getTs();
|
||||
|
||||
@ -21,8 +21,6 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@ -30,23 +28,20 @@ import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.common.util.DonAsynchron;
|
||||
import org.thingsboard.common.util.ThingsBoardExecutors;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.actors.calculatedField.CalculatedFieldLinkedTelemetryMsg;
|
||||
import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg;
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.queue.QueueConfig;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
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.common.msg.rpc.ToDeviceRpcRequestActorMsg;
|
||||
import org.thingsboard.server.common.util.ProtoUtils;
|
||||
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
|
||||
import org.thingsboard.server.queue.TbQueueConsumer;
|
||||
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
|
||||
import org.thingsboard.server.queue.discovery.PartitionService;
|
||||
@ -166,10 +161,10 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
|
||||
pendingMsgHolder.setMsg(toCfMsg);
|
||||
if (toCfMsg.hasTelemetryMsg()) {
|
||||
log.trace("[{}] Forwarding regular telemetry message for processing {}", id, toCfMsg.getTelemetryMsg());
|
||||
forwardToCalculatedFieldService(toCfMsg.getTelemetryMsg(), callback);
|
||||
forwardToActorSystem(toCfMsg.getTelemetryMsg(), callback);
|
||||
} else if (toCfMsg.hasLinkedTelemetryMsg()) {
|
||||
log.trace("[{}] Forwarding linked telemetry message for processing {}", id, toCfMsg.getLinkedTelemetryMsg());
|
||||
forwardToCalculatedFieldService(toCfMsg.getLinkedTelemetryMsg(), callback);
|
||||
forwardToActorSystem(toCfMsg.getLinkedTelemetryMsg(), callback);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.warn("[{}] Failed to process message: {}", id, msg, e);
|
||||
@ -219,9 +214,9 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
|
||||
protected void handleNotification(UUID id, TbProtoQueueMsg<ToCalculatedFieldNotificationMsg> msg, TbCallback callback) {
|
||||
ToCalculatedFieldNotificationMsg toCfNotification = msg.getValue();
|
||||
if (toCfNotification.hasComponentLifecycle()) {
|
||||
forwardToCalculatedFieldService(toCfNotification.getComponentLifecycle(), callback);
|
||||
forwardToActorSystem(toCfNotification.getComponentLifecycle(), callback);
|
||||
} else if (toCfNotification.hasEntityUpdateMsg()) {
|
||||
forwardToCalculatedFieldService(toCfNotification.getEntityUpdateMsg(), callback);
|
||||
forwardToActorSystem(toCfNotification.getEntityUpdateMsg(), callback);
|
||||
}
|
||||
callback.onSuccess();
|
||||
}
|
||||
@ -249,33 +244,20 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
|
||||
// }
|
||||
//
|
||||
|
||||
private void forwardToCalculatedFieldService(CalculatedFieldLinkedTelemetryMsgProto linkedMsg, TbCallback callback) {
|
||||
private void forwardToActorSystem(CalculatedFieldTelemetryMsgProto msg, TbCallback callback) {
|
||||
var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
|
||||
var entityId = EntityIdFactory.getByTypeAndUuid(msg.getEntityType(), new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
|
||||
actorContext.tell(new CalculatedFieldTelemetryMsg(tenantId, entityId, msg, callback));
|
||||
}
|
||||
|
||||
private void forwardToActorSystem(CalculatedFieldLinkedTelemetryMsgProto linkedMsg, TbCallback callback) {
|
||||
var msg = linkedMsg.getMsg();
|
||||
var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
|
||||
var calculatedFieldId = new CalculatedFieldId(new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
|
||||
ListenableFuture<?> future = calculatedFieldsExecutor.submit(() -> calculatedFieldExecutionService.onLinkedTelemetryMsg(linkedMsg, callback));
|
||||
DonAsynchron.withCallback(future,
|
||||
__ -> callback.onSuccess(),
|
||||
t -> {
|
||||
log.warn("[{}] Failed to process calculated field message for calculated field [{}]", tenantId.getId(), calculatedFieldId.getId(), t);
|
||||
callback.onFailure(t);
|
||||
});
|
||||
|
||||
var entityId = EntityIdFactory.getByTypeAndUuid(msg.getEntityType(), new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
|
||||
actorContext.tell(new CalculatedFieldLinkedTelemetryMsg(tenantId, entityId, linkedMsg, callback));
|
||||
}
|
||||
|
||||
private void forwardToCalculatedFieldService(CalculatedFieldTelemetryMsgProto msg, TbCallback callback) {
|
||||
var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
|
||||
var calculatedFieldId = new CalculatedFieldId(new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
|
||||
ListenableFuture<?> future = calculatedFieldsExecutor.submit(() -> calculatedFieldExecutionService.onTelemetryMsg(msg, callback));
|
||||
DonAsynchron.withCallback(future,
|
||||
__ -> callback.onSuccess(),
|
||||
t -> {
|
||||
log.warn("[{}] Failed to process calculated field message for calculated field [{}]", tenantId.getId(), calculatedFieldId.getId(), t);
|
||||
callback.onFailure(t);
|
||||
});
|
||||
}
|
||||
|
||||
private void forwardToCalculatedFieldService(TransportProtos.ComponentLifecycleMsgProto msg, TbCallback callback) {
|
||||
private void forwardToActorSystem(TransportProtos.ComponentLifecycleMsgProto msg, TbCallback callback) {
|
||||
var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
|
||||
var calculatedFieldId = new CalculatedFieldId(new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
|
||||
ListenableFuture<?> future = calculatedFieldsExecutor.submit(() -> calculatedFieldExecutionService.onCalculatedFieldLifecycleMsg(msg, callback));
|
||||
@ -287,7 +269,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
|
||||
});
|
||||
}
|
||||
|
||||
private void forwardToCalculatedFieldService(TransportProtos.CalculatedFieldEntityUpdateMsgProto msg, TbCallback callback) {
|
||||
private void forwardToActorSystem(TransportProtos.CalculatedFieldEntityUpdateMsgProto msg, TbCallback callback) {
|
||||
var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
|
||||
var entityId = EntityIdFactory.getByTypeAndUuid(msg.getEntityType(), new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
|
||||
ListenableFuture<?> future = calculatedFieldsExecutor.submit(() -> calculatedFieldExecutionService.onEntityUpdateMsg(msg, callback));
|
||||
|
||||
@ -147,7 +147,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
|
||||
resultFuture = tsService.saveWithoutLatest(tenantId, entityId, request.getEntries(), request.getTtl());
|
||||
}
|
||||
DonAsynchron.withCallback(resultFuture, result -> {
|
||||
calculatedFieldExecutionService.pushRequestToQueue(request, result);
|
||||
calculatedFieldExecutionService.pushRequestToQueue(request, result, request.getCallback());
|
||||
}, safeCallback(request.getCallback()), tsCallBackExecutor);
|
||||
addWsCallback(resultFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries()));
|
||||
if (request.isSaveLatest() && !request.isOnlyLatest()) {
|
||||
@ -167,7 +167,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
|
||||
log.trace("Executing saveInternal [{}]", request);
|
||||
ListenableFuture<List<Long>> saveFuture = attrService.save(request.getTenantId(), request.getEntityId(), request.getScope(), request.getEntries());
|
||||
DonAsynchron.withCallback(saveFuture, result -> {
|
||||
calculatedFieldExecutionService.pushRequestToQueue(request, result);
|
||||
calculatedFieldExecutionService.pushRequestToQueue(request, result, request.getCallback());
|
||||
}, safeCallback(request.getCallback()), tsCallBackExecutor);
|
||||
addWsCallback(saveFuture, success -> onAttributesUpdate(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getEntries(), request.isNotifyDevice()));
|
||||
}
|
||||
|
||||
@ -441,6 +441,8 @@ actors:
|
||||
device_dispatcher_pool_size: "${ACTORS_SYSTEM_DEVICE_DISPATCHER_POOL_SIZE:4}" # Thread pool size for actor system dispatcher that process messages for device actors
|
||||
rule_dispatcher_pool_size: "${ACTORS_SYSTEM_RULE_DISPATCHER_POOL_SIZE:8}" # Thread pool size for actor system dispatcher that process messages for rule engine (chain/node) actors
|
||||
edge_dispatcher_pool_size: "${ACTORS_SYSTEM_EDGE_DISPATCHER_POOL_SIZE:4}" # Thread pool size for actor system dispatcher that process messages for edge actors
|
||||
cfm_dispatcher_pool_size: "${ACTORS_SYSTEM_CFM_DISPATCHER_POOL_SIZE:2}" # Thread pool size for actor system dispatcher that process messages for CalculatedField manager actors
|
||||
cfe_dispatcher_pool_size: "${ACTORS_SYSTEM_CFE_DISPATCHER_POOL_SIZE:8}" # Thread pool size for actor system dispatcher that process messages for CalculatedField entity actors
|
||||
tenant:
|
||||
create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" # Create components in initialization
|
||||
session:
|
||||
|
||||
@ -35,6 +35,8 @@ sql.events.batch_threads=2
|
||||
actors.system.tenant_dispatcher_pool_size=4
|
||||
actors.system.device_dispatcher_pool_size=8
|
||||
actors.system.rule_dispatcher_pool_size=12
|
||||
actors.system.cfm_dispatcher_pool_size=2
|
||||
actors.system.cfe_dispatcher_pool_size=2
|
||||
transport.sessions.report_timeout=10000
|
||||
queue.transport_api.request_poll_interval=5
|
||||
queue.transport_api.response_poll_interval=5
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TbCalculatedFieldEntityActorId implements TbActorId {
|
||||
|
||||
@Getter
|
||||
private final EntityId entityId;
|
||||
|
||||
public TbCalculatedFieldEntityActorId(EntityId entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return entityId.getEntityType() + "|" + entityId.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TbCalculatedFieldEntityActorId that = (TbCalculatedFieldEntityActorId) o;
|
||||
return entityId.equals(that.entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Magic number to ensure that the hash does not match with the hash of other actor id - (TbEntityActorId)
|
||||
return 42 + Objects.hash(entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType getEntityType() {
|
||||
return entityId.getEntityType();
|
||||
}
|
||||
}
|
||||
@ -27,4 +27,6 @@ public class ReferencedEntityKey {
|
||||
private ArgumentType type;
|
||||
private AttributeScope scope;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -133,7 +133,16 @@ public enum MsgType {
|
||||
* Messages that are sent to and from edge session to start edge synchronization process
|
||||
*/
|
||||
EDGE_SYNC_REQUEST_TO_EDGE_SESSION_MSG,
|
||||
EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG;
|
||||
EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG,
|
||||
|
||||
CF_INIT_MSG, // Sent to init particular calculated field;
|
||||
CF_LINK_INIT_MSG, // Sent to init particular calculated field;
|
||||
CF_STATE_RESTORE_MSG,// Sent to init particular calculated field entity state;
|
||||
CF_TELEMETRY_MSG,
|
||||
CF_ENTITY_TELEMETRY_MSG,
|
||||
CF_LINKED_TELEMETRY_MSG,
|
||||
CF_UPDATE_MSG,
|
||||
CF_ENTITY_UPDATE_MSG;
|
||||
|
||||
@Getter
|
||||
private final boolean ignoreOnStart;
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.common.msg;
|
||||
|
||||
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
|
||||
import org.thingsboard.server.common.msg.queue.TbCallback;
|
||||
|
||||
public interface ToCalculatedFieldSystemMsg extends TenantAwareMsg {
|
||||
|
||||
default TbCallback getCallback() {
|
||||
return TbCallback.EMPTY;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.common.msg.cf;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
|
||||
@Data
|
||||
public class CalculatedFieldInitMsg implements ToCalculatedFieldSystemMsg {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final CalculatedField cf;
|
||||
|
||||
@Override
|
||||
public MsgType getMsgType() {
|
||||
return MsgType.CF_INIT_MSG;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.common.msg.cf;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.MsgType;
|
||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
|
||||
|
||||
@Data
|
||||
public class CalculatedFieldLinkInitMsg implements ToCalculatedFieldSystemMsg {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final CalculatedFieldLink link;
|
||||
|
||||
@Override
|
||||
public MsgType getMsgType() {
|
||||
return MsgType.CF_LINK_INIT_MSG;
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,10 @@ public @interface AfterStartUp {
|
||||
int ACTOR_SYSTEM = 9;
|
||||
int REGULAR_SERVICE = 10;
|
||||
|
||||
int CF_INIT_SERVICE = 10;
|
||||
int CF_STATE_RESTORE_SERVICE = 11;
|
||||
int CF_CONSUMER_SERVICE = 12;
|
||||
|
||||
int BEFORE_TRANSPORT_SERVICE = Integer.MAX_VALUE - 1001;
|
||||
int TRANSPORT_SERVICE = Integer.MAX_VALUE - 1000;
|
||||
int AFTER_TRANSPORT_SERVICE = Integer.MAX_VALUE - 999;
|
||||
|
||||
@ -145,6 +145,8 @@ sql.events.batch_threads=2
|
||||
actors.system.tenant_dispatcher_pool_size=4
|
||||
actors.system.device_dispatcher_pool_size=8
|
||||
actors.system.rule_dispatcher_pool_size=12
|
||||
actors.system.cfm_dispatcher_pool_size=2
|
||||
actors.system.cfe_dispatcher_pool_size=2
|
||||
transport.sessions.report_timeout=10000
|
||||
queue.transport_api.request_poll_interval=5
|
||||
queue.transport_api.response_poll_interval=5
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user