Browse Source

Merge remote-tracking branch 'upstream/master' into improvement/extend-multiple-input-settings

pull/3561/head
Chantsova Ekaterina 6 years ago
parent
commit
db0b0dcb30
  1. 6
      application/pom.xml
  2. 3
      application/src/main/data/json/demo/rule_chains/root_rule_chain.json
  3. 2
      application/src/main/data/json/system/widget_bundles/charts.json
  4. 135
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  5. 24
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  6. 1
      application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
  7. 4
      application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql
  8. 28
      application/src/main/data/upgrade/3.1.1/schema_update_after.sql
  9. 81
      application/src/main/data/upgrade/3.1.1/schema_update_before.sql
  10. 21
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  11. 5
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  12. 61
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  13. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  14. 11
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  15. 6
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  16. 59
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  17. 9
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  18. 203
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  19. 59
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  20. 6
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  21. 33
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  22. 162
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  23. 8
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  24. 1
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  25. 83
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  26. 37
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  27. 10
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  28. 92
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  29. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  30. 4
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  31. 130
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  32. 99
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java
  33. 31
      application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java
  34. 58
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  35. 16
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  36. 18
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  37. 11
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  38. 5
      application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java
  39. 12
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java
  40. 60
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java
  41. 85
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java
  42. 75
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java
  43. 35
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  44. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java
  45. 8
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java
  46. 39
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  47. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  48. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  49. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  50. 7
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  51. 7
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  52. 11
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  53. 186
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  54. 19
      application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java
  55. 2
      application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java
  56. 11
      application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java
  57. 5
      application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java
  58. 2
      application/src/main/resources/logback.xml
  59. 50
      application/src/main/resources/thingsboard.yml
  60. 56
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  61. 6
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  62. 309
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  63. 5
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  64. 77
      application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java
  65. 294
      application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java
  66. 2
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  67. 23
      application/src/test/java/org/thingsboard/server/controller/sql/DeviceProfileControllerSqlTest.java
  68. 23
      application/src/test/java/org/thingsboard/server/controller/sql/TenantProfileControllerSqlTest.java
  69. 246
      application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java
  70. 2
      application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java
  71. 8
      application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java
  72. 111
      application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java
  73. 154
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java
  74. 51
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java
  75. 201
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java
  76. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java
  77. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java
  78. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java
  79. 23
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java
  80. 171
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java
  81. 51
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java
  82. 149
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java
  83. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java
  84. 23
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java
  85. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java
  86. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java
  87. 206
      application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java
  88. 58
      application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java
  89. 116
      application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java
  90. 24
      application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java
  91. 24
      application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java
  92. 24
      application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java
  93. 23
      application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java
  94. 137
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java
  95. 242
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  96. 66
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java
  97. 114
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java
  98. 4
      application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java
  99. 23
      application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java
  100. 24
      application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java

6
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.1.1-SNAPSHOT</version>
<version>3.2.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>
@ -46,10 +46,6 @@
</properties>
<dependencies>
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>

3
application/src/main/data/json/demo/rule_chains/root_rule_chain.json

@ -43,7 +43,8 @@
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
}
},
{

2
application/src/main/data/json/system/widget_bundles/charts.json

@ -166,7 +166,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
}
}
]

135
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -0,0 +1,135 @@
{
"ruleChain": {
"additionalInfo": {
"description": ""
},
"name": "Device Profile Rule Chain Template",
"firstRuleNodeId": null,
"root": false,
"debugMode": false,
"configuration": null
},
"metadata": {
"firstNodeIndex": 6,
"nodes": [
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 294
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
"debugMode": false,
"configuration": {
"defaultTTL": 0
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 221
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
}
},
{
"additionalInfo": {
"layoutX": 494,
"layoutY": 309
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugMode": false,
"configuration": {
"version": 0
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 383
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 823,
"layoutY": 444
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 507
},
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request",
"debugMode": false,
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "",
"layoutX": 209,
"layoutY": 307
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugMode": false,
"configuration": {
"persistAlarmRulesState": false
}
}
],
"connections": [
{
"fromIndex": 2,
"toIndex": 4,
"type": "Other"
},
{
"fromIndex": 2,
"toIndex": 1,
"type": "Post attributes"
},
{
"fromIndex": 2,
"toIndex": 0,
"type": "Post telemetry"
},
{
"fromIndex": 2,
"toIndex": 3,
"type": "RPC Request from Device"
},
{
"fromIndex": 2,
"toIndex": 5,
"type": "RPC Request to Device"
},
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
}
],
"ruleChainConnections": null
}
}

24
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -8,7 +8,7 @@
"configuration": null
},
"metadata": {
"firstNodeIndex": 2,
"firstNodeIndex": 6,
"nodes": [
{
"additionalInfo": {
@ -31,7 +31,8 @@
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
}
},
{
@ -81,9 +82,28 @@
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
"layoutX": 204,
"layoutY": 240
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugMode": false,
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
}
}
],
"connections": [
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
},
{
"fromIndex": 2,
"toIndex": 4,

1
application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql

@ -64,6 +64,7 @@ BEGIN
AND tablename like 'ts_kv_' || '%'
AND tablename != 'ts_kv_latest'
AND tablename != 'ts_kv_dictionary'
AND tablename != 'ts_kv_indefinite'
LOOP
IF partition != partition_by_max_ttl_date THEN
IF partition_year IS NOT NULL THEN

4
application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql

@ -59,8 +59,8 @@ $$
DECLARE
tenant_cursor CURSOR FOR select tenant.id as tenant_id
from tenant;
tenant_id_record varchar;
customer_id_record varchar;
tenant_id_record uuid;
customer_id_record uuid;
tenant_ttl bigint;
customer_ttl bigint;
deleted_for_entities bigint;

28
application/src/main/data/upgrade/3.1.1/schema_update_after.sql

@ -0,0 +1,28 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP PROCEDURE IF EXISTS update_tenant_profiles;
DROP PROCEDURE IF EXISTS update_device_profiles;
ALTER TABLE tenant ALTER COLUMN tenant_profile_id SET NOT NULL;
ALTER TABLE tenant DROP CONSTRAINT IF EXISTS fk_tenant_profile;
ALTER TABLE tenant ADD CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id);
ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_core;
ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_rule_engine;
ALTER TABLE device ALTER COLUMN device_profile_id SET NOT NULL;
ALTER TABLE device DROP CONSTRAINT IF EXISTS fk_device_profile;
ALTER TABLE device ADD CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id);

81
application/src/main/data/upgrade/3.1.1/schema_update_before.sql

@ -0,0 +1,81 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
type varchar(255),
transport_type varchar(255),
profile_data jsonb,
description varchar,
search_text varchar(255),
is_default boolean,
tenant_id uuid,
default_rule_chain_id uuid,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
);
CREATE TABLE IF NOT EXISTS tenant_profile (
id uuid NOT NULL CONSTRAINT tenant_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
profile_data jsonb,
description varchar,
search_text varchar(255),
is_default boolean,
isolated_tb_core boolean,
isolated_tb_rule_engine boolean,
CONSTRAINT tenant_profile_name_unq_key UNIQUE (name)
);
CREATE OR REPLACE PROCEDURE update_tenant_profiles()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = false) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = false;
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = false) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = false;
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = true) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = true;
UPDATE tenant as t SET tenant_profile_id = p.id
FROM
(SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = true) as p
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = true;
END;
$$;
CREATE OR REPLACE PROCEDURE update_device_profiles()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE device as d SET device_profile_id = p.id, device_data = '{"configuration":{"type":"DEFAULT"}, "transportConfiguration":{"type":"DEFAULT"}}'
FROM
(SELECT id, tenant_id, name from device_profile) as p
WHERE d.device_profile_id IS NULL AND p.tenant_id = d.tenant_id AND d.type = p.name;
END;
$$;

21
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -32,6 +32,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.DataConstants;
@ -44,7 +45,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -58,17 +58,20 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeStateService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
@ -89,7 +92,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
@ -125,6 +127,10 @@ public class ActorSystemContext {
@Getter
private DeviceService deviceService;
@Autowired
@Getter
private TbDeviceProfileCache deviceProfileCache;
@Autowired
@Getter
private AssetService assetService;
@ -137,6 +143,10 @@ public class ActorSystemContext {
@Getter
private TenantService tenantService;
@Autowired
@Getter
private TenantProfileService tenantProfileService;
@Autowired
@Getter
private CustomerService customerService;
@ -149,6 +159,10 @@ public class ActorSystemContext {
@Getter
private RuleChainService ruleChainService;
@Autowired
@Getter
private RuleNodeStateService ruleNodeStateService;
@Autowired
private PartitionService partitionService;
@ -527,4 +541,5 @@ public class ActorSystemContext {
log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS);
}
}

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

@ -27,6 +27,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
@ -116,7 +117,9 @@ public class AppActor extends ContextAwareActor {
boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
for (Tenant tenant : tenantIterator) {
if (isCore || (isRuleEngine && !tenant.isIsolatedTbRuleEngine())) {
// TODO: Tenant Profile from cache
TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId());
if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) {
log.debug("[{}] Creating tenant actor", tenant.getId());
getOrCreateTenantActor(tenant.getId());
log.debug("[{}] Tenant actor created.", tenant.getId());

61
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -23,6 +23,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.ScriptEngine;
@ -39,13 +40,15 @@ import org.thingsboard.server.common.data.id.EntityId;
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.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
@ -67,7 +70,6 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@ -105,6 +107,7 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
}
msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
}
@ -122,7 +125,7 @@ class DefaultTbContext implements TbContext {
@Override
public void enqueue(TbMsg tbMsg, String queueName, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueue(tpi, tbMsg, onFailure, onSuccess);
}
@ -139,46 +142,54 @@ class DefaultTbContext implements TbContext {
@Override
public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String relationType) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, null, null);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, null, null);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
}
@Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
}
private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) {
return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
}
private TopicPartitionInfo resolvePartition(TbMsg tbMsg) {
return resolvePartition(tbMsg, tbMsg.getQueueName());
}
private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
@ -203,6 +214,7 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "ACK", null);
}
tbMsg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
tbMsg.getCallback().onSuccess();
}
@ -388,6 +400,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getEntityViewService();
}
@Override
public RuleEngineDeviceProfileCache getDeviceProfileCache() {
return mainCtx.getDeviceProfileCache();
}
@Override
public EventLoopGroup getSharedEventLoop() {
return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();
@ -422,6 +439,30 @@ class DefaultTbContext implements TbContext {
return mainCtx.getRedisTemplate();
}
@Override
public PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Fetch Rule Node States.", getTenantId(), getSelfId());
}
return mainCtx.getRuleNodeStateService().findByRuleNodeId(getTenantId(), getSelfId(), pageLink);
}
@Override
public RuleNodeState findRuleNodeStateForEntity(EntityId entityId) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Fetch Rule Node State for entity.", getTenantId(), getSelfId(), entityId);
}
return mainCtx.getRuleNodeStateService().findByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId);
}
@Override
public RuleNodeState saveRuleNodeState(RuleNodeState state) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Persist Rule Node State for entity: {}", getTenantId(), getSelfId(), state.getEntityId(), state.getStateData());
}
state.setRuleNodeId(getSelfId());
return mainCtx.getRuleNodeStateService().save(getTenantId(), state);
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData();

2
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -103,7 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
msg.getMsg().getCallback().visit(info);
msg.getMsg().getCallback().onProcessingStart(info);
checkActive(msg.getMsg());
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());

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

@ -31,6 +31,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
@ -46,6 +47,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import java.util.List;
import java.util.Optional;
@ -75,12 +77,16 @@ public class TenantActor extends RuleChainManagerActor {
// This Service may be started for specific tenant only.
Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
// TODO: Tenant Profile from cache
TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId());
isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
if (isRuleEngineForCurrentTenant) {
try {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenant.isIsolatedTbRuleEngine())) {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
log.info("[{}] Going to init rule chains", tenantId);
initRuleChains();
} else {
@ -111,6 +117,9 @@ public class TenantActor extends RuleChainManagerActor {
if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) {
QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg;
queueMsg.getTbMsg().getCallback().onSuccess();
} else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)){
TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg;
transportMsg.getCallback().onSuccess();
}
return true;
}

