From bb6d04be3b6e9320cb162280fed843ee6d2d12c1 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 8 Sep 2023 14:54:02 +0300 Subject: [PATCH 01/14] Logic for handling rule chain updates from edge, including metadata --- .../service/edge/rpc/EdgeGrpcSession.java | 14 +- .../service/edge/rpc/EdgeSyncCursor.java | 2 +- .../edge/rpc/processor/BaseEdgeProcessor.java | 3 + .../rule/BaseRuleChainProcessor.java | 127 ++++++++++++++++++ .../rule/RuleChainEdgeProcessor.java | 102 +++++++++++++- .../server/edge/AbstractEdgeTest.java | 46 ++----- .../server/edge/AssetEdgeTest.java | 26 ++++ .../server/edge/DashboardEdgeTest.java | 23 ++++ .../server/edge/DeviceEdgeTest.java | 31 +++++ .../server/edge/RuleChainEdgeTest.java | 72 +++++----- .../server/dao/rule/RuleChainService.java | 2 + common/edge-api/src/main/proto/edge.proto | 5 +- .../server/dao/rule/BaseRuleChainService.java | 15 ++- 13 files changed, 390 insertions(+), 78 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index cfb92d0bfd..538a12bd08 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -65,6 +65,8 @@ import org.thingsboard.server.gen.edge.v1.RequestMsg; import org.thingsboard.server.gen.edge.v1.RequestMsgType; import org.thingsboard.server.gen.edge.v1.ResponseMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg; import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; @@ -628,7 +630,7 @@ public final class EdgeGrpcSession implements Closeable { case CUSTOMER: return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent); case RULE_CHAIN: - return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent); + return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent, this.edgeVersion); case RULE_CHAIN_METADATA: return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion); case ALARM: @@ -690,6 +692,16 @@ public final class EdgeGrpcSession implements Closeable { result.add(ctx.getAssetProcessor().processAssetMsgFromEdge(edge.getTenantId(), edge, assetUpdateMsg)); } } + if (uplinkMsg.getRuleChainUpdateMsgCount() > 0) { + for (RuleChainUpdateMsg ruleChainUpdateMsg : uplinkMsg.getRuleChainUpdateMsgList()) { + result.add(ctx.getRuleChainProcessor().processRuleChainMsgFromEdge(edge.getTenantId(), edge, ruleChainUpdateMsg)); + } + } + if (uplinkMsg.getRuleChainMetadataUpdateMsgCount() > 0) { + for (RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg : uplinkMsg.getRuleChainMetadataUpdateMsgList()) { + result.add(ctx.getRuleChainProcessor().processRuleChainMetadataMsgFromEdge(edge.getTenantId(), edge, ruleChainMetadataUpdateMsg)); + } + } if (uplinkMsg.getAlarmUpdateMsgCount() > 0) { for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) { result.add(ctx.getAlarmProcessor().processAlarmMsg(edge.getTenantId(), alarmUpdateMsg)); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index a39dae3b54..586e1b403a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -53,7 +53,6 @@ public class EdgeSyncCursor { public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge, boolean fullSync) { if (fullSync) { fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); - fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig())); fetchers.add(new TenantEdgeEventFetcher(ctx.getTenantService())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); @@ -64,6 +63,7 @@ public class EdgeSyncCursor { fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); } } + fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService())); fetchers.add(new DefaultProfilesEdgeEventFetcher(ctx.getDeviceProfileService(), ctx.getAssetProfileService())); fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 328a64efe2..78bd56d90c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -233,6 +233,9 @@ public abstract class BaseEdgeProcessor { @Autowired protected DataValidator entityViewValidator; + @Autowired + protected DataValidator ruleChainValidator; + @Autowired protected EdgeMsgConstructor edgeMsgConstructor; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java new file mode 100644 index 0000000000..dc9fa6e14d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java @@ -0,0 +1,127 @@ +/** + * Copyright © 2016-2023 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.edge.rpc.processor.rule; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleNodeProto; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +@Slf4j +public class BaseRuleChainProcessor extends BaseEdgeProcessor { + + protected boolean saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg) { + boolean created = false; + RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, ruleChainId); + if (ruleChain == null) { + created = true; + ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + ruleChain.setCreatedTime(Uuids.unixTimestamp(ruleChainId.getId())); + } + ruleChain.setName(ruleChainUpdateMsg.getName()); + ruleChain.setType(RuleChainType.EDGE); + ruleChain.setDebugMode(ruleChainUpdateMsg.getDebugMode()); + ruleChain.setConfiguration(JacksonUtil.toJsonNode(ruleChainUpdateMsg.getConfiguration())); + + UUID firstRuleNodeUUID = safeGetUUID(ruleChainUpdateMsg.getFirstRuleNodeIdMSB(), ruleChainUpdateMsg.getFirstRuleNodeIdLSB()); + ruleChain.setFirstRuleNodeId(firstRuleNodeUUID != null ? new RuleNodeId(firstRuleNodeUUID) : null); + + ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); + if (created) { + ruleChain.setId(ruleChainId); + } + ruleChainService.saveRuleChain(ruleChain, false); + return created; + } + + protected boolean saveOrUpdateRuleChainMetadata(TenantId tenantId, RuleChainId ruleChainId, RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg) throws IOException { + RuleChainMetaData ruleChainMetadata = new RuleChainMetaData(); + ruleChainMetadata.setRuleChainId(ruleChainId); + ruleChainMetadata.setNodes(parseNodeProtos(ruleChainId, ruleChainMetadataUpdateMsg.getNodesList())); + ruleChainMetadata.setConnections(parseConnectionProtos(ruleChainMetadataUpdateMsg.getConnectionsList())); + ruleChainMetadata.setRuleChainConnections(parseRuleChainConnectionProtos(ruleChainMetadataUpdateMsg.getRuleChainConnectionsList())); + if (ruleChainMetadataUpdateMsg.getFirstNodeIndex() != -1) { + ruleChainMetadata.setFirstNodeIndex(ruleChainMetadataUpdateMsg.getFirstNodeIndex()); + } + if (ruleChainMetadata.getNodes().size() > 0) { + ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetadata, Function.identity()); + return true; + } + return false; + } + + private List parseNodeProtos(RuleChainId ruleChainId, List nodesList) throws IOException { + List result = new ArrayList<>(); + for (RuleNodeProto proto : nodesList) { + RuleNode ruleNode = new RuleNode(); + RuleNodeId ruleNodeId = new RuleNodeId(new UUID(proto.getIdMSB(), proto.getIdLSB())); + ruleNode.setId(ruleNodeId); + ruleNode.setCreatedTime(Uuids.unixTimestamp(ruleNodeId.getId())); + ruleNode.setRuleChainId(ruleChainId); + ruleNode.setType(proto.getType()); + ruleNode.setName(proto.getName()); + ruleNode.setDebugMode(proto.getDebugMode()); + ruleNode.setConfiguration(JacksonUtil.OBJECT_MAPPER.readTree(proto.getConfiguration())); + ruleNode.setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.readTree(proto.getAdditionalInfo())); + result.add(ruleNode); + } + return result; + } + + private List parseConnectionProtos(List connectionsList) { + List result = new ArrayList<>(); + for (org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto proto : connectionsList) { + NodeConnectionInfo info = new NodeConnectionInfo(); + info.setFromIndex(proto.getFromIndex()); + info.setToIndex(proto.getToIndex()); + info.setType(proto.getType()); + result.add(info); + } + return result; + } + + private List parseRuleChainConnectionProtos(List ruleChainConnectionsList) throws IOException { + List result = new ArrayList<>(); + for (org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto proto : ruleChainConnectionsList) { + RuleChainConnectionInfo info = new RuleChainConnectionInfo(); + info.setFromIndex(proto.getFromIndex()); + info.setTargetRuleChainId(new RuleChainId(new UUID(proto.getTargetRuleChainIdMSB(), proto.getTargetRuleChainIdLSB()))); + info.setType(proto.getType()); + info.setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.readTree(proto.getAdditionalInfo())); + result.add(info); + } + return result; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index ba6fc1ef97..ae5e158ba7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -15,29 +15,120 @@ */ package org.thingsboard.server.service.edge.rpc.processor.rule; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +import java.util.UUID; import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY; @Component @Slf4j @TbCoreComponent -public class RuleChainEdgeProcessor extends BaseEdgeProcessor { +public class RuleChainEdgeProcessor extends BaseRuleChainProcessor { + + public ListenableFuture processRuleChainMsgFromEdge(TenantId tenantId, Edge edge, RuleChainUpdateMsg ruleChainUpdateMsg) { + log.trace("[{}] executing processRuleChainMsgFromEdge [{}] from edge [{}]", tenantId, ruleChainUpdateMsg, edge.getName()); + RuleChainId ruleChainId = new RuleChainId(new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB())); + try { + edgeSynchronizationManager.getSync().set(true); + + switch (ruleChainUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE: + case ENTITY_UPDATED_RPC_MESSAGE: + saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg, edge); + return Futures.immediateFuture(null); + case ENTITY_DELETED_RPC_MESSAGE: + RuleChain ruleChainToDelete = ruleChainService.findRuleChainById(tenantId, ruleChainId); + if (ruleChainToDelete != null) { + ruleChainService.unassignRuleChainFromEdge(tenantId, ruleChainId, edge.getId(), false); + } + return Futures.immediateFuture(null); + case UNRECOGNIZED: + default: + return handleUnsupportedMsgType(ruleChainUpdateMsg.getMsgType()); + } + } catch (DataValidationException e) { + if (e.getMessage().contains("limit reached")) { + log.warn("[{}] Number of allowed rule chains violated {}", tenantId, ruleChainUpdateMsg, e); + return Futures.immediateFuture(null); + } else { + return Futures.immediateFailedFuture(e); + } + } finally { + edgeSynchronizationManager.getSync().remove(); + } + } + + + public ListenableFuture processRuleChainMetadataMsgFromEdge(TenantId tenantId, Edge edge, RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg) { + log.trace("[{}] executing processRuleChainMetadataMsgFromEdge [{}] from edge [{}]", tenantId, ruleChainMetadataUpdateMsg, edge.getName()); + RuleChainId ruleChainId = new RuleChainId(new UUID(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB())); + try { + edgeSynchronizationManager.getSync().set(true); + + switch (ruleChainMetadataUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE: + case ENTITY_UPDATED_RPC_MESSAGE: + saveOrUpdateRuleChainMetadata(tenantId, ruleChainId, ruleChainMetadataUpdateMsg); + return Futures.immediateFuture(null); + case UNRECOGNIZED: + default: + return handleUnsupportedMsgType(ruleChainMetadataUpdateMsg.getMsgType()); + } + } catch (Exception e) { + String errMsg = String.format("Can't process rule chain metadata update msg %s", ruleChainMetadataUpdateMsg); + log.error(errMsg, e); + return Futures.immediateFailedFuture(new RuntimeException(errMsg, e)); + } finally { + edgeSynchronizationManager.getSync().remove(); + } + } + + private void saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg, Edge edge) { + boolean created = super.saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg); + if (created) { + createRelationFromEdge(tenantId, edge.getId(), ruleChainId); + pushRuleChainCreatedEventToRuleEngine(tenantId, edge, ruleChainId); + ruleChainService.assignRuleChainToEdge(tenantId, ruleChainId, edge.getId()); + } + if (ruleChainUpdateMsg.getRoot()) { + edge.setRootRuleChainId(ruleChainId); + edgeService.saveEdge(edge); + } + } + + private void pushRuleChainCreatedEventToRuleEngine(TenantId tenantId, Edge edge, RuleChainId ruleChainId) { + try { + RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, ruleChainId); + String ruleChainAsString = JacksonUtil.toString(ruleChain); + TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, null); + pushEntityEventToRuleEngine(tenantId, ruleChainId, null, TbMsgType.ENTITY_CREATED, ruleChainAsString, msgMetaData); + } catch (Exception e) { + log.warn("[{}][{}] Failed to push rule chain action to rule engine: {}", tenantId, ruleChainId, TbMsgType.ENTITY_CREATED.name(), e); + } + } - public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { @@ -55,10 +146,13 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainMsgConstructor.constructRuleChainUpdatedMsg(msgType, ruleChain, isRoot); + RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = + ruleChainMsgConstructor.constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addRuleChainUpdateMsg(ruleChainUpdateMsg) - .build(); + .addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg).build(); } break; case DELETED: diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 410348762f..dc1015ef4a 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -81,14 +81,12 @@ import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import org.thingsboard.server.queue.util.DataDecodingEncodingService; @@ -129,36 +127,12 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { installation(); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); - edgeImitator.expectMessageAmount(26); + edgeImitator.expectMessageAmount(27); edgeImitator.connect(); - requestEdgeRuleChainMetadata(); - verifyEdgeConnectionAndInitialData(); } - private void requestEdgeRuleChainMetadata() throws Exception { - RuleChainId rootRuleChainId = getEdgeRootRuleChainId(); - RuleChainMetadataRequestMsg.Builder builder = RuleChainMetadataRequestMsg.newBuilder() - .setRuleChainIdMSB(rootRuleChainId.getId().getMostSignificantBits()) - .setRuleChainIdLSB(rootRuleChainId.getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(builder); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() - .addRuleChainMetadataRequestMsg(builder.build()); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - } - - private RuleChainId getEdgeRootRuleChainId() throws Exception { - List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", - new TypeReference>() {}, new PageLink(100)).getData(); - for (RuleChain edgeRuleChain : edgeRuleChains) { - if (edgeRuleChain.isRoot()) { - return edgeRuleChain.getId(); - } - } - throw new RuntimeException("Root rule chain not found"); - } - @After public void teardownEdgeTest() { try { @@ -230,7 +204,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { // 2 messages - 1 from rule chain fetcher and 1 from rule chain controller UUID ruleChainUUID = validateRuleChains(); - // 1 from request message + // 2 messages - 1 from rule chain fetcher and 1 from rule chain controller (it goes along with RuleChainUpdateMsg) validateRuleChainMetadataUpdates(ruleChainUUID); // 4 messages - 4 messages from fetcher - 2 from system level ('mail', 'mailTemplates') and 2 from admin level ('mail', 'mailTemplates') @@ -385,11 +359,17 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } private void validateRuleChainMetadataUpdates(UUID expectedRuleChainUUID) { - Optional ruleChainMetadataUpdateOpt = edgeImitator.findMessageByType(RuleChainMetadataUpdateMsg.class); - Assert.assertTrue(ruleChainMetadataUpdateOpt.isPresent()); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMetadataUpdateOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsg.getMsgType()); - UUID ruleChainUUID = new UUID(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB()); + List ruleChainMetadataUpdateMsgList = edgeImitator.findAllMessagesByType(RuleChainMetadataUpdateMsg.class); + Assert.assertEquals(2, ruleChainMetadataUpdateMsgList.size()); + // metadata create msg + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsgCreated = ruleChainMetadataUpdateMsgList.get(0); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsgCreated.getMsgType()); + UUID ruleChainUUID = new UUID(ruleChainMetadataUpdateMsgCreated.getRuleChainIdMSB(), ruleChainMetadataUpdateMsgCreated.getRuleChainIdLSB()); + Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID); + // metadata update msg + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsgUpdated = ruleChainMetadataUpdateMsgList.get(1); + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainMetadataUpdateMsgUpdated.getMsgType()); + ruleChainUUID = new UUID(ruleChainMetadataUpdateMsgUpdated.getRuleChainIdMSB(), ruleChainMetadataUpdateMsgUpdated.getRuleChainIdLSB()); Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID); } diff --git a/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java index faf0896b81..e3ad8aaa25 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java @@ -168,6 +168,7 @@ public class AssetEdgeTest extends AbstractEdgeTest { public void testSendAssetToCloud() throws Exception { UUID uuid = Uuids.timeBased(); + // created asset on edge UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); AssetUpdateMsg.Builder assetUpdateMsgBuilder = AssetUpdateMsg.newBuilder(); assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); @@ -191,6 +192,31 @@ public class AssetEdgeTest extends AbstractEdgeTest { Asset asset = doGet("/api/asset/" + uuid, Asset.class); Assert.assertNotNull(asset); Assert.assertEquals("Asset Edge 2", asset.getName()); + + // updated asset on edge + uplinkMsgBuilder = UplinkMsg.newBuilder(); + assetUpdateMsgBuilder = AssetUpdateMsg.newBuilder(); + assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + assetUpdateMsgBuilder.setName("Asset Edge 2 Updated"); + assetUpdateMsgBuilder.setType("test"); + assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder); + uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + + latestResponseMsg = edgeImitator.getLatestResponseMsg(); + Assert.assertTrue(latestResponseMsg.getSuccess()); + + asset = doGet("/api/asset/" + uuid, Asset.class); + Assert.assertNotNull(asset); + Assert.assertEquals("Asset Edge 2 Updated", asset.getName()); } @Test diff --git a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java index 2d21d30174..7ef3da1fa0 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java @@ -171,6 +171,7 @@ public class DashboardEdgeTest extends AbstractEdgeTest { public void testSendDashboardToCloud() throws Exception { UUID uuid = Uuids.timeBased(); + // create dashboard on edge UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); DashboardUpdateMsg.Builder dashboardUpdateMsgBuilder = DashboardUpdateMsg.newBuilder(); dashboardUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); @@ -191,6 +192,28 @@ public class DashboardEdgeTest extends AbstractEdgeTest { Dashboard dashboard = doGet("/api/dashboard/" + uuid, Dashboard.class); Assert.assertNotNull(dashboard); Assert.assertEquals("Edge Test Dashboard", dashboard.getName()); + + // update dashboard on edge + uplinkMsgBuilder = UplinkMsg.newBuilder(); + dashboardUpdateMsgBuilder = DashboardUpdateMsg.newBuilder(); + dashboardUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + dashboardUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + dashboardUpdateMsgBuilder.setTitle("Edge Test Dashboard Updated"); + dashboardUpdateMsgBuilder.setConfiguration(""); + dashboardUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(dashboardUpdateMsgBuilder); + uplinkMsgBuilder.addDashboardUpdateMsg(dashboardUpdateMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + + dashboard = doGet("/api/dashboard/" + uuid, Dashboard.class); + Assert.assertNotNull(dashboard); + Assert.assertEquals("Edge Test Dashboard Updated", dashboard.getName()); } @Test diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 15b15c0be0..42f8d1d272 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -538,6 +538,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { public void testSendDeviceToCloud() throws Exception { UUID uuid = Uuids.timeBased(); + // create device on edge UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); @@ -566,6 +567,36 @@ public class DeviceEdgeTest extends AbstractEdgeTest { Device device = doGet("/api/device/" + newDeviceId, Device.class); Assert.assertNotNull(device); Assert.assertEquals("Edge Device 2", device.getName()); + + // update device on edge + uplinkMsgBuilder = UplinkMsg.newBuilder(); + deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + deviceUpdateMsgBuilder.setName("Edge Device 2 Updated"); + deviceUpdateMsgBuilder.setType("test"); + deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg); + latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage; + Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB()); + Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); + + newDeviceId = new UUID(latestDeviceCredentialsRequestMsg.getDeviceIdMSB(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); + + device = doGet("/api/device/" + newDeviceId, Device.class); + Assert.assertNotNull(device); + Assert.assertEquals("Edge Device 2 Updated", device.getName()); } @Test diff --git a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java index 83b2370cf0..4be82e7d8b 100644 --- a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.edge; -import com.google.protobuf.AbstractMessage; +import com.datastax.oss.driver.api.core.uuid.Uuids; import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; @@ -29,11 +29,10 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; +import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; import java.util.ArrayList; import java.util.Collections; @@ -49,7 +48,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { @Test public void testRuleChains() throws Exception { // create rule chain - edgeImitator.expectMessageAmount(2); + edgeImitator.expectMessageAmount(4); RuleChain ruleChain = new RuleChain(); ruleChain.setName("Edge Test Rule Chain"); ruleChain.setType(RuleChainType.EDGE); @@ -67,8 +66,6 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName()); - testRuleChainMetadataRequestMsg(savedRuleChain.getId()); - // unassign rule chain from edge edgeImitator.expectMessageAmount(1); doDelete("/api/edge/" + edge.getUuidId() @@ -89,55 +86,56 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { } @Test - public void testSendRuleChainMetadataRequestToCloud() throws Exception { - RuleChainId edgeRootRuleChainId = edge.getRootRuleChainId(); + public void testRuleChainToCloud() throws Exception { + UUID uuid = Uuids.timeBased(); + // create rule chain on edge UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - RuleChainMetadataRequestMsg.Builder ruleChainMetadataRequestMsgBuilder = RuleChainMetadataRequestMsg.newBuilder(); - ruleChainMetadataRequestMsgBuilder.setRuleChainIdMSB(edgeRootRuleChainId.getId().getMostSignificantBits()); - ruleChainMetadataRequestMsgBuilder.setRuleChainIdLSB(edgeRootRuleChainId.getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(ruleChainMetadataRequestMsgBuilder); - uplinkMsgBuilder.addRuleChainMetadataRequestMsg(ruleChainMetadataRequestMsgBuilder.build()); + RuleChainUpdateMsg.Builder ruleChainUpdateMsgBuilder = RuleChainUpdateMsg.newBuilder(); + ruleChainUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + ruleChainUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + ruleChainUpdateMsgBuilder.setName("Rule Chain Edge"); + ruleChainUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsgBuilder); + uplinkMsgBuilder.addRuleChainUpdateMsg(ruleChainUpdateMsgBuilder.build()); testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = (RuleChainMetadataUpdateMsg) latestMessage; - Assert.assertEquals(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), edgeRootRuleChainId.getId().getMostSignificantBits()); - Assert.assertEquals(ruleChainMetadataUpdateMsg.getRuleChainIdLSB(), edgeRootRuleChainId.getId().getLeastSignificantBits()); + UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg(); + Assert.assertTrue(latestResponseMsg.getSuccess()); - testAutoGeneratedCodeByProtobuf(ruleChainMetadataUpdateMsg); - } + RuleChain ruleChain = doGet("/api/ruleChain/" + uuid, RuleChain.class); + Assert.assertNotNull(ruleChain); + Assert.assertEquals("Rule Chain Edge", ruleChain.getName()); - private void testRuleChainMetadataRequestMsg(RuleChainId ruleChainId) throws Exception { - RuleChainMetadataRequestMsg.Builder ruleChainMetadataRequestMsgBuilder = RuleChainMetadataRequestMsg.newBuilder() - .setRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits()) - .setRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(ruleChainMetadataRequestMsgBuilder); + // update rule chain on edge + uplinkMsgBuilder = UplinkMsg.newBuilder(); + ruleChainUpdateMsgBuilder = RuleChainUpdateMsg.newBuilder(); + ruleChainUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + ruleChainUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + ruleChainUpdateMsgBuilder.setName("Rule Chain Edge Updated"); + ruleChainUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsgBuilder); + uplinkMsgBuilder.addRuleChainUpdateMsg(ruleChainUpdateMsgBuilder.build()); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() - .addRuleChainMetadataRequestMsg(ruleChainMetadataRequestMsgBuilder.build()); testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = (RuleChainMetadataUpdateMsg) latestMessage; - RuleChainId receivedRuleChainId = - new RuleChainId(new UUID(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB())); - Assert.assertEquals(ruleChainId, receivedRuleChainId); + latestResponseMsg = edgeImitator.getLatestResponseMsg(); + Assert.assertTrue(latestResponseMsg.getSuccess()); + + ruleChain = doGet("/api/ruleChain/" + uuid, RuleChain.class); + Assert.assertNotNull(ruleChain); + Assert.assertEquals("Rule Chain Edge Updated", ruleChain.getName()); } private void createRuleChainMetadata(RuleChain ruleChain) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 95cdc0e02d..94e0979ef5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -44,6 +44,8 @@ public interface RuleChainService extends EntityDaoService { RuleChain saveRuleChain(RuleChain ruleChain); + RuleChain saveRuleChain(RuleChain ruleChain, boolean doValidate); + boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId); RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index d9da289001..f7d5b5d771 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -420,6 +420,7 @@ message TenantProfileUpdateMsg { bytes profileDataBytes = 8; } +// deprecated message RuleChainMetadataRequestMsg { int64 ruleChainIdMSB = 1; int64 ruleChainIdLSB = 2; @@ -557,7 +558,7 @@ message UplinkMsg { repeated DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = 4; repeated AlarmUpdateMsg alarmUpdateMsg = 5; repeated RelationUpdateMsg relationUpdateMsg = 6; - repeated RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg = 7; + repeated RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg = 7; // deprecated repeated AttributesRequestMsg attributesRequestMsg = 8; repeated RelationRequestMsg relationRequestMsg = 9; repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10; @@ -571,6 +572,8 @@ message UplinkMsg { repeated EntityViewUpdateMsg entityViewUpdateMsg = 18; repeated AssetProfileUpdateMsg assetProfileUpdateMsg = 19; repeated DeviceProfileUpdateMsg deviceProfileUpdateMsg = 20; + repeated RuleChainUpdateMsg ruleChainUpdateMsg = 21; + repeated RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = 22; } message UplinkResponseMsg { diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 5e6b1e8009..387fc00c6c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -108,7 +108,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override @Transactional public RuleChain saveRuleChain(RuleChain ruleChain) { - ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); + return doSaveRuleChain(ruleChain, true); + } + + @Override + @Transactional + public RuleChain saveRuleChain(RuleChain ruleChain, boolean doValidate) { + return doSaveRuleChain(ruleChain, doValidate); + } + + private RuleChain doSaveRuleChain(RuleChain ruleChain, boolean doValidate) { + log.trace("Executing doSaveRuleChain [{}]", ruleChain); + if (doValidate) { + ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); + } try { RuleChain savedRuleChain = ruleChainDao.save(ruleChain.getTenantId(), ruleChain); if (ruleChain.getId() == null) { From 2ef441d115fe861dc5ba8ea31ccd6c9780811375 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 8 Sep 2023 15:27:49 +0300 Subject: [PATCH 02/14] Tmp check test logic for rule chain --- .../thingsboard/server/dao/rule/BaseRuleChainService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 387fc00c6c..7e9ae12035 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -178,11 +178,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (nodes != null) { for (RuleNode node : nodes) { setSingletonMode(node); + /* TODO: voba - merge comment if (node.getId() != null) { ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node)); } else { toAddOrUpdate.add(node); } + */ + ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node)); + toAddOrUpdate.add(node); } } @@ -193,8 +197,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC Integer index = ruleNodeIndexMap.get(existingNode.getId()); RuleNode newRuleNode = null; if (index != null) { - newRuleNode = ruleChainMetaData.getNodes().get(index); - toAddOrUpdate.add(newRuleNode); +// newRuleNode = ruleChainMetaData.getNodes().get(index); +// toAddOrUpdate.add(newRuleNode); } else { updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, null)); toDelete.add(existingNode); From 055fab9ad54a789ecb6451692e6a57c85b2e02bd Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 8 Sep 2023 17:09:06 +0300 Subject: [PATCH 03/14] Fix test: missing bean and improve safe for rule chain --- .../edge/rpc/processor/rule/BaseRuleChainProcessor.java | 4 ++-- .../edge/rpc/processor/rule/RuleChainEdgeProcessor.java | 3 ++- .../service/edge/rpc/processor/BaseEdgeProcessorTest.java | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java index dc9fa6e14d..66a471e3a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java @@ -41,7 +41,7 @@ import java.util.function.Function; @Slf4j public class BaseRuleChainProcessor extends BaseEdgeProcessor { - protected boolean saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg) { + protected boolean saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg, RuleChainType ruleChainType) { boolean created = false; RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, ruleChainId); if (ruleChain == null) { @@ -51,7 +51,7 @@ public class BaseRuleChainProcessor extends BaseEdgeProcessor { ruleChain.setCreatedTime(Uuids.unixTimestamp(ruleChainId.getId())); } ruleChain.setName(ruleChainUpdateMsg.getName()); - ruleChain.setType(RuleChainType.EDGE); + ruleChain.setType(ruleChainType); ruleChain.setDebugMode(ruleChainUpdateMsg.getDebugMode()); ruleChain.setConfiguration(JacksonUtil.toJsonNode(ruleChainUpdateMsg.getConfiguration())); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index ae5e158ba7..e2ce1fe651 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -105,7 +106,7 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor { } private void saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg, Edge edge) { - boolean created = super.saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg); + boolean created = super.saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg, RuleChainType.EDGE); if (created) { createRelationFromEdge(tenantId, edge.getId(), ruleChainId); pushRuleChainCreatedEventToRuleEngine(tenantId, edge, ruleChainId); diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java index 1c4b481302..9519362305 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; @@ -196,6 +197,9 @@ public abstract class BaseEdgeProcessorTest { @MockBean protected DataValidator entityViewValidator; + @MockBean + protected DataValidator ruleChainValidator; + @MockBean protected EdgeMsgConstructor edgeMsgConstructor; From e3a72fec09ca0f82584123b2ee2050ec41824db2 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 4 Feb 2025 12:39:06 +0200 Subject: [PATCH 04/14] Refactor TopicService. Change ymls to be able to configure notification topic names --- .../src/main/resources/thingsboard.yml | 7 +++- .../server/queue/discovery/TopicService.java | 41 +++++++++++++------ .../src/main/resources/tb-vc-executor.yml | 2 + .../src/main/resources/tb-coap-transport.yml | 4 ++ .../src/main/resources/tb-http-transport.yml | 4 ++ .../src/main/resources/tb-lwm2m-transport.yml | 4 ++ .../src/main/resources/tb-mqtt-transport.yml | 4 ++ .../src/main/resources/tb-snmp-transport.yml | 4 ++ 8 files changed, 57 insertions(+), 13 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f29cc55197..4ee08c7095 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1644,6 +1644,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices @@ -1688,7 +1690,6 @@ queue: enabled: "${TB_HOUSEKEEPER_STATS_ENABLED:true}" # Statistics printing interval for Housekeeper print-interval-ms: "${TB_HOUSEKEEPER_STATS_PRINT_INTERVAL_MS:60000}" - vc: # Default topic name topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}" @@ -1720,6 +1721,8 @@ queue: rule-engine: # Deprecated. It will be removed in the nearest releases topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_RULE_ENGINE_NOTIFICATIONS_TOPIC:tb_rule_engine.notifications}" # Interval in milliseconds to poll messages by Rule Engine poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" # Timeout for processing a message pack of Rule Engine @@ -1743,6 +1746,8 @@ queue: edge: # Default topic name topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}" # Amount of partitions used by Edge services partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}" # Poll interval for topics related to Edge services diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java index 927c311a2d..254d0a95d6 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java @@ -32,6 +32,18 @@ public class TopicService { @Value("${queue.prefix:}") private String prefix; + @Value("${queue.core.notifications-topic:tb_core.notifications}") + private String tbCoreNotificationsTopic; + + @Value("${queue.rule-engine.notifications-topic:tb_rule_engine.notifications}") + private String tbRuleEngineNotificationsTopic; + + @Value("${queue.transport.notifications-topics:tb_transport.notifications}") + private String tbTransportNotificationsTopic; + + @Value("${queue.edge.notifications-topic:tb_edge.notifications}") + private String tbEdgeNotificationsTopic; + private final ConcurrentMap tbCoreNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap tbRuleEngineNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap tbEdgeNotificationTopics = new ConcurrentHashMap<>(); @@ -47,19 +59,28 @@ public class TopicService { public TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId) { return switch (serviceType) { case TB_CORE -> tbCoreNotificationTopics.computeIfAbsent(serviceId, - id -> buildNotificationsTopicPartitionInfo(serviceType, serviceId)); + id -> buildNotificationsTopicPartitionInfo(tbCoreNotificationsTopic, serviceId)); case TB_RULE_ENGINE -> tbRuleEngineNotificationTopics.computeIfAbsent(serviceId, - id -> buildNotificationsTopicPartitionInfo(serviceType, serviceId)); - default -> buildNotificationsTopicPartitionInfo(serviceType, serviceId); + id -> buildNotificationsTopicPartitionInfo(tbRuleEngineNotificationsTopic, serviceId)); + case TB_TRANSPORT -> buildNotificationsTopicPartitionInfo(tbTransportNotificationsTopic, serviceId); + default -> throw new IllegalStateException("Unexpected service type: " + serviceType); }; } + private TopicPartitionInfo buildNotificationsTopicPartitionInfo(String topic, String serviceId) { + return buildTopicPartitionInfo(buildNotificationTopicName(topic, serviceId), null, null, false); + } + + public TopicPartitionInfo buildTopicPartitionInfo(String topic, TenantId tenantId, Integer partition, boolean myPartition) { + return new TopicPartitionInfo(buildTopicName(topic), tenantId, partition, myPartition); + } + public TopicPartitionInfo getEdgeNotificationsTopic(String serviceId) { return tbEdgeNotificationTopics.computeIfAbsent(serviceId, id -> buildEdgeNotificationsTopicPartitionInfo(serviceId)); } private TopicPartitionInfo buildEdgeNotificationsTopicPartitionInfo(String serviceId) { - return buildTopicPartitionInfo("tb_edge.notifications." + serviceId, null, null, false); + return buildTopicPartitionInfo(buildNotificationTopicName(tbEdgeNotificationsTopic, serviceId), null, null, false); } public TopicPartitionInfo getEdgeEventNotificationsTopic(TenantId tenantId, EdgeId edgeId) { @@ -70,18 +91,14 @@ public class TopicService { return buildTopicPartitionInfo("tb_edge_event.notifications." + tenantId + "." + edgeId, null, null, false); } - private TopicPartitionInfo buildNotificationsTopicPartitionInfo(ServiceType serviceType, String serviceId) { - return buildTopicPartitionInfo(serviceType.name().toLowerCase() + ".notifications." + serviceId, null, null, false); - } - - public TopicPartitionInfo buildTopicPartitionInfo(String topic, TenantId tenantId, Integer partition, boolean myPartition) { - return new TopicPartitionInfo(buildTopicName(topic), tenantId, partition, myPartition); - } - public String buildTopicName(String topic) { return prefix.isBlank() ? topic : prefix + "." + topic; } + private String buildNotificationTopicName(String topic, String serviceId) { + return topic + "." + serviceId; + } + public String buildConsumerGroupId(String servicePrefix, TenantId tenantId, String queueName, Integer partitionId) { return this.buildTopicName( servicePrefix + queueName diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index cba83e5ff7..595e514907 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -151,6 +151,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 66ab703a54..794eaed047 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -351,6 +351,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices @@ -381,6 +383,8 @@ queue: rule-engine: # Deprecated. It will be removed in the nearest releases topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_RULE_ENGINE_NOTIFICATIONS_TOPIC:tb_rule_engine.notifications}" # Interval in milliseconds to poll messages by Rule Engine poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" # Timeout for processing a message pack of Rule Engine diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index be91e4fe67..860059b1ee 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -296,6 +296,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices @@ -326,6 +328,8 @@ queue: rule-engine: # Deprecated. It will be removed in the nearest releases topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_RULE_ENGINE_NOTIFICATIONS_TOPIC:tb_rule_engine.notifications}" # Interval in milliseconds to poll messages by Rule Engine poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" # Timeout for processing a message pack of Rule Engine diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 7bdd68baf1..824d998836 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -397,6 +397,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices @@ -427,6 +429,8 @@ queue: rule-engine: # Deprecated. It will be removed in the nearest releases topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_RULE_ENGINE_NOTIFICATIONS_TOPIC:tb_rule_engine.notifications}" # Interval in milliseconds to poll messages by Rule Engine poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" # Timeout for processing a message pack of Rule Engine diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 8d9a60a319..4b0adab7d1 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -330,6 +330,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices @@ -360,6 +362,8 @@ queue: rule-engine: # Deprecated. It will be removed in the nearest releases topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_RULE_ENGINE_NOTIFICATIONS_TOPIC:tb_rule_engine.notifications}" # Interval in milliseconds to poll messages by Rule Engine poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" # Timeout for processing a message pack of Rule Engine diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 4c11bd0018..e6fe253e79 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -283,6 +283,8 @@ queue: core: # Default topic name topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_CORE_NOTIFICATIONS_TOPIC:tb_core.notifications}" # Interval in milliseconds to poll messages by Core microservices poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" # Amount of partitions used by Core microservices @@ -313,6 +315,8 @@ queue: rule-engine: # Deprecated. It will be removed in the nearest releases topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + # For high-priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_RULE_ENGINE_NOTIFICATIONS_TOPIC:tb_rule_engine.notifications}" # Interval in milliseconds to poll messages by Rule Engine poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" # Timeout for processing a message pack of Rule Engine From 8de861f8977d1da48b85959dd520983aaa6a0876 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Feb 2025 16:11:33 +0200 Subject: [PATCH 05/14] Improve customization for edge-event topic --- application/src/main/resources/thingsboard.yml | 2 ++ .../org/thingsboard/server/queue/discovery/TopicService.java | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 4ee08c7095..7b1df1d9d7 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1748,6 +1748,8 @@ queue: topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}" # For high-priority notifications that require minimum latency and processing time notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}" + # For edge events messages + event_notifications_topic: "${TB_QUEUE_EDGE_EVENT_NOTIFICATIONS_TOPIC:tb_edge_event.notifications}" # Amount of partitions used by Edge services partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}" # Poll interval for topics related to Edge services diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java index 254d0a95d6..bab55eece8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java @@ -44,6 +44,9 @@ public class TopicService { @Value("${queue.edge.notifications-topic:tb_edge.notifications}") private String tbEdgeNotificationsTopic; + @Value("${queue.edge.event-notifications-topic:tb_edge.notifications}") + private String tbEdgeEventNotificationsTopic; + private final ConcurrentMap tbCoreNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap tbRuleEngineNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap tbEdgeNotificationTopics = new ConcurrentHashMap<>(); @@ -88,7 +91,7 @@ public class TopicService { } public TopicPartitionInfo buildEdgeEventNotificationsTopicPartitionInfo(TenantId tenantId, EdgeId edgeId) { - return buildTopicPartitionInfo("tb_edge_event.notifications." + tenantId + "." + edgeId, null, null, false); + return buildTopicPartitionInfo(tbEdgeEventNotificationsTopic + "." + tenantId + "." + edgeId, null, null, false); } public String buildTopicName(String topic) { From b54e0cd59c0d70181c509279a80bf985e61fd719 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Feb 2025 16:12:53 +0200 Subject: [PATCH 06/14] Rename tbEdgeEventNotificationsTopic --- .../org/thingsboard/server/queue/discovery/TopicService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java index bab55eece8..ca080f9481 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java @@ -44,7 +44,7 @@ public class TopicService { @Value("${queue.edge.notifications-topic:tb_edge.notifications}") private String tbEdgeNotificationsTopic; - @Value("${queue.edge.event-notifications-topic:tb_edge.notifications}") + @Value("${queue.edge.event-notifications-topic:tb_edge_event.notifications}") private String tbEdgeEventNotificationsTopic; private final ConcurrentMap tbCoreNotificationTopics = new ConcurrentHashMap<>(); From e372587926fc2fbf152df3c4f54fdf7121bca8c8 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 27 Feb 2025 13:01:21 +0200 Subject: [PATCH 07/14] Edge rule chain CRUD: fixed setting root rule chain --- .../processor/edge/EdgeEntityProcessor.java | 4 +++ .../rule/BaseRuleChainProcessor.java | 13 +++++--- .../rule/RuleChainEdgeProcessor.java | 33 +++++++++++-------- .../entitiy/EntityStateSourcingListener.java | 5 +++ .../server/edge/AbstractEdgeTest.java | 28 +++++++--------- .../server/edge/RuleChainEdgeTest.java | 1 + 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java index ddbe4810df..77fa31c028 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java @@ -49,8 +49,12 @@ public class EdgeEntityProcessor extends BaseEdgeProcessor { @Override public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { try { + EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + if (edgeId.equals(originatorEdgeId)) { + return Futures.immediateFuture(null); + } switch (actionType) { case ASSIGNED_TO_CUSTOMER: { CustomerId customerId = JacksonUtil.fromString(edgeNotificationMsg.getBody(), CustomerId.class); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java index 03904ce5ef..3231d7788a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java @@ -39,24 +39,27 @@ public class BaseRuleChainProcessor extends BaseEdgeProcessor { protected Pair saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg, RuleChainType ruleChainType) { boolean created = false; - RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(tenantId, ruleChainId); - if (ruleChain == null) { + RuleChain ruleChainFromDb = edgeCtx.getRuleChainService().findRuleChainById(tenantId, ruleChainId); + if (ruleChainFromDb == null) { created = true; } - ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true); + RuleChain ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true); if (ruleChain == null) { throw new RuntimeException("[{" + tenantId + "}] ruleChainUpdateMsg {" + ruleChainUpdateMsg + "} cannot be converted to rule chain"); } boolean isRoot = ruleChain.isRoot(); - ruleChain.setRoot(false); + if (RuleChainType.CORE.equals(ruleChainType)) { + ruleChain.setRoot(false); + } else { + ruleChain.setRoot(ruleChainFromDb == null ? false : ruleChainFromDb.isRoot()); + } ruleChain.setType(ruleChainType); ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); if (created) { ruleChain.setId(ruleChainId); } - edgeCtx.getRuleChainService().saveRuleChain(ruleChain); return Pair.of(created, isRoot); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index 340fb09eab..06fb4c37a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -57,8 +57,7 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor { switch (ruleChainUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: - saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg, edge); - return Futures.immediateFuture(null); + return saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg, edge); case ENTITY_DELETED_RPC_MESSAGE: RuleChain ruleChainToDelete = edgeCtx.getRuleChainService().findRuleChainById(tenantId, ruleChainId); if (ruleChainToDelete != null) { @@ -81,19 +80,25 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor { } } - private void saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg, Edge edge) { - Pair resultPair = super.saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg, RuleChainType.EDGE); - Boolean created = resultPair.getFirst(); - if (created) { - createRelationFromEdge(tenantId, edge.getId(), ruleChainId); - pushRuleChainCreatedEventToRuleEngine(tenantId, edge, ruleChainId, ruleChainUpdateMsg.getEntity()); - edgeCtx.getRuleChainService().assignRuleChainToEdge(tenantId, ruleChainId, edge.getId()); - } - Boolean isRoot = resultPair.getSecond(); - if (isRoot) { - edge.setRootRuleChainId(ruleChainId); - edgeCtx.getEdgeService().saveEdge(edge); + private ListenableFuture saveOrUpdateRuleChain(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateMsg ruleChainUpdateMsg, Edge edge) { + try { + Pair resultPair = super.saveOrUpdateRuleChain(tenantId, ruleChainId, ruleChainUpdateMsg, RuleChainType.EDGE); + Boolean created = resultPair.getFirst(); + if (created) { + createRelationFromEdge(tenantId, edge.getId(), ruleChainId); + pushRuleChainCreatedEventToRuleEngine(tenantId, edge, ruleChainId, ruleChainUpdateMsg.getEntity()); + edgeCtx.getRuleChainService().assignRuleChainToEdge(tenantId, ruleChainId, edge.getId()); + } + Boolean isRoot = resultPair.getSecond(); + if (isRoot) { + edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edge.getId()); + edgeCtx.getEdgeService().setEdgeRootRuleChain(tenantId, edge, ruleChainId); + } + } catch (Exception e) { + log.error("Failed to save or update rule chain", e); + return Futures.immediateFailedFuture(e); } + return Futures.immediateFuture(null); } private void pushRuleChainCreatedEventToRuleEngine(TenantId tenantId, Edge edge, RuleChainId ruleChainId, String ruleChainAsString) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 35aad36dff..e40251673d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg; +import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -64,6 +65,7 @@ public class EntityStateSourcingListener { private final TenantService tenantService; private final TbClusterService tbClusterService; + private final EdgeSynchronizationManager edgeSynchronizationManager; @PostConstruct public void init() { @@ -241,6 +243,9 @@ public class EntityStateSourcingListener { private void onEdgeEvent(TenantId tenantId, EntityId entityId, Object entity, ComponentLifecycleEvent lifecycleEvent) { if (entity instanceof Edge) { + if (entityId.equals(edgeSynchronizationManager.getEdgeId().get())) { + return; + } tbClusterService.onEdgeStateChangeEvent(new ComponentLifecycleMsg(tenantId, entityId, lifecycleEvent)); } else if (entity instanceof EdgeEvent edgeEvent) { tbClusterService.onEdgeEventUpdate(new EdgeEventUpdateMsg(tenantId, edgeEvent.getEdgeId())); diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 44dac215e2..feac7adb04 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -140,7 +140,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { installation(); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); - edgeImitator.expectMessageAmount(27); + edgeImitator.expectMessageAmount(25); edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class); edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class); edgeImitator.connect(); @@ -191,8 +191,10 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } private RuleChainId getEdgeRootRuleChainId() throws Exception { - List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", - new TypeReference>() {}, new PageLink(100)).getData(); + List edgeRuleChains = doGetTypedWithPageLink("/api/ruleChains?type={type}&", + new TypeReference>() {}, + new PageLink(100, 0, "Edge Root Rule Chain"), + "EDGE").getData(); for (RuleChain edgeRuleChain : edgeRuleChains) { if (edgeRuleChain.isRoot()) { return edgeRuleChain.getId(); @@ -243,8 +245,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { validateMsgsCnt(RuleChainUpdateMsg.class, 1); UUID ruleChainUUID = validateRuleChains(); - // 2 messages - 1 from rule chain fetcher and 1 from rule chain controller (it goes along with RuleChainUpdateMsg) - validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 2); + // 1 from rule chain fetcher + validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 1); validateRuleChainMetadataUpdates(ruleChainUUID); // 4 messages ('general', 'mail', 'connectivity', 'jwt') @@ -426,17 +428,11 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } private void validateRuleChainMetadataUpdates(UUID expectedRuleChainUUID) { - List ruleChainMetadataUpdateMsgList = edgeImitator.findAllMessagesByType(RuleChainMetadataUpdateMsg.class); - Assert.assertEquals(2, ruleChainMetadataUpdateMsgList.size()); - // metadata create msg - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsgCreated = ruleChainMetadataUpdateMsgList.get(0); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsgCreated.getMsgType()); - RuleChainMetaData ruleChainMetaData = JacksonUtil.fromString(ruleChainMetadataUpdateMsgCreated.getEntity(), RuleChainMetaData.class, true); - Assert.assertEquals(expectedRuleChainUUID, ruleChainMetaData.getRuleChainId().getId()); - // metadata update msg - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsgUpdated = ruleChainMetadataUpdateMsgList.get(1); - Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainMetadataUpdateMsgUpdated.getMsgType()); - ruleChainMetaData = JacksonUtil.fromString(ruleChainMetadataUpdateMsgUpdated.getEntity(), RuleChainMetaData.class, true); + Optional ruleChainMetadataUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainMetadataUpdateMsg.class); + Assert.assertTrue(ruleChainMetadataUpdateMsgOpt.isPresent()); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMetadataUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsg.getMsgType()); + RuleChainMetaData ruleChainMetaData = JacksonUtil.fromString(ruleChainMetadataUpdateMsg.getEntity(), RuleChainMetaData.class, true); Assert.assertEquals(expectedRuleChainUUID, ruleChainMetaData.getRuleChainId().getId()); } diff --git a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java index ed77c4b97a..7c029e0471 100644 --- a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java @@ -105,6 +105,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { // create rule chain on edge RuleChain edgeRuleChain = new RuleChain(); + edgeRuleChain.setTenantId(tenantId); edgeRuleChain.setId(new RuleChainId(uuid)); edgeRuleChain.setName(ruleChainName); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); From b29b192efc78a3b2af9bed0679f111bf39be2c10 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 27 Feb 2025 13:20:59 +0200 Subject: [PATCH 08/14] Edge Rule Chain CRUD: revert metadata validatino --- .../rule/BaseRuleChainProcessor.java | 8 +++++- .../server/dao/rule/RuleChainService.java | 2 -- .../server/dao/rule/BaseRuleChainService.java | 26 +++++-------------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java index 3231d7788a..5156956c3d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; @@ -70,7 +71,12 @@ public class BaseRuleChainProcessor extends BaseEdgeProcessor { throw new RuntimeException("[{" + tenantId + "}] ruleChainMetadataUpdateMsg {" + ruleChainMetadataUpdateMsg + "} cannot be converted to rule chain metadata"); } if (!ruleChainMetadata.getNodes().isEmpty()) { - edgeCtx.getRuleChainService().saveRuleChainMetaData(tenantId, ruleChainMetadata, Function.identity(), true, false); + ruleChainMetadata.setVersion(null); + for (RuleNode ruleNode : ruleChainMetadata.getNodes()) { + ruleNode.setRuleChainId(null); + ruleNode.setId(null); + } + edgeCtx.getRuleChainService().saveRuleChainMetaData(tenantId, ruleChainMetadata, Function.identity(), true); } } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 7558d49971..a2356ee149 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -54,8 +54,6 @@ public interface RuleChainService extends EntityDaoService { RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, boolean publishSaveEvent); - RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, boolean publishSaveEvent, boolean doValidate); - RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId); RuleChain findRuleChainById(TenantId tenantId, RuleChainId ruleChainId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 685a25a41e..b7325ed714 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -174,25 +174,18 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override @Transactional public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater) { - return saveRuleChainMetaData(tenantId, ruleChainMetaData, ruleNodeUpdater, true, true); + return saveRuleChainMetaData(tenantId, ruleChainMetaData, ruleNodeUpdater, true); } @Transactional @Override public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, boolean publishSaveEvent) { - return saveRuleChainMetaData(tenantId, ruleChainMetaData, ruleNodeUpdater, publishSaveEvent, true); - } - - @Transactional - @Override - public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, - boolean publishSaveEvent, boolean doValidate) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); if (ruleChain == null) { return RuleChainUpdateResult.failed(); - } else if (doValidate && ruleChainMetaData.getVersion() != null && !ruleChainMetaData.getVersion().equals(ruleChain.getVersion())) { + } else if (ruleChainMetaData.getVersion() != null && !ruleChainMetaData.getVersion().equals(ruleChain.getVersion())) { throw new EntityVersionMismatchException(EntityType.RULE_CHAIN, null); } RuleChainDataValidator.validateMetaDataFieldsAndConnections(ruleChainMetaData); @@ -206,14 +199,9 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (nodes != null) { for (RuleNode node : nodes) { setSingletonMode(node); - if (doValidate) { - if (node.getId() != null) { - ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node)); - } else { - toAddOrUpdate.add(node); - } - } else { + if (node.getId() != null) { ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node)); + } else { toAddOrUpdate.add(node); } } @@ -226,10 +214,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC Integer index = ruleNodeIndexMap.get(existingNode.getId()); RuleNode newRuleNode = null; if (index != null) { - if (doValidate) { - newRuleNode = ruleChainMetaData.getNodes().get(index); - toAddOrUpdate.add(newRuleNode); - } + newRuleNode = ruleChainMetaData.getNodes().get(index); + toAddOrUpdate.add(newRuleNode); } else { updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, null)); toDelete.add(existingNode); From cc925a75c42e50d8978f9ade2c7487d6063be704 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 27 Feb 2025 13:41:17 +0200 Subject: [PATCH 09/14] edge.proto - mark deprecated fields --- common/edge-api/src/main/proto/edge.proto | 25 +++++++++++++------ .../server/dao/rule/BaseRuleChainService.java | 3 +-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 4058260629..023ac00634 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -42,6 +42,8 @@ enum EdgeVersion { V_3_8_0 = 8; V_3_9_0 = 9; V_4_0_0 = 10; + + V_LATEST = 999; } /** @@ -303,8 +305,9 @@ message NotificationTemplateUpdateMsg { optional string entity = 4; } -// deprecated +// DEPRECATED. FOR REMOVAL message RuleChainMetadataRequestMsg { + option deprecated = true; int64 ruleChainIdMSB = 1; int64 ruleChainIdLSB = 2; } @@ -322,22 +325,30 @@ message RelationRequestMsg { string entityType = 3; } +// DEPRECATED. FOR REMOVAL message UserCredentialsRequestMsg { + option deprecated = true; int64 userIdMSB = 1; int64 userIdLSB = 2; } +// DEPRECATED. FOR REMOVAL message DeviceCredentialsRequestMsg { + option deprecated = true; int64 deviceIdMSB = 1; int64 deviceIdLSB = 2; } +// DEPRECATED. FOR REMOVAL message WidgetBundleTypesRequestMsg { + option deprecated = true; int64 widgetBundleIdMSB = 1; int64 widgetBundleIdLSB = 2; } +// DEPRECATED. FOR REMOVAL message EntityViewsRequestMsg { + option deprecated = true; int64 entityIdMSB = 1; int64 entityIdLSB = 2; string entityType = 3; @@ -395,14 +406,14 @@ message UplinkMsg { repeated DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = 4; repeated AlarmUpdateMsg alarmUpdateMsg = 5; repeated RelationUpdateMsg relationUpdateMsg = 6; - repeated RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg = 7; // deprecated + repeated RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg = 7 [deprecated = true]; repeated AttributesRequestMsg attributesRequestMsg = 8; repeated RelationRequestMsg relationRequestMsg = 9; - repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10; - repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 11; + repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10 [deprecated = true]; + repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 11 [deprecated = true]; repeated DeviceRpcCallMsg deviceRpcCallMsg = 12; - repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14; - repeated EntityViewsRequestMsg entityViewsRequestMsg = 15; + repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14 [deprecated = true]; + repeated EntityViewsRequestMsg entityViewsRequestMsg = 15 [deprecated = true]; repeated AssetUpdateMsg assetUpdateMsg = 16; repeated DashboardUpdateMsg dashboardUpdateMsg = 17; repeated EntityViewUpdateMsg entityViewUpdateMsg = 18; @@ -430,7 +441,7 @@ message DownlinkMsg { int32 downlinkMsgId = 1; SyncCompletedMsg syncCompletedMsg = 2; repeated EntityDataProto entityData = 3; - repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 4; + repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 4 [deprecated = true]; repeated DeviceUpdateMsg deviceUpdateMsg = 5; repeated DeviceProfileUpdateMsg deviceProfileUpdateMsg = 6; repeated DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = 7; diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index b7325ed714..b5fde88a0b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -179,8 +179,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Transactional @Override - public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, - boolean publishSaveEvent) { + public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, boolean publishSaveEvent) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); if (ruleChain == null) { From ffa9aec2b1fca4eb17f7d84b1068f94506cb8618 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 28 Feb 2025 11:49:00 +0200 Subject: [PATCH 10/14] BaseRuleChainProcessor - use saveRuleChain method without validation --- .../service/edge/rpc/processor/rule/BaseRuleChainProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java index 5156956c3d..00b9c732aa 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/BaseRuleChainProcessor.java @@ -61,7 +61,7 @@ public class BaseRuleChainProcessor extends BaseEdgeProcessor { if (created) { ruleChain.setId(ruleChainId); } - edgeCtx.getRuleChainService().saveRuleChain(ruleChain); + edgeCtx.getRuleChainService().saveRuleChain(ruleChain, true, false); return Pair.of(created, isRoot); } From 0e819850c83a2c5bc27d894cd312a61efc02545a Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 28 Feb 2025 15:11:21 +0200 Subject: [PATCH 11/14] lwm2m: fix bug monitoring --- .../transport/TransportHealthChecker.java | 2 +- .../main/resources/lwm2m/device_profile.json | 6 +- .../resources/lwm2m/models/test-model.xml | 324 +++++++++++++++++- .../src/main/resources/lwm2m/resource.json | 2 +- pom.xml | 2 + 5 files changed, 312 insertions(+), 24 deletions(-) diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java index 1b8a704fad..f5e6cb5469 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java @@ -145,7 +145,7 @@ public abstract class TransportHealthChecker { TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); diff --git a/monitoring/src/main/resources/lwm2m/device_profile.json b/monitoring/src/main/resources/lwm2m/device_profile.json index c75fe9ce7a..d23e02825d 100644 --- a/monitoring/src/main/resources/lwm2m/device_profile.json +++ b/monitoring/src/main/resources/lwm2m/device_profile.json @@ -12,14 +12,14 @@ "transportConfiguration": { "observeAttr": { "observe": [ - "/3_1.0/0/0" + "/3_1.1/0/0" ], "attribute": [], "telemetry": [ - "/3_1.0/0/0" + "/3_1.1/0/0" ], "keyName": { - "/3_1.0/0/0": "testData" + "/3_1.1/0/0": "testData" }, "attributeLwm2m": {} }, diff --git a/monitoring/src/main/resources/lwm2m/models/test-model.xml b/monitoring/src/main/resources/lwm2m/models/test-model.xml index 2d25f4698e..02b084127f 100644 --- a/monitoring/src/main/resources/lwm2m/models/test-model.xml +++ b/monitoring/src/main/resources/lwm2m/models/test-model.xml @@ -1,45 +1,331 @@ + - + + LwM2M Monitoring - - + 3 - urn:oma:lwm2m:oma:3 + urn:oma:lwm2m:oma:3:1.1 1.1 - 1.0 + 1.1 Single Mandatory - Test data + Manufacturer + R + Single + Optional + String + + + + + + Model Number R Single Optional String - + - + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + /100 + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..32 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + diff --git a/monitoring/src/main/resources/lwm2m/resource.json b/monitoring/src/main/resources/lwm2m/resource.json index dcc0182bdd..bbef8da7ec 100644 --- a/monitoring/src/main/resources/lwm2m/resource.json +++ b/monitoring/src/main/resources/lwm2m/resource.json @@ -2,5 +2,5 @@ "title": "", "resourceType": "LWM2M_MODEL", "fileName": "test-model.xml", - "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQoKICAgIENvcHlyaWdodCDCqSAyMDE2LTIwMjQgVGhlIFRoaW5nc2JvYXJkIEF1dGhvcnMKCiAgICBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKICAgIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KICAgIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiAgICBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiAgICBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogICAgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiAgICBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiAgICBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCi0tPgo8TFdNMk0geG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIKICAgICAgIHhzaTpub05hbWVzcGFjZVNjaGVtYUxvY2F0aW9uPSJodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS12MV8xLnhzZCI+CiAgICA8T2JqZWN0IE9iamVjdFR5cGU9Ik1PRGVmaW5pdGlvbiI+CiAgICAgICAgPE5hbWU+THdNMk0gTW9uaXRvcmluZzwvTmFtZT4KICAgICAgICA8RGVzY3JpcHRpb24xPgogICAgICAgICAgICA8IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMT4KICAgICAgICA8T2JqZWN0SUQ+MzwvT2JqZWN0SUQ+CiAgICAgICAgPE9iamVjdFVSTj51cm46b21hOmx3bTJtOm9tYTozPC9PYmplY3RVUk4+CiAgICAgICAgPExXTTJNVmVyc2lvbj4xLjE8L0xXTTJNVmVyc2lvbj4KICAgICAgICA8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CiAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgPE1hbmRhdG9yeT5NYW5kYXRvcnk8L01hbmRhdG9yeT4KICAgICAgICA8UmVzb3VyY2VzPgogICAgICAgICAgICA8SXRlbSBJRD0iMCI+CiAgICAgICAgICAgICAgICA8TmFtZT5UZXN0IGRhdGE8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5TdHJpbmc8L1R5cGU+CiAgICAgICAgICAgICAgICA8UmFuZ2VFbnVtZXJhdGlvbj48L1JhbmdlRW51bWVyYXRpb24+CiAgICAgICAgICAgICAgICA8VW5pdHM+PC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVtUZXN0IGRhdGFdXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgPC9SZXNvdXJjZXM+CiAgICAgICAgPERlc2NyaXB0aW9uMj48L0Rlc2NyaXB0aW9uMj4KICAgIDwvT2JqZWN0Pgo8L0xXTTJNPgo=" + "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KCjwhLS0KRklMRSBJTkZPUk1BVElPTgoKT01BIFBlcm1hbmVudCBEb2N1bWVudAogICBGaWxlOiBPTUEtU1VQLVhNTF8zLVYxXzItMjAyMDExMTAtQS54bWwKICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3JlbGVhc2UvT2JqTHdNMk1fRGV2aWNlLwoKT01OQSBMd00yTSBSZWdpc3RyeQogICBQYXRoOiBodHRwczovL2dpdGh1Yi5jb20vT3Blbk1vYmlsZUFsbGlhbmNlL2x3bTJtLXJlZ2lzdHJ5CiAgIE5hbWU6IDMueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogICAgT01BLVRTLUxpZ2h0d2VpZ2h0TTJNX0NvcmUtVjFfMgoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvcmVsZWFzZS9MaWdodHdlaWdodE0yTS8KCiAgU2VuZCBjb21tZW50cyB0byBodHRwczovL2dpdGh1Yi5jb20vT3Blbk1vYmlsZUFsbGlhbmNlL09NQV9Md00yTV9mb3JfRGV2ZWxvcGVycy9pc3N1ZXMKCkxFR0FMIERJU0NMQUlNRVIKCiAgQ29weXJpZ2h0IDIwMjAgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgogIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAogIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucwogIGFyZSBtZXQ6CgogIDEuIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0CiAgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogIDIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0CiAgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyIGluIHRoZQogIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uCiAgMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwogIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0byBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZAogIGZyb20gdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KCiAgVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwogICJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UCiAgTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MKICBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQogIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsCiAgSU5DSURFTlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywKICBCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CiAgTE9TUyBPRiBVU0UsIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUgogIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVAogIExJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCiAgQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRSBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFCiAgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCgogIFRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiAgUGxlYXNlCiAgcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKICBodHRwczovL3d3dy5vbWFzcGVjd29ya3Mub3JnL2Fib3V0L2ludGVsbGVjdHVhbC1wcm9wZXJ0eS1yaWdodHMvCgotLT4KCjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMvTFdNMk0tdjFfMS54c2QiPgogICAgPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgogICAgICAgIDxOYW1lPkx3TTJNIE1vbml0b3Jpbmc8L05hbWU+CiAgICAgICAgPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdCBwcm92aWRlcyBhIHJhbmdlIG9mIGRldmljZSByZWxhdGVkIGluZm9ybWF0aW9uIHdoaWNoIGNhbiBiZSBxdWVyaWVkIGJ5IHRoZSBMd00yTSBTZXJ2ZXIsIGFuZCBhIGRldmljZSByZWJvb3QgYW5kIGZhY3RvcnkgcmVzZXQgZnVuY3Rpb24uXV0+PC9EZXNjcmlwdGlvbjE+CiAgICAgICAgPE9iamVjdElEPjM8L09iamVjdElEPgogICAgICAgIDxPYmplY3RVUk4+dXJuOm9tYTpsd20ybTpvbWE6MzoxLjE8L09iamVjdFVSTj4KICAgICAgICA8TFdNMk1WZXJzaW9uPjEuMTwvTFdNMk1WZXJzaW9uPgogICAgICAgIDxPYmplY3RWZXJzaW9uPjEuMTwvT2JqZWN0VmVyc2lvbj4KICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICA8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgogICAgICAgIDxSZXNvdXJjZXM+CiAgICAgICAgICAgIDxJdGVtIElEPSIwIj4KICAgICAgICAgICAgICAgIDxOYW1lPk1hbnVmYWN0dXJlcjwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlI8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPlN0cmluZzwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW0h1bWFuIHJlYWRhYmxlIG1hbnVmYWN0dXJlciBuYW1lXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iMSI+CiAgICAgICAgICAgICAgICA8TmFtZT5Nb2RlbCBOdW1iZXI8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5TdHJpbmc8L1R5cGU+CiAgICAgICAgICAgICAgICA8UmFuZ2VFbnVtZXJhdGlvbj48L1JhbmdlRW51bWVyYXRpb24+CiAgICAgICAgICAgICAgICA8VW5pdHM+PC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVtBIG1vZGVsIGlkZW50aWZpZXIgKG1hbnVmYWN0dXJlciBzcGVjaWZpZWQgc3RyaW5nKV1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjIiPgogICAgICAgICAgICAgICAgPE5hbWU+U2VyaWFsIE51bWJlcjwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlI8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPlN0cmluZzwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW1NlcmlhbCBOdW1iZXJdXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIzIj4KICAgICAgICAgICAgICAgIDxOYW1lPkZpcm13YXJlIFZlcnNpb248L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5TdHJpbmc8L1R5cGU+CiAgICAgICAgICAgICAgICA8UmFuZ2VFbnVtZXJhdGlvbj48L1JhbmdlRW51bWVyYXRpb24+CiAgICAgICAgICAgICAgICA8VW5pdHM+PC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVtDdXJyZW50IGZpcm13YXJlIHZlcnNpb24gb2YgdGhlIERldmljZS5UaGUgRmlybXdhcmUgTWFuYWdlbWVudCBmdW5jdGlvbiBjb3VsZCByZWx5IG9uIHRoaXMgcmVzb3VyY2UuXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iNCI+CiAgICAgICAgICAgICAgICA8TmFtZT5SZWJvb3Q8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5FPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgogICAgICAgICAgICAgICAgPFR5cGU+PC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbUmVib290IHRoZSBMd00yTSBEZXZpY2UgdG8gcmVzdG9yZSB0aGUgRGV2aWNlIGZyb20gdW5leHBlY3RlZCBmaXJtd2FyZSBmYWlsdXJlLl1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjUiPgogICAgICAgICAgICAgICAgPE5hbWU+RmFjdG9yeSBSZXNldDwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPkU8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPjwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW1BlcmZvcm0gZmFjdG9yeSByZXNldCBvZiB0aGUgTHdNMk0gRGV2aWNlIHRvIG1ha2UgdGhlIEx3TTJNIERldmljZSB0byBnbyB0aHJvdWdoIGluaXRpYWwgZGVwbG95bWVudCBzZXF1ZW5jZSB3aGVyZSBwcm92aXNpb25pbmcgYW5kIGJvb3RzdHJhcCBzZXF1ZW5jZSBpcyBwZXJmb3JtZWQuIFRoaXMgcmVxdWlyZXMgY2xpZW50IGVuc3VyaW5nIHBvc3QgZmFjdG9yeSByZXNldCB0byBoYXZlIG1pbmltYWwgaW5mb3JtYXRpb24gdG8gYWxsb3cgaXQgdG8gY2Fycnkgb3V0IG9uZSBvZiB0aGUgYm9vdHN0cmFwIG1ldGhvZHMgc3BlY2lmaWVkIGluIHNlY3Rpb24gNS4yLjMuCldoZW4gdGhpcyBSZXNvdXJjZSBpcyBleGVjdXRlZCwgIkRlLXJlZ2lzdGVyIiBvcGVyYXRpb24gTUFZIGJlIHNlbnQgdG8gdGhlIEx3TTJNIFNlcnZlcihzKSBiZWZvcmUgZmFjdG9yeSByZXNldCBvZiB0aGUgTHdNMk0gRGV2aWNlLl1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjYiPgogICAgICAgICAgICAgICAgPE5hbWU+QXZhaWxhYmxlIFBvd2VyIFNvdXJjZXM8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPkludGVnZXI8L1R5cGU+CiAgICAgICAgICAgICAgICA8UmFuZ2VFbnVtZXJhdGlvbj4wLi43PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbMDogREMgcG93ZXIKMTogSW50ZXJuYWwgQmF0dGVyeQoyOiBFeHRlcm5hbCBCYXR0ZXJ5CjM6IEZ1ZWwgQ2VsbAo0OiBQb3dlciBvdmVyIEV0aGVybmV0CjU6IFVTQgo2OiBBQyAoTWFpbnMpIHBvd2VyCjc6IFNvbGFyClRoZSBzYW1lIFJlc291cmNlIEluc3RhbmNlIElEIE1VU1QgYmUgdXNlZCB0byBhc3NvY2lhdGUgYSBnaXZlbiBQb3dlciBTb3VyY2UgKFJlc291cmNlIElEOjYpIHdpdGggaXRzIFByZXNlbnQgVm9sdGFnZSAoUmVzb3VyY2UgSUQ6NykgYW5kIGl0cyBQcmVzZW50IEN1cnJlbnQgKFJlc291cmNlIElEOjgpXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iNyI+CiAgICAgICAgICAgICAgICA8TmFtZT5Qb3dlciBTb3VyY2UgVm9sdGFnZTwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlI8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+TXVsdGlwbGU8L011bHRpcGxlSW5zdGFuY2VzPgogICAgICAgICAgICAgICAgPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgogICAgICAgICAgICAgICAgPFR5cGU+SW50ZWdlcjwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW1ByZXNlbnQgdm9sdGFnZSBmb3IgZWFjaCBBdmFpbGFibGUgUG93ZXIgU291cmNlcyBSZXNvdXJjZSBJbnN0YW5jZS4gVGhlIHVuaXQgdXNlZCBmb3IgdGhpcyByZXNvdXJjZSBpcyBpbiBtVi5dXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSI4Ij4KICAgICAgICAgICAgICAgIDxOYW1lPlBvd2VyIFNvdXJjZSBDdXJyZW50PC9OYW1lPgogICAgICAgICAgICAgICAgPE9wZXJhdGlvbnM+UjwvT3BlcmF0aW9ucz4KICAgICAgICAgICAgICAgIDxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5JbnRlZ2VyPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbUHJlc2VudCBjdXJyZW50IGZvciBlYWNoIEF2YWlsYWJsZSBQb3dlciBTb3VyY2UuIFRoZSB1bml0IHVzZWQgZm9yIHRoaXMgcmVzb3VyY2UgaXMgaW4gbUEuXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iOSI+CiAgICAgICAgICAgICAgICA8TmFtZT5CYXR0ZXJ5IExldmVsPC9OYW1lPgogICAgICAgICAgICAgICAgPE9wZXJhdGlvbnM+UjwvT3BlcmF0aW9ucz4KICAgICAgICAgICAgICAgIDxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgogICAgICAgICAgICAgICAgPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgogICAgICAgICAgICAgICAgPFR5cGU+SW50ZWdlcjwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjAuLjEwMDwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz4vMTAwPC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVtDb250YWlucyB0aGUgY3VycmVudCBiYXR0ZXJ5IGxldmVsIGFzIGEgcGVyY2VudGFnZSAod2l0aCBhIHJhbmdlIGZyb20gMCB0byAxMDApLiBUaGlzIHZhbHVlIGlzIG9ubHkgdmFsaWQgZm9yIHRoZSBEZXZpY2UgaW50ZXJuYWwgQmF0dGVyeSBpZiBwcmVzZW50IChvbmUgQXZhaWxhYmxlIFBvd2VyIFNvdXJjZXMgUmVzb3VyY2UgSW5zdGFuY2UgaXMgMSkuXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iMTAiPgogICAgICAgICAgICAgICAgPE5hbWU+TWVtb3J5IEZyZWU8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5JbnRlZ2VyPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbRXN0aW1hdGVkIGN1cnJlbnQgYXZhaWxhYmxlIGFtb3VudCBvZiBzdG9yYWdlIHNwYWNlIHdoaWNoIGNhbiBzdG9yZSBkYXRhIGFuZCBzb2Z0d2FyZSBpbiB0aGUgTHdNMk0gRGV2aWNlIChleHByZXNzZWQgaW4ga2lsb2J5dGVzKS4gTm90ZTogMSBraWxvYnl0ZSBjb3JyZXNwb25kcyB0byAxMDAwIGJ5dGVzLl1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjExIj4KICAgICAgICAgICAgICAgIDxOYW1lPkVycm9yIENvZGU8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+TWFuZGF0b3J5PC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5JbnRlZ2VyPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+MC4uMzI8L1JhbmdlRW51bWVyYXRpb24+CiAgICAgICAgICAgICAgICA8VW5pdHM+PC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVswPU5vIGVycm9yCjE9TG93IGJhdHRlcnkgcG93ZXIKMj1FeHRlcm5hbCBwb3dlciBzdXBwbHkgb2ZmCjM9R1BTIG1vZHVsZSBmYWlsdXJlCjQ9TG93IHJlY2VpdmVkIHNpZ25hbCBzdHJlbmd0aAo1PU91dCBvZiBtZW1vcnkKNj1TTVMgZmFpbHVyZQo3PUlQIGNvbm5lY3Rpdml0eSBmYWlsdXJlCjg9UGVyaXBoZXJhbCBtYWxmdW5jdGlvbgo5Li4xNT1SZXNlcnZlZCBmb3IgZnV0dXJlIHVzZQoxNi4uMzI9RGV2aWNlIHNwZWNpZmljIGVycm9yIGNvZGVzCgpXaGVuIHRoZSBzaW5nbGUgRGV2aWNlIE9iamVjdCBJbnN0YW5jZSBpcyBpbml0aWF0ZWQsIHRoZXJlIGlzIG9ubHkgb25lIGVycm9yIGNvZGUgUmVzb3VyY2UgSW5zdGFuY2Ugd2hvc2UgdmFsdWUgaXMgZXF1YWwgdG8gMCB0aGF0IG1lYW5zIG5vIGVycm9yLiBXaGVuIHRoZSBmaXJzdCBlcnJvciBoYXBwZW5zLCB0aGUgTHdNMk0gQ2xpZW50IGNoYW5nZXMgZXJyb3IgY29kZSBSZXNvdXJjZSBJbnN0YW5jZSB0byBhbnkgbm9uLXplcm8gdmFsdWUgdG8gaW5kaWNhdGUgdGhlIGVycm9yIHR5cGUuIFdoZW4gYW55IG90aGVyIGVycm9yIGhhcHBlbnMsIGEgbmV3IGVycm9yIGNvZGUgUmVzb3VyY2UgSW5zdGFuY2UgaXMgY3JlYXRlZC4gV2hlbiBhbiBlcnJvciBhc3NvY2lhdGVkIHdpdGggYSBSZXNvdXJjZSBJbnN0YW5jZSBpcyBubyBsb25nZXIgcHJlc2VudCwgdGhhdCBSZXNvdXJjZSBJbnN0YW5jZSBpcyBkZWxldGVkLiBXaGVuIHRoZSBzaW5nbGUgZXhpc3RpbmcgZXJyb3IgaXMgbm8gbG9uZ2VyIHByZXNlbnQsIHRoZSBMd00yTSBDbGllbnQgcmV0dXJucyB0byB0aGUgb3JpZ2luYWwgbm8gZXJyb3Igc3RhdGUgd2hlcmUgSW5zdGFuY2UgMCBoYXMgdmFsdWUgMC4KVGhpcyBlcnJvciBjb2RlIFJlc291cmNlIE1BWSBiZSBvYnNlcnZlZCBieSB0aGUgTHdNMk0gU2VydmVyLiBIb3cgdG8gZGVhbCB3aXRoIEx3TTJNIENsaWVudOKAmXMgZXJyb3IgcmVwb3J0IGRlcGVuZHMgb24gdGhlIHBvbGljeSBvZiB0aGUgTHdNMk0gU2VydmVyLiBFcnJvciBjb2RlcyBpbiBiZXR3ZWVuIDE2IGFuZCAzMiBhcmUgc3BlY2lmaWMgdG8gdGhlIERldmljZSBhbmQgbWF5IGhhdmUgZGlmZmVyZW50IG1lYW5pbmdzIGFtb25nIGltcGxlbWVudGF0aW9ucy5dXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIxMiI+CiAgICAgICAgICAgICAgICA8TmFtZT5SZXNldCBFcnJvciBDb2RlPC9OYW1lPgogICAgICAgICAgICAgICAgPE9wZXJhdGlvbnM+RTwvT3BlcmF0aW9ucz4KICAgICAgICAgICAgICAgIDxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgogICAgICAgICAgICAgICAgPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgogICAgICAgICAgICAgICAgPFR5cGU+PC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbRGVsZXRlIGFsbCBlcnJvciBjb2RlIFJlc291cmNlIEluc3RhbmNlcyBhbmQgY3JlYXRlIG9ubHkgb25lIHplcm8tdmFsdWUgZXJyb3IgY29kZSB0aGF0IGltcGxpZXMgbm8gZXJyb3IsIHRoZW4gcmUtZXZhbHVhdGUgYWxsIGVycm9yIGNvbmRpdGlvbnMgYW5kIHVwZGF0ZSBhbmQgY3JlYXRlIFJlc291cmNlcyBJbnN0YW5jZXMgdG8gY2FwdHVyZSBhbGwgY3VycmVudCBlcnJvciBjb25kaXRpb25zLl1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjEzIj4KICAgICAgICAgICAgICAgIDxOYW1lPkN1cnJlbnQgVGltZTwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5UaW1lPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbQ3VycmVudCBVTklYIHRpbWUgb2YgdGhlIEx3TTJNIENsaWVudC4KVGhlIEx3TTJNIENsaWVudCBzaG91bGQgYmUgcmVzcG9uc2libGUgdG8gaW5jcmVhc2UgdGhpcyB0aW1lIHZhbHVlIGFzIGV2ZXJ5IHNlY29uZCBlbGFwc2VzLgpUaGUgTHdNMk0gU2VydmVyIGlzIGFibGUgdG8gd3JpdGUgdGhpcyBSZXNvdXJjZSB0byBtYWtlIHRoZSBMd00yTSBDbGllbnQgc3luY2hyb25pemVkIHdpdGggdGhlIEx3TTJNIFNlcnZlci5dXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIxNCI+CiAgICAgICAgICAgICAgICA8TmFtZT5VVEMgT2Zmc2V0PC9OYW1lPgogICAgICAgICAgICAgICAgPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPlN0cmluZzwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgVVRDIG9mZnNldCBjdXJyZW50bHkgaW4gZWZmZWN0IGZvciB0aGlzIEx3TTJNIERldmljZS4gVVRDK1ggW0lTTyA4NjAxXS5dXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIxNSI+CiAgICAgICAgICAgICAgICA8TmFtZT5UaW1lem9uZTwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5TdHJpbmc8L1R5cGU+CiAgICAgICAgICAgICAgICA8UmFuZ2VFbnVtZXJhdGlvbj48L1JhbmdlRW51bWVyYXRpb24+CiAgICAgICAgICAgICAgICA8VW5pdHM+PC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgaW4gd2hpY2ggdGltZSB6b25lIHRoZSBMd00yTSBEZXZpY2UgaXMgbG9jYXRlZCwgaW4gSUFOQSBUaW1lem9uZSAoVFopIGRhdGFiYXNlIGZvcm1hdC5dXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIxNiI+CiAgICAgICAgICAgICAgICA8TmFtZT5TdXBwb3J0ZWQgQmluZGluZyBhbmQgTW9kZXM8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgogICAgICAgICAgICAgICAgPFR5cGU+U3RyaW5nPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHdoaWNoIGJpbmRpbmdzIGFuZCBtb2RlcyBhcmUgc3VwcG9ydGVkIGluIHRoZSBMd00yTSBDbGllbnQuIFRoZSBwb3NzaWJsZSB2YWx1ZXMgYXJlIHRob3NlIGxpc3RlZCBpbiB0aGUgTHdNMk0gQ29yZSBTcGVjaWZpY2F0aW9uLl1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjE3Ij48TmFtZT5EZXZpY2UgVHlwZTwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlI8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPlN0cmluZzwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW1R5cGUgb2YgdGhlIGRldmljZSAobWFudWZhY3R1cmVyIHNwZWNpZmllZCBzdHJpbmc6IGUuZy4gc21hcnQgbWV0ZXJzIC8gZGV2IENsYXNzIC8gLi4uKV1dPjwvRGVzY3JpcHRpb24+CiAgICAgICAgICAgIDwvSXRlbT4KICAgICAgICAgICAgPEl0ZW0gSUQ9IjE4Ij48TmFtZT5IYXJkd2FyZSBWZXJzaW9uPC9OYW1lPgogICAgICAgICAgICAgICAgPE9wZXJhdGlvbnM+UjwvT3BlcmF0aW9ucz4KICAgICAgICAgICAgICAgIDxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgogICAgICAgICAgICAgICAgPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgogICAgICAgICAgICAgICAgPFR5cGU+U3RyaW5nPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+PC9SYW5nZUVudW1lcmF0aW9uPgogICAgICAgICAgICAgICAgPFVuaXRzPjwvVW5pdHM+CiAgICAgICAgICAgICAgICA8RGVzY3JpcHRpb24+PCFbQ0RBVEFbQ3VycmVudCBoYXJkd2FyZSB2ZXJzaW9uIG9mIHRoZSBkZXZpY2VdXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIxOSI+PE5hbWU+U29mdHdhcmUgVmVyc2lvbjwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlI8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPlN0cmluZzwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW0N1cnJlbnQgc29mdHdhcmUgdmVyc2lvbiBvZiB0aGUgZGV2aWNlIChtYW51ZmFjdHVyZXIgc3BlY2lmaWVkIHN0cmluZykuIE9uIGVsYWJvcmF0ZWQgTHdNMk0gZGV2aWNlLCBTVyBjb3VsZCBiZSBzcGxpdCBpbiAyIHBhcnRzOiBhIGZpcm13YXJlIG9uZSBhbmQgYSBoaWdoZXIgbGV2ZWwgc29mdHdhcmUgb24gdG9wLgpCb3RoIHBpZWNlcyBvZiBTb2Z0d2FyZSBhcmUgdG9nZXRoZXIgbWFuYWdlZCBieSBMd00yTSBGaXJtd2FyZSBVcGRhdGUgT2JqZWN0IChPYmplY3QgSUQgNSldXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+CiAgICAgICAgICAgIDxJdGVtIElEPSIyMCI+PE5hbWU+QmF0dGVyeSBTdGF0dXM8L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CiAgICAgICAgICAgICAgICA8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CiAgICAgICAgICAgICAgICA8VHlwZT5JbnRlZ2VyPC9UeXBlPgogICAgICAgICAgICAgICAgPFJhbmdlRW51bWVyYXRpb24+MC4uNjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW1RoaXMgdmFsdWUgaXMgb25seSB2YWxpZCBmb3IgdGhlIERldmljZSBJbnRlcm5hbCBCYXR0ZXJ5IGlmIHByZXNlbnQgKG9uZSBBdmFpbGFibGUgUG93ZXIgU291cmNlcyBSZXNvdXJjZSBJbnN0YW5jZSB2YWx1ZSBpcyAxKS4KQmF0dGVyeQpTdGF0dXMJTWVhbmluZwlEZXNjcmlwdGlvbgowCU5vcm1hbAlUaGUgYmF0dGVyeSBpcyBvcGVyYXRpbmcgbm9ybWFsbHkgYW5kIG5vdCBvbiBwb3dlci4KMQlDaGFyZ2luZwlUaGUgYmF0dGVyeSBpcyBjdXJyZW50bHkgY2hhcmdpbmcuCjIJQ2hhcmdlIENvbXBsZXRlCVRoZSBiYXR0ZXJ5IGlzIGZ1bGx5IGNoYXJnZWQgYW5kIHN0aWxsIG9uIHBvd2VyLgozCURhbWFnZWQJVGhlIGJhdHRlcnkgaGFzIHNvbWUgcHJvYmxlbS4KNAlMb3cgQmF0dGVyeQlUaGUgYmF0dGVyeSBpcyBsb3cgb24gY2hhcmdlLgo1CU5vdCBJbnN0YWxsZWQJVGhlIGJhdHRlcnkgaXMgbm90IGluc3RhbGxlZC4KNglVbmtub3duCVRoZSBiYXR0ZXJ5IGluZm9ybWF0aW9uIGlzIG5vdCBhdmFpbGFibGUuXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iMjEiPjxOYW1lPk1lbW9yeSBUb3RhbDwvTmFtZT4KICAgICAgICAgICAgICAgIDxPcGVyYXRpb25zPlI8L09wZXJhdGlvbnM+CiAgICAgICAgICAgICAgICA8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPkludGVnZXI8L1R5cGU+CiAgICAgICAgICAgICAgICA8UmFuZ2VFbnVtZXJhdGlvbj48L1JhbmdlRW51bWVyYXRpb24+CiAgICAgICAgICAgICAgICA8VW5pdHM+PC9Vbml0cz4KICAgICAgICAgICAgICAgIDxEZXNjcmlwdGlvbj48IVtDREFUQVtUb3RhbCBhbW91bnQgb2Ygc3RvcmFnZSBzcGFjZSB3aGljaCBjYW4gc3RvcmUgZGF0YSBhbmQgc29mdHdhcmUgaW4gdGhlIEx3TTJNIERldmljZSAoZXhwcmVzc2VkIGluIGtpbG9ieXRlcykuICBOb3RlOiAxIGtpbG9ieXRlIGNvcnJlc3BvbmRzIHRvIDEwMDAgYnl0ZXMuXV0+PC9EZXNjcmlwdGlvbj4KICAgICAgICAgICAgPC9JdGVtPgogICAgICAgICAgICA8SXRlbSBJRD0iMjIiPjxOYW1lPkV4dERldkluZm88L05hbWU+CiAgICAgICAgICAgICAgICA8T3BlcmF0aW9ucz5SPC9PcGVyYXRpb25zPgogICAgICAgICAgICAgICAgPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KICAgICAgICAgICAgICAgIDxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KICAgICAgICAgICAgICAgIDxUeXBlPk9iamxuazwvVHlwZT4KICAgICAgICAgICAgICAgIDxSYW5nZUVudW1lcmF0aW9uPjwvUmFuZ2VFbnVtZXJhdGlvbj4KICAgICAgICAgICAgICAgIDxVbml0cz48L1VuaXRzPgogICAgICAgICAgICAgICAgPERlc2NyaXB0aW9uPjwhW0NEQVRBW1JlZmVyZW5jZSB0byBleHRlcm5hbCAiRGV2aWNlIiBvYmplY3QgaW5zdGFuY2UgY29udGFpbmluZyBpbmZvcm1hdGlvbi4gRm9yIGV4YW1wbGUsIHN1Y2ggYW4gZXh0ZXJuYWwgZGV2aWNlIGNhbiBiZSBhIEhvc3QgRGV2aWNlLCB3aGljaCBpcyBhIGRldmljZSBpbnRvIHdoaWNoIHRoZSBEZXZpY2UgY29udGFpbmluZyB0aGUgTHdNMk0gY2xpZW50IGlzIGVtYmVkZGVkLiBUaGlzIFJlc291cmNlIG1heSBiZSB1c2VkIHRvIHJldHJpZXZlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBIb3N0IERldmljZS5dXT48L0Rlc2NyaXB0aW9uPgogICAgICAgICAgICA8L0l0ZW0+PC9SZXNvdXJjZXM+CiAgICAgICAgPERlc2NyaXB0aW9uMj48L0Rlc2NyaXB0aW9uMj4KICAgIDwvT2JqZWN0Pgo8L0xXTTJNPgo=" } \ No newline at end of file diff --git a/pom.xml b/pom.xml index b851b8fc10..736c49e80f 100755 --- a/pom.xml +++ b/pom.xml @@ -848,6 +848,8 @@ .run/** **/NetworkReceive.java **/lwm2m-registry/** + **/test/resources/lwm2m/** + **/resources/lwm2m/models/** src/main/data/resources/** From 56ba30a4e4f3432023b32f92652fa93f3d7a25ca Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 28 Feb 2025 16:19:35 +0200 Subject: [PATCH 12/14] Use CLIENT_SCOPE by default for Push to cloud node --- .../main/data/json/edge/rule_chains/edge_root_rule_chain.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json index 6701b59e0e..ef0bf2698f 100644 --- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json @@ -119,7 +119,7 @@ "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "name": "Push to cloud", "configuration": { - "scope": "SERVER_SCOPE" + "scope": "CLIENT_SCOPE" }, "externalId": null }, From b29cc4fc413a481ea6ce49b867a90d6aadfe8a51 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 3 Mar 2025 11:20:04 +0200 Subject: [PATCH 13/14] Fix tupo --- .../org/thingsboard/server/queue/discovery/TopicService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java index 3e3051ee21..5992083d85 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java @@ -47,7 +47,7 @@ public class TopicService { @Value("${queue.edge.event-notifications-topic:tb_edge_event.notifications}") private String tbEdgeEventNotificationsTopic; - @Value("${queue.calculated_fields.notifications-topic:calculated_field}") + @Value("${queue.calculated_fields.notifications-topic:calculated_field.notifications}") private String tbCalculatedFieldNotificationsTopic; private final ConcurrentMap tbCoreNotificationTopics = new ConcurrentHashMap<>(); From bfef1a3ea3c7068c426ad3c69002c296532849d5 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 3 Mar 2025 11:21:50 +0200 Subject: [PATCH 14/14] Fix notifications_topic for cf --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 730dfec128..ec9925bf06 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1759,7 +1759,7 @@ queue: # Topic name for Calculated Field (CF) compacted states state_topic: "${TB_QUEUE_CF_STATE_TOPIC:tb_cf_state}" # For high-priority notifications that require minimum latency and processing time - notifications_topic: "${TB_QUEUE_CF_NOTIFICATIONS_TOPIC:calculated_field}" + notifications_topic: "${TB_QUEUE_CF_NOTIFICATIONS_TOPIC:calculated_field.notifications}" # Interval in milliseconds to poll messages by CF (Rule Engine) microservices poll_interval: "${TB_QUEUE_CF_POLL_INTERVAL_MS:25}" # Amount of partitions used by CF microservices