Browse Source

Merge branch 'master' into fix-edge-zombie-consumer-cleanup

pull/13843/head
ShadowBlades 1 year ago
committed by GitHub
parent
commit
2966ec77fc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 171
      README.md
  2. 30
      application/src/main/data/upgrade/basic/schema_update.sql
  3. 3
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  4. 12
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java
  5. 61
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  6. 3
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  7. 10
      application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java
  8. 8
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java
  10. 59
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  11. 3
      application/src/main/resources/thingsboard.yml
  12. 2
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  13. 95
      application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java
  14. 3
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  15. 34
      common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitMsg.java
  16. 36
      common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java
  17. 34
      common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldLinkInitMsg.java
  18. 2
      dao/src/test/java/org/thingsboard/server/dao/AbstractRedisClusterContainer.java
  19. 2
      dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java
  20. 2
      dao/src/test/java/org/thingsboard/server/dao/RedisJUnit5Test.java
  21. 2
      docker/docker-compose.kafka.yml
  22. 12
      docker/docker-compose.valkey-cluster.yml
  23. 6
      docker/docker-compose.valkey-sentinel.yml
  24. 2
      docker/docker-compose.valkey.yml
  25. 2
      msa/black-box-tests/src/test/resources/docker-compose.valkey-ssl.yml
  26. 7
      ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts

171
README.md