6
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -90,7 +90,7 @@ public class AlarmController extends BaseController {
checkEntity(alarm.getId(), alarm, Resource.ALARM);
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm,
logEntityAction(savedAlarm.getOriginator(), savedAlarm,
getCurrentUser().getCustomerId(),
alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAlarm;
@ -126,7 +126,7 @@ public class AlarmController extends BaseController {
long ackTs = System.currentTimeMillis();
alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get();
alarm.setAckTs(ackTs);
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
} catch (Exception e) {
throw handleException(e);
}
@ -143,7 +143,7 @@ public class AlarmController extends BaseController {
long clearTs = System.currentTimeMillis();
alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get();
alarm.setClearTs(clearTs);
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
} catch (Exception e) {
throw handleException(e);
}

59
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -33,12 +33,15 @@ import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -52,12 +55,14 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
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.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
@ -82,6 +87,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -89,6 +95,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
@ -98,6 +105,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
@ -134,6 +142,9 @@ public abstract class BaseController {
@Autowired
protected TenantService tenantService;
@Autowired
protected TenantProfileService tenantProfileService;
@Autowired
protected CustomerService customerService;
@ -143,6 +154,9 @@ public abstract class BaseController {
@Autowired
protected DeviceService deviceService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetService assetService;
@ -197,6 +211,9 @@ public abstract class BaseController {
@Autowired
protected TbQueueProducerProvider producerProvider;
@Autowired
protected TbDeviceProfileCache deviceProfileCache;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ -312,6 +329,30 @@ public abstract class BaseController {
}
}
TenantInfo checkTenantInfoId(TenantId tenantId, Operation operation) throws ThingsboardException {
try {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
TenantInfo tenant = tenantService.findTenantInfoById(tenantId);
checkNotNull(tenant);
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
return tenant;
} catch (Exception e) {
throw handleException(e, false);
}
}
TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException {
try {
validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId);
checkNotNull(tenantProfile);
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation);
return tenantProfile;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected TenantId getTenantId() throws ThingsboardException {
return getCurrentUser().getTenantId();
}
@ -360,12 +401,18 @@ public abstract class BaseController {
case DEVICE:
checkDeviceId(new DeviceId(entityId.getId()), operation);
return;
case DEVICE_PROFILE:
checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation);
return;
case CUSTOMER:
checkCustomerId(new CustomerId(entityId.getId()), operation);
return;
case TENANT:
checkTenantId(new TenantId(entityId.getId()), operation);
return;
case TENANT_PROFILE:
checkTenantProfileId(new TenantProfileId(entityId.getId()), operation);
return;
case RULE_CHAIN:
checkRuleChain(new RuleChainId(entityId.getId()), operation);
return;
@ -422,6 +469,18 @@ public abstract class BaseController {
}
}
DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException {
try {
validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId);
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId);
checkNotNull(deviceProfile);
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile);
return deviceProfile;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId);

9
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -315,6 +316,7 @@ public class DeviceController extends BaseController {
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String type,
@RequestParam(required = false) String deviceProfileId,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
@ -323,6 +325,9 @@ public class DeviceController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (type != null && type.trim().length() > 0) {
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink));
} else if (deviceProfileId != null && deviceProfileId.length() > 0) {
DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink));
} else {
return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink));
}
@ -379,6 +384,7 @@ public class DeviceController extends BaseController {
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String type,
@RequestParam(required = false) String deviceProfileId,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
@ -390,6 +396,9 @@ public class DeviceController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (type != null && type.trim().length() > 0) {
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
} else if (deviceProfileId != null && deviceProfileId.length() > 0) {
DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink));
} else {
return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
}

203
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -0,0 +1,203 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class DeviceProfileController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public DeviceProfile getDeviceProfileById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkDeviceProfileId(deviceProfileId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
public DeviceProfileInfo getDefaultDeviceProfileInfo() throws ThingsboardException {
try {
return checkNotNull(deviceProfileService.findDefaultDeviceProfileInfo(getTenantId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
@ResponseBody
public DeviceProfile saveDeviceProfile(@RequestBody DeviceProfile deviceProfile) throws ThingsboardException {
try {
boolean created = deviceProfile.getId() == null;
deviceProfile.setTenantId(getTenantId());
checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE);
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
deviceProfileCache.put(savedDeviceProfile);
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile,
null,
savedDeviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedDeviceProfile;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile,
null, deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE);
deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId);
deviceProfileCache.evict(deviceProfileId);
tbClusterService.onDeviceProfileDelete(deviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED);
logEntityAction(deviceProfileId, deviceProfile,
null,
ActionType.DELETED, null, strDeviceProfileId);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
null,
null,
ActionType.DELETED, e, strDeviceProfileId);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
@ResponseBody
public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE);
DeviceProfile previousDefaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(getTenantId());
if (deviceProfileService.setDefaultDeviceProfile(getTenantId(), deviceProfileId)) {
if (previousDefaultDeviceProfile != null) {
previousDefaultDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), previousDefaultDeviceProfile.getId());
logEntityAction(previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile,
null, ActionType.UPDATED, null);
}
deviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfileId);
logEntityAction(deviceProfile.getId(), deviceProfile,
null, ActionType.UPDATED, null);
}
return deviceProfile;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
null,
null,
ActionType.UPDATED, e, strDeviceProfileId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<DeviceProfile> getDeviceProfiles(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<DeviceProfileInfo> getDeviceProfileInfos(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

59
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -47,7 +48,10 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
@ -55,6 +59,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
@ -77,6 +82,9 @@ public class RuleChainController extends BaseController {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private InstallScripts installScripts;
@Autowired
private EventService eventService;
@ -146,6 +154,27 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST)
@ResponseBody
public RuleChain saveRuleChain(@RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException {
try {
checkNotNull(request);
checkNotNull(request.getName());
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
return savedRuleChain;
} catch (Exception e) {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(request.getName());
logEntityAction(emptyId(EntityType.RULE_CHAIN), ruleChain, null, ActionType.ADDED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
@ResponseBody
@ -360,6 +389,36 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public RuleChainData exportRuleChains(@RequestParam("limit") int limit) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = new PageLink(limit);
return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST)
@ResponseBody
public void importRuleChains(@RequestBody RuleChainData ruleChainData, @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite);
if (!CollectionUtils.isEmpty(importResults)) {
for (RuleChainImportResult importResult : importResults) {
tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
}
}
} catch (Exception e) {
throw handleException(e);
}
}
private String msgToOutput(TbMsg msg) throws Exception {
ObjectNode msgData = objectMapper.createObjectNode();
if (!StringUtils.isEmpty(msg.getData())) {

6
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -197,19 +197,21 @@ public class TelemetryController extends BaseController {
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
@ResponseBody
public DeferredResult<ResponseEntity> getTimeseries(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("entityType") String entityType,
@PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys") String keys,
@RequestParam(name = "startTs") Long startTs,
@RequestParam(name = "endTs") Long endTs,
@RequestParam(name = "interval", defaultValue = "0") Long interval,
@RequestParam(name = "limit", defaultValue = "100") Integer limit,
@RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
@RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy,
@RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> {
// If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());

33
application/src/main/java/org/thingsboard/server/controller/TenantController.java

@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -58,8 +59,20 @@ public class TenantController extends BaseController {
checkParameter("tenantId", strTenantId);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
checkTenantId(tenantId, Operation.READ);
return checkNotNull(tenantService.findTenantById(tenantId));
return checkTenantId(tenantId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET)
@ResponseBody
public TenantInfo getTenantInfoById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
checkParameter("tenantId", strTenantId);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
return checkTenantInfoId(tenantId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
@ -115,4 +128,20 @@ public class TenantController extends BaseController {
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<TenantInfo> getTenantInfos(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(tenantService.findTenantInfos(pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

162
application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class TenantProfileController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.GET)
@ResponseBody
public TenantProfile getTenantProfileById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
return checkTenantProfileId(tenantProfileId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfileInfo/{tenantProfileId}", method = RequestMethod.GET)
@ResponseBody
public EntityInfo getTenantProfileInfoById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
return checkNotNull(tenantProfileService.findTenantProfileInfoById(getTenantId(), tenantProfileId));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
public EntityInfo getDefaultTenantProfileInfo() throws ThingsboardException {
try {
return checkNotNull(tenantProfileService.findDefaultTenantProfileInfo(getTenantId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile", method = RequestMethod.POST)
@ResponseBody
public TenantProfile saveTenantProfile(@RequestBody TenantProfile tenantProfile) throws ThingsboardException {
try {
boolean newTenantProfile = tenantProfile.getId() == null;
if (newTenantProfile) {
accessControlService
.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE);
} else {
checkEntityId(tenantProfile.getId(), Operation.WRITE);
}
tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile));
return tenantProfile;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
checkTenantProfileId(tenantProfileId, Operation.DELETE);
tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile/{tenantProfileId}/default", method = RequestMethod.POST)
@ResponseBody
public TenantProfile setDefaultTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
checkParameter("tenantProfileId", strTenantProfileId);
try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE);
tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId);
return tenantProfile;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<TenantProfile> getTenantProfiles(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<EntityInfo> getTenantProfileInfos(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

8
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -175,6 +175,13 @@ public class ThingsboardInstallService {
case "3.1.0":
log.info("Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.1.0");
case "3.1.1":
log.info("Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("3.1.1");
}
databaseEntitiesUpgradeService.upgradeDatabase("3.1.1");
dataUpdateService.updateData("3.1.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
@ -206,6 +213,7 @@ public class ThingsboardInstallService {
componentDiscoveryService.discoverComponents();
systemDataLoaderService.createSysAdmin();
systemDataLoaderService.createDefaultTenantProfiles();
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
// systemDataLoaderService.loadSystemPlugins();

1
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -49,6 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
log.info("Schema updated.");
break;
case "2.5.0":
case "3.1.1":
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

83
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -27,11 +27,15 @@ import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.TenantProfileData;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
@ -46,9 +50,12 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -82,6 +89,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private TenantService tenantService;
@Autowired
private TenantProfileService tenantProfileService;
@Autowired
private CustomerService customerService;
@ -94,6 +104,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private DeviceService deviceService;
@Autowired
private DeviceProfileService deviceProfileService;
@Autowired
private AttributesService attributesService;
@ -110,6 +123,50 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin");
}
@Override
public void createDefaultTenantProfiles() throws Exception {
tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID);
TenantProfile isolatedTbCoreProfile = new TenantProfile();
isolatedTbCoreProfile.setDefault(false);
isolatedTbCoreProfile.setName("Isolated TB Core");
isolatedTbCoreProfile.setProfileData(new TenantProfileData());
isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile");
isolatedTbCoreProfile.setIsolatedTbCore(true);
isolatedTbCoreProfile.setIsolatedTbRuleEngine(false);
try {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile);
} catch (DataValidationException e) {
log.warn(e.getMessage());
}
TenantProfile isolatedTbRuleEngineProfile = new TenantProfile();
isolatedTbRuleEngineProfile.setDefault(false);
isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine");
isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData());
isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile");
isolatedTbRuleEngineProfile.setIsolatedTbCore(false);
isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
try {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbRuleEngineProfile);
} catch (DataValidationException e) {
log.warn(e.getMessage());
}
TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile();
isolatedTbCoreAndTbRuleEngineProfile.setDefault(false);
isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine");
isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData());
isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile");
isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true);
isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
try {
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile);
} catch (DataValidationException e) {
log.warn(e.getMessage());
}
}
@Override
public void createAdminSettings() throws Exception {
AdminSettings generalSettings = new AdminSettings();
@ -162,16 +219,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerB.getId(), "customerB@thingsboard.org", CUSTOMER_CRED);
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerC.getId(), "customerC@thingsboard.org", CUSTOMER_CRED);
createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A1", "A1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A2", "A2_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A3", "A3_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerB.getId(), DEFAULT_DEVICE_TYPE, "Test Device B1", "B1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerC.getId(), DEFAULT_DEVICE_TYPE, "Test Device C1", "C1_TEST_TOKEN", null);
DeviceProfile defaultDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), DEFAULT_DEVICE_TYPE);
createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A1", "A1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A2", "A2_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A3", "A3_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerB.getId(), defaultDeviceProfile.getId(), "Test Device B1", "B1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerC.getId(), defaultDeviceProfile.getId(), "Test Device C1", "C1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
"applications that upload data from DHT11 temperature and humidity sensor");
createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
"Raspberry Pi GPIO control sample application");
Asset thermostatAlarms = new Asset();
@ -180,8 +239,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
thermostatAlarms.setType("AlarmPropagationAsset");
thermostatAlarms = assetService.saveAsset(thermostatAlarms);
DeviceId t1Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceId t2Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat");
DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset"));
relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset"));
@ -257,14 +318,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private Device createDevice(TenantId tenantId,
CustomerId customerId,
String type,
DeviceProfileId deviceProfileId,
String name,
String accessToken,
String description) {
Device device = new Device();
device.setTenantId(tenantId);
device.setCustomerId(customerId);
device.setType(type);
device.setDeviceProfileId(deviceProfileId);
device.setName(name);
if (description != null) {
ObjectNode additionalInfo = objectMapper.createObjectNode();

37
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -57,6 +57,7 @@ public class InstallScripts {
public static final String JSON_DIR = "json";
public static final String SYSTEM_DIR = "system";
public static final String TENANT_DIR = "tenant";
public static final String DEVICE_PROFILE_DIR = "device_profile";
public static final String DEMO_DIR = "demo";
public static final String RULE_CHAINS_DIR = "rule_chains";
public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
@ -83,6 +84,10 @@ public class InstallScripts {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
public Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
}
public String getDataDir() {
if (!StringUtils.isEmpty(dataDir)) {
if (!Paths.get(this.dataDir).toFile().isDirectory()) {
@ -110,15 +115,7 @@ public class InstallScripts {
dirStream.forEach(
path -> {
try {
JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
ruleChain.setTenantId(tenantId);
ruleChain = ruleChainService.saveRuleChain(ruleChain);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
createRuleChainFromFile(tenantId, path, null);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
@ -128,6 +125,28 @@ public class InstallScripts {
}
}
public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException {
return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName);
}
public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) throws IOException {
JsonNode ruleChainJson = objectMapper.readTree(templateFilePath.toFile());
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
ruleChain.setTenantId(tenantId);
if (!StringUtils.isEmpty(newRuleChainName)) {
ruleChain.setName(newRuleChainName);
}
ruleChain = ruleChainService.saveRuleChain(ruleChain);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
return ruleChain;
}
public void loadSystemWidgets() throws Exception {
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {

10
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -195,6 +195,14 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
case "3.1.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Load TTL functions ...");
loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
log.info("Load Drop Partitions functions ...");
loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
@ -239,4 +247,4 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage());
}
}
}
}

92
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -20,7 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.install.sql.SqlDbHelper;
import java.nio.charset.Charset;
@ -34,6 +41,7 @@ import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.List;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
@ -76,6 +84,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
@Autowired
private InstallScripts installScripts;
@Autowired
private SystemDataLoaderService systemDataLoaderService;
@Autowired
private TenantService tenantService;
@Autowired
private DeviceService deviceService;
@Autowired
private DeviceProfileService deviceProfileService;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
@ -303,6 +324,77 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Schema updated.");
}
break;
case "3.1.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3001000)) {
try {
conn.createStatement().execute("ALTER TABLE device ADD COLUMN device_profile_id uuid, ADD COLUMN device_data jsonb");
} catch (Exception e) {
}
try {
conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN tenant_profile_id uuid");
} catch (Exception e) {
}
try {
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS rule_node_state (" +
" id uuid NOT NULL CONSTRAINT rule_node_state_pkey PRIMARY KEY," +
" created_time bigint NOT NULL," +
" rule_node_id uuid NOT NULL," +
" entity_type varchar(32) NOT NULL," +
" entity_id uuid NOT NULL," +
" state_data varchar(16384) NOT NULL," +
" CONSTRAINT rule_node_state_unq_key UNIQUE (rule_node_id, entity_id)," +
" CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE)");
} catch (Exception e) {
}
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql");
loadSql(schemaUpdateFile, conn);
log.info("Creating default tenant profiles...");
systemDataLoaderService.createDefaultTenantProfiles();
log.info("Updating tenant profiles...");
conn.createStatement().execute("call update_tenant_profiles()");
log.info("Creating default device profiles...");
PageLink pageLink = new PageLink(100);
PageData<Tenant> pageData;
do {
pageData = tenantService.findTenants(pageLink);
for (Tenant tenant : pageData.getData()) {
List<EntitySubtype> deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get();
try {
deviceProfileService.createDefaultDeviceProfile(tenant.getId());
} catch (Exception e) {
}
for (EntitySubtype deviceType : deviceTypes) {
try {
deviceProfileService.findOrCreateDeviceProfile(tenant.getId(), deviceType.getType());
} catch (Exception e) {
}
}
}
pageLink = pageLink.nextPageLink();
} while (pageData.hasNext());
log.info("Updating device profiles...");
conn.createStatement().execute("call update_device_profiles()");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_after.sql");
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002000;");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

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

@ -19,6 +19,8 @@ public interface SystemDataLoaderService {
void createSysAdmin() throws Exception;
void createDefaultTenantProfiles() throws Exception;
void createAdminSettings() throws Exception;
void loadSystemWidgets() throws Exception;

4
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -177,6 +177,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
case "3.1.1":
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
@ -207,4 +209,4 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage());
}
}
}
}

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

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.service.install.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -23,9 +25,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
@ -35,10 +41,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.service.install.InstallScripts;
import javax.annotation.Nullable;
@ -49,6 +58,7 @@ import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
@Service
@Profile("install")
@ -81,6 +91,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
log.info("Updating data from version 3.0.1 to 3.1.0 ...");
tenantsEntityViewsUpdater.updateEntities(null);
break;
case "3.1.1":
log.info("Updating data from version 3.1.1 to 3.2.0 ...");
tenantsRootRuleChainUpdater.updateEntities(null);
break;
default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
}
@ -107,6 +121,60 @@ public class DefaultDataUpdateService implements DataUpdateService {
}
};
private PaginatedUpdater<String, Tenant> tenantsRootRuleChainUpdater =
new PaginatedUpdater<String, Tenant>() {
@Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
return tenantService.findTenants(pageLink);
}
@Override
protected void updateEntity(Tenant tenant) {
try {
RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
if (ruleChain == null) {
installScripts.createDefaultRuleChains(tenant.getId());
} else {
RuleChainMetaData md = ruleChainService.loadRuleChainMetaData(tenant.getId(), ruleChain.getId());
int oldIdx = md.getFirstNodeIndex();
int newIdx = md.getNodes().size();
if (md.getNodes().size() < oldIdx) {
// Skip invalid rule chains
return;
}
RuleNode oldFirstNode = md.getNodes().get(oldIdx);
if (oldFirstNode.getType().equals(TbDeviceProfileNode.class.getName())) {
// No need to update the rule node twice.
return;
}
RuleNode ruleNode = new RuleNode();
ruleNode.setRuleChainId(ruleChain.getId());
ruleNode.setName("Device Profile Node");
ruleNode.setType(TbDeviceProfileNode.class.getName());
ruleNode.setDebugMode(false);
TbDeviceProfileNodeConfiguration ruleNodeConfiguration = new TbDeviceProfileNodeConfiguration().defaultConfiguration();
ruleNode.setConfiguration(JacksonUtil.valueToTree(ruleNodeConfiguration));
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
additionalInfo.put("description", "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.");
additionalInfo.put("layoutX", 204);
additionalInfo.put("layoutY", 240);
ruleNode.setAdditionalInfo(additionalInfo);
md.getNodes().add(ruleNode);
md.setFirstNodeIndex(newIdx);
md.addConnectionInfo(newIdx, oldIdx, "Success");
ruleChainService.saveRuleChainMetaData(tenant.getId(), md);
}
} catch (Exception e) {
log.error("Unable to update Tenant", e);
}
}
};
private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater =
new PaginatedUpdater<String, Tenant>() {
@ -121,30 +189,30 @@ public class DefaultDataUpdateService implements DataUpdateService {
}
};
private void updateTenantEntityViews(TenantId tenantId) {
PageLink pageLink = new PageLink(100);
PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
boolean hasNext = true;
while (hasNext) {
List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();
for (EntityView entityView : pageData.getData()) {
updateFutures.add(updateEntityViewLatestTelemetry(entityView));
}
try {
Futures.allAsList(updateFutures).get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to copy latest telemetry to entity view", e);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
} else {
hasNext = false;
}
}
}
private void updateTenantEntityViews(TenantId tenantId) {
PageLink pageLink = new PageLink(100);
PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
boolean hasNext = true;
while (hasNext) {
List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();
for (EntityView entityView : pageData.getData()) {
updateFutures.add(updateEntityViewLatestTelemetry(entityView));
}
try {
Futures.allAsList(updateFutures).get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to copy latest telemetry to entity view", e);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
} else {
hasNext = false;
}
}
}
private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) {
EntityViewId entityId = entityView.getId();
@ -160,13 +228,13 @@ public class DefaultDataUpdateService implements DataUpdateService {
keysFuture = Futures.immediateFuture(keys);
}
ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> {
List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());
if (!queries.isEmpty()) {
return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);
} else {
return Futures.immediateFuture(null);
}
}, MoreExecutors.directExecutor());
List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());
if (!queries.isEmpty()) {
return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);
} else {
return Futures.immediateFuture(null);
}
}, MoreExecutors.directExecutor());
return Futures.transformAsync(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) {
ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues);

99
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java

@ -0,0 +1,99 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.profile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
private final Lock deviceProfileFetchLock = new ReentrantLock();
private final DeviceProfileService deviceProfileService;
private final DeviceService deviceService;
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfilesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, DeviceProfileId> devicesMap = new ConcurrentHashMap<>();
public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) {
this.deviceProfileService = deviceProfileService;
this.deviceService = deviceService;
}
@Override
public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) {
DeviceProfile profile = deviceProfilesMap.get(deviceProfileId);
if (profile == null) {
profile = deviceProfilesMap.get(deviceProfileId);
if (profile == null) {
deviceProfileFetchLock.lock();
try {
profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
if (profile != null) {
deviceProfilesMap.put(deviceProfileId, profile);
}
} finally {
deviceProfileFetchLock.unlock();
}
}
}
return profile;
}
@Override
public DeviceProfile get(TenantId tenantId, DeviceId deviceId) {
DeviceProfileId profileId = devicesMap.get(deviceId);
if (profileId == null) {
Device device = deviceService.findDeviceById(tenantId, deviceId);
if (device != null) {
profileId = device.getDeviceProfileId();
devicesMap.put(deviceId, profileId);
}
}
return get(tenantId, profileId);
}
@Override
public void put(DeviceProfile profile) {
if (profile.getId() != null) {
deviceProfilesMap.put(profile.getId(), profile);
}
}
@Override
public void evict(DeviceProfileId profileId) {
deviceProfilesMap.remove(profileId);
}
@Override
public void evict(DeviceId deviceId) {
devicesMap.remove(deviceId);
}
}

31
application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.profile;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache {
void put(DeviceProfile profile);
void evict(DeviceProfileId id);
void evict(DeviceId id);
}