@ -1,43 +1,146 @@
# ThingsBoard
[![ThingsBoard Builds Server Status](https://img.shields.io/teamcity/build/e/ThingsBoard_Build?label=TB%20builds%20server&server=https%3A%2F%2Fbuilds.thingsboard.io&logo=data:image/png;base64,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&labelColor=305680)](https://builds.thingsboard.io/viewType.html?buildTypeId=ThingsBoard_Build&guest=1)
![banner](https://github.com/user-attachments/assets/3584b592-33dd-4fb4-91d4-47b62b34806c)
<div align="center">
# Open-source IoT platform for data collection, processing, visualization, and device management.
</div>
<br>
<div align="center">
💡 [Get started](https://thingsboard.io/docs/getting-started-guides/helloworld/)&ensp;•&ensp;🌐 [Website](https://thingsboard.io/)&ensp;•&ensp;📚 [Documentation](https://thingsboard.io/docs/)&ensp;•&ensp;📔 [Blog](https://thingsboard.io/blog/)&ensp;•&ensp;▶️ [Live demo](https://demo.thingsboard.io/signup)&ensp;•&ensp;🔗 [LinkedIn](https://www.linkedin.com/company/thingsboard/posts/?feedView=all)
</div>
## 🚀 Installation options
* Install ThingsBoard [On-premise](https://thingsboard.io/docs/user-guide/install/installation-options/?ceInstallType=onPremise)
* Try [ThingsBoard Cloud](https://thingsboard.io/installations/)
* or [Use our Live demo](https://demo.thingsboard.io/signup)
## 💡 Getting started with ThingsBoard
Check out our [Getting Started guide](https://thingsboard.io/docs/getting-started-guides/helloworld/) or [watch the video](https://www.youtube.com/watch?v=80L0ubQLXsc) to learn the basics of ThingsBoard and create your first dashboard! You will learn to:
* Connect devices to ThingsBoard
* Push data from devices to ThingsBoard
* Build real-time dashboards
* Create a Customer and assign the dashboard with them.
* Define thresholds and trigger alarms
* Set up notifications via email, SMS, mobile apps, or integrate with third-party services.
## ✨ Features
<table>
<tr>
<td width="50%" valign="top">
<br>
<div align="center">
<img src="https://github.com/user-attachments/assets/255cca4f-b111-44e8-99ea-0af55f8e3681" alt="Provision and manage devices and assets" width="378" />
<h3>Provision and manage <br> devices and assets</h3>
</div>
<div align="center">
<p>Provision, monitor and control your IoT entities in secure way using rich server-side APIs. Define relations between your devices, assets, customers or any other entities.</p>
</div>
<div align="center">
<a href="https://thingsboard.io/docs/user-guide/entities-and-relations/">Read more 🡪</a>
</div>
<br>
</td>
<td width="50%" valign="top">
<br>
<div align="center">
<img src="https://github.com/user-attachments/assets/24b41d10-150a-42dd-ab1a-32ac9b5978c1" alt="Collect and visualize your data" width="378" />
<h3>Collect and visualize <br> your data</h3>
</div>
<div align="center">
<p>Collect and store telemetry data in scalable and fault-tolerant way. Visualize your data with built-in or custom widgets and flexible dashboards. Share dashboards with your customers.</p>
</div>
<div align="center">
<a href="https://thingsboard.io/iot-data-visualization/">Read more 🡪</a>
</div>
<br>
</td>
</tr>
<tr>
<td width="50%" valign="top">
<br>
<div align="center">
<img src="https://github.com/user-attachments/assets/6f2a6dd2-7b33-4d17-8b92-d1f995adda2c" alt="SCADA Dashboards" width="378" />
<h3>SCADA Dashboards</h3>
</div>
<div align="center">
<p>Monitor and control your industrial processes in real time with SCADA. Use SCADA symbols on dashboards to create and manage any workflow, offering full flexibility to design and oversee operations according to your requirements.</p>
</div>
<div align="center">
<a href="https://thingsboard.io/use-cases/scada/">Read more 🡪</a>
</div>
<br>
</td>
<td width="50%">
<br>
<div align="center">
<img src="https://github.com/user-attachments/assets/c23dcc9b-aeba-40ef-9973-49b953fc1257" alt="Process and React" width="378" />
<h3>Process and React</h3>
</div>
<div align="center">
<p>Define data processing rule chains. Transform and normalize your device data. Raise alarms on incoming telemetry events, attribute updates, device inactivity and user actions.<br></p>
</div>
<br>
<div align="center">
<a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/">Read more 🡪</a>
</div>
<br>
</td>
</tr>
</table>
## ⚙️ Powerful IoT Rule Engine
ThingsBoard allows you to create complex [Rule Chains](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) to process data from your devices and match your application specific use cases.
[![IoT Rule Engine](https://github.com/user-attachments/assets/ccc048a8-5aa3-44dc-abd4-c20d1d833102 "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
<div align="center">
[**Read more about Rule Engine 🡪**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
</div>
## 📦 Real-Time IoT Dashboards
ThingsBoard is a scalable, user-friendly, and device-agnostic IoT platform that speeds up time-to-market with powerful built-in solution templates. It enables data collection and analysis from any devices, saving resources on routine tasks and letting you focus on your solution’s unique aspects. See more our Use Cases [here](https://thingsboard.io/iot-use-cases/).
[**Smart energy**](https://thingsboard.io/use-cases/smart-energy/)
[![Smart energy](https://github.com/user-attachments/assets/7952d0f1-2ba4-4989-bfc9-75b40de6ea3f "Smart energy")](https://thingsboard.io/use-cases/smart-energy/)
[**SCADA swimming pool**](https://thingsboard.io/use-cases/scada/)
[![SCADA Swimming pool](https://github.com/user-attachments/assets/b357c129-ea72-4b64-9dfe-ac25011603b6 "SCADA Swimming pool")](https://thingsboard.io/use-cases/scada/)
[**Fleet tracking**](https://thingsboard.io/use-cases/fleet-tracking/)
[![Fleet tracking](https://github.com/user-attachments/assets/80b63841-40c9-4db9-bec2-6a400dc6e58d "Fleet tracking")](https://thingsboard.io/use-cases/fleet-tracking/)
[**Smart farming**](https://thingsboard.io/use-cases/smart-farming/)
[![Smart farming](https://github.com/user-attachments/assets/8fe84ad6-6ea4-4cb1-bc31-6cd5c20c357b "Smart farming")](https://thingsboard.io/use-cases/smart-farming/)
ThingsBoard is an open-source IoT platform for data collection, processing, visualization, and device management.
<img src="./img/logo.png?raw=true" width="100" height="100">
## Documentation
ThingsBoard documentation is hosted on [thingsboard.io](https://thingsboard.io/docs).
## IoT use cases
[**Smart energy**](https://thingsboard.io/smart-energy/)
[![Smart energy](https://user-images.githubusercontent.com/8308069/152984256-eb48564a-645c-468d-912b-f554b63104a5.gif "Smart energy")](https://thingsboard.io/smart-energy/)
[**SCADA Swimming pool**](https://thingsboard.io/use-cases/scada/)
[![SCADA Swimming pool](https://github.com/user-attachments/assets/0878a2f5-d358-47c5-b295-03b4533685cf "SCADA Swimming pool")](https://thingsboard.io/use-cases/scada/)
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/)
[![Fleet tracking](https://user-images.githubusercontent.com/8308069/152984528-0054ed55-8b8b-4cda-ba45-02fe95a81222.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/)
[**Smart farming**](https://thingsboard.io/smart-farming/)
[![Smart farming](https://user-images.githubusercontent.com/8308069/152984443-a98b7d3d-ff7a-4037-9011-e71e1e6f755f.gif "Smart farming")](https://thingsboard.io/smart-farming/)
[**Smart metering**](https://thingsboard.io/smart-metering/)
[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[![IoT Rule Engine](https://img.thingsboard.io/demo/send-email-rule-chain.gif "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[![Smart metering](https://github.com/user-attachments/assets/564e5ed0-afad-452c-a16c-6270b468ebdc "Smart metering")](https://thingsboard.io/smart-metering/)
[**Smart metering**](https://thingsboard.io/smart-metering/)
[![Smart metering](https://user-images.githubusercontent.com/8308069/31455788-6888a948-aec1-11e7-9819-410e0ba785e0.gif "Smart metering")](https://thingsboard.io/smart-metering/)
<div align="center">
## Getting Started
[**Check more of our use cases 🡪**](https://thingsboard.io/iot-use-cases/)
Collect and Visualize your IoT data in minutes by following this [guide](https://thingsboard.io/docs/getting-started-guides/helloworld/).
</div>
## Support
## 🫶 Support
- [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard)
To get support, please visit our [GitHub issues page](https://github.com/thingsboard/thingsboard/issues)
## Licenses
## 📄 Licenses
This project is released under [Apache 2.0 License](./LICENSE).
This project is released under [Apache 2.0 License](./LICENSE)

30
application/src/main/data/upgrade/basic/schema_update.sql

@ -14,33 +14,3 @@
-- limitations under the License.
--
-- UPDATE OTA PACKAGE EXTERNAL ID START
ALTER TABLE ota_package
ADD COLUMN IF NOT EXISTS external_id uuid;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'ota_package_external_id_unq_key') THEN
ALTER TABLE ota_package ADD CONSTRAINT ota_package_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
-- UPDATE OTA PACKAGE EXTERNAL ID END
-- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT START
DROP INDEX IF EXISTS idx_device_external_id;
DROP INDEX IF EXISTS idx_device_profile_external_id;
DROP INDEX IF EXISTS idx_asset_external_id;
DROP INDEX IF EXISTS idx_entity_view_external_id;
DROP INDEX IF EXISTS idx_rule_chain_external_id;
DROP INDEX IF EXISTS idx_dashboard_external_id;
DROP INDEX IF EXISTS idx_customer_external_id;
DROP INDEX IF EXISTS idx_widgets_bundle_external_id;
-- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT END
ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS title varchar(255);

3
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -114,9 +114,6 @@ public class AppActor extends ContextAwareActor {
ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
break;
case CF_CACHE_INIT_MSG:
case CF_INIT_PROFILE_ENTITY_MSG:
case CF_INIT_MSG:
case CF_LINK_INIT_MSG:
case CF_STATE_RESTORE_MSG:
//TODO: use priority from the message body. For example, messages about CF lifecycle are important and Device lifecycle are not.
// same for the Linked telemetry.

12
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java

@ -24,9 +24,6 @@ import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
/**
@ -70,15 +67,6 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor {
case CF_CACHE_INIT_MSG:
processor.onCacheInitMsg((CalculatedFieldCacheInitMsg) msg);
break;
case CF_INIT_PROFILE_ENTITY_MSG:
processor.onProfileEntityMsg((CalculatedFieldInitProfileEntityMsg) msg);
break;
case CF_INIT_MSG:
processor.onFieldInitMsg((CalculatedFieldInitMsg) msg);
break;
case CF_LINK_INIT_MSG:
processor.onLinkInitMsg((CalculatedFieldLinkInitMsg) msg);
break;
case CF_STATE_RESTORE_MSG:
processor.onStateRestoreMsg((CalculatedFieldStateRestoreMsg) msg);
break;

61
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -35,9 +35,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
@ -120,37 +117,6 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
msg.getCallback().onSuccess();
}
public void onProfileEntityMsg(CalculatedFieldInitProfileEntityMsg msg) {
log.debug("[{}] Processing profile entity message.", msg.getTenantId().getId());
entityProfileCache.add(msg.getProfileEntityId(), msg.getEntityId());
msg.getCallback().onSuccess();
}
public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
log.debug("[{}] Processing CF init message.", msg.getCf().getId());
var cf = msg.getCf();
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
try {
cfCtx.init();
} catch (Exception e) {
throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
}
calculatedFields.put(cf.getId(), cfCtx);
// We use copy on write lists to safely pass the reference to another actor for the iteration.
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
msg.getCallback().onSuccess();
}
public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) {
log.debug("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId());
var link = msg.getLink();
// We use copy on write lists to safely pass the reference to another actor for the iteration.
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link);
msg.getCallback().onSuccess();
}
public void onStateRestoreMsg(CalculatedFieldStateRestoreMsg msg) {
var cfId = msg.getId().cfId();
var calculatedField = calculatedFields.get(cfId);
@ -566,20 +532,37 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
cfs.forEach(cf -> {
log.trace("Processing calculated field record: {}", cf);
try {
onFieldInitMsg(new CalculatedFieldInitMsg(cf.getTenantId(), cf));
initCalculatedField(cf);
} catch (CalculatedFieldException e) {
log.error("Failed to process calculated field record: {}", cf, e);
}
});
calculatedFields.values().forEach(cf -> {
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cf);
});
PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(pageLink -> cfDaoService.findAllCalculatedFieldLinksByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
cfls.forEach(link -> {
onLinkInitMsg(new CalculatedFieldLinkInitMsg(link.getTenantId(), link));
log.trace("Processing calculated field link record: {}", link);
initCalculatedFieldLink(link);
});
}
private void initCalculatedField(CalculatedField cf) throws CalculatedFieldException {
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
try {
cfCtx.init();
} catch (Exception e) {
throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
}
calculatedFields.put(cf.getId(), cfCtx);
// We use copy on write lists to safely pass the reference to another actor for the iteration.
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
}
private void initCalculatedFieldLink(CalculatedFieldLink link) {
// We use copy on write lists to safely pass the reference to another actor for the iteration.
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link);
}
private void initEntityProfileCache() {
PageDataIterable<ProfileEntityIdInfo> deviceIdInfos = new PageDataIterable<>(pageLink -> deviceService.findProfileEntityIdInfosByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
for (ProfileEntityIdInfo idInfo : deviceIdInfos) {

3
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -180,9 +180,6 @@ public class TenantActor extends RuleChainManagerActor {
onRuleChainMsg((RuleChainAwareMsg) msg);
break;
case CF_CACHE_INIT_MSG:
case CF_INIT_PROFILE_ENTITY_MSG:
case CF_INIT_MSG:
case CF_LINK_INIT_MSG:
case CF_STATE_RESTORE_MSG:
case CF_PARTITIONS_CHANGE_MSG:
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);

10
application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java

@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@ -60,7 +61,10 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_
@RequestMapping(TbUrlConstants.RULE_ENGINE_URL_PREFIX)
@Slf4j
public class RuleEngineController extends BaseController {
public static final int DEFAULT_TIMEOUT = 10000;
@Value("${server.rest.rule_engine.response_timeout:10000}")
public int defaultResponseTimeout;
private static final String MSG_DESCRIPTION_PREFIX = "Creates the Message with type 'REST_API_REQUEST' and payload taken from the request body. ";
private static final String MSG_DESCRIPTION = "This method allows you to extend the regular platform API with the power of Rule Engine. You may use default and custom rule nodes to handle the message. " +
"The generated message contains two important metadata fields:\n\n" +
@ -85,7 +89,7 @@ public class RuleEngineController extends BaseController {
public DeferredResult<ResponseEntity> handleRuleEngineRequest(
@Parameter(description = "A JSON value representing the message.", required = true)
@RequestBody String requestBody) throws ThingsboardException {
return handleRuleEngineRequest(null, null, null, DEFAULT_TIMEOUT, requestBody);
return handleRuleEngineRequest(null, null, null, defaultResponseTimeout, requestBody);
}
@ApiOperation(value = "Push entity message to the rule engine (handleRuleEngineRequest)",
@ -104,7 +108,7 @@ public class RuleEngineController extends BaseController {
@PathVariable("entityId") String entityIdStr,
@Parameter(description = "A JSON value representing the message.", required = true)
@RequestBody String requestBody) throws ThingsboardException {
return handleRuleEngineRequest(entityType, entityIdStr, null, DEFAULT_TIMEOUT, requestBody);
return handleRuleEngineRequest(entityType, entityIdStr, null, defaultResponseTimeout, requestBody);
}
@ApiOperation(value = "Push entity message with timeout to the rule engine (handleRuleEngineRequest)",

8
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java

@ -19,11 +19,9 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
@ -31,8 +29,6 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.queue.util.AfterStartUp;
@ -56,8 +52,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
private final CalculatedFieldService calculatedFieldService;
private final TbelInvokeService tbelInvokeService;
private final ApiLimitService apiLimitService;
@Lazy
private final ActorSystemContext actorSystemContext;
private final ConcurrentMap<CalculatedFieldId, CalculatedField> calculatedFields = new ConcurrentHashMap<>();
private final ConcurrentMap<EntityId, List<CalculatedField>> entityIdCalculatedFields = new ConcurrentHashMap<>();
@ -75,7 +69,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
cfs.forEach(cf -> {
if (cf != null) {
calculatedFields.putIfAbsent(cf.getId(), cf);
actorSystemContext.tell(new CalculatedFieldInitMsg(cf.getTenantId(), cf));
}
});
calculatedFields.values().forEach(cf -> {
@ -84,7 +77,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFieldLinks, initFetchPackSize);
cfls.forEach(link -> {
calculatedFieldLinks.computeIfAbsent(link.getCalculatedFieldId(), id -> new CopyOnWriteArrayList<>()).add(link);
actorSystemContext.tell(new CalculatedFieldLinkInitMsg(link.getTenantId(), link));
});
calculatedFieldLinks.values().stream()
.flatMap(List::stream)

2
application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java

@ -32,7 +32,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
// This list should include all versions which are compatible for the upgrade.
// The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release.
private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.1.0");
private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.0");
private final ProjectInfo projectInfo;
private final JdbcTemplate jdbcTemplate;

59
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.service.install.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -24,12 +22,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
@ -129,60 +124,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
return ruleNodeIds;
}
boolean convertDeviceProfileForVersion330(JsonNode profileData) {
boolean isUpdated = false;
if (profileData.has("alarms") && !profileData.get("alarms").isNull()) {
JsonNode alarms = profileData.get("alarms");
for (JsonNode alarm : alarms) {
if (alarm.has("createRules")) {
JsonNode createRules = alarm.get("createRules");
for (AlarmSeverity severity : AlarmSeverity.values()) {
if (createRules.has(severity.name())) {
JsonNode spec = createRules.get(severity.name()).get("condition").get("spec");
if (convertDeviceProfileAlarmRulesForVersion330(spec)) {
isUpdated = true;
}
}
}
}
if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) {
JsonNode spec = alarm.get("clearRule").get("condition").get("spec");
if (convertDeviceProfileAlarmRulesForVersion330(spec)) {
isUpdated = true;
}
}
}
}
return isUpdated;
}
boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) {
if (spec != null) {
if (spec.has("type") && spec.get("type").asText().equals("DURATION")) {
if (spec.has("value")) {
long value = spec.get("value").asLong();
var predicate = new FilterPredicateValue<>(
value, null, new DynamicValue<>(null, null, false)
);
((ObjectNode) spec).remove("value");
((ObjectNode) spec).putPOJO("predicate", predicate);
return true;
}
} else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) {
if (spec.has("count")) {
int count = spec.get("count").asInt();
var predicate = new FilterPredicateValue<>(
count, null, new DynamicValue<>(null, null, false)
);
((ObjectNode) spec).remove("count");
((ObjectNode) spec).putPOJO("predicate", predicate);
return true;
}
}
}
return false;
}
public static boolean getEnv(String name, boolean defaultValue) {
String env = System.getenv(name);
if (env == null) {

3
application/src/main/resources/thingsboard.yml

@ -100,6 +100,9 @@ server:
rate_limits:
# Limit that prohibits resetting the password for the user too often. The value of the rate limit. By default, no more than 5 requests per hour
reset_password_per_user: "${RESET_PASSWORD_PER_USER_RATE_LIMIT_CONFIGURATION:5:3600}"
rule_engine:
# Default timeout for waiting response of REST API request to Rule Engine in milliseconds
response_timeout: "${DEFAULT_RULE_ENGINE_RESPONSE_TIMEOUT:10000}"
# Application info parameters
app:

2
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -953,7 +953,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
});
EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter);
EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter, keyFilters);
countByQueryAndCheck(countQuery, 97);
}

95
application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java

@ -1,95 +0,0 @@
/**
* Copyright © 2016-2025 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.install.update;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.thingsboard.common.util.JacksonUtil;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
@ActiveProfiles("install")
@SpringBootTest(classes = DefaultDataUpdateService.class)
class DefaultDataUpdateServiceTest {
@MockBean
DefaultDataUpdateService service;
@BeforeEach
void setUp() {
willCallRealMethod().given(service).convertDeviceProfileAlarmRulesForVersion330(any());
willCallRealMethod().given(service).convertDeviceProfileForVersion330(any());
}
JsonNode readFromResource(String resourceName) throws IOException {
return JacksonUtil.OBJECT_MAPPER.readTree(this.getClass().getClassLoader().getResourceAsStream(resourceName));
}
@Test
void convertDeviceProfileAlarmRulesForVersion330FirstRun() throws IOException {
JsonNode spec = readFromResource("update/330/device_profile_001_in.json");
JsonNode expected = readFromResource("update/330/device_profile_001_out.json");
assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isTrue();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference>
}
@Test
void convertDeviceProfileAlarmRulesForVersion330SecondRun() throws IOException {
JsonNode spec = readFromResource("update/330/device_profile_001_out.json");
JsonNode expected = readFromResource("update/330/device_profile_001_out.json");
assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference>
}
@Test
void convertDeviceProfileAlarmRulesForVersion330EmptyJson() throws JsonProcessingException {
JsonNode spec = JacksonUtil.toJsonNode("{ }");
JsonNode expected = JacksonUtil.toJsonNode("{ }");
assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
}
@Test
void convertDeviceProfileAlarmRulesForVersion330AlarmNodeNull() throws JsonProcessingException {
JsonNode spec = JacksonUtil.toJsonNode("{ \"alarms\" : null }");
JsonNode expected = JacksonUtil.toJsonNode("{ \"alarms\" : null }");
assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
}
@Test
void convertDeviceProfileAlarmRulesForVersion330NoAlarmNode() throws JsonProcessingException {
JsonNode spec = JacksonUtil.toJsonNode("{ \"configuration\": { \"type\": \"DEFAULT\" } }");
JsonNode expected = JacksonUtil.toJsonNode("{ \"configuration\": { \"type\": \"DEFAULT\" } }");
assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
}
}

3
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -137,9 +137,6 @@ public enum MsgType {
CF_CACHE_INIT_MSG, // Sent to init caches for CF actor;
CF_INIT_PROFILE_ENTITY_MSG, // Sent to init profile entities cache;
CF_INIT_MSG, // Sent to init particular calculated field;
CF_LINK_INIT_MSG, // Sent to init particular calculated field;
CF_STATE_RESTORE_MSG, // Sent to restore particular calculated field entity state;
CF_PARTITIONS_CHANGE_MSG, // Sent when cluster event occures;

34
common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitMsg.java

@ -1,34 +0,0 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.cf;
import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
@Data
public class CalculatedFieldInitMsg implements ToCalculatedFieldSystemMsg {
private final TenantId tenantId;
private final CalculatedField cf;
@Override
public MsgType getMsgType() {
return MsgType.CF_INIT_MSG;
}
}

36
common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java

@ -1,36 +0,0 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.cf;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
@Data
public class CalculatedFieldInitProfileEntityMsg implements ToCalculatedFieldSystemMsg {
private final TenantId tenantId;
private final EntityId profileEntityId;
private final EntityId entityId;
@Override
public MsgType getMsgType() {
return MsgType.CF_INIT_PROFILE_ENTITY_MSG;
}
}

34
common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldLinkInitMsg.java

@ -1,34 +0,0 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.cf;
import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
@Data
public class CalculatedFieldLinkInitMsg implements ToCalculatedFieldSystemMsg {
private final TenantId tenantId;
private final CalculatedFieldLink link;
@Override
public MsgType getMsgType() {
return MsgType.CF_LINK_INIT_MSG;
}
}

2
dao/src/test/java/org/thingsboard/server/dao/AbstractRedisClusterContainer.java

@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit;
public class AbstractRedisClusterContainer {
static final String NODES = "127.0.0.1:6371,127.0.0.1:6372,127.0.0.1:6373,127.0.0.1:6374,127.0.0.1:6375,127.0.0.1:6376";
static final String IMAGE = "bitnami/valkey-cluster:8.0";
static final String IMAGE = "bitnamilegacy/valkey-cluster:8.0";
static final Map<String,String> ENVS = Map.of(
"VALKEY_CLUSTER_ANNOUNCE_IP", "127.0.0.1",
"VALKEY_CLUSTER_DYNAMIC_IPS", "no",

2
dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java

@ -27,7 +27,7 @@ import java.util.List;
public class AbstractRedisContainer {
@ClassRule(order = 0)
public static GenericContainer redis = new GenericContainer("bitnami/valkey:8.0")
public static GenericContainer redis = new GenericContainer("bitnamilegacy/valkey:8.0")
.withEnv("ALLOW_EMPTY_PASSWORD","yes")
.withLogConsumer(s -> log.warn(((OutputFrame) s).getUtf8String().trim()))
.withExposedPorts(6379);

2
dao/src/test/java/org/thingsboard/server/dao/RedisJUnit5Test.java

@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class RedisJUnit5Test {
@Container
private static final GenericContainer REDIS = new GenericContainer("bitnami/valkey:8.0")
private static final GenericContainer REDIS = new GenericContainer("bitnamilegacy/valkey:8.0")
.withEnv("ALLOW_EMPTY_PASSWORD","yes")
.withLogConsumer(s -> log.error(((OutputFrame) s).getUtf8String().trim()))
.withExposedPorts(6379);

2
docker/docker-compose.kafka.yml

@ -17,7 +17,7 @@
services:
kafka:
restart: always
image: "bitnami/kafka:4.0"
image: "bitnamilegacy/kafka:4.0"
ports:
- "9092:9092"
env_file:

12
docker/docker-compose.valkey-cluster.yml

@ -18,7 +18,7 @@ services:
# Valkey cluster
# The latest version of Valkey compatible with ThingsBoard is 8.0
valkey-node-0:
image: bitnami/valkey-cluster:8.0
image: bitnamilegacy/valkey-cluster:8.0
volumes:
- ./tb-node/valkey-cluster-data-0:/bitnami/valkey/data
environment:
@ -26,7 +26,7 @@ services:
- 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
valkey-node-1:
image: bitnami/valkey-cluster:8.0
image: bitnamilegacy/valkey-cluster:8.0
volumes:
- ./tb-node/valkey-cluster-data-1:/bitnami/valkey/data
depends_on:
@ -36,7 +36,7 @@ services:
- 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
valkey-node-2:
image: bitnami/valkey-cluster:8.0
image: bitnamilegacy/valkey-cluster:8.0
volumes:
- ./tb-node/valkey-cluster-data-2:/bitnami/valkey/data
depends_on:
@ -46,7 +46,7 @@ services:
- 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
valkey-node-3:
image: bitnami/valkey-cluster:8.0
image: bitnamilegacy/valkey-cluster:8.0
volumes:
- ./tb-node/valkey-cluster-data-3:/bitnami/valkey/data
depends_on:
@ -56,7 +56,7 @@ services:
- 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
valkey-node-4:
image: bitnami/valkey-cluster:8.0
image: bitnamilegacy/valkey-cluster:8.0
volumes:
- ./tb-node/valkey-cluster-data-4:/bitnami/valkey/data
depends_on:
@ -66,7 +66,7 @@ services:
- 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
valkey-node-5:
image: bitnami/valkey-cluster:8.0
image: bitnamilegacy/valkey-cluster:8.0
volumes:
- ./tb-node/valkey-cluster-data-5:/bitnami/valkey/data
depends_on:

6
docker/docker-compose.valkey-sentinel.yml

@ -18,7 +18,7 @@ services:
# Valkey sentinel
# The latest version of Valkey compatible with ThingsBoard is 8.0
valkey-primary:
image: 'bitnami/valkey:8.0'
image: 'bitnamilegacy/valkey:8.0'
volumes:
- ./tb-node/valkey-sentinel-data-primary:/bitnami/valkey/data
environment:
@ -26,7 +26,7 @@ services:
- 'VALKEY_PASSWORD=thingsboard'
valkey-replica:
image: 'bitnami/valkey:8.0'
image: 'bitnamilegacy/valkey:8.0'
volumes:
- ./tb-node/valkey-sentinel-data-replica:/bitnami/valkey/data
environment:
@ -38,7 +38,7 @@ services:
- valkey-primary
valkey-sentinel:
image: 'bitnami/valkey-sentinel:8.0'
image: 'bitnamilegacy/valkey-sentinel:8.0'
volumes:
- ./tb-node/valkey-sentinel-data-sentinel:/bitnami/valkey/data
environment:

2
docker/docker-compose.valkey.yml

@ -19,7 +19,7 @@ services:
# The latest version of Valkey compatible with ThingsBoard is 8.0
valkey:
restart: always
image: bitnami/valkey:8.0
image: bitnamilegacy/valkey:8.0
environment:
# ALLOW_EMPTY_PASSWORD is recommended only for development.
ALLOW_EMPTY_PASSWORD: "yes"

2
msa/black-box-tests/src/test/resources/docker-compose.valkey-ssl.yml

@ -19,7 +19,7 @@ services:
# The latest version of Valkey compatible with ThingsBoard is 8.0
valkey:
restart: always
image: bitnami/valkey:8.0
image: bitnamilegacy/valkey:8.0
environment:
# ALLOW_EMPTY_PASSWORD is recommended only for development.
- 'ALLOW_EMPTY_PASSWORD=yes'

7
ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts

@ -79,6 +79,9 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
if (!this.aiConfigForm.get('systemPrompt').value) {
delete config.systemPrompt;
}
if (this.aiConfigForm.get('responseFormat.type').value !== ResponseFormat.JSON_SCHEMA) {
delete config.responseFormat.schema;
}
return deepTrim(config);
}
@ -88,10 +91,10 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
if (this.aiConfigForm.get('responseFormat.type').value !== ResponseFormat.TEXT) {
this.aiConfigForm.get('responseFormat.type').patchValue(ResponseFormat.TEXT, {emitEvent: true});
}
this.aiConfigForm.get('responseFormat.type').disable();
this.aiConfigForm.get('responseFormat.type').disable({emitEvent: false});
}
} else {
this.aiConfigForm.get('responseFormat.type').enable();
this.aiConfigForm.get('responseFormat.type').enable({emitEvent: false});
}
}

Loading…
Cancel
Save