58
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -21,14 +21,20 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
@ -40,7 +46,7 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import java.util.HashSet;
@ -64,11 +70,13 @@ public class DefaultTbClusterService implements TbClusterService {
private final TbQueueProducerProvider producerProvider;
private final PartitionService partitionService;
private final DataDecodingEncodingService encodingService;
private final TbDeviceProfileCache deviceProfileCache;
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService) {
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService, TbDeviceProfileCache deviceProfileCache) {
this.producerProvider = producerProvider;
this.partitionService = partitionService;
this.encodingService = encodingService;
this.deviceProfileCache = deviceProfileCache;
}
@Override
@ -124,6 +132,12 @@ public class DefaultTbClusterService implements TbClusterService {
log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg);
return;
}
} else {
if (entityId.getEntityType().equals(EntityType.DEVICE)) {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())));
} else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
}
}
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
@ -135,6 +149,16 @@ public class DefaultTbClusterService implements TbClusterService {
toRuleEngineMsgs.incrementAndGet();
}
private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) {
if (deviceProfile != null) {
RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId();
if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId);
}
}
return tbMsg;
}
@Override
public void pushNotificationToRuleEngine(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId);
@ -163,6 +187,36 @@ public class DefaultTbClusterService implements TbClusterService {
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
}
@Override
public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) {
log.trace("[{}][{}] Processing device profile [{}] change event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
TransportProtos.DeviceProfileUpdateMsg profileUpdateMsg = TransportProtos.DeviceProfileUpdateMsg.newBuilder()
.setData(ByteString.copyFrom(encodingService.encode(deviceProfile))).build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileUpdateMsg(profileUpdateMsg).build();
broadcast(transportMsg);
}
@Override
public void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback) {
log.trace("[{}][{}] Processing device profile [{}] delete event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
TransportProtos.DeviceProfileDeleteMsg profileDeleteMsg = TransportProtos.DeviceProfileDeleteMsg.newBuilder()
.setProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits())
.setProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileDeleteMsg(profileDeleteMsg).build();
broadcast(transportMsg);
}
private void broadcast(ToTransportMsg transportMsg) {
TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer();
Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
for (String transportServiceId : tbTransportServices) {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);
toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
toTransportNfs.incrementAndGet();
}
}
private void broadcast(ComponentLifecycleMsg msg) {
byte[] msgBytes = encodingService.encode(msg);
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();

16
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -21,10 +21,13 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
@ -47,7 +50,8 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
@ -92,8 +96,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService,
SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService,
TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory) {
super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) {
super(actorContext, encodingService, deviceProfileCache, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.stateService = stateService;
this.localSubscriptionService = localSubscriptionService;
@ -211,11 +215,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
log.trace("[{}] Forwarding message to RPC service {}", id, toCoreNotification.getFromDeviceRpcResponse());
forwardToCoreRpcService(toCoreNotification.getFromDeviceRpcResponse(), callback);
} else if (toCoreNotification.getComponentLifecycleMsg() != null && !toCoreNotification.getComponentLifecycleMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreNotification.getComponentLifecycleMsg().toByteArray());
if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tellWithHighPriority(actorMsg.get());
}
handleComponentLifecycleMsg(id, toCoreNotification.getComponentLifecycleMsg());
callback.onSuccess();
}
if (statsEnabled) {

18
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.queue;
import com.google.protobuf.ByteString;
import com.google.protobuf.ProtocolStringList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -37,7 +38,8 @@ import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.*;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
@ -80,8 +82,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService,
ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbRuleEngineDeviceRpcService tbDeviceRpcService,
StatsFactory statsFactory) {
super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) {
super(actorContext, encodingService, deviceProfileCache, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
this.statisticsService = statisticsService;
this.ruleEngineSettings = ruleEngineSettings;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
@ -144,7 +146,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
submitStrategy.init(msgs);
while (!stopped) {
TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy);
TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(configuration.getName(), submitStrategy);
submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> {
log.trace("[{}] Creating callback for message: {}", id, msg.getValue());
ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
@ -175,6 +177,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
if (!ctx.getFailedMap().isEmpty()) {
printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed");
}
ctx.printProfilerStats();
TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
if (statsEnabled) {
stats.log(result, decision.isCommit());
@ -237,11 +241,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) throws Exception {
ToRuleEngineNotificationMsg nfMsg = msg.getValue();
if (nfMsg.getComponentLifecycleMsg() != null && !nfMsg.getComponentLifecycleMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(nfMsg.getComponentLifecycleMsg().toByteArray());
if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tellWithHighPriority(actorMsg.get());
}
handleComponentLifecycleMsg(id, nfMsg.getComponentLifecycleMsg());
callback.onSuccess();
} else if (nfMsg.hasFromDeviceRpcResponse()) {
TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse();

11
application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java

@ -19,7 +19,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ -31,15 +33,20 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
private final TenantService tenantService;
public DefaultTenantRoutingInfoService(TenantService tenantService) {
private final TenantProfileService tenantProfileService;
public DefaultTenantRoutingInfoService(TenantService tenantService, TenantProfileService tenantProfileService) {
this.tenantService = tenantService;
this.tenantProfileService = tenantProfileService;
}
@Override
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
Tenant tenant = tenantService.findTenantById(tenantId);
if (tenant != null) {
return new TenantRoutingInfo(tenantId, tenant.isIsolatedTbCore(), tenant.isIsolatedTbRuleEngine());
// TODO: Tenant Profile from cache
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbCore(), tenantProfile.isIsolatedTbRuleEngine());
} else {
throw new RuntimeException("Tenant not found!");
}

5
application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.queue;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -49,4 +51,7 @@ public interface TbClusterService {
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback);
void onDeviceProfileDelete(DeviceProfile deviceProfileId, TbQueueCallback callback);
}

12
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java

@ -67,8 +67,14 @@ public class TbMsgPackCallback implements TbMsgCallback {
}
@Override
public void visit(RuleNodeInfo ruleNodeInfo) {
log.trace("[{}] ON PROCESS: {}", id, ruleNodeInfo);
ctx.visit(id, ruleNodeInfo);
public void onProcessingStart(RuleNodeInfo ruleNodeInfo) {
log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo);
ctx.onProcessingStart(id, ruleNodeInfo);
}
@Override
public void onProcessingEnd(RuleNodeId ruleNodeId) {
log.trace("[{}] ON PROCESSING END: {}", id, ruleNodeId);
ctx.onProcessingEnd(id, ruleNodeId);
}
}

60
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.queue;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
@ -24,6 +25,8 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
import java.util.Comparator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -31,9 +34,13 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class TbMsgPackProcessingContext {
private final String queueName;
private final TbRuleEngineSubmitStrategy submitStrategy;
@Getter
private final boolean profilerEnabled;
private final AtomicInteger pendingCount;
private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
@Getter
@ -47,14 +54,20 @@ public class TbMsgPackProcessingContext {
private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) {
this.queueName = queueName;
this.submitStrategy = submitStrategy;
this.profilerEnabled = log.isDebugEnabled();
this.pendingMap = submitStrategy.getPendingMap();
this.pendingCount = new AtomicInteger(pendingMap.size());
}
public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException {
return processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
if (!success && profilerEnabled) {
msgProfilerMap.values().forEach(this::onTimeout);
}
return success;
}
public void onSuccess(UUID id) {
@ -85,12 +98,53 @@ public class TbMsgPackProcessingContext {
}
}
public void visit(UUID id, RuleNodeInfo ruleNodeInfo) {
private final ConcurrentHashMap<UUID, TbMsgProfilerInfo> msgProfilerMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<UUID, TbRuleNodeProfilerInfo> ruleNodeProfilerMap = new ConcurrentHashMap<>();
public void onProcessingStart(UUID id, RuleNodeInfo ruleNodeInfo) {
lastRuleNodeMap.put(id, ruleNodeInfo);
if (profilerEnabled) {
msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onStart(ruleNodeInfo.getRuleNodeId());
ruleNodeProfilerMap.putIfAbsent(ruleNodeInfo.getRuleNodeId().getId(), new TbRuleNodeProfilerInfo(ruleNodeInfo));
}
}
public void onProcessingEnd(UUID id, RuleNodeId ruleNodeId) {
if (profilerEnabled) {
long processingTime = msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onEnd(ruleNodeId);
if (processingTime > 0) {
ruleNodeProfilerMap.computeIfAbsent(ruleNodeId.getId(), TbRuleNodeProfilerInfo::new).record(processingTime);
}
}
}
public void onTimeout(TbMsgProfilerInfo profilerInfo) {
Map.Entry<UUID, Long> ruleNodeInfo = profilerInfo.onTimeout();
if (ruleNodeInfo != null) {
ruleNodeProfilerMap.computeIfAbsent(ruleNodeInfo.getKey(), TbRuleNodeProfilerInfo::new).record(ruleNodeInfo.getValue());
}
}
public RuleNodeInfo getLastVisitedRuleNode(UUID id) {
return lastRuleNodeMap.get(id);
}
public void printProfilerStats() {
if (profilerEnabled) {
log.debug("Top Rule Nodes by max execution time:");
ruleNodeProfilerMap.values().stream()
.sorted(Comparator.comparingLong(TbRuleNodeProfilerInfo::getMaxExecutionTime).reversed()).limit(5)
.forEach(info -> log.debug("[{}][{}] max execution time: {}. {}", queueName, info.getRuleNodeId(), info.getMaxExecutionTime(), info.getLabel()));
log.info("Top Rule Nodes by avg execution time:");
ruleNodeProfilerMap.values().stream()
.sorted(Comparator.comparingDouble(TbRuleNodeProfilerInfo::getAvgExecutionTime).reversed()).limit(5)
.forEach(info -> log.info("[{}][{}] avg execution time: {}. {}", queueName, info.getRuleNodeId(), info.getAvgExecutionTime(), info.getLabel()));
log.info("Top Rule Nodes by execution count:");
ruleNodeProfilerMap.values().stream()
.sorted(Comparator.comparingInt(TbRuleNodeProfilerInfo::getExecutionCount).reversed()).limit(5)
.forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel()));
}
}
}

85
application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import java.util.AbstractMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class TbMsgProfilerInfo {
private final UUID msgId;
private AtomicLong totalProcessingTime = new AtomicLong();
private Lock stateLock = new ReentrantLock();
private RuleNodeId currentRuleNodeId;
private long stateChangeTime;
public TbMsgProfilerInfo(UUID msgId) {
this.msgId = msgId;
}
public void onStart(RuleNodeId ruleNodeId) {
long currentTime = System.currentTimeMillis();
stateLock.lock();
try {
currentRuleNodeId = ruleNodeId;
stateChangeTime = currentTime;
} finally {
stateLock.unlock();
}
}
public long onEnd(RuleNodeId ruleNodeId) {
long currentTime = System.currentTimeMillis();
stateLock.lock();
try {
if (ruleNodeId.equals(currentRuleNodeId)) {
long processingTime = currentTime - stateChangeTime;
stateChangeTime = currentTime;
totalProcessingTime.addAndGet(processingTime);
currentRuleNodeId = null;
return processingTime;
} else {
log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId);
return 0;
}
} finally {
stateLock.unlock();
}
}
public Map.Entry<UUID, Long> onTimeout() {
long currentTime = System.currentTimeMillis();
stateLock.lock();
try {
if (currentRuleNodeId != null && stateChangeTime > 0) {
long timeoutTime = currentTime - stateChangeTime;
totalProcessingTime.addAndGet(timeoutTime);
return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime);
}
} finally {
stateLock.unlock();
}
return null;
}
}

75
application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
import lombok.Getter;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class TbRuleNodeProfilerInfo {
@Getter
private final UUID ruleNodeId;
@Getter
private final String label;
private AtomicInteger executionCount = new AtomicInteger(0);
private AtomicLong executionTime = new AtomicLong(0);
private AtomicLong maxExecutionTime = new AtomicLong(0);
public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) {
this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId();
this.label = ruleNodeInfo.toString();
}
public TbRuleNodeProfilerInfo(UUID ruleNodeId) {
this.ruleNodeId = ruleNodeId;
this.label = "";
}
public void record(long processingTime) {
executionCount.incrementAndGet();
executionTime.addAndGet(processingTime);
while (true) {
long value = maxExecutionTime.get();
if (value >= processingTime) {
break;
}
if (maxExecutionTime.compareAndSet(value, processingTime)) {
break;
}
}
}
int getExecutionCount() {
return executionCount.get();
}
long getMaxExecutionTime() {
return maxExecutionTime.get();
}
double getAvgExecutionTime() {
double executionCnt = (double) executionCount.get();
if (executionCnt > 0) {
return executionTime.get() / executionCnt;
} else {
return 0.0;
}
}
}

35
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -15,23 +15,31 @@
*/
package org.thingsboard.server.service.queue.processing;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbPackCallback;
import org.thingsboard.server.service.queue.TbPackProcessingContext;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -51,12 +59,15 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final ActorSystemContext actorContext;
protected final DataDecodingEncodingService encodingService;
protected final TbDeviceProfileCache deviceProfileCache;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbDeviceProfileCache deviceProfileCache, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
this.actorContext = actorContext;
this.encodingService = encodingService;
this.deviceProfileCache = deviceProfileCache;
this.nfConsumer = nfConsumer;
}
@ -126,18 +137,32 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
});
}
protected void handleComponentLifecycleMsg(UUID id, ByteString nfMsg) {
Optional<TbActorMsg> actorMsgOpt = encodingService.decode(nfMsg.toByteArray());
if (actorMsgOpt.isPresent()) {
TbActorMsg actorMsg = actorMsgOpt.get();
if (actorMsg instanceof ComponentLifecycleMsg) {
ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId()));
}
}
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);
actorContext.tellWithHighPriority(actorMsg);
}
}
protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception;
@PreDestroy
public void destroy() {
stopped = true;
stopMainConsumers();
if (nfConsumer != null) {
nfConsumer.unsubscribe();
}
if (consumersExecutor != null) {
consumersExecutor.shutdownNow();
}

4
application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java

@ -68,18 +68,20 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS
int listSize = orderedMsgList.size();
int startIdx = Math.min(packIdx.get() * batchSize, listSize);
int endIdx = Math.min(startIdx + batchSize, listSize);
Map<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> tmpPack;
synchronized (pendingPack) {
pendingPack.clear();
for (int i = startIdx; i < endIdx; i++) {
IdMsgPair pair = orderedMsgList.get(i);
pendingPack.put(pair.uuid, pair.msg);
}
tmpPack = new LinkedHashMap<>(pendingPack);
}
int submitSize = pendingPack.size();
if (log.isDebugEnabled() && submitSize > 0) {
log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize);
}
pendingPack.forEach(msgConsumer);
tmpPack.forEach(msgConsumer);
}
}

8
application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java

@ -56,7 +56,9 @@ public class TbRuleEngineProcessingStrategyFactory {
private final boolean retryTimeout;
private final int maxRetries;
private final double maxAllowedFailurePercentage;
private final long pauseBetweenRetries;
private final long maxPauseBetweenRetries;
private long pauseBetweenRetries;
private int initialTotalCount;
private int retryCount;
@ -69,6 +71,7 @@ public class TbRuleEngineProcessingStrategyFactory {
this.maxRetries = configuration.getRetries();
this.maxAllowedFailurePercentage = configuration.getFailurePercentage();
this.pauseBetweenRetries = configuration.getPauseBetweenRetries();
this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries();
}
@Override
@ -108,6 +111,9 @@ public class TbRuleEngineProcessingStrategyFactory {
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (maxPauseBetweenRetries > pauseBetweenRetries) {
pauseBetweenRetries = Math.min(maxPauseBetweenRetries, pauseBetweenRetries * 2);
}
}
return new TbRuleEngineProcessingDecision(false, toReprocess);
}

39
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -27,6 +27,7 @@ import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
@ -48,6 +50,7 @@ import org.thingsboard.server.controller.HttpValidationCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -72,6 +75,7 @@ import java.util.function.BiConsumer;
@Component
public class AccessValidator {
public static final String ONLY_SYSTEM_ADMINISTRATOR_IS_ALLOWED_TO_PERFORM_THIS_OPERATION = "Only system administrator is allowed to perform this operation!";
public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
@ -89,6 +93,9 @@ public class AccessValidator {
@Autowired
protected DeviceService deviceService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetService assetService;
@ -162,6 +169,9 @@ public class AccessValidator {
case DEVICE:
validateDevice(currentUser, operation, entityId, callback);
return;
case DEVICE_PROFILE:
validateDeviceProfile(currentUser, operation, entityId, callback);
return;
case ASSET:
validateAsset(currentUser, operation, entityId, callback);
return;
@ -174,6 +184,9 @@ public class AccessValidator {
case TENANT:
validateTenant(currentUser, operation, entityId, callback);
return;
case TENANT_PROFILE:
validateTenantProfile(currentUser, operation, entityId, callback);
return;
case USER:
validateUser(currentUser, operation, entityId, callback);
return;
@ -206,6 +219,24 @@ public class AccessValidator {
}
}
private void validateDeviceProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(currentUser.getTenantId(), new DeviceProfileId(entityId.getId()));
if (deviceProfile == null) {
callback.onSuccess(ValidationResult.entityNotFound("Device profile with requested id wasn't found!"));
} else {
try {
accessControlService.checkPermission(currentUser, Resource.DEVICE_PROFILE, operation, entityId, deviceProfile);
} catch (ThingsboardException e) {
callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
}
callback.onSuccess(ValidationResult.ok(deviceProfile));
}
}
}
private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
@ -313,6 +344,14 @@ public class AccessValidator {
}
}
private void validateTenantProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.ok(null));
} else {
callback.onSuccess(ValidationResult.accessDenied(ONLY_SYSTEM_ADMINISTRATOR_IS_ALLOWED_TO_PERFORM_THIS_OPERATION));
}
}
private void validateUser(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
ListenableFuture<User> userFuture = userService.findUserByIdAsync(currentUser.getTenantId(), new UserId(entityId.getId()));
Futures.addCallback(userFuture, getCallback(callback, user -> {

4
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -31,7 +31,9 @@ public enum Resource {
RULE_CHAIN(EntityType.RULE_CHAIN),
USER(EntityType.USER),
WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE),
WIDGET_TYPE(EntityType.WIDGET_TYPE);
WIDGET_TYPE(EntityType.WIDGET_TYPE),
TENANT_PROFILE(EntityType.TENANT_PROFILE),
DEVICE_PROFILE(EntityType.DEVICE_PROFILE);
private final EntityType entityType;

1
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -39,6 +39,7 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.USER, userPermissionChecker);
put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker);
put(Resource.WIDGET_TYPE, systemEntityPermissionChecker);
put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
}
private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.USER, userPermissionChecker);
put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
}
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {

7
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -226,6 +226,11 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
@Override
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) {
onAttributesUpdate(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
@ -254,7 +259,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
}
}
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope)) {
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
, null);

7
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -17,13 +17,12 @@ package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import java.util.List;
@ -37,9 +36,13 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback);
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
}

11
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -171,9 +171,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes));
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
}
@Override
@ -236,11 +241,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
, System.currentTimeMillis())), callback);
}
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, TbCallback.EMPTY);
subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}

186
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -21,26 +21,36 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
@ -74,44 +84,64 @@ public class DefaultTransportApiService implements TransportApiService {
private static final ObjectMapper mapper = new ObjectMapper();
//TODO: Constructor dependencies;
@Autowired
private TenantService tenantService;
private final DeviceProfileService deviceProfileService;
private final TenantService tenantService;
private final TenantProfileService tenantProfileService;
private final DeviceService deviceService;
private final RelationService relationService;
private final DeviceCredentialsService deviceCredentialsService;
private final DeviceStateService deviceStateService;
private final DbCallbackExecutorService dbCallbackExecutorService;
private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService;
@Autowired
private DeviceService deviceService;
@Autowired
private RelationService relationService;
@Autowired
private DeviceCredentialsService deviceCredentialsService;
@Autowired
private DeviceStateService deviceStateService;
@Autowired
private DbCallbackExecutorService dbCallbackExecutorService;
@Autowired
protected TbClusterService tbClusterService;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService,
TenantProfileService tenantProfileService, DeviceService deviceService,
RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService) {
this.deviceProfileService = deviceProfileService;
this.tenantService = tenantService;
this.tenantProfileService = tenantProfileService;
this.deviceService = deviceService;
this.relationService = relationService;
this.deviceCredentialsService = deviceCredentialsService;
this.deviceStateService = deviceStateService;
this.dbCallbackExecutorService = dbCallbackExecutorService;
this.tbClusterService = tbClusterService;
this.dataDecodingEncodingService = dataDecodingEncodingService;
}
@Override
public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue();
if (transportApiRequestMsg.hasValidateTokenRequestMsg()) {
ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg();
return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) {
TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg();
return Futures.transform(validateCredentials(msg),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg();
return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasGetTenantRoutingInfoRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
}
return Futures.transform(getEmptyTransportApiResponseFuture(), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
return Futures.transform(getEmptyTransportApiResponseFuture(),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
}
private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) {
@ -124,6 +154,62 @@ public class DefaultTransportApiService implements TransportApiService {
}
}
private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
if (credentials != null) {
if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
return getDeviceInfo(credentials.getDeviceId(), credentials);
} else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
if (!checkMqttCredentials(mqtt, credentials)) {
credentials = null;
}
}
}
if (credentials == null) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
if (credentials == null) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
}
}
if (credentials != null) {
return getDeviceInfo(credentials.getDeviceId(), credentials);
} else {
return getEmptyTransportApiResponseFuture();
}
}
private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId);
if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
if (!checkMqttCredentials(clientCred, deviceCredentials)) {
return null;
} else {
return deviceCredentials;
}
}
return null;
}
private boolean checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) {
BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class);
if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) {
return false;
}
if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) {
return false;
}
if (!StringUtils.isEmpty(dbCred.getPassword())) {
if (StringUtils.isEmpty(clientCred.getPassword())) {
return false;
} else {
if (!dbCred.getPassword().equals(clientCred.getPassword())) {
return false;
}
}
}
return true;
}
private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) {
DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB()));
ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId);
@ -139,6 +225,8 @@ public class DefaultTransportApiService implements TransportApiService {
device.setName(requestMsg.getDeviceName());
device.setType(requestMsg.getDeviceType());
device.setCustomerId(gateway.getCustomerId());
DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
device.setDeviceProfileId(deviceProfile.getId());
device = deviceService.saveDevice(device);
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
deviceStateService.onDeviceAdded(device);
@ -155,10 +243,19 @@ public class DefaultTransportApiService implements TransportApiService {
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, mapper.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null);
}
GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder()
.setDeviceInfo(getDeviceInfoProto(device));
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
if (deviceProfile != null) {
builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
} else {
log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId());
}
return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
.setGetOrCreateDeviceResponseMsg(builder.build())
.build();
} catch (JsonProcessingException e) {
log.warn("[{}][{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e);
log.warn("[{}] Failed to lookup device by gateway id and name: [{}]", gatewayId, requestMsg.getDeviceName(), e);
throw new RuntimeException(e);
} finally {
deviceCreationLock.unlock();
@ -168,10 +265,23 @@ public class DefaultTransportApiService implements TransportApiService {
private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(TenantId.SYS_TENANT_ID, tenantId);
return Futures.transform(tenantFuture, tenant -> TransportApiResponseMsg.newBuilder()
.setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenant.isIsolatedTbCore())
.setIsolatedTbRuleEngine(tenant.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService);
// TODO: Tenant Profile from cache
ListenableFuture<TenantProfile> tenantProfileFuture =
Futures.transform(tenantService.findTenantByIdAsync(TenantId.SYS_TENANT_ID, tenantId), tenant ->
tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()), dbCallbackExecutorService);
return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder()
.setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore())
.setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService);
}
private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetDeviceProfileRequestMsg requestMsg) {
DeviceProfileId profileId = new DeviceProfileId(new UUID(requestMsg.getProfileIdMSB(), requestMsg.getProfileIdLSB()));
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, profileId);
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder()
.setGetDeviceProfileResponseMsg(
TransportProtos.GetDeviceProfileResponseMsg.newBuilder()
.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)))
.build()).build());
}
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
@ -183,11 +293,17 @@ public class DefaultTransportApiService implements TransportApiService {
try {
ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder();
builder.setDeviceInfo(getDeviceInfoProto(device));
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
if (deviceProfile != null) {
builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
} else {
log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId());
}
if (!StringUtils.isEmpty(credentials.getCredentialsValue())) {
builder.setCredentialsBody(credentials.getCredentialsValue());
}
return TransportApiResponseMsg.newBuilder()
.setValidateTokenResponseMsg(builder.build()).build();
.setValidateCredResponseMsg(builder.build()).build();
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by id", deviceId, e);
return getEmptyTransportApiResponse();
@ -203,6 +319,8 @@ public class DefaultTransportApiService implements TransportApiService {
.setDeviceIdLSB(device.getId().getId().getLeastSignificantBits())
.setDeviceName(device.getName())
.setDeviceType(device.getType())
.setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits())
.setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo()))
.build();
}
@ -213,6 +331,6 @@ public class DefaultTransportApiService implements TransportApiService {
private TransportApiResponseMsg getEmptyTransportApiResponse() {
return TransportApiResponseMsg.newBuilder()
.setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build();
.setValidateCredResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build();
}
}

19
application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java

@ -38,19 +38,14 @@ public abstract class AbstractCleanUpService {
@Value("${spring.datasource.password}")
protected String dbPassword;
protected long executeQuery(Connection conn, String query) {
long removed = 0L;
try {
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(query);
getWarnings(statement);
protected long executeQuery(Connection conn, String query) throws SQLException {
try (Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(query)) {
if (log.isDebugEnabled()) {
getWarnings(statement);
}
resultSet.next();
removed = resultSet.getLong(1);
log.debug("Successfully executed query: {}", query);
} catch (SQLException e) {
log.debug("Failed to execute query: {} due to: {}", query, e.getMessage());
return resultSet.getLong(1);
}
return removed;
}
protected void getWarnings(Statement statement) throws SQLException {
@ -65,6 +60,6 @@ public abstract class AbstractCleanUpService {
}
}
protected abstract void doCleanUp(Connection connection);
protected abstract void doCleanUp(Connection connection) throws SQLException;
}

2
application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java

@ -52,7 +52,7 @@ public class EventsCleanUpService extends AbstractCleanUpService {
}
@Override
protected void doCleanUp(Connection connection) {
protected void doCleanUp(Connection connection) throws SQLException {
long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);");
log.info("Total events removed by TTL: [{}]", totalEventsRemoved);
}

11
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import java.sql.Connection;
import java.sql.SQLException;
@SqlTsDao
@PsqlDao
@ -34,10 +35,10 @@ public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpServi
private String partitionType;
@Override
protected void doCleanUp(Connection connection) {
long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);");
log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved);
long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);");
log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
protected void doCleanUp(Connection connection) throws SQLException {
long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);");
log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved);
long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);");
log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
}
}

5
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.sql.Connection;
import java.sql.SQLException;
@TimescaleDBTsDao
@Service
@ -28,8 +29,8 @@ import java.sql.Connection;
public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService {
@Override
protected void doCleanUp(Connection connection) {
protected void doCleanUp(Connection connection) throws SQLException {
long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);");
log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
}
}
}

2
application/src/main/resources/logback.xml

@ -29,6 +29,8 @@
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />-->
<!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
<!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>-->

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

@ -194,8 +194,21 @@ cassandra:
url: "${CASSANDRA_URL:127.0.0.1:9042}"
# Specify local datacenter name
local_datacenter: "${CASSANDRA_LOCAL_DATACENTER:datacenter1}"
# Enable/disable secure connection
ssl: "${CASSANDRA_USE_SSL:false}"
ssl:
# Enable/disable secure connection
enabled: "${CASSANDRA_USE_SSL:false}"
# Enable/disable validation of Cassandra server hostname
# If enabled, hostname of Cassandra server must match CN of server certificate
hostname_validation: "${CASSANDRA_SSL_HOSTNAME_VALIDATION:true}"
# Set trust store for client authentication of server (optional, uses trust store from default SSLContext if not set)
trust_store: "${CASSANDRA_SSL_TRUST_STORE:}"
trust_store_password: "${CASSANDRA_SSL_TRUST_STORE_PASSWORD:}"
# Set key store for server authentication of client (optional, uses key store from default SSLContext if not set)
# A key store is only needed if the Cassandra server requires client authentication
key_store: "${CASSANDRA_SSL_KEY_STORE:}"
key_store_password: "${CASSANDRA_SSL_KEY_STORE_PASSWORD:}"
# Comma separated list of cipher suites (optional, uses Java default cipher suites if not set)
cipher_suites: "${CASSANDRA_SSL_CIPHER_SUITES:}"
# Enable/disable JMX
jmx: "${CASSANDRA_USE_JMX:false}"
# Enable/disable metrics collection.
@ -271,6 +284,8 @@ sql:
batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert
remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
# Specify whether to log database queries and their parameters generated by entity query repository
@ -372,6 +387,12 @@ caffeine:
securitySettings:
timeToLiveInMinutes: 1440
maxSize: 0
tenantProfiles:
timeToLiveInMinutes: 1440
maxSize: 0
deviceProfiles:
timeToLiveInMinutes: 1440
maxSize: 0
redis:
# standalone or cluster
@ -418,6 +439,9 @@ updates:
# Enable/disable updates checking.
enabled: "${UPDATES_ENABLED:true}"
# spring freemarker configuration
spring.freemarker.checkTemplateLocation: "false"
# spring CORS configuration
spring.mvc.cors:
mappings:
@ -483,6 +507,7 @@ audit-log:
"rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
"alarm": "${AUDIT_LOG_MASK_ALARM:W}"
"entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
"device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
sink:
# Type of external sink. possible options: none, elasticsearch
type: "${AUDIT_LOG_SINK_TYPE:none}"
@ -583,6 +608,8 @@ transport:
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
# Type of the key store
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
# Local CoAP transport parameters
coap:
# Enable/disable coap transport protocol.
@ -608,6 +635,10 @@ swagger:
queue:
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
in_memory:
stats:
# For debug lvl
print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}"
kafka:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}"
@ -627,11 +658,11 @@ queue:
security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
other:
topic-properties:
rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}"
rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
aws_sqs:
use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
@ -739,6 +770,7 @@ queue:
retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries.
- name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
@ -754,6 +786,7 @@ queue:
retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
- name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
@ -769,6 +802,7 @@ queue:
retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
transport:
# For high priority notifications that require minimum latency and processing time
notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
@ -793,4 +827,4 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: '${METRICS_ENDPOINTS_EXPOSE:info}'

56
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.controller;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -59,8 +60,16 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageLink;
@ -217,6 +226,10 @@ public abstract class AbstractWebTest {
login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
}
protected void loginUser(String userName, String password) throws Exception {
login(userName, password);
}
private Tenant savedDifferentTenant;
protected void loginDifferentTenant() throws Exception {
@ -242,15 +255,27 @@ public abstract class AbstractWebTest {
protected User createUserAndLogin(User user, String password) throws Exception {
User savedUser = doPost("/api/user", user, User.class);
logout();
JsonNode activateRequest = getActivateRequest(password);
JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
validateAndSetJwtToken(tokenInfo, user.getEmail());
return savedUser;
}
protected User createUser(User user, String password) throws Exception {
User savedUser = doPost("/api/user", user, User.class);
JsonNode activateRequest = getActivateRequest(password);
ResultActions resultActions = doPost("/api/noauth/activate", activateRequest);
resultActions.andExpect(status().isOk());
return savedUser;
}
private JsonNode getActivateRequest(String password) throws Exception {
doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
.andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
JsonNode activateRequest = new ObjectMapper().createObjectNode()
return new ObjectMapper().createObjectNode()
.put("activateToken", TestMailService.currentActivateToken)
.put("password", password);
JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
validateAndSetJwtToken(tokenInfo, user.getEmail());
return savedUser;
}
protected void login(String username, String password) throws Exception {
@ -304,6 +329,23 @@ public abstract class AbstractWebTest {
}
}
protected DeviceProfile createDeviceProfile(String name) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(name);
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
deviceProfile.setDescription(name + " Test");
DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration();
deviceProfileData.setConfiguration(configuration);
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setDefault(false);
deviceProfile.setDefaultRuleChainId(null);
return deviceProfile;
}
protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception {
MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables);
setJwtToken(getRequest);
@ -416,6 +458,10 @@ public abstract class AbstractWebTest {
return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass);
}
protected <T> T doPostClaimAsync(String urlTemplate, Object content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
}
protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass);
}
@ -486,7 +532,7 @@ public abstract class AbstractWebTest {
return mapper.readerFor(type).readValue(content);
}
public class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> {
public class IdComparator<D extends HasId> implements Comparator<D> {
@Override
public int compare(D o1, D o2) {
return o1.getId().getId().compareTo(o2.getId().getId());

6
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -24,6 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -185,9 +186,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceWithEmptyType() throws Exception {
Device device = new Device();
device.setName("My device");
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device type should be specified")));
Device savedDevice = doPost("/api/device", device, Device.class);
Assert.assertEquals("default", savedDevice.getType());
}
@Test

309
application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java

@ -0,0 +1,309 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest {
private IdComparator<DeviceProfile> idComparator = new IdComparator<>();
private IdComparator<DeviceProfileInfo> deviceProfileInfoIdComparator = new IdComparator<>();
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveDeviceProfile() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertNotNull(savedDeviceProfile);
Assert.assertNotNull(savedDeviceProfile.getId());
Assert.assertTrue(savedDeviceProfile.getCreatedTime() > 0);
Assert.assertEquals(deviceProfile.getName(), savedDeviceProfile.getName());
Assert.assertEquals(deviceProfile.getDescription(), savedDeviceProfile.getDescription());
Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData());
Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault());
Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId());
savedDeviceProfile.setName("New device profile");
doPost("/api/deviceProfile", savedDeviceProfile, DeviceProfile.class);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName());
}
@Test
public void testFindDeviceProfileById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
Assert.assertNotNull(foundDeviceProfile);
Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
}
@Test
public void testFindDeviceProfileInfoById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class);
Assert.assertNotNull(foundDeviceProfileInfo);
Assert.assertEquals(savedDeviceProfile.getId(), foundDeviceProfileInfo.getId());
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfileInfo.getName());
Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType());
}
@Test
public void testFindDefaultDeviceProfileInfo() throws Exception {
DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class);
Assert.assertNotNull(foundDefaultDeviceProfileInfo);
Assert.assertNotNull(foundDefaultDeviceProfileInfo.getId());
Assert.assertNotNull(foundDefaultDeviceProfileInfo.getName());
Assert.assertNotNull(foundDefaultDeviceProfileInfo.getType());
Assert.assertEquals(DeviceProfileType.DEFAULT, foundDefaultDeviceProfileInfo.getType());
Assert.assertEquals("default", foundDefaultDeviceProfileInfo.getName());
}
@Test
public void testSetDefaultDeviceProfile() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class);
Assert.assertNotNull(defaultDeviceProfile);
DeviceProfileInfo foundDefaultDeviceProfile = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class);
Assert.assertNotNull(foundDefaultDeviceProfile);
Assert.assertEquals(savedDeviceProfile.getName(), foundDefaultDeviceProfile.getName());
Assert.assertEquals(savedDeviceProfile.getId(), foundDefaultDeviceProfile.getId());
Assert.assertEquals(savedDeviceProfile.getType(), foundDefaultDeviceProfile.getType());
}
@Test
public void testSaveDeviceProfileWithEmptyName() throws Exception {
DeviceProfile deviceProfile = new DeviceProfile();
doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device profile name should be specified")));
}
@Test
public void testSaveDeviceProfileWithSameName() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk());
DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile");
doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device profile with such name already exists")));
}
@Ignore
@Test
public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device device = new Device();
device.setName("Test device");
device.setType("default");
device.setDeviceProfileId(savedDeviceProfile.getId());
doPost("/api/device", device, Device.class);
//TODO uncomment once we have other device types;
//savedDeviceProfile.setType(DeviceProfileType.LWM2M);
doPost("/api/deviceProfile", savedDeviceProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't change device profile type because devices referenced it")));
}
@Test
public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device device = new Device();
device.setName("Test device");
device.setType("default");
device.setDeviceProfileId(savedDeviceProfile.getId());
doPost("/api/device", device, Device.class);
savedDeviceProfile.setTransportType(DeviceTransportType.MQTT);
doPost("/api/deviceProfile", savedDeviceProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't change device profile transport type because devices referenced it")));
}
@Test
public void testDeleteDeviceProfileWithExistingDevice() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device device = new Device();
device.setName("Test device");
device.setType("default");
device.setDeviceProfileId(savedDeviceProfile.getId());
Device savedDevice = doPost("/api/device", device, Device.class);
doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("The device profile referenced by the devices cannot be deleted")));
}
@Test
public void testDeleteDeviceProfile() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testFindDeviceProfiles() throws Exception {
List<DeviceProfile> deviceProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<DeviceProfile> pageData = doGetTypedWithPageLink("/api/deviceProfiles?",
new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
deviceProfiles.addAll(pageData.getData());
for (int i=0;i<28;i++) {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i);
deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class));
}
List<DeviceProfile> loadedDeviceProfiles = new ArrayList<>();
pageLink = new PageLink(17);
do {
pageData = doGetTypedWithPageLink("/api/deviceProfiles?",
new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
loadedDeviceProfiles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(deviceProfiles, idComparator);
Collections.sort(loadedDeviceProfiles, idComparator);
Assert.assertEquals(deviceProfiles, loadedDeviceProfiles);
for (DeviceProfile deviceProfile : loadedDeviceProfiles) {
if (!deviceProfile.isDefault()) {
doDelete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/deviceProfiles?",
new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@Test
public void testFindDeviceProfileInfos() throws Exception {
List<DeviceProfile> deviceProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<DeviceProfile> deviceProfilePageData = doGetTypedWithPageLink("/api/deviceProfiles?",
new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
Assert.assertFalse(deviceProfilePageData.hasNext());
Assert.assertEquals(1, deviceProfilePageData.getTotalElements());
deviceProfiles.addAll(deviceProfilePageData.getData());
for (int i=0;i<28;i++) {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i);
deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class));
}
List<DeviceProfileInfo> loadedDeviceProfileInfos = new ArrayList<>();
pageLink = new PageLink(17);
PageData<DeviceProfileInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/deviceProfileInfos?",
new TypeReference<PageData<DeviceProfileInfo>>(){}, pageLink);
loadedDeviceProfileInfos.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(deviceProfiles, idComparator);
Collections.sort(loadedDeviceProfileInfos, deviceProfileInfoIdComparator);
List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream().map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(),
deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);
for (DeviceProfile deviceProfile : deviceProfiles) {
if (!deviceProfile.isDefault()) {
doDelete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/deviceProfileInfos?",
new TypeReference<PageData<DeviceProfileInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
}

5
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -22,6 +22,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -424,7 +425,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
@ -466,7 +467,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);

77
application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java

@ -15,21 +15,21 @@
*/
package org.thingsboard.server.controller;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.junit.Assert;
import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseTenantControllerTest extends AbstractControllerTest {
@ -65,6 +65,19 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindTenantInfoById() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
TenantInfo foundTenant = doGet("/api/tenant/info/"+savedTenant.getId().getId().toString(), TenantInfo.class);
Assert.assertNotNull(foundTenant);
Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveTenantWithEmptyTitle() throws Exception {
@ -217,4 +230,48 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@Test
public void testFindTenantInfos() throws Exception {
loginSysAdmin();
List<TenantInfo> tenants = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantInfo> pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getData().size());
tenants.addAll(pageData.getData());
for (int i=0;i<56;i++) {
Tenant tenant = new Tenant();
tenant.setTitle("Tenant"+i);
tenants.add(new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default"));
}
List<TenantInfo> loadedTenants = new ArrayList<>();
pageLink = new PageLink(17);
do {
pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink);
loadedTenants.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(tenants, idComparator);
Collections.sort(loadedTenants, idComparator);
Assert.assertEquals(tenants, loadedTenants);
for (TenantInfo tenant : loadedTenants) {
if (!tenant.getTitle().equals(TEST_TENANT_NAME)) {
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getData().size());
}
}

294
application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java

@ -0,0 +1,294 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.TenantProfileData;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseTenantProfileControllerTest extends AbstractControllerTest {
private IdComparator<TenantProfile> idComparator = new IdComparator<>();
private IdComparator<EntityInfo> tenantProfileInfoIdComparator = new IdComparator<>();
@Autowired
private TenantProfileService tenantProfileService;
@After
@Override
public void teardown() throws Exception {
super.teardown();
tenantProfileService.deleteTenantProfiles(TenantId.SYS_TENANT_ID);
}
@Test
public void testSaveTenantProfile() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
Assert.assertNotNull(savedTenantProfile);
Assert.assertNotNull(savedTenantProfile.getId());
Assert.assertTrue(savedTenantProfile.getCreatedTime() > 0);
Assert.assertEquals(tenantProfile.getName(), savedTenantProfile.getName());
Assert.assertEquals(tenantProfile.getDescription(), savedTenantProfile.getDescription());
Assert.assertEquals(tenantProfile.getProfileData(), savedTenantProfile.getProfileData());
Assert.assertEquals(tenantProfile.isDefault(), savedTenantProfile.isDefault());
Assert.assertEquals(tenantProfile.isIsolatedTbCore(), savedTenantProfile.isIsolatedTbCore());
Assert.assertEquals(tenantProfile.isIsolatedTbRuleEngine(), savedTenantProfile.isIsolatedTbRuleEngine());
savedTenantProfile.setName("New tenant profile");
doPost("/api/tenantProfile", savedTenantProfile, TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName());
}
@Test
public void testFindTenantProfileById() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertNotNull(foundTenantProfile);
Assert.assertEquals(savedTenantProfile, foundTenantProfile);
}
@Test
public void testFindTenantProfileInfoById() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
EntityInfo foundTenantProfileInfo = doGet("/api/tenantProfileInfo/"+savedTenantProfile.getId().getId().toString(), EntityInfo.class);
Assert.assertNotNull(foundTenantProfileInfo);
Assert.assertEquals(savedTenantProfile.getId(), foundTenantProfileInfo.getId());
Assert.assertEquals(savedTenantProfile.getName(), foundTenantProfileInfo.getName());
}
@Test
public void testFindDefaultTenantProfileInfo() throws Exception {
loginSysAdmin();
EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
Assert.assertNotNull(foundDefaultTenantProfile);
Assert.assertEquals("Default", foundDefaultTenantProfile.getName());
}
@Test
public void testSetDefaultTenantProfile() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile 1");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString()+"/default", null, TenantProfile.class);
Assert.assertNotNull(defaultTenantProfile);
EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
Assert.assertNotNull(foundDefaultTenantProfile);
Assert.assertEquals(savedTenantProfile.getName(), foundDefaultTenantProfile.getName());
Assert.assertEquals(savedTenantProfile.getId(), foundDefaultTenantProfile.getId());
}
@Test
public void testSaveTenantProfileWithEmptyName() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = new TenantProfile();
doPost("/api/tenantProfile", tenantProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Tenant profile name should be specified")));
}
@Test
public void testSaveTenantProfileWithSameName() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
doPost("/api/tenantProfile", tenantProfile).andExpect(status().isOk());
TenantProfile tenantProfile2 = this.createTenantProfile("Tenant Profile");
doPost("/api/tenantProfile", tenantProfile2).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Tenant profile with such name already exists")));
}
@Test
public void testSaveSameTenantProfileWithDifferentIsolatedTbRuleEngine() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
savedTenantProfile.setIsolatedTbRuleEngine(true);
doPost("/api/tenantProfile", savedTenantProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't update isolatedTbRuleEngine property")));
}
@Test
public void testSaveSameTenantProfileWithDifferentIsolatedTbCore() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
savedTenantProfile.setIsolatedTbCore(true);
doPost("/api/tenantProfile", savedTenantProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't update isolatedTbCore property")));
}
@Test
public void testDeleteTenantProfileWithExistingTenant() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
Tenant tenant = new Tenant();
tenant.setTitle("My tenant with tenant profile");
tenant.setTenantProfileId(savedTenantProfile.getId());
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("The tenant profile referenced by the tenants cannot be deleted")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testDeleteTenantProfile() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testFindTenantProfiles() throws Exception {
loginSysAdmin();
List<TenantProfile> tenantProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantProfile> pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<PageData<TenantProfile>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
tenantProfiles.addAll(pageData.getData());
for (int i=0;i<28;i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
}
List<TenantProfile> loadedTenantProfiles = new ArrayList<>();
pageLink = new PageLink(17);
do {
pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<PageData<TenantProfile>>(){}, pageLink);
loadedTenantProfiles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(tenantProfiles, idComparator);
Collections.sort(loadedTenantProfiles, idComparator);
Assert.assertEquals(tenantProfiles, loadedTenantProfiles);
for (TenantProfile tenantProfile : loadedTenantProfiles) {
if (!tenantProfile.isDefault()) {
doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<PageData<TenantProfile>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@Test
public void testFindTenantProfileInfos() throws Exception {
loginSysAdmin();
List<TenantProfile> tenantProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantProfile> tenantProfilePageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<PageData<TenantProfile>>(){}, pageLink);
Assert.assertFalse(tenantProfilePageData.hasNext());
Assert.assertEquals(1, tenantProfilePageData.getTotalElements());
tenantProfiles.addAll(tenantProfilePageData.getData());
for (int i=0;i<28;i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
}
List<EntityInfo> loadedTenantProfileInfos = new ArrayList<>();
pageLink = new PageLink(17);
PageData<EntityInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
new TypeReference<PageData<EntityInfo>>(){}, pageLink);
loadedTenantProfileInfos.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(tenantProfiles, idComparator);
Collections.sort(loadedTenantProfileInfos, tenantProfileInfoIdComparator);
List<EntityInfo> tenantProfileInfos = tenantProfiles.stream().map(tenantProfile -> new EntityInfo(tenantProfile.getId(),
tenantProfile.getName())).collect(Collectors.toList());
Assert.assertEquals(tenantProfileInfos, loadedTenantProfileInfos);
for (TenantProfile tenantProfile : tenantProfiles) {
if (!tenantProfile.isDefault()) {
doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
new TypeReference<PageData<EntityInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
private TenantProfile createTenantProfile(String name) {
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName(name);
tenantProfile.setDescription(name + " Test");
tenantProfile.setProfileData(new TenantProfileData());
tenantProfile.setDefault(false);
tenantProfile.setIsolatedTbCore(false);
tenantProfile.setIsolatedTbRuleEngine(false);
return tenantProfile;
}
}

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

@ -34,7 +34,7 @@ public class ControllerSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");

23
application/src/test/java/org/thingsboard/server/controller/sql/DeviceProfileControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseDeviceProfileControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class DeviceProfileControllerSqlTest extends BaseDeviceProfileControllerTest {
}

23
application/src/test/java/org/thingsboard/server/controller/sql/TenantProfileControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseTenantProfileControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class TenantProfileControllerSqlTest extends BaseTenantProfileControllerTest {
}

246
application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java

@ -0,0 +1,246 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.junit.Assert;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest {
protected static final String MQTT_URL = "tcp://localhost:1883";
private static final AtomicInteger atomicInteger = new AtomicInteger(2);
protected Tenant savedTenant;
protected User tenantAdmin;
protected Device savedDevice;
protected String accessToken;
protected Device savedGateway;
protected String gatewayAccessToken;
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Device device = new Device();
device.setName(deviceName);
device.setType("default");
Device gateway = new Device();
gateway.setName(gatewayName);
gateway.setType("default");
ObjectNode additionalInfo = mapper.createObjectNode();
additionalInfo.put("gateway", true);
gateway.setAdditionalInfo(additionalInfo);
if (payloadType != null) {
DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic);
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
device.setType(savedDeviceProfile.getName());
device.setDeviceProfileId(savedDeviceProfile.getId());
gateway.setType(savedDeviceProfile.getName());
gateway.setDeviceProfileId(savedDeviceProfile.getId());
}
savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
savedGateway = doPost("/api/device", gateway, Device.class);
DeviceCredentials gatewayCredentials =
doGet("/api/device/" + savedGateway.getId().getId().toString() + "/credentials", DeviceCredentials.class);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
assertEquals(savedGateway.getId(), gatewayCredentials.getDeviceId());
gatewayAccessToken = gatewayCredentials.getCredentialsId();
assertNotNull(gatewayAccessToken);
}
protected void processAfterTest() throws Exception {
loginSysAdmin();
if (savedTenant != null) {
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
}
}
protected MqttAsyncClient getMqttAsyncClient(String accessToken) throws MqttException {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
return client;
}
protected void publishMqttMsg(MqttAsyncClient client, byte[] payload, String topic) throws MqttException {
MqttMessage message = new MqttMessage();
message.setPayload(payload);
client.publish(topic, message);
}
protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "3.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "4", TransportProtos.KeyValueType.LONG_V);
TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
keyValueProtos.add(strKeyValueProto);
keyValueProtos.add(boolKeyValueProto);
keyValueProtos.add(dblKeyValueProto);
keyValueProtos.add(longKeyValueProto);
keyValueProtos.add(jsonKeyValueProto);
return keyValueProtos;
}
protected TransportProtos.KeyValueProto getKeyValueProto(String key, String strValue, TransportProtos.KeyValueType type) {
TransportProtos.KeyValueProto.Builder keyValueProtoBuilder = TransportProtos.KeyValueProto.newBuilder();
keyValueProtoBuilder.setKey(key);
keyValueProtoBuilder.setType(type);
switch (type) {
case BOOLEAN_V:
keyValueProtoBuilder.setBoolV(Boolean.parseBoolean(strValue));
break;
case LONG_V:
keyValueProtoBuilder.setLongV(Long.parseLong(strValue));
break;
case DOUBLE_V:
keyValueProtoBuilder.setDoubleV(Double.parseDouble(strValue));
break;
case STRING_V:
keyValueProtoBuilder.setStringV(strValue);
break;
case JSON_V:
keyValueProtoBuilder.setJsonV(strValue);
break;
}
return keyValueProtoBuilder.build();
}
protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(transportPayloadType.name());
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.MQTT);
deviceProfile.setDescription(transportPayloadType.name() + " Test");
DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration();
transportConfiguration.setTransportPayloadType(transportPayloadType);
if (!StringUtils.isEmpty(telemetryTopic)) {
transportConfiguration.setDeviceTelemetryTopic(telemetryTopic);
}
if (!StringUtils.isEmpty(attributesTopic)) {
transportConfiguration.setDeviceAttributesTopic(attributesTopic);
}
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfileData.setConfiguration(configuration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setDefault(false);
deviceProfile.setDefaultRuleChainId(null);
return deviceProfile;
}
protected TransportProtos.PostAttributeMsg getPostAttributeMsg(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> kvProtos = getKvProtos(expectedKeys);
TransportProtos.PostAttributeMsg.Builder builder = TransportProtos.PostAttributeMsg.newBuilder();
builder.addAllKv(kvProtos);
return builder.build();
}
protected <T> T doExecuteWithRetriesAndInterval(SupplierWithThrowable<T> supplier, int retries, int intervalMs) throws Exception {
int count = 0;
T result = null;
Throwable lastException = null;
while (count < retries) {
try {
result = supplier.get();
if (result != null) {
return result;
}
} catch (Throwable e) {
lastException = e;
}
count++;
if (count < retries) {
Thread.sleep(intervalMs);
}
}
if (lastException != null) {
throw new RuntimeException(lastException);
} else {
return result;
}
}
@FunctionalInterface
public interface SupplierWithThrowable<T> {
T get() throws Throwable;
}
}

2
application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java

@ -33,7 +33,7 @@ public class MqttNoSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-entities-hsql.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"nosql-test.properties");

8
application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java

@ -27,13 +27,17 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.mqtt.rpc.sql.*Test",
"org.thingsboard.server.mqtt.telemetry.sql.*Test"
"org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test",
"org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
"org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
"org.thingsboard.server.mqtt.attributes.request.sql.*Test",
"org.thingsboard.server.mqtt.claim.sql.*Test"
})
public class MqttSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");

111
application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java

@ -0,0 +1,111 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@Slf4j
public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," +
"\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
}
protected void processAfterTest() throws Exception {
super.processAfterTest();
}
protected List<TransportProtos.TsKvProto> getTsKvProtoList() {
TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V);
TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V);
List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>();
tsKvProtoList.add(tsKvProtoAttribute1);
tsKvProtoList.add(tsKvProtoAttribute2);
tsKvProtoList.add(tsKvProtoAttribute3);
tsKvProtoList.add(tsKvProtoAttribute4);
tsKvProtoList.add(tsKvProtoAttribute5);
return tsKvProtoList;
}
protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) {
TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder();
TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType);
tsKvProtoBuilder.setKv(keyValueProto);
return tsKvProtoBuilder.build();
}
protected TestMqttCallback getTestMqttCallback() {
CountDownLatch latch = new CountDownLatch(1);
return new TestMqttCallback(latch);
}
protected static class TestMqttCallback implements MqttCallback {
private final CountDownLatch latch;
private Integer qoS;
private byte[] payloadBytes;
TestMqttCallback(CountDownLatch latch) {
this.latch = latch;
}
public int getQoS() {
return qoS;
}
public byte[] getPayloadBytes() {
return payloadBytes;
}
public CountDownLatch getLatch() {
return latch;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
}

154
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java

@ -0,0 +1,154 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttAttributesRequestIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server", "Gateway Test Request attribute values from the server", null, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer();
}
protected void processTestRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
postAttributesAndSubscribeToTopic(savedDevice, client);
Thread.sleep(1000);
TestMqttCallback callback = getTestMqttCallback();
client.setCallback(callback);
validateResponse(client, callback.getLatch(), callback);
}
protected void processTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
postGatewayDeviceClientAttributes(client);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
20,
100);
assertNotNull(savedDevice);
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
Thread.sleep(1000);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value());
TestMqttCallback clientAttributesCallback = getTestMqttCallback();
client.setCallback(clientAttributesCallback);
validateClientResponseGateway(client, clientAttributesCallback);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
client.setCallback(sharedAttributesCallback);
validateSharedResponseGateway(client, sharedAttributesCallback);
}
protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes()));
client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
}
protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes()));
}
protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
}

51
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server json", "Gateway Test Request attribute values from the server json", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer();
}
}

201
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java

@ -0,0 +1,201 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer();
}
protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
byte[] payload = postAttributeMsg.toByteArray();
client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload));
client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
}
protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
attributesMsgBuilder.setDeviceName("Gateway Device Request Attributes");
attributesMsgBuilder.setMsg(postAttributeMsg);
TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build();
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
gatewayAttributeMsgBuilder.addMsg(attributesMsg);
byte[] bytes = gatewayAttributeMsgBuilder.build().toByteArray();
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes));
}
protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
attributesRequestBuilder.setClientKeys(keys);
attributesRequestBuilder.setSharedKeys(keys);
TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(attributesRequest.toByteArray());
client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg();
TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(true);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
}
protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(false);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String keys, boolean client) {
return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder()
.setClient(client)
.addAllKeys(Arrays.asList(keys.split(",")))
.setDeviceName("Gateway Device Request Attributes")
.setId(1).build();
}
private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() {
TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
result.addAllClientAttributeList(tsKvProtoList);
result.addAllSharedAttributeList(tsKvProtoList);
result.setRequestId(1);
return result.build();
}
private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(boolean client) {
TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
if (client) {
getAttributeResponseMsgBuilder.addAllClientAttributeList(tsKvProtoList);
} else {
getAttributeResponseMsgBuilder.addAllSharedAttributeList(tsKvProtoList);
}
getAttributeResponseMsgBuilder.setRequestId(1);
TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build();
gatewayAttributeResponseMsg.setDeviceName("Gateway Device Request Attributes");
gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg);
return gatewayAttributeResponseMsg.build();
}
protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "42.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "73", TransportProtos.KeyValueType.LONG_V);
TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
keyValueProtos.add(strKeyValueProto);
keyValueProtos.add(boolKeyValueProto);
keyValueProtos.add(dblKeyValueProto);
keyValueProtos.add(longKeyValueProto);
keyValueProtos.add(jsonKeyValueProto);
return keyValueProtos;
}
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesRequestNoSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestJsonSqlIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestProtoSqlIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
}

171
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java

@ -0,0 +1,171 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest {
private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}";
private static String getResponseGatewayAttributesUpdatedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\"," +
"\"data\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
}
private static String getResponseGatewayAttributesDeletedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\",\"data\":{\"deleted\":[\"attribute5\"]}}";
}
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates();
}
protected void processTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateDeleteAttributesResponse(onDeleteCallback);
}
protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
}
protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
}
protected void processGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
Device device = new Device();
device.setName("Gateway Device Subscribe to attribute updates");
device.setType("default");
byte[] connectPayloadBytes = getConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class),
20,
100);
assertNotNull(savedDevice);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateGatewayUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateGatewayDeleteAttributesResponse(onDeleteCallback);
}
protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(getResponseGatewayAttributesUpdatedPayload(), s);
}
protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(s, getResponseGatewayAttributesDeletedPayload());
}
protected byte[] getConnectPayloadBytes() {
String connectPayload = "{\"device\": \"Gateway Device Subscribe to attribute updates\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
return connectPayload.getBytes();
}
}

51
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates();
}
}

149
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java

@ -0,0 +1,149 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates();
}
protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected byte[] getConnectPayloadBytes() {
TransportApiProtos.ConnectMsg connectProto = getConnectProto();
return connectProto.toByteArray();
}
private TransportApiProtos.ConnectMsg getConnectProto() {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName("Gateway Device Subscribe to attribute updates");
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesUpdatesNoSqlIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesSqlIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesSqlJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesSqlProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest {
}

206
application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java

@ -0,0 +1,206 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegrationTest {
protected static final String CUSTOMER_USER_PASSWORD = "customerUser123!";
protected User customerAdmin;
protected Customer savedCustomer;
@Before
public void beforeTest() throws Exception {
super.processBeforeTest("Test Claim device", "Test Claim gateway", null, null, null);
createCustomerAndUser();
}
protected void createCustomerAndUser() throws Exception {
Customer customer = new Customer();
customer.setTenantId(savedTenant.getId());
customer.setTitle("Test Claiming Customer");
savedCustomer = doPost("/api/customer", customer, Customer.class);
assertNotNull(savedCustomer);
assertEquals(savedTenant.getId(), savedCustomer.getTenantId());
User user = new User();
user.setAuthority(Authority.CUSTOMER_USER);
user.setTenantId(savedTenant.getId());
user.setCustomerId(savedCustomer.getId());
user.setEmail("customer@thingsboard.org");
customerAdmin = createUser(user, CUSTOMER_USER_PASSWORD);
assertNotNull(customerAdmin);
assertEquals(customerAdmin.getCustomerId(), savedCustomer.getId());
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testClaimingDevice() throws Exception {
processTestClaimingDevice(false);
}
@Test
public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestClaimingDevice(true);
}
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload", true);
}
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
byte[] payloadBytes;
byte[] failurePayloadBytes;
if (emptyPayload) {
payloadBytes = "{}".getBytes();
failurePayloadBytes = "{\"durationMs\":1}".getBytes();
} else {
payloadBytes = "{\"secretKey\":\"value\", \"durationMs\":60000}".getBytes();
failurePayloadBytes = "{\"secretKey\":\"value\", \"durationMs\":1}".getBytes();
}
validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
}
protected void validateClaimResponse(boolean emptyPayload, MqttAsyncClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception {
client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes));
loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
ClaimRequest claimRequest;
if (!emptyPayload) {
claimRequest = new ClaimRequest("value");
} else {
claimRequest = new ClaimRequest(null);
}
ClaimResponse claimResponse = doExecuteWithRetriesAndInterval(
() -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest()),
20,
100
);
assertEquals(claimResponse, ClaimResponse.FAILURE);
client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(payloadBytes));
ClaimResult claimResult = doExecuteWithRetriesAndInterval(
() -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResult.class, status().isOk()),
20,
100
);
assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS);
Device claimedDevice = claimResult.getDevice();
assertNotNull(claimedDevice);
assertNotNull(claimedDevice.getCustomerId());
assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId());
claimResponse = doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.CLAIMED);
}
protected void validateGatewayClaimResponse(String deviceName, boolean emptyPayload, MqttAsyncClient client, byte[] failurePayloadBytes, byte[] payloadBytes) throws Exception {
client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes));
Device savedDevice = doExecuteWithRetriesAndInterval(
() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
20,
100
);
assertNotNull(savedDevice);
loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
ClaimRequest claimRequest;
if (!emptyPayload) {
claimRequest = new ClaimRequest("value");
} else {
claimRequest = new ClaimRequest(null);
}
ClaimResponse claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.FAILURE);
client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(payloadBytes));
ClaimResult claimResult = doExecuteWithRetriesAndInterval(
() -> doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResult.class, status().isOk()),
20,
100
);
assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS);
Device claimedDevice = claimResult.getDevice();
assertNotNull(claimedDevice);
assertNotNull(claimedDevice.getCustomerId());
assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId());
claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.CLAIMED);
}
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
byte[] failurePayloadBytes;
byte[] payloadBytes;
String failurePayload;
String payload;
if (emptyPayload) {
failurePayload = "{\"" + deviceName + "\": " + "{\"durationMs\":1}" + "}";
payload = "{\"" + deviceName + "\": " + "{}" + "}";
} else {
failurePayload = "{\"" + deviceName + "\": " + "{\"secretKey\":\"value\", \"durationMs\":1}" + "}";
payload = "{\"" + deviceName + "\": " + "{\"secretKey\":\"value\", \"durationMs\":60000}" + "}";
}
payloadBytes = payload.getBytes();
failurePayloadBytes = failurePayload.getBytes();
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
}
}

58
application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java

@ -0,0 +1,58 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
@Slf4j
public abstract class AbstractMqttClaimJsonDeviceTest extends AbstractMqttClaimDeviceTest {
@Before
public void beforeTest() throws Exception {
super.processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.JSON, null, null);
createCustomerAndUser();
}
@After
public void afterTest() throws Exception {
super.afterTest();
}
@Test
public void testClaimingDevice() throws Exception {
processTestClaimingDevice(false);
}
@Test
public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestClaimingDevice(true);
}
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device Json", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload Json", true);
}
}

116
application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java

@ -0,0 +1,116 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.gen.transport.TransportApiProtos;
@Slf4j
public abstract class AbstractMqttClaimProtoDeviceTest extends AbstractMqttClaimDeviceTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.PROTOBUF, null, null);
createCustomerAndUser();
}
@After
public void afterTest() throws Exception { super.afterTest(); }
@Test
public void testClaimingDevice() throws Exception {
processTestClaimingDevice(false);
}
@Test
public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestClaimingDevice(true);
}
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device Proto", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload Proto", true);
}
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
byte[] payloadBytes;
if (emptyPayload) {
payloadBytes = getClaimDevice(0, emptyPayload).toByteArray();
} else {
payloadBytes = getClaimDevice(60000, emptyPayload).toByteArray();
}
byte[] failurePayloadBytes = getClaimDevice(1, emptyPayload).toByteArray();
validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
}
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
byte[] failurePayloadBytes;
byte[] payloadBytes;
if (emptyPayload) {
payloadBytes = getGatewayClaimMsg(deviceName, 0, emptyPayload).toByteArray();
} else {
payloadBytes = getGatewayClaimMsg(deviceName, 60000, emptyPayload).toByteArray();
}
failurePayloadBytes = getGatewayClaimMsg(deviceName, 1, emptyPayload).toByteArray();
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
}
private TransportApiProtos.GatewayClaimMsg getGatewayClaimMsg(String deviceName, long duration, boolean emptyPayload) {
TransportApiProtos.GatewayClaimMsg.Builder gatewayClaimMsgBuilder = TransportApiProtos.GatewayClaimMsg.newBuilder();
TransportApiProtos.ClaimDeviceMsg.Builder claimDeviceMsgBuilder = TransportApiProtos.ClaimDeviceMsg.newBuilder();
TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
if (!emptyPayload) {
claimDeviceBuilder.setSecretKey("value");
}
if (duration > 0) {
claimDeviceBuilder.setDurationMs(duration);
}
TransportApiProtos.ClaimDevice claimDevice = claimDeviceBuilder.build();
claimDeviceMsgBuilder.setClaimRequest(claimDevice);
claimDeviceMsgBuilder.setDeviceName(deviceName);
TransportApiProtos.ClaimDeviceMsg claimDeviceMsg = claimDeviceMsgBuilder.build();
gatewayClaimMsgBuilder.addMsg(claimDeviceMsg);
return gatewayClaimMsgBuilder.build();
}
private TransportApiProtos.ClaimDevice getClaimDevice(long duration, boolean emptyPayload) {
TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
if (!emptyPayload) {
claimDeviceBuilder.setSecretKey("value");
}
if (duration > 0) {
claimDeviceBuilder.setSecretKey("value");
claimDeviceBuilder.setDurationMs(duration);
}
return claimDeviceBuilder.build();
}
}

24
application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
@DaoNoSqlTest
public class MqttClaimDeviceNoSqlTest extends AbstractMqttClaimDeviceTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceJsonSqlTest extends AbstractMqttClaimJsonDeviceTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceProtoSqlTest extends AbstractMqttClaimProtoDeviceTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceSqlTest extends AbstractMqttClaimDeviceTest {
}

137
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java

@ -0,0 +1,137 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.rpc;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.InvalidProtocolBufferException;
import com.nimbusds.jose.util.StandardCharset;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.service.security.AccessValidator;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Valerii Sosliuk
*/
@Slf4j
public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", null, null, null);
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpcDeviceOffline() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
@Test
public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
}
@Test
public void testServerMqttTwoWayRpcDeviceOffline() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
@Test
public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
@Test
public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC");
}
@Test
public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC");
}
}

242
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -16,6 +16,10 @@
package org.thingsboard.server.mqtt.rpc;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.InvalidProtocolBufferException;
import com.nimbusds.jose.util.StandardCharset;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -23,15 +27,28 @@ import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.*;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.service.security.AccessValidator;
import java.util.Arrays;
@ -47,72 +64,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Valerii Sosliuk
*/
@Slf4j
public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractControllerTest {
public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest {
private static final String MQTT_URL = "tcp://localhost:1883";
private static final Long TIME_TO_HANDLE_REQUEST = 500L;
protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
private Tenant savedTenant;
private User tenantAdmin;
private Long asyncContextTimeoutToUseRpcPlugin;
private static final AtomicInteger atomicInteger = new AtomicInteger(2);
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
protected Long asyncContextTimeoutToUseRpcPlugin;
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
asyncContextTimeoutToUseRpcPlugin = 10000L;
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
if (savedTenant != null) {
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
}
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
Device device = new Device();
device.setName("Test One-Way Server-Side RPC");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
protected void processOneWayRpcTest() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
client.subscribe("v1/devices/me/rpc/request/+", MqttQoS.AT_MOST_ONCE.value());
client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(2000);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
@ -122,100 +94,112 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
@Test
public void testServerMqttOneWayRpcDeviceOffline() throws Exception {
Device device = new Device();
device.setName("Test One-Way Server-Side RPC Device Offline");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
protected void processTwoWayRpcTest() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, 1);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"value1\":\"A\",\"value2\":\"B\"}";
latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
@Test
public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes();
String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
validateTwoWayRpcGateway(deviceName, client, payloadBytes);
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
Device device = new Device();
device.setName("Test Two-Way Server-Side RPC");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
protected void validateOneWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
Device savedDevice = doExecuteWithRetriesAndInterval(
() -> getDeviceByName(deviceName),
20,
100
);
assertNotNull(savedDevice);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
client.subscribe("v1/devices/me/rpc/request/+", 1);
client.setCallback(new TestMqttCallback(client, new CountDownLatch(1)));
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
Thread.sleep(2000);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
Thread.sleep(1000);
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertEquals("{\"value1\":\"A\",\"value2\":\"B\"}", result);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertTrue(StringUtils.isEmpty(result));
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
@Test
public void testServerMqttTwoWayRpcDeviceOffline() throws Exception {
Device device = new Device();
device.setName("Test Two-Way Server-Side RPC Device Offline");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
protected void validateTwoWayRpcGateway(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
Device savedDevice = doExecuteWithRetriesAndInterval(
() -> getDeviceByName(deviceName),
20,
100
);
assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
@Test
public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
Thread.sleep(1000);
String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS);
String expected = "{\"success\":true}";
assertEquals(expected, result);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
private Device getSavedDevice(Device device) throws Exception {
return doPost("/api/device", device, Device.class);
private Device getDeviceByName(String deviceName) throws Exception {
return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
}
private DeviceCredentials getDeviceCredentials(Device savedDevice) throws Exception {
return doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
message.setPayload(DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8));
} else {
JsonNode requestMsgNode = JacksonUtil.toJsonNode(new String(mqttMessage.getPayload(), StandardCharset.UTF_8));
String deviceName = requestMsgNode.get("device").asText();
int requestId = requestMsgNode.get("data").get("id").asInt();
message.setPayload(("{\"device\": \"" + deviceName + "\", \"id\": " + requestId + ", \"data\": {\"success\": true}}").getBytes(StandardCharset.UTF_8));
}
return message;
}
private static class TestMqttCallback implements MqttCallback {
private class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
@ -237,11 +221,9 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
MqttMessage message = new MqttMessage();
String responseTopic = requestTopic.replace("request", "response");
message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes("UTF-8"));
qoS = mqttMessage.getQos();
client.publish(responseTopic, message);
client.publish(responseTopic, processMessageArrived(requestTopic, mqttMessage));
latch.countDown();
}

66
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java

@ -0,0 +1,66 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.rpc;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
@Slf4j
public abstract class AbstractMqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
@Test
public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC Json");
}
@Test
public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Json");
}
protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
}

114
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java

@ -0,0 +1,114 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.rpc;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Slf4j
public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null);
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
@Test
public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto");
}
@Test
public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto");
}
protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateTwoWayRpcGateway(deviceName, client, payloadBytes);
}
protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setPayload(DEVICE_RESPONSE)
.setRequestId(0)
.build();
message.setPayload(toDeviceRpcResponseMsg.toByteArray());
} else {
TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
String deviceName = msg.getDeviceName();
int requestId = msg.getRpcRequestMsg().getRequestId();
TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.newBuilder()
.setDeviceName(deviceName)
.setId(requestId)
.setData("{\"success\": true}")
.build();
message.setPayload(gatewayRpcResponseMsg.toByteArray());
}
return message;
}
}

4
application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java

@ -16,11 +16,11 @@
package org.thingsboard.server.mqtt.rpc.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest;
/**
* Created by Valerii Sosliuk on 8/22/2017.
*/
@DaoNoSqlTest
public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest;
@DaoSqlTest
public class MqttServerSideRpcJsonSqlIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcProtoIntegrationTest;
@DaoSqlTest
public class MqttServerSideRpcProtoSqlIntegrationTest extends AbstractMqttServerSideRpcProtoIntegrationTest {
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